mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-19 00:04:22 +01:00
new: custom tabs (#889)
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import 'package:fl_lib/fl_lib.dart';
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hive_ce_flutter/adapters.dart';
|
||||||
import 'package:icons_plus/icons_plus.dart';
|
import 'package:icons_plus/icons_plus.dart';
|
||||||
import 'package:server_box/core/extension/context/locale.dart';
|
import 'package:server_box/core/extension/context/locale.dart';
|
||||||
import 'package:server_box/view/page/server/tab/tab.dart';
|
import 'package:server_box/view/page/server/tab/tab.dart';
|
||||||
@@ -8,10 +9,17 @@ import 'package:server_box/view/page/snippet/list.dart';
|
|||||||
import 'package:server_box/view/page/ssh/tab.dart';
|
import 'package:server_box/view/page/ssh/tab.dart';
|
||||||
import 'package:server_box/view/page/storage/local.dart';
|
import 'package:server_box/view/page/storage/local.dart';
|
||||||
|
|
||||||
|
part 'tab.g.dart';
|
||||||
|
|
||||||
|
@HiveType(typeId: 103)
|
||||||
enum AppTab {
|
enum AppTab {
|
||||||
|
@HiveField(0)
|
||||||
server,
|
server,
|
||||||
|
@HiveField(1)
|
||||||
ssh,
|
ssh,
|
||||||
|
@HiveField(2)
|
||||||
file,
|
file,
|
||||||
|
@HiveField(3)
|
||||||
snippet
|
snippet
|
||||||
//settings,
|
//settings,
|
||||||
;
|
;
|
||||||
@@ -93,4 +101,35 @@ enum AppTab {
|
|||||||
static List<NavigationRailDestination> get navRailDestinations {
|
static List<NavigationRailDestination> get navRailDestinations {
|
||||||
return AppTab.values.map((e) => e.navRailDestination).toList();
|
return AppTab.values.map((e) => e.navRailDestination).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Helper function to parse AppTab list from stored object
|
||||||
|
static List<AppTab> parseAppTabsFromObj(dynamic val) {
|
||||||
|
if (val is List) {
|
||||||
|
final tabs = <AppTab>[];
|
||||||
|
for (final e in val) {
|
||||||
|
final tab = _parseAppTabFromElement(e);
|
||||||
|
if (tab != null) {
|
||||||
|
tabs.add(tab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tabs.isNotEmpty) return tabs;
|
||||||
|
}
|
||||||
|
return AppTab.values;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to parse a single AppTab from various element types
|
||||||
|
static AppTab? _parseAppTabFromElement(dynamic e) {
|
||||||
|
if (e is AppTab) {
|
||||||
|
return e;
|
||||||
|
} else if (e is String) {
|
||||||
|
return AppTab.values.firstWhereOrNull((t) => t.name == e);
|
||||||
|
} else if (e is int) {
|
||||||
|
if (e >= 0 && e < AppTab.values.length) {
|
||||||
|
return AppTab.values[e];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
52
lib/data/model/app/tab.g.dart
Normal file
52
lib/data/model/app/tab.g.dart
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'tab.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// TypeAdapterGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
class AppTabAdapter extends TypeAdapter<AppTab> {
|
||||||
|
@override
|
||||||
|
final typeId = 103;
|
||||||
|
|
||||||
|
@override
|
||||||
|
AppTab read(BinaryReader reader) {
|
||||||
|
switch (reader.readByte()) {
|
||||||
|
case 0:
|
||||||
|
return AppTab.server;
|
||||||
|
case 1:
|
||||||
|
return AppTab.ssh;
|
||||||
|
case 2:
|
||||||
|
return AppTab.file;
|
||||||
|
case 3:
|
||||||
|
return AppTab.snippet;
|
||||||
|
default:
|
||||||
|
return AppTab.server;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void write(BinaryWriter writer, AppTab obj) {
|
||||||
|
switch (obj) {
|
||||||
|
case AppTab.server:
|
||||||
|
writer.writeByte(0);
|
||||||
|
case AppTab.ssh:
|
||||||
|
writer.writeByte(1);
|
||||||
|
case AppTab.file:
|
||||||
|
writer.writeByte(2);
|
||||||
|
case AppTab.snippet:
|
||||||
|
writer.writeByte(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => typeId.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
other is AppTabAdapter &&
|
||||||
|
runtimeType == other.runtimeType &&
|
||||||
|
typeId == other.typeId;
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import 'package:fl_lib/fl_lib.dart';
|
|||||||
import 'package:server_box/data/model/app/menu/server_func.dart';
|
import 'package:server_box/data/model/app/menu/server_func.dart';
|
||||||
import 'package:server_box/data/model/app/net_view.dart';
|
import 'package:server_box/data/model/app/net_view.dart';
|
||||||
import 'package:server_box/data/model/app/server_detail_card.dart';
|
import 'package:server_box/data/model/app/server_detail_card.dart';
|
||||||
|
import 'package:server_box/data/model/app/tab.dart';
|
||||||
import 'package:server_box/data/model/ssh/virtual_key.dart';
|
import 'package:server_box/data/model/ssh/virtual_key.dart';
|
||||||
import 'package:server_box/data/res/default.dart';
|
import 'package:server_box/data/res/default.dart';
|
||||||
|
|
||||||
@@ -22,10 +23,7 @@ class SettingStore extends HiveStore {
|
|||||||
// late final launchPage = property('launchPage', Defaults.launchPageIdx);
|
// late final launchPage = property('launchPage', Defaults.launchPageIdx);
|
||||||
|
|
||||||
/// Disk view: amount / IO
|
/// Disk view: amount / IO
|
||||||
late final serverTabPreferDiskAmount = propertyDefault(
|
late final serverTabPreferDiskAmount = propertyDefault('serverTabPreferDiskAmount', false);
|
||||||
'serverTabPreferDiskAmount',
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Bigger for bigger font size
|
/// Bigger for bigger font size
|
||||||
/// 1.0 means 100%
|
/// 1.0 means 100%
|
||||||
@@ -70,20 +68,14 @@ class SettingStore extends HiveStore {
|
|||||||
late final locale = propertyDefault('locale', '');
|
late final locale = propertyDefault('locale', '');
|
||||||
|
|
||||||
// SSH virtual key (ctrl | alt) auto turn off
|
// SSH virtual key (ctrl | alt) auto turn off
|
||||||
late final sshVirtualKeyAutoOff = propertyDefault(
|
late final sshVirtualKeyAutoOff = propertyDefault('sshVirtualKeyAutoOff', true);
|
||||||
'sshVirtualKeyAutoOff',
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
late final editorFontSize = propertyDefault('editorFontSize', 12.5);
|
late final editorFontSize = propertyDefault('editorFontSize', 12.5);
|
||||||
|
|
||||||
// Editor theme
|
// Editor theme
|
||||||
late final editorTheme = propertyDefault('editorTheme', Defaults.editorTheme);
|
late final editorTheme = propertyDefault('editorTheme', Defaults.editorTheme);
|
||||||
|
|
||||||
late final editorDarkTheme = propertyDefault(
|
late final editorDarkTheme = propertyDefault('editorDarkTheme', Defaults.editorDarkTheme);
|
||||||
'editorDarkTheme',
|
|
||||||
Defaults.editorDarkTheme,
|
|
||||||
);
|
|
||||||
|
|
||||||
late final fullScreen = propertyDefault('fullScreen', false);
|
late final fullScreen = propertyDefault('fullScreen', false);
|
||||||
|
|
||||||
@@ -113,29 +105,20 @@ class SettingStore extends HiveStore {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Only valid on iOS
|
// Only valid on iOS
|
||||||
late final autoUpdateHomeWidget = propertyDefault(
|
late final autoUpdateHomeWidget = propertyDefault('autoUpdateHomeWidget', isIOS);
|
||||||
'autoUpdateHomeWidget',
|
|
||||||
isIOS,
|
|
||||||
);
|
|
||||||
|
|
||||||
late final autoCheckAppUpdate = propertyDefault('autoCheckAppUpdate', true);
|
late final autoCheckAppUpdate = propertyDefault('autoCheckAppUpdate', true);
|
||||||
|
|
||||||
/// Display server tab function buttons on the bottom of each server card if [true]
|
/// Display server tab function buttons on the bottom of each server card if [true]
|
||||||
///
|
///
|
||||||
/// Otherwise, display them on the top of server detail page
|
/// Otherwise, display them on the top of server detail page
|
||||||
late final moveServerFuncs = propertyDefault(
|
late final moveServerFuncs = propertyDefault('moveOutServerTabFuncBtns', false);
|
||||||
'moveOutServerTabFuncBtns',
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Whether use `rm -r` to delete directory on SFTP
|
/// Whether use `rm -r` to delete directory on SFTP
|
||||||
late final sftpRmrDir = propertyDefault('sftpRmrDir', false);
|
late final sftpRmrDir = propertyDefault('sftpRmrDir', false);
|
||||||
|
|
||||||
/// Whether use system's primary color as the app's primary color
|
/// Whether use system's primary color as the app's primary color
|
||||||
late final useSystemPrimaryColor = propertyDefault(
|
late final useSystemPrimaryColor = propertyDefault('useSystemPrimaryColor', false);
|
||||||
'useSystemPrimaryColor',
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Only valid on iOS / Android / Windows
|
/// Only valid on iOS / Android / Windows
|
||||||
late final useBioAuth = propertyDefault('useBioAuth', false);
|
late final useBioAuth = propertyDefault('useBioAuth', false);
|
||||||
@@ -151,10 +134,7 @@ class SettingStore extends HiveStore {
|
|||||||
late final sftpOpenLastPath = propertyDefault('sftpOpenLastPath', true);
|
late final sftpOpenLastPath = propertyDefault('sftpOpenLastPath', true);
|
||||||
|
|
||||||
/// Show folders first in SFTP file browser
|
/// Show folders first in SFTP file browser
|
||||||
late final sftpShowFoldersFirst = propertyDefault(
|
late final sftpShowFoldersFirst = propertyDefault('sftpShowFoldersFirst', true);
|
||||||
'sftpShowFoldersFirst',
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Show tip of suspend
|
/// Show tip of suspend
|
||||||
late final showSuspendTip = propertyDefault('showSuspendTip', true);
|
late final showSuspendTip = propertyDefault('showSuspendTip', true);
|
||||||
@@ -162,10 +142,7 @@ class SettingStore extends HiveStore {
|
|||||||
/// Whether collapse UI items by default
|
/// Whether collapse UI items by default
|
||||||
late final collapseUIDefault = propertyDefault('collapseUIDefault', true);
|
late final collapseUIDefault = propertyDefault('collapseUIDefault', true);
|
||||||
|
|
||||||
late final serverFuncBtns = listProperty(
|
late final serverFuncBtns = listProperty('serverBtns', defaultValue: ServerFuncBtn.defaultIdxs);
|
||||||
'serverBtns',
|
|
||||||
defaultValue: ServerFuncBtn.defaultIdxs,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Docker is more popular than podman, set to `false` to use docker
|
/// Docker is more popular than podman, set to `false` to use docker
|
||||||
late final usePodman = propertyDefault('usePodman', false);
|
late final usePodman = propertyDefault('usePodman', false);
|
||||||
@@ -180,16 +157,10 @@ class SettingStore extends HiveStore {
|
|||||||
late final containerParseStat = propertyDefault('containerParseStat', true);
|
late final containerParseStat = propertyDefault('containerParseStat', true);
|
||||||
|
|
||||||
/// Auto refresh container status
|
/// Auto refresh container status
|
||||||
late final containerAutoRefresh = propertyDefault(
|
late final containerAutoRefresh = propertyDefault('containerAutoRefresh', true);
|
||||||
'containerAutoRefresh',
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Use double column servers page on Desktop
|
/// Use double column servers page on Desktop
|
||||||
late final doubleColumnServersPage = propertyDefault(
|
late final doubleColumnServersPage = propertyDefault('doubleColumnServersPage', true);
|
||||||
'doubleColumnServersPage',
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Ignore local network device (eg: br-xxx, ovs-system...)
|
/// Ignore local network device (eg: br-xxx, ovs-system...)
|
||||||
/// when building traffic view on server tab
|
/// when building traffic view on server tab
|
||||||
@@ -244,8 +215,7 @@ class SettingStore extends HiveStore {
|
|||||||
/// Record the position and size of the window.
|
/// Record the position and size of the window.
|
||||||
late final windowState = property<WindowState>(
|
late final windowState = property<WindowState>(
|
||||||
'windowState',
|
'windowState',
|
||||||
fromObj: (raw) =>
|
fromObj: (raw) => WindowState.fromJson(jsonDecode(raw as String) as Map<String, dynamic>),
|
||||||
WindowState.fromJson(jsonDecode(raw as String) as Map<String, dynamic>),
|
|
||||||
toObj: (state) => state == null ? null : jsonEncode(state.toJson()),
|
toObj: (state) => state == null ? null : jsonEncode(state.toJson()),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -258,10 +228,7 @@ class SettingStore extends HiveStore {
|
|||||||
late final sftpEditor = propertyDefault('sftpEditor', '');
|
late final sftpEditor = propertyDefault('sftpEditor', '');
|
||||||
|
|
||||||
/// Preferred terminal emulator command on desktop
|
/// Preferred terminal emulator command on desktop
|
||||||
late final desktopTerminal = propertyDefault(
|
late final desktopTerminal = propertyDefault('desktopTerminal', 'x-terminal-emulator');
|
||||||
'desktopTerminal',
|
|
||||||
'x-terminal-emulator',
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Run foreground service on Android, if the SSH terminal is running
|
/// Run foreground service on Android, if the SSH terminal is running
|
||||||
late final fgService = propertyDefault('fgService', false);
|
late final fgService = propertyDefault('fgService', false);
|
||||||
@@ -280,4 +247,14 @@ class SettingStore extends HiveStore {
|
|||||||
|
|
||||||
/// Whether to read SSH config from ~/.ssh/config on first time
|
/// Whether to read SSH config from ~/.ssh/config on first time
|
||||||
late final firstTimeReadSSHCfg = propertyDefault('firstTimeReadSSHCfg', true);
|
late final firstTimeReadSSHCfg = propertyDefault('firstTimeReadSSHCfg', true);
|
||||||
|
|
||||||
|
/// Tabs at home page
|
||||||
|
late final homeTabs = listProperty(
|
||||||
|
'homeTabs',
|
||||||
|
defaultValue: AppTab.values,
|
||||||
|
fromObj: AppTab.parseAppTabsFromObj,
|
||||||
|
toObj: (val) {
|
||||||
|
return val?.map((e) => e.name).toList() ?? [];
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1699,6 +1699,36 @@ abstract class AppLocalizations {
|
|||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Are you sure you want to clear connection statistics for server \"{serverName}\"? This action cannot be undone.'**
|
/// **'Are you sure you want to clear connection statistics for server \"{serverName}\"? This action cannot be undone.'**
|
||||||
String clearServerStatsContent(String serverName);
|
String clearServerStatsContent(String serverName);
|
||||||
|
|
||||||
|
/// No description provided for @homeTabs.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Home Tabs'**
|
||||||
|
String get homeTabs;
|
||||||
|
|
||||||
|
/// No description provided for @homeTabsCustomizeDesc.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Customize which tabs appear on the home page and their order'**
|
||||||
|
String get homeTabsCustomizeDesc;
|
||||||
|
|
||||||
|
/// No description provided for @reset.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Reset'**
|
||||||
|
String get reset;
|
||||||
|
|
||||||
|
/// No description provided for @availableTabs.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Available Tabs'**
|
||||||
|
String get availableTabs;
|
||||||
|
|
||||||
|
/// No description provided for @atLeastOneTab.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'At least one tab must be selected'**
|
||||||
|
String get atLeastOneTab;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppLocalizationsDelegate
|
class _AppLocalizationsDelegate
|
||||||
|
|||||||
@@ -895,4 +895,20 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
String clearServerStatsContent(String serverName) {
|
String clearServerStatsContent(String serverName) {
|
||||||
return 'Sind Sie sicher, dass Sie die Verbindungsstatistiken für Server \"$serverName\" löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.';
|
return 'Sind Sie sicher, dass Sie die Verbindungsstatistiken für Server \"$serverName\" löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabs => 'Home-Tabs';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabsCustomizeDesc =>
|
||||||
|
'Passen Sie an, welche Tabs auf der Startseite angezeigt werden und ihre Reihenfolge';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get reset => 'Zurücksetzen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get availableTabs => 'Verfügbare Tabs';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get atLeastOneTab => 'Mindestens ein Tab muss ausgewählt sein';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -887,4 +887,20 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
String clearServerStatsContent(String serverName) {
|
String clearServerStatsContent(String serverName) {
|
||||||
return 'Are you sure you want to clear connection statistics for server \"$serverName\"? This action cannot be undone.';
|
return 'Are you sure you want to clear connection statistics for server \"$serverName\"? This action cannot be undone.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabs => 'Home Tabs';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabsCustomizeDesc =>
|
||||||
|
'Customize which tabs appear on the home page and their order';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get reset => 'Reset';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get availableTabs => 'Available Tabs';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get atLeastOneTab => 'At least one tab must be selected';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -897,4 +897,20 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
String clearServerStatsContent(String serverName) {
|
String clearServerStatsContent(String serverName) {
|
||||||
return '¿Estás seguro de que quieres limpiar las estadísticas de conexión del servidor \"$serverName\"? Esta acción no se puede deshacer.';
|
return '¿Estás seguro de que quieres limpiar las estadísticas de conexión del servidor \"$serverName\"? Esta acción no se puede deshacer.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabs => 'Pestañas de inicio';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabsCustomizeDesc =>
|
||||||
|
'Personaliza qué pestañas aparecen en la página de inicio y su orden';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get reset => 'Restablecer';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get availableTabs => 'Pestañas disponibles';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get atLeastOneTab => 'Al menos una pestaña debe estar seleccionada';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -900,4 +900,20 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
String clearServerStatsContent(String serverName) {
|
String clearServerStatsContent(String serverName) {
|
||||||
return 'Êtes-vous sûr de vouloir effacer les statistiques de connexion du serveur \"$serverName\" ? Cette action ne peut pas être annulée.';
|
return 'Êtes-vous sûr de vouloir effacer les statistiques de connexion du serveur \"$serverName\" ? Cette action ne peut pas être annulée.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabs => 'Onglets d\'accueil';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabsCustomizeDesc =>
|
||||||
|
'Personnalisez les onglets qui apparaissent sur la page d\'accueil et leur ordre';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get reset => 'Réinitialiser';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get availableTabs => 'Onglets disponibles';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get atLeastOneTab => 'Au moins un onglet doit être sélectionné';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -887,4 +887,20 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
String clearServerStatsContent(String serverName) {
|
String clearServerStatsContent(String serverName) {
|
||||||
return 'Apakah Anda yakin ingin menghapus statistik koneksi untuk server \"$serverName\"? Tindakan ini tidak dapat dibatalkan.';
|
return 'Apakah Anda yakin ingin menghapus statistik koneksi untuk server \"$serverName\"? Tindakan ini tidak dapat dibatalkan.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabs => 'Tab Beranda';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabsCustomizeDesc =>
|
||||||
|
'Sesuaikan tab mana yang muncul di halaman beranda dan urutannya';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get reset => 'Reset';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get availableTabs => 'Tab Tersedia';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get atLeastOneTab => 'Setidaknya satu tab harus dipilih';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -861,4 +861,19 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
String clearServerStatsContent(String serverName) {
|
String clearServerStatsContent(String serverName) {
|
||||||
return 'サーバー\"$serverName\"の接続統計を削除してもよろしいですか?この操作は元に戻せません。';
|
return 'サーバー\"$serverName\"の接続統計を削除してもよろしいですか?この操作は元に戻せません。';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabs => 'ホームタブ';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabsCustomizeDesc => 'ホームページに表示するタブとその順序をカスタマイズします';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get reset => 'リセット';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get availableTabs => '利用可能なタブ';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get atLeastOneTab => '少なくとも1つのタブを選択する必要があります';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -893,4 +893,21 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
String clearServerStatsContent(String serverName) {
|
String clearServerStatsContent(String serverName) {
|
||||||
return 'Weet u zeker dat u de verbindingsstatistieken voor server \"$serverName\" wilt wissen? Deze actie kan niet ongedaan worden gemaakt.';
|
return 'Weet u zeker dat u de verbindingsstatistieken voor server \"$serverName\" wilt wissen? Deze actie kan niet ongedaan worden gemaakt.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabs => 'Home-tabbladen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabsCustomizeDesc =>
|
||||||
|
'Pas aan welke tabbladen op de startpagina worden weergegeven en hun volgorde';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get reset => 'Resetten';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get availableTabs => 'Beschikbare tabbladen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get atLeastOneTab =>
|
||||||
|
'Er moet minimaal één tabblad worden geselecteerd';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -890,4 +890,20 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
String clearServerStatsContent(String serverName) {
|
String clearServerStatsContent(String serverName) {
|
||||||
return 'Tem certeza de que deseja limpar as estatísticas de conexão para o servidor \"$serverName\"? Esta ação não pode ser desfeita.';
|
return 'Tem certeza de que deseja limpar as estatísticas de conexão para o servidor \"$serverName\"? Esta ação não pode ser desfeita.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabs => 'Abas iniciais';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabsCustomizeDesc =>
|
||||||
|
'Personalize quais abas aparecem na página inicial e sua ordem';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get reset => 'Redefinir';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get availableTabs => 'Abas disponíveis';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get atLeastOneTab => 'Pelo menos uma aba deve ser selecionada';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -892,4 +892,20 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
String clearServerStatsContent(String serverName) {
|
String clearServerStatsContent(String serverName) {
|
||||||
return 'Вы уверены, что хотите очистить статистику соединений для сервера \"$serverName\"? Это действие не может быть отменено.';
|
return 'Вы уверены, что хотите очистить статистику соединений для сервера \"$serverName\"? Это действие не может быть отменено.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabs => 'Вкладки дома';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabsCustomizeDesc =>
|
||||||
|
'Настройте, какие вкладки появляются на главной странице и их порядок';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get reset => 'Сброс';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get availableTabs => 'Доступные вкладки';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get atLeastOneTab => 'Должна быть выбрана хотя бы одна вкладка';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -887,4 +887,20 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
String clearServerStatsContent(String serverName) {
|
String clearServerStatsContent(String serverName) {
|
||||||
return '\"$serverName\" sunucusu için bağlantı istatistiklerini temizlemek istediğinizden emin misiniz? Bu işlem geri alınamaz.';
|
return '\"$serverName\" sunucusu için bağlantı istatistiklerini temizlemek istediğinizden emin misiniz? Bu işlem geri alınamaz.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabs => 'Ana Sayfa Sekmeleri';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabsCustomizeDesc =>
|
||||||
|
'Ana sayfada görünecek sekmeleri ve sıralarını özelleştirin';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get reset => 'Sıfırla';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get availableTabs => 'Mevcut Sekmeler';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get atLeastOneTab => 'En az bir sekme seçilmelidir';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -893,4 +893,20 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
String clearServerStatsContent(String serverName) {
|
String clearServerStatsContent(String serverName) {
|
||||||
return 'Ви впевнені, що хочете очистити статистику з\'єднань для сервера \"$serverName\"? Цю дію не можна скасувати.';
|
return 'Ви впевнені, що хочете очистити статистику з\'єднань для сервера \"$serverName\"? Цю дію не можна скасувати.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabs => 'Домашні вкладки';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabsCustomizeDesc =>
|
||||||
|
'Налаштуйте, які вкладки відображаються на головній сторінці та їх порядок';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get reset => 'Скинути';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get availableTabs => 'Доступні вкладки';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get atLeastOneTab => 'Потрібно вибрати принаймні одну вкладку';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -846,6 +846,21 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
String clearServerStatsContent(String serverName) {
|
String clearServerStatsContent(String serverName) {
|
||||||
return '确定要清空服务器 \"$serverName\" 的连接统计数据吗?此操作无法撤销。';
|
return '确定要清空服务器 \"$serverName\" 的连接统计数据吗?此操作无法撤销。';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabs => '主页标签';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabsCustomizeDesc => '自定义主页上显示的标签及其顺序';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get reset => '重置';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get availableTabs => '可用标签';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get atLeastOneTab => '至少需要选择一个标签';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The translations for Chinese, as used in Taiwan (`zh_TW`).
|
/// The translations for Chinese, as used in Taiwan (`zh_TW`).
|
||||||
@@ -1690,4 +1705,19 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
|
|||||||
String clearServerStatsContent(String serverName) {
|
String clearServerStatsContent(String serverName) {
|
||||||
return '確定要清空伺服器 \"$serverName\" 的連線統計資料嗎?此操作無法撤銷。';
|
return '確定要清空伺服器 \"$serverName\" 的連線統計資料嗎?此操作無法撤銷。';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabs => '主頁標籤';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabsCustomizeDesc => '自訂主頁上顯示的標籤及其順序';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get reset => '重置';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get availableTabs => '可用標籤';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get atLeastOneTab => '至少需要選擇一個標籤';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,13 @@
|
|||||||
// Check in to version control
|
// Check in to version control
|
||||||
|
|
||||||
import 'package:hive_ce/hive.dart';
|
import 'package:hive_ce/hive.dart';
|
||||||
|
import 'package:server_box/data/model/app/tab.dart';
|
||||||
import 'package:server_box/data/model/server/connection_stat.dart';
|
import 'package:server_box/data/model/server/connection_stat.dart';
|
||||||
import 'package:server_box/hive/hive_adapters.dart';
|
import 'package:server_box/hive/hive_adapters.dart';
|
||||||
|
|
||||||
extension HiveRegistrar on HiveInterface {
|
extension HiveRegistrar on HiveInterface {
|
||||||
void registerAdapters() {
|
void registerAdapters() {
|
||||||
|
registerAdapter(AppTabAdapter());
|
||||||
registerAdapter(ConnectionResultAdapter());
|
registerAdapter(ConnectionResultAdapter());
|
||||||
registerAdapter(ConnectionStatAdapter());
|
registerAdapter(ConnectionStatAdapter());
|
||||||
registerAdapter(NetViewTypeAdapter());
|
registerAdapter(NetViewTypeAdapter());
|
||||||
@@ -25,6 +27,7 @@ extension HiveRegistrar on HiveInterface {
|
|||||||
|
|
||||||
extension IsolatedHiveRegistrar on IsolatedHiveInterface {
|
extension IsolatedHiveRegistrar on IsolatedHiveInterface {
|
||||||
void registerAdapters() {
|
void registerAdapters() {
|
||||||
|
registerAdapter(AppTabAdapter());
|
||||||
registerAdapter(ConnectionResultAdapter());
|
registerAdapter(ConnectionResultAdapter());
|
||||||
registerAdapter(ConnectionStatAdapter());
|
registerAdapter(ConnectionStatAdapter());
|
||||||
registerAdapter(NetViewTypeAdapter());
|
registerAdapter(NetViewTypeAdapter());
|
||||||
|
|||||||
@@ -276,5 +276,10 @@
|
|||||||
"type": "String"
|
"type": "String"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"homeTabs": "Home-Tabs",
|
||||||
|
"homeTabsCustomizeDesc": "Passen Sie an, welche Tabs auf der Startseite angezeigt werden und ihre Reihenfolge",
|
||||||
|
"reset": "Zurücksetzen",
|
||||||
|
"availableTabs": "Verfügbare Tabs",
|
||||||
|
"atLeastOneTab": "Mindestens ein Tab muss ausgewählt sein"
|
||||||
}
|
}
|
||||||
@@ -276,5 +276,10 @@
|
|||||||
"type": "String"
|
"type": "String"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"homeTabs": "Home Tabs",
|
||||||
|
"homeTabsCustomizeDesc": "Customize which tabs appear on the home page and their order",
|
||||||
|
"reset": "Reset",
|
||||||
|
"availableTabs": "Available Tabs",
|
||||||
|
"atLeastOneTab": "At least one tab must be selected"
|
||||||
}
|
}
|
||||||
@@ -276,5 +276,10 @@
|
|||||||
"type": "String"
|
"type": "String"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"homeTabs": "Pestañas de inicio",
|
||||||
|
"homeTabsCustomizeDesc": "Personaliza qué pestañas aparecen en la página de inicio y su orden",
|
||||||
|
"reset": "Restablecer",
|
||||||
|
"availableTabs": "Pestañas disponibles",
|
||||||
|
"atLeastOneTab": "Al menos una pestaña debe estar seleccionada"
|
||||||
}
|
}
|
||||||
@@ -276,5 +276,10 @@
|
|||||||
"type": "String"
|
"type": "String"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"homeTabs": "Onglets d'accueil",
|
||||||
|
"homeTabsCustomizeDesc": "Personnalisez les onglets qui apparaissent sur la page d'accueil et leur ordre",
|
||||||
|
"reset": "Réinitialiser",
|
||||||
|
"availableTabs": "Onglets disponibles",
|
||||||
|
"atLeastOneTab": "Au moins un onglet doit être sélectionné"
|
||||||
}
|
}
|
||||||
@@ -276,5 +276,10 @@
|
|||||||
"type": "String"
|
"type": "String"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"homeTabs": "Tab Beranda",
|
||||||
|
"homeTabsCustomizeDesc": "Sesuaikan tab mana yang muncul di halaman beranda dan urutannya",
|
||||||
|
"reset": "Reset",
|
||||||
|
"availableTabs": "Tab Tersedia",
|
||||||
|
"atLeastOneTab": "Setidaknya satu tab harus dipilih"
|
||||||
}
|
}
|
||||||
@@ -276,5 +276,10 @@
|
|||||||
"type": "String"
|
"type": "String"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"homeTabs": "ホームタブ",
|
||||||
|
"homeTabsCustomizeDesc": "ホームページに表示するタブとその順序をカスタマイズします",
|
||||||
|
"reset": "リセット",
|
||||||
|
"availableTabs": "利用可能なタブ",
|
||||||
|
"atLeastOneTab": "少なくとも1つのタブを選択する必要があります"
|
||||||
}
|
}
|
||||||
@@ -276,5 +276,10 @@
|
|||||||
"type": "String"
|
"type": "String"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"homeTabs": "Home-tabbladen",
|
||||||
|
"homeTabsCustomizeDesc": "Pas aan welke tabbladen op de startpagina worden weergegeven en hun volgorde",
|
||||||
|
"reset": "Resetten",
|
||||||
|
"availableTabs": "Beschikbare tabbladen",
|
||||||
|
"atLeastOneTab": "Er moet minimaal één tabblad worden geselecteerd"
|
||||||
}
|
}
|
||||||
@@ -276,5 +276,10 @@
|
|||||||
"type": "String"
|
"type": "String"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"homeTabs": "Abas iniciais",
|
||||||
|
"homeTabsCustomizeDesc": "Personalize quais abas aparecem na página inicial e sua ordem",
|
||||||
|
"reset": "Redefinir",
|
||||||
|
"availableTabs": "Abas disponíveis",
|
||||||
|
"atLeastOneTab": "Pelo menos uma aba deve ser selecionada"
|
||||||
}
|
}
|
||||||
@@ -276,5 +276,10 @@
|
|||||||
"type": "String"
|
"type": "String"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"homeTabs": "Вкладки дома",
|
||||||
|
"homeTabsCustomizeDesc": "Настройте, какие вкладки появляются на главной странице и их порядок",
|
||||||
|
"reset": "Сброс",
|
||||||
|
"availableTabs": "Доступные вкладки",
|
||||||
|
"atLeastOneTab": "Должна быть выбрана хотя бы одна вкладка"
|
||||||
}
|
}
|
||||||
@@ -276,5 +276,10 @@
|
|||||||
"type": "String"
|
"type": "String"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"homeTabs": "Ana Sayfa Sekmeleri",
|
||||||
|
"homeTabsCustomizeDesc": "Ana sayfada görünecek sekmeleri ve sıralarını özelleştirin",
|
||||||
|
"reset": "Sıfırla",
|
||||||
|
"availableTabs": "Mevcut Sekmeler",
|
||||||
|
"atLeastOneTab": "En az bir sekme seçilmelidir"
|
||||||
}
|
}
|
||||||
@@ -276,5 +276,10 @@
|
|||||||
"type": "String"
|
"type": "String"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"homeTabs": "Домашні вкладки",
|
||||||
|
"homeTabsCustomizeDesc": "Налаштуйте, які вкладки відображаються на головній сторінці та їх порядок",
|
||||||
|
"reset": "Скинути",
|
||||||
|
"availableTabs": "Доступні вкладки",
|
||||||
|
"atLeastOneTab": "Потрібно вибрати принаймні одну вкладку"
|
||||||
}
|
}
|
||||||
@@ -276,5 +276,10 @@
|
|||||||
"type": "String"
|
"type": "String"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"homeTabs": "主页标签",
|
||||||
|
"homeTabsCustomizeDesc": "自定义主页上显示的标签及其顺序",
|
||||||
|
"reset": "重置",
|
||||||
|
"availableTabs": "可用标签",
|
||||||
|
"atLeastOneTab": "至少需要选择一个标签"
|
||||||
}
|
}
|
||||||
@@ -276,5 +276,10 @@
|
|||||||
"type": "String"
|
"type": "String"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"homeTabs": "主頁標籤",
|
||||||
|
"homeTabsCustomizeDesc": "自訂主頁上顯示的標籤及其順序",
|
||||||
|
"reset": "重置",
|
||||||
|
"availableTabs": "可用標籤",
|
||||||
|
"atLeastOneTab": "至少需要選擇一個標籤"
|
||||||
}
|
}
|
||||||
@@ -33,6 +33,7 @@ class _HomePageState extends ConsumerState<HomePage>
|
|||||||
|
|
||||||
late final _notifier = ref.read(serversNotifierProvider.notifier);
|
late final _notifier = ref.read(serversNotifierProvider.notifier);
|
||||||
late final _provider = ref.read(serversNotifierProvider);
|
late final _provider = ref.read(serversNotifierProvider);
|
||||||
|
late List<AppTab> _tabs = Stores.setting.homeTabs.fetch();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@@ -51,13 +52,30 @@ class _HomePageState extends ConsumerState<HomePage>
|
|||||||
SystemUIs.switchStatusBar(hide: false);
|
SystemUIs.switchStatusBar(hide: false);
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
// avoid index out of range
|
// avoid index out of range
|
||||||
if (_selectIndex.value >= AppTab.values.length || _selectIndex.value < 0) {
|
if (_selectIndex.value >= _tabs.length || _selectIndex.value < 0) {
|
||||||
_selectIndex.value = 0;
|
_selectIndex.value = 0;
|
||||||
}
|
}
|
||||||
_pageController = PageController(initialPage: _selectIndex.value);
|
_pageController = PageController(initialPage: _selectIndex.value);
|
||||||
if (Stores.setting.generalWakeLock.fetch()) {
|
if (Stores.setting.generalWakeLock.fetch()) {
|
||||||
WakelockPlus.enable();
|
WakelockPlus.enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Listen to homeTabs changes
|
||||||
|
Stores.setting.homeTabs.listenable().addListener(() {
|
||||||
|
final newTabs = Stores.setting.homeTabs.fetch();
|
||||||
|
if (mounted && newTabs != _tabs) {
|
||||||
|
setState(() {
|
||||||
|
_tabs = newTabs;
|
||||||
|
// Ensure current page index is valid
|
||||||
|
if (_selectIndex.value >= _tabs.length) {
|
||||||
|
_selectIndex.value = _tabs.length - 1;
|
||||||
|
}
|
||||||
|
if (_selectIndex.value < 0 && _tabs.isNotEmpty) {
|
||||||
|
_selectIndex.value = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -119,9 +137,9 @@ class _HomePageState extends ConsumerState<HomePage>
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: PageView.builder(
|
child: PageView.builder(
|
||||||
controller: _pageController,
|
controller: _pageController,
|
||||||
itemCount: AppTab.values.length,
|
itemCount: _tabs.length,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
itemBuilder: (_, index) => AppTab.values[index].page,
|
itemBuilder: (_, index) => _tabs[index].page,
|
||||||
onPageChanged: (value) {
|
onPageChanged: (value) {
|
||||||
FocusScope.of(context).unfocus();
|
FocusScope.of(context).unfocus();
|
||||||
if (!_switchingPage) {
|
if (!_switchingPage) {
|
||||||
@@ -146,7 +164,7 @@ class _HomePageState extends ConsumerState<HomePage>
|
|||||||
animationDuration: const Duration(milliseconds: 250),
|
animationDuration: const Duration(milliseconds: 250),
|
||||||
onDestinationSelected: _onDestinationSelected,
|
onDestinationSelected: _onDestinationSelected,
|
||||||
labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected,
|
labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected,
|
||||||
destinations: AppTab.navDestinations,
|
destinations: _tabs.map((tab) => tab.navDestination).toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -165,7 +183,7 @@ class _HomePageState extends ConsumerState<HomePage>
|
|||||||
trailing: extended ? const SizedBox(height: 20) : null,
|
trailing: extended ? const SizedBox(height: 20) : null,
|
||||||
labelType: extended ? NavigationRailLabelType.none : NavigationRailLabelType.all,
|
labelType: extended ? NavigationRailLabelType.none : NavigationRailLabelType.all,
|
||||||
selectedIndex: idx,
|
selectedIndex: idx,
|
||||||
destinations: AppTab.navRailDestinations,
|
destinations: _tabs.map((tab) => tab.navRailDestination).toList(),
|
||||||
onDestinationSelected: _onDestinationSelected,
|
onDestinationSelected: _onDestinationSelected,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -236,6 +254,7 @@ class _HomePageState extends ConsumerState<HomePage>
|
|||||||
|
|
||||||
void _onDestinationSelected(int index) {
|
void _onDestinationSelected(int index) {
|
||||||
if (_selectIndex.value == index) return;
|
if (_selectIndex.value == index) return;
|
||||||
|
if (index < 0 || index >= _tabs.length) return;
|
||||||
_selectIndex.value = index;
|
_selectIndex.value = index;
|
||||||
_switchingPage = true;
|
_switchingPage = true;
|
||||||
_pageController.animateToPage(
|
_pageController.animateToPage(
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ extension _App on _AppSettingsPageState {
|
|||||||
_buildThemeMode(),
|
_buildThemeMode(),
|
||||||
_buildAppColor(),
|
_buildAppColor(),
|
||||||
_buildCheckUpdate(),
|
_buildCheckUpdate(),
|
||||||
|
_buildHomeTabs(),
|
||||||
PlatformPublicSettings.buildBioAuth,
|
PlatformPublicSettings.buildBioAuth,
|
||||||
if (specific != null) specific,
|
if (specific != null) specific,
|
||||||
_buildAppMore(),
|
_buildAppMore(),
|
||||||
@@ -274,4 +275,15 @@ extension _App on _AppSettingsPageState {
|
|||||||
trailing: StoreSwitch(prop: _setting.hideTitleBar),
|
trailing: StoreSwitch(prop: _setting.hideTitleBar),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildHomeTabs() {
|
||||||
|
return ListTile(
|
||||||
|
leading: const Icon(Icons.tab),
|
||||||
|
title: Text(l10n.homeTabs),
|
||||||
|
trailing: const Icon(Icons.keyboard_arrow_right),
|
||||||
|
onTap: () {
|
||||||
|
HomeTabsConfigPage.route.go(context);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
130
lib/view/page/setting/entries/home_tabs.dart
Normal file
130
lib/view/page/setting/entries/home_tabs.dart
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:server_box/core/extension/context/locale.dart';
|
||||||
|
import 'package:server_box/data/model/app/tab.dart';
|
||||||
|
import 'package:server_box/data/res/store.dart';
|
||||||
|
|
||||||
|
class HomeTabsConfigPage extends ConsumerStatefulWidget {
|
||||||
|
const HomeTabsConfigPage({super.key});
|
||||||
|
|
||||||
|
static final route = AppRouteNoArg(page: HomeTabsConfigPage.new, path: '/settings/home-tabs');
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<HomeTabsConfigPage> createState() => _HomeTabsConfigPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HomeTabsConfigPageState extends ConsumerState<HomeTabsConfigPage> {
|
||||||
|
final _availableTabs = AppTab.values;
|
||||||
|
var _selectedTabs = List<AppTab>.from(Stores.setting.homeTabs.fetch());
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: CustomAppBar(
|
||||||
|
title: Text(l10n.homeTabs),
|
||||||
|
actions: [
|
||||||
|
TextButton(onPressed: _resetToDefault, child: Text(libL10n.reset)),
|
||||||
|
TextButton(onPressed: _saveAndExit, child: Text(libL10n.save)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Text(l10n.homeTabsCustomizeDesc, style: context.theme.textTheme.bodyMedium),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ReorderableListView.builder(
|
||||||
|
itemCount: _selectedTabs.length,
|
||||||
|
onReorder: _onReorder,
|
||||||
|
buildDefaultDragHandles: false,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final tab = _selectedTabs[index];
|
||||||
|
return _buildTabItem(tab, index, true);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Text(l10n.availableTabs, style: context.theme.textTheme.titleMedium),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: _availableTabs.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final tab = _availableTabs[index];
|
||||||
|
if (_selectedTabs.contains(tab)) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
return _buildTabItem(tab, index, false);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTabItem(AppTab tab, int index, bool isSelected) {
|
||||||
|
final canRemove = _selectedTabs.length > 1;
|
||||||
|
final child = ListTile(
|
||||||
|
leading: tab.navDestination.icon,
|
||||||
|
title: Text(tab.navDestination.label),
|
||||||
|
trailing: isSelected
|
||||||
|
? IconButton(
|
||||||
|
icon: const Icon(Icons.delete),
|
||||||
|
onPressed: canRemove ? () => _removeTab(tab) : null,
|
||||||
|
color: canRemove ? null : Theme.of(context).disabledColor,
|
||||||
|
tooltip: canRemove ? libL10n.delete : l10n.atLeastOneTab,
|
||||||
|
)
|
||||||
|
: IconButton(icon: const Icon(Icons.add), onPressed: () => _addTab(tab)),
|
||||||
|
onTap: isSelected && canRemove ? () => _removeTab(tab) : null,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
key: ValueKey(tab.name),
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||||
|
child: isSelected ? ReorderableDragStartListener(index: index, child: child) : child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onReorder(int oldIndex, int newIndex) {
|
||||||
|
setState(() {
|
||||||
|
if (newIndex > oldIndex) {
|
||||||
|
newIndex -= 1;
|
||||||
|
}
|
||||||
|
final tab = _selectedTabs.removeAt(oldIndex);
|
||||||
|
_selectedTabs.insert(newIndex, tab);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _addTab(AppTab tab) {
|
||||||
|
setState(() {
|
||||||
|
_selectedTabs.add(tab);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _removeTab(AppTab tab) {
|
||||||
|
if (_selectedTabs.length <= 1) {
|
||||||
|
context.showSnackBar(l10n.atLeastOneTab);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_selectedTabs.remove(tab);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _saveAndExit() {
|
||||||
|
Stores.setting.homeTabs.put(_selectedTabs);
|
||||||
|
context.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _resetToDefault() {
|
||||||
|
setState(() {
|
||||||
|
_selectedTabs = List<AppTab>.from(AppTab.values);
|
||||||
|
});
|
||||||
|
Stores.setting.homeTabs.put(_selectedTabs);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ import 'package:server_box/generated/l10n/l10n.dart';
|
|||||||
import 'package:server_box/view/page/backup.dart';
|
import 'package:server_box/view/page/backup.dart';
|
||||||
import 'package:server_box/view/page/private_key/list.dart';
|
import 'package:server_box/view/page/private_key/list.dart';
|
||||||
import 'package:server_box/view/page/server/connection_stats.dart';
|
import 'package:server_box/view/page/server/connection_stats.dart';
|
||||||
|
import 'package:server_box/view/page/setting/entries/home_tabs.dart';
|
||||||
import 'package:server_box/view/page/setting/platform/android.dart';
|
import 'package:server_box/view/page/setting/platform/android.dart';
|
||||||
import 'package:server_box/view/page/setting/platform/ios.dart';
|
import 'package:server_box/view/page/setting/platform/ios.dart';
|
||||||
import 'package:server_box/view/page/setting/platform/platform_pub.dart';
|
import 'package:server_box/view/page/setting/platform/platform_pub.dart';
|
||||||
|
|||||||
@@ -497,8 +497,8 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: "v1.0.345"
|
ref: "v1.0.346"
|
||||||
resolved-ref: "1b797643ef7603dd825caf96a6c57b88dbd23c34"
|
resolved-ref: f277b7a4259e45889320ef6d80ab320662558784
|
||||||
url: "https://github.com/lppcg/fl_lib"
|
url: "https://github.com/lppcg/fl_lib"
|
||||||
source: git
|
source: git
|
||||||
version: "0.0.1"
|
version: "0.0.1"
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ dependencies:
|
|||||||
fl_lib:
|
fl_lib:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/lppcg/fl_lib
|
url: https://github.com/lppcg/fl_lib
|
||||||
ref: v1.0.345
|
ref: v1.0.346
|
||||||
flutter_gbk2utf8: ^1.0.1
|
flutter_gbk2utf8: ^1.0.1
|
||||||
get_it: ^8.2.0
|
get_it: ^8.2.0
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user