From aa7428884ab71324fe65d0d19a9118a073f61436 Mon Sep 17 00:00:00 2001 From: Amarillys Date: Thu, 2 Mar 2023 03:25:07 +0800 Subject: [PATCH] feat: 1. add photo_view to improve experience. 2. improve performance. 3. add drawer for portrait mode. 4. improve UI. --- lib/components/collection.dart | 3 +- lib/components/file_manager.dart | 9 ++-- lib/components/fullscreen_viewer.dart | 17 +++--- lib/components/menu.dart | 8 +-- lib/components/sidebar.dart | 28 +++++++--- lib/components/storage_bar.dart | 1 + lib/events/events_definition.dart | 4 ++ lib/l10n/app_en.arb | 3 +- lib/l10n/app_ja.arb | 3 +- lib/l10n/app_zh.arb | 3 +- lib/l10n/app_zh_tw.arb | 3 +- lib/main.dart | 4 +- lib/pages/main.dart | 78 ++++++++++++++++++++------- lib/pages/viewer.dart | 70 +++++++++++++++++------- lib/storage/android.dart | 5 ++ lib/storage/base.dart | 2 + lib/storage/windows.dart | 9 ++++ lib/utils/image_util.dart | 7 ++- lib/utils/string_util.dart | 7 +++ pubspec.lock | 8 +++ pubspec.yaml | 1 + 21 files changed, 203 insertions(+), 70 deletions(-) diff --git a/lib/components/collection.dart b/lib/components/collection.dart index 3310510..d82dc88 100644 --- a/lib/components/collection.dart +++ b/lib/components/collection.dart @@ -2,6 +2,7 @@ import 'dart:core'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:global_configs/global_configs.dart'; +import 'package:path/path.dart' as p; import 'package:chiyo_gallery/events/eventbus.dart'; import 'package:chiyo_gallery/events/events_definition.dart'; @@ -58,7 +59,7 @@ class _CollectionBarState extends State { margin: const EdgeInsets.all(10), child: Icon(Icons.folder_special, size: 40, color: ImageUtil.mapColorFromString(GlobalConfigs().get('baseColor')))), - Text(collections[i]) + Text(p.basename(collections[i])) ]) )); } diff --git a/lib/components/file_manager.dart b/lib/components/file_manager.dart index 70ccf4f..6b1fe45 100644 --- a/lib/components/file_manager.dart +++ b/lib/components/file_manager.dart @@ -27,7 +27,7 @@ class FileBrowser extends StatefulWidget { class ViewerState extends State { static final eventBus = GlobalEventBus.instance; - static const int rowWidth = 330; + static const int rowWidth = 300; List files = []; List histories = [""]; List selected = []; @@ -39,6 +39,7 @@ class ViewerState extends State { final _scrollController = ScrollController(); Color _baseColor = Colors.green; bool showEmptyFile = false; + Executor executor = Executor(concurrency: 2); @override void initState() { @@ -70,6 +71,9 @@ class ViewerState extends State { } void initPath([String params = '']) async { + if (executor.waitingCount > 0) { + executor.close(); + } var fetchedFiles = await widget.controller.fetchFile(params: params, sortType: sortType); if (!showEmptyFile) { @@ -99,8 +103,7 @@ class ViewerState extends State { } fetchThumb(Iterable files) async { - final generateThread = Platform.isWindows ? 3 : 2; - final executor = Executor(concurrency: generateThread); + executor = Executor(concurrency: 2); for (var i = 0; i < files.length; ++i) { executor.scheduleTask(() async { final newThumbFile = diff --git a/lib/components/fullscreen_viewer.dart b/lib/components/fullscreen_viewer.dart index b343178..2fe45ee 100644 --- a/lib/components/fullscreen_viewer.dart +++ b/lib/components/fullscreen_viewer.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_avif/flutter_avif.dart'; +import 'package:photo_view/photo_view.dart'; class FullScreenViewer extends StatefulWidget { final String imagePath; @@ -13,17 +14,19 @@ class FullScreenViewer extends StatefulWidget { class FullScreenViewerState extends State { @override Widget build(BuildContext context) { - StatefulWidget imageWidget; + ImageProvider imageWidget; if (widget.imagePath.contains('.avif')) { - imageWidget = AvifImage.file(File(widget.imagePath), fit: BoxFit.contain); + imageWidget = AvifImage.file(File(widget.imagePath), fit: BoxFit.contain).image; } else { - imageWidget = Image.file(File(widget.imagePath), fit: BoxFit.contain); + imageWidget = Image.file(File(widget.imagePath), fit: BoxFit.contain).image; } return Scaffold( body: Stack( - children: [ - Positioned.fill(child: imageWidget) - ], - )); + children: [ + Positioned.fill(child: + PhotoView(imageProvider: imageWidget) + ) + ] + )); } } diff --git a/lib/components/menu.dart b/lib/components/menu.dart index 9e48741..71e9a07 100644 --- a/lib/components/menu.dart +++ b/lib/components/menu.dart @@ -1,6 +1,8 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + import 'package:chiyo_gallery/events/eventbus.dart'; import 'package:chiyo_gallery/events/events_definition.dart'; -import 'package:flutter/material.dart'; class ContextMenu extends StatefulWidget { const ContextMenu({super.key}); @@ -24,9 +26,9 @@ class _ContextMenuState extends State { if (event.itemIndexes.length == 1) { setState(() { options = [ - const PopupMenuItem( + PopupMenuItem( value: 'add_collection', - child: Text('添加到收藏夹'), + child: Text(AppLocalizations.of(context)!.addToCollection), ), const PopupMenuItem( value: 'move', diff --git a/lib/components/sidebar.dart b/lib/components/sidebar.dart index 0dda0c0..dca1cd7 100644 --- a/lib/components/sidebar.dart +++ b/lib/components/sidebar.dart @@ -1,10 +1,12 @@ import 'package:flutter/material.dart'; +import 'package:global_configs/global_configs.dart'; import 'package:chiyo_gallery/components/storage_bar.dart'; import 'package:chiyo_gallery/components/collection.dart'; class SideBar extends StatefulWidget { - const SideBar({ super.key }); + final bool isPortrait; + const SideBar({ super.key, required this.isPortrait }); @override State createState() => SideBarState(); @@ -20,11 +22,25 @@ class SideBarState extends State { @override Widget build(BuildContext context) { - return Column( - children: [ - storageBar, - const CollectionBar() - ], + var widgets = [ + storageBar, + const CollectionBar() + ]; + if (widget.isPortrait) { + widgets.insert(0, Container( + margin: const EdgeInsets.only(top: 30), + height: 59, + color: Colors.pink, + child: const Align( + alignment: Alignment.center, + child: Text("CHIYO GALLERY", style: TextStyle(fontSize: 30, color: Colors.white)) + ) + )); + } + return Container( + width: widget.isPortrait ? MediaQuery.of(context).size.width / 1.5 : null, + color: widget.isPortrait ? const Color.fromRGBO(250, 250, 250, 0.9) : Colors.transparent, + child: Column(children: widgets) ); } } \ No newline at end of file diff --git a/lib/components/storage_bar.dart b/lib/components/storage_bar.dart index eb11acd..d410660 100644 --- a/lib/components/storage_bar.dart +++ b/lib/components/storage_bar.dart @@ -44,6 +44,7 @@ class StorageBar extends StatelessWidget { onItemTap(String path) { eventBus.fire(ChangePathEvent(path)); + eventBus.fire(CloseDrawerEvent()); } static Widget setupIconByType(int index) { diff --git a/lib/events/events_definition.dart b/lib/events/events_definition.dart index 026039d..2164195 100644 --- a/lib/events/events_definition.dart +++ b/lib/events/events_definition.dart @@ -24,4 +24,8 @@ class ItemChooseEvent { List itemIndexes; List paths; ItemChooseEvent(this.itemIndexes, this.paths); +} + +class CloseDrawerEvent { + CloseDrawerEvent(); } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 6629f31..fcb32f5 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -27,5 +27,6 @@ "collection": "Collection", "storageDevice": "Storage Devices", "internalStorage": "Internal Storage", - "externalStorage": "External Storage" + "externalStorage": "External Storage", + "addToCollection": "Add to Collections" } \ No newline at end of file diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 2f72859..73c98ce 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -8,5 +8,6 @@ "collection": "コレクション", "storageDevice": "Storage Devices", "internalStorage": "Internal Storage", - "externalStorage": "External Storage" + "externalStorage": "External Storage", + "addToCollection": "Add to Collections" } \ No newline at end of file diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 2f9641c..b90855c 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -8,5 +8,6 @@ "collection": "收藏夹", "storageDevice": "存储设备", "internalStorage": "手机存储", - "externalStorage": "外部存储" + "externalStorage": "外部存储", + "addToCollection": "添加到收藏夹" } \ No newline at end of file diff --git a/lib/l10n/app_zh_tw.arb b/lib/l10n/app_zh_tw.arb index 2a2c987..864335c 100644 --- a/lib/l10n/app_zh_tw.arb +++ b/lib/l10n/app_zh_tw.arb @@ -8,5 +8,6 @@ "collection": "收藏夹", "storageDevice": "Storage Devices", "internalStorage": "Internal Storage", - "externalStorage": "External Storage" + "externalStorage": "External Storage", + "addToCollection": "添加到收藏夾" } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 8963434..def78f8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:global_configs/global_configs.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import './storage/storage.dart'; -import './pages/main.dart'; +import 'package:chiyo_gallery/storage/storage.dart'; +import 'package:chiyo_gallery/pages/main.dart'; void main() async { WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); diff --git a/lib/pages/main.dart b/lib/pages/main.dart index a014804..3e89088 100644 --- a/lib/pages/main.dart +++ b/lib/pages/main.dart @@ -1,3 +1,6 @@ + +import 'package:chiyo_gallery/utils/string_util.dart'; +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:toast/toast.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -8,7 +11,7 @@ import 'package:chiyo_gallery/components/sidebar.dart'; import 'package:chiyo_gallery/components/menu.dart'; import 'package:chiyo_gallery/events/eventbus.dart'; import 'package:chiyo_gallery/events/events_definition.dart'; - +import 'package:chiyo_gallery/storage/storage.dart'; class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); @@ -21,6 +24,15 @@ class MyHomePage extends StatefulWidget { class _MyHomePageState extends State { static final eventBus = GlobalEventBus.instance; + final GlobalKey _scaffoldKey = GlobalKey(); + + @override + void initState() { + super.initState(); + eventBus.on().listen((event) { + _scaffoldKey.currentState?.closeDrawer(); + }); + } @override Widget build(BuildContext context) { @@ -28,19 +40,20 @@ class _MyHomePageState extends State { return OrientationBuilder( builder: (BuildContext context, Orientation orientation) { bool isPortrait = orientation == Orientation.portrait; - List titleComps = [const TitleBar()]; + final sideBarWidget = SideBar(isPortrait: isPortrait); + List titleComps = [TitleBar(isPortrait: isPortrait)]; if (!isPortrait) { titleComps.insert(0, Container( - margin: const EdgeInsets.only(bottom: 3, right: 15), - child: const Text("CHIYO GALLERY", style: TextStyle(fontSize: 24)))); + margin: const EdgeInsets.only(bottom: 3), + child: const Text("CHIYO GALLERY - ", style: TextStyle(fontSize: 24)))); } return Scaffold( + key: _scaffoldKey, appBar: AppBar( title: Row(children: titleComps), leading: isPortrait ? IconButton( icon: const Icon(Icons.menu), - onPressed: () { - }, + onPressed: () { _scaffoldKey.currentState?.openDrawer(); }, ) : IconButton( icon: const Icon(Icons.arrow_back_ios_outlined), onPressed: () { @@ -56,22 +69,24 @@ class _MyHomePageState extends State { ), body: isPortrait ? Row(children: const [Expanded(flex: 1, child: BrowserPage())]) - : Row(children: const [ - Expanded(flex: 2, child: SideBar()), - Expanded(flex: 5, child: BrowserPage()) + : Row(children: [ + Expanded(flex: 2, child: sideBarWidget), + const Expanded(flex: 6, child: BrowserPage()) ]), floatingActionButton: FloatingActionButton( onPressed: () {}, tooltip: 'Increment', child: const Icon(Icons.add) - ) + ), + drawer: sideBarWidget ); }); } } class TitleBar extends StatefulWidget { - const TitleBar({ super.key }); + final bool isPortrait; + const TitleBar({ super.key, required this.isPortrait }); @override State createState() => _TitleBarState(); @@ -79,6 +94,7 @@ class TitleBar extends StatefulWidget { class _TitleBarState extends State { static final eventBus = GlobalEventBus.instance; + static final storage = Storage.instance; List paths = []; @override @@ -87,22 +103,44 @@ class _TitleBarState extends State { eventBus.on().listen((event) { setState(() { paths = p.split(event.path); + paths[0] = storage.dealPrefixPath(paths[0]); }); }); } @override Widget build(BuildContext ctx) { + List pathTab = []; + var startIndex = 0; + if (widget.isPortrait && paths.length > 2) { + startIndex = paths.length - 2; + } + + var iconSize = widget.isPortrait ? 15.0 : 18.0; + var fontSize = widget.isPortrait ? 18.0 : 24.0; + var margin = widget.isPortrait ? + const EdgeInsets.only(left: 3, right:3) : + const EdgeInsets.only(bottom: 3, left: 5, right: 5); + for (var i = startIndex; i < paths.length; ++i) { + pathTab.add(InkWell( + onTap: () { + final targetPath = paths.slice(0, i + 1).join('/'); + eventBus.fire(ChangePathEvent(targetPath)); + }, + child: Container( + margin: margin, + child: Text(StringUtil.cutString(paths[i], 9), style: TextStyle(fontSize: fontSize)), + ) + )); + if (i < paths.length - 1) { + pathTab.add(Container( + margin: const EdgeInsets.all(2.0), + child: Icon(Icons.arrow_forward_ios, size: iconSize), + )); + } + } return Row( - children: paths.map((title) { - return InkWell( - onTap: () {}, - child: Container( - margin: const EdgeInsets.only(bottom: 3, right: 20), - child: Text(title), - ), - ); - }).toList() + children: pathTab ); } } \ No newline at end of file diff --git a/lib/pages/viewer.dart b/lib/pages/viewer.dart index 0f83f55..ff87a62 100644 --- a/lib/pages/viewer.dart +++ b/lib/pages/viewer.dart @@ -13,6 +13,8 @@ class ViewerPage extends StatefulWidget { class ViewerState extends State { String imagePath = ''; int currentIndex = 0; + Color buttonBg = const Color.fromRGBO(100, 100, 100, 0.55); + static const iconColor = Color.fromRGBO(233, 233, 233, 0.95); @override void initState() { @@ -23,6 +25,7 @@ class ViewerState extends State { @override Widget build(BuildContext context) { + const radius = BorderRadius.all(Radius.circular(20)); return Scaffold( body: Stack( children: [ @@ -30,26 +33,44 @@ class ViewerState extends State { child: FullScreenViewer(imagePath: imagePath) ), Positioned( - top: 50, - left: 10, - child: IconButton( - icon: const Icon(Icons.arrow_back), - onPressed: () => Navigator.pop(context)) + top: 30, + left: 30, + child: Material( + color: Colors.transparent, + child: ClipRRect( + borderRadius: radius, + child: InkWell( + borderRadius: radius, + onTap: () => Navigator.pop(context), + child: Container( + padding: const EdgeInsets.all(10), + color: const Color.fromRGBO(135, 135, 135, 1), + child:const Icon(Icons.arrow_back, size: 28, color: Colors.white) + )))) ), - Positioned( - top: 650, - left: 500, - child: IconButton( - icon: const Icon(Icons.arrow_left, size: 80, color: Colors.black54), - onPressed: () => prevPicture()) - ), - Positioned( - top: 650, - left: 650, - child: IconButton( - icon: const Icon(Icons.arrow_right, size: 80, color: Colors.black54), - onPressed: () => nextPicture()) - )], + Container( + margin: const EdgeInsets.only(bottom: 15), + child: Align( + alignment: FractionalOffset.bottomCenter, + child: Material( + color: Colors.transparent, + child:ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(20)), + child: Container( + width: 300, + height: 60, + color: buttonBg, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + setupActionIcon(Icons.arrow_back_ios_sharp, prevPicture), + setupActionIcon(Icons.arrow_forward_ios_sharp, nextPicture), + ], + ) + ), + ), + )), + )] ) ); } @@ -76,5 +97,14 @@ class ViewerState extends State { }); } - + setupActionIcon(IconData icon, VoidCallback callback) { + return SizedBox( + width: 60, + height: 60, + child: InkWell( + onTap: callback, + child: Icon(icon, color: iconColor), + ) + ); + } } diff --git a/lib/storage/android.dart b/lib/storage/android.dart index f29764f..68a3378 100644 --- a/lib/storage/android.dart +++ b/lib/storage/android.dart @@ -110,4 +110,9 @@ class AndroidStorage implements BaseStorage { return 'externalStorage'; } } + + @override + String dealPrefixPath(String firstPath) { + return firstPath; + } } diff --git a/lib/storage/base.dart b/lib/storage/base.dart index dcb6d13..8560a21 100644 --- a/lib/storage/base.dart +++ b/lib/storage/base.dart @@ -18,4 +18,6 @@ abstract class BaseStorage { Future openFile(String path); String convertStoragePathForDisplay(String path); + + String dealPrefixPath(String firstPath); } diff --git a/lib/storage/windows.dart b/lib/storage/windows.dart index 49ad980..0596122 100644 --- a/lib/storage/windows.dart +++ b/lib/storage/windows.dart @@ -102,4 +102,13 @@ class WindowsStorage implements BaseStorage { } return partitions; } + + @override + String dealPrefixPath(String firstPath) { + final partitionSignalReg = RegExp(r'^\w:\/$'); + if (partitionSignalReg.hasMatch(firstPath)) { + return firstPath.substring(0, 2).toUpperCase(); + } + return firstPath; + } } diff --git a/lib/utils/image_util.dart b/lib/utils/image_util.dart index 36e1767..f49120c 100644 --- a/lib/utils/image_util.dart +++ b/lib/utils/image_util.dart @@ -59,7 +59,6 @@ class ImageUtil { imageStream.addListener(imageStreamListener); await completer.future; - print(decodeWidth); if (bytes.isEmpty) { return null; } @@ -100,10 +99,10 @@ class ImageUtil { return null; } } - return DefaultCacheManager().putFile( - filePath, thumbnailFiles, - eTag: '${fileStat.size}-${fileStat.modified}'); } + return DefaultCacheManager().putFile( + filePath, thumbnailFiles, + eTag: '${fileStat.size}-${fileStat.modified}'); } return null; } diff --git a/lib/utils/string_util.dart b/lib/utils/string_util.dart index 3b73db2..33ec3f1 100644 --- a/lib/utils/string_util.dart +++ b/lib/utils/string_util.dart @@ -3,4 +3,11 @@ class StringUtil { final dateStr = time.toLocal().toString(); return dateStr.substring(0, dateStr.length - 4); } + + static String cutString(String str, int maxLength) { + if (str.length <= maxLength) { + return str; + } + return '${str.substring(0, maxLength)}...'; + } } \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 6977f44..552dd2c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -565,6 +565,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.1.0" + photo_view: + dependency: "direct main" + description: + name: photo_view + sha256: "8036802a00bae2a78fc197af8a158e3e2f7b500561ed23b4c458107685e645bb" + url: "https://pub.dev" + source: hosted + version: "0.14.0" platform: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 1155ce8..a931302 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -54,6 +54,7 @@ dependencies: sdk: flutter executor: ^2.2.3 event_bus: ^2.0.0 + photo_view: ^0.14.0 dev_dependencies: flutter_test: