From 65f2a5e2643ea194b00d4bb9b1b844078a794b8b Mon Sep 17 00:00:00 2001 From: Bo Lopker Date: Sun, 18 Feb 2024 18:27:18 -0800 Subject: [PATCH] Add directory support, add in place file optimizations --- lib/compressor.dart | 22 +- lib/config.dart | 27 +- lib/dropper.dart | 62 +++- lib/main.dart | 76 +++-- lib/settings.dart | 296 +++++++++++++----- lib/slider.dart | 15 + lib/src/rust/api/simple.dart | 17 +- lib/src/rust/frb_generated.dart | 56 +++- lib/src/rust/frb_generated.io.dart | 9 + lib/src/rust/frb_generated.web.dart | 9 + macos/Runner.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- pubspec.lock | 118 ++++--- pubspec.yaml | 2 +- rust/src/api/simple.rs | 15 +- rust/src/frb_generated.rs | 27 +- rust_builder/cargokit/build_tool/pubspec.lock | 62 ++-- 17 files changed, 620 insertions(+), 197 deletions(-) create mode 100644 lib/slider.dart diff --git a/lib/compressor.dart b/lib/compressor.dart index f768e7c..1575cab 100644 --- a/lib/compressor.dart +++ b/lib/compressor.dart @@ -1,19 +1,29 @@ import 'dart:io'; -import 'package:flutter/foundation.dart'; import 'package:alic/imagefiles.dart'; import 'package:alic/src/rust/api/simple.dart'; import 'package:alic/workqueue.dart'; +import 'package:flutter/foundation.dart'; + +import './config.dart'; void compressor(ImageFile imageFile, void Function(ImageFile) callback) { // Compress the image file by adding it to the queue, then run the callback when done. + final config = Config.signal.value; final ext = imageFile.path.split('.').last; - final outPath = imageFile.path.replaceAll('.$ext', '.min.$ext'); + final outPath = imageFile.path.replaceAll('.$ext', '${config.postfix}.$ext'); workQueue.add(() async { callback(imageFile.copyWith(status: ImageFileStatus.compressing)); final timer = Stopwatch()..start(); - var result = await imgcompress(path: imageFile.path, outPath: outPath); + + var result = await imgcompress( + path: imageFile.path, + outPath: outPath, + jpegQuality: config.qualityJPEG, + pngQuality: config.qualityPNG, + gifQuality: config.qualityGIF, + webpQuality: config.qualityWEBP); timer.stop(); debugPrint( 'Compressed ${imageFile.file} in ${timer.elapsedMilliseconds}ms'); @@ -33,6 +43,12 @@ void compressor(ImageFile imageFile, void Function(ImageFile) callback) { )); return; } + // Success! + if (!config.enablePostfix) { + // If postfix is disabled, replace the original file with the optimized one + File(imageFile.path).delete(); + File(outPath).rename(imageFile.path); + } callback(imageFile.copyWith( sizeAfterOptimization: sizeAfterOptimization, status: ImageFileStatus.success, diff --git a/lib/config.dart b/lib/config.dart index ea3484d..ac59290 100644 --- a/lib/config.dart +++ b/lib/config.dart @@ -14,6 +14,8 @@ class ConfigData { final int qualityPNG; final int qualityWEBP; final int qualityGIF; + final bool enablePostfix; + final String postfix; const ConfigData({ required this.lossy, @@ -21,14 +23,25 @@ class ConfigData { required this.qualityPNG, required this.qualityWEBP, required this.qualityGIF, + required this.enablePostfix, + required this.postfix, }); + static validateQuality(dynamic quality, int defaultValue) { + if (quality is int) { + return quality.clamp(10, 100); + } + return defaultValue; + } + ConfigData.fromJson(Map json) : lossy = json['lossy'] ?? true, - qualityJPEG = json['qualityJPEG'] ?? 80, - qualityPNG = json['qualityPNG'] ?? 80, - qualityWEBP = json['qualityWEBP'] ?? 60, - qualityGIF = json['qualityGIF'] ?? 80; + qualityJPEG = validateQuality(json['qualityJPEG'], 80), + qualityPNG = validateQuality(json['qualityPNG'], 80), + qualityWEBP = validateQuality(json['qualityWEBP'], 60), + qualityGIF = validateQuality(json['qualityGIF'], 80), + enablePostfix = json['enablePostfix'] ?? true, + postfix = json['postfix'] ?? '.min'; Map toJson() => { 'lossy': lossy, @@ -44,6 +57,8 @@ class ConfigData { int? qualityPNG, int? qualityWEBP, int? qualityGIF, + bool? enablePostfix, + String? postfix, }) { return ConfigData( lossy: lossy ?? this.lossy, @@ -51,6 +66,8 @@ class ConfigData { qualityPNG: qualityPNG ?? this.qualityPNG, qualityWEBP: qualityWEBP ?? this.qualityWEBP, qualityGIF: qualityGIF ?? this.qualityGIF, + enablePostfix: enablePostfix ?? this.enablePostfix, + postfix: postfix ?? this.postfix, ); } } @@ -65,6 +82,7 @@ class Config { static void init() { signal.value = readConfig(); signals.effect(() { + print('Config changed: ${signal.value}'); writeConfig(signal.value); }); } @@ -72,6 +90,7 @@ class Config { static ConfigData readConfig() { ensureConfigExists(); var configData = configFile.readAsStringSync(); + print('Read config: $configData'); return ConfigData.fromJson(jsonDecode(configData)); } diff --git a/lib/dropper.dart b/lib/dropper.dart index f2827d4..0ef2910 100644 --- a/lib/dropper.dart +++ b/lib/dropper.dart @@ -1,3 +1,6 @@ +import 'dart:async'; +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:super_clipboard/super_clipboard.dart'; import 'package:super_drag_and_drop/super_drag_and_drop.dart'; @@ -68,16 +71,21 @@ class _MyDropRegionState extends State { onPerformDrop: (event) async { debugPrint('onPerformDrop'); final items = event.session.items; + final mixedPaths = []; for (var item in items) { for (var format in formats) { if (item.canProvide(format)) { - final reader = item.dataReader!; - reader.getValue(Formats.fileUri, (value) { - widget.onDrop(value!.toFilePath()); - }); + var uri = await _getValueFromItem(item); + if (uri != null) { + mixedPaths.add(uri); + } } } } + final paths = await _resolvePaths(mixedPaths); + for (var path in paths) { + widget.onDrop(path); + } }, child: Stack(children: [ widget.child, @@ -104,3 +112,49 @@ class _MyDropRegionState extends State { ); } } + +Future _getValueFromItem(DropItem item) async { + final reader = item.dataReader!; + final completer = Completer(); + reader.getValue(Formats.fileUri, (value) { + if (value == null) { + completer.complete(null); + } else { + completer.complete(value.toFilePath()); + } + }); + return completer.future; +} + +Future> _resolvePaths(List paths) async { + List resolvedPaths = []; + for (var path in paths) { + if (path.endsWith('/')) { + resolvedPaths.addAll(await _getImagesFromDirectory(path) + .then((imageFiles) => imageFiles.map((e) => e.path).toList())); + } else { + resolvedPaths.add(path); + } + } + return resolvedPaths; +} + +// Return a list of images from a directory, recursively +Future> _getImagesFromDirectory(String path) async { + var dir = Directory(path); + + List imageFiles = []; + + await for (var entity in dir.list(recursive: true, followLinks: false)) { + if (entity is File && _isImage(entity)) { + imageFiles.add(entity); + } + } + + return imageFiles; +} + +bool _isImage(File file) { + var ext = file.path.split('.').last; + return ['jpg', 'jpeg', 'png', 'gif', 'webp'].contains(ext.toLowerCase()); +} diff --git a/lib/main.dart b/lib/main.dart index f7f6938..a39ad2a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'package:alic/dropper.dart'; import 'package:alic/imagefiles.dart'; +import 'package:alic/settings.dart'; import 'package:alic/src/rust/frb_generated.dart'; import 'package:alic/table.dart'; import 'package:file_selector/file_selector.dart'; @@ -7,13 +8,14 @@ import 'package:flutter/material.dart'; import 'package:signals/signals_flutter.dart'; import 'package:window_manager/window_manager.dart'; +import './config.dart'; import 'glass.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await RustLib.init(); await windowManager.ensureInitialized(); - // Config.init(); + Config.init(); WindowOptions windowOptions = const WindowOptions( minimumSize: Size(600, 400), @@ -39,15 +41,32 @@ class MyApp extends StatelessWidget { colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple) .copyWith(background: const Color(0xFF1B1B1B)), textTheme: const TextTheme( - bodyLarge: TextStyle(color: Colors.white), - bodyMedium: TextStyle(color: Colors.white), - bodySmall: TextStyle(color: Colors.white), - headlineLarge: TextStyle(color: Colors.white), - headlineMedium: TextStyle(color: Colors.white), - headlineSmall: TextStyle(color: Colors.white), - titleLarge: TextStyle(color: Colors.white), - titleMedium: TextStyle(color: Colors.white), - titleSmall: TextStyle(color: Colors.white), + bodyLarge: TextStyle(color: Colors.white70), + bodyMedium: TextStyle(color: Colors.white70), + bodySmall: TextStyle(color: Colors.white70), + headlineLarge: TextStyle(color: Colors.white70), + headlineMedium: TextStyle(color: Colors.white70), + headlineSmall: TextStyle(color: Colors.white70), + titleLarge: TextStyle(color: Colors.white70), + titleMedium: TextStyle(color: Colors.white70), + titleSmall: TextStyle(color: Colors.white70), + ), + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom( + foregroundColor: Colors.white70, + ), + ), + iconButtonTheme: IconButtonThemeData( + style: IconButton.styleFrom( + foregroundColor: Colors.white70, + )), + sliderTheme: const SliderThemeData( + activeTrackColor: Colors.white70, + inactiveTrackColor: Colors.white70, + thumbColor: Colors.white70, + overlayColor: Colors.white38, + thumbShape: RoundSliderThumbShape(enabledThumbRadius: 10), + overlayShape: RoundSliderOverlayShape(overlayRadius: 10), ), ), home: const HomePage(), @@ -175,31 +194,32 @@ class BottomBar extends StatelessWidget { ), Row( children: [ - // IconButton( - // constraints: - // const BoxConstraints.tightFor(width: 37, height: 37), - // onPressed: () { - // showDialog( - // context: context, - // builder: (context) { - // return const SettingsWidget(); - // }); - // }, - // icon: const Icon(Icons.settings), - // iconSize: 20, - // color: Colors.white70, - // padding: const EdgeInsets.all(0), - // ), + IconButton( + constraints: + const BoxConstraints.tightFor(width: 37, height: 37), + onPressed: () { + showDialog( + context: context, + builder: (context) { + return const SettingsWidget(); + }); + }, + icon: const Icon(Icons.settings), + iconSize: 20, + padding: const EdgeInsets.all(0), + ), Watch( (_) => TextButton.icon( + style: TextButton.styleFrom( + disabledIconColor: Colors.white30, + disabledForegroundColor: Colors.white30, + foregroundColor: Colors.white70, + ), onPressed: ImageFiles.signal.isEmpty ? null : () { ImageFiles.removeDone(); }, - style: TextButton.styleFrom( - foregroundColor: Colors.white70, - ), icon: const Icon( Icons.close, size: 20, diff --git a/lib/settings.dart b/lib/settings.dart index 5ed899a..cc1147b 100644 --- a/lib/settings.dart +++ b/lib/settings.dart @@ -1,84 +1,66 @@ -import 'package:flutter/material.dart'; import 'package:alic/config.dart'; +import 'package:flutter/material.dart'; import 'package:signals/signals_flutter.dart'; -class SettingsWidget extends StatelessWidget { +enum SettingsPages { + general('General'), + quality('Quality'); + // advanced('Advanced'); + + const SettingsPages(this.title); + + final String title; +} + +class SettingsWidget extends StatefulWidget { const SettingsWidget({super.key}); + @override + State createState() => _SettingsWidgetState(); +} + +class _SettingsWidgetState extends State { + SettingsPages _selectedPage = SettingsPages.general; + @override Widget build(BuildContext context) { return SimpleDialog( + shadowColor: Colors.black, backgroundColor: const Color(0xFF2D2D2D), - title: const Text('Settings'), children: [ Padding( - padding: const EdgeInsets.all(10), - child: Watch( - (context) { - final config = Config.signal.value; - return Column( - children: [ - Row( - children: [ - const Text('JPEG quality'), - const Spacer(), - SliderWidget( - value: config.qualityJPEG, - onChanged: (value) { - Config.signal.value = Config.signal.value - .copyWith(qualityJPEG: value); - }), - ], - ), - Row( - children: [ - const Text('PNG quality'), - const Spacer(), - SliderWidget( - value: config.qualityPNG, - onChanged: (value) { - Config.signal.value = - Config.signal.value.copyWith(qualityPNG: value); - }), - ], - ), - Row( - children: [ - const Text('GIF quality'), - const Spacer(), - SliderWidget( - value: config.qualityGIF, - onChanged: (value) { - Config.signal.value = - Config.signal.value.copyWith(qualityGIF: value); - }), - ], - ), - Row( - children: [ - const Text('WEBP quality'), - const Spacer(), - SliderWidget( - value: config.qualityWEBP, - onChanged: (value) { - Config.signal.value = Config.signal.value - .copyWith(qualityWEBP: value); - }), - ], - ), - ], - ); + padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 5), + child: SegmentedButton( + selectedIcon: Container(), + style: ButtonStyle( + foregroundColor: MaterialStateProperty.resolveWith((states) { + if (states.contains(MaterialState.selected)) { + return Colors.black; + } + return Colors.white70; + }), + ), + onSelectionChanged: (p0) { + final newPage = SettingsPages.values.firstWhere( + (element) => element.toString() == p0.first.toString()); + setState(() { + _selectedPage = newPage; + }); }, + segments: SettingsPages.values + .map((e) => + ButtonSegment(value: e.toString(), label: Text(e.title))) + .toList(), + selected: {_selectedPage.toString()}, ), ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 10), + child: _getSelectedPage(), + ), Row( children: [ const Spacer(), - TextButton( - onPressed: () { - Config.reset(); - }, - child: const Text('Reset')), TextButton( onPressed: () { Navigator.pop(context); @@ -90,6 +72,40 @@ class SettingsWidget extends StatelessWidget { ], ); } + + Widget _getSelectedPage() { + switch (_selectedPage) { + case SettingsPages.general: + return const GeneralPage(); + case SettingsPages.quality: + return const QualityPage(); + // case SettingsPages.advanced: + // return const AdvancedPage(); + } + } +} + +class QualitySliderWidget extends StatelessWidget { + const QualitySliderWidget( + {super.key, required this.value, required this.onChanged}); + final int value; + final void Function(int) onChanged; + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Slider( + value: value.toDouble(), + max: 100, + min: 10, + divisions: 10, + label: value.round().toString(), + onChanged: (double value) { + onChanged(value.round()); + }, + ), + ); + } } class SliderWidget extends StatelessWidget { @@ -98,15 +114,151 @@ class SliderWidget extends StatelessWidget { final void Function(int) onChanged; @override Widget build(BuildContext context) { - return Slider( - value: value.toDouble(), - max: 100, - min: 10, - divisions: 90, - label: value.round().toString(), - onChanged: (double value) { - onChanged(value.round()); + return Padding( + padding: const EdgeInsets.all(8.0), + child: Slider( + value: value.toDouble(), + max: 100, + min: 0, + divisions: 10, + label: value.round().toString(), + onChanged: (double value) { + onChanged(value.round()); + }, + ), + ); + } +} + +class GeneralPage extends StatelessWidget { + const GeneralPage({super.key}); + + @override + Widget build(BuildContext context) { + final postfixValue = Config.signal.value.postfix; + return Watch( + (context) { + final config = Config.signal.value; + return Column( + children: [ + Row( + children: [ + const Text('Add postfix to filename'), + const Spacer(), + Switch( + value: config.enablePostfix, + onChanged: (value) { + Config.signal.value = + Config.signal.value.copyWith(enablePostfix: value); + }, + ), + ], + ), + // Row( + // children: [ + // const Text('Postfix'), + // const Spacer(), + // SizedBox( + // width: 100, + // child: TextField( + // controller: TextEditingController( + // text: postfixValue == '.min' ? '' : postfixValue), + // onTapOutside: (value) { + // final newVal = value.isEmpty ? '.min' : value; + // Config.signal.value = + // Config.signal.value.copyWith(postfix: newVal); + // }, + // decoration: const InputDecoration( + // labelText: '.min', + // ), + // ), + // ) + // ], + // ), + ], + ); }, ); } } + +class QualityPage extends StatelessWidget { + const QualityPage({super.key}); + + @override + Widget build(BuildContext context) { + return Watch( + (context) { + final config = Config.signal.value; + return Column( + children: [ + Row( + children: [ + const Text('JPEG quality'), + const Spacer(), + QualitySliderWidget( + value: config.qualityJPEG, + onChanged: (value) { + Config.signal.value = config.copyWith(qualityJPEG: value); + }), + ], + ), + Row( + children: [ + const Text('PNG quality'), + const Spacer(), + QualitySliderWidget( + value: config.qualityPNG, + onChanged: (value) { + Config.signal.value = + Config.signal.value.copyWith(qualityPNG: value); + }), + ], + ), + Row( + children: [ + const Text('GIF quality'), + const Spacer(), + QualitySliderWidget( + value: config.qualityGIF, + onChanged: (value) { + Config.signal.value = + Config.signal.value.copyWith(qualityGIF: value); + }), + ], + ), + Row( + children: [ + const Text('WEBP quality'), + const Spacer(), + QualitySliderWidget( + value: config.qualityWEBP, + onChanged: (value) { + Config.signal.value = + Config.signal.value.copyWith(qualityWEBP: value); + }), + ], + ), + const SizedBox(height: 10), + TextButton( + onPressed: () { + Config.reset(); + }, + child: const Text('Reset')), + ], + ); + }, + ); + } +} + +class AdvancedPage extends StatelessWidget { + const AdvancedPage({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + child: const Text('Advanced'), + ); + } +} diff --git a/lib/slider.dart b/lib/slider.dart new file mode 100644 index 0000000..dd70b38 --- /dev/null +++ b/lib/slider.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class ASlider extends StatelessWidget { + const ASlider({super.key, required this.value, required this.onChanged}); + final double value; + final void Function(double) onChanged; + + @override + Widget build(BuildContext context) { + return Slider( + value: value, + onChanged: onChanged, + ); + } +} diff --git a/lib/src/rust/api/simple.dart b/lib/src/rust/api/simple.dart index a1947ad..5458f2b 100644 --- a/lib/src/rust/api/simple.dart +++ b/lib/src/rust/api/simple.dart @@ -7,5 +7,18 @@ import '../frb_generated.dart'; import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; Future imgcompress( - {required String path, required String outPath, dynamic hint}) => - RustLib.instance.api.imgcompress(path: path, outPath: outPath, hint: hint); + {required String path, + required String outPath, + required int jpegQuality, + required int pngQuality, + required int webpQuality, + required int gifQuality, + dynamic hint}) => + RustLib.instance.api.imgcompress( + path: path, + outPath: outPath, + jpegQuality: jpegQuality, + pngQuality: pngQuality, + webpQuality: webpQuality, + gifQuality: gifQuality, + hint: hint); diff --git a/lib/src/rust/frb_generated.dart b/lib/src/rust/frb_generated.dart index 46fb342..6ce474a 100644 --- a/lib/src/rust/frb_generated.dart +++ b/lib/src/rust/frb_generated.dart @@ -65,7 +65,13 @@ class RustLib extends BaseEntrypoint { abstract class RustLibApi extends BaseApi { Future imgcompress( - {required String path, required String outPath, dynamic hint}); + {required String path, + required String outPath, + required int jpegQuality, + required int pngQuality, + required int webpQuality, + required int gifQuality, + dynamic hint}); Future initApp({dynamic hint}); } @@ -80,12 +86,22 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { @override Future imgcompress( - {required String path, required String outPath, dynamic hint}) { + {required String path, + required String outPath, + required int jpegQuality, + required int pngQuality, + required int webpQuality, + required int gifQuality, + dynamic hint}) { return handler.executeNormal(NormalTask( callFfi: (port_) { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_String(path, serializer); sse_encode_String(outPath, serializer); + sse_encode_u_32(jpegQuality, serializer); + sse_encode_u_32(pngQuality, serializer); + sse_encode_u_32(webpQuality, serializer); + sse_encode_u_32(gifQuality, serializer); pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 1, port: port_); }, @@ -94,7 +110,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { decodeErrorData: null, ), constMeta: kImgcompressConstMeta, - argValues: [path, outPath], + argValues: [ + path, + outPath, + jpegQuality, + pngQuality, + webpQuality, + gifQuality + ], apiImpl: this, hint: hint, )); @@ -102,7 +125,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { TaskConstMeta get kImgcompressConstMeta => const TaskConstMeta( debugName: "imgcompress", - argNames: ["path", "outPath"], + argNames: [ + "path", + "outPath", + "jpegQuality", + "pngQuality", + "webpQuality", + "gifQuality" + ], ); @override @@ -141,6 +171,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return raw as Uint8List; } + @protected + int dco_decode_u_32(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as int; + } + @protected int dco_decode_u_8(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -167,6 +203,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return deserializer.buffer.getUint8List(len_); } + @protected + int sse_decode_u_32(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getUint32(); + } + @protected int sse_decode_u_8(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -204,6 +246,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { serializer.buffer.putUint8List(self); } + @protected + void sse_encode_u_32(int self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putUint32(self); + } + @protected void sse_encode_u_8(int self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs diff --git a/lib/src/rust/frb_generated.io.dart b/lib/src/rust/frb_generated.io.dart index 46ee855..c30b481 100644 --- a/lib/src/rust/frb_generated.io.dart +++ b/lib/src/rust/frb_generated.io.dart @@ -24,6 +24,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected Uint8List dco_decode_list_prim_u_8_strict(dynamic raw); + @protected + int dco_decode_u_32(dynamic raw); + @protected int dco_decode_u_8(dynamic raw); @@ -36,6 +39,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer); + @protected + int sse_decode_u_32(SseDeserializer deserializer); + @protected int sse_decode_u_8(SseDeserializer deserializer); @@ -55,6 +61,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { void sse_encode_list_prim_u_8_strict( Uint8List self, SseSerializer serializer); + @protected + void sse_encode_u_32(int self, SseSerializer serializer); + @protected void sse_encode_u_8(int self, SseSerializer serializer); diff --git a/lib/src/rust/frb_generated.web.dart b/lib/src/rust/frb_generated.web.dart index 0695b95..0cd514c 100644 --- a/lib/src/rust/frb_generated.web.dart +++ b/lib/src/rust/frb_generated.web.dart @@ -23,6 +23,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected Uint8List dco_decode_list_prim_u_8_strict(dynamic raw); + @protected + int dco_decode_u_32(dynamic raw); + @protected int dco_decode_u_8(dynamic raw); @@ -35,6 +38,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer); + @protected + int sse_decode_u_32(SseDeserializer deserializer); + @protected int sse_decode_u_8(SseDeserializer deserializer); @@ -54,6 +60,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { void sse_encode_list_prim_u_8_strict( Uint8List self, SseSerializer serializer); + @protected + void sse_encode_u_32(int self, SseSerializer serializer); + @protected void sse_encode_u_8(int self, SseSerializer serializer); diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index c8b5518..de9f75f 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -258,7 +258,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 331C80D4294CF70F00263BE5 = { diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 8994e74..0abd88b 100644 --- a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ =3.2.6 <4.0.0" + dart: ">=3.3.0-279.1.beta <4.0.0" flutter: ">=3.16.0" diff --git a/pubspec.yaml b/pubspec.yaml index 1f8fd30..d0f93a8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -26,7 +26,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^2.0.0 + flutter_lints: ^3.0.1 integration_test: sdk: flutter diff --git a/rust/src/api/simple.rs b/rust/src/api/simple.rs index c75eb71..d58df35 100644 --- a/rust/src/api/simple.rs +++ b/rust/src/api/simple.rs @@ -1,7 +1,18 @@ use caesium; -pub fn imgcompress(path: String, out_path: String) -> String { - let pars = caesium::initialize_parameters(); +pub fn imgcompress( + path: String, + out_path: String, + jpeg_quality: u32, + png_quality: u32, + webp_quality: u32, + gif_quality: u32, +) -> String { + let mut pars = caesium::initialize_parameters(); + pars.jpeg.quality = jpeg_quality; + pars.png.quality = png_quality; + pars.webp.quality = webp_quality; + pars.gif.quality = gif_quality; let result = caesium::compress( String::from(path.clone()), String::from(out_path.clone()), diff --git a/rust/src/frb_generated.rs b/rust/src/frb_generated.rs index 7276da2..e60d159 100644 --- a/rust/src/frb_generated.rs +++ b/rust/src/frb_generated.rs @@ -62,10 +62,21 @@ fn wire_imgcompress_impl( flutter_rust_bridge::for_generated::SseDeserializer::new(message); let api_path = ::sse_decode(&mut deserializer); let api_out_path = ::sse_decode(&mut deserializer); + let api_jpeg_quality = ::sse_decode(&mut deserializer); + let api_png_quality = ::sse_decode(&mut deserializer); + let api_webp_quality = ::sse_decode(&mut deserializer); + let api_gif_quality = ::sse_decode(&mut deserializer); deserializer.end(); move |context| { transform_result_sse((move || { - Result::<_, ()>::Ok(crate::api::simple::imgcompress(api_path, api_out_path)) + Result::<_, ()>::Ok(crate::api::simple::imgcompress( + api_path, + api_out_path, + api_jpeg_quality, + api_png_quality, + api_webp_quality, + api_gif_quality, + )) })()) } }, @@ -125,6 +136,13 @@ impl SseDecode for Vec { } } +impl SseDecode for u32 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_u32::().unwrap() + } +} + impl SseDecode for u8 { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -197,6 +215,13 @@ impl SseEncode for Vec { } } +impl SseEncode for u32 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer.cursor.write_u32::(self).unwrap(); + } +} + impl SseEncode for u8 { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { diff --git a/rust_builder/cargokit/build_tool/pubspec.lock b/rust_builder/cargokit/build_tool/pubspec.lock index 343bdd3..b3160bb 100644 --- a/rust_builder/cargokit/build_tool/pubspec.lock +++ b/rust_builder/cargokit/build_tool/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" url: "https://pub.dev" source: hosted - version: "64.0.0" + version: "67.0.0" adaptive_number: dependency: transitive description: @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: analyzer - sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.4.1" args: dependency: "direct main" description: @@ -69,10 +69,10 @@ packages: dependency: transitive description: name: coverage - sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097" + sha256: "8acabb8306b57a409bf4c83522065672ee13179297a6bb0cb9ead73948df7c76" url: "https://pub.dev" source: hosted - version: "1.6.3" + version: "1.7.2" crypto: dependency: "direct main" description: @@ -173,10 +173,10 @@ packages: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.7.1" json_annotation: dependency: transitive description: @@ -205,26 +205,26 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" meta: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.12.0" mime: dependency: transitive description: name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" node_preamble: dependency: transitive description: @@ -365,26 +365,26 @@ packages: dependency: "direct dev" description: name: test - sha256: "9b0dd8e36af4a5b1569029949d50a52cb2a2a2fdaa20cebb96e6603b9ae241f9" + sha256: "7ee446762c2c50b3bd4ea96fe13ffac69919352bd3b4b17bac3f3465edc58073" url: "https://pub.dev" source: hosted - version: "1.24.6" + version: "1.25.2" test_api: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.0" test_core: dependency: transitive description: name: test_core - sha256: "4bef837e56375537055fdbbbf6dd458b1859881f4c7e6da936158f77d61ab265" + sha256: "2bc4b4ecddd75309300d8096f781c0e3280ca1ef85beda558d33fcbedc2eead4" url: "https://pub.dev" source: hosted - version: "0.5.6" + version: "0.6.0" toml: dependency: "direct main" description: @@ -413,10 +413,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "0fae432c85c4ea880b33b497d32824b97795b04cdaa74d270219572a1f50268d" + sha256: a2662fb1f114f4296cf3f5a50786a2d888268d7776cf681aa17d660ffa23b246 url: "https://pub.dev" source: hosted - version: "11.9.0" + version: "14.0.0" watcher: dependency: transitive description: @@ -425,22 +425,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: "1d9158c616048c38f712a6646e317a3426da10e884447626167240d45209cbad" + url: "https://pub.dev" + source: hosted + version: "0.5.0" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: "1d8e795e2a8b3730c41b8a98a2dff2e0fb57ae6f0764a1c46ec5915387d257b2" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.4" webkit_inspection_protocol: dependency: transitive description: name: webkit_inspection_protocol - sha256: "67d3a8b6c79e1987d19d848b0892e582dbb0c66c57cc1fef58a177dd2aa2823d" + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" yaml: dependency: "direct main" description: @@ -450,4 +458,4 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.3.0 <4.0.0"