This commit is contained in:
2024-12-08 17:56:30 +08:00
parent e999652a3e
commit ef435dcaa5
9 changed files with 237 additions and 293 deletions

View File

@@ -1012,11 +1012,15 @@ class ComicListState extends State<ComicList> {
while (_data[page] == null) { while (_data[page] == null) {
await _fetchNext(); await _fetchNext();
} }
setState(() {}); if(mounted) {
setState(() {});
}
} catch (e) { } catch (e) {
setState(() { if(mounted) {
_error = e.toString(); setState(() {
}); _error = e.toString();
});
}
} }
} }
} finally { } finally {

View File

@@ -51,12 +51,10 @@ class _MenuRoute<T> extends PopupRoute<T> {
], ],
), ),
child: BlurEffect( child: BlurEffect(
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(8),
child: Material( child: Material(
color: context.brightness == Brightness.light color: context.colorScheme.surface.withOpacity(0.82),
? const Color(0xFFFAFAFA).withOpacity(0.82) borderRadius: BorderRadius.circular(8),
: const Color(0xFF090909).withOpacity(0.82),
borderRadius: BorderRadius.circular(4),
child: Container( child: Container(
width: width, width: width,
padding: padding:

View File

@@ -23,14 +23,15 @@ class PaneActionEntry {
} }
class NaviPane extends StatefulWidget { class NaviPane extends StatefulWidget {
const NaviPane({required this.paneItems, const NaviPane(
required this.paneActions, {required this.paneItems,
required this.pageBuilder, required this.paneActions,
this.initialPage = 0, required this.pageBuilder,
this.onPageChanged, this.initialPage = 0,
required this.observer, this.onPageChanged,
required this.navigatorKey, required this.observer,
super.key}); required this.navigatorKey,
super.key});
final List<PaneItemEntry> paneItems; final List<PaneItemEntry> paneItems;
@@ -84,17 +85,14 @@ class NaviPaneState extends State<NaviPane>
static const _kBottomBarHeight = 58.0; 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; static const _kTopBarHeight = 48.0;
double get bottomBarHeight => double get bottomBarHeight =>
_kBottomBarHeight + MediaQuery _kBottomBarHeight + MediaQuery.of(context).padding.bottom;
.of(context)
.padding
.bottom;
void onNavigatorStateChange() { void onNavigatorStateChange() {
onRebuild(context); onRebuild(context);
@@ -136,10 +134,7 @@ class NaviPaneState extends State<NaviPane>
} }
double targetFormContext(BuildContext context) { double targetFormContext(BuildContext context) {
var width = MediaQuery var width = MediaQuery.of(context).size.width;
.of(context)
.size
.width;
double target = 0; double target = 0;
if (width > changePoint) { if (width > changePoint) {
target = 2; target = 2;
@@ -208,14 +203,13 @@ class NaviPaneState extends State<NaviPane>
return Navigator( return Navigator(
observers: [widget.observer], observers: [widget.observer],
key: widget.navigatorKey, key: widget.navigatorKey,
onGenerateRoute: (settings) => onGenerateRoute: (settings) => AppPageRoute(
AppPageRoute( preventRebuild: false,
preventRebuild: false, isRootRoute: true,
isRootRoute: true, builder: (context) {
builder: (context) { return _NaviMainView(state: this);
return _NaviMainView(state: this); },
}, ),
),
); );
} }
@@ -252,20 +246,14 @@ class NaviPaneState extends State<NaviPane>
Widget buildBottom() { Widget buildBottom() {
return Material( return Material(
textStyle: Theme textStyle: Theme.of(context).textTheme.labelSmall,
.of(context)
.textTheme
.labelSmall,
elevation: 0, elevation: 0,
child: Container( child: Container(
height: _kBottomBarHeight, height: _kBottomBarHeight,
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border( border: Border(
top: BorderSide( top: BorderSide(
color: Theme color: Theme.of(context).colorScheme.outlineVariant,
.of(context)
.colorScheme
.outlineVariant,
width: 1, width: 1,
), ),
), ),
@@ -273,7 +261,7 @@ class NaviPaneState extends State<NaviPane>
child: Row( child: Row(
children: List<Widget>.generate( children: List<Widget>.generate(
widget.paneItems.length, widget.paneItems.length,
(index) { (index) {
return Expanded( return Expanded(
child: _SingleBottomNaviWidget( child: _SingleBottomNaviWidget(
enabled: currentPage == index, enabled: currentPage == index,
@@ -293,7 +281,7 @@ class NaviPaneState extends State<NaviPane>
Widget buildLeft() { Widget buildLeft() {
final value = controller.value; final value = controller.value;
const paddingHorizontal = 16.0; const paddingHorizontal = 12.0;
return Material( return Material(
child: Container( child: Container(
width: _kFoldedSideBarWidth + width: _kFoldedSideBarWidth +
@@ -303,57 +291,39 @@ class NaviPaneState extends State<NaviPane>
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border( border: Border(
right: BorderSide( right: BorderSide(
color: Theme color: Theme.of(context).colorScheme.outlineVariant,
.of(context) width: 1.0,
.colorScheme
.outlineVariant,
width: 1,
), ),
), ),
), ),
child: Row( child: Column(
children: [ children: [
SizedBox( const SizedBox(height: 16),
width: value == 3 SizedBox(height: MediaQuery.of(context).padding.top),
? (_kSideBarWidth - paddingHorizontal * 2 - 1) ...List<Widget>.generate(
: (_kFoldedSideBarWidth - paddingHorizontal * 2 - 1), widget.paneItems.length,
child: Column( (index) => _SideNaviWidget(
children: [ enabled: currentPage == index,
const SizedBox(height: 16), entry: widget.paneItems[index],
SizedBox(height: MediaQuery showTitle: value == 3,
.of(context) onTap: () {
.padding updatePage(index);
.top), },
...List<Widget>.generate( key: ValueKey(index),
widget.paneItems.length,
(index) =>
_SideNaviWidget(
enabled: currentPage == index,
entry: widget.paneItems[index],
showTitle: value == 3,
onTap: () {
updatePage(index);
},
key: ValueKey(index),
),
),
const Spacer(),
...List<Widget>.generate(
widget.paneActions.length,
(index) =>
_PaneActionWidget(
entry: widget.paneActions[index],
showTitle: value == 3,
key: ValueKey(index + widget.paneItems.length),
),
),
const SizedBox(
height: 16,
)
],
), ),
), ),
const Spacer(), const Spacer(),
...List<Widget>.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<NaviPane>
} }
} }
class _SideNaviWidget extends StatefulWidget { class _SideNaviWidget extends StatelessWidget {
const _SideNaviWidget({required this.enabled, const _SideNaviWidget(
required this.entry, {required this.enabled,
required this.onTap, required this.entry,
required this.showTitle, required this.onTap,
super.key}); required this.showTitle,
super.key});
final bool enabled; final bool enabled;
@@ -376,60 +347,37 @@ class _SideNaviWidget extends StatefulWidget {
final bool showTitle; final bool showTitle;
@override
State<_SideNaviWidget> createState() => _SideNaviWidgetState();
}
class _SideNaviWidgetState extends State<_SideNaviWidget> {
bool isHovering = false;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme final colorScheme = Theme.of(context).colorScheme;
.of(context) final icon = Icon(enabled ? entry.activeIcon : entry.icon);
.colorScheme; return InkWell(
final icon = borderRadius: BorderRadius.circular(12),
Icon(widget.enabled ? widget.entry.activeIcon : widget.entry.icon); onTap: onTap,
return MouseRegion( child: AnimatedContainer(
cursor: SystemMouseCursors.click, duration: const Duration(milliseconds: 180),
onEnter: (details) => setState(() => isHovering = true), padding: const EdgeInsets.symmetric(horizontal: 12),
onExit: (details) => setState(() => isHovering = false), height: 38,
child: GestureDetector( decoration: BoxDecoration(
behavior: HitTestBehavior.translucent, color: enabled ? colorScheme.primaryContainer : null,
onTap: widget.onTap, borderRadius: BorderRadius.circular(12),
child: AnimatedContainer( ),
duration: const Duration(milliseconds: 180), child: showTitle ? Row(
margin: const EdgeInsets.symmetric(vertical: 4), children: [
padding: const EdgeInsets.symmetric(horizontal: 12), icon,
width: double.infinity, const SizedBox(width: 12),
height: 42, Text(entry.label)
decoration: BoxDecoration( ],
color: widget.enabled ) : Align(
? colorScheme.primaryContainer alignment: Alignment.centerLeft,
: isHovering child: icon,
? colorScheme.surfaceContainerHigh ),
: null,
borderRadius: BorderRadius.circular(8),
),
child: widget.showTitle
? Row(
children: [
icon,
const SizedBox(
width: 12,
),
Text(widget.entry.label)
],
)
: Center(
child: icon,
)),
), ),
); ).paddingVertical(4);
} }
} }
class _PaneActionWidget extends StatefulWidget { class _PaneActionWidget extends StatelessWidget {
const _PaneActionWidget( const _PaneActionWidget(
{required this.entry, required this.showTitle, super.key}); {required this.entry, required this.showTitle, super.key});
@@ -437,58 +385,37 @@ class _PaneActionWidget extends StatefulWidget {
final bool showTitle; final bool showTitle;
@override
State<_PaneActionWidget> createState() => _PaneActionWidgetState();
}
class _PaneActionWidgetState extends State<_PaneActionWidget> {
bool isHovering = false;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme final icon = Icon(entry.icon);
.of(context) return InkWell(
.colorScheme; onTap: entry.onTap,
final icon = Icon(widget.entry.icon); borderRadius: BorderRadius.circular(12),
return MouseRegion( child: AnimatedContainer(
cursor: SystemMouseCursors.click, duration: const Duration(milliseconds: 180),
onEnter: (details) => setState(() => isHovering = true), padding: const EdgeInsets.symmetric(horizontal: 12),
onExit: (details) => setState(() => isHovering = false), height: 38,
child: GestureDetector( child: showTitle ? Row(
behavior: HitTestBehavior.translucent, children: [
onTap: widget.entry.onTap, icon,
child: AnimatedContainer( const SizedBox(width: 12),
duration: const Duration(milliseconds: 180), Text(entry.label)
margin: const EdgeInsets.symmetric(vertical: 4), ],
padding: const EdgeInsets.symmetric(horizontal: 12), ) : Align(
width: double.infinity, alignment: Alignment.centerLeft,
height: 42, child: icon,
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,
)),
), ),
); ).paddingVertical(4);
} }
} }
class _SingleBottomNaviWidget extends StatefulWidget { class _SingleBottomNaviWidget extends StatefulWidget {
const _SingleBottomNaviWidget({required this.enabled, const _SingleBottomNaviWidget(
required this.entry, {required this.enabled,
required this.onTap, required this.entry,
super.key}); required this.onTap,
super.key});
final bool enabled; final bool enabled;
@@ -556,11 +483,9 @@ class _SingleBottomNaviWidgetState extends State<_SingleBottomNaviWidget>
Widget buildContent() { Widget buildContent() {
final value = controller.value; final value = controller.value;
final colorScheme = Theme final colorScheme = Theme.of(context).colorScheme;
.of(context)
.colorScheme;
final icon = final icon =
Icon(widget.enabled ? widget.entry.activeIcon : widget.entry.icon); Icon(widget.enabled ? widget.entry.activeIcon : widget.entry.icon);
return Center( return Center(
child: Container( child: Container(
width: 64, width: 64,
@@ -661,12 +586,12 @@ class _NaviPopScope extends StatelessWidget {
Widget res = App.isIOS Widget res = App.isIOS
? child ? child
: PopScope( : PopScope(
canPop: App.isAndroid ? false : true, canPop: App.isAndroid ? false : true,
onPopInvokedWithResult: (value, result) { onPopInvokedWithResult: (value, result) {
action(); action();
}, },
child: child, child: child,
); );
if (popGesture) { if (popGesture) {
res = GestureDetector( res = GestureDetector(
onPanStart: (details) { onPanStart: (details) {
@@ -725,8 +650,8 @@ class _NaviMainViewState extends State<_NaviMainView> {
), ),
), ),
), ),
if (shouldShowAppBar) state.buildBottom().paddingBottom( if (shouldShowAppBar)
context.padding.bottom), state.buildBottom().paddingBottom(context.padding.bottom),
], ],
); );
} }

View File

@@ -63,22 +63,9 @@ class _App {
} }
} }
var mainColor = Colors.blue;
Future<void> init() async { Future<void> init() async {
cachePath = (await getApplicationCacheDirectory()).path; cachePath = (await getApplicationCacheDirectory()).path;
dataPath = (await getApplicationSupportDirectory()).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; Function? _forceRebuildHandler;

View File

@@ -92,7 +92,7 @@ class _Settings with ChangeNotifier {
final _data = <String, dynamic>{ final _data = <String, dynamic>{
'comicDisplayMode': 'detailed', // detailed, brief 'comicDisplayMode': 'detailed', // detailed, brief
'comicTileScale': 1.00, // 0.75-1.25 '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 'theme_mode': 'system', // light, dark, system
'newFavoriteAddTo': 'end', // start, end 'newFavoriteAddTo': 'end', // start, end
'moveFavoriteAfterRead': 'none', // none, end, start 'moveFavoriteAfterRead': 'none', // none, end, start

View File

@@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:desktop_webview_window/desktop_webview_window.dart'; import 'package:desktop_webview_window/desktop_webview_window.dart';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
@@ -128,6 +129,20 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
setState(() {}); 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget home; Widget home;
@@ -140,90 +155,95 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
} else { } else {
home = const MainPage(); home = const MainPage();
} }
return MaterialApp( return DynamicColorBuilder(builder: (light, dark) {
home: home, if (appdata.settings['color'] != 'system' || light == null || dark == null) {
debugShowCheckedModeBanner: false, var color = translateColorSetting();
theme: ThemeData( light = ColorScheme.fromSeed(
colorScheme: ColorScheme.fromSeed( seedColor: color,
seedColor: App.mainColor, );
surface: Colors.white, dark = ColorScheme.fromSeed(
primary: App.mainColor.shade600, seedColor: color,
// ignore: deprecated_member_use
background: Colors.white,
),
fontFamily: App.isWindows ? "Microsoft YaHei" : null,
),
navigatorKey: App.rootNavigatorKey,
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: App.mainColor,
brightness: Brightness.dark, brightness: Brightness.dark,
surface: Colors.black, );
primary: App.mainColor.shade400, }
// ignore: deprecated_member_use return MaterialApp(
background: Colors.black, 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, navigatorKey: App.rootNavigatorKey,
), darkTheme: ThemeData(
themeMode: switch (appdata.settings['theme_mode']) { colorScheme: dark.copyWith(
'light' => ThemeMode.light, surface: Colors.black,
'dark' => ThemeMode.dark, background: Colors.black,
_ => ThemeMode.system ),
}, fontFamily: App.isWindows ? "Microsoft YaHei" : null,
localizationsDelegates: const [ ),
GlobalMaterialLocalizations.delegate, themeMode: switch (appdata.settings['theme_mode']) {
GlobalWidgetsLocalizations.delegate, 'light' => ThemeMode.light,
GlobalCupertinoLocalizations.delegate, 'dark' => ThemeMode.dark,
], _ => ThemeMode.system
locale: () { },
var lang = appdata.settings['language']; localizationsDelegates: const [
if (lang == 'system') { GlobalMaterialLocalizations.delegate,
return null; GlobalWidgetsLocalizations.delegate,
} GlobalCupertinoLocalizations.delegate,
return switch (lang) { ],
'zh-CN' => const Locale('zh', 'CN'), locale: () {
'zh-TW' => const Locale('zh', 'TW'), var lang = appdata.settings['language'];
'en-US' => const Locale('en'), if (lang == 'system') {
_ => null return null;
}; }
}(), return switch (lang) {
supportedLocales: const [ 'zh-CN' => const Locale('zh', 'CN'),
Locale('en'), 'zh-TW' => const Locale('zh', 'TW'),
Locale('zh', 'CN'), 'en-US' => const Locale('en'),
Locale('zh', 'TW'), _ => null
], };
builder: (context, widget) { }(),
ErrorWidget.builder = (details) { supportedLocales: const [
Log.error( Locale('en'),
"Unhandled Exception", "${details.exception}\n${details.stack}"); Locale('zh', 'CN'),
return Material( Locale('zh', 'TW'),
child: Center( ],
child: Text(details.exception.toString()), builder: (context, widget) {
), ErrorWidget.builder = (details) {
); Log.error(
}; "Unhandled Exception", "${details.exception}\n${details.stack}");
if (widget != null) { return Material(
widget = OverlayWidget(widget); child: Center(
if (App.isDesktop) { child: Text(details.exception.toString()),
widget = Shortcuts(
shortcuts: {
LogicalKeySet(LogicalKeyboardKey.escape): VoidCallbackIntent(
App.pop,
),
},
child: MouseBackDetector(
onTapDown: App.pop,
child: WindowFrame(widget),
), ),
); );
};
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( throw ('widget is null');
child: widget, },
)); );
} });
throw ('widget is null');
},
);
} }
} }

View File

@@ -29,6 +29,7 @@ class _AppearanceSettingsState extends State<AppearanceSettings> {
title: "Theme Color".tl, title: "Theme Color".tl,
settingKey: "color", settingKey: "color",
optionTranslation: { optionTranslation: {
"system": "System".tl,
"red": "Red".tl, "red": "Red".tl,
"pink": "Pink".tl, "pink": "Pink".tl,
"purple": "Purple".tl, "purple": "Purple".tl,

View File

@@ -194,6 +194,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.0" 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: fake_async:
dependency: transitive dependency: transitive
description: description:

View File

@@ -70,6 +70,7 @@ dependencies:
url: https://github.com/pkuislm/flutter_saf.git url: https://github.com/pkuislm/flutter_saf.git
ref: 3315082b9f7055655610e4f6f136b69e48228c05 ref: 3315082b9f7055655610e4f6f136b69e48228c05
pdf: ^3.11.1 pdf: ^3.11.1
dynamic_color: ^1.7.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: