From 3f745cb8e4bcd11014b6ff2ae3a1713d26c87125 Mon Sep 17 00:00:00 2001 From: matthias sweertvaegher <121732-mx1up@users.noreply.gitlab.com> Date: Tue, 11 Nov 2025 18:02:51 +0100 Subject: [PATCH 1/9] re-add flutter_test; bump deps --- pubspec.lock | 104 ++++++++++++++++++++++++++++++++------------------- pubspec.yaml | 13 ++++--- 2 files changed, 73 insertions(+), 44 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 97f31e2..3b3e672 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: archive - sha256: "7dcbd0f87fe5f61cb28da39a1a8b70dbc106e2fe0516f7836eb7bb2948481a12" + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" url: "https://pub.dev" source: hosted - version: "4.0.5" + version: "4.0.7" args: dependency: transitive description: @@ -41,30 +41,30 @@ packages: url: "https://pub.dev" source: hosted version: "8.1.4" - characters: + boolean_selector: dependency: transitive description: - name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" url: "https://pub.dev" source: hosted - version: "1.3.0" - checked_yaml: + version: "2.1.1" + characters: dependency: transitive description: - name: checked_yaml - sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.dev" source: hosted - version: "2.0.3" - cli_util: + version: "1.3.0" + clock: dependency: transitive description: - name: cli_util - sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf url: "https://pub.dev" source: hosted - version: "0.4.1" + version: "1.1.1" collection: dependency: transitive description: @@ -97,6 +97,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.7" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" ffi: dependency: transitive description: @@ -190,15 +198,11 @@ packages: url: "https://pub.dev" source: hosted version: "8.1.6" - flutter_launcher_icons: + flutter_test: dependency: "direct dev" - description: - path: "." - ref: multi_res_ico - resolved-ref: "3a8a02159cb6d0d8d97bf25628408fa30dbbf356" - url: "https://github.com/mx1up/flutter_launcher_icons.git" - source: git - version: "0.14.3" + description: flutter + source: sdk + version: "0.0.0" flutter_web_plugins: dependency: transitive description: flutter @@ -237,14 +241,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.8" - json_annotation: + matcher: dependency: transitive description: - name: json_annotation - sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + name: matcher + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "4.9.0" + version: "0.12.16" material_color_utilities: dependency: transitive description: @@ -297,10 +301,10 @@ packages: dependency: "direct main" description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.8.3" path_provider_linux: dependency: transitive description: @@ -353,10 +357,10 @@ packages: dependency: transitive description: name: posix - sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a + sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" url: "https://pub.dev" source: hosted - version: "6.0.1" + version: "6.0.3" process_run: dependency: "direct main" description: @@ -369,10 +373,10 @@ packages: dependency: transitive description: name: provider - sha256: "489024f942069c2920c844ee18bb3d467c69e48955a4f32d1677f71be103e310" + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "6.1.5+1" pub_semver: dependency: transitive description: @@ -446,18 +450,34 @@ packages: dependency: transitive description: name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 url: "https://pub.dev" source: hosted - version: "1.10.1" + version: "1.11.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" + source: hosted + version: "2.1.1" string_scanner: dependency: transitive description: name: string_scanner - sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.2.0" synchronized: dependency: transitive description: @@ -470,10 +490,18 @@ packages: dependency: transitive description: name: term_glyph - sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "0.6.0" typed_data: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 5ef163f..0e174c2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,12 +30,13 @@ dependencies: shared_preferences: ^2.2.2 dev_dependencies: -# flutter_test: -# sdk: flutter - flutter_launcher_icons: - git: - url: https://github.com/mx1up/flutter_launcher_icons.git - ref: multi_res_ico + flutter_test: + sdk: flutter + # disable for now due to conflicting `path` dep with flutter_test +# flutter_launcher_icons: +# git: +# url: https://github.com/mx1up/flutter_launcher_icons.git +# ref: multi_res_ico flutter: uses-material-design: true From bc0d96feed6fe3d9e4b5989ae77f08f4e43456c8 Mon Sep 17 00:00:00 2001 From: matthias sweertvaegher <121732-mx1up@users.noreply.gitlab.com> Date: Tue, 11 Nov 2025 18:05:31 +0100 Subject: [PATCH 2/9] better solution to naming conflict --- lib/presentation/about_dialog.dart | 2 +- lib/presentation/dupes_top_bar.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/about_dialog.dart b/lib/presentation/about_dialog.dart index c9fb914..7c9fb4e 100644 --- a/lib/presentation/about_dialog.dart +++ b/lib/presentation/about_dialog.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:url_launcher/link.dart'; -Future showAboutDialoog( +Future showAppAboutDialog( BuildContext context, ) async { final appInfo = await PackageInfo.fromPlatform(); diff --git a/lib/presentation/dupes_top_bar.dart b/lib/presentation/dupes_top_bar.dart index efa589d..9bc2375 100644 --- a/lib/presentation/dupes_top_bar.dart +++ b/lib/presentation/dupes_top_bar.dart @@ -48,7 +48,7 @@ class DupesTopBar extends StatelessWidget { message: 'Find duplicates', child: ElevatedButton( child: Icon(Icons.info_outline), - onPressed: () => showAboutDialoog(context), + onPressed: () => showAppAboutDialog(context), ), ), ], From 5a74fc1bd6699d7969f7c1d2c2a3954995755e3d Mon Sep 17 00:00:00 2001 From: matthias sweertvaegher <121732-mx1up@users.noreply.gitlab.com> Date: Tue, 11 Nov 2025 18:09:47 +0100 Subject: [PATCH 3/9] fix about tooltip --- lib/presentation/dupes_top_bar.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/dupes_top_bar.dart b/lib/presentation/dupes_top_bar.dart index 9bc2375..8549dd8 100644 --- a/lib/presentation/dupes_top_bar.dart +++ b/lib/presentation/dupes_top_bar.dart @@ -45,7 +45,7 @@ class DupesTopBar extends StatelessWidget { child: BaseDirs(baseDirs: baseDirs), ), Tooltip( - message: 'Find duplicates', + message: 'About this app', child: ElevatedButton( child: Icon(Icons.info_outline), onPressed: () => showAppAboutDialog(context), From 23a784c5204dd0aa9b2ffcf60ac7ec0c321e9749 Mon Sep 17 00:00:00 2001 From: matthias sweertvaegher <121732-mx1up@users.noreply.gitlab.com> Date: Tue, 11 Nov 2025 19:22:49 +0100 Subject: [PATCH 4/9] support --cache and --noempty cli option; add pref screen --- lib/domain/fdupes_bloc.dart | 35 ++++++++++++--- lib/main.dart | 26 +++++++++-- lib/presentation/dupe_screen.dart | 36 ++++++++++++--- lib/presentation/dupes_top_bar.dart | 28 +++++++++--- lib/presentation/prefs_dialog.dart | 68 +++++++++++++++++++++++++++++ test/widget_test.dart | 7 ++- 6 files changed, 177 insertions(+), 23 deletions(-) create mode 100644 lib/presentation/prefs_dialog.dart diff --git a/lib/domain/fdupes_bloc.dart b/lib/domain/fdupes_bloc.dart index 2002b14..c33963d 100644 --- a/lib/domain/fdupes_bloc.dart +++ b/lib/domain/fdupes_bloc.dart @@ -14,8 +14,12 @@ part 'fdupes_state.dart'; class FdupesBloc extends Bloc { final List? initialDirs; String? fdupesLocation; + final SharedPreferences sharedPreferences; - FdupesBloc({this.initialDirs}) : super(FdupesStateInitial(initialDirs)) { + FdupesBloc({ + this.initialDirs, + required this.sharedPreferences, + }) : super(FdupesStateInitial(initialDirs)) { on(_onCheckFdupesAvailability); on(_onSelectFdupesLocation); on(_onDirsSelected); @@ -62,8 +66,8 @@ class FdupesBloc extends Bloc { return; } add(FdupesEventCheckFdupesAvailability()); - } - else emit(FdupesStateFdupesNotFound(statusMsg: 'Not a valid fdupes binary.')); + } else + emit(FdupesStateFdupesNotFound(statusMsg: 'Not a valid fdupes binary.')); } Future validFdupesLocation(String path) async { @@ -84,7 +88,14 @@ class FdupesBloc extends Bloc { if (s is FdupesStateResult) { emit(s.copyWith(loading: true)); } - final dupes = await findDupes(event.dirs, emit: emit); + final skipEmpty = sharedPreferences.getBool('noempty') ?? false; + final useCache = sharedPreferences.getBool('usecache') ?? false; + final dupes = await findDupes( + event.dirs, + emit: emit, + skipEmpty: skipEmpty, + useCache: useCache, + ); emit(FdupesStateResult(dirs: event.dirs, dupeGroups: dupes)); } @@ -156,10 +167,22 @@ class FdupesBloc extends Bloc { } } - Future>> findDupes(List dirs, {required Emitter emit}) async { + Future>> findDupes( + List dirs, { + required Emitter emit, + bool skipEmpty = false, + bool useCache = false, + }) async { print("finding dupes in dirs $dirs"); + final args = [ + '-r', + if (skipEmpty) '--noempty', + if (useCache) '--usecache', + ...dirs.map((d) => d.path), + ]; + print('cmd line: $fdupesLocation $args'); + Process process = await Process.start(fdupesLocation!, args); List> dupes = []; - Process process = await Process.start(fdupesLocation!, ['-r', ...dirs.map((d) => d.path)]); // stdout.addStream(process.stdout); final regex = RegExp(r'\[(\d+)/(\d+)\]'); final stderrBC = process.stderr.asBroadcastStream(); diff --git a/lib/main.dart b/lib/main.dart index d3953e3..570c1d0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,6 +7,7 @@ import 'package:fdupes_gui/theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:shared_preferences/shared_preferences.dart'; class MyBlocObserver extends BlocObserver { @override @@ -49,18 +50,31 @@ Future main(List args) async { Bloc.observer = MyBlocObserver(); - runApp(MyApp(initialDirs)); + WidgetsFlutterBinding.ensureInitialized(); + final sharedPreferences = await SharedPreferences.getInstance(); + + runApp(MyApp( + initialDirs, + sharedPreferences: sharedPreferences, + )); } class MyApp extends StatelessWidget { final List? initialDirs; + final SharedPreferences sharedPreferences; - MyApp(this.initialDirs); + MyApp( + this.initialDirs, { + required this.sharedPreferences, + }); @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => FdupesBloc(initialDirs: initialDirs), + create: (context) => FdupesBloc( + initialDirs: initialDirs, + sharedPreferences: sharedPreferences, + ), child: AdaptiveTheme( // debugShowFloatingThemeButton: true, light: FdupesTheme.light(), @@ -70,7 +84,11 @@ class MyApp extends StatelessWidget { title: 'Fdupes gui', theme: theme, darkTheme: darkTheme, - home: Material(child: DupeScreen()), + home: Material( + child: DupeScreen( + sharedPreferences: sharedPreferences, + ), + ), ), ), ); diff --git a/lib/presentation/dupe_screen.dart b/lib/presentation/dupe_screen.dart index 72dd069..7f79343 100644 --- a/lib/presentation/dupe_screen.dart +++ b/lib/presentation/dupe_screen.dart @@ -2,22 +2,43 @@ import 'package:fdupes_gui/core/util.dart' as util; import 'package:fdupes_gui/domain/fdupes_bloc.dart'; import 'package:fdupes_gui/presentation/dupes_body.dart'; import 'package:fdupes_gui/presentation/dupes_top_bar.dart'; +import 'package:fdupes_gui/presentation/prefs_dialog.dart'; import 'package:fdupes_gui/presentation/select_folder_dialog.dart'; import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:shared_preferences/shared_preferences.dart'; class DupeScreen extends StatelessWidget { + final SharedPreferences sharedPreferences; + + DupeScreen({super.key, required this.sharedPreferences}); + @override Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { if (state is FdupesStateInitial) { - return Center( - child: ElevatedButton( - child: Text('Select folder'), - onPressed: () => showSelectFolderDialog(context, initialDir: null, currentDirs: []), - ), + return Stack( + alignment: Alignment.center, + children: [ + ElevatedButton( + child: Text('Select folder'), + onPressed: () => showSelectFolderDialog(context, initialDir: null, currentDirs: []), + ), + Positioned( + right: 16, + top: 16, + child: Tooltip( + message: 'Preferences', + child: ElevatedButton( + child: Icon(Icons.settings), + style: ElevatedButton.styleFrom(shape: CircleBorder()), + onPressed: () => showPreferencesDialog(context, sharedPreferences), + ), + ), + ), + ], ); } if (state is FdupesStateFdupesNotFound) { @@ -72,7 +93,10 @@ class DupeScreen extends StatelessWidget { padding: EdgeInsets.all(8), child: Column( children: [ - DupesTopBar(baseDirs: state.dirs), + DupesTopBar( + baseDirs: state.dirs, + sharedPreferences: sharedPreferences, + ), SizedBox(height: 8), if (state.dupeGroups.isEmpty) Text('no dupes found') diff --git a/lib/presentation/dupes_top_bar.dart b/lib/presentation/dupes_top_bar.dart index 8549dd8..a284190 100644 --- a/lib/presentation/dupes_top_bar.dart +++ b/lib/presentation/dupes_top_bar.dart @@ -3,16 +3,20 @@ import 'dart:io'; import 'package:fdupes_gui/domain/fdupes_bloc.dart'; import 'package:fdupes_gui/presentation/about_dialog.dart'; import 'package:fdupes_gui/presentation/base_dirs.dart'; +import 'package:fdupes_gui/presentation/prefs_dialog.dart'; import 'package:fdupes_gui/presentation/select_folder_dialog.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:shared_preferences/shared_preferences.dart'; class DupesTopBar extends StatelessWidget { final List baseDirs; + final SharedPreferences sharedPreferences; DupesTopBar({ super.key, required this.baseDirs, + required this.sharedPreferences, }); @override @@ -44,12 +48,24 @@ class DupesTopBar extends StatelessWidget { Expanded( child: BaseDirs(baseDirs: baseDirs), ), - Tooltip( - message: 'About this app', - child: ElevatedButton( - child: Icon(Icons.info_outline), - onPressed: () => showAppAboutDialog(context), - ), + Column( + children: [ + Tooltip( + message: 'About this app', + child: ElevatedButton( + child: Icon(Icons.info_outline), + onPressed: () => showAppAboutDialog(context), + ), + ), + SizedBox(height: 8), + Tooltip( + message: 'Preferences', + child: ElevatedButton( + child: Icon(Icons.settings), + onPressed: () => showPreferencesDialog(context, sharedPreferences), + ), + ), + ], ), ], ); diff --git a/lib/presentation/prefs_dialog.dart b/lib/presentation/prefs_dialog.dart new file mode 100644 index 0000000..1fbb420 --- /dev/null +++ b/lib/presentation/prefs_dialog.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +Future showPreferencesDialog( + BuildContext context, + SharedPreferences sharedPreferences, +) async { + showDialog( + context: context, + barrierDismissible: true, + builder: (context) { + return PreferencesDialog(sharedPreferences); + }); +} + +class PreferencesDialog extends StatefulWidget { + final SharedPreferences sharedPreferences; + + PreferencesDialog(this.sharedPreferences); + + @override + State createState() => _PreferencesDialogState(); +} + +class _PreferencesDialogState extends State { + late bool skipEmpty; + late bool useCache; + + @override + void initState() { + super.initState(); + skipEmpty = widget.sharedPreferences.getBool('noempty') ?? false; + useCache = widget.sharedPreferences.getBool('usecache') ?? false; + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text('Preferences'), + content: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + SwitchListTile( + title: const Text('Skip empty files', softWrap: false), + value: skipEmpty, + onChanged: (value) { + setState(() { + skipEmpty = value; + widget.sharedPreferences.setBool('noempty', value); + }); + }, + ), + SwitchListTile( + title: const Text('Use cache'), + subtitle: const Text('fdupes 2.3.0+', softWrap: false), + value: widget.sharedPreferences.getBool('usecache') ?? false, + onChanged: (value) { + setState(() { + useCache = value; + widget.sharedPreferences.setBool('usecache', value); + }); + }), + ], + ), + ); + } +} diff --git a/test/widget_test.dart b/test/widget_test.dart index e936983..784aa11 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -11,11 +11,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:fdupes_gui/main.dart'; +import 'package:shared_preferences/shared_preferences.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { + final sharedPrefs = await SharedPreferences.getInstance(); // Build our app and trigger a frame. - await tester.pumpWidget(MyApp([Directory('')])); + await tester.pumpWidget(MyApp( + [Directory('')], + sharedPreferences: sharedPrefs, + )); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget); From e59aa748b2d6d4c52e030314216fedf2695b776f Mon Sep 17 00:00:00 2001 From: matthias sweertvaegher <121732-mx1up@users.noreply.gitlab.com> Date: Tue, 11 Nov 2025 19:25:05 +0100 Subject: [PATCH 5/9] formatting --- lib/presentation/prefs_dialog.dart | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/presentation/prefs_dialog.dart b/lib/presentation/prefs_dialog.dart index 1fbb420..fb34ee0 100644 --- a/lib/presentation/prefs_dialog.dart +++ b/lib/presentation/prefs_dialog.dart @@ -6,11 +6,10 @@ Future showPreferencesDialog( SharedPreferences sharedPreferences, ) async { showDialog( - context: context, - barrierDismissible: true, - builder: (context) { - return PreferencesDialog(sharedPreferences); - }); + context: context, + barrierDismissible: true, + builder: (context) => PreferencesDialog(sharedPreferences), + ); } class PreferencesDialog extends StatefulWidget { From 1984cf13de531a42cef4e35043def36f958fa376 Mon Sep 17 00:00:00 2001 From: matthias sweertvaegher <121732-mx1up@users.noreply.gitlab.com> Date: Tue, 11 Nov 2025 19:25:14 +0100 Subject: [PATCH 6/9] remove column stretch --- lib/presentation/prefs_dialog.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/presentation/prefs_dialog.dart b/lib/presentation/prefs_dialog.dart index fb34ee0..14b3d4e 100644 --- a/lib/presentation/prefs_dialog.dart +++ b/lib/presentation/prefs_dialog.dart @@ -37,7 +37,6 @@ class _PreferencesDialogState extends State { return AlertDialog( title: Text('Preferences'), content: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: [ SwitchListTile( From eb1bd85fb451aca0d2ee6da2a4dac298fbbd7bea Mon Sep 17 00:00:00 2001 From: matthias sweertvaegher <121732-mx1up@users.noreply.gitlab.com> Date: Tue, 11 Nov 2025 19:27:37 +0100 Subject: [PATCH 7/9] update changelog; bump version --- CHANGELOG.md | 5 +++++ pubspec.yaml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76d0842..fc6ed22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.4.0 - 2025-11-11 + +* support --noempty and --cache cli option +* added preferences screen + ## 0.3.2 - 2025-11-11 * print app version on startup diff --git a/pubspec.yaml b/pubspec.yaml index 0e174c2..a0352f4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: fdupes_gui description: fdupes front-end publish_to: 'none' -version: 0.3.2+8 +version: 0.4.0+9 dependency_overrides: # https://github.com/brendan-duncan/image/pull/732 From e37e9f3738f5ed3f1dd8a078a182f09123feed4a Mon Sep 17 00:00:00 2001 From: matthias sweertvaegher <121732-mx1up@users.noreply.gitlab.com> Date: Tue, 11 Nov 2025 19:32:01 +0100 Subject: [PATCH 8/9] add follow symlinks option --- CHANGELOG.md | 2 +- lib/domain/fdupes_bloc.dart | 8 ++++++-- lib/presentation/prefs_dialog.dart | 11 +++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc6ed22..aa7de24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 0.4.0 - 2025-11-11 -* support --noempty and --cache cli option +* support --noempty, --cache and --symlinks cli option * added preferences screen ## 0.3.2 - 2025-11-11 diff --git a/lib/domain/fdupes_bloc.dart b/lib/domain/fdupes_bloc.dart index c33963d..0c8e7a8 100644 --- a/lib/domain/fdupes_bloc.dart +++ b/lib/domain/fdupes_bloc.dart @@ -90,11 +90,13 @@ class FdupesBloc extends Bloc { } final skipEmpty = sharedPreferences.getBool('noempty') ?? false; final useCache = sharedPreferences.getBool('usecache') ?? false; + final followSymlinks = sharedPreferences.getBool('followsymlinks') ?? false; final dupes = await findDupes( event.dirs, emit: emit, skipEmpty: skipEmpty, useCache: useCache, + followSymlinks: followSymlinks, ); emit(FdupesStateResult(dirs: event.dirs, dupeGroups: dupes)); @@ -170,14 +172,16 @@ class FdupesBloc extends Bloc { Future>> findDupes( List dirs, { required Emitter emit, - bool skipEmpty = false, - bool useCache = false, + required bool skipEmpty, + required bool useCache, + required bool followSymlinks, }) async { print("finding dupes in dirs $dirs"); final args = [ '-r', if (skipEmpty) '--noempty', if (useCache) '--usecache', + if (followSymlinks) '--symlinks', ...dirs.map((d) => d.path), ]; print('cmd line: $fdupesLocation $args'); diff --git a/lib/presentation/prefs_dialog.dart b/lib/presentation/prefs_dialog.dart index 14b3d4e..49e866b 100644 --- a/lib/presentation/prefs_dialog.dart +++ b/lib/presentation/prefs_dialog.dart @@ -24,12 +24,14 @@ class PreferencesDialog extends StatefulWidget { class _PreferencesDialogState extends State { late bool skipEmpty; late bool useCache; + late bool followSymlinks; @override void initState() { super.initState(); skipEmpty = widget.sharedPreferences.getBool('noempty') ?? false; useCache = widget.sharedPreferences.getBool('usecache') ?? false; + followSymlinks = widget.sharedPreferences.getBool('followsymlinks') ?? false; } @override @@ -59,6 +61,15 @@ class _PreferencesDialogState extends State { widget.sharedPreferences.setBool('usecache', value); }); }), + SwitchListTile( + title: const Text('Follow symlinks'), + value: followSymlinks, + onChanged: (value) { + setState(() { + followSymlinks = value; + widget.sharedPreferences.setBool('followsymlinks', value); + }); + }), ], ), ); From a8daf0a56fdbbfc108c5d1338d326f0e1fc0df71 Mon Sep 17 00:00:00 2001 From: matthias sweertvaegher <121732-mx1up@users.noreply.gitlab.com> Date: Tue, 11 Nov 2025 19:33:16 +0100 Subject: [PATCH 9/9] explain usage of local state vs shared prefs --- lib/presentation/prefs_dialog.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/presentation/prefs_dialog.dart b/lib/presentation/prefs_dialog.dart index 49e866b..a847d37 100644 --- a/lib/presentation/prefs_dialog.dart +++ b/lib/presentation/prefs_dialog.dart @@ -22,6 +22,7 @@ class PreferencesDialog extends StatefulWidget { } class _PreferencesDialogState extends State { + /// use local state instead of directly accessing shared preferences since we write the settings asynchronously without waiting late bool skipEmpty; late bool useCache; late bool followSymlinks; @@ -54,7 +55,7 @@ class _PreferencesDialogState extends State { SwitchListTile( title: const Text('Use cache'), subtitle: const Text('fdupes 2.3.0+', softWrap: false), - value: widget.sharedPreferences.getBool('usecache') ?? false, + value: useCache, onChanged: (value) { setState(() { useCache = value;