refactor: remove video page routing and update video state initialization

This commit is contained in:
ZhuJHua
2025-02-23 16:28:41 +08:00
parent e5578bdeae
commit f172e0791a
15 changed files with 828 additions and 595 deletions

View File

@@ -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,
),
);
}),
],

View File

@@ -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,
});

View File

@@ -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),
);
}
}

View File

@@ -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,
),
],

View File

@@ -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,
),
),
],

View File

@@ -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,
),
),
],
);

View File

@@ -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,
);
}
}

View File

@@ -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 {
],
),
),
),
],
),
),
],
),
],
),
),
],
),
);
});
),
);
},
);
}
}

View File

@@ -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']);
}
}

View File

@@ -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();
},
),
],
);
},

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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,
),
),
),
),

View File

@@ -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(),

View File

@@ -58,9 +58,6 @@ abstract class AppRoutes {
//关于
static const aboutPage = '/about';
//视频预览路由
static const videoPage = '/video';
//地图
static const mapPage = '/map';