From cab66619df71543359483aa96193f9929c90ea23 Mon Sep 17 00:00:00 2001 From: nyne Date: Sun, 8 Dec 2024 17:56:30 +0800 Subject: [PATCH] fix #61 --- lib/components/comic.dart | 12 +- lib/components/menu.dart | 8 +- lib/components/navigation_bar.dart | 307 +++++++++++------------------ lib/foundation/app.dart | 13 -- lib/foundation/appdata.dart | 2 +- lib/main.dart | 178 +++++++++-------- lib/pages/settings/appearance.dart | 1 + pubspec.lock | 8 + pubspec.yaml | 1 + 9 files changed, 237 insertions(+), 293 deletions(-) diff --git a/lib/components/comic.dart b/lib/components/comic.dart index d175649..b8e5061 100644 --- a/lib/components/comic.dart +++ b/lib/components/comic.dart @@ -1012,11 +1012,15 @@ class ComicListState extends State { while (_data[page] == null) { await _fetchNext(); } - setState(() {}); + if(mounted) { + setState(() {}); + } } catch (e) { - setState(() { - _error = e.toString(); - }); + if(mounted) { + setState(() { + _error = e.toString(); + }); + } } } } finally { diff --git a/lib/components/menu.dart b/lib/components/menu.dart index 1e13448..7ba17f0 100644 --- a/lib/components/menu.dart +++ b/lib/components/menu.dart @@ -51,12 +51,10 @@ class _MenuRoute extends PopupRoute { ], ), child: BlurEffect( - borderRadius: BorderRadius.circular(4), + borderRadius: BorderRadius.circular(8), child: Material( - color: context.brightness == Brightness.light - ? const Color(0xFFFAFAFA).withOpacity(0.82) - : const Color(0xFF090909).withOpacity(0.82), - borderRadius: BorderRadius.circular(4), + color: context.colorScheme.surface.withOpacity(0.82), + borderRadius: BorderRadius.circular(8), child: Container( width: width, padding: diff --git a/lib/components/navigation_bar.dart b/lib/components/navigation_bar.dart index 7965921..09dc06e 100644 --- a/lib/components/navigation_bar.dart +++ b/lib/components/navigation_bar.dart @@ -23,14 +23,15 @@ class PaneActionEntry { } class NaviPane extends StatefulWidget { - const NaviPane({required this.paneItems, - required this.paneActions, - required this.pageBuilder, - this.initialPage = 0, - this.onPageChanged, - required this.observer, - required this.navigatorKey, - super.key}); + const NaviPane( + {required this.paneItems, + required this.paneActions, + required this.pageBuilder, + this.initialPage = 0, + this.onPageChanged, + required this.observer, + required this.navigatorKey, + super.key}); final List paneItems; @@ -84,17 +85,14 @@ class NaviPaneState extends State static const _kBottomBarHeight = 58.0; - static const _kFoldedSideBarWidth = 80.0; + static const _kFoldedSideBarWidth = 72.0; - static const _kSideBarWidth = 256.0; + static const _kSideBarWidth = 224.0; static const _kTopBarHeight = 48.0; double get bottomBarHeight => - _kBottomBarHeight + MediaQuery - .of(context) - .padding - .bottom; + _kBottomBarHeight + MediaQuery.of(context).padding.bottom; void onNavigatorStateChange() { onRebuild(context); @@ -136,10 +134,7 @@ class NaviPaneState extends State } double targetFormContext(BuildContext context) { - var width = MediaQuery - .of(context) - .size - .width; + var width = MediaQuery.of(context).size.width; double target = 0; if (width > changePoint) { target = 2; @@ -208,14 +203,13 @@ class NaviPaneState extends State return Navigator( observers: [widget.observer], key: widget.navigatorKey, - onGenerateRoute: (settings) => - AppPageRoute( - preventRebuild: false, - isRootRoute: true, - builder: (context) { - return _NaviMainView(state: this); - }, - ), + onGenerateRoute: (settings) => AppPageRoute( + preventRebuild: false, + isRootRoute: true, + builder: (context) { + return _NaviMainView(state: this); + }, + ), ); } @@ -252,20 +246,14 @@ class NaviPaneState extends State Widget buildBottom() { return Material( - textStyle: Theme - .of(context) - .textTheme - .labelSmall, + textStyle: Theme.of(context).textTheme.labelSmall, elevation: 0, child: Container( height: _kBottomBarHeight, decoration: BoxDecoration( border: Border( top: BorderSide( - color: Theme - .of(context) - .colorScheme - .outlineVariant, + color: Theme.of(context).colorScheme.outlineVariant, width: 1, ), ), @@ -273,7 +261,7 @@ class NaviPaneState extends State child: Row( children: List.generate( widget.paneItems.length, - (index) { + (index) { return Expanded( child: _SingleBottomNaviWidget( enabled: currentPage == index, @@ -293,7 +281,7 @@ class NaviPaneState extends State Widget buildLeft() { final value = controller.value; - const paddingHorizontal = 16.0; + const paddingHorizontal = 12.0; return Material( child: Container( width: _kFoldedSideBarWidth + @@ -303,57 +291,39 @@ class NaviPaneState extends State decoration: BoxDecoration( border: Border( right: BorderSide( - color: Theme - .of(context) - .colorScheme - .outlineVariant, - width: 1, + color: Theme.of(context).colorScheme.outlineVariant, + width: 1.0, ), ), ), - child: Row( + child: Column( children: [ - SizedBox( - width: value == 3 - ? (_kSideBarWidth - paddingHorizontal * 2 - 1) - : (_kFoldedSideBarWidth - paddingHorizontal * 2 - 1), - child: Column( - children: [ - const SizedBox(height: 16), - SizedBox(height: MediaQuery - .of(context) - .padding - .top), - ...List.generate( - widget.paneItems.length, - (index) => - _SideNaviWidget( - enabled: currentPage == index, - entry: widget.paneItems[index], - showTitle: value == 3, - onTap: () { - updatePage(index); - }, - key: ValueKey(index), - ), - ), - const Spacer(), - ...List.generate( - widget.paneActions.length, - (index) => - _PaneActionWidget( - entry: widget.paneActions[index], - showTitle: value == 3, - key: ValueKey(index + widget.paneItems.length), - ), - ), - const SizedBox( - height: 16, - ) - ], + const SizedBox(height: 16), + SizedBox(height: MediaQuery.of(context).padding.top), + ...List.generate( + widget.paneItems.length, + (index) => _SideNaviWidget( + enabled: currentPage == index, + entry: widget.paneItems[index], + showTitle: value == 3, + onTap: () { + updatePage(index); + }, + key: ValueKey(index), ), ), const Spacer(), + ...List.generate( + widget.paneActions.length, + (index) => _PaneActionWidget( + entry: widget.paneActions[index], + showTitle: value == 3, + key: ValueKey(index + widget.paneItems.length), + ), + ), + const SizedBox( + height: 16, + ) ], ), ), @@ -361,12 +331,13 @@ class NaviPaneState extends State } } -class _SideNaviWidget extends StatefulWidget { - const _SideNaviWidget({required this.enabled, - required this.entry, - required this.onTap, - required this.showTitle, - super.key}); +class _SideNaviWidget extends StatelessWidget { + const _SideNaviWidget( + {required this.enabled, + required this.entry, + required this.onTap, + required this.showTitle, + super.key}); final bool enabled; @@ -376,60 +347,37 @@ class _SideNaviWidget extends StatefulWidget { final bool showTitle; - @override - State<_SideNaviWidget> createState() => _SideNaviWidgetState(); -} - -class _SideNaviWidgetState extends State<_SideNaviWidget> { - bool isHovering = false; - @override Widget build(BuildContext context) { - final colorScheme = Theme - .of(context) - .colorScheme; - final icon = - Icon(widget.enabled ? widget.entry.activeIcon : widget.entry.icon); - return MouseRegion( - cursor: SystemMouseCursors.click, - onEnter: (details) => setState(() => isHovering = true), - onExit: (details) => setState(() => isHovering = false), - child: GestureDetector( - behavior: HitTestBehavior.translucent, - onTap: widget.onTap, - child: AnimatedContainer( - duration: const Duration(milliseconds: 180), - margin: const EdgeInsets.symmetric(vertical: 4), - padding: const EdgeInsets.symmetric(horizontal: 12), - width: double.infinity, - height: 42, - decoration: BoxDecoration( - color: widget.enabled - ? colorScheme.primaryContainer - : isHovering - ? colorScheme.surfaceContainerHigh - : null, - borderRadius: BorderRadius.circular(8), - ), - child: widget.showTitle - ? Row( - children: [ - icon, - const SizedBox( - width: 12, - ), - Text(widget.entry.label) - ], - ) - : Center( - child: icon, - )), + final colorScheme = Theme.of(context).colorScheme; + final icon = Icon(enabled ? entry.activeIcon : entry.icon); + return InkWell( + borderRadius: BorderRadius.circular(12), + onTap: onTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 180), + padding: const EdgeInsets.symmetric(horizontal: 12), + height: 38, + decoration: BoxDecoration( + color: enabled ? colorScheme.primaryContainer : null, + borderRadius: BorderRadius.circular(12), + ), + child: showTitle ? Row( + children: [ + icon, + const SizedBox(width: 12), + Text(entry.label) + ], + ) : Align( + alignment: Alignment.centerLeft, + child: icon, + ), ), - ); + ).paddingVertical(4); } } -class _PaneActionWidget extends StatefulWidget { +class _PaneActionWidget extends StatelessWidget { const _PaneActionWidget( {required this.entry, required this.showTitle, super.key}); @@ -437,58 +385,37 @@ class _PaneActionWidget extends StatefulWidget { final bool showTitle; - @override - State<_PaneActionWidget> createState() => _PaneActionWidgetState(); -} - -class _PaneActionWidgetState extends State<_PaneActionWidget> { - bool isHovering = false; - @override Widget build(BuildContext context) { - final colorScheme = Theme - .of(context) - .colorScheme; - final icon = Icon(widget.entry.icon); - return MouseRegion( - cursor: SystemMouseCursors.click, - onEnter: (details) => setState(() => isHovering = true), - onExit: (details) => setState(() => isHovering = false), - child: GestureDetector( - behavior: HitTestBehavior.translucent, - onTap: widget.entry.onTap, - child: AnimatedContainer( - duration: const Duration(milliseconds: 180), - margin: const EdgeInsets.symmetric(vertical: 4), - padding: const EdgeInsets.symmetric(horizontal: 12), - width: double.infinity, - height: 42, - decoration: BoxDecoration( - color: isHovering ? colorScheme.surfaceContainerHigh : null, - borderRadius: BorderRadius.circular(8)), - child: widget.showTitle - ? Row( - children: [ - icon, - const SizedBox( - width: 12, - ), - Text(widget.entry.label) - ], - ) - : Center( - child: icon, - )), + final icon = Icon(entry.icon); + return InkWell( + onTap: entry.onTap, + borderRadius: BorderRadius.circular(12), + child: AnimatedContainer( + duration: const Duration(milliseconds: 180), + padding: const EdgeInsets.symmetric(horizontal: 12), + height: 38, + child: showTitle ? Row( + children: [ + icon, + const SizedBox(width: 12), + Text(entry.label) + ], + ) : Align( + alignment: Alignment.centerLeft, + child: icon, + ), ), - ); + ).paddingVertical(4); } } class _SingleBottomNaviWidget extends StatefulWidget { - const _SingleBottomNaviWidget({required this.enabled, - required this.entry, - required this.onTap, - super.key}); + const _SingleBottomNaviWidget( + {required this.enabled, + required this.entry, + required this.onTap, + super.key}); final bool enabled; @@ -556,11 +483,9 @@ class _SingleBottomNaviWidgetState extends State<_SingleBottomNaviWidget> Widget buildContent() { final value = controller.value; - final colorScheme = Theme - .of(context) - .colorScheme; + final colorScheme = Theme.of(context).colorScheme; final icon = - Icon(widget.enabled ? widget.entry.activeIcon : widget.entry.icon); + Icon(widget.enabled ? widget.entry.activeIcon : widget.entry.icon); return Center( child: Container( width: 64, @@ -661,12 +586,12 @@ class _NaviPopScope extends StatelessWidget { Widget res = App.isIOS ? child : PopScope( - canPop: App.isAndroid ? false : true, - onPopInvokedWithResult: (value, result) { - action(); - }, - child: child, - ); + canPop: App.isAndroid ? false : true, + onPopInvokedWithResult: (value, result) { + action(); + }, + child: child, + ); if (popGesture) { res = GestureDetector( onPanStart: (details) { @@ -725,8 +650,8 @@ class _NaviMainViewState extends State<_NaviMainView> { ), ), ), - if (shouldShowAppBar) state.buildBottom().paddingBottom( - context.padding.bottom), + if (shouldShowAppBar) + state.buildBottom().paddingBottom(context.padding.bottom), ], ); } diff --git a/lib/foundation/app.dart b/lib/foundation/app.dart index 9c94bd4..22e2da1 100644 --- a/lib/foundation/app.dart +++ b/lib/foundation/app.dart @@ -63,22 +63,9 @@ class _App { } } - var mainColor = Colors.blue; - Future init() async { cachePath = (await getApplicationCacheDirectory()).path; dataPath = (await getApplicationSupportDirectory()).path; - mainColor = switch (appdata.settings['color']) { - 'red' => Colors.red, - 'pink' => Colors.pink, - 'purple' => Colors.purple, - 'green' => Colors.green, - 'orange' => Colors.orange, - 'blue' => Colors.blue, - 'yellow' => Colors.yellow, - 'cyan' => Colors.cyan, - _ => Colors.blue, - }; } Function? _forceRebuildHandler; diff --git a/lib/foundation/appdata.dart b/lib/foundation/appdata.dart index c113a00..128d0bf 100644 --- a/lib/foundation/appdata.dart +++ b/lib/foundation/appdata.dart @@ -92,7 +92,7 @@ class _Settings with ChangeNotifier { final _data = { 'comicDisplayMode': 'detailed', // detailed, brief 'comicTileScale': 1.00, // 0.75-1.25 - 'color': 'blue', // red, pink, purple, green, orange, blue + 'color': 'system', // red, pink, purple, green, orange, blue 'theme_mode': 'system', // light, dark, system 'newFavoriteAddTo': 'end', // start, end 'moveFavoriteAfterRead': 'none', // none, end, start diff --git a/lib/main.dart b/lib/main.dart index 01cd575..100c974 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'package:desktop_webview_window/desktop_webview_window.dart'; +import 'package:dynamic_color/dynamic_color.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; @@ -128,6 +129,20 @@ class _MyAppState extends State with WidgetsBindingObserver { setState(() {}); } + Color translateColorSetting() { + return switch (appdata.settings['color']) { + 'red' => Colors.red, + 'pink' => Colors.pink, + 'purple' => Colors.purple, + 'green' => Colors.green, + 'orange' => Colors.orange, + 'blue' => Colors.blue, + 'yellow' => Colors.yellow, + 'cyan' => Colors.cyan, + _ => Colors.blue, + }; + } + @override Widget build(BuildContext context) { Widget home; @@ -140,90 +155,95 @@ class _MyAppState extends State with WidgetsBindingObserver { } else { home = const MainPage(); } - return MaterialApp( - home: home, - debugShowCheckedModeBanner: false, - theme: ThemeData( - colorScheme: ColorScheme.fromSeed( - seedColor: App.mainColor, - surface: Colors.white, - primary: App.mainColor.shade600, - // ignore: deprecated_member_use - background: Colors.white, - ), - fontFamily: App.isWindows ? "Microsoft YaHei" : null, - ), - navigatorKey: App.rootNavigatorKey, - darkTheme: ThemeData( - colorScheme: ColorScheme.fromSeed( - seedColor: App.mainColor, + return DynamicColorBuilder(builder: (light, dark) { + if (appdata.settings['color'] != 'system' || light == null || dark == null) { + var color = translateColorSetting(); + light = ColorScheme.fromSeed( + seedColor: color, + ); + dark = ColorScheme.fromSeed( + seedColor: color, brightness: Brightness.dark, - surface: Colors.black, - primary: App.mainColor.shade400, - // ignore: deprecated_member_use - background: Colors.black, + ); + } + return MaterialApp( + home: home, + debugShowCheckedModeBanner: false, + theme: ThemeData( + colorScheme: light.copyWith( + surface: Colors.white, + background: Colors.white, + ), + fontFamily: App.isWindows ? "Microsoft YaHei" : null, ), - fontFamily: App.isWindows ? "Microsoft YaHei" : null, - ), - themeMode: switch (appdata.settings['theme_mode']) { - 'light' => ThemeMode.light, - 'dark' => ThemeMode.dark, - _ => ThemeMode.system - }, - localizationsDelegates: const [ - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - locale: () { - var lang = appdata.settings['language']; - if (lang == 'system') { - return null; - } - return switch (lang) { - 'zh-CN' => const Locale('zh', 'CN'), - 'zh-TW' => const Locale('zh', 'TW'), - 'en-US' => const Locale('en'), - _ => null - }; - }(), - supportedLocales: const [ - Locale('en'), - Locale('zh', 'CN'), - Locale('zh', 'TW'), - ], - builder: (context, widget) { - ErrorWidget.builder = (details) { - Log.error( - "Unhandled Exception", "${details.exception}\n${details.stack}"); - return Material( - child: Center( - child: Text(details.exception.toString()), - ), - ); - }; - if (widget != null) { - widget = OverlayWidget(widget); - if (App.isDesktop) { - widget = Shortcuts( - shortcuts: { - LogicalKeySet(LogicalKeyboardKey.escape): VoidCallbackIntent( - App.pop, - ), - }, - child: MouseBackDetector( - onTapDown: App.pop, - child: WindowFrame(widget), + navigatorKey: App.rootNavigatorKey, + darkTheme: ThemeData( + colorScheme: dark.copyWith( + surface: Colors.black, + background: Colors.black, + ), + fontFamily: App.isWindows ? "Microsoft YaHei" : null, + ), + themeMode: switch (appdata.settings['theme_mode']) { + 'light' => ThemeMode.light, + 'dark' => ThemeMode.dark, + _ => ThemeMode.system + }, + localizationsDelegates: const [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + locale: () { + var lang = appdata.settings['language']; + if (lang == 'system') { + return null; + } + return switch (lang) { + 'zh-CN' => const Locale('zh', 'CN'), + 'zh-TW' => const Locale('zh', 'TW'), + 'en-US' => const Locale('en'), + _ => null + }; + }(), + supportedLocales: const [ + Locale('en'), + Locale('zh', 'CN'), + Locale('zh', 'TW'), + ], + builder: (context, widget) { + ErrorWidget.builder = (details) { + Log.error( + "Unhandled Exception", "${details.exception}\n${details.stack}"); + return Material( + child: Center( + child: Text(details.exception.toString()), ), ); + }; + if (widget != null) { + widget = OverlayWidget(widget); + if (App.isDesktop) { + widget = Shortcuts( + shortcuts: { + LogicalKeySet(LogicalKeyboardKey.escape): VoidCallbackIntent( + App.pop, + ), + }, + child: MouseBackDetector( + onTapDown: App.pop, + child: WindowFrame(widget), + ), + ); + } + return _SystemUiProvider(Material( + child: widget, + )); } - return _SystemUiProvider(Material( - child: widget, - )); - } - throw ('widget is null'); - }, - ); + throw ('widget is null'); + }, + ); + }); } } diff --git a/lib/pages/settings/appearance.dart b/lib/pages/settings/appearance.dart index 2f96bd8..fcc7a17 100644 --- a/lib/pages/settings/appearance.dart +++ b/lib/pages/settings/appearance.dart @@ -29,6 +29,7 @@ class _AppearanceSettingsState extends State { title: "Theme Color".tl, settingKey: "color", optionTranslation: { + "system": "System".tl, "red": "Red".tl, "pink": "Pink".tl, "purple": "Purple".tl, diff --git a/pubspec.lock b/pubspec.lock index 9f64ace..e85ba84 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -194,6 +194,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + dynamic_color: + dependency: "direct main" + description: + name: dynamic_color + sha256: eae98052fa6e2826bdac3dd2e921c6ce2903be15c6b7f8b6d8a5d49b5086298d + url: "https://pub.dev" + source: hosted + version: "1.7.0" fake_async: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index da623e6..7b4097e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -70,6 +70,7 @@ dependencies: url: https://github.com/pkuislm/flutter_saf.git ref: 3315082b9f7055655610e4f6f136b69e48228c05 pdf: ^3.11.1 + dynamic_color: ^1.7.0 dev_dependencies: flutter_test: