diff --git a/lib/components/base/image.dart b/lib/components/base/image.dart new file mode 100644 index 0000000..c2fb1da --- /dev/null +++ b/lib/components/base/image.dart @@ -0,0 +1,66 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:moodiary/utils/media_util.dart'; + +class ThumbnailImage extends StatelessWidget { + final String imagePath; + final int size; + final BoxFit? fit; + final Function() onTap; + + const ThumbnailImage({ + super.key, + required this.imagePath, + required this.size, + this.fit, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + final fileImage = FileImage(File(imagePath)); + final devicePixelRatio = MediaQuery.devicePixelRatioOf(context); + final colorScheme = Theme.of(context).colorScheme; + final Future getAspectRatio = MediaUtil.getImageAspectRatio(fileImage); + return FutureBuilder( + future: getAspectRatio, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + final aspectRatio = snapshot.data as double; + return GestureDetector( + onTap: onTap, + child: Hero( + tag: imagePath, + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: ResizeImage( + fileImage, + width: aspectRatio < 1.0 + ? size * devicePixelRatio.toInt() + : null, + height: aspectRatio >= 1.0 + ? (size * devicePixelRatio).toInt() + : null, + ), + fit: fit ?? BoxFit.cover, + ), + ), + ), + ), + ); + } else { + return Container( + color: colorScheme.surfaceContainer, + child: const Center( + child: Icon( + Icons.image_search_rounded, + ), + ), + ); + } + }, + ); + } +} diff --git a/lib/components/loading/loading.dart b/lib/components/loading/loading.dart index 78509ab..2a9a0bb 100644 --- a/lib/components/loading/loading.dart +++ b/lib/components/loading/loading.dart @@ -2,7 +2,9 @@ import 'package:flutter/material.dart'; import 'package:rive_animated_icon/rive_animated_icon.dart'; class Processing extends StatelessWidget { - const Processing({super.key}); + final Color? color; + + const Processing({super.key, this.color}); @override Widget build(BuildContext context) { @@ -11,7 +13,7 @@ class Processing extends StatelessWidget { riveIcon: RiveIcon.reload, width: 80, height: 80, - color: colorScheme.onSurface, + color: color ?? colorScheme.onSurface, strokeWidth: 4.0, ); } diff --git a/lib/components/markdown_bar/markdown_bar.dart b/lib/components/markdown_bar/markdown_bar.dart index 0fd76dc..533fad7 100644 --- a/lib/components/markdown_bar/markdown_bar.dart +++ b/lib/components/markdown_bar/markdown_bar.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'parser.dart'; @@ -639,22 +640,28 @@ class MarkdownToolbarState extends State { widget.useIncludedTextField ? _includedFocusNode.requestFocus() : widget.focusNode?.requestFocus(); - Format.toolbarItemPressed( - markdownToolbarOption: markdownToolbarOption, - controller: widget.useIncludedTextField - ? _includedController - : widget.controller ?? _includedController, - selection: widget.useIncludedTextField - ? _includedController.selection - : widget.controller?.selection ?? _includedController.selection, - option: option, - customBoldCharacter: widget.boldCharacter, - customItalicCharacter: widget.italicCharacter, - customCodeCharacter: widget.codeCharacter, - customBulletedListCharacter: widget.bulletedListCharacter, - customHorizontalRuleCharacter: widget.horizontalRuleCharacter, - mediaPath: mediaPath, - ); + try { + Format.toolbarItemPressed( + markdownToolbarOption: markdownToolbarOption, + controller: widget.useIncludedTextField + ? _includedController + : widget.controller ?? _includedController, + selection: widget.useIncludedTextField + ? _includedController.selection + : widget.controller?.selection ?? _includedController.selection, + option: option, + customBoldCharacter: widget.boldCharacter, + customItalicCharacter: widget.italicCharacter, + customCodeCharacter: widget.codeCharacter, + customBulletedListCharacter: widget.bulletedListCharacter, + customHorizontalRuleCharacter: widget.horizontalRuleCharacter, + mediaPath: mediaPath, + ); + } catch (e) { + if (kDebugMode) { + print(e); + } + } } @override diff --git a/lib/components/media/media_audio_view.dart b/lib/components/media/media_audio_view.dart index 4a27a1a..28e6e71 100644 --- a/lib/components/media/media_audio_view.dart +++ b/lib/components/media/media_audio_view.dart @@ -18,7 +18,7 @@ class MediaAudioComponent extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(4.0), child: Text( DateFormat.yMMMEd().format(dateTime), style: textStyle.titleSmall?.copyWith(color: colorScheme.secondary), diff --git a/lib/components/media/media_image_view.dart b/lib/components/media/media_image_view.dart index 1d4cfd6..d488d17 100644 --- a/lib/components/media/media_image_view.dart +++ b/lib/components/media/media_image_view.dart @@ -1,8 +1,6 @@ -import 'dart:io'; - import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; -import 'package:moodiary/common/values/border.dart'; +import 'package:moodiary/components/base/image.dart'; import 'package:moodiary/router/app_routes.dart'; import 'package:refreshed/refreshed.dart'; @@ -19,7 +17,6 @@ class MediaImageComponent extends StatelessWidget { @override Widget build(BuildContext context) { - final pixelRatio = MediaQuery.devicePixelRatioOf(context); final textStyle = Theme.of(context).textTheme; final colorScheme = Theme.of(context).colorScheme; @@ -28,9 +25,9 @@ class MediaImageComponent extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(4.0), child: Text( - DateFormat.yMMMEd().format(dateTime), + DateFormat.yMMMMEEEEd().format(dateTime), style: textStyle.titleSmall?.copyWith(color: colorScheme.secondary), ), ), @@ -38,27 +35,19 @@ class MediaImageComponent extends StatelessWidget { gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 120, childAspectRatio: 1.0, + crossAxisSpacing: 1.0, + mainAxisSpacing: 1.0, ), physics: const NeverScrollableScrollPhysics(), - padding: EdgeInsets.zero, + padding: const EdgeInsets.all(4.0), shrinkWrap: true, itemBuilder: (context, index) { - return InkWell( - borderRadius: AppBorderRadius.mediumBorderRadius, + return ThumbnailImage( + imagePath: imageList[index], + size: 120, onTap: () { _toPhotoView(index, imageList); }, - child: Hero( - tag: imageList[index], - child: Card( - clipBehavior: Clip.hardEdge, - child: Image.file( - File(imageList[index]), - fit: BoxFit.cover, - cacheWidth: 120 * pixelRatio.toInt(), - ), - ), - ), ); }, itemCount: imageList.length, diff --git a/lib/components/media/media_video_view.dart b/lib/components/media/media_video_view.dart index 8573d2a..da794a2 100644 --- a/lib/components/media/media_video_view.dart +++ b/lib/components/media/media_video_view.dart @@ -1,9 +1,7 @@ -import 'dart:io'; - import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:intl/intl.dart'; -import 'package:moodiary/common/values/border.dart'; +import 'package:moodiary/components/base/image.dart'; import 'package:moodiary/router/app_routes.dart'; import 'package:path/path.dart'; import 'package:refreshed/refreshed.dart'; @@ -22,7 +20,6 @@ class MediaVideoComponent extends StatelessWidget { @override Widget build(BuildContext context) { - final pixelRatio = MediaQuery.devicePixelRatioOf(context); final textStyle = Theme.of(context).textTheme; final colorScheme = Theme.of(context).colorScheme; // 将视频路径转换为缩略图路径 @@ -36,7 +33,7 @@ class MediaVideoComponent extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(4.0), child: Text( DateFormat.yMMMEd().format(dateTime), style: textStyle.titleSmall?.copyWith(color: colorScheme.secondary), @@ -44,34 +41,26 @@ class MediaVideoComponent extends StatelessWidget { ), GridView.builder( gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: 120, - childAspectRatio: 1.0, - ), + maxCrossAxisExtent: 120, + childAspectRatio: 1.0, + crossAxisSpacing: 1.0, + mainAxisSpacing: 1.0), physics: const NeverScrollableScrollPhysics(), - padding: EdgeInsets.zero, + padding: const EdgeInsets.all(4.0), shrinkWrap: true, itemBuilder: (context, index) { - return InkWell( - borderRadius: AppBorderRadius.mediumBorderRadius, - onTap: () { - _toVideoView(videoList, index); - }, - child: Stack( - alignment: Alignment.center, - children: [ - Positioned.fill( - child: Card( - clipBehavior: Clip.hardEdge, - child: Image.file( - File(thumbnailList[index]), - fit: BoxFit.cover, - cacheWidth: 120 * pixelRatio.toInt(), - ), - ), - ), - const FaIcon(FontAwesomeIcons.play) - ], - ), + return Stack( + alignment: Alignment.center, + children: [ + ThumbnailImage( + imagePath: thumbnailList[index], + size: 120, + onTap: () { + _toVideoView(videoList, index); + }, + ), + const FaIcon(FontAwesomeIcons.play) + ], ); }, itemCount: thumbnailList.length, diff --git a/lib/pages/diary_details/diary_details_logic.dart b/lib/pages/diary_details/diary_details_logic.dart index bb721ab..cd4d14d 100644 --- a/lib/pages/diary_details/diary_details_logic.dart +++ b/lib/pages/diary_details/diary_details_logic.dart @@ -41,9 +41,11 @@ class DiaryDetailsLogic extends GetxController { } //点击图片跳转到图片预览页面 - void toPhotoView(List imagePathList, int index) { + void toPhotoView( + List imagePathList, int index, BuildContext context) { HapticFeedback.selectionClick(); Get.toNamed(AppRoutes.photoPage, arguments: [imagePathList, index]); + // showImagePreview(context, imagePathList, index); } //点击视频跳转到视频预览页面 diff --git a/lib/pages/diary_details/diary_details_view.dart b/lib/pages/diary_details/diary_details_view.dart index 3891f70..8c2e488 100644 --- a/lib/pages/diary_details/diary_details_view.dart +++ b/lib/pages/diary_details/diary_details_view.dart @@ -256,6 +256,7 @@ class DiaryDetailsPage extends StatelessWidget { 'image', state.diary.imageName[i]); }), index, + context, ); }, child: Image.file( diff --git a/lib/pages/edit/edit_logic.dart b/lib/pages/edit/edit_logic.dart index e3fbf9a..07b2141 100644 --- a/lib/pages/edit/edit_logic.dart +++ b/lib/pages/edit/edit_logic.dart @@ -306,9 +306,9 @@ class EditLogic extends GetxController { } //预览图片 - void toPhotoView(List imagePath, int index) { - Get.toNamed(AppRoutes.photoPage, arguments: [imagePath, index]); - } + // void toPhotoView(List imagePath, int index) { + // Get.toNamed(AppRoutes.photoPage, arguments: [imagePath, index]); + // } //预览视频 void toVideoView(List videoPath, int index) { diff --git a/lib/pages/home/diary/diary_logic.dart b/lib/pages/home/diary/diary_logic.dart index 8c38103..0bfea75 100644 --- a/lib/pages/home/diary/diary_logic.dart +++ b/lib/pages/home/diary/diary_logic.dart @@ -36,7 +36,6 @@ class DiaryLogic extends GetxController with GetTickerProviderStateMixin { @override void onReady() { getHitokoto(); - //监听 tab tabController.addListener(_tabBarListener); //监听 inner @@ -79,7 +78,7 @@ class DiaryLogic extends GetxController with GetTickerProviderStateMixin { checkPageChange(); // 检查是否显示顶部内容 _checkShowTop(); - //homeLogic.resetNavigatorBar(); + homeLogic.resetNavigatorBar(); } /// 跳转到指定分类 diff --git a/lib/pages/home/home_logic.dart b/lib/pages/home/home_logic.dart index 8139265..9a48e2b 100644 --- a/lib/pages/home/home_logic.dart +++ b/lib/pages/home/home_logic.dart @@ -31,7 +31,7 @@ class HomeLogic extends GetxController with GetTickerProviderStateMixin { CurvedAnimation( parent: _barAnimationController, curve: Curves.easeInOut)); - //late final PageController pageController = PageController(); + late final PageController pageController = PageController(); late final FrostedGlassOverlayLogic frostedGlassOverlayLogic = Bind.find(); @@ -58,7 +58,7 @@ class HomeLogic extends GetxController with GetTickerProviderStateMixin { void onClose() { _fabAnimationController.dispose(); _barAnimationController.dispose(); - //pageController.dispose(); + pageController.dispose(); _appLifecycleListener.dispose(); super.onClose(); } @@ -100,7 +100,7 @@ class HomeLogic extends GetxController with GetTickerProviderStateMixin { void changeNavigator(int index) { navigatorIndex.value = index; shouldShow.value = index == 0; - //pageController.jumpToPage(index); + pageController.jumpToPage(index); } Future hideNavigatorBar() async { diff --git a/lib/pages/home/home_view.dart b/lib/pages/home/home_view.dart index e887637..ba226d2 100644 --- a/lib/pages/home/home_view.dart +++ b/lib/pages/home/home_view.dart @@ -109,23 +109,17 @@ class HomePage extends StatelessWidget { key: const ValueKey('body'), builder: (_) { return AdaptiveBackground( - child: Obx( - () { - return AnimatedSwitcher( - duration: const Duration(milliseconds: 150), - reverseDuration: - const Duration(milliseconds: 100), - key: logic.bodyKey, - child: switch (logic.navigatorIndex.value) { - 0 => const DiaryPage(), - 1 => const CalendarPage(), - 2 => const MediaPage(), - 3 => const SettingPage(), - _ => throw UnimplementedError(), - }); - }, - ), - ); + child: PageView( + key: logic.bodyKey, + controller: logic.pageController, + physics: const NeverScrollableScrollPhysics(), + children: const [ + DiaryPage(), + CalendarPage(), + MediaPage(), + SettingPage(), + ], + )); }, ) }, diff --git a/lib/pages/home/media/media_view.dart b/lib/pages/home/media/media_view.dart index 65df6ac..cd4dafc 100644 --- a/lib/pages/home/media/media_view.dart +++ b/lib/pages/home/media/media_view.dart @@ -126,7 +126,9 @@ class MediaPage extends StatelessWidget { state.datetimeMediaMap[datetime]!; return switch (state.mediaType.value) { MediaType.image => MediaImageComponent( - dateTime: datetime, imageList: fileList), + dateTime: datetime, + imageList: fileList, + ), MediaType.audio => MediaAudioComponent( dateTime: datetime, audioList: fileList), MediaType.video => MediaVideoComponent( diff --git a/lib/pages/image/image_logic.dart b/lib/pages/image/image_logic.dart index 4dde71e..6e37e2e 100644 --- a/lib/pages/image/image_logic.dart +++ b/lib/pages/image/image_logic.dart @@ -5,8 +5,13 @@ import 'image_state.dart'; class ImageLogic extends GetxController { final ImageState state = ImageState(); - late final PageController pageController = - PageController(initialPage: state.imageIndex.value); + late final PageController pageController; + + @override + void onInit() { + pageController = PageController(initialPage: state.imageIndex.value); + super.onInit(); + } @override void onClose() { diff --git a/lib/pages/image/image_view.dart b/lib/pages/image/image_view.dart index 20cef1b..6669832 100644 --- a/lib/pages/image/image_view.dart +++ b/lib/pages/image/image_view.dart @@ -3,12 +3,12 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:moodiary/common/values/media_type.dart'; import 'package:moodiary/components/base/button.dart'; +import 'package:moodiary/components/loading/loading.dart'; +import 'package:moodiary/pages/image/image_logic.dart'; import 'package:moodiary/utils/media_util.dart'; import 'package:photo_view/photo_view.dart'; import 'package:photo_view/photo_view_gallery.dart'; -import 'package:refreshed/refreshed.dart'; - -import 'image_logic.dart'; +import 'package:refreshed/get_state_manager/get_state_manager.dart'; class ImagePage extends StatelessWidget { const ImagePage({super.key}); @@ -24,15 +24,15 @@ class ImagePage extends StatelessWidget { extendBodyBehindAppBar: true, appBar: AppBar( backgroundColor: colorScheme.scrim.withAlpha((255 * 0.6).toInt()), - title: Obx(() { - return Visibility( - visible: state.imagePathList.length > 1, - child: Text( + title: Visibility( + visible: state.imagePathList.length > 1, + child: Obx(() { + return Text( '${state.imageIndex.value + 1}/${state.imagePathList.length}', style: const TextStyle(color: Colors.white), - ), - ); - }), + ); + }), + ), iconTheme: const IconThemeData(color: Colors.white), actions: [ IconButton( @@ -49,7 +49,12 @@ class ImagePage extends StatelessWidget { children: [ PhotoViewGallery.builder( scrollPhysics: const PageScrollPhysics(), + backgroundDecoration: const BoxDecoration( + color: Colors.black, + ), pageController: logic.pageController, + wantKeepAlive: true, + gaplessPlayback: true, builder: (BuildContext context, int index) { return PhotoViewGalleryPageOptions( imageProvider: FileImage(File(state.imagePathList[index])), @@ -60,8 +65,14 @@ class ImagePage extends StatelessWidget { }, itemCount: state.imagePathList.length, onPageChanged: logic.changePage, - loadingBuilder: (context, event) => - const Center(child: CircularProgressIndicator()), + loadingBuilder: (context, event) { + return Container( + color: Colors.black, + child: const Center( + child: Processing(color: Colors.white), + ), + ); + }, ), Obx(() { return Visibility( diff --git a/lib/pages/web_view/web_view_view.dart b/lib/pages/web_view/web_view_view.dart index 8cd3298..6f44976 100644 --- a/lib/pages/web_view/web_view_view.dart +++ b/lib/pages/web_view/web_view_view.dart @@ -58,7 +58,7 @@ class WebViewPage extends StatelessWidget { child: InAppWebView( initialUrlRequest: URLRequest(url: WebUri(state.url)), initialSettings: logic.webSettings, - // pullToRefreshController: logic.pullToRefreshController, + // pullToRefreshController: logic.pullToRefreshController, onWebViewCreated: (controller) { logic.webViewController = controller; }, diff --git a/lib/utils/media_util.dart b/lib/utils/media_util.dart index 9a91eff..5ff3e2f 100644 --- a/lib/utils/media_util.dart +++ b/lib/utils/media_util.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:collection'; import 'dart:io'; import 'package:fc_native_video_thumbnail/fc_native_video_thumbnail.dart'; @@ -185,24 +186,47 @@ class MediaUtil { return completer.future; } - //获取图片宽高比例 + static const int _maxCacheSize = 1000; + static final LinkedHashMap _cache = LinkedHashMap(); + static Future getImageAspectRatio(ImageProvider imageProvider) async { - final Completer completer = Completer(); - final ImageStream stream = - imageProvider.resolve(const ImageConfiguration()); - stream.addListener( - ImageStreamListener( - (ImageInfo info, bool _) { - final double aspectRatio = double.parse( - (info.image.width.toDouble() / info.image.height.toDouble()) - .toStringAsPrecision(2)); - completer.complete(aspectRatio); - }, - ), + final key = _getImageKey(imageProvider); + + if (_cache.containsKey(key)) { + return _cache[key]!; + } + + final completer = Completer(); + final imageStream = imageProvider.resolve(const ImageConfiguration()); + imageStream.addListener( + ImageStreamListener((ImageInfo info, bool _) { + final aspectRatio = info.image.width / info.image.height; + _addToCache(key, aspectRatio); + completer.complete(aspectRatio); + }), ); + return completer.future; } + static void _addToCache(String key, double ratio) { + if (_cache.length >= _maxCacheSize) { + _cache.remove(_cache.keys.first); + } + _cache[key] = ratio; + } + + static String _getImageKey(ImageProvider imageProvider) { + if (imageProvider is AssetImage) { + return 'asset_${imageProvider.assetName}'; + } else if (imageProvider is NetworkImage) { + return 'network_${imageProvider.url}'; + } else if (imageProvider is FileImage) { + return 'file_${imageProvider.file.path}'; + } + return imageProvider.toString(); + } + //获取单个图片,拍照或者相册 static Future pickPhoto(ImageSource imageSource) async { return await _picker.pickImage(