mirror of
https://github.com/ZhuJHua/moodiary.git
synced 2026-04-05 07:59:07 +08:00
feat: optimize image processing capabilities
This commit is contained in:
@@ -1,70 +1,322 @@
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:moodiary/utils/media_util.dart';
|
||||
import 'package:moodiary/utils/cache_util.dart';
|
||||
import 'package:moodiary/utils/log_util.dart';
|
||||
|
||||
class ThumbnailImage extends StatelessWidget {
|
||||
final kTransparentImage = Uint8List.fromList(<int>[
|
||||
0x89,
|
||||
0x50,
|
||||
0x4E,
|
||||
0x47,
|
||||
0x0D,
|
||||
0x0A,
|
||||
0x1A,
|
||||
0x0A,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0D,
|
||||
0x49,
|
||||
0x48,
|
||||
0x44,
|
||||
0x52,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x08,
|
||||
0x06,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x1F,
|
||||
0x15,
|
||||
0xC4,
|
||||
0x89,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0A,
|
||||
0x49,
|
||||
0x44,
|
||||
0x41,
|
||||
0x54,
|
||||
0x78,
|
||||
0x9C,
|
||||
0x63,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x01,
|
||||
0x0D,
|
||||
0x0A,
|
||||
0x2D,
|
||||
0xB4,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x49,
|
||||
0x45,
|
||||
0x4E,
|
||||
0x44,
|
||||
0xAE,
|
||||
0x42,
|
||||
0x60,
|
||||
0x82,
|
||||
]);
|
||||
|
||||
enum _ImageLoadState { loading, error, success }
|
||||
|
||||
class _ImageState {
|
||||
final int width;
|
||||
final int height;
|
||||
final String path;
|
||||
final double aspectRatio;
|
||||
|
||||
_ImageState({
|
||||
required this.width,
|
||||
required this.height,
|
||||
required this.path,
|
||||
required this.aspectRatio,
|
||||
});
|
||||
}
|
||||
|
||||
class MoodiaryImage extends StatefulWidget {
|
||||
final String imagePath;
|
||||
final int size;
|
||||
final BoxFit? fit;
|
||||
final VoidCallback? onTap;
|
||||
final String? heroTag;
|
||||
final BorderRadius? borderRadius;
|
||||
final bool showBorder;
|
||||
final EdgeInsets? padding;
|
||||
|
||||
final String heroTag;
|
||||
|
||||
const ThumbnailImage({
|
||||
const MoodiaryImage({
|
||||
super.key,
|
||||
required this.imagePath,
|
||||
required this.size,
|
||||
this.fit,
|
||||
this.onTap,
|
||||
required this.heroTag,
|
||||
this.heroTag,
|
||||
this.borderRadius,
|
||||
this.showBorder = false,
|
||||
this.padding,
|
||||
});
|
||||
|
||||
Widget _buildImage({
|
||||
required double aspectRatio,
|
||||
required ImageProvider imageProvider,
|
||||
required double pixelRatio,
|
||||
}) {
|
||||
final image = Image(
|
||||
key: ValueKey(imagePath),
|
||||
image: ResizeImage(
|
||||
imageProvider,
|
||||
width: aspectRatio < 1.0 ? (size * pixelRatio).toInt() : null,
|
||||
height: aspectRatio >= 1.0 ? (size * pixelRatio).toInt() : null,
|
||||
@override
|
||||
State<MoodiaryImage> createState() => _MoodiaryImageState();
|
||||
}
|
||||
|
||||
class _MoodiaryImageState extends State<MoodiaryImage> {
|
||||
final Rx<_ImageLoadState> _loadState = Rx<_ImageLoadState>(
|
||||
_ImageLoadState.loading,
|
||||
);
|
||||
|
||||
late _ImageState _imageState;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadImage();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_loadState.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant MoodiaryImage oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.imagePath != oldWidget.imagePath ||
|
||||
widget.size != oldWidget.size) {
|
||||
_loadImage();
|
||||
}
|
||||
}
|
||||
|
||||
void _loadImage() async {
|
||||
_loadState.value = _ImageLoadState.loading;
|
||||
try {
|
||||
logger.d('Image loaded from path: ${widget.imagePath}');
|
||||
final imageAspect = await ImageCacheUtil().getImageAspectRatioWithCache(
|
||||
imagePath: widget.imagePath,
|
||||
);
|
||||
|
||||
final imageSize = widget.size;
|
||||
final width =
|
||||
imageAspect < 1.0 ? imageSize : (imageSize * imageAspect).ceil();
|
||||
final height =
|
||||
imageAspect >= 1.0 ? imageSize : (imageSize / imageAspect).ceil();
|
||||
|
||||
final path = await ImageCacheUtil().getLocalImagePathWithCache(
|
||||
imagePath: widget.imagePath,
|
||||
imageWidth: width * 2,
|
||||
imageHeight: height * 2,
|
||||
imageAspectRatio: imageAspect,
|
||||
);
|
||||
|
||||
_imageState = _ImageState(
|
||||
width: width,
|
||||
height: height,
|
||||
path: path,
|
||||
aspectRatio: imageAspect,
|
||||
);
|
||||
|
||||
_loadState.value = _ImageLoadState.success;
|
||||
} catch (e) {
|
||||
_loadState.value = _ImageLoadState.error;
|
||||
}
|
||||
}
|
||||
|
||||
BorderRadius _shrinkBorderRadius(BorderRadius radius, double amount) {
|
||||
return BorderRadius.only(
|
||||
topLeft: Radius.elliptical(
|
||||
(radius.topLeft.x - amount).clamp(0, double.infinity),
|
||||
(radius.topLeft.y - amount).clamp(0, double.infinity),
|
||||
),
|
||||
topRight: Radius.elliptical(
|
||||
(radius.topRight.x - amount).clamp(0, double.infinity),
|
||||
(radius.topRight.y - amount).clamp(0, double.infinity),
|
||||
),
|
||||
bottomLeft: Radius.elliptical(
|
||||
(radius.bottomLeft.x - amount).clamp(0, double.infinity),
|
||||
(radius.bottomLeft.y - amount).clamp(0, double.infinity),
|
||||
),
|
||||
bottomRight: Radius.elliptical(
|
||||
(radius.bottomRight.x - amount).clamp(0, double.infinity),
|
||||
(radius.bottomRight.y - amount).clamp(0, double.infinity),
|
||||
),
|
||||
fit: fit ?? BoxFit.cover,
|
||||
);
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Hero(tag: heroTag, child: image),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final fileImage = FileImage(File(imagePath));
|
||||
final devicePixelRatio = MediaQuery.devicePixelRatioOf(context);
|
||||
const borderWidth = 1.0;
|
||||
|
||||
final Future getAspectRatio = MediaUtil.getImageAspectRatio(fileImage);
|
||||
final loading = ColoredBox(
|
||||
color: context.theme.colorScheme.surfaceContainer,
|
||||
child: const Center(child: Icon(Icons.image_search_rounded)),
|
||||
);
|
||||
return FutureBuilder(
|
||||
future: getAspectRatio,
|
||||
builder: (context, snapshot) {
|
||||
return switch (snapshot.connectionState) {
|
||||
ConnectionState.none => loading,
|
||||
ConnectionState.waiting => loading,
|
||||
ConnectionState.active => loading,
|
||||
ConnectionState.done => _buildImage(
|
||||
aspectRatio: snapshot.data as double,
|
||||
imageProvider: fileImage,
|
||||
pixelRatio: devicePixelRatio,
|
||||
),
|
||||
};
|
||||
},
|
||||
final outerRadius = widget.borderRadius ?? BorderRadius.zero;
|
||||
final innerRadius =
|
||||
widget.showBorder
|
||||
? _shrinkBorderRadius(outerRadius, borderWidth)
|
||||
: outerRadius;
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: outerRadius,
|
||||
border:
|
||||
widget.showBorder
|
||||
? Border.all(
|
||||
color: context.theme.colorScheme.outline.withValues(
|
||||
alpha: 0.6,
|
||||
),
|
||||
width: borderWidth,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
margin: widget.padding,
|
||||
child: ClipRRect(
|
||||
borderRadius: innerRadius,
|
||||
child: AnimatedSwitcher(
|
||||
duration: Durations.short3,
|
||||
switchInCurve: Curves.easeOut,
|
||||
switchOutCurve: Curves.easeIn,
|
||||
child: Obx(() {
|
||||
switch (_loadState.value) {
|
||||
case _ImageLoadState.loading:
|
||||
return const _LoadingPlaceholder(key: ValueKey('loading'));
|
||||
case _ImageLoadState.error:
|
||||
return const _ErrorPlaceholder(key: ValueKey('error'));
|
||||
case _ImageLoadState.success:
|
||||
final imagePath = _imageState.path;
|
||||
final width = _imageState.width;
|
||||
final height = _imageState.height;
|
||||
|
||||
return GestureDetector(
|
||||
key: const ValueKey('image'),
|
||||
onTap:
|
||||
widget.onTap != null
|
||||
? () async {
|
||||
if (widget.heroTag != null) {
|
||||
await precacheImage(
|
||||
FileImage(File(widget.imagePath)),
|
||||
context,
|
||||
);
|
||||
}
|
||||
widget.onTap?.call();
|
||||
}
|
||||
: null,
|
||||
behavior: HitTestBehavior.translucent,
|
||||
child: HeroMode(
|
||||
enabled: widget.heroTag != null,
|
||||
child: Hero(
|
||||
tag: widget.heroTag ?? '',
|
||||
child: FadeInImage(
|
||||
key: ValueKey(imagePath),
|
||||
image: FileImage(File(imagePath)),
|
||||
placeholder: MemoryImage(kTransparentImage),
|
||||
fadeInDuration: Durations.short2,
|
||||
fadeOutDuration: Durations.short1,
|
||||
fit: widget.fit ?? BoxFit.cover,
|
||||
width: width.toDouble(),
|
||||
height: height.toDouble(),
|
||||
imageErrorBuilder: (_, __, ___) {
|
||||
return const _ErrorPlaceholder(
|
||||
key: ValueKey('image_error'),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ErrorPlaceholder extends StatelessWidget {
|
||||
const _ErrorPlaceholder({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ColoredBox(
|
||||
color: context.theme.colorScheme.errorContainer,
|
||||
child: Center(
|
||||
child: Icon(
|
||||
Icons.error_rounded,
|
||||
color: context.theme.colorScheme.onErrorContainer,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LoadingPlaceholder extends StatelessWidget {
|
||||
const _LoadingPlaceholder({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ColoredBox(
|
||||
color: context.theme.colorScheme.surfaceContainer,
|
||||
child: Center(
|
||||
child: Icon(
|
||||
Icons.image_search_rounded,
|
||||
color: context.theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
@@ -7,6 +5,7 @@ import 'package:intl/intl.dart';
|
||||
import 'package:moodiary/common/models/isar/diary.dart';
|
||||
import 'package:moodiary/common/values/border.dart';
|
||||
import 'package:moodiary/common/values/diary_type.dart';
|
||||
import 'package:moodiary/components/base/image.dart';
|
||||
import 'package:moodiary/components/base/text.dart';
|
||||
import 'package:moodiary/components/diary_card/basic_card_logic.dart';
|
||||
import 'package:moodiary/utils/file_util.dart';
|
||||
@@ -18,33 +17,23 @@ class CalendarDiaryCardComponent extends StatelessWidget with BasicCardLogic {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final pixelRatio = MediaQuery.devicePixelRatioOf(context);
|
||||
|
||||
Widget buildImage() {
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
spacing: 4.0,
|
||||
spacing: 8.0,
|
||||
children: List.generate(diary.imageName.length, (index) {
|
||||
return SizedBox(
|
||||
height: 100,
|
||||
width: 100,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: ResizeImage(
|
||||
FileImage(
|
||||
File(
|
||||
FileUtil.getRealPath('image', diary.imageName[index]),
|
||||
),
|
||||
),
|
||||
width: (100 * pixelRatio).toInt(),
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
border: Border.all(color: context.theme.colorScheme.outline),
|
||||
borderRadius: AppBorderRadius.smallBorderRadius,
|
||||
height: 100,
|
||||
child: MoodiaryImage(
|
||||
imagePath: FileUtil.getRealPath(
|
||||
'image',
|
||||
diary.imageName[index],
|
||||
),
|
||||
borderRadius: AppBorderRadius.smallBorderRadius,
|
||||
showBorder: true,
|
||||
size: 100,
|
||||
),
|
||||
);
|
||||
}),
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
@@ -7,6 +5,7 @@ import 'package:intl/intl.dart';
|
||||
import 'package:moodiary/common/models/isar/diary.dart';
|
||||
import 'package:moodiary/common/values/border.dart';
|
||||
import 'package:moodiary/common/values/diary_type.dart';
|
||||
import 'package:moodiary/components/base/image.dart';
|
||||
import 'package:moodiary/components/base/text.dart';
|
||||
import 'package:moodiary/components/diary_card/basic_card_logic.dart';
|
||||
import 'package:moodiary/utils/file_util.dart';
|
||||
@@ -18,22 +17,15 @@ class GirdDiaryCardComponent extends StatelessWidget with BasicCardLogic {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final pixelRatio = MediaQuery.devicePixelRatioOf(context);
|
||||
|
||||
Widget buildImage() {
|
||||
return Container(
|
||||
height: 154.0,
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: ResizeImage(
|
||||
FileImage(
|
||||
File(FileUtil.getRealPath('image', diary.imageName.first)),
|
||||
),
|
||||
width: (250 * pixelRatio).toInt(),
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
return SizedBox(
|
||||
height: 154,
|
||||
child: ClipRRect(
|
||||
borderRadius: AppBorderRadius.mediumBorderRadius,
|
||||
child: MoodiaryImage(
|
||||
imagePath: FileUtil.getRealPath('image', diary.imageName.first),
|
||||
size: 250,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
@@ -7,6 +5,7 @@ import 'package:intl/intl.dart';
|
||||
import 'package:moodiary/common/models/isar/diary.dart';
|
||||
import 'package:moodiary/common/values/border.dart';
|
||||
import 'package:moodiary/common/values/diary_type.dart';
|
||||
import 'package:moodiary/components/base/image.dart';
|
||||
import 'package:moodiary/components/base/text.dart';
|
||||
import 'package:moodiary/components/diary_card/basic_card_logic.dart';
|
||||
import 'package:moodiary/utils/file_util.dart';
|
||||
@@ -24,22 +23,14 @@ class ListDiaryCardComponent extends StatelessWidget with BasicCardLogic {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final pixelRatio = MediaQuery.devicePixelRatioOf(context);
|
||||
Widget buildImage() {
|
||||
return AspectRatio(
|
||||
aspectRatio: 1.0,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: ResizeImage(
|
||||
FileImage(
|
||||
File(FileUtil.getRealPath('image', diary.imageName.first)),
|
||||
),
|
||||
width: (132 * pixelRatio).toInt(),
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
borderRadius: AppBorderRadius.mediumBorderRadius,
|
||||
child: ClipRRect(
|
||||
borderRadius: AppBorderRadius.mediumBorderRadius,
|
||||
child: MoodiaryImage(
|
||||
imagePath: FileUtil.getRealPath('image', diary.imageName.first),
|
||||
size: 132,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:moodiary/common/values/border.dart';
|
||||
import 'package:moodiary/components/base/image.dart';
|
||||
import 'package:moodiary/pages/image/image_view.dart';
|
||||
import 'package:moodiary/utils/file_util.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
@@ -20,24 +20,23 @@ class MarkdownImageEmbed extends StatelessWidget {
|
||||
final imagePath =
|
||||
isEdit ? imageName : FileUtil.getRealPath('image', imageName);
|
||||
final heroPrefix = const Uuid().v4();
|
||||
final image = MoodiaryImage(
|
||||
imagePath: imagePath,
|
||||
size: 300,
|
||||
heroTag: '${heroPrefix}0',
|
||||
borderRadius: AppBorderRadius.mediumBorderRadius,
|
||||
showBorder: true,
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
onTap: () {
|
||||
if (!isEdit) {
|
||||
showImageView(context, [imagePath], 0, heroTagPrefix: heroPrefix);
|
||||
}
|
||||
},
|
||||
);
|
||||
return Center(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (!isEdit) {
|
||||
showImageView(context, [imagePath], 0, heroTagPrefix: heroPrefix);
|
||||
}
|
||||
},
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 300),
|
||||
child: Hero(
|
||||
tag: '${heroPrefix}0',
|
||||
child: Card.outlined(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
color: Colors.transparent,
|
||||
child: Image.file(File(imagePath)),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 300),
|
||||
child: image,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ class MediaImageComponent extends StatelessWidget {
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (context, index) {
|
||||
final image = ThumbnailImage(
|
||||
final image = MoodiaryImage(
|
||||
imagePath: imageList[index],
|
||||
size: 120,
|
||||
heroTag: '$heroPrefix$index',
|
||||
@@ -66,17 +66,7 @@ class MediaImageComponent extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
);
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
await showImageView(
|
||||
context,
|
||||
imageList,
|
||||
index,
|
||||
heroTagPrefix: heroPrefix,
|
||||
);
|
||||
},
|
||||
child: image,
|
||||
);
|
||||
return image;
|
||||
},
|
||||
itemCount: imageList.length,
|
||||
),
|
||||
|
||||
@@ -61,31 +61,29 @@ class MediaVideoComponent extends StatelessWidget {
|
||||
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,
|
||||
),
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: MoodiaryImage(
|
||||
imagePath: thumbnailList[index],
|
||||
heroTag: '$heroPrefix$index',
|
||||
onTap: () async {
|
||||
await showVideoView(
|
||||
context,
|
||||
videoList,
|
||||
index,
|
||||
heroTagPrefix: '$heroPrefix$index',
|
||||
);
|
||||
},
|
||||
size: 120,
|
||||
),
|
||||
const FrostedGlassButton(
|
||||
size: 32,
|
||||
child: Center(child: Icon(Icons.play_arrow_rounded)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const FrostedGlassButton(
|
||||
size: 32,
|
||||
child: Center(child: Icon(Icons.play_arrow_rounded)),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
itemCount: thumbnailList.length,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_quill/flutter_quill.dart';
|
||||
import 'package:moodiary/common/values/border.dart';
|
||||
import 'package:moodiary/components/base/image.dart';
|
||||
import 'package:moodiary/pages/image/image_view.dart';
|
||||
import 'package:moodiary/utils/file_util.dart';
|
||||
|
||||
@@ -35,31 +34,28 @@ class ImageEmbedBuilder extends EmbedBuilder {
|
||||
isEdit
|
||||
? imageEmbed.name
|
||||
: FileUtil.getRealPath('image', imageEmbed.name);
|
||||
final image = MoodiaryImage(
|
||||
imagePath: imagePath,
|
||||
size: 300,
|
||||
heroTag: '${imageEmbed.name}0',
|
||||
borderRadius: AppBorderRadius.mediumBorderRadius,
|
||||
showBorder: true,
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
onTap: () {
|
||||
if (!isEdit) {
|
||||
showImageView(
|
||||
context,
|
||||
[imagePath],
|
||||
0,
|
||||
heroTagPrefix: imageEmbed.name,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
return Center(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (!isEdit) {
|
||||
showImageView(
|
||||
context,
|
||||
[imagePath],
|
||||
0,
|
||||
heroTagPrefix: imageEmbed.name,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 300),
|
||||
child: Card.outlined(
|
||||
color: Colors.transparent,
|
||||
child: Hero(
|
||||
tag: '${imageEmbed.name}0',
|
||||
child: ClipRRect(
|
||||
borderRadius: AppBorderRadius.mediumBorderRadius,
|
||||
child: Image.file(File(imagePath)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 300),
|
||||
child: image,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// ignore: unused_import
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
|
||||
import 'app_localizations.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
@@ -18,6 +18,7 @@ import 'package:moodiary/components/window_buttons/window_buttons.dart';
|
||||
import 'package:moodiary/config/env.dart';
|
||||
import 'package:moodiary/l10n/app_localizations.dart';
|
||||
import 'package:moodiary/l10n/l10n.dart';
|
||||
import 'package:moodiary/persistence/hive.dart';
|
||||
import 'package:moodiary/persistence/isar.dart';
|
||||
import 'package:moodiary/persistence/pref.dart';
|
||||
import 'package:moodiary/router/app_pages.dart';
|
||||
@@ -31,11 +32,12 @@ import 'package:video_player_media_kit/video_player_media_kit.dart';
|
||||
|
||||
Future<void> _initSystem() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await RustLib.init();
|
||||
await PrefUtil.initPref();
|
||||
await IsarUtil.initIsar();
|
||||
await ThemeUtil().buildTheme();
|
||||
await WebDavUtil().initWebDav();
|
||||
await HiveUtil().init();
|
||||
unawaited(RustLib.init());
|
||||
unawaited(_platFormOption());
|
||||
WebDavUtil().initWebDav();
|
||||
VideoPlayerMediaKit.ensureInitialized(windows: true);
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||
SystemChrome.setSystemUIOverlayStyle(
|
||||
@@ -44,7 +46,7 @@ Future<void> _initSystem() async {
|
||||
systemNavigationBarContrastEnforced: false,
|
||||
),
|
||||
);
|
||||
await _platFormOption();
|
||||
await ThemeUtil().buildTheme();
|
||||
}
|
||||
|
||||
Future<Locale> _findLanguage() async {
|
||||
|
||||
@@ -15,6 +15,12 @@ class ImageLogic extends GetxController {
|
||||
@override
|
||||
void onInit() {
|
||||
pageController = PageController(initialPage: state.imageIndex.value);
|
||||
pageController.addListener(() {
|
||||
final index = pageController.page?.round() ?? 0;
|
||||
if (state.imageIndex.value != index) {
|
||||
state.imageIndex.value = index;
|
||||
}
|
||||
});
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'dart:io';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:moodiary/persistence/pref.dart';
|
||||
import 'package:moodiary/utils/aes_util.dart';
|
||||
import 'package:moodiary/utils/cache_util.dart';
|
||||
import 'package:moodiary/utils/file_util.dart';
|
||||
import 'package:moodiary/utils/notice_util.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
@@ -75,4 +76,13 @@ class LaboratoryLogic extends GetxController {
|
||||
final decrypted = await AesUtil.decrypt(key: key, encryptedData: encrypted);
|
||||
return decrypted == 'Hello World';
|
||||
}
|
||||
|
||||
Future<bool> clearImageThumbnail() async {
|
||||
try {
|
||||
await ImageCacheUtil().clearImageCache();
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +96,18 @@ class LaboratoryPage extends StatelessWidget {
|
||||
},
|
||||
title: const Text('加密测试'),
|
||||
),
|
||||
const Gap(12),
|
||||
ListTile(
|
||||
onTap: () async {
|
||||
final res = await logic.clearImageThumbnail();
|
||||
if (res) {
|
||||
toast.success(message: '清理成功');
|
||||
} else {
|
||||
toast.error(message: '清理失败');
|
||||
}
|
||||
},
|
||||
title: const Text('清理图片缩略图缓存'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
||||
37
lib/persistence/hive.dart
Normal file
37
lib/persistence/hive.dart
Normal file
@@ -0,0 +1,37 @@
|
||||
import 'package:hive_ce/hive.dart';
|
||||
import 'package:hive_ce_flutter/adapters.dart';
|
||||
import 'package:moodiary/utils/file_util.dart';
|
||||
|
||||
class HiveUtil {
|
||||
HiveUtil._();
|
||||
|
||||
static final HiveUtil _instance = HiveUtil._();
|
||||
|
||||
factory HiveUtil() => _instance;
|
||||
|
||||
late LazyBox<bool> _imageCacheBox;
|
||||
|
||||
late LazyBox<double> _imageAspectBox;
|
||||
|
||||
LazyBox<bool> get imageCacheBox => _imageCacheBox;
|
||||
|
||||
LazyBox<double> get imageAspectBox => _imageAspectBox;
|
||||
|
||||
Future<void> init() async {
|
||||
Hive.init(
|
||||
FileUtil.getRealPath('hive', ''),
|
||||
backendPreference: HiveStorageBackendPreference.native,
|
||||
);
|
||||
_imageCacheBox = await Hive.openLazyBox<bool>('image_cache');
|
||||
_imageAspectBox = await Hive.openLazyBox<double>('image_aspect');
|
||||
}
|
||||
|
||||
Future<void> clear() async {
|
||||
await _imageCacheBox.clear();
|
||||
await _imageAspectBox.clear();
|
||||
}
|
||||
|
||||
Future<void> close() async {
|
||||
await Hive.close();
|
||||
}
|
||||
}
|
||||
@@ -213,7 +213,7 @@ class MoodiaryFadeInPageRoute<T> extends PageRoute<T>
|
||||
required this.builder,
|
||||
super.settings,
|
||||
super.requestFocus,
|
||||
this.maintainState = true,
|
||||
this.maintainState = false,
|
||||
super.fullscreenDialog,
|
||||
super.allowSnapshotting = true,
|
||||
super.barrierDismissible = false,
|
||||
|
||||
@@ -9,6 +9,7 @@ import '../frb_generated.dart';
|
||||
import 'constants.dart';
|
||||
|
||||
// These functions are ignored because they are not marked as `pub`: `calculate_target_dimensions`, `load_image`
|
||||
// These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `ResizeOptions`
|
||||
|
||||
Future<Uint8List> compress({
|
||||
required DynamicImage img,
|
||||
@@ -29,15 +30,23 @@ abstract class DynamicImage implements RustOpaqueInterface {}
|
||||
|
||||
// Rust type: RustOpaqueMoi<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<ImageCompress>>
|
||||
abstract class ImageCompress implements RustOpaqueInterface {
|
||||
static Future<Uint8List> contain({
|
||||
static Future<Uint8List> containWithOptions({
|
||||
required String filePath,
|
||||
CompressFormat? compressFormat,
|
||||
int? targetWidth,
|
||||
int? targetHeight,
|
||||
int? minWidth,
|
||||
int? minHeight,
|
||||
int? maxWidth,
|
||||
int? maxHeight,
|
||||
int? quality,
|
||||
}) => RustLib.instance.api.crateApiCompressImageCompressContain(
|
||||
}) => RustLib.instance.api.crateApiCompressImageCompressContainWithOptions(
|
||||
filePath: filePath,
|
||||
compressFormat: compressFormat,
|
||||
targetWidth: targetWidth,
|
||||
targetHeight: targetHeight,
|
||||
minWidth: minWidth,
|
||||
minHeight: minHeight,
|
||||
maxWidth: maxWidth,
|
||||
maxHeight: maxHeight,
|
||||
quality: quality,
|
||||
|
||||
@@ -16,7 +16,7 @@ import 'api/kmp.dart';
|
||||
import 'api/zip.dart';
|
||||
import 'frb_generated.dart';
|
||||
import 'frb_generated.io.dart'
|
||||
if (dart.library.js_interop) 'frb_generated.webated.dart';
|
||||
if (dart.library.js_interop) 'frb_generated.webated.dart';
|
||||
|
||||
/// Main entrypoint of the Rust API
|
||||
class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
|
||||
@@ -69,7 +69,7 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
|
||||
String get codegenVersion => '2.9.0';
|
||||
|
||||
@override
|
||||
int get rustContentHash => 1427840433;
|
||||
int get rustContentHash => 199828844;
|
||||
|
||||
static const kDefaultExternalLibraryLoaderConfig =
|
||||
ExternalLibraryLoaderConfig(
|
||||
@@ -103,9 +103,13 @@ abstract class RustLibApi extends BaseApi {
|
||||
required String ttfFilePath,
|
||||
});
|
||||
|
||||
Future<Uint8List> crateApiCompressImageCompressContain({
|
||||
Future<Uint8List> crateApiCompressImageCompressContainWithOptions({
|
||||
required String filePath,
|
||||
CompressFormat? compressFormat,
|
||||
int? targetWidth,
|
||||
int? targetHeight,
|
||||
int? minWidth,
|
||||
int? minHeight,
|
||||
int? maxWidth,
|
||||
int? maxHeight,
|
||||
int? quality,
|
||||
@@ -389,9 +393,13 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
);
|
||||
|
||||
@override
|
||||
Future<Uint8List> crateApiCompressImageCompressContain({
|
||||
Future<Uint8List> crateApiCompressImageCompressContainWithOptions({
|
||||
required String filePath,
|
||||
CompressFormat? compressFormat,
|
||||
int? targetWidth,
|
||||
int? targetHeight,
|
||||
int? minWidth,
|
||||
int? minHeight,
|
||||
int? maxWidth,
|
||||
int? maxHeight,
|
||||
int? quality,
|
||||
@@ -405,8 +413,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
compressFormat,
|
||||
serializer,
|
||||
);
|
||||
sse_encode_opt_box_autoadd_i_32(maxWidth, serializer);
|
||||
sse_encode_opt_box_autoadd_i_32(maxHeight, serializer);
|
||||
sse_encode_opt_box_autoadd_u_32(targetWidth, serializer);
|
||||
sse_encode_opt_box_autoadd_u_32(targetHeight, serializer);
|
||||
sse_encode_opt_box_autoadd_u_32(minWidth, serializer);
|
||||
sse_encode_opt_box_autoadd_u_32(minHeight, serializer);
|
||||
sse_encode_opt_box_autoadd_u_32(maxWidth, serializer);
|
||||
sse_encode_opt_box_autoadd_u_32(maxHeight, serializer);
|
||||
sse_encode_opt_box_autoadd_u_8(quality, serializer);
|
||||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
@@ -419,19 +431,33 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
decodeSuccessData: sse_decode_list_prim_u_8_strict,
|
||||
decodeErrorData: sse_decode_AnyhowException,
|
||||
),
|
||||
constMeta: kCrateApiCompressImageCompressContainConstMeta,
|
||||
argValues: [filePath, compressFormat, maxWidth, maxHeight, quality],
|
||||
constMeta: kCrateApiCompressImageCompressContainWithOptionsConstMeta,
|
||||
argValues: [
|
||||
filePath,
|
||||
compressFormat,
|
||||
targetWidth,
|
||||
targetHeight,
|
||||
minWidth,
|
||||
minHeight,
|
||||
maxWidth,
|
||||
maxHeight,
|
||||
quality,
|
||||
],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateApiCompressImageCompressContainConstMeta =>
|
||||
TaskConstMeta get kCrateApiCompressImageCompressContainWithOptionsConstMeta =>
|
||||
const TaskConstMeta(
|
||||
debugName: "ImageCompress_contain",
|
||||
debugName: "ImageCompress_contain_with_options",
|
||||
argNames: [
|
||||
"filePath",
|
||||
"compressFormat",
|
||||
"targetWidth",
|
||||
"targetHeight",
|
||||
"minWidth",
|
||||
"minHeight",
|
||||
"maxWidth",
|
||||
"maxHeight",
|
||||
"quality",
|
||||
@@ -994,7 +1020,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
}
|
||||
|
||||
@protected
|
||||
int dco_decode_box_autoadd_i_32(dynamic raw) {
|
||||
int dco_decode_box_autoadd_u_32(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
return raw as int;
|
||||
}
|
||||
@@ -1078,9 +1104,9 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
}
|
||||
|
||||
@protected
|
||||
int? dco_decode_opt_box_autoadd_i_32(dynamic raw) {
|
||||
int? dco_decode_opt_box_autoadd_u_32(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
return raw == null ? null : dco_decode_box_autoadd_i_32(raw);
|
||||
return raw == null ? null : dco_decode_box_autoadd_u_32(raw);
|
||||
}
|
||||
|
||||
@protected
|
||||
@@ -1337,9 +1363,9 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
}
|
||||
|
||||
@protected
|
||||
int sse_decode_box_autoadd_i_32(SseDeserializer deserializer) {
|
||||
int sse_decode_box_autoadd_u_32(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
return (sse_decode_i_32(deserializer));
|
||||
return (sse_decode_u_32(deserializer));
|
||||
}
|
||||
|
||||
@protected
|
||||
@@ -1465,11 +1491,11 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
}
|
||||
|
||||
@protected
|
||||
int? sse_decode_opt_box_autoadd_i_32(SseDeserializer deserializer) {
|
||||
int? sse_decode_opt_box_autoadd_u_32(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
|
||||
if (sse_decode_bool(deserializer)) {
|
||||
return (sse_decode_box_autoadd_i_32(deserializer));
|
||||
return (sse_decode_box_autoadd_u_32(deserializer));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@@ -1757,9 +1783,9 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_i_32(int self, SseSerializer serializer) {
|
||||
void sse_encode_box_autoadd_u_32(int self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_i_32(self, serializer);
|
||||
sse_encode_u_32(self, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
@@ -1887,12 +1913,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_box_autoadd_i_32(int? self, SseSerializer serializer) {
|
||||
void sse_encode_opt_box_autoadd_u_32(int? self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
|
||||
sse_encode_bool(self != null, serializer);
|
||||
if (self != null) {
|
||||
sse_encode_box_autoadd_i_32(self, serializer);
|
||||
sse_encode_box_autoadd_u_32(self, serializer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
CompressFormat dco_decode_box_autoadd_compress_format(dynamic raw);
|
||||
|
||||
@protected
|
||||
int dco_decode_box_autoadd_i_32(dynamic raw);
|
||||
int dco_decode_box_autoadd_u_32(dynamic raw);
|
||||
|
||||
@protected
|
||||
int dco_decode_box_autoadd_u_8(dynamic raw);
|
||||
@@ -185,7 +185,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
CompressFormat? dco_decode_opt_box_autoadd_compress_format(dynamic raw);
|
||||
|
||||
@protected
|
||||
int? dco_decode_opt_box_autoadd_i_32(dynamic raw);
|
||||
int? dco_decode_opt_box_autoadd_u_32(dynamic raw);
|
||||
|
||||
@protected
|
||||
int? dco_decode_opt_box_autoadd_u_8(dynamic raw);
|
||||
@@ -311,7 +311,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
);
|
||||
|
||||
@protected
|
||||
int sse_decode_box_autoadd_i_32(SseDeserializer deserializer);
|
||||
int sse_decode_box_autoadd_u_32(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
int sse_decode_box_autoadd_u_8(SseDeserializer deserializer);
|
||||
@@ -359,7 +359,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
);
|
||||
|
||||
@protected
|
||||
int? sse_decode_opt_box_autoadd_i_32(SseDeserializer deserializer);
|
||||
int? sse_decode_opt_box_autoadd_u_32(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
int? sse_decode_opt_box_autoadd_u_8(SseDeserializer deserializer);
|
||||
@@ -509,7 +509,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_i_32(int self, SseSerializer serializer);
|
||||
void sse_encode_box_autoadd_u_32(int self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_u_8(int self, SseSerializer serializer);
|
||||
@@ -569,7 +569,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_box_autoadd_i_32(int? self, SseSerializer serializer);
|
||||
void sse_encode_opt_box_autoadd_u_32(int? self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_box_autoadd_u_8(int? self, SseSerializer serializer);
|
||||
|
||||
@@ -145,7 +145,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
CompressFormat dco_decode_box_autoadd_compress_format(dynamic raw);
|
||||
|
||||
@protected
|
||||
int dco_decode_box_autoadd_i_32(dynamic raw);
|
||||
int dco_decode_box_autoadd_u_32(dynamic raw);
|
||||
|
||||
@protected
|
||||
int dco_decode_box_autoadd_u_8(dynamic raw);
|
||||
@@ -187,7 +187,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
CompressFormat? dco_decode_opt_box_autoadd_compress_format(dynamic raw);
|
||||
|
||||
@protected
|
||||
int? dco_decode_opt_box_autoadd_i_32(dynamic raw);
|
||||
int? dco_decode_opt_box_autoadd_u_32(dynamic raw);
|
||||
|
||||
@protected
|
||||
int? dco_decode_opt_box_autoadd_u_8(dynamic raw);
|
||||
@@ -313,7 +313,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
);
|
||||
|
||||
@protected
|
||||
int sse_decode_box_autoadd_i_32(SseDeserializer deserializer);
|
||||
int sse_decode_box_autoadd_u_32(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
int sse_decode_box_autoadd_u_8(SseDeserializer deserializer);
|
||||
@@ -361,7 +361,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
);
|
||||
|
||||
@protected
|
||||
int? sse_decode_opt_box_autoadd_i_32(SseDeserializer deserializer);
|
||||
int? sse_decode_opt_box_autoadd_u_32(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
int? sse_decode_opt_box_autoadd_u_8(SseDeserializer deserializer);
|
||||
@@ -511,7 +511,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_i_32(int self, SseSerializer serializer);
|
||||
void sse_encode_box_autoadd_u_32(int self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_u_8(int self, SseSerializer serializer);
|
||||
@@ -571,7 +571,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_box_autoadd_i_32(int? self, SseSerializer serializer);
|
||||
void sse_encode_opt_box_autoadd_u_32(int? self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_box_autoadd_u_8(int? self, SseSerializer serializer);
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:moodiary/persistence/hive.dart';
|
||||
import 'package:moodiary/persistence/pref.dart';
|
||||
import 'package:moodiary/utils/file_util.dart';
|
||||
import 'package:moodiary/utils/log_util.dart';
|
||||
import 'package:moodiary/utils/media_util.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
||||
class CacheUtil {
|
||||
static Future<List<String>?> getCacheList(
|
||||
@@ -39,3 +47,97 @@ class CacheUtil {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ImageCacheUtil {
|
||||
ImageCacheUtil._();
|
||||
|
||||
static final ImageCacheUtil _instance = ImageCacheUtil._();
|
||||
|
||||
factory ImageCacheUtil() => _instance;
|
||||
|
||||
late final _imageCacheBox = HiveUtil().imageCacheBox;
|
||||
|
||||
late final _imageAspectBox = HiveUtil().imageAspectBox;
|
||||
|
||||
Future<void> close() async {
|
||||
await _imageCacheBox.close();
|
||||
}
|
||||
|
||||
Future<void> clearImageCache() async {
|
||||
await _imageCacheBox.clear();
|
||||
await _imageAspectBox.clear();
|
||||
await FileUtil.deleteDir(FileUtil.getRealPath('image_thumbnail', ''));
|
||||
}
|
||||
|
||||
Future<String> getLocalImagePathWithCache({
|
||||
required String imagePath,
|
||||
required int imageWidth,
|
||||
required int imageHeight,
|
||||
required double imageAspectRatio,
|
||||
}) async {
|
||||
final int minSize = min(imageWidth, imageHeight);
|
||||
final int rangeStart = (minSize ~/ 100) * 100;
|
||||
final int rangeEnd = rangeStart + 100;
|
||||
final int baseMinSize = ((rangeStart + rangeEnd) / 2).round();
|
||||
|
||||
final bool isWidthMin = imageWidth < imageHeight;
|
||||
final int standardWidth =
|
||||
isWidthMin ? baseMinSize : (baseMinSize * imageAspectRatio).round();
|
||||
final int standardHeight =
|
||||
isWidthMin ? (baseMinSize / imageAspectRatio).round() : baseMinSize;
|
||||
|
||||
final cachedImageName =
|
||||
'resized_w${standardWidth}_h${standardHeight}_${basename(imagePath)}';
|
||||
final cachedImagePath = FileUtil.getRealPath(
|
||||
'image_thumbnail',
|
||||
cachedImageName,
|
||||
);
|
||||
final cachedImageFile = File(cachedImagePath);
|
||||
|
||||
final bool isCached =
|
||||
(await _imageCacheBox.get(cachedImageName)) == true &&
|
||||
await cachedImageFile.exists();
|
||||
|
||||
if (isCached) {
|
||||
//logger.i('Image cache hit at $cachedImageName');
|
||||
return cachedImagePath;
|
||||
}
|
||||
|
||||
try {
|
||||
final compressedImage = await MediaUtil.compressImageData(
|
||||
imagePath: imagePath,
|
||||
size: baseMinSize,
|
||||
imageAspectRatio: imageAspectRatio,
|
||||
);
|
||||
|
||||
if (compressedImage != null) {
|
||||
final newFile = await cachedImageFile.create(recursive: true);
|
||||
await newFile.writeAsBytes(compressedImage);
|
||||
await _imageCacheBox.put(cachedImageName, true);
|
||||
//logger.i('Image cached at $cachedImageName');
|
||||
return cachedImagePath;
|
||||
}
|
||||
} catch (e) {
|
||||
logger.d('Error compressing image: $e');
|
||||
}
|
||||
|
||||
return imagePath;
|
||||
}
|
||||
|
||||
Future<double> getImageAspectRatioWithCache({
|
||||
required String imagePath,
|
||||
}) async {
|
||||
final cachedAspectRatio = await (_imageAspectBox.get(basename(imagePath)));
|
||||
if (cachedAspectRatio != null) return cachedAspectRatio;
|
||||
try {
|
||||
final aspectRatio = await MediaUtil.getImageAspectRatio(
|
||||
FileImage(File(imagePath)),
|
||||
);
|
||||
await _imageAspectBox.put(basename(imagePath), aspectRatio);
|
||||
return aspectRatio;
|
||||
} catch (e) {
|
||||
logger.d('Error getting image aspect ratio: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,11 +19,11 @@ class LogUtil {
|
||||
);
|
||||
|
||||
void e(message, {required Object error, StackTrace? stackTrace}) {
|
||||
_logger.e(message, error: error, stackTrace: stackTrace);
|
||||
if (kDebugMode) _logger.e(message, error: error, stackTrace: stackTrace);
|
||||
}
|
||||
|
||||
void f(message, {required Object error, StackTrace? stackTrace}) {
|
||||
_logger.f(message, error: error, stackTrace: stackTrace);
|
||||
if (kDebugMode) _logger.f(message, error: error, stackTrace: stackTrace);
|
||||
}
|
||||
|
||||
void i(message) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:fc_native_video_thumbnail/fc_native_video_thumbnail.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -13,6 +14,7 @@ import 'package:moodiary/common/values/media_type.dart';
|
||||
import 'package:moodiary/persistence/pref.dart';
|
||||
import 'package:moodiary/src/rust/api/compress.dart';
|
||||
import 'package:moodiary/src/rust/api/constants.dart' as r_type;
|
||||
import 'package:moodiary/utils/cache_util.dart';
|
||||
import 'package:moodiary/utils/file_util.dart';
|
||||
import 'package:moodiary/utils/log_util.dart';
|
||||
import 'package:moodiary/utils/lru.dart';
|
||||
@@ -20,6 +22,34 @@ import 'package:moodiary/utils/notice_util.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
enum ImageFormat {
|
||||
jpeg(extension: '.jpg'),
|
||||
png(extension: '.png'),
|
||||
heic(extension: '.heic'),
|
||||
webp(extension: '.webp');
|
||||
|
||||
final String extension;
|
||||
|
||||
const ImageFormat({required this.extension});
|
||||
|
||||
static ImageFormat getImageFormat(String imagePath) {
|
||||
final mimeType = lookupMimeType(imagePath);
|
||||
if (mimeType == null) return ImageFormat.png;
|
||||
switch (mimeType) {
|
||||
case 'image/jpeg':
|
||||
return ImageFormat.jpeg;
|
||||
case 'image/png':
|
||||
return ImageFormat.png;
|
||||
case 'image/heic':
|
||||
return ImageFormat.heic;
|
||||
case 'image/webp':
|
||||
return ImageFormat.webp;
|
||||
default:
|
||||
return ImageFormat.png;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MediaUtil {
|
||||
static final _picker = ImagePicker();
|
||||
|
||||
@@ -37,14 +67,6 @@ class MediaUtil {
|
||||
}
|
||||
}
|
||||
|
||||
// 定义 MIME 类型到扩展名和压缩格式的映射
|
||||
static final _compressConfig = {
|
||||
'image/jpeg': ['.jpg', r_type.CompressFormat.jpeg],
|
||||
'image/png': ['.png', r_type.CompressFormat.png],
|
||||
'image/heic': ['.heic', CompressFormat.heic],
|
||||
'image/webp': ['.webp', r_type.CompressFormat.webP],
|
||||
};
|
||||
|
||||
/// 保存图片
|
||||
/// 返回值:
|
||||
/// key:XFile 文件的临时目录
|
||||
@@ -59,15 +81,10 @@ class MediaUtil {
|
||||
imageNameMap[imageFile.path] = basename(imageFile.path);
|
||||
return;
|
||||
}
|
||||
final mimeType =
|
||||
lookupMimeType(imageFile.path) ?? 'image/png'; // 默认使用 PNG
|
||||
final config =
|
||||
_compressConfig[mimeType] ?? ['.png', r_type.CompressFormat.png];
|
||||
final extension = config[0] as String;
|
||||
final format = config[1];
|
||||
final imageName = 'image-${const Uuid().v7()}$extension';
|
||||
final imageFormat = ImageFormat.getImageFormat(imageFile.path);
|
||||
final imageName = 'image-${const Uuid().v7()}${imageFormat.extension}';
|
||||
final outputPath = FileUtil.getRealPath('image', imageName);
|
||||
await _compressImage(imageFile, outputPath, format);
|
||||
await compressAndSaveImage(imageFile, outputPath, imageFormat);
|
||||
imageNameMap[imageFile.path] = imageName;
|
||||
}),
|
||||
);
|
||||
@@ -114,19 +131,8 @@ class MediaUtil {
|
||||
// 保存视频文件
|
||||
await videoFile.saveTo(FileUtil.getRealPath('video', videoName));
|
||||
// 获取缩略图
|
||||
final tempThumbnailPath = FileUtil.getCachePath(
|
||||
'${const Uuid().v7()}.jpeg',
|
||||
);
|
||||
final tempThumbnailPath = FileUtil.getRealPath('thumbnail', videoName);
|
||||
await _getVideoThumbnail(videoFile, tempThumbnailPath);
|
||||
// 压缩缩略图并保存
|
||||
final compressedPath = FileUtil.getRealPath('thumbnail', videoName);
|
||||
await _compressRust(
|
||||
XFile(tempThumbnailPath),
|
||||
compressedPath,
|
||||
r_type.CompressFormat.jpeg,
|
||||
);
|
||||
// 清理临时文件
|
||||
await File(tempThumbnailPath).delete();
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -154,23 +160,8 @@ class MediaUtil {
|
||||
logger.d("Thumbnail missing for $videoName. Regenerating...");
|
||||
|
||||
try {
|
||||
// 生成临时缩略图路径
|
||||
final tempThumbnailPath = FileUtil.getCachePath(
|
||||
'${const Uuid().v7()}.jpeg',
|
||||
);
|
||||
|
||||
// 获取视频缩略图
|
||||
await _getVideoThumbnail(XFile(videoFile.path), tempThumbnailPath);
|
||||
|
||||
// 压缩并保存缩略图
|
||||
await _compressRust(
|
||||
XFile(tempThumbnailPath),
|
||||
thumbnailPath,
|
||||
r_type.CompressFormat.jpeg,
|
||||
);
|
||||
|
||||
// 删除临时文件
|
||||
await File(tempThumbnailPath).delete();
|
||||
await _getVideoThumbnail(XFile(videoFile.path), thumbnailPath);
|
||||
|
||||
logger.d("Thumbnail regenerated for $videoName.");
|
||||
} catch (e) {
|
||||
@@ -209,13 +200,24 @@ class MediaUtil {
|
||||
|
||||
final completer = Completer<double>();
|
||||
final imageStream = imageProvider.resolve(const ImageConfiguration());
|
||||
|
||||
imageStream.addListener(
|
||||
ImageStreamListener((ImageInfo info, bool _) async {
|
||||
final aspectRatio = info.image.width / info.image.height;
|
||||
await _imageAspectRatioCache.put(key, aspectRatio);
|
||||
completer.complete(aspectRatio);
|
||||
}),
|
||||
ImageStreamListener(
|
||||
(ImageInfo info, bool _) async {
|
||||
final aspectRatio = info.image.width / info.image.height;
|
||||
await _imageAspectRatioCache.put(key, aspectRatio);
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete(aspectRatio);
|
||||
}
|
||||
},
|
||||
onError: (Object error, StackTrace? stackTrace) {
|
||||
if (!completer.isCompleted) {
|
||||
completer.completeError(error, stackTrace);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
@@ -256,72 +258,144 @@ class MediaUtil {
|
||||
}
|
||||
|
||||
// 通用压缩逻辑
|
||||
static Future<void> _compressImage(
|
||||
static Future<Uint8List?> compressImageData({
|
||||
required String imagePath,
|
||||
ImageFormat? imageFormat,
|
||||
int? size,
|
||||
double? imageAspectRatio,
|
||||
}) async {
|
||||
final imageFormat_ = imageFormat ?? ImageFormat.getImageFormat(imagePath);
|
||||
return await switch (imageFormat_) {
|
||||
ImageFormat.jpeg => _compressRust(
|
||||
imagePath,
|
||||
r_type.CompressFormat.jpeg,
|
||||
size: size,
|
||||
imageAspectRatio: imageAspectRatio,
|
||||
),
|
||||
ImageFormat.png => _compressRust(
|
||||
imagePath,
|
||||
r_type.CompressFormat.png,
|
||||
size: size,
|
||||
imageAspectRatio: imageAspectRatio,
|
||||
),
|
||||
ImageFormat.heic => _compressNative(
|
||||
imagePath,
|
||||
CompressFormat.heic,
|
||||
size: size,
|
||||
imageAspectRatio: imageAspectRatio,
|
||||
),
|
||||
ImageFormat.webp => _compressRust(
|
||||
imagePath,
|
||||
r_type.CompressFormat.webP,
|
||||
size: size,
|
||||
imageAspectRatio: imageAspectRatio,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/// 压缩图片并保存到指定路径
|
||||
static Future<void> compressAndSaveImage(
|
||||
XFile imageFile,
|
||||
String outputPath,
|
||||
dynamic format,
|
||||
ImageFormat imageFormat,
|
||||
) async {
|
||||
// 如果选择了原图,则直接复制文件
|
||||
if (PrefUtil.getValue<int>('quality') == 3) {
|
||||
await imageFile.saveTo(outputPath);
|
||||
return;
|
||||
}
|
||||
if (format == CompressFormat.heic) {
|
||||
await _compressNative(imageFile, outputPath, format);
|
||||
final newImage = await compressImageData(
|
||||
imagePath: imageFile.path,
|
||||
imageFormat: imageFormat,
|
||||
);
|
||||
if (newImage != null) {
|
||||
await File(outputPath).writeAsBytes(newImage);
|
||||
} else {
|
||||
await _compressRust(imageFile, outputPath, format);
|
||||
await imageFile.saveTo(outputPath);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> _compressRust(
|
||||
XFile oldImage,
|
||||
String targetPath,
|
||||
r_type.CompressFormat format,
|
||||
) async {
|
||||
final quality = switch (PrefUtil.getValue<int>('quality')) {
|
||||
0 => 720,
|
||||
1 => 1080,
|
||||
2 => 1440,
|
||||
_ => 1080,
|
||||
};
|
||||
final oldPath = oldImage.path;
|
||||
final newImage = await ImageCompress.contain(
|
||||
filePath: oldPath,
|
||||
maxWidth: quality,
|
||||
maxHeight: quality,
|
||||
compressFormat: format,
|
||||
);
|
||||
await File(targetPath).writeAsBytes(newImage);
|
||||
static Future<Uint8List?> _compressRust(
|
||||
String imagePath,
|
||||
r_type.CompressFormat format, {
|
||||
int? size,
|
||||
double? imageAspectRatio,
|
||||
}) async {
|
||||
final imageSize =
|
||||
size ??
|
||||
switch (PrefUtil.getValue<int>('quality')) {
|
||||
0 => 720,
|
||||
1 => 1080,
|
||||
2 => 1440,
|
||||
_ => 1080,
|
||||
};
|
||||
final oldPath = imagePath;
|
||||
Uint8List? newImage;
|
||||
try {
|
||||
final imageAspect =
|
||||
imageAspectRatio ??
|
||||
await ImageCacheUtil().getImageAspectRatioWithCache(
|
||||
imagePath: imagePath,
|
||||
);
|
||||
|
||||
/// 计算新的宽高
|
||||
/// 对于横图,高度为 size,宽度按比例缩放
|
||||
/// 对于竖图,宽度为 size,高度按比例缩放
|
||||
final width =
|
||||
imageAspect < 1.0 ? imageSize : (imageSize * imageAspect).ceil();
|
||||
final height =
|
||||
imageAspect >= 1.0 ? imageSize : (imageSize / imageAspect).ceil();
|
||||
newImage = await ImageCompress.containWithOptions(
|
||||
filePath: oldPath,
|
||||
targetHeight: height,
|
||||
targetWidth: width,
|
||||
compressFormat: format,
|
||||
);
|
||||
} catch (e) {
|
||||
logger.d('Image compression failed: $e');
|
||||
newImage = null;
|
||||
}
|
||||
return newImage;
|
||||
}
|
||||
|
||||
//图片压缩
|
||||
static Future<void> _compressNative(
|
||||
XFile oldImage,
|
||||
String targetPath,
|
||||
CompressFormat format,
|
||||
) async {
|
||||
static Future<Uint8List?> _compressNative(
|
||||
String imagePath,
|
||||
CompressFormat format, {
|
||||
int? size,
|
||||
double? imageAspectRatio,
|
||||
}) async {
|
||||
if (Platform.isWindows) {
|
||||
oldImage.saveTo(targetPath);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
final quality = PrefUtil.getValue<int>('quality');
|
||||
final height = switch (quality) {
|
||||
0 => 720,
|
||||
1 => 1080,
|
||||
2 => 1440,
|
||||
_ => 1080,
|
||||
};
|
||||
final imageSize =
|
||||
size ??
|
||||
switch (quality) {
|
||||
0 => 720,
|
||||
1 => 1080,
|
||||
2 => 1440,
|
||||
_ => 1080,
|
||||
};
|
||||
final imageAspect =
|
||||
imageAspectRatio ??
|
||||
await ImageCacheUtil().getImageAspectRatioWithCache(
|
||||
imagePath: imagePath,
|
||||
);
|
||||
|
||||
/// 计算新的宽高
|
||||
/// 对于横图,宽度为 size,高度按比例缩放
|
||||
/// 对于竖图,高度为 size,宽度按比例缩放
|
||||
final width =
|
||||
imageAspect < 1.0 ? imageSize : (imageSize * imageAspect).ceil();
|
||||
final height =
|
||||
imageAspect >= 1.0 ? imageSize : (imageSize / imageAspect).ceil();
|
||||
final newImage = await FlutterImageCompress.compressWithFile(
|
||||
oldImage.path,
|
||||
imagePath,
|
||||
minHeight: height,
|
||||
minWidth: height,
|
||||
minWidth: width,
|
||||
format: format,
|
||||
);
|
||||
if (newImage == null) {
|
||||
oldImage.saveTo(targetPath);
|
||||
return;
|
||||
}
|
||||
await File(targetPath).writeAsBytes(newImage);
|
||||
return newImage;
|
||||
}
|
||||
|
||||
//获取视频缩略图
|
||||
|
||||
@@ -33,7 +33,7 @@ class WebDavUtil {
|
||||
|
||||
factory WebDavUtil() => _instance;
|
||||
|
||||
Future<void> initWebDav() async {
|
||||
void initWebDav() {
|
||||
final webDavOption = options;
|
||||
if (webDavOption.isEmpty) {
|
||||
_client = null;
|
||||
@@ -104,7 +104,7 @@ class WebDavUtil {
|
||||
required String password,
|
||||
}) async {
|
||||
await PrefUtil.setValue('webDavOption', [baseUrl, username, password]);
|
||||
await initWebDav();
|
||||
initWebDav();
|
||||
}
|
||||
|
||||
Future<void> removeWebDavOption() async {
|
||||
|
||||
@@ -66,7 +66,6 @@ pub fn compress(
|
||||
}
|
||||
}
|
||||
|
||||
// 返回压缩结果
|
||||
Ok(result_buf.into_inner()?)
|
||||
}
|
||||
|
||||
@@ -74,11 +73,15 @@ pub fn compress(
|
||||
pub struct ImageCompress;
|
||||
|
||||
impl ImageCompress {
|
||||
pub fn contain(
|
||||
pub fn contain_with_options(
|
||||
file_path: String,
|
||||
compress_format: Option<CompressFormat>,
|
||||
max_width: Option<i32>,
|
||||
max_height: Option<i32>,
|
||||
target_width: Option<u32>,
|
||||
target_height: Option<u32>,
|
||||
min_width: Option<u32>,
|
||||
min_height: Option<u32>,
|
||||
max_width: Option<u32>,
|
||||
max_height: Option<u32>,
|
||||
quality: Option<u8>,
|
||||
) -> Result<Vec<u8>> {
|
||||
let src_img = Self::load_image(&file_path)?;
|
||||
@@ -89,41 +92,64 @@ impl ImageCompress {
|
||||
let (dst_width, dst_height) = Self::calculate_target_dimensions(
|
||||
img_width,
|
||||
img_height,
|
||||
max_width.unwrap_or(1024) as u32,
|
||||
max_height.unwrap_or(1024) as u32,
|
||||
&ResizeOptions {
|
||||
target_width,
|
||||
target_height,
|
||||
min_width,
|
||||
min_height,
|
||||
max_width,
|
||||
max_height,
|
||||
},
|
||||
);
|
||||
|
||||
compress(&src_img, dst_height, dst_width, compress_format, quality)
|
||||
}
|
||||
|
||||
fn calculate_target_dimensions(
|
||||
img_width: u32,
|
||||
img_height: u32,
|
||||
options: &ResizeOptions,
|
||||
) -> (u32, u32) {
|
||||
if let (Some(w), Some(h)) = (options.target_width, options.target_height) {
|
||||
return (w, h);
|
||||
}
|
||||
|
||||
let aspect_ratio = img_width as f64 / img_height as f64;
|
||||
|
||||
if let Some(min_w) = options.min_width {
|
||||
let ratio = min_w as f64 / img_width as f64;
|
||||
return (min_w, (img_height as f64 * ratio).round() as u32);
|
||||
}
|
||||
|
||||
if let Some(min_h) = options.min_height {
|
||||
let ratio = min_h as f64 / img_height as f64;
|
||||
return ((img_width as f64 * ratio).round() as u32, min_h);
|
||||
}
|
||||
|
||||
let max_width = options.max_width.unwrap_or(1024);
|
||||
let max_height = options.max_height.unwrap_or(1024);
|
||||
|
||||
if aspect_ratio > 1.0 {
|
||||
let ratio = max_height as f64 / img_height as f64;
|
||||
((img_width as f64 * ratio).round() as u32, max_height)
|
||||
} else {
|
||||
let ratio = max_width as f64 / img_width as f64;
|
||||
(max_width, (img_height as f64 * ratio).round() as u32)
|
||||
}
|
||||
}
|
||||
|
||||
fn load_image(file_path: &str) -> Result<DynamicImage> {
|
||||
ImageReader::open(file_path)?
|
||||
.with_guessed_format()?
|
||||
.decode()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to decode image: {}", e))
|
||||
}
|
||||
|
||||
fn calculate_target_dimensions(
|
||||
img_width: u32,
|
||||
img_height: u32,
|
||||
max_width: u32,
|
||||
max_height: u32,
|
||||
) -> (u32, u32) {
|
||||
// 确保浮点计算
|
||||
let aspect_ratio = img_width as f64 / img_height as f64;
|
||||
|
||||
if aspect_ratio > 1.0 {
|
||||
// 横图,根据 max_height 缩放
|
||||
let ratio = max_height as f64 / img_height as f64;
|
||||
let dst_width = (img_width as f64 * ratio).round() as u32;
|
||||
let dst_height = max_height;
|
||||
(dst_width, dst_height)
|
||||
} else {
|
||||
// 竖图,根据 max_width 缩放
|
||||
let ratio = max_width as f64 / img_width as f64;
|
||||
let dst_width = max_width;
|
||||
let dst_height = (img_height as f64 * ratio).round() as u32;
|
||||
(dst_width, dst_height)
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct ResizeOptions {
|
||||
pub target_width: Option<u32>,
|
||||
pub target_height: Option<u32>,
|
||||
pub min_width: Option<u32>,
|
||||
pub min_height: Option<u32>,
|
||||
pub max_width: Option<u32>,
|
||||
pub max_height: Option<u32>,
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ flutter_rust_bridge::frb_generated_boilerplate!(
|
||||
default_rust_auto_opaque = RustAutoOpaqueMoi,
|
||||
);
|
||||
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.9.0";
|
||||
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 1427840433;
|
||||
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 199828844;
|
||||
|
||||
// Section: executor
|
||||
|
||||
@@ -230,7 +230,7 @@ fn wire__crate__api__font__FontReader_get_wght_axis_from_vf_font_impl(
|
||||
},
|
||||
)
|
||||
}
|
||||
fn wire__crate__api__compress__ImageCompress_contain_impl(
|
||||
fn wire__crate__api__compress__ImageCompress_contain_with_options_impl(
|
||||
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||
rust_vec_len_: i32,
|
||||
@@ -238,7 +238,7 @@ fn wire__crate__api__compress__ImageCompress_contain_impl(
|
||||
) {
|
||||
FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::<flutter_rust_bridge::for_generated::SseCodec, _, _>(
|
||||
flutter_rust_bridge::for_generated::TaskInfo {
|
||||
debug_name: "ImageCompress_contain",
|
||||
debug_name: "ImageCompress_contain_with_options",
|
||||
port: Some(port_),
|
||||
mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
|
||||
},
|
||||
@@ -255,16 +255,24 @@ fn wire__crate__api__compress__ImageCompress_contain_impl(
|
||||
let api_file_path = <String>::sse_decode(&mut deserializer);
|
||||
let api_compress_format =
|
||||
<Option<crate::api::constants::CompressFormat>>::sse_decode(&mut deserializer);
|
||||
let api_max_width = <Option<i32>>::sse_decode(&mut deserializer);
|
||||
let api_max_height = <Option<i32>>::sse_decode(&mut deserializer);
|
||||
let api_target_width = <Option<u32>>::sse_decode(&mut deserializer);
|
||||
let api_target_height = <Option<u32>>::sse_decode(&mut deserializer);
|
||||
let api_min_width = <Option<u32>>::sse_decode(&mut deserializer);
|
||||
let api_min_height = <Option<u32>>::sse_decode(&mut deserializer);
|
||||
let api_max_width = <Option<u32>>::sse_decode(&mut deserializer);
|
||||
let api_max_height = <Option<u32>>::sse_decode(&mut deserializer);
|
||||
let api_quality = <Option<u8>>::sse_decode(&mut deserializer);
|
||||
deserializer.end();
|
||||
move |context| {
|
||||
transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>(
|
||||
(move || {
|
||||
let output_ok = crate::api::compress::ImageCompress::contain(
|
||||
let output_ok = crate::api::compress::ImageCompress::contain_with_options(
|
||||
api_file_path,
|
||||
api_compress_format,
|
||||
api_target_width,
|
||||
api_target_height,
|
||||
api_min_width,
|
||||
api_min_height,
|
||||
api_max_width,
|
||||
api_max_height,
|
||||
api_quality,
|
||||
@@ -1013,11 +1021,11 @@ impl SseDecode for Option<crate::api::constants::CompressFormat> {
|
||||
}
|
||||
}
|
||||
|
||||
impl SseDecode for Option<i32> {
|
||||
impl SseDecode for Option<u32> {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||
if (<bool>::sse_decode(deserializer)) {
|
||||
return Some(<i32>::sse_decode(deserializer));
|
||||
return Some(<u32>::sse_decode(deserializer));
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
@@ -1112,7 +1120,7 @@ fn pde_ffi_dispatcher_primary_impl(
|
||||
rust_vec_len,
|
||||
data_len,
|
||||
),
|
||||
6 => wire__crate__api__compress__ImageCompress_contain_impl(
|
||||
6 => wire__crate__api__compress__ImageCompress_contain_with_options_impl(
|
||||
port,
|
||||
ptr,
|
||||
rust_vec_len,
|
||||
@@ -1493,12 +1501,12 @@ impl SseEncode for Option<crate::api::constants::CompressFormat> {
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for Option<i32> {
|
||||
impl SseEncode for Option<u32> {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
<bool>::sse_encode(self.is_some(), serializer);
|
||||
if let Some(value) = self {
|
||||
<i32>::sse_encode(value, serializer);
|
||||
<u32>::sse_encode(value, serializer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user