From 4cbf9b9c64c366ca5ad12200321a3c172611581c Mon Sep 17 00:00:00 2001 From: Liplum Date: Thu, 18 Apr 2024 04:52:35 +0800 Subject: [PATCH] [timetable] wallpaper support on web --- lib/app.dart | 40 ++++------ lib/main.dart | 2 + lib/timetable/page/p13n/background.dart | 90 ++++++++++++++++++---- lib/timetable/page/p13n/cell_style.dart | 1 + lib/timetable/widgets/timetable/board.dart | 16 +++- 5 files changed, 108 insertions(+), 41 deletions(-) diff --git a/lib/app.dart b/lib/app.dart index b3a8569c6..46104836d 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -36,29 +36,6 @@ class _MimirAppState extends ConsumerState { ); late final router = buildRouter($routingConfig); - @override - void initState() { - super.initState(); - if (!kIsWeb) { - fitSystemScreenshot.init(); - } - } - - @override - void dispose() { - fitSystemScreenshot.release(); - super.dispose(); - } - - @override - void didChangeDependencies() { - // precache timetable background file - if (Settings.timetable.backgroundImage?.enabled == true) { - precacheImage(FileImage(Files.timetable.backgroundFile), context); - } - super.didChangeDependencies(); - } - @override Widget build(BuildContext context) { final demoMode = ref.watch(Dev.$demoMode); @@ -139,6 +116,9 @@ class _PostServiceRunnerState extends ConsumerState<_PostServiceRunner> { @override void initState() { super.initState(); + if (!kIsWeb) { + fitSystemScreenshot.init(); + } if (!kIsWeb) { Future.delayed(Duration.zero).then((value) async { await checkAppUpdate( @@ -168,9 +148,23 @@ class _PostServiceRunnerState extends ConsumerState<_PostServiceRunner> { }); } + @override + void didChangeDependencies() { + // precache timetable background file + final timetableBk = Settings.timetable.backgroundImage; + if (timetableBk != null && timetableBk.enabled) { + if (kIsWeb) { + precacheImage(NetworkImage(timetableBk.path), context); + } else { + precacheImage(FileImage(Files.timetable.backgroundFile), context); + } + } + super.didChangeDependencies(); + } @override void dispose() { $appLink?.cancel(); + fitSystemScreenshot.release(); super.dispose(); } diff --git a/lib/main.dart b/lib/main.dart index 28c751402..b225e6ca9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,6 +8,7 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:go_router/go_router.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:sit/files.dart'; import 'package:sit/migration/foundation.dart'; @@ -36,6 +37,7 @@ void main() async { // debugRepaintTextRainbowEnabled = true; // debugPaintSizeEnabled = true; WidgetsFlutterBinding.ensureInitialized(); + GoRouter.optionURLReflectsImperativeAPIs = kDebugMode; final prefs = await SharedPreferences.getInstance(); final lastSize = prefs.getLastWindowSize(); await DesktopInit.init(size: lastSize); diff --git a/lib/timetable/page/p13n/background.dart b/lib/timetable/page/p13n/background.dart index 1d76e20bd..6ebc8b267 100644 --- a/lib/timetable/page/p13n/background.dart +++ b/lib/timetable/page/p13n/background.dart @@ -1,12 +1,14 @@ import 'dart:async'; import 'dart:io'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:go_router/go_router.dart'; import 'package:image_picker/image_picker.dart'; import 'package:rettulf/rettulf.dart'; +import 'package:sit/design/adaptive/editor.dart'; import 'package:sit/design/adaptive/multiplatform.dart'; import 'package:sit/design/widgets/common.dart'; import 'package:sit/files.dart'; @@ -17,6 +19,7 @@ import 'package:sit/widgets/modal_image_view.dart'; import 'package:universal_platform/universal_platform.dart'; import "../../i18n.dart"; +/// Persist changes to storage before route popping class TimetableBackgroundEditor extends StatefulWidget { const TimetableBackgroundEditor({super.key}); @@ -35,7 +38,9 @@ class _TimetableBackgroundEditorState extends State w _TimetableBackgroundEditorState() { final bk = Settings.timetable.backgroundImage; rawPath = bk?.path; - renderImageFile = bk?.path == null ? null : Files.timetable.backgroundFile; + if (!kIsWeb) { + renderImageFile = bk?.path == null ? null : Files.timetable.backgroundFile; + } opacity = bk?.opacity ?? 1.0; repeat = bk?.repeat ?? true; antialias = bk?.antialias ?? true; @@ -71,7 +76,7 @@ class _TimetableBackgroundEditorState extends State w SliverList.list(children: [ buildImage().padH(10), buildToolBar().padV(4), - if (Dev.on && rawPath != null) + if (rawPath != null && (Dev.on || (UniversalPlatform.isDesktop || kIsWeb))) ListTile( title: "Selected image".text(), subtitle: rawPath.text(), @@ -86,6 +91,28 @@ class _TimetableBackgroundEditorState extends State w } Future onSave() async { + if (kIsWeb) { + await onSaveWeb(); + } else { + await onSaveIo(); + } + } + + Future onSaveWeb() async { + final background = buildBackgroundImage(); + if (background == null) { + Settings.timetable.backgroundImage = background; + context.pop(null); + return; + } + final img = NetworkImage(background.path); + Settings.timetable.backgroundImage = background; + await precacheImage(img, context); + if (!mounted) return; + context.pop(background); + } + + Future onSaveIo() async { final background = buildBackgroundImage(); final img = FileImage(Files.timetable.backgroundFile); if (background == null) { @@ -96,19 +123,19 @@ class _TimetableBackgroundEditorState extends State w Settings.timetable.backgroundImage = null; if (!mounted) return; context.pop(null); - } else { - final renderImageFile = this.renderImageFile; - if (renderImageFile == null) return; - Settings.timetable.backgroundImage = background; - if (renderImageFile.path != Files.timetable.backgroundFile.path) { - await copyCompressedImageToTarget(source: renderImageFile, target: Files.timetable.backgroundFile.path); - await img.evict(); - if (!mounted) return; - await precacheImage(img, context); - } + return; + } + final renderImageFile = this.renderImageFile; + if (renderImageFile == null) return; + Settings.timetable.backgroundImage = background; + if (renderImageFile.path != Files.timetable.backgroundFile.path) { + await copyCompressedImageToTarget(source: renderImageFile, target: Files.timetable.backgroundFile.path); + await img.evict(); if (!mounted) return; - context.pop(background); + await precacheImage(img, context); } + if (!mounted) return; + context.pop(background); } Future copyCompressedImageToTarget({ @@ -127,6 +154,26 @@ class _TimetableBackgroundEditorState extends State w } } + Future chooseImage() async { + if (kIsWeb) { + await inputImageUrl(); + } else { + await pickImage(); + } + } + + Future inputImageUrl() async { + final url = await Editor.showStringEditor( + context, + desc: "Image URL", + initial: rawPath ?? "", + ); + if (url == null) return; + setState(() { + rawPath = url; + }); + } + Future pickImage() async { final picker = ImagePicker(); final XFile? fi = await picker.pickImage( @@ -156,7 +203,7 @@ class _TimetableBackgroundEditorState extends State w Widget buildToolBar() { return [ FilledButton.icon( - onPressed: pickImage, + onPressed: chooseImage, icon: Icon(context.icons.create), label: i18n.choose.text(), ), @@ -185,20 +232,31 @@ class _TimetableBackgroundEditorState extends State w Widget buildPreviewBoxContent() { final renderImageFile = this.renderImageFile; final height = context.mediaQuery.size.height / 3; + final rawPath = this.rawPath; + final filterQuality = antialias ? FilterQuality.low : FilterQuality.none; if (renderImageFile != null) { return ModalImageViewer( child: Image.file( renderImageFile, opacity: $opacity, height: height, - filterQuality: antialias ? FilterQuality.low : FilterQuality.none, + filterQuality: filterQuality, + ), + ); + } else if (kIsWeb && rawPath != null) { + return ModalImageViewer( + child: Image.network( + rawPath, + opacity: $opacity, + height: height, + filterQuality: filterQuality, ), ); } else { return LeavingBlank( icon: Icons.add_photo_alternate_outlined, desc: i18n.p13n.background.pickTip, - onIconTap: renderImageFile == null ? pickImage : null, + onIconTap: renderImageFile == null ? chooseImage : null, ).sized(h: height); } } diff --git a/lib/timetable/page/p13n/cell_style.dart b/lib/timetable/page/p13n/cell_style.dart index d70239edf..1ae9d293f 100644 --- a/lib/timetable/page/p13n/cell_style.dart +++ b/lib/timetable/page/p13n/cell_style.dart @@ -9,6 +9,7 @@ import '../../widgets/style.dart'; import '../../i18n.dart'; import 'palette.dart'; +/// Persist changes to storage before route popping class TimetableCellStyleEditor extends StatefulWidget { const TimetableCellStyleEditor({super.key}); diff --git a/lib/timetable/widgets/timetable/board.dart b/lib/timetable/widgets/timetable/board.dart index a9d9260cf..4c35a4745 100644 --- a/lib/timetable/widgets/timetable/board.dart +++ b/lib/timetable/widgets/timetable/board.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:rettulf/rettulf.dart'; import 'package:sit/files.dart'; @@ -97,12 +98,23 @@ class _TimetableBackgroundState extends State with SingleTi @override Widget build(BuildContext context) { final bk = widget.background; + final filterQuality = bk.antialias ? FilterQuality.low : FilterQuality.none; + final repeat = bk.repeat ? ImageRepeat.repeat : ImageRepeat.noRepeat; + if (kIsWeb) { + return Image.network( + key: ValueKey(bk.path), + bk.path, + opacity: $opacity, + filterQuality: filterQuality, + repeat: repeat, + ); + } return Image.file( key: ValueKey(bk.path), Files.timetable.backgroundFile, opacity: $opacity, - filterQuality: bk.antialias ? FilterQuality.low : FilterQuality.none, - repeat: bk.repeat ? ImageRepeat.repeat : ImageRepeat.noRepeat, + filterQuality: filterQuality, + repeat: repeat, ); } }