mirror of
https://github.com/ZhuJHua/moodiary.git
synced 2026-04-05 16:31:45 +08:00
feat: improve image handling with enhanced hero transitions and dynamic sizing
This commit is contained in:
@@ -29,7 +29,7 @@ class ThumbnailImage extends StatelessWidget {
|
||||
key: ValueKey(imagePath),
|
||||
image: ResizeImage(
|
||||
imageProvider,
|
||||
width: aspectRatio < 1.0 ? size * pixelRatio.toInt() : null,
|
||||
width: aspectRatio < 1.0 ? (size * pixelRatio).toInt() : null,
|
||||
height: aspectRatio >= 1.0 ? (size * pixelRatio).toInt() : null,
|
||||
),
|
||||
fit: fit ?? BoxFit.cover,
|
||||
|
||||
@@ -1,38 +1,36 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:moodiary/router/app_routes.dart';
|
||||
import 'package:moodiary/pages/image/image_view.dart';
|
||||
import 'package:moodiary/utils/file_util.dart';
|
||||
import 'package:refreshed/refreshed.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class MarkdownImageEmbed extends StatelessWidget {
|
||||
final bool isEdit;
|
||||
final String imageName;
|
||||
|
||||
const MarkdownImageEmbed(
|
||||
{super.key, required this.isEdit, required this.imageName});
|
||||
const MarkdownImageEmbed({
|
||||
super.key,
|
||||
required this.isEdit,
|
||||
required this.imageName,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
) {
|
||||
Widget build(BuildContext context) {
|
||||
final imagePath =
|
||||
isEdit ? imageName : FileUtil.getRealPath('image', imageName);
|
||||
|
||||
final heroPrefix = const Uuid().v4();
|
||||
return Center(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (!isEdit) {
|
||||
Get.toNamed(AppRoutes.photoPage, arguments: [
|
||||
[imagePath],
|
||||
0,
|
||||
]);
|
||||
showImageView(context, [imagePath], 0, heroTagPrefix: heroPrefix);
|
||||
}
|
||||
},
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 300),
|
||||
child: Hero(
|
||||
tag: imagePath,
|
||||
tag: '${heroPrefix}0',
|
||||
child: Card.outlined(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
color: Colors.transparent,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
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/pages/image/image_view.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class MediaImageComponent extends StatelessWidget {
|
||||
final DateTime dateTime;
|
||||
@@ -17,38 +19,73 @@ class MediaImageComponent extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final textStyle = Theme.of(context).textTheme;
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
final heroPrefix = const Uuid().v4();
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: Text(
|
||||
DateFormat.yMMMMEEEEd().format(dateTime),
|
||||
style: textStyle.titleSmall?.copyWith(color: colorScheme.secondary),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 4.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
DateFormat.yMMMMEEEEd().format(dateTime),
|
||||
style: textStyle.titleSmall?.copyWith(
|
||||
color: colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${imageList.length} ${imageList.length > 1 ? 'Photos' : 'Photo'}',
|
||||
style: textStyle.labelMedium?.copyWith(
|
||||
color: colorScheme.tertiary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
GridView.builder(
|
||||
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: 120,
|
||||
childAspectRatio: 1.0,
|
||||
crossAxisSpacing: 1.0,
|
||||
mainAxisSpacing: 1.0,
|
||||
),
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (context, index) {
|
||||
return ThumbnailImage(
|
||||
imagePath: imageList[index],
|
||||
size: 120,
|
||||
onTap: () async {
|
||||
await showImageView(context, imageList, index);
|
||||
child: ClipRRect(
|
||||
borderRadius: AppBorderRadius.mediumBorderRadius,
|
||||
child: GridView.builder(
|
||||
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: 120,
|
||||
childAspectRatio: 1.0,
|
||||
crossAxisSpacing: 1.5,
|
||||
mainAxisSpacing: 1.5,
|
||||
),
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (context, index) {
|
||||
final image = ThumbnailImage(
|
||||
imagePath: imageList[index],
|
||||
size: 120,
|
||||
heroTag: '$heroPrefix$index',
|
||||
onTap: () async {
|
||||
await showImageView(
|
||||
context,
|
||||
imageList,
|
||||
index,
|
||||
heroTagPrefix: heroPrefix,
|
||||
);
|
||||
},
|
||||
);
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
await showImageView(
|
||||
context,
|
||||
imageList,
|
||||
index,
|
||||
heroTagPrefix: heroPrefix,
|
||||
);
|
||||
},
|
||||
child: image,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
itemCount: imageList.length,
|
||||
itemCount: imageList.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -5,13 +5,17 @@ import 'package:moodiary/components/base/image.dart';
|
||||
import 'package:moodiary/router/app_routes.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:refreshed/refreshed.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class MediaVideoComponent extends StatelessWidget {
|
||||
final DateTime dateTime;
|
||||
final List<String> videoList;
|
||||
|
||||
const MediaVideoComponent(
|
||||
{super.key, required this.dateTime, required this.videoList});
|
||||
const MediaVideoComponent({
|
||||
super.key,
|
||||
required this.dateTime,
|
||||
required this.videoList,
|
||||
});
|
||||
|
||||
//点击视频跳转到视频预览
|
||||
void _toVideoView(List<String> videoPathList, int index) {
|
||||
@@ -23,11 +27,12 @@ class MediaVideoComponent extends StatelessWidget {
|
||||
final textStyle = Theme.of(context).textTheme;
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
// 将视频路径转换为缩略图路径
|
||||
final thumbnailList = videoList.map((e) {
|
||||
final id = e.split('video-')[1].split('.')[0];
|
||||
return '${dirname(e)}/thumbnail-$id.jpeg';
|
||||
}).toList();
|
||||
|
||||
final thumbnailList =
|
||||
videoList.map((e) {
|
||||
final id = e.split('video-')[1].split('.')[0];
|
||||
return '${dirname(e)}/thumbnail-$id.jpeg';
|
||||
}).toList();
|
||||
final heroPrefix = const Uuid().v4();
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@@ -41,10 +46,11 @@ class MediaVideoComponent extends StatelessWidget {
|
||||
),
|
||||
GridView.builder(
|
||||
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: 120,
|
||||
childAspectRatio: 1.0,
|
||||
crossAxisSpacing: 1.0,
|
||||
mainAxisSpacing: 1.0),
|
||||
maxCrossAxisExtent: 120,
|
||||
childAspectRatio: 1.0,
|
||||
crossAxisSpacing: 1.0,
|
||||
mainAxisSpacing: 1.0,
|
||||
),
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
shrinkWrap: true,
|
||||
@@ -54,12 +60,13 @@ class MediaVideoComponent extends StatelessWidget {
|
||||
children: [
|
||||
ThumbnailImage(
|
||||
imagePath: thumbnailList[index],
|
||||
heroTag: '$heroPrefix$index',
|
||||
size: 120,
|
||||
onTap: () {
|
||||
_toVideoView(videoList, index);
|
||||
},
|
||||
),
|
||||
const FaIcon(FontAwesomeIcons.play)
|
||||
const FaIcon(FontAwesomeIcons.play),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
||||
@@ -2,9 +2,9 @@ import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_quill/flutter_quill.dart';
|
||||
import 'package:moodiary/router/app_routes.dart';
|
||||
import 'package:moodiary/common/values/border.dart';
|
||||
import 'package:moodiary/pages/image/image_view.dart';
|
||||
import 'package:moodiary/utils/file_util.dart';
|
||||
import 'package:refreshed/refreshed.dart';
|
||||
|
||||
class ImageBlockEmbed extends BlockEmbed {
|
||||
const ImageBlockEmbed(String value) : super(embedType, value);
|
||||
@@ -28,34 +28,35 @@ class ImageEmbedBuilder extends EmbedBuilder {
|
||||
String toPlainText(Embed node) => '';
|
||||
|
||||
@override
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
EmbedContext embedContext,
|
||||
) {
|
||||
Widget build(BuildContext context, EmbedContext embedContext) {
|
||||
final imageEmbed = ImageBlockEmbed(embedContext.node.value.data);
|
||||
// 从数据构造 ImageBlockEmbed
|
||||
final imagePath = isEdit
|
||||
? imageEmbed.name
|
||||
: FileUtil.getRealPath('image', imageEmbed.name);
|
||||
|
||||
final imagePath =
|
||||
isEdit
|
||||
? imageEmbed.name
|
||||
: FileUtil.getRealPath('image', imageEmbed.name);
|
||||
return Center(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (!isEdit) {
|
||||
Get.toNamed(AppRoutes.photoPage, arguments: [
|
||||
showImageView(
|
||||
context,
|
||||
[imagePath],
|
||||
0,
|
||||
]);
|
||||
heroTagPrefix: imageEmbed.name,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 300),
|
||||
child: Hero(
|
||||
tag: imagePath,
|
||||
child: Card.outlined(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
color: Colors.transparent,
|
||||
child: Image.file(File(imagePath)),
|
||||
child: Card.outlined(
|
||||
color: Colors.transparent,
|
||||
child: Hero(
|
||||
tag: '${imageEmbed.name}0',
|
||||
child: ClipRRect(
|
||||
borderRadius: AppBorderRadius.mediumBorderRadius,
|
||||
child: Image.file(File(imagePath)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -139,64 +139,59 @@ class ImagePage extends StatelessWidget {
|
||||
tag: _tag,
|
||||
assignId: true,
|
||||
builder: (_) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
DismissiblePage(
|
||||
backgroundColor: Colors.black,
|
||||
onDismissed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
onDragUpdate: (details) {
|
||||
logic.updateOpacity(details.opacity);
|
||||
},
|
||||
direction: DismissiblePageDismissDirection.vertical,
|
||||
isFullScreen: true,
|
||||
minScale: 0.2,
|
||||
dragSensitivity: 0.8,
|
||||
startingOpacity: 0.9,
|
||||
maxTransformValue: 0.6,
|
||||
child: imageView,
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
DismissiblePage(
|
||||
backgroundColor: Colors.black,
|
||||
onDismissed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
onDragUpdate: (details) {
|
||||
logic.updateOpacity(details.opacity);
|
||||
},
|
||||
direction: DismissiblePageDismissDirection.vertical,
|
||||
minScale: 0.2,
|
||||
dragSensitivity: 0.8,
|
||||
startingOpacity: 0.9,
|
||||
maxTransformValue: 0.6,
|
||||
child: imageView,
|
||||
),
|
||||
Positioned(
|
||||
left: 24,
|
||||
right: 24,
|
||||
child: _buildPageButton(
|
||||
previous: logic.previous,
|
||||
next: logic.next,
|
||||
opacity: state.opacity,
|
||||
imageIndex: state.imageIndex,
|
||||
imagePathList: state.imagePathList,
|
||||
),
|
||||
Positioned(
|
||||
left: 24,
|
||||
right: 24,
|
||||
child: _buildPageButton(
|
||||
previous: logic.previous,
|
||||
next: logic.next,
|
||||
opacity: state.opacity,
|
||||
),
|
||||
Positioned(
|
||||
bottom: 24,
|
||||
left: 24,
|
||||
right: 24,
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
child: _buildOperationButton(
|
||||
onSaved: () {
|
||||
MediaUtil.saveToGallery(
|
||||
path: state.imagePathList[state.imageIndex.value],
|
||||
type: MediaType.image,
|
||||
);
|
||||
},
|
||||
onExit: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
imageIndex: state.imageIndex,
|
||||
imagePathList: state.imagePathList,
|
||||
textStyle: textStyle.labelLarge,
|
||||
opacity: state.opacity,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 24,
|
||||
left: 24,
|
||||
right: 24,
|
||||
child: SafeArea(
|
||||
child: _buildOperationButton(
|
||||
onSaved: () {
|
||||
MediaUtil.saveToGallery(
|
||||
path: state.imagePathList[state.imageIndex.value],
|
||||
type: MediaType.image,
|
||||
);
|
||||
},
|
||||
onExit: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
imageIndex: state.imageIndex,
|
||||
imagePathList: state.imagePathList,
|
||||
textStyle: textStyle.labelLarge,
|
||||
opacity: state.opacity,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -252,6 +247,10 @@ class _ImageViewGalleryState extends State<ImageViewGallery> {
|
||||
imageProvider: FileImage(File(widget.imagePathList[0])),
|
||||
backgroundDecoration: const BoxDecoration(color: Colors.transparent),
|
||||
initialScale: PhotoViewComputedScale.contained,
|
||||
minScale: PhotoViewComputedScale.contained,
|
||||
onTapDown: (context, details, controllerValue) {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
heroAttributes: PhotoViewHeroAttributes(
|
||||
tag: '${widget._heroTagPrefix}0',
|
||||
),
|
||||
@@ -266,10 +265,14 @@ class _ImageViewGalleryState extends State<ImageViewGallery> {
|
||||
heroAttributes: PhotoViewHeroAttributes(
|
||||
tag: '${widget._heroTagPrefix}$index',
|
||||
),
|
||||
onTapDown: (context, details, controllerValue) {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
backgroundDecoration: const BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
initialScale: PhotoViewComputedScale.contained,
|
||||
minScale: PhotoViewComputedScale.contained,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -224,6 +224,15 @@ class _MoodiaryPageTransition implements CustomTransition {
|
||||
) {
|
||||
final pageRoute = ModalRoute.of(context) as PageRoute;
|
||||
if (Platform.isAndroid) {
|
||||
if (pageRoute.popGestureInProgress) {
|
||||
return const PredictiveBackPageTransitionsBuilder().buildTransitions(
|
||||
pageRoute,
|
||||
context,
|
||||
animation,
|
||||
secondaryAnimation,
|
||||
child,
|
||||
);
|
||||
}
|
||||
return const FadeForwardsPageTransitionsBuilder().buildTransitions(
|
||||
pageRoute,
|
||||
context,
|
||||
|
||||
Reference in New Issue
Block a user