diff --git a/l10n_arb/intl_zh_CN.arb b/l10n_arb/intl_zh_CN.arb index af04bcd..ff6f724 100644 --- a/l10n_arb/intl_zh_CN.arb +++ b/l10n_arb/intl_zh_CN.arb @@ -16,6 +16,7 @@ "continueInWebVersion": "继续使用网页版", "changeServer": "切换服务器", "continueInCurrentServer": "继续使用", + "loadFailed": "加载失败{reason}", "durationJustNow": "刚刚", "durationSeconds": "{number} 秒前", @@ -52,6 +53,8 @@ "uploadImageFailed": "上传图像失败", "gebura": "Gebura", + "store": "商店", + "library": "库", "pleaseSetupApplicationPath": "请设置应用程序路径", "pleaseDontReRunApplication": "请不要重复运行应用程序", "applicationExitAbnormally": "应用程序异常退出", diff --git a/lib/bloc/deeplink_bloc.dart b/lib/bloc/deeplink_bloc.dart index 24332ad..ce0aae2 100644 --- a/lib/bloc/deeplink_bloc.dart +++ b/lib/bloc/deeplink_bloc.dart @@ -19,6 +19,7 @@ class DeepLinkConnectState extends DeepLinkState { DeepLinkConnectState(this.serverConfig); } +// DeepLinkBloc manages deep link events and states. class DeepLinkBloc extends Bloc { StreamSubscription? _sub; diff --git a/lib/bloc/main_bloc.dart b/lib/bloc/main_bloc.dart index a45d1a4..81d3bca 100644 --- a/lib/bloc/main_bloc.dart +++ b/lib/bloc/main_bloc.dart @@ -31,6 +31,8 @@ import 'yesod/yesod_bloc.dart'; part 'main_event.dart'; part 'main_state.dart'; +// MainBloc manage the basic state of the app, +// including login, logout, and server switching. class MainBloc extends Bloc { final ApiHelper _api; final ClientCommonRepo _repo; diff --git a/lib/init.dart b/lib/init.dart index 0ac28b4..dc81944 100644 --- a/lib/init.dart +++ b/lib/init.dart @@ -19,7 +19,7 @@ Future init() async { final api = ApiHelper(); // system tray - await initSystemTray(); + await _initSystemTray(); WidgetsFlutterBinding.ensureInitialized(); @@ -39,7 +39,7 @@ Future init() async { Bloc.observer = SimpleBlocObserver(); } final packageInfo = await PackageInfo.fromPlatform(); - final deviceInfo = await genClientDeviceInfo(); + final deviceInfo = await _genClientDeviceInfo(); final clientSettingBloc = ClientSettingBloc(common); final deepLinkBloc = DeepLinkBloc(initialUri); final mainBloc = MainBloc(api, common, clientSettingBloc, deepLinkBloc, @@ -51,7 +51,7 @@ Future init() async { return MyApp(router, mainBloc); } -Future initSystemTray() async { +Future _initSystemTray() async { if (!PlatformHelper.isWindowsApp()) { return; } @@ -92,7 +92,7 @@ Future initSystemTray() async { }); } -Future genClientDeviceInfo() async { +Future _genClientDeviceInfo() async { final deviceInfo = DeviceInfoPlugin(); if (PlatformHelper.isAndroidApp()) { final androidInfo = await deviceInfo.androidInfo; diff --git a/lib/l10n/intl/messages_zh-CN.dart b/lib/l10n/intl/messages_zh-CN.dart index d464c87..a9b3303 100644 --- a/lib/l10n/intl/messages_zh-CN.dart +++ b/lib/l10n/intl/messages_zh-CN.dart @@ -37,6 +37,8 @@ class MessageLookup extends MessageLookupByLibrary { static String m7(success, failed) => "导入Steam应用程序完成,成功 ${success} 个,失败 ${failed} 个。"; + static String m11(reason) => "加载失败${reason}"; + static String m8(server) => "登录到 ${server}"; static String m9(reason) => "登录失败,${reason}"; @@ -123,6 +125,8 @@ class MessageLookup extends MessageLookupByLibrary { "importingSteamApplications": MessageLookupByLibrary.simpleMessage("正在导入Steam应用程序"), "launcherError": MessageLookupByLibrary.simpleMessage("启动器错误"), + "library": MessageLookupByLibrary.simpleMessage("库"), + "loadFailed": m11, "loggingInTo": m8, "login": MessageLookupByLibrary.simpleMessage("登录"), "loginFailed": m9, @@ -141,6 +145,7 @@ class MessageLookup extends MessageLookupByLibrary { "scanningLocalFiles": MessageLookupByLibrary.simpleMessage("正在扫描本地文件"), "serverSetup": MessageLookupByLibrary.simpleMessage("服务器设置"), "showServerConfig": MessageLookupByLibrary.simpleMessage("查看服务器信息"), + "store": MessageLookupByLibrary.simpleMessage("商店"), "title": MessageLookupByLibrary.simpleMessage("TuiHub"), "tls": MessageLookupByLibrary.simpleMessage("TLS"), "unknownErrorOccurred": MessageLookupByLibrary.simpleMessage("发生未知错误"), diff --git a/lib/l10n/l10n.dart b/lib/l10n/l10n.dart index e6c5a26..92597fd 100644 --- a/lib/l10n/l10n.dart +++ b/lib/l10n/l10n.dart @@ -210,6 +210,16 @@ class S { ); } + /// `加载失败{reason}` + String loadFailed(Object reason) { + return Intl.message( + '加载失败$reason', + name: 'loadFailed', + desc: '', + args: [reason], + ); + } + /// `刚刚` String get durationJustNow { return Intl.message( @@ -530,6 +540,26 @@ class S { ); } + /// `商店` + String get store { + return Intl.message( + '商店', + name: 'store', + desc: '', + args: [], + ); + } + + /// `库` + String get library { + return Intl.message( + '库', + name: 'library', + desc: '', + args: [], + ); + } + /// `请设置应用程序路径` String get pleaseSetupApplicationPath { return Intl.message( diff --git a/lib/main.dart b/lib/main.dart index c77b7d2..3a14b06 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:bitsdojo_window/bitsdojo_window.dart'; +import 'package:chinese_font_library/chinese_font_library.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flutter/foundation.dart'; @@ -33,9 +34,11 @@ import 'model/common_model.dart'; import 'repo/grpc/api_helper.dart'; import 'repo/local/common.dart'; import 'route.dart'; +import 'view/helper/spacing.dart'; part 'init.dart'; +// main function is the entry point of the app void main(List args) async { // Required for Android App await Hive.initFlutter(); @@ -148,6 +151,39 @@ class _MyAppWidget extends StatelessWidget { if (state is DefaultAppState) { context.read().add(InitClientSettingEvent()); } + + final lightTheme = _customizeTheme( + Brightness.light, + FlexThemeData.light( + scheme: state.theme.scheme, + useMaterial3: true, + surfaceMode: FlexSurfaceMode.highSurfaceLowScaffold, + blendLevel: 10, + subThemesData: const FlexSubThemesData( + blendOnLevel: 7, + useTextTheme: true, + useM2StyleDividerInM3: true, + ), + visualDensity: FlexColorScheme.comfortablePlatformDensity, + swapLegacyOnMaterial3: true, + )); + + final darkTheme = _customizeTheme( + Brightness.dark, + FlexThemeData.dark( + scheme: state.theme.scheme, + surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, + blendLevel: 13, + subThemesData: const FlexSubThemesData( + blendOnLevel: 20, + useTextTheme: true, + useM2StyleDividerInM3: true, + ), + visualDensity: FlexColorScheme.comfortablePlatformDensity, + useMaterial3: true, + swapLegacyOnMaterial3: true, + )); + return MaterialApp.router( onGenerateTitle: (context) => S.of(context).title, localizationsDelegates: const [ @@ -157,39 +193,28 @@ class _MyAppWidget extends StatelessWidget { GlobalCupertinoLocalizations.delegate, ], supportedLocales: S.delegate.supportedLocales, - // The Mandy red, light theme. - theme: FlexThemeData.light( - scheme: state.theme.scheme, - useMaterial3: true, - surfaceMode: FlexSurfaceMode.highSurfaceLowScaffold, - blendLevel: 10, - subThemesData: const FlexSubThemesData( - blendOnLevel: 7, - useTextTheme: true, - useM2StyleDividerInM3: true, - ), - visualDensity: FlexColorScheme.comfortablePlatformDensity, - swapLegacyOnMaterial3: true, - ), - // The Mandy red, dark theme. - darkTheme: FlexThemeData.dark( - scheme: state.theme.scheme, - surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, - blendLevel: 13, - subThemesData: const FlexSubThemesData( - blendOnLevel: 20, - useTextTheme: true, - useM2StyleDividerInM3: true, - ), - visualDensity: FlexColorScheme.comfortablePlatformDensity, - useMaterial3: true, - swapLegacyOnMaterial3: true, - ), + theme: lightTheme, + darkTheme: darkTheme, themeMode: state.themeMode, - // Use dark or light theme based on system setting. routerConfig: router, ); }, ); } } + +ThemeData _customizeTheme(Brightness brightness, ThemeData theme) { + // Temporary fix for font issue + // https://github.com/flutter/flutter/issues/48381 + return theme + .copyWith( + listTileTheme: theme.listTileTheme.copyWith( + selectedColor: theme.colorScheme.onPrimary, + selectedTileColor: theme.colorScheme.primary, + shape: RoundedRectangleBorder( + borderRadius: SpacingHelper.defaultBorderRadius, + ), + ), + ) + .useSystemChineseFont(brightness); +} diff --git a/lib/main_window.dart b/lib/main_window.dart index 6f55000..90d5ef8 100644 --- a/lib/main_window.dart +++ b/lib/main_window.dart @@ -9,6 +9,7 @@ import 'view/pages/server_select_overlay.dart'; import 'view/specialized/theme_mode_toggle.dart'; import 'view/specialized/title_bar.dart'; +// MainWindow is the top-level widget of the app class MainWindow extends StatelessWidget { const MainWindow({super.key, required this.child}); diff --git a/lib/route.dart b/lib/route.dart index d440527..367a7ad 100644 --- a/lib/route.dart +++ b/lib/route.dart @@ -126,11 +126,11 @@ class AppRoutes { static const String _gebura = '$_module/${ModuleName._gebura}'; static const AppRoutes gebura = AppRoutes._(_gebura); static const AppRoutes geburaStore = - AppRoutes._('$_gebura/${_GeburaFunctions.store}'); + AppRoutes._('$_gebura/${GeburaFunctions.store}'); static const AppRoutes geburaLibrary = - AppRoutes._('$_gebura/${_GeburaFunctions.library}'); + AppRoutes._('$_gebura/${GeburaFunctions.library}'); static const AppRoutes geburaLibrarySettings = - AppRoutes._('$_gebura/${_GeburaFunctions.librarySettings}'); + AppRoutes._('$_gebura/${GeburaFunctions.librarySettings}'); static AppRoutes geburaLibraryDetail(int id) => AppRoutes._('$geburaLibrary?id=$id'); @@ -230,7 +230,7 @@ class _YesodActions { static const String recentFilter = 'filterRecent'; } -class _GeburaFunctions { +class GeburaFunctions { static const String store = 'store'; static const String library = 'library'; static const String librarySettings = 'librarySettings'; @@ -427,9 +427,9 @@ GoRouter getRouter(MainBloc mainBloc, ApiHelper apiHelper) { final function = state.pathParameters['function'] ?? AppRoutes.geburaStore.toString(); final geburaPages = { - _GeburaFunctions.store: const GeburaStorePage(), - _GeburaFunctions.library: const GeburaLibraryOverview(), - _GeburaFunctions.librarySettings: + GeburaFunctions.store: const GeburaStorePage(), + GeburaFunctions.library: const GeburaLibraryOverview(), + GeburaFunctions.librarySettings: const GeburaLibrarySettings(), }; Widget? page = geburaPages[function]; diff --git a/lib/view/components/expand_rail_tile.dart b/lib/view/components/expand_rail_tile.dart deleted file mode 100644 index 032e297..0000000 --- a/lib/view/components/expand_rail_tile.dart +++ /dev/null @@ -1,446 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter/material.dart'; - -import './rail_tile.dart'; - -const Duration _kExpand = Duration(milliseconds: 200); - -/// A single-line [ListTile] with an expansion arrow icon that expands or collapses -/// the tile to reveal or hide the [children]. -/// -/// This widget is typically used with [ListView] to create an -/// "expand / collapse" list entry. When used with scrolling widgets like -/// [ListView], a unique [PageStorageKey] must be specified to enable the -/// [ExpandRailTile] to save and restore its expanded state when it is scrolled -/// in and out of view. -/// -/// This class overrides the [ListTileThemeData.iconColor] and [ListTileThemeData.textColor] -/// theme properties for its [ListTile]. These colors animate between values when -/// the tile is expanded and collapsed: between [iconColor], [collapsedIconColor] and -/// between [textColor] and [collapsedTextColor]. -/// -/// The expansion arrow icon is shown on the right by default in left-to-right languages -/// (i.e. the trailing edge). This can be changed using [controlAffinity]. This maps -/// to the [leading] and [trailing] properties of [ExpandRailTile]. -/// -/// {@tool dartpad} -/// This example demonstrates different configurations of ExpansionTile. -/// -/// ** See code in examples/api/lib/material/expansion_tile/expansion_tile.0.dart ** -/// {@end-tool} -/// -/// See also: -/// -/// * [ListTile], useful for creating expansion tile [children] when the -/// expansion tile represents a sublist. -/// * The "Expand and collapse" section of -/// -class ExpandRailTile extends StatefulWidget { - /// Creates a single-line [ListTile] with an expansion arrow icon that expands or collapses - /// the tile to reveal or hide the [children]. The [initiallyExpanded] property must - /// be non-null. - const ExpandRailTile({ - super.key, - this.leading, - required this.title, - this.onExpansionChanged, - this.children = const [], - this.trailing, - this.initiallyExpanded = false, - this.maintainState = false, - this.expandedCrossAxisAlignment, - this.expandedAlignment, - this.childrenPadding, - this.textColor, - this.collapsedTextColor, - this.iconColor, - this.collapsedIconColor, - this.shape, - this.collapsedShape, - this.clipBehavior, - this.controlAffinity, - }) : assert( - expandedCrossAxisAlignment != CrossAxisAlignment.baseline, - 'CrossAxisAlignment.baseline is not supported since the expanded children ' - 'are aligned in a column, not a row. Try to use another constant.', - ); - - /// A widget to display before the title. - /// - /// Typically a [CircleAvatar] widget. - /// - /// Note that depending on the value of [controlAffinity], the [leading] widget - /// may replace the rotating expansion arrow icon. - final Widget? leading; - - /// The primary content of the list item. - /// - /// Typically a [Text] widget. - final Widget title; - - /// Called when the tile expands or collapses. - /// - /// When the tile starts expanding, this function is called with the value - /// true. When the tile starts collapsing, this function is called with - /// the value false. - final ValueChanged? onExpansionChanged; - - /// The widgets that are displayed when the tile expands. - /// - /// Typically [ListTile] widgets. - final List children; - - /// A widget to display after the title. - /// - /// Note that depending on the value of [controlAffinity], the [trailing] widget - /// may replace the rotating expansion arrow icon. - final Widget? trailing; - - /// Specifies if the list tile is initially expanded (true) or collapsed (false, the default). - final bool initiallyExpanded; - - /// Specifies whether the state of the children is maintained when the tile expands and collapses. - /// - /// When true, the children are kept in the tree while the tile is collapsed. - /// When false (default), the children are removed from the tree when the tile is - /// collapsed and recreated upon expansion. - final bool maintainState; - - /// Specifies the alignment of [children], which are arranged in a column when - /// the tile is expanded. - /// - /// The internals of the expanded tile make use of a [Column] widget for - /// [children], and [Align] widget to align the column. The [expandedAlignment] - /// parameter is passed directly into the [Align]. - /// - /// Modifying this property controls the alignment of the column within the - /// expanded tile, not the alignment of [children] widgets within the column. - /// To align each child within [children], see [expandedCrossAxisAlignment]. - /// - /// The width of the column is the width of the widest child widget in [children]. - /// - /// If this property is null then [ExpansionTileThemeData.expandedAlignment]is used. If that - /// is also null then the value of [expandedAlignment] is [Alignment.center]. - /// - /// See also: - /// - /// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s - /// [ExpansionTileThemeData]. - final Alignment? expandedAlignment; - - /// Specifies the alignment of each child within [children] when the tile is expanded. - /// - /// The internals of the expanded tile make use of a [Column] widget for - /// [children], and the `crossAxisAlignment` parameter is passed directly into the [Column]. - /// - /// Modifying this property controls the cross axis alignment of each child - /// within its [Column]. Note that the width of the [Column] that houses - /// [children] will be the same as the widest child widget in [children]. It is - /// not necessarily the width of [Column] is equal to the width of expanded tile. - /// - /// To align the [Column] along the expanded tile, use the [expandedAlignment] property - /// instead. - /// - /// When the value is null, the value of [expandedCrossAxisAlignment] is [CrossAxisAlignment.center]. - final CrossAxisAlignment? expandedCrossAxisAlignment; - - /// Specifies padding for [children]. - /// - /// If this property is null then [ExpansionTileThemeData.childrenPadding] is used. If that - /// is also null then the value of [childrenPadding] is [EdgeInsets.zero]. - /// - /// See also: - /// - /// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s - /// [ExpansionTileThemeData]. - final EdgeInsetsGeometry? childrenPadding; - - /// The icon color of tile's expansion arrow icon when the sublist is expanded. - /// - /// Used to override to the [ListTileThemeData.iconColor]. - /// - /// If this property is null then [ExpansionTileThemeData.iconColor] is used. If that - /// is also null then the value of [ListTileThemeData.iconColor] is used. - /// - /// See also: - /// - /// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s - /// [ExpansionTileThemeData]. - final Color? iconColor; - - /// The icon color of tile's expansion arrow icon when the sublist is collapsed. - /// - /// Used to override to the [ListTileThemeData.iconColor]. - final Color? collapsedIconColor; - - /// The color of the tile's titles when the sublist is expanded. - /// - /// Used to override to the [ListTileThemeData.textColor]. - /// - /// If this property is null then [ExpansionTileThemeData.textColor] is used. If that - /// is also null then the value of [ListTileThemeData.textColor] is used. - /// - /// See also: - /// - /// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s - /// [ExpansionTileThemeData]. - final Color? textColor; - - /// The color of the tile's titles when the sublist is collapsed. - /// - /// Used to override to the [ListTileThemeData.textColor]. - /// - /// If this property is null then [ExpansionTileThemeData.collapsedTextColor] is used. If that - /// is also null then the value of [ListTileThemeData.textColor] is used. - /// - /// See also: - /// - /// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s - /// [ExpansionTileThemeData]. - final Color? collapsedTextColor; - - /// The tile's border shape when the sublist is expanded. - /// - /// If this property is null, the [ExpansionTileThemeData.shape] is used. If that - /// is also null, a [Border] with vertical sides default to [ThemeData.dividerColor] is used - /// - /// See also: - /// - /// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s - /// [ExpansionTileThemeData]. - final ShapeBorder? shape; - - /// The tile's border shape when the sublist is collapsed. - /// - /// If this property is null, the [ExpansionTileThemeData.collapsedShape] is used. If that - /// is also null, a [Border] with vertical sides default to Color [Colors.transparent] is used - /// - /// See also: - /// - /// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s - /// [ExpansionTileThemeData]. - final ShapeBorder? collapsedShape; - - /// {@macro flutter.material.Material.clipBehavior} - /// - /// If this property is null, the [ExpansionTileThemeData.clipBehavior] is used. If that - /// is also null, a [Clip.none] is used - /// - /// See also: - /// - /// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s - /// [ExpansionTileThemeData]. - final Clip? clipBehavior; - - /// Typically used to force the expansion arrow icon to the tile's leading or trailing edge. - /// - /// By default, the value of [controlAffinity] is [ListTileControlAffinity.platform], - /// which means that the expansion arrow icon will appear on the tile's trailing edge. - final ListTileControlAffinity? controlAffinity; - - @override - State createState() => _ExpandRailTileState(); -} - -class _ExpandRailTileState extends State - with SingleTickerProviderStateMixin { - static final Animatable _easeInTween = - CurveTween(curve: Curves.easeIn); - static final Animatable _halfTween = - Tween(begin: 0.0, end: 0.5); - - final ShapeBorderTween _borderTween = ShapeBorderTween(); - final ColorTween _headerColorTween = ColorTween(); - final ColorTween _iconColorTween = ColorTween(); - - late AnimationController _controller; - late Animation _iconTurns; - late Animation _heightFactor; - late Animation _headerColor; - late Animation _iconColor; - - bool _isExpanded = false; - - @override - void initState() { - super.initState(); - _controller = AnimationController(duration: _kExpand, vsync: this); - _heightFactor = _controller.drive(_easeInTween); - _iconTurns = _controller.drive(_halfTween.chain(_easeInTween)); - _headerColor = _controller.drive(_headerColorTween.chain(_easeInTween)); - _iconColor = _controller.drive(_iconColorTween.chain(_easeInTween)); - _isExpanded = PageStorage.maybeOf(context)?.readState(context) as bool? ?? - widget.initiallyExpanded; - if (_isExpanded) { - _controller.value = 1.0; - } - } - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - - void _handleTap() { - setState(() { - _isExpanded = !_isExpanded; - if (_isExpanded) { - _controller.forward(); - } else { - _controller.reverse().then((void value) { - if (!mounted) { - return; - } - setState(() { - // Rebuild without widget.children. - }); - }); - } - PageStorage.maybeOf(context)?.writeState(context, _isExpanded); - }); - widget.onExpansionChanged?.call(_isExpanded); - } - - // Platform or null affinity defaults to trailing. - ListTileControlAffinity _effectiveAffinity( - ListTileControlAffinity? affinity) { - switch (affinity ?? ListTileControlAffinity.trailing) { - case ListTileControlAffinity.leading: - return ListTileControlAffinity.leading; - case ListTileControlAffinity.trailing: - case ListTileControlAffinity.platform: - return ListTileControlAffinity.trailing; - } - } - - Widget? _buildIcon(BuildContext context) { - return RotationTransition( - turns: _iconTurns, - child: const Icon(Icons.expand_more), - ); - } - - Widget? _buildLeadingIcon(BuildContext context) { - if (_effectiveAffinity(widget.controlAffinity) != - ListTileControlAffinity.leading) { - return null; - } - return _buildIcon(context); - } - - Widget? _buildTrailingIcon(BuildContext context) { - if (_effectiveAffinity(widget.controlAffinity) != - ListTileControlAffinity.trailing) { - return null; - } - return _buildIcon(context); - } - - Widget _buildChildren(BuildContext context, Widget? child) { - final ExpansionTileThemeData expansionTileTheme = - ExpansionTileTheme.of(context); - - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - DefaultTextStyle( - style: TextStyle( - color: _headerColor.value, - ), - child: Theme( - data: Theme.of(context).copyWith( - iconTheme: IconThemeData( - color: _iconColor.value ?? expansionTileTheme.iconColor), - ), - child: RailTile( - onTap: _handleTap, - leading: widget.leading ?? _buildLeadingIcon(context), - title: widget.title, - trailing: widget.trailing ?? _buildTrailingIcon(context), - ), - ), - ), - ClipRect( - child: Align( - alignment: widget.expandedAlignment ?? - expansionTileTheme.expandedAlignment ?? - Alignment.center, - heightFactor: _heightFactor.value, - child: child, - ), - ), - ], - ); - } - - @override - void didChangeDependencies() { - final ThemeData theme = Theme.of(context); - final ExpansionTileThemeData expansionTileTheme = - ExpansionTileTheme.of(context); - final ColorScheme colorScheme = theme.colorScheme; - _borderTween - ..begin = widget.collapsedShape ?? - expansionTileTheme.collapsedShape ?? - const Border( - top: BorderSide(color: Colors.transparent), - bottom: BorderSide(color: Colors.transparent), - ) - ..end = widget.shape ?? - expansionTileTheme.collapsedShape ?? - Border( - top: BorderSide(color: theme.dividerColor), - bottom: BorderSide(color: theme.dividerColor), - ); - _headerColorTween - ..begin = widget.collapsedTextColor ?? - expansionTileTheme.collapsedTextColor ?? - theme.textTheme.titleMedium!.color - ..end = widget.textColor ?? - expansionTileTheme.textColor ?? - colorScheme.primary; - _iconColorTween - ..begin = widget.collapsedIconColor ?? - expansionTileTheme.collapsedIconColor ?? - theme.unselectedWidgetColor - ..end = widget.iconColor ?? - expansionTileTheme.iconColor ?? - colorScheme.primary; - super.didChangeDependencies(); - } - - @override - Widget build(BuildContext context) { - final ExpansionTileThemeData expansionTileTheme = - ExpansionTileTheme.of(context); - final bool closed = !_isExpanded && _controller.isDismissed; - final bool shouldRemoveChildren = closed && !widget.maintainState; - - final Widget result = Offstage( - offstage: closed, - child: TickerMode( - enabled: !closed, - child: Padding( - padding: widget.childrenPadding ?? - expansionTileTheme.childrenPadding ?? - EdgeInsets.zero, - child: Column( - crossAxisAlignment: - widget.expandedCrossAxisAlignment ?? CrossAxisAlignment.center, - children: widget.children, - ), - ), - ), - ); - - return AnimatedBuilder( - animation: _controller.view, - builder: _buildChildren, - child: shouldRemoveChildren ? null : result, - ); - } -} diff --git a/lib/view/components/rail_tile.dart b/lib/view/components/rail_tile.dart deleted file mode 100644 index 541560c..0000000 --- a/lib/view/components/rail_tile.dart +++ /dev/null @@ -1,90 +0,0 @@ -import 'package:flutter/material.dart'; - -class RailTile extends StatelessWidget { - final Widget? leading; - final Widget? title; - final Widget? trailing; - - final VoidCallback? onTap; - final bool selected; - - const RailTile({ - super.key, - this.leading, - this.title, - this.trailing, - this.onTap, - this.selected = false, - }); - - @override - Widget build(BuildContext context) { - return Material( - color: Colors.transparent, - borderRadius: BorderRadius.circular(kToolbarHeight), - child: SizedBox( - height: kToolbarHeight - 8, - child: InkWell( - onTap: onTap, - borderRadius: BorderRadius.circular(kToolbarHeight), - child: AnimatedContainer( - decoration: BoxDecoration( - color: selected - ? Theme.of(context).colorScheme.primary - : Colors.transparent, - borderRadius: BorderRadius.circular(kToolbarHeight), - ), - duration: const Duration(milliseconds: 200), - curve: Curves.easeIn, - child: Row( - children: [ - const SizedBox( - width: 12, - ), - if (leading != null) - SizedBox( - width: kToolbarHeight - 16, - child: IconTheme( - data: IconThemeData( - size: 24, - color: selected - ? Theme.of(context).colorScheme.onPrimary - : Theme.of(context).colorScheme.onSurfaceVariant, - ), - child: leading!, - ), - ), - if (leading == null) - const SizedBox( - width: 4, - ), - Expanded( - child: DefaultTextStyle( - style: TextStyle( - color: selected - ? Theme.of(context).colorScheme.onPrimary - : Theme.of(context).colorScheme.onSurfaceVariant, - fontWeight: FontWeight.w600, - fontSize: 16, - ), - child: SizedBox( - child: title, - ), - ), - ), - const SizedBox( - width: 4, - ), - if (trailing != null) - SizedBox( - width: kToolbarHeight - 16, - child: trailing, - ), - ], - ), - ), - ), - ), - ); - } -} diff --git a/lib/view/pages/frame_page.dart b/lib/view/pages/frame_page.dart index 59b9433..1499138 100644 --- a/lib/view/pages/frame_page.dart +++ b/lib/view/pages/frame_page.dart @@ -54,7 +54,8 @@ class FramePageState extends State { ? Row( children: [ _Nav(selectedNav: widget.selectedNav), - SizedBox( + Container( + padding: const EdgeInsets.only(top: 8, bottom: 8, right: 8), width: widget.leftPartWidth != null ? widget.leftPartWidth! - 64 : 256, @@ -78,7 +79,9 @@ class FramePageState extends State { left: Row( children: [ _Nav(selectedNav: widget.selectedNav), - SizedBox( + Container( + padding: + const EdgeInsets.only(top: 8, bottom: 8, right: 8), width: width - 64 - restWidth, child: widget.leftPart, ), diff --git a/lib/view/pages/gebura/gebura_nav.dart b/lib/view/pages/gebura/gebura_nav.dart index d5c88b2..0f9dd15 100644 --- a/lib/view/pages/gebura/gebura_nav.dart +++ b/lib/view/pages/gebura/gebura_nav.dart @@ -5,8 +5,8 @@ import 'package:smooth_scroll_multiplatform/smooth_scroll_multiplatform.dart'; import 'package:tuihub_protos/librarian/v1/common.pb.dart'; import '../../../bloc/gebura/gebura_bloc.dart'; +import '../../../l10n/l10n.dart'; import '../../../route.dart'; -import '../../components/rail_tile.dart'; import '../../helper/spacing.dart'; import '../../layout/overlapping_panels.dart'; @@ -17,68 +17,64 @@ class GeburaNav extends StatelessWidget { @override Widget build(BuildContext context) { + var firstBuild = true; return BlocBuilder(builder: (context, state) { - if (state.purchasedAppInfos == null) { - context.read().add(GeburaInitEvent()); + if (firstBuild) { + firstBuild = false; + if (state.purchasedAppInfos == null) { + context.read().add(GeburaInitEvent()); + } } - return Scaffold( - body: Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceTint.withAlpha(24), - borderRadius: BorderRadius.circular(12), + return Column( + children: [ + ListTile( + leading: const Icon( + Icons.shopping_cart, + ), + onTap: () { + context + .read() + .add(GeburaSetPurchasedAppInfoIndexEvent(null)); + AppRoutes.geburaStore.go(context); + OverlappingPanels.of(context)?.reveal(RevealSide.main); + }, + title: Text(S.of(context).store), + selected: function == GeburaFunctions.store, ), - margin: const EdgeInsets.symmetric(horizontal: 8), - padding: const EdgeInsets.all(8), - child: Column( - children: [ - RailTile( - leading: const Icon( - Icons.shopping_cart, - ), - onTap: () { - context - .read() - .add(GeburaSetPurchasedAppInfoIndexEvent(null)); - AppRoutes.geburaStore.go(context); - OverlappingPanels.of(context)?.reveal(RevealSide.main); - }, - title: const Text('Store'), - selected: function == 'store', - ), - RailTile( - leading: const Icon( - Icons.apps, - ), - onTap: () { - context - .read() - .add(GeburaSetPurchasedAppInfoIndexEvent(null)); - AppRoutes.geburaLibrary.push(context); - OverlappingPanels.of(context)?.reveal(RevealSide.main); - }, - title: const Text('Library'), - selected: function == 'library' && - state.selectedPurchasedAppInfoIndex == null, - ), - SpacingHelper.defaultDivider, - Expanded( - child: DynMouseScroll( - builder: (context, controller, physics) { - return SingleChildScrollView( - controller: controller, - physics: physics, - child: Column( - children: [ - if (state.purchasedAppInfos != null && - state.purchasedAppInfos!.isNotEmpty) + ListTile( + leading: const Icon( + Icons.apps, + ), + onTap: () { + context + .read() + .add(GeburaSetPurchasedAppInfoIndexEvent(null)); + AppRoutes.geburaLibrary.push(context); + OverlappingPanels.of(context)?.reveal(RevealSide.main); + }, + title: Text(S.of(context).library), + selected: function == GeburaFunctions.library && + state.selectedPurchasedAppInfoIndex == null, + ), + SpacingHelper.defaultDivider, + Expanded( + child: DynMouseScroll( + builder: (context, controller, physics) { + return SingleChildScrollView( + controller: controller, + physics: physics, + child: (state.purchasedAppInfos != null && + state.purchasedAppInfos!.isNotEmpty) + ? Column( + children: [ for (var i = 0, app = state.purchasedAppInfos![i]; i < state.purchasedAppInfos!.length; i++, app = state.purchasedAppInfos! .elementAtOrNull(i) ?? AppInfoMixed()) - RailTile( + ListTile( selected: i == state.selectedPurchasedAppInfoIndex, onTap: () { @@ -109,24 +105,26 @@ class GeburaNav extends StatelessWidget { ? app.id.id.toHexString() : app.name), ) - else if (state is GeburaPurchasedAppsLoadState) - if (state.processing) - const Center( - child: CircularProgressIndicator(), - ) - else if (state.failed) - Center( - child: Text('加载失败: ${state.msg}'), - ), - ], - ), - ); - }, - ), - ) - ], - ), - ), + ], + ) + : (state is GeburaPurchasedAppsLoadState) + ? (state.processing) + ? const Center( + child: CircularProgressIndicator(), + ) + : (state.failed) + ? Center( + child: Text(S + .of(context) + .loadFailed(state.msg ?? '')), + ) + : Container() + : Container(), + ); + }, + ), + ) + ], ); }); } diff --git a/lib/view/pages/settings/about_page.dart b/lib/view/pages/settings/about_page.dart index 80e1891..6d81b19 100644 --- a/lib/view/pages/settings/about_page.dart +++ b/lib/view/pages/settings/about_page.dart @@ -5,7 +5,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import '../../../bloc/main_bloc.dart'; -import '../../components/expand_rail_tile.dart'; import '../../helper/spacing.dart'; class AboutPage extends StatelessWidget { @@ -32,7 +31,7 @@ class AboutPage extends StatelessWidget { backgroundColor: Theme.of(context).colorScheme.surfaceVariant, ), body: Padding( - padding: const EdgeInsets.all(8), + padding: const EdgeInsets.only(top: 8), child: Material( color: Colors.transparent, child: ListView( @@ -71,7 +70,7 @@ class AboutPage extends StatelessWidget { ), ), const SizedBox(height: 16), - ExpandRailTile( + ExpansionTile( title: const Text( 'Privacy Policy', ), @@ -92,7 +91,7 @@ class AboutPage extends StatelessWidget { ], ), const SizedBox(height: 16), - ExpandRailTile( + ExpansionTile( title: const Text( 'Current Server Information', ), diff --git a/lib/view/pages/settings/client/client_setting_page.dart b/lib/view/pages/settings/client/client_setting_page.dart index 875fa34..02225dd 100644 --- a/lib/view/pages/settings/client/client_setting_page.dart +++ b/lib/view/pages/settings/client/client_setting_page.dart @@ -22,7 +22,7 @@ class ClientSettingPage extends StatelessWidget { backgroundColor: Theme.of(context).colorScheme.surfaceVariant, ), body: Padding( - padding: const EdgeInsets.all(8), + padding: const EdgeInsets.only(top: 8), child: Material( color: Colors.transparent, child: BlocBuilder( diff --git a/lib/view/pages/settings/notify/notify_flow_add_page.dart b/lib/view/pages/settings/notify/notify_flow_add_page.dart index 15e9050..b924781 100644 --- a/lib/view/pages/settings/notify/notify_flow_add_page.dart +++ b/lib/view/pages/settings/notify/notify_flow_add_page.dart @@ -11,7 +11,6 @@ import '../../../../model/netzach_model.dart'; import '../../../../repo/grpc/l10n.dart'; import '../../../../route.dart'; import '../../../components/chips_input.dart'; -import '../../../components/expand_rail_tile.dart'; import '../../../components/toast.dart'; import '../../../form/form_field.dart'; import '../../../helper/spacing.dart'; @@ -195,7 +194,7 @@ class _NotifyFlowAddPageState extends State { const SizedBox( height: 8, ), - ExpandRailTile( + ExpansionTile( title: const Text('过滤器'), children: [ const SizedBox( @@ -406,7 +405,7 @@ class _NotifyFlowAddPageState extends State { const SizedBox( height: 8, ), - ExpandRailTile( + ExpansionTile( title: const Text('过滤器'), children: [ const SizedBox( diff --git a/lib/view/pages/settings/notify/notify_flow_edit_page.dart b/lib/view/pages/settings/notify/notify_flow_edit_page.dart index 196d3de..2f124a1 100644 --- a/lib/view/pages/settings/notify/notify_flow_edit_page.dart +++ b/lib/view/pages/settings/notify/notify_flow_edit_page.dart @@ -11,7 +11,6 @@ import '../../../../model/netzach_model.dart'; import '../../../../repo/grpc/l10n.dart'; import '../../../../route.dart'; import '../../../components/chips_input.dart'; -import '../../../components/expand_rail_tile.dart'; import '../../../components/toast.dart'; import '../../../form/form_field.dart'; import '../../../helper/spacing.dart'; @@ -213,7 +212,7 @@ class _NotifyFlowAddPageState extends State { const SizedBox( height: 8, ), - ExpandRailTile( + ExpansionTile( title: const Text('过滤器'), children: [ const SizedBox( @@ -424,7 +423,7 @@ class _NotifyFlowAddPageState extends State { const SizedBox( height: 8, ), - ExpandRailTile( + ExpansionTile( title: const Text('过滤器'), children: [ const SizedBox( diff --git a/lib/view/pages/settings/settings_nav.dart b/lib/view/pages/settings/settings_nav.dart index 8077f29..2c57e46 100644 --- a/lib/view/pages/settings/settings_nav.dart +++ b/lib/view/pages/settings/settings_nav.dart @@ -1,11 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:smooth_scroll_multiplatform/smooth_scroll_multiplatform.dart'; import 'package:tuihub_protos/librarian/sephirah/v1/tiphereth.pb.dart'; import '../../../bloc/main_bloc.dart'; import '../../../route.dart'; -import '../../components/expand_rail_tile.dart'; -import '../../components/rail_tile.dart'; import '../../layout/overlapping_panels.dart'; class SettingsNav extends StatelessWidget { @@ -17,127 +16,122 @@ class SettingsNav extends StatelessWidget { Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - return Scaffold( - body: Column( - children: [ - Expanded( - child: Container( - decoration: BoxDecoration( - color: - Theme.of(context).colorScheme.surfaceTint.withAlpha(24), - borderRadius: BorderRadius.circular(12), - ), - margin: const EdgeInsets.symmetric(horizontal: 8), - padding: const EdgeInsets.all(8), - child: Column( - children: [ - RailTile( - onTap: () { - AppRoutes.settingsClient.go(context); - OverlappingPanels.of(context) - ?.reveal(RevealSide.main); - }, - title: const Text('客户端设置'), - selected: function == 'client', - ), - RailTile( - onTap: () { - AppRoutes.settingsSession.go(context); - OverlappingPanels.of(context) - ?.reveal(RevealSide.main); - }, - title: const Text('登录设备管理'), - selected: function == 'session', - ), - ExpandRailTile( - title: const Text( - '通知设置', + return Column( + children: [ + Expanded( + child: DynMouseScroll( + builder: (context, controller, physics) { + return SingleChildScrollView( + controller: controller, + physics: physics, + child: Column( + children: [ + ListTile( + onTap: () { + AppRoutes.settingsClient.go(context); + OverlappingPanels.of(context) + ?.reveal(RevealSide.main); + }, + title: const Text('客户端设置'), + selected: function == 'client', ), - childrenPadding: const EdgeInsets.only(left: 12), - children: [ - RailTile( - title: const Text('Token管理'), - onTap: () { - AppRoutes.notifyTarget.go(context); - OverlappingPanels.of(context) - ?.reveal(RevealSide.main); - }, - selected: function == 'notifyTarget', - ), - RailTile( - title: const Text('规则管理'), - onTap: () { - AppRoutes.notifyFlow.go(context); - OverlappingPanels.of(context) - ?.reveal(RevealSide.main); - }, - selected: function == 'notifyFlow', - ), - ], - ), - if (state.currentUser!.type == UserType.USER_TYPE_ADMIN) - ExpandRailTile( + ListTile( + onTap: () { + AppRoutes.settingsSession.go(context); + OverlappingPanels.of(context) + ?.reveal(RevealSide.main); + }, + title: const Text('登录设备管理'), + selected: function == 'session', + ), + ExpansionTile( title: const Text( - '管理区域', + '通知设置', ), childrenPadding: const EdgeInsets.only(left: 12), children: [ - RailTile( - title: const Text('插件管理'), - onTap: () { - AppRoutes.settingsPorter.go(context); - OverlappingPanels.of(context) - ?.reveal(RevealSide.main); - }, - selected: function == 'porter', - ), - RailTile( - title: const Text('用户管理'), + ListTile( + title: const Text('Token管理'), onTap: () { - AppRoutes.settingsUser.go(context); + AppRoutes.notifyTarget.go(context); OverlappingPanels.of(context) ?.reveal(RevealSide.main); }, - selected: function == 'user', + selected: function == 'notifyTarget', ), - RailTile( - title: const Text('应用管理'), + ListTile( + title: const Text('规则管理'), onTap: () { - AppRoutes.settingsApp.go(context); + AppRoutes.notifyFlow.go(context); OverlappingPanels.of(context) ?.reveal(RevealSide.main); }, - selected: function == 'app', - ), - RailTile( - title: const Text('应用包管理'), - onTap: () { - AppRoutes.settingsAppPackage.go(context); - OverlappingPanels.of(context) - ?.reveal(RevealSide.main); - }, - selected: function == 'appPackage', + selected: function == 'notifyFlow', ), ], - ) - else - const SizedBox(), - const Expanded(child: SizedBox()), - RailTile( - onTap: () { - AppRoutes.settingsAbout.go(context); - OverlappingPanels.of(context) - ?.reveal(RevealSide.main); - }, - title: const Text('关于'), - selected: function == 'about', - ), - ], - ), - ), + ), + if (state.currentUser!.type == UserType.USER_TYPE_ADMIN) + ExpansionTile( + title: const Text( + '管理区域', + ), + childrenPadding: const EdgeInsets.only(left: 12), + children: [ + ListTile( + title: const Text('插件管理'), + onTap: () { + AppRoutes.settingsPorter.go(context); + OverlappingPanels.of(context) + ?.reveal(RevealSide.main); + }, + selected: function == 'porter', + ), + ListTile( + title: const Text('用户管理'), + onTap: () { + AppRoutes.settingsUser.go(context); + OverlappingPanels.of(context) + ?.reveal(RevealSide.main); + }, + selected: function == 'user', + ), + ListTile( + title: const Text('应用管理'), + onTap: () { + AppRoutes.settingsApp.go(context); + OverlappingPanels.of(context) + ?.reveal(RevealSide.main); + }, + selected: function == 'app', + ), + ListTile( + title: const Text('应用包管理'), + onTap: () { + AppRoutes.settingsAppPackage.go(context); + OverlappingPanels.of(context) + ?.reveal(RevealSide.main); + }, + selected: function == 'appPackage', + ), + ], + ) + else + const SizedBox(), + ], + ), + ); + }, ), - ], - ), + ), + ListTile( + onTap: () { + AppRoutes.settingsAbout.go(context); + OverlappingPanels.of(context)?.reveal(RevealSide.main); + }, + title: const Text('关于'), + selected: function == 'about', + ), + ], ); }, ); diff --git a/lib/view/pages/settings/user/user_edit_page.dart b/lib/view/pages/settings/user/user_edit_page.dart index 7c4210a..a950c6e 100644 --- a/lib/view/pages/settings/user/user_edit_page.dart +++ b/lib/view/pages/settings/user/user_edit_page.dart @@ -5,7 +5,6 @@ import 'package:tuihub_protos/librarian/sephirah/v1/tiphereth.pb.dart'; import '../../../../bloc/tiphereth/tiphereth_bloc.dart'; import '../../../../repo/grpc/l10n.dart'; import '../../../../route.dart'; -import '../../../components/expand_rail_tile.dart'; import '../../../components/toast.dart'; import '../../../specialized/right_panel_form.dart'; @@ -137,7 +136,7 @@ class _UserEditPageState extends State { const SizedBox( height: 16, ), - ExpandRailTile( + ExpansionTile( title: const Text('修改密码'), children: [ const Text('你必须拥有旧密码才能修改密码'), diff --git a/lib/view/pages/tiphereth/tiphereth_frame_page.dart b/lib/view/pages/tiphereth/tiphereth_frame_page.dart index c85adf7..ceb7f3a 100644 --- a/lib/view/pages/tiphereth/tiphereth_frame_page.dart +++ b/lib/view/pages/tiphereth/tiphereth_frame_page.dart @@ -13,7 +13,7 @@ class TipherethFramePage extends StatelessWidget { return Scaffold( body: const Column( children: [ - SizedBox(height: 80), + SizedBox(height: 72), MyProfileCard(), MyAccountsCard(), Expanded(child: SizedBox()), diff --git a/lib/view/pages/yesod/yesod_nav.dart b/lib/view/pages/yesod/yesod_nav.dart index 080c778..faf91a6 100644 --- a/lib/view/pages/yesod/yesod_nav.dart +++ b/lib/view/pages/yesod/yesod_nav.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import '../../../route.dart'; -import '../../components/rail_tile.dart'; import '../../layout/overlapping_panels.dart'; class YesodNav extends StatelessWidget { @@ -11,67 +10,50 @@ class YesodNav extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - body: Column( - children: [ - Expanded( - child: Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceTint.withAlpha(24), - borderRadius: BorderRadius.circular(12), + return Column( + children: [ + Expanded( + child: Column( + children: [ + ListTile( + leading: const Icon( + Icons.timelapse, + ), + onTap: () { + AppRoutes.yesodRecent.go(context); + OverlappingPanels.of(context)?.reveal(RevealSide.main); + }, + title: const Text('Recent'), + selected: function == 'recent', ), - margin: const EdgeInsets.symmetric(horizontal: 8), - padding: const EdgeInsets.all(8), - child: Column( - children: [ - RailTile( - leading: const Icon( - Icons.timelapse, - ), - onTap: () { - AppRoutes.yesodRecent.go(context); - OverlappingPanels.of(context)?.reveal(RevealSide.main); - }, - title: const Text('Recent'), - selected: function == 'recent', - ), - // RailTile( - // leading: const Icon( - // Icons.timeline, - // ), - // onTap: () { - // GoRouter.of(context).go('/app/Yesod/timeline'); - // OverlappingPanels.of(context)?.reveal(RevealSide.main); - // }, - // title: const Text('Timeline'), - // selected: function == 'timeline', - // ), - // const YesodCategoryListPage(selectedAppID: ''), - const Expanded(child: SizedBox()), - ], - ), - ), + // RailTile( + // leading: const Icon( + // Icons.timeline, + // ), + // onTap: () { + // GoRouter.of(context).go('/app/Yesod/timeline'); + // OverlappingPanels.of(context)?.reveal(RevealSide.main); + // }, + // title: const Text('Timeline'), + // selected: function == 'timeline', + // ), + // const YesodCategoryListPage(selectedAppID: ''), + // const Expanded(child: SizedBox()), + ], ), - Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceTint.withAlpha(24), - borderRadius: BorderRadius.circular(kToolbarHeight), - ), - margin: const EdgeInsets.all(8), - child: RailTile( - leading: const Icon( - Icons.rss_feed, - ), - onTap: () { - AppRoutes.yesodConfig.go(context); - OverlappingPanels.of(context)?.reveal(RevealSide.main); - }, - title: const Text('Feed Config'), - selected: function == 'config', - ), + ), + ListTile( + leading: const Icon( + Icons.rss_feed, ), - ], - ), + onTap: () { + AppRoutes.yesodConfig.go(context); + OverlappingPanels.of(context)?.reveal(RevealSide.main); + }, + title: const Text('Feed Config'), + selected: function == 'config', + ), + ], ); } } diff --git a/pubspec.yaml b/pubspec.yaml index 08e0977..8c79f5f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -59,6 +59,7 @@ dependencies: # theme flex_color_scheme: ^7.0.0 + chinese_font_library: ^1.1.0 # ui components fl_chart: ^0.64.0