mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-18 07:44:26 +01:00
new: custom tabs (#889)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive_ce_flutter/adapters.dart';
|
||||
import 'package:icons_plus/icons_plus.dart';
|
||||
import 'package:server_box/core/extension/context/locale.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/storage/local.dart';
|
||||
|
||||
part 'tab.g.dart';
|
||||
|
||||
@HiveType(typeId: 103)
|
||||
enum AppTab {
|
||||
@HiveField(0)
|
||||
server,
|
||||
@HiveField(1)
|
||||
ssh,
|
||||
@HiveField(2)
|
||||
file,
|
||||
@HiveField(3)
|
||||
snippet
|
||||
//settings,
|
||||
;
|
||||
@@ -93,4 +101,35 @@ enum AppTab {
|
||||
static List<NavigationRailDestination> get navRailDestinations {
|
||||
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/net_view.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/res/default.dart';
|
||||
|
||||
@@ -22,10 +23,7 @@ class SettingStore extends HiveStore {
|
||||
// late final launchPage = property('launchPage', Defaults.launchPageIdx);
|
||||
|
||||
/// Disk view: amount / IO
|
||||
late final serverTabPreferDiskAmount = propertyDefault(
|
||||
'serverTabPreferDiskAmount',
|
||||
false,
|
||||
);
|
||||
late final serverTabPreferDiskAmount = propertyDefault('serverTabPreferDiskAmount', false);
|
||||
|
||||
/// Bigger for bigger font size
|
||||
/// 1.0 means 100%
|
||||
@@ -70,20 +68,14 @@ class SettingStore extends HiveStore {
|
||||
late final locale = propertyDefault('locale', '');
|
||||
|
||||
// SSH virtual key (ctrl | alt) auto turn off
|
||||
late final sshVirtualKeyAutoOff = propertyDefault(
|
||||
'sshVirtualKeyAutoOff',
|
||||
true,
|
||||
);
|
||||
late final sshVirtualKeyAutoOff = propertyDefault('sshVirtualKeyAutoOff', true);
|
||||
|
||||
late final editorFontSize = propertyDefault('editorFontSize', 12.5);
|
||||
|
||||
// Editor theme
|
||||
late final editorTheme = propertyDefault('editorTheme', Defaults.editorTheme);
|
||||
|
||||
late final editorDarkTheme = propertyDefault(
|
||||
'editorDarkTheme',
|
||||
Defaults.editorDarkTheme,
|
||||
);
|
||||
late final editorDarkTheme = propertyDefault('editorDarkTheme', Defaults.editorDarkTheme);
|
||||
|
||||
late final fullScreen = propertyDefault('fullScreen', false);
|
||||
|
||||
@@ -113,29 +105,20 @@ class SettingStore extends HiveStore {
|
||||
);
|
||||
|
||||
// Only valid on iOS
|
||||
late final autoUpdateHomeWidget = propertyDefault(
|
||||
'autoUpdateHomeWidget',
|
||||
isIOS,
|
||||
);
|
||||
late final autoUpdateHomeWidget = propertyDefault('autoUpdateHomeWidget', isIOS);
|
||||
|
||||
late final autoCheckAppUpdate = propertyDefault('autoCheckAppUpdate', 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
|
||||
late final moveServerFuncs = propertyDefault(
|
||||
'moveOutServerTabFuncBtns',
|
||||
false,
|
||||
);
|
||||
late final moveServerFuncs = propertyDefault('moveOutServerTabFuncBtns', false);
|
||||
|
||||
/// Whether use `rm -r` to delete directory on SFTP
|
||||
late final sftpRmrDir = propertyDefault('sftpRmrDir', false);
|
||||
|
||||
/// Whether use system's primary color as the app's primary color
|
||||
late final useSystemPrimaryColor = propertyDefault(
|
||||
'useSystemPrimaryColor',
|
||||
false,
|
||||
);
|
||||
late final useSystemPrimaryColor = propertyDefault('useSystemPrimaryColor', false);
|
||||
|
||||
/// Only valid on iOS / Android / Windows
|
||||
late final useBioAuth = propertyDefault('useBioAuth', false);
|
||||
@@ -151,10 +134,7 @@ class SettingStore extends HiveStore {
|
||||
late final sftpOpenLastPath = propertyDefault('sftpOpenLastPath', true);
|
||||
|
||||
/// Show folders first in SFTP file browser
|
||||
late final sftpShowFoldersFirst = propertyDefault(
|
||||
'sftpShowFoldersFirst',
|
||||
true,
|
||||
);
|
||||
late final sftpShowFoldersFirst = propertyDefault('sftpShowFoldersFirst', true);
|
||||
|
||||
/// Show tip of suspend
|
||||
late final showSuspendTip = propertyDefault('showSuspendTip', true);
|
||||
@@ -162,10 +142,7 @@ class SettingStore extends HiveStore {
|
||||
/// Whether collapse UI items by default
|
||||
late final collapseUIDefault = propertyDefault('collapseUIDefault', true);
|
||||
|
||||
late final serverFuncBtns = listProperty(
|
||||
'serverBtns',
|
||||
defaultValue: ServerFuncBtn.defaultIdxs,
|
||||
);
|
||||
late final serverFuncBtns = listProperty('serverBtns', defaultValue: ServerFuncBtn.defaultIdxs);
|
||||
|
||||
/// Docker is more popular than podman, set to `false` to use docker
|
||||
late final usePodman = propertyDefault('usePodman', false);
|
||||
@@ -180,16 +157,10 @@ class SettingStore extends HiveStore {
|
||||
late final containerParseStat = propertyDefault('containerParseStat', true);
|
||||
|
||||
/// Auto refresh container status
|
||||
late final containerAutoRefresh = propertyDefault(
|
||||
'containerAutoRefresh',
|
||||
true,
|
||||
);
|
||||
late final containerAutoRefresh = propertyDefault('containerAutoRefresh', true);
|
||||
|
||||
/// Use double column servers page on Desktop
|
||||
late final doubleColumnServersPage = propertyDefault(
|
||||
'doubleColumnServersPage',
|
||||
true,
|
||||
);
|
||||
late final doubleColumnServersPage = propertyDefault('doubleColumnServersPage', true);
|
||||
|
||||
/// Ignore local network device (eg: br-xxx, ovs-system...)
|
||||
/// when building traffic view on server tab
|
||||
@@ -244,8 +215,7 @@ class SettingStore extends HiveStore {
|
||||
/// Record the position and size of the window.
|
||||
late final windowState = property<WindowState>(
|
||||
'windowState',
|
||||
fromObj: (raw) =>
|
||||
WindowState.fromJson(jsonDecode(raw as String) as Map<String, dynamic>),
|
||||
fromObj: (raw) => WindowState.fromJson(jsonDecode(raw as String) as Map<String, dynamic>),
|
||||
toObj: (state) => state == null ? null : jsonEncode(state.toJson()),
|
||||
);
|
||||
|
||||
@@ -258,10 +228,7 @@ class SettingStore extends HiveStore {
|
||||
late final sftpEditor = propertyDefault('sftpEditor', '');
|
||||
|
||||
/// Preferred terminal emulator command on desktop
|
||||
late final desktopTerminal = propertyDefault(
|
||||
'desktopTerminal',
|
||||
'x-terminal-emulator',
|
||||
);
|
||||
late final desktopTerminal = propertyDefault('desktopTerminal', 'x-terminal-emulator');
|
||||
|
||||
/// Run foreground service on Android, if the SSH terminal is running
|
||||
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
|
||||
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:
|
||||
/// **'Are you sure you want to clear connection statistics for server \"{serverName}\"? This action cannot be undone.'**
|
||||
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
|
||||
|
||||
@@ -895,4 +895,20 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
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.';
|
||||
}
|
||||
|
||||
@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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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`).
|
||||
@@ -1690,4 +1705,19 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
|
||||
String clearServerStatsContent(String 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
|
||||
|
||||
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/hive/hive_adapters.dart';
|
||||
|
||||
extension HiveRegistrar on HiveInterface {
|
||||
void registerAdapters() {
|
||||
registerAdapter(AppTabAdapter());
|
||||
registerAdapter(ConnectionResultAdapter());
|
||||
registerAdapter(ConnectionStatAdapter());
|
||||
registerAdapter(NetViewTypeAdapter());
|
||||
@@ -25,6 +27,7 @@ extension HiveRegistrar on HiveInterface {
|
||||
|
||||
extension IsolatedHiveRegistrar on IsolatedHiveInterface {
|
||||
void registerAdapters() {
|
||||
registerAdapter(AppTabAdapter());
|
||||
registerAdapter(ConnectionResultAdapter());
|
||||
registerAdapter(ConnectionStatAdapter());
|
||||
registerAdapter(NetViewTypeAdapter());
|
||||
|
||||
@@ -276,5 +276,10 @@
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"homeTabs": "ホームタブ",
|
||||
"homeTabsCustomizeDesc": "ホームページに表示するタブとその順序をカスタマイズします",
|
||||
"reset": "リセット",
|
||||
"availableTabs": "利用可能なタブ",
|
||||
"atLeastOneTab": "少なくとも1つのタブを選択する必要があります"
|
||||
}
|
||||
@@ -276,5 +276,10 @@
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"homeTabs": "Вкладки дома",
|
||||
"homeTabsCustomizeDesc": "Настройте, какие вкладки появляются на главной странице и их порядок",
|
||||
"reset": "Сброс",
|
||||
"availableTabs": "Доступные вкладки",
|
||||
"atLeastOneTab": "Должна быть выбрана хотя бы одна вкладка"
|
||||
}
|
||||
@@ -276,5 +276,10 @@
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"homeTabs": "Домашні вкладки",
|
||||
"homeTabsCustomizeDesc": "Налаштуйте, які вкладки відображаються на головній сторінці та їх порядок",
|
||||
"reset": "Скинути",
|
||||
"availableTabs": "Доступні вкладки",
|
||||
"atLeastOneTab": "Потрібно вибрати принаймні одну вкладку"
|
||||
}
|
||||
@@ -276,5 +276,10 @@
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"homeTabs": "主页标签",
|
||||
"homeTabsCustomizeDesc": "自定义主页上显示的标签及其顺序",
|
||||
"reset": "重置",
|
||||
"availableTabs": "可用标签",
|
||||
"atLeastOneTab": "至少需要选择一个标签"
|
||||
}
|
||||
@@ -276,5 +276,10 @@
|
||||
"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 _provider = ref.read(serversNotifierProvider);
|
||||
late List<AppTab> _tabs = Stores.setting.homeTabs.fetch();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
@@ -51,13 +52,30 @@ class _HomePageState extends ConsumerState<HomePage>
|
||||
SystemUIs.switchStatusBar(hide: false);
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
// 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;
|
||||
}
|
||||
_pageController = PageController(initialPage: _selectIndex.value);
|
||||
if (Stores.setting.generalWakeLock.fetch()) {
|
||||
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
|
||||
@@ -119,9 +137,9 @@ class _HomePageState extends ConsumerState<HomePage>
|
||||
Expanded(
|
||||
child: PageView.builder(
|
||||
controller: _pageController,
|
||||
itemCount: AppTab.values.length,
|
||||
itemCount: _tabs.length,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (_, index) => AppTab.values[index].page,
|
||||
itemBuilder: (_, index) => _tabs[index].page,
|
||||
onPageChanged: (value) {
|
||||
FocusScope.of(context).unfocus();
|
||||
if (!_switchingPage) {
|
||||
@@ -146,7 +164,7 @@ class _HomePageState extends ConsumerState<HomePage>
|
||||
animationDuration: const Duration(milliseconds: 250),
|
||||
onDestinationSelected: _onDestinationSelected,
|
||||
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,
|
||||
labelType: extended ? NavigationRailLabelType.none : NavigationRailLabelType.all,
|
||||
selectedIndex: idx,
|
||||
destinations: AppTab.navRailDestinations,
|
||||
destinations: _tabs.map((tab) => tab.navRailDestination).toList(),
|
||||
onDestinationSelected: _onDestinationSelected,
|
||||
),
|
||||
),
|
||||
@@ -236,6 +254,7 @@ class _HomePageState extends ConsumerState<HomePage>
|
||||
|
||||
void _onDestinationSelected(int index) {
|
||||
if (_selectIndex.value == index) return;
|
||||
if (index < 0 || index >= _tabs.length) return;
|
||||
_selectIndex.value = index;
|
||||
_switchingPage = true;
|
||||
_pageController.animateToPage(
|
||||
|
||||
@@ -8,6 +8,7 @@ extension _App on _AppSettingsPageState {
|
||||
_buildThemeMode(),
|
||||
_buildAppColor(),
|
||||
_buildCheckUpdate(),
|
||||
_buildHomeTabs(),
|
||||
PlatformPublicSettings.buildBioAuth,
|
||||
if (specific != null) specific,
|
||||
_buildAppMore(),
|
||||
@@ -274,4 +275,15 @@ extension _App on _AppSettingsPageState {
|
||||
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/private_key/list.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/ios.dart';
|
||||
import 'package:server_box/view/page/setting/platform/platform_pub.dart';
|
||||
|
||||
@@ -497,8 +497,8 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "v1.0.345"
|
||||
resolved-ref: "1b797643ef7603dd825caf96a6c57b88dbd23c34"
|
||||
ref: "v1.0.346"
|
||||
resolved-ref: f277b7a4259e45889320ef6d80ab320662558784
|
||||
url: "https://github.com/lppcg/fl_lib"
|
||||
source: git
|
||||
version: "0.0.1"
|
||||
|
||||
@@ -63,7 +63,7 @@ dependencies:
|
||||
fl_lib:
|
||||
git:
|
||||
url: https://github.com/lppcg/fl_lib
|
||||
ref: v1.0.345
|
||||
ref: v1.0.346
|
||||
flutter_gbk2utf8: ^1.0.1
|
||||
get_it: ^8.2.0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user