mirror of
https://github.com/ZhuJHua/moodiary.git
synced 2026-04-05 16:39:01 +08:00
refactor: remove video page routing and update video state initialization
This commit is contained in:
@@ -27,31 +27,38 @@ class SmallThumbShape extends SfThumbShape {
|
||||
}) {
|
||||
// 使用原有逻辑绘制缩小的滑块
|
||||
final double radius = getPreferredSize(themeData).width / 2;
|
||||
final Paint thumbPaint = Paint()
|
||||
..color = themeData.thumbColor ?? Colors.blue
|
||||
..isAntiAlias = true;
|
||||
final Paint thumbPaint =
|
||||
Paint()
|
||||
..color = themeData.thumbColor ?? Colors.blue
|
||||
..isAntiAlias = true;
|
||||
|
||||
context.canvas.drawCircle(center, radius, thumbPaint);
|
||||
|
||||
if (child != null) {
|
||||
context.paintChild(
|
||||
child,
|
||||
Offset(center.dx - child.size.width / 2,
|
||||
center.dy - child.size.height / 2));
|
||||
child,
|
||||
Offset(
|
||||
center.dx - child.size.width / 2,
|
||||
center.dy - child.size.height / 2,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NoOverlayShape extends SfOverlayShape {
|
||||
@override
|
||||
void paint(PaintingContext context, Offset center,
|
||||
{required RenderBox parentBox,
|
||||
required dynamic themeData,
|
||||
SfRangeValues? currentValues,
|
||||
currentValue,
|
||||
required Paint? paint,
|
||||
required Animation<double> animation,
|
||||
required SfThumb? thumb}) {}
|
||||
void paint(
|
||||
PaintingContext context,
|
||||
Offset center, {
|
||||
required RenderBox parentBox,
|
||||
required dynamic themeData,
|
||||
SfRangeValues? currentValues,
|
||||
currentValue,
|
||||
required Paint? paint,
|
||||
required Animation<double> animation,
|
||||
required SfThumb? thumb,
|
||||
}) {}
|
||||
}
|
||||
|
||||
class AudioPlayerComponent extends StatelessWidget {
|
||||
@@ -74,6 +81,7 @@ class AudioPlayerComponent extends StatelessWidget {
|
||||
builder: (_) {
|
||||
return Card.filled(
|
||||
color: colorScheme.secondaryContainer,
|
||||
margin: EdgeInsets.zero,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
@@ -84,11 +92,18 @@ class AudioPlayerComponent extends StatelessWidget {
|
||||
children: [
|
||||
Obx(() {
|
||||
return SfSlider(
|
||||
value: state.totalDuration.value != Duration.zero
|
||||
? ((state.currentDuration.value.inMilliseconds /
|
||||
state.totalDuration.value.inMilliseconds)
|
||||
.clamp(0, 1))
|
||||
: 0,
|
||||
value:
|
||||
state.totalDuration.value != Duration.zero
|
||||
? ((state
|
||||
.currentDuration
|
||||
.value
|
||||
.inMilliseconds /
|
||||
state
|
||||
.totalDuration
|
||||
.value
|
||||
.inMilliseconds)
|
||||
.clamp(0, 1))
|
||||
: 0,
|
||||
onChangeEnd: (value) {
|
||||
logic.to(value);
|
||||
},
|
||||
@@ -112,7 +127,8 @@ class AudioPlayerComponent extends StatelessWidget {
|
||||
.split('.')[0]
|
||||
.padLeft(8, '0'),
|
||||
style: TextStyle(
|
||||
color: colorScheme.onSecondaryContainer),
|
||||
color: colorScheme.onSecondaryContainer,
|
||||
),
|
||||
);
|
||||
}),
|
||||
IconButton.filled(
|
||||
@@ -121,9 +137,6 @@ class AudioPlayerComponent extends StatelessWidget {
|
||||
? logic.pause()
|
||||
: logic.play(path);
|
||||
},
|
||||
style: const ButtonStyle(
|
||||
tapTargetSize:
|
||||
MaterialTapTargetSize.shrinkWrap),
|
||||
icon: AnimatedIcon(
|
||||
icon: AnimatedIcons.play_pause,
|
||||
color: colorScheme.onPrimary,
|
||||
@@ -137,7 +150,8 @@ class AudioPlayerComponent extends StatelessWidget {
|
||||
.split('.')[0]
|
||||
.padLeft(8, '0'),
|
||||
style: TextStyle(
|
||||
color: colorScheme.onSecondaryContainer),
|
||||
color: colorScheme.onSecondaryContainer,
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -7,7 +7,7 @@ class ThumbnailImage extends StatelessWidget {
|
||||
final String imagePath;
|
||||
final int size;
|
||||
final BoxFit? fit;
|
||||
final Function() onTap;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
final String heroTag;
|
||||
|
||||
@@ -16,7 +16,7 @@ class ThumbnailImage extends StatelessWidget {
|
||||
required this.imagePath,
|
||||
required this.size,
|
||||
this.fit,
|
||||
required this.onTap,
|
||||
this.onTap,
|
||||
required this.heroTag,
|
||||
});
|
||||
|
||||
|
||||
@@ -32,16 +32,15 @@ class AdaptiveText extends StatelessWidget {
|
||||
textStyle = textTheme.bodyLarge?.copyWith(color: colorScheme.onSurface);
|
||||
}
|
||||
if (isTileSubtitle == true) {
|
||||
textStyle =
|
||||
textTheme.bodyMedium?.copyWith(color: colorScheme.onSurfaceVariant);
|
||||
textStyle = textTheme.bodyMedium?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
);
|
||||
}
|
||||
if (isTitle == true) {
|
||||
textStyle = textTheme.titleLarge;
|
||||
}
|
||||
if (isPrimaryTitle == true) {
|
||||
textStyle = textTheme.titleLarge?.copyWith(
|
||||
color: colorScheme.primary,
|
||||
);
|
||||
textStyle = textTheme.titleLarge?.copyWith(color: colorScheme.primary);
|
||||
}
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
@@ -53,27 +52,27 @@ class AdaptiveText extends StatelessWidget {
|
||||
)..layout(maxWidth: maxWidth ?? constraints.maxWidth);
|
||||
return textPainter.didExceedMaxLines
|
||||
? SizedBox(
|
||||
height: textPainter.height,
|
||||
width: maxWidth ?? constraints.maxWidth,
|
||||
child: Marquee(
|
||||
text: text,
|
||||
velocity: 20,
|
||||
blankSpace: 20,
|
||||
textScaler: textScaler,
|
||||
pauseAfterRound: const Duration(seconds: 1),
|
||||
accelerationDuration: const Duration(seconds: 1),
|
||||
accelerationCurve: Curves.linear,
|
||||
decelerationDuration: const Duration(milliseconds: 300),
|
||||
decelerationCurve: Curves.easeOut,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
text,
|
||||
height: textPainter.height,
|
||||
width: maxWidth ?? constraints.maxWidth,
|
||||
child: Marquee(
|
||||
text: text,
|
||||
velocity: 20,
|
||||
blankSpace: 20,
|
||||
textScaler: textScaler,
|
||||
pauseAfterRound: const Duration(seconds: 1),
|
||||
accelerationDuration: const Duration(seconds: 1),
|
||||
accelerationCurve: Curves.linear,
|
||||
decelerationDuration: const Duration(milliseconds: 300),
|
||||
decelerationCurve: Curves.easeOut,
|
||||
style: textStyle,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
);
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
text,
|
||||
style: textStyle,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -85,7 +84,7 @@ class EllipsisText extends StatelessWidget {
|
||||
final String ellipsis;
|
||||
final int? maxLines;
|
||||
|
||||
static final _cache = LRUCache<String, String>(maxSize: 1000);
|
||||
static final _cache = LRUCache<int, String>(maxSize: 1000);
|
||||
|
||||
/// 当字体相关属性发生变化时,需要清空缓存
|
||||
static void clearCache() {
|
||||
@@ -100,6 +99,24 @@ class EllipsisText extends StatelessWidget {
|
||||
this.maxLines,
|
||||
});
|
||||
|
||||
int _getCacheKey(
|
||||
String text,
|
||||
String ellipsis,
|
||||
double maxWidth,
|
||||
int? maxLines,
|
||||
TextStyle? style,
|
||||
TextScaler textScaler,
|
||||
) {
|
||||
return Object.hash(
|
||||
text,
|
||||
ellipsis,
|
||||
maxWidth.toStringAsFixed(2),
|
||||
maxLines,
|
||||
style,
|
||||
textScaler,
|
||||
);
|
||||
}
|
||||
|
||||
double _calculateTextWidth(String text, TextScaler textScaler) {
|
||||
final span = TextSpan(text: text.fixAutoLines(), style: style);
|
||||
final tp = TextPainter(
|
||||
@@ -128,96 +145,109 @@ class EllipsisText extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final textScaler = MediaQuery.textScalerOf(context);
|
||||
|
||||
return LayoutBuilder(builder: (context, constraints) {
|
||||
if (text.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final cacheKey =
|
||||
'${text.hashCode}_${ellipsis}_${constraints.maxWidth}_$maxLines';
|
||||
final cachedResult = _cache.get(cacheKey);
|
||||
if (cachedResult != null) {
|
||||
return Text(
|
||||
cachedResult,
|
||||
maxLines: maxLines,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: style,
|
||||
textScaler: textScaler,
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final cacheKey = _getCacheKey(
|
||||
text,
|
||||
ellipsis,
|
||||
constraints.maxWidth,
|
||||
maxLines,
|
||||
style,
|
||||
textScaler,
|
||||
);
|
||||
}
|
||||
if (text.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final cachedResult = _cache.get(cacheKey);
|
||||
if (cachedResult != null) {
|
||||
return Text(
|
||||
cachedResult,
|
||||
maxLines: maxLines,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: style,
|
||||
textScaler: textScaler,
|
||||
);
|
||||
}
|
||||
|
||||
if (!_createTextPainter(
|
||||
text,
|
||||
constraints.maxWidth,
|
||||
textScaler,
|
||||
).didExceedMaxLines) {
|
||||
_cache.put(cacheKey, text.fixAutoLines());
|
||||
return Text(
|
||||
text.fixAutoLines(),
|
||||
maxLines: maxLines,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: style,
|
||||
textScaler: textScaler,
|
||||
);
|
||||
}
|
||||
|
||||
int leftIndex = 0;
|
||||
int rightIndex = text.characters.length;
|
||||
double leftWidth = 0;
|
||||
double rightWidth = 0;
|
||||
|
||||
int lastValidLeftIndex = 0;
|
||||
int lastValidRightIndex = text.characters.length;
|
||||
|
||||
while (leftIndex < rightIndex) {
|
||||
final nextLeftWidth = _calculateTextWidth(
|
||||
text.characters.elementAt(leftIndex), textScaler) +
|
||||
leftWidth;
|
||||
final nextRightWidth = _calculateTextWidth(
|
||||
text.characters.elementAt(rightIndex - 1), textScaler) +
|
||||
rightWidth;
|
||||
final currentText =
|
||||
'${text.runeSubstring(0, leftIndex)}$ellipsis${text.runeSubstring(rightIndex)}';
|
||||
|
||||
if (_createTextPainter(
|
||||
currentText,
|
||||
if (!_createTextPainter(
|
||||
text,
|
||||
constraints.maxWidth,
|
||||
textScaler,
|
||||
).didExceedMaxLines) {
|
||||
break;
|
||||
} else {
|
||||
lastValidLeftIndex = leftIndex;
|
||||
lastValidRightIndex = rightIndex;
|
||||
if (leftWidth <= rightWidth) {
|
||||
leftWidth = nextLeftWidth;
|
||||
leftIndex++;
|
||||
_cache.put(cacheKey, text.fixAutoLines());
|
||||
return Text(
|
||||
text.fixAutoLines(),
|
||||
maxLines: maxLines,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: style,
|
||||
textScaler: textScaler,
|
||||
);
|
||||
}
|
||||
|
||||
int leftIndex = 0;
|
||||
int rightIndex = text.characters.length;
|
||||
double leftWidth = 0;
|
||||
double rightWidth = 0;
|
||||
|
||||
int lastValidLeftIndex = 0;
|
||||
int lastValidRightIndex = text.characters.length;
|
||||
|
||||
while (leftIndex < rightIndex) {
|
||||
final nextLeftWidth =
|
||||
_calculateTextWidth(
|
||||
text.characters.elementAt(leftIndex),
|
||||
textScaler,
|
||||
) +
|
||||
leftWidth;
|
||||
final nextRightWidth =
|
||||
_calculateTextWidth(
|
||||
text.characters.elementAt(rightIndex - 1),
|
||||
textScaler,
|
||||
) +
|
||||
rightWidth;
|
||||
final currentText =
|
||||
'${text.charSubstring(0, leftIndex)}$ellipsis${text.charSubstring(rightIndex)}';
|
||||
|
||||
if (_createTextPainter(
|
||||
currentText,
|
||||
constraints.maxWidth,
|
||||
textScaler,
|
||||
).didExceedMaxLines) {
|
||||
break;
|
||||
} else {
|
||||
rightWidth = nextRightWidth;
|
||||
rightIndex--;
|
||||
lastValidLeftIndex = leftIndex;
|
||||
lastValidRightIndex = rightIndex;
|
||||
if (leftWidth <= rightWidth) {
|
||||
leftWidth = nextLeftWidth;
|
||||
leftIndex++;
|
||||
} else {
|
||||
rightWidth = nextRightWidth;
|
||||
rightIndex--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final truncatedText =
|
||||
'${text.runeSubstring(0, lastValidLeftIndex)}$ellipsis${text.runeSubstring(lastValidRightIndex)}';
|
||||
final truncatedText =
|
||||
'${text.charSubstring(0, lastValidLeftIndex)}$ellipsis${text.charSubstring(lastValidRightIndex)}';
|
||||
|
||||
_cache.put(cacheKey, truncatedText.fixAutoLines());
|
||||
_cache.put(cacheKey, truncatedText.fixAutoLines());
|
||||
|
||||
return Text(
|
||||
truncatedText.fixAutoLines(),
|
||||
maxLines: maxLines,
|
||||
style: style,
|
||||
textScaler: textScaler,
|
||||
);
|
||||
});
|
||||
return Text(
|
||||
truncatedText.fixAutoLines(),
|
||||
maxLines: maxLines,
|
||||
style: style,
|
||||
textScaler: textScaler,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension StringExt on String {
|
||||
String fixAutoLines() => characters.join('\u{200B}');
|
||||
|
||||
String runeSubstring(int start, [int? end]) {
|
||||
return String.fromCharCodes(runes.toList().sublist(start, end));
|
||||
}
|
||||
String charSubstring(int start, [int? end]) =>
|
||||
characters.getRange(start, end).join();
|
||||
|
||||
String removeLineBreaks() => replaceAll(RegExp(r'[\r\n]+'), '');
|
||||
}
|
||||
@@ -240,17 +270,10 @@ class AnimatedText extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: isFetching
|
||||
? Text(
|
||||
placeholder,
|
||||
key: const ValueKey('empty'),
|
||||
style: style,
|
||||
)
|
||||
: Text(
|
||||
text,
|
||||
key: const ValueKey('text'),
|
||||
style: style,
|
||||
),
|
||||
child:
|
||||
isFetching
|
||||
? Text(placeholder, key: const ValueKey('empty'), style: style)
|
||||
: Text(text, key: const ValueKey('text'), style: style),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,11 @@ class MediaAudioComponent extends StatelessWidget {
|
||||
final DateTime dateTime;
|
||||
final List<String> audioList;
|
||||
|
||||
const MediaAudioComponent(
|
||||
{super.key, required this.dateTime, required this.audioList});
|
||||
const MediaAudioComponent({
|
||||
super.key,
|
||||
required this.dateTime,
|
||||
required this.audioList,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -18,19 +21,33 @@ class MediaAudioComponent extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: Text(
|
||||
DateFormat.yMMMEd().format(dateTime),
|
||||
style: textStyle.titleSmall?.copyWith(color: colorScheme.secondary),
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
DateFormat.yMMMEd().format(dateTime),
|
||||
style: textStyle.titleSmall?.copyWith(
|
||||
color: colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${audioList.length} ${audioList.length > 1 ? 'Audios' : 'Audio'}',
|
||||
style: textStyle.labelMedium?.copyWith(
|
||||
color: colorScheme.tertiary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
ListView.builder(
|
||||
ListView.separated(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (context, index) {
|
||||
return AudioPlayerComponent(path: audioList[index]);
|
||||
},
|
||||
separatorBuilder: (_, _) => const SizedBox(height: 8),
|
||||
itemCount: audioList.length,
|
||||
),
|
||||
],
|
||||
|
||||
@@ -25,7 +25,7 @@ class MediaImageComponent extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 4.0),
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
@@ -44,47 +44,44 @@ class MediaImageComponent extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
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,
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
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/button.dart';
|
||||
import 'package:moodiary/components/base/image.dart';
|
||||
import 'package:moodiary/router/app_routes.dart';
|
||||
import 'package:moodiary/pages/video/video_view.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:refreshed/refreshed.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class MediaVideoComponent extends StatelessWidget {
|
||||
@@ -17,11 +17,6 @@ class MediaVideoComponent extends StatelessWidget {
|
||||
required this.videoList,
|
||||
});
|
||||
|
||||
//点击视频跳转到视频预览
|
||||
void _toVideoView(List<String> videoPathList, int index) {
|
||||
Get.toNamed(AppRoutes.videoPage, arguments: [videoPathList, index]);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textStyle = Theme.of(context).textTheme;
|
||||
@@ -38,39 +33,66 @@ class MediaVideoComponent extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: Text(
|
||||
DateFormat.yMMMEd().format(dateTime),
|
||||
style: textStyle.titleSmall?.copyWith(color: colorScheme.secondary),
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
DateFormat.yMMMEd().format(dateTime),
|
||||
style: textStyle.titleSmall?.copyWith(
|
||||
color: colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${videoList.length} ${videoList.length > 1 ? 'Videos' : 'Video'}',
|
||||
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: const EdgeInsets.all(4.0),
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (context, index) {
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
ThumbnailImage(
|
||||
imagePath: thumbnailList[index],
|
||||
heroTag: '$heroPrefix$index',
|
||||
size: 120,
|
||||
onTap: () {
|
||||
_toVideoView(videoList, index);
|
||||
},
|
||||
ClipRRect(
|
||||
borderRadius: AppBorderRadius.mediumBorderRadius,
|
||||
child: GridView.builder(
|
||||
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: 120,
|
||||
childAspectRatio: 1.0,
|
||||
crossAxisSpacing: 1.0,
|
||||
mainAxisSpacing: 1.0,
|
||||
),
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (context, index) {
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
await showVideoView(
|
||||
context,
|
||||
videoList,
|
||||
index,
|
||||
heroTagPrefix: '$heroPrefix$index',
|
||||
);
|
||||
},
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: ThumbnailImage(
|
||||
imagePath: thumbnailList[index],
|
||||
heroTag: '$heroPrefix$index',
|
||||
size: 120,
|
||||
),
|
||||
),
|
||||
const FrostedGlassButton(
|
||||
size: 32,
|
||||
child: Center(child: Icon(Icons.play_arrow_rounded)),
|
||||
),
|
||||
],
|
||||
),
|
||||
const FaIcon(FontAwesomeIcons.play),
|
||||
],
|
||||
);
|
||||
},
|
||||
itemCount: thumbnailList.length,
|
||||
);
|
||||
},
|
||||
itemCount: thumbnailList.length,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -6,6 +6,8 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_quill/flutter_quill.dart';
|
||||
import 'package:moodiary/common/models/isar/diary.dart';
|
||||
import 'package:moodiary/common/values/diary_type.dart';
|
||||
import 'package:moodiary/pages/image/image_view.dart';
|
||||
import 'package:moodiary/pages/video/video_view.dart';
|
||||
import 'package:moodiary/presentation/isar.dart';
|
||||
import 'package:moodiary/router/app_routes.dart';
|
||||
import 'package:refreshed/refreshed.dart';
|
||||
@@ -41,16 +43,35 @@ class DiaryDetailsLogic extends GetxController {
|
||||
}
|
||||
|
||||
//点击图片跳转到图片预览页面
|
||||
void toPhotoView(
|
||||
List<String> imagePathList, int index, BuildContext context) {
|
||||
Future<void> toPhotoView(
|
||||
List<String> imagePathList,
|
||||
int index,
|
||||
BuildContext context,
|
||||
String heroPrefix,
|
||||
) async {
|
||||
HapticFeedback.selectionClick();
|
||||
Get.toNamed(AppRoutes.photoPage, arguments: [imagePathList, index]);
|
||||
// showImagePreview(context, imagePathList, index);
|
||||
await showImageView(
|
||||
context,
|
||||
imagePathList,
|
||||
index,
|
||||
heroTagPrefix: heroPrefix,
|
||||
);
|
||||
}
|
||||
|
||||
//点击视频跳转到视频预览页面
|
||||
void toVideoView(List<String> videoPathList, int index) {
|
||||
Get.toNamed(AppRoutes.videoPage, arguments: [videoPathList, index]);
|
||||
Future<void> toVideoView(
|
||||
List<String> videoPathList,
|
||||
int index,
|
||||
BuildContext context,
|
||||
String heroPrefix,
|
||||
) async {
|
||||
HapticFeedback.selectionClick();
|
||||
await showVideoView(
|
||||
context,
|
||||
videoPathList,
|
||||
index,
|
||||
heroTagPrefix: heroPrefix,
|
||||
);
|
||||
}
|
||||
|
||||
//点击分享跳转到分享页面
|
||||
@@ -82,7 +103,10 @@ class DiaryDetailsLogic extends GetxController {
|
||||
}
|
||||
|
||||
Future<void> jumpToPage(int index) async {
|
||||
await pageController.animateToPage(index,
|
||||
duration: const Duration(milliseconds: 200), curve: Curves.easeInOut);
|
||||
await pageController.animateToPage(
|
||||
index,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import 'package:moodiary/utils/file_util.dart';
|
||||
import 'package:moodiary/utils/theme_util.dart';
|
||||
import 'package:refreshed/refreshed.dart';
|
||||
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import 'diary_details_logic.dart';
|
||||
|
||||
@@ -31,21 +32,29 @@ class DiaryDetailsPage extends StatelessWidget {
|
||||
|
||||
final tag = (Get.arguments[0] as Diary).id;
|
||||
|
||||
Widget _buildMarkdownWidget(
|
||||
{required Brightness brightness, required String data}) {
|
||||
final config = brightness == Brightness.dark
|
||||
? MarkdownConfig.darkConfig
|
||||
: MarkdownConfig.defaultConfig;
|
||||
Widget _buildMarkdownWidget({
|
||||
required Brightness brightness,
|
||||
required String data,
|
||||
}) {
|
||||
final config =
|
||||
brightness == Brightness.dark
|
||||
? MarkdownConfig.darkConfig
|
||||
: MarkdownConfig.defaultConfig;
|
||||
return MarkdownBlock(
|
||||
data: data,
|
||||
config: config.copy(configs: [
|
||||
ImgConfig(builder: (src, _) {
|
||||
return MarkdownImageEmbed(isEdit: false, imageName: src);
|
||||
}),
|
||||
data: data,
|
||||
config: config.copy(
|
||||
configs: [
|
||||
ImgConfig(
|
||||
builder: (src, _) {
|
||||
return MarkdownImageEmbed(isEdit: false, imageName: src);
|
||||
},
|
||||
),
|
||||
brightness == Brightness.dark
|
||||
? PreConfig.darkConfig.copy(theme: a11yDarkTheme)
|
||||
: const PreConfig().copy(theme: a11yLightTheme)
|
||||
]));
|
||||
: const PreConfig().copy(theme: a11yLightTheme),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -69,71 +78,82 @@ class DiaryDetailsPage extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget buildChipList(ColorScheme colorScheme) {
|
||||
final dateTime =
|
||||
DateFormat.yMMMd().add_Hms().format(state.diary.time).split(' ');
|
||||
final dateTime = DateFormat.yMMMd()
|
||||
.add_Hms()
|
||||
.format(state.diary.time)
|
||||
.split(' ');
|
||||
final date = dateTime.first;
|
||||
final time = dateTime.last;
|
||||
return Wrap(
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
buildAChip(
|
||||
MoodIconComponent(value: state.diary.mood), null, colorScheme),
|
||||
MoodIconComponent(value: state.diary.mood),
|
||||
null,
|
||||
colorScheme,
|
||||
),
|
||||
buildAChip(
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
date,
|
||||
style: textStyle.labelSmall!
|
||||
.copyWith(color: colorScheme.onSurface),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
date,
|
||||
style: textStyle.labelSmall!.copyWith(
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
Text(
|
||||
time,
|
||||
style: textStyle.labelSmall!
|
||||
.copyWith(color: colorScheme.onSurface),
|
||||
)
|
||||
],
|
||||
),
|
||||
const Icon(Icons.access_time_outlined),
|
||||
colorScheme),
|
||||
),
|
||||
Text(
|
||||
time,
|
||||
style: textStyle.labelSmall!.copyWith(
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Icon(Icons.access_time_outlined),
|
||||
colorScheme,
|
||||
),
|
||||
if (state.diary.position.isNotEmpty) ...[
|
||||
buildAChip(Text(state.diary.position[2]),
|
||||
const Icon(Icons.location_city_rounded), colorScheme)
|
||||
buildAChip(
|
||||
Text(state.diary.position[2]),
|
||||
const Icon(Icons.location_city_rounded),
|
||||
colorScheme,
|
||||
),
|
||||
],
|
||||
if (state.diary.weather.isNotEmpty) ...[
|
||||
buildAChip(
|
||||
Text(
|
||||
'${state.diary.weather[2]} ${state.diary.weather[1]}°C',
|
||||
style: textStyle.labelLarge!
|
||||
.copyWith(color: colorScheme.onSurface),
|
||||
Text(
|
||||
'${state.diary.weather[2]} ${state.diary.weather[1]}°C',
|
||||
style: textStyle.labelLarge!.copyWith(
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
Icon(
|
||||
WeatherIcon.map[state.diary.weather[0]],
|
||||
),
|
||||
colorScheme)
|
||||
),
|
||||
Icon(WeatherIcon.map[state.diary.weather[0]]),
|
||||
colorScheme,
|
||||
),
|
||||
],
|
||||
buildAChip(
|
||||
Text(
|
||||
l10n.diaryCount(state.diary.contentText.length),
|
||||
style: textStyle.labelLarge!
|
||||
.copyWith(color: colorScheme.onSurface),
|
||||
Text(
|
||||
l10n.diaryCount(state.diary.contentText.length),
|
||||
style: textStyle.labelLarge!.copyWith(
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
const Icon(
|
||||
Icons.text_fields_outlined,
|
||||
),
|
||||
colorScheme),
|
||||
),
|
||||
const Icon(Icons.text_fields_outlined),
|
||||
colorScheme,
|
||||
),
|
||||
...List.generate(state.diary.tags.length, (index) {
|
||||
return buildAChip(
|
||||
Text(
|
||||
state.diary.tags[index],
|
||||
style: textStyle.labelLarge!
|
||||
.copyWith(color: colorScheme.onSurface),
|
||||
Text(
|
||||
state.diary.tags[index],
|
||||
style: textStyle.labelLarge!.copyWith(
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
const Icon(
|
||||
Icons.tag_outlined,
|
||||
),
|
||||
colorScheme);
|
||||
})
|
||||
),
|
||||
const Icon(Icons.tag_outlined),
|
||||
colorScheme,
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -242,6 +262,7 @@ class DiaryDetailsPage extends StatelessWidget {
|
||||
// }
|
||||
|
||||
Widget buildImageView(ColorScheme colorScheme) {
|
||||
final heroPrefix = const Uuid().v4();
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
@@ -249,20 +270,30 @@ class DiaryDetailsPage extends StatelessWidget {
|
||||
controller: logic.pageController,
|
||||
itemBuilder: (context, index) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
logic.toPhotoView(
|
||||
onTap: () async {
|
||||
await logic.toPhotoView(
|
||||
List.generate(state.diary.imageName.length, (i) {
|
||||
return FileUtil.getRealPath(
|
||||
'image', state.diary.imageName[i]);
|
||||
'image',
|
||||
state.diary.imageName[i],
|
||||
);
|
||||
}),
|
||||
index,
|
||||
context,
|
||||
heroPrefix,
|
||||
);
|
||||
},
|
||||
child: Image.file(
|
||||
File(FileUtil.getRealPath(
|
||||
'image', state.diary.imageName[index])),
|
||||
fit: BoxFit.cover,
|
||||
child: Hero(
|
||||
tag: '$heroPrefix$index',
|
||||
child: Image.file(
|
||||
File(
|
||||
FileUtil.getRealPath(
|
||||
'image',
|
||||
state.diary.imageName[index],
|
||||
),
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -274,171 +305,184 @@ class DiaryDetailsPage extends StatelessWidget {
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Container(
|
||||
decoration: ShapeDecoration(
|
||||
shape: const StadiumBorder(side: BorderSide.none),
|
||||
color: colorScheme.surfaceContainer.withAlpha(100)),
|
||||
padding: state.diary.imageName.length > 9
|
||||
? const EdgeInsets.fromLTRB(6.0, 2.0, 6.0, 2.0)
|
||||
: const EdgeInsets.all(2.0),
|
||||
shape: const StadiumBorder(side: BorderSide.none),
|
||||
color: colorScheme.surfaceContainer.withAlpha(100),
|
||||
),
|
||||
padding:
|
||||
state.diary.imageName.length > 9
|
||||
? const EdgeInsets.fromLTRB(6.0, 2.0, 6.0, 2.0)
|
||||
: const EdgeInsets.all(2.0),
|
||||
child: SmoothPageIndicator(
|
||||
controller: logic.pageController,
|
||||
count: state.diary.imageName.length,
|
||||
effect: state.diary.imageName.length > 9
|
||||
? ScrollingDotsEffect(
|
||||
activeDotColor: colorScheme.tertiary,
|
||||
dotColor:
|
||||
colorScheme.surfaceContainerLow.withAlpha(200),
|
||||
dotWidth: 8,
|
||||
dotHeight: 8,
|
||||
maxVisibleDots: 9)
|
||||
: WormEffect(
|
||||
activeDotColor: colorScheme.tertiary,
|
||||
dotColor:
|
||||
colorScheme.surfaceContainerLow.withAlpha(200),
|
||||
dotWidth: 8,
|
||||
dotHeight: 8,
|
||||
),
|
||||
effect:
|
||||
state.diary.imageName.length > 9
|
||||
? ScrollingDotsEffect(
|
||||
activeDotColor: colorScheme.tertiary,
|
||||
dotColor: colorScheme.surfaceContainerLow.withAlpha(
|
||||
200,
|
||||
),
|
||||
dotWidth: 8,
|
||||
dotHeight: 8,
|
||||
maxVisibleDots: 9,
|
||||
)
|
||||
: WormEffect(
|
||||
activeDotColor: colorScheme.tertiary,
|
||||
dotColor: colorScheme.surfaceContainerLow.withAlpha(
|
||||
200,
|
||||
),
|
||||
dotWidth: 8,
|
||||
dotHeight: 8,
|
||||
),
|
||||
onDotClicked: logic.jumpToPage,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return GetBuilder<DiaryDetailsLogic>(
|
||||
tag: state.diary.id,
|
||||
builder: (_) {
|
||||
final customColorScheme = (state.imageColor != null &&
|
||||
PrefUtil.getValue<bool>('dynamicColor') == true)
|
||||
? ColorScheme.fromSeed(
|
||||
tag: state.diary.id,
|
||||
builder: (_) {
|
||||
final customColorScheme =
|
||||
(state.imageColor != null &&
|
||||
PrefUtil.getValue<bool>('dynamicColor') == true)
|
||||
? ColorScheme.fromSeed(
|
||||
seedColor: Color(state.imageColor!),
|
||||
brightness: colorScheme.brightness,
|
||||
)
|
||||
: colorScheme;
|
||||
return Theme(
|
||||
data: Theme.of(context).copyWith(colorScheme: customColorScheme),
|
||||
child: Scaffold(
|
||||
backgroundColor: customColorScheme.surface,
|
||||
body: CustomScrollView(
|
||||
slivers: [
|
||||
SliverAppBar(
|
||||
expandedHeight: state.diaryHeader
|
||||
? (state.aspect != null
|
||||
? min(
|
||||
size.width / state.aspect!, size.height * 0.382)
|
||||
: null)
|
||||
: null,
|
||||
title: Text(
|
||||
state.diary.title,
|
||||
style: textStyle.titleMedium,
|
||||
),
|
||||
leading: const PageBackButton(),
|
||||
centerTitle: false,
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
collapseMode: CollapseMode.pin,
|
||||
background: state.diaryHeader
|
||||
? (state.diary.imageName.isNotEmpty
|
||||
? buildImageView(customColorScheme)
|
||||
: colorScheme;
|
||||
return Theme(
|
||||
data: Theme.of(context).copyWith(colorScheme: customColorScheme),
|
||||
child: Scaffold(
|
||||
backgroundColor: customColorScheme.surface,
|
||||
body: CustomScrollView(
|
||||
slivers: [
|
||||
SliverAppBar(
|
||||
expandedHeight:
|
||||
state.diaryHeader
|
||||
? (state.aspect != null
|
||||
? min(
|
||||
size.width / state.aspect!,
|
||||
size.height * 0.382,
|
||||
)
|
||||
: null)
|
||||
: null,
|
||||
),
|
||||
pinned: true,
|
||||
actions: [
|
||||
// IconButton(
|
||||
// onPressed: () {
|
||||
// showModalBottomSheet(
|
||||
// context: context,
|
||||
// useSafeArea: true,
|
||||
// showDragHandle: true,
|
||||
// builder: (context) {
|
||||
// return Padding(
|
||||
// padding: MediaQuery.viewInsetsOf(context),
|
||||
// child: AskQuestionComponent(
|
||||
// content: state.diary.contentText,
|
||||
// ),
|
||||
// );
|
||||
// });
|
||||
// },
|
||||
// icon: const Icon(Icons.chat)),
|
||||
PopupMenuButton(
|
||||
offset: const Offset(0, 46),
|
||||
itemBuilder: (context) {
|
||||
return <PopupMenuEntry<String>>[
|
||||
if (state.showAction) ...[
|
||||
PopupMenuItem(
|
||||
onTap: () async {
|
||||
logic.delete(state.diary);
|
||||
},
|
||||
child: Row(
|
||||
spacing: 16.0,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.delete_rounded),
|
||||
Text(l10n.diaryDelete)
|
||||
],
|
||||
),
|
||||
),
|
||||
const PopupMenuDivider(),
|
||||
PopupMenuItem(
|
||||
onTap: () async {
|
||||
logic.toEditPage(state.diary);
|
||||
},
|
||||
child: Row(
|
||||
spacing: 16.0,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.edit_rounded),
|
||||
Text(l10n.diaryEdit)
|
||||
],
|
||||
),
|
||||
),
|
||||
const PopupMenuDivider(),
|
||||
],
|
||||
title: Text(state.diary.title, style: textStyle.titleMedium),
|
||||
leading: const PageBackButton(),
|
||||
centerTitle: false,
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
collapseMode: CollapseMode.pin,
|
||||
background:
|
||||
state.diaryHeader
|
||||
? (state.diary.imageName.isNotEmpty
|
||||
? buildImageView(customColorScheme)
|
||||
: null)
|
||||
: null,
|
||||
),
|
||||
pinned: true,
|
||||
actions: [
|
||||
// IconButton(
|
||||
// onPressed: () {
|
||||
// showModalBottomSheet(
|
||||
// context: context,
|
||||
// useSafeArea: true,
|
||||
// showDragHandle: true,
|
||||
// builder: (context) {
|
||||
// return Padding(
|
||||
// padding: MediaQuery.viewInsetsOf(context),
|
||||
// child: AskQuestionComponent(
|
||||
// content: state.diary.contentText,
|
||||
// ),
|
||||
// );
|
||||
// });
|
||||
// },
|
||||
// icon: const Icon(Icons.chat)),
|
||||
PopupMenuButton(
|
||||
offset: const Offset(0, 46),
|
||||
itemBuilder: (context) {
|
||||
return <PopupMenuEntry<String>>[
|
||||
if (state.showAction) ...[
|
||||
PopupMenuItem(
|
||||
onTap: () async {
|
||||
logic.toSharePage();
|
||||
logic.delete(state.diary);
|
||||
},
|
||||
child: Row(
|
||||
spacing: 16.0,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.share_rounded),
|
||||
Text(l10n.diaryShare)
|
||||
const Icon(Icons.delete_rounded),
|
||||
Text(l10n.diaryDelete),
|
||||
],
|
||||
),
|
||||
),
|
||||
];
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
sliver: SliverList.list(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: buildChipList(customColorScheme)),
|
||||
const PopupMenuDivider(),
|
||||
PopupMenuItem(
|
||||
onTap: () async {
|
||||
logic.toEditPage(state.diary);
|
||||
},
|
||||
child: Row(
|
||||
spacing: 16.0,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.edit_rounded),
|
||||
Text(l10n.diaryEdit),
|
||||
],
|
||||
),
|
||||
),
|
||||
const PopupMenuDivider(),
|
||||
],
|
||||
PopupMenuItem(
|
||||
onTap: () async {
|
||||
logic.toSharePage();
|
||||
},
|
||||
child: Row(
|
||||
spacing: 16.0,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.share_rounded),
|
||||
Text(l10n.diaryShare),
|
||||
],
|
||||
),
|
||||
),
|
||||
];
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
sliver: SliverList.list(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: buildChipList(customColorScheme),
|
||||
),
|
||||
Card.filled(
|
||||
color: customColorScheme.surfaceContainerLow,
|
||||
child: state.diary.type == DiaryType.markdown.value
|
||||
? Padding(
|
||||
),
|
||||
Card.filled(
|
||||
color: customColorScheme.surfaceContainerLow,
|
||||
child:
|
||||
state.diary.type == DiaryType.markdown.value
|
||||
? Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: _buildMarkdownWidget(
|
||||
brightness: customColorScheme.brightness,
|
||||
data: state.diary.content),
|
||||
brightness: customColorScheme.brightness,
|
||||
data: state.diary.content,
|
||||
),
|
||||
)
|
||||
: QuillEditor.basic(
|
||||
: QuillEditor.basic(
|
||||
controller: logic.quillController!,
|
||||
config: QuillEditorConfig(
|
||||
showCursor: false,
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
customStyles: ThemeUtil.getInstance(context,
|
||||
customColorScheme: customColorScheme),
|
||||
customStyles: ThemeUtil.getInstance(
|
||||
context,
|
||||
customColorScheme: customColorScheme,
|
||||
),
|
||||
embedBuilders: [
|
||||
ImageEmbedBuilder(isEdit: false),
|
||||
VideoEmbedBuilder(isEdit: false),
|
||||
@@ -447,14 +491,15 @@ class DiaryDetailsPage extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,20 +56,21 @@ class EditLogic extends GetxController {
|
||||
void onInit() {
|
||||
if (state.showWriteTime) _calculateDuration();
|
||||
keyboardObserver = KeyboardObserver(
|
||||
onHeightChanged: (_) {},
|
||||
onStateChanged: (state) {
|
||||
switch (state) {
|
||||
case KeyboardState.opening:
|
||||
break;
|
||||
case KeyboardState.closing:
|
||||
unFocus();
|
||||
break;
|
||||
case KeyboardState.closed:
|
||||
break;
|
||||
case KeyboardState.unknown:
|
||||
break;
|
||||
}
|
||||
});
|
||||
onHeightChanged: (_) {},
|
||||
onStateChanged: (state) {
|
||||
switch (state) {
|
||||
case KeyboardState.opening:
|
||||
break;
|
||||
case KeyboardState.closing:
|
||||
unFocus();
|
||||
break;
|
||||
case KeyboardState.closed:
|
||||
break;
|
||||
case KeyboardState.unknown:
|
||||
break;
|
||||
}
|
||||
},
|
||||
);
|
||||
keyboardObserver.start();
|
||||
super.onInit();
|
||||
}
|
||||
@@ -124,14 +125,16 @@ class EditLogic extends GetxController {
|
||||
//如果是编辑,将日记对象赋值
|
||||
state.isNew = false;
|
||||
state.originalDiary = Get.arguments as Diary;
|
||||
state.type = DiaryType.values
|
||||
.firstWhere((type) => type.value == state.originalDiary!.type);
|
||||
state.type = DiaryType.values.firstWhere(
|
||||
(type) => type.value == state.originalDiary!.type,
|
||||
);
|
||||
state.currentDiary = state.originalDiary!.clone();
|
||||
// 获取分类名称
|
||||
if (state.originalDiary!.categoryId != null) {
|
||||
state.categoryName =
|
||||
IsarUtil.getCategoryName(state.originalDiary!.categoryId!)!
|
||||
.categoryName;
|
||||
IsarUtil.getCategoryName(
|
||||
state.originalDiary!.categoryId!,
|
||||
)!.categoryName;
|
||||
}
|
||||
// 初始化标题控制器
|
||||
titleTextEditingController.text = state.originalDiary!.title;
|
||||
@@ -147,8 +150,9 @@ class EditLogic extends GetxController {
|
||||
//临时拷贝一份拷贝音频数据到缓存目录
|
||||
for (final name in state.originalDiary!.audioName) {
|
||||
state.audioNameList.add(name);
|
||||
await File(FileUtil.getRealPath('audio', name))
|
||||
.copy(FileUtil.getCachePath(name));
|
||||
await File(
|
||||
FileUtil.getRealPath('audio', name),
|
||||
).copy(FileUtil.getCachePath(name));
|
||||
}
|
||||
//临时拷贝一份视频数据,别忘记了缩略图
|
||||
for (final name in state.originalDiary!.videoName) {
|
||||
@@ -158,9 +162,16 @@ class EditLogic extends GetxController {
|
||||
state.videoFileList.add(videoXFile);
|
||||
}
|
||||
quillController = QuillController(
|
||||
document: Document.fromJson(jsonDecode(await Kmp.replaceWithKmp(
|
||||
text: state.originalDiary!.content, replacements: replaceMap))),
|
||||
selection: const TextSelection.collapsed(offset: 0));
|
||||
document: Document.fromJson(
|
||||
jsonDecode(
|
||||
await Kmp.replaceWithKmp(
|
||||
text: state.originalDiary!.content,
|
||||
replacements: replaceMap,
|
||||
),
|
||||
),
|
||||
),
|
||||
selection: const TextSelection.collapsed(offset: 0),
|
||||
);
|
||||
state.totalCount.value = _toPlainText().length;
|
||||
}
|
||||
state.isInit = true;
|
||||
@@ -172,8 +183,10 @@ class EditLogic extends GetxController {
|
||||
_timer?.cancel();
|
||||
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
state.duration += const Duration(seconds: 1);
|
||||
state.durationString.value =
|
||||
state.duration.toString().split('.')[0].padLeft(8, '0');
|
||||
state.durationString.value = state.duration
|
||||
.toString()
|
||||
.split('.')[0]
|
||||
.padLeft(8, '0');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -181,11 +194,11 @@ class EditLogic extends GetxController {
|
||||
return state.type == DiaryType.markdown
|
||||
? _markdownToPlainText(markdownTextEditingController!.text)
|
||||
: quillController!.document.toPlainText([
|
||||
ImageEmbedBuilder(isEdit: true),
|
||||
VideoEmbedBuilder(isEdit: true),
|
||||
AudioEmbedBuilder(isEdit: true),
|
||||
TextIndentEmbedBuilder(isEdit: true),
|
||||
]).trim();
|
||||
ImageEmbedBuilder(isEdit: true),
|
||||
VideoEmbedBuilder(isEdit: true),
|
||||
AudioEmbedBuilder(isEdit: true),
|
||||
TextIndentEmbedBuilder(isEdit: true),
|
||||
]).trim();
|
||||
}
|
||||
|
||||
String _markdownToPlainText(String markdown) {
|
||||
@@ -204,7 +217,11 @@ class EditLogic extends GetxController {
|
||||
final index = quillController!.selection.baseOffset;
|
||||
final length = quillController!.selection.extentOffset - index;
|
||||
quillController?.replaceText(
|
||||
index, length, const TextIndentEmbed('2'), null);
|
||||
index,
|
||||
length,
|
||||
const TextIndentEmbed('2'),
|
||||
null,
|
||||
);
|
||||
quillController?.moveCursorToPosition(index + 1);
|
||||
}
|
||||
|
||||
@@ -249,8 +266,11 @@ class EditLogic extends GetxController {
|
||||
}
|
||||
|
||||
//单张照片
|
||||
Future<void> pickPhoto(ImageSource imageSource, BuildContext context,
|
||||
{bool isMarkdown = false}) async {
|
||||
Future<void> pickPhoto(
|
||||
ImageSource imageSource,
|
||||
BuildContext context, {
|
||||
bool isMarkdown = false,
|
||||
}) async {
|
||||
//获取一张图片
|
||||
final XFile? photo = await MediaUtil.pickPhoto(imageSource);
|
||||
if (photo != null && context.mounted) {
|
||||
@@ -311,9 +331,9 @@ class EditLogic extends GetxController {
|
||||
// }
|
||||
|
||||
//预览视频
|
||||
void toVideoView(List<String> videoPath, int index) {
|
||||
Get.toNamed(AppRoutes.videoPage, arguments: [videoPath, index]);
|
||||
}
|
||||
// void toVideoView(List<String> videoPath, int index) {
|
||||
// Get.toNamed(AppRoutes.videoPage, arguments: [videoPath, index]);
|
||||
// }
|
||||
|
||||
//删除图片
|
||||
void deleteImage({required String path}) async {
|
||||
@@ -339,7 +359,8 @@ class EditLogic extends GetxController {
|
||||
Future<int?> getCoverColor() async {
|
||||
if (state.imageFileList.isNotEmpty) {
|
||||
return await MediaUtil.getColorScheme(
|
||||
FileImage(File(state.imageFileList.first.path)));
|
||||
FileImage(File(state.imageFileList.first.path)),
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@@ -350,7 +371,8 @@ class EditLogic extends GetxController {
|
||||
//如果有封面就获取
|
||||
if (state.imageFileList.isNotEmpty) {
|
||||
return await MediaUtil.getImageAspectRatio(
|
||||
FileImage(File(state.imageFileList.first.path)));
|
||||
FileImage(File(state.imageFileList.first.path)),
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@@ -361,29 +383,39 @@ class EditLogic extends GetxController {
|
||||
state.isSaving = true;
|
||||
update(['modal']);
|
||||
// 根据文本中的实际内容移除不需要的资源
|
||||
final originContent = state.type == DiaryType.markdown
|
||||
? markdownTextEditingController!.text.trim()
|
||||
: jsonEncode(quillController!.document.toDelta().toJson());
|
||||
final originContent =
|
||||
state.type == DiaryType.markdown
|
||||
? markdownTextEditingController!.text.trim()
|
||||
: jsonEncode(quillController!.document.toDelta().toJson());
|
||||
final needImage = await Kmp.findMatches(
|
||||
text: originContent, patterns: state.imagePathList);
|
||||
text: originContent,
|
||||
patterns: state.imagePathList,
|
||||
);
|
||||
final needVideo = await Kmp.findMatches(
|
||||
text: originContent, patterns: state.videoPathList);
|
||||
text: originContent,
|
||||
patterns: state.videoPathList,
|
||||
);
|
||||
final needAudio = await Kmp.findMatches(
|
||||
text: originContent, patterns: state.audioNameList);
|
||||
text: originContent,
|
||||
patterns: state.audioNameList,
|
||||
);
|
||||
state.imageFileList.removeWhere((file) => !needImage.contains(file.path));
|
||||
state.videoFileList.removeWhere((file) => !needVideo.contains(file.path));
|
||||
state.audioNameList.removeWhere((name) => !needAudio.contains(name));
|
||||
// 保存图片
|
||||
final imageNameMap =
|
||||
await MediaUtil.saveImages(imageFileList: state.imageFileList);
|
||||
final imageNameMap = await MediaUtil.saveImages(
|
||||
imageFileList: state.imageFileList,
|
||||
);
|
||||
// 保存视频
|
||||
final videoNameMap =
|
||||
await MediaUtil.saveVideo(videoFileList: state.videoFileList);
|
||||
final videoNameMap = await MediaUtil.saveVideo(
|
||||
videoFileList: state.videoFileList,
|
||||
);
|
||||
//保存录音
|
||||
final audioNameMap = await MediaUtil.saveAudio(state.audioNameList);
|
||||
final content = await Kmp.replaceWithKmp(
|
||||
text: originContent,
|
||||
replacements: {...imageNameMap, ...videoNameMap, ...audioNameMap});
|
||||
text: originContent,
|
||||
replacements: {...imageNameMap, ...videoNameMap, ...audioNameMap},
|
||||
);
|
||||
state.currentDiary
|
||||
..title = titleTextEditingController.text
|
||||
..content = content
|
||||
@@ -396,12 +428,15 @@ class EditLogic extends GetxController {
|
||||
..aspect = await getCoverAspect();
|
||||
|
||||
await IsarUtil.updateADiary(
|
||||
oldDiary: state.originalDiary, newDiary: state.currentDiary);
|
||||
oldDiary: state.originalDiary,
|
||||
newDiary: state.currentDiary,
|
||||
);
|
||||
state.isNew
|
||||
? Get.back(result: state.currentDiary.categoryId ?? '')
|
||||
: Get.back(result: 'changed');
|
||||
NoticeUtil.showToast(
|
||||
state.isNew ? l10n.editSaveSuccess : l10n.editChangeSuccess);
|
||||
state.isNew ? l10n.editSaveSuccess : l10n.editChangeSuccess,
|
||||
);
|
||||
}
|
||||
|
||||
DateTime? oldTime;
|
||||
@@ -428,20 +463,24 @@ class EditLogic extends GetxController {
|
||||
);
|
||||
if (nowDateTime != null) {
|
||||
state.currentDiary.time = state.currentDiary.time.copyWith(
|
||||
year: nowDateTime.year,
|
||||
month: nowDateTime.month,
|
||||
day: nowDateTime.day);
|
||||
year: nowDateTime.year,
|
||||
month: nowDateTime.month,
|
||||
day: nowDateTime.day,
|
||||
);
|
||||
update(['Date']);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> changeTime() async {
|
||||
final nowTime = await showTimePicker(
|
||||
context: Get.context!,
|
||||
initialTime: TimeOfDay.fromDateTime(state.currentDiary.time));
|
||||
context: Get.context!,
|
||||
initialTime: TimeOfDay.fromDateTime(state.currentDiary.time),
|
||||
);
|
||||
if (nowTime != null) {
|
||||
state.currentDiary.time = state.currentDiary.time
|
||||
.copyWith(hour: nowTime.hour, minute: nowTime.minute);
|
||||
state.currentDiary.time = state.currentDiary.time.copyWith(
|
||||
hour: nowTime.hour,
|
||||
minute: nowTime.minute,
|
||||
);
|
||||
update(['Date']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,13 +20,13 @@ class MediaPage extends StatelessWidget {
|
||||
static final iconMap = {
|
||||
MediaType.image: Icons.image_rounded,
|
||||
MediaType.audio: Icons.audiotrack_rounded,
|
||||
MediaType.video: Icons.movie_rounded
|
||||
MediaType.video: Icons.movie_rounded,
|
||||
};
|
||||
|
||||
static final textMap = {
|
||||
MediaType.image: l10n.mediaTypeImage,
|
||||
MediaType.audio: l10n.mediaTypeAudio,
|
||||
MediaType.video: l10n.mediaTypeVideo
|
||||
MediaType.video: l10n.mediaTypeVideo,
|
||||
};
|
||||
|
||||
@override
|
||||
@@ -46,34 +46,36 @@ class MediaPage extends StatelessWidget {
|
||||
title: Text(l10n.homeNavigatorMedia),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
final res = await showCalendarDatePicker2Dialog(
|
||||
context: context,
|
||||
config: CalendarDatePicker2WithActionButtonsConfig(
|
||||
calendarViewMode: CalendarDatePicker2Mode.day,
|
||||
calendarType: CalendarDatePicker2Type.single,
|
||||
hideMonthPickerDividers: true,
|
||||
hideYearPickerDividers: true,
|
||||
useAbbrLabelForMonthModePicker: true,
|
||||
allowSameValueSelection: true,
|
||||
dayTextStylePredicate: (
|
||||
{required DateTime date}) {
|
||||
return state.dateTimeList.contains(date)
|
||||
? textStyle.labelMedium
|
||||
?.copyWith(color: colorScheme.primary)
|
||||
: null;
|
||||
},
|
||||
selectableDayPredicate: (DateTime date) {
|
||||
return state.dateTimeList.contains(date);
|
||||
},
|
||||
),
|
||||
borderRadius: AppBorderRadius.mediumBorderRadius,
|
||||
dialogSize: const Size(300, 400));
|
||||
if (res != null && res.isNotEmpty) {
|
||||
logic.jumpTo(res.first ?? state.dateTimeList.first);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.calendar_month_rounded)),
|
||||
onPressed: () async {
|
||||
final res = await showCalendarDatePicker2Dialog(
|
||||
context: context,
|
||||
config: CalendarDatePicker2WithActionButtonsConfig(
|
||||
calendarViewMode: CalendarDatePicker2Mode.day,
|
||||
calendarType: CalendarDatePicker2Type.single,
|
||||
hideMonthPickerDividers: true,
|
||||
hideYearPickerDividers: true,
|
||||
useAbbrLabelForMonthModePicker: true,
|
||||
allowSameValueSelection: true,
|
||||
dayTextStylePredicate: ({required DateTime date}) {
|
||||
return state.dateTimeList.contains(date)
|
||||
? textStyle.labelMedium?.copyWith(
|
||||
color: colorScheme.primary,
|
||||
)
|
||||
: null;
|
||||
},
|
||||
selectableDayPredicate: (DateTime date) {
|
||||
return state.dateTimeList.contains(date);
|
||||
},
|
||||
),
|
||||
borderRadius: AppBorderRadius.mediumBorderRadius,
|
||||
dialogSize: const Size(300, 400),
|
||||
);
|
||||
if (res != null && res.isNotEmpty) {
|
||||
logic.jumpTo(res.first ?? state.dateTimeList.first);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.calendar_month_rounded),
|
||||
),
|
||||
Obx(() {
|
||||
return PopupMenuButton(
|
||||
offset: const Offset(0, 46),
|
||||
@@ -104,7 +106,7 @@ class MediaPage extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.delete_sweep),
|
||||
Text(l10n.mediaDeleteUseLessFile)
|
||||
Text(l10n.mediaDeleteUseLessFile),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -115,51 +117,68 @@ class MediaPage extends StatelessWidget {
|
||||
),
|
||||
body: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
child: state.isFetching
|
||||
? const Center(
|
||||
key: ValueKey('searching'), child: SearchLoading())
|
||||
: (state.datetimeMediaMap.isNotEmpty
|
||||
? ScrollablePositionedList.builder(
|
||||
itemBuilder: (context, index) {
|
||||
final datetime = state.dateTimeList[index];
|
||||
final fileList =
|
||||
state.datetimeMediaMap[datetime]!;
|
||||
return switch (state.mediaType.value) {
|
||||
MediaType.image => MediaImageComponent(
|
||||
dateTime: datetime,
|
||||
imageList: fileList,
|
||||
),
|
||||
MediaType.audio => MediaAudioComponent(
|
||||
dateTime: datetime, audioList: fileList),
|
||||
MediaType.video => MediaVideoComponent(
|
||||
dateTime: datetime, videoList: fileList),
|
||||
};
|
||||
},
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
itemCount: state.dateTimeList.length,
|
||||
itemScrollController: logic.itemScrollController,
|
||||
itemPositionsListener: logic.itemPositionsListener,
|
||||
scrollOffsetController:
|
||||
logic.scrollOffsetController,
|
||||
scrollOffsetListener: logic.scrollOffsetListener,
|
||||
)
|
||||
: Center(
|
||||
key: const ValueKey('empty'),
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.boxArchive,
|
||||
size: 80,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
)),
|
||||
child:
|
||||
state.isFetching
|
||||
? const Center(
|
||||
key: ValueKey('searching'),
|
||||
child: SearchLoading(),
|
||||
)
|
||||
: (state.datetimeMediaMap.isNotEmpty
|
||||
? Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: ClipRRect(
|
||||
borderRadius:
|
||||
AppBorderRadius.mediumBorderRadius,
|
||||
child: ScrollablePositionedList.builder(
|
||||
itemBuilder: (context, index) {
|
||||
final datetime = state.dateTimeList[index];
|
||||
final fileList =
|
||||
state.datetimeMediaMap[datetime]!;
|
||||
return switch (state.mediaType.value) {
|
||||
MediaType.image => MediaImageComponent(
|
||||
dateTime: datetime,
|
||||
imageList: fileList,
|
||||
),
|
||||
MediaType.audio => MediaAudioComponent(
|
||||
dateTime: datetime,
|
||||
audioList: fileList,
|
||||
),
|
||||
MediaType.video => MediaVideoComponent(
|
||||
dateTime: datetime,
|
||||
videoList: fileList,
|
||||
),
|
||||
};
|
||||
},
|
||||
itemCount: state.dateTimeList.length,
|
||||
itemScrollController:
|
||||
logic.itemScrollController,
|
||||
itemPositionsListener:
|
||||
logic.itemPositionsListener,
|
||||
scrollOffsetController:
|
||||
logic.scrollOffsetController,
|
||||
scrollOffsetListener:
|
||||
logic.scrollOffsetListener,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Center(
|
||||
key: const ValueKey('empty'),
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.boxArchive,
|
||||
size: 80,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
)),
|
||||
),
|
||||
),
|
||||
GetBuilder<MediaLogic>(
|
||||
id: 'modal',
|
||||
builder: (_) {
|
||||
return state.isCleaning
|
||||
? const LottieModal(type: LoadingType.fileProcess)
|
||||
: const SizedBox.shrink();
|
||||
}),
|
||||
id: 'modal',
|
||||
builder: (_) {
|
||||
return state.isCleaning
|
||||
? const LottieModal(type: LoadingType.fileProcess)
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
||||
@@ -10,13 +10,19 @@ class VideoLogic extends GetxController {
|
||||
|
||||
late final videoController = media_kit.VideoController(player);
|
||||
|
||||
VideoLogic({required List<String> videoPathList, required int initialIndex}) {
|
||||
state.videoPathList = videoPathList;
|
||||
state.videoIndex = initialIndex.obs;
|
||||
}
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
state.playable = Playlist(
|
||||
List.generate(state.videoPathList.length, (index) {
|
||||
return Media(state.videoPathList[index]);
|
||||
}),
|
||||
index: state.videoIndex.value);
|
||||
List.generate(state.videoPathList.length, (index) {
|
||||
return Media(state.videoPathList[index]);
|
||||
}),
|
||||
index: state.videoIndex.value,
|
||||
);
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,5 @@ class VideoState {
|
||||
//当前位置
|
||||
late Rx<int> videoIndex;
|
||||
|
||||
VideoState() {
|
||||
videoPathList = Get.arguments[0] as List<String>;
|
||||
videoIndex = (Get.arguments[1] as int).obs;
|
||||
}
|
||||
VideoState();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dismissible_page/dismissible_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:media_kit_video/media_kit_video.dart';
|
||||
import 'package:moodiary/common/values/media_type.dart';
|
||||
@@ -8,13 +9,45 @@ import 'package:refreshed/refreshed.dart';
|
||||
|
||||
import 'video_logic.dart';
|
||||
|
||||
Future<T?> showVideoView<T>(
|
||||
BuildContext context,
|
||||
List<String> videoPathList,
|
||||
int initialIndex, {
|
||||
required String heroTagPrefix,
|
||||
}) async {
|
||||
return await context.pushTransparentRoute(
|
||||
VideoPage(
|
||||
videoPathList: videoPathList,
|
||||
initialIndex: initialIndex,
|
||||
heroTagPrefix: heroTagPrefix,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class VideoPage extends StatelessWidget {
|
||||
const VideoPage({super.key});
|
||||
final List<String> _videoPathList;
|
||||
final int _initialIndex;
|
||||
|
||||
final String _heroTagPrefix;
|
||||
|
||||
String get _tag => Object.hash(_videoPathList, _initialIndex).toString();
|
||||
|
||||
const VideoPage({
|
||||
super.key,
|
||||
required List<String> videoPathList,
|
||||
required int initialIndex,
|
||||
required String heroTagPrefix,
|
||||
}) : _videoPathList = videoPathList,
|
||||
_initialIndex = initialIndex,
|
||||
_heroTagPrefix = heroTagPrefix;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final logic = Bind.find<VideoLogic>();
|
||||
final state = Bind.find<VideoLogic>().state;
|
||||
final logic = Get.put(
|
||||
VideoLogic(videoPathList: _videoPathList, initialIndex: _initialIndex),
|
||||
tag: _tag,
|
||||
);
|
||||
final state = Bind.find<VideoLogic>(tag: _tag).state;
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
Widget buildCustomTheme({required Widget child}) {
|
||||
@@ -46,6 +79,8 @@ class VideoPage extends StatelessWidget {
|
||||
}
|
||||
|
||||
return GetBuilder<VideoLogic>(
|
||||
tag: _tag,
|
||||
assignId: true,
|
||||
builder: (_) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
@@ -59,22 +94,27 @@ class VideoPage extends StatelessWidget {
|
||||
}),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
MediaUtil.saveToGallery(
|
||||
path: state.videoPathList[state.videoIndex.value],
|
||||
type: MediaType.video);
|
||||
},
|
||||
icon: const Icon(Icons.save_alt)),
|
||||
onPressed: () {
|
||||
MediaUtil.saveToGallery(
|
||||
path: state.videoPathList[state.videoIndex.value],
|
||||
type: MediaType.video,
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.save_alt),
|
||||
),
|
||||
],
|
||||
iconTheme: const IconThemeData(color: Colors.white),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 56.0),
|
||||
child: buildCustomTheme(
|
||||
child: Video(
|
||||
controller: logic.videoController,
|
||||
controls: AdaptiveVideoControls,
|
||||
alignment: Alignment.center,
|
||||
child: Hero(
|
||||
tag: '$_heroTagPrefix${state.videoIndex.value}',
|
||||
child: Video(
|
||||
controller: logic.videoController,
|
||||
controls: AdaptiveVideoControls,
|
||||
alignment: Alignment.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -44,8 +44,6 @@ import 'package:moodiary/pages/start/start_logic.dart';
|
||||
import 'package:moodiary/pages/start/start_view.dart';
|
||||
import 'package:moodiary/pages/user/user_logic.dart';
|
||||
import 'package:moodiary/pages/user/user_view.dart';
|
||||
import 'package:moodiary/pages/video/video_logic.dart';
|
||||
import 'package:moodiary/pages/video/video_view.dart';
|
||||
import 'package:page_transition/page_transition.dart';
|
||||
import 'package:refreshed/refreshed.dart';
|
||||
|
||||
@@ -150,11 +148,6 @@ class AppPages {
|
||||
page: () => const AboutPage(),
|
||||
binds: [Bind.lazyPut(fenix: true, () => AboutLogic())],
|
||||
),
|
||||
MoodiaryGetPage(
|
||||
name: AppRoutes.videoPage,
|
||||
page: () => const VideoPage(),
|
||||
binds: [Bind.lazyPut(fenix: true, () => VideoLogic())],
|
||||
),
|
||||
MoodiaryGetPage(
|
||||
name: AppRoutes.mapPage,
|
||||
page: () => const MapPage(),
|
||||
|
||||
@@ -58,9 +58,6 @@ abstract class AppRoutes {
|
||||
//关于
|
||||
static const aboutPage = '/about';
|
||||
|
||||
//视频预览路由
|
||||
static const videoPage = '/video';
|
||||
|
||||
//地图
|
||||
static const mapPage = '/map';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user