From 12660917468ae3d51ecff70ba5055001780283b6 Mon Sep 17 00:00:00 2001 From: Kristen McWilliam <9575627+Merrit@users.noreply.github.com> Date: Mon, 1 Apr 2024 14:47:53 -0400 Subject: [PATCH] feat: add option to keep suspended windows pinned to the top of the list Makes it easy to keep track of windows that are suspended. Resolves https://github.com/Merrit/nyrna/issues/209 --- lib/apps_list/cubit/apps_list_cubit.dart | 23 ++++++++-- lib/localization/app_en.arb | 8 ++++ lib/settings/cubit/settings_cubit.dart | 9 ++++ lib/settings/cubit/settings_state.dart | 4 ++ lib/settings/widgets/behaviour_section.dart | 44 +++++++++++++++++++ .../apps_list/cubit/apps_list_cubit_test.dart | 1 + 6 files changed, 86 insertions(+), 3 deletions(-) diff --git a/lib/apps_list/cubit/apps_list_cubit.dart b/lib/apps_list/cubit/apps_list_cubit.dart index 3234bf18..a14d1cea 100644 --- a/lib/apps_list/cubit/apps_list_cubit.dart +++ b/lib/apps_list/cubit/apps_list_cubit.dart @@ -73,7 +73,7 @@ class AppsListCubit extends Cubit { windows[i] = await _refreshWindowProcess(windows[i]); } - windows.sortWindows(); + windows.sortWindows(_settingsCubit.state.pinSuspendedWindows); emit(state.copyWith(windows: windows)); } @@ -155,6 +155,8 @@ class AppsListCubit extends Cubit { [window], ); + windows.sortWindows(_settingsCubit.state.pinSuspendedWindows); + emit(state.copyWith( windows: windows, )); @@ -318,7 +320,22 @@ extension on List { extension on List { /// Sort the windows by executable name. - void sortWindows() { - sortBy((window) => window.process.executable.toLowerCase()); + /// + /// If the user has enabled pinning suspended windows to the top of the list, + /// those windows will be sorted to the top. + void sortWindows(bool pinSuspendedWindows) { + sort((a, b) { + final aIsSuspended = a.process.status == ProcessStatus.suspended; + final bIsSuspended = b.process.status == ProcessStatus.suspended; + + if (pinSuspendedWindows) { + if (aIsSuspended && !bIsSuspended) return -1; + if (!aIsSuspended && bIsSuspended) return 1; + } + + return a.process.executable.toLowerCase().compareTo( + b.process.executable.toLowerCase(), + ); + }); } } diff --git a/lib/localization/app_en.arb b/lib/localization/app_en.arb index 1b44dc4a..215a8128 100644 --- a/lib/localization/app_en.arb +++ b/lib/localization/app_en.arb @@ -94,6 +94,14 @@ "@minimizeAndRestoreWindows": { "description": "Label for the minimize / restore windows setting" }, + "pinSuspendedWindows": "Pin suspended windows", + "@pinSuspendedWindows": { + "description": "Whether to pin suspended windows to the top of the window list" + }, + "pinSuspendedWindowsTooltip": "If enabled, suspended windows will always be shown at the top of the window list.", + "@pinSuspendedWindowsTooltip": { + "description": "Tooltip for the pin suspended windows setting" + }, "showHiddenWindows": "Show hidden windows", "@showHiddenWindows": { "description": "Label for the show hidden windows setting" diff --git a/lib/settings/cubit/settings_cubit.dart b/lib/settings/cubit/settings_cubit.dart index de99e102..c7906550 100644 --- a/lib/settings/cubit/settings_cubit.dart +++ b/lib/settings/cubit/settings_cubit.dart @@ -63,6 +63,8 @@ class SettingsCubit extends Cubit { final bool minimizeWindows = await storage.getValue('minimizeWindows') ?? true; + final bool pinSuspendedWindows = + await storage.getValue('pinSuspendedWindows') ?? false; final int refreshInterval = await storage.getValue('refreshInterval') ?? 5; final bool showHiddenWindows = await storage.getValue('showHiddenWindows') ?? false; @@ -80,6 +82,7 @@ class SettingsCubit extends Cubit { closeToTray: closeToTray, hotKey: hotkey, minimizeWindows: minimizeWindows, + pinSuspendedWindows: pinSuspendedWindows, refreshInterval: refreshInterval, showHiddenWindows: showHiddenWindows, startHiddenInTray: startHiddenInTray, @@ -141,6 +144,12 @@ class SettingsCubit extends Cubit { await _storage.saveValue(key: 'minimizeWindows', value: value); } + /// Update the preference for pinning suspended windows to the top of the list. + Future updatePinSuspendedWindows(bool value) async { + emit(state.copyWith(pinSuspendedWindows: value)); + await _storage.saveValue(key: 'pinSuspendedWindows', value: value); + } + Future updateShowHiddenWindows(bool value) async { await _storage.saveValue(key: 'showHiddenWindows', value: value); emit(state.copyWith(showHiddenWindows: value)); diff --git a/lib/settings/cubit/settings_state.dart b/lib/settings/cubit/settings_state.dart index b729dc67..b98a5984 100644 --- a/lib/settings/cubit/settings_state.dart +++ b/lib/settings/cubit/settings_state.dart @@ -24,6 +24,9 @@ class SettingsState with _$SettingsState { /// restored when resuming. required bool minimizeWindows, + /// If true suspended windows will be shown at the top of the list. + required bool pinSuspendedWindows, + /// How often to automatically refresh the list of open windows, in seconds. required int refreshInterval, required bool showHiddenWindows, @@ -41,6 +44,7 @@ class SettingsState with _$SettingsState { closeToTray: false, hotKey: defaultHotkey, minimizeWindows: true, + pinSuspendedWindows: false, refreshInterval: 5, showHiddenWindows: false, startHiddenInTray: false, diff --git a/lib/settings/widgets/behaviour_section.dart b/lib/settings/widgets/behaviour_section.dart index 2a69faf4..044146c3 100644 --- a/lib/settings/widgets/behaviour_section.dart +++ b/lib/settings/widgets/behaviour_section.dart @@ -75,6 +75,7 @@ class BehaviourSection extends StatelessWidget { }, ), ), + const _PinSuspendedWindowsTile(), const ShowHiddenTile(), ], ); @@ -95,6 +96,47 @@ class BehaviourSection extends StatelessWidget { } } +/// Toggle pinning suspended windows to the top of the window list. +class _PinSuspendedWindowsTile extends StatelessWidget { + const _PinSuspendedWindowsTile(); + + @override + Widget build(BuildContext context) { + return ListTile( + title: Text.rich( + TextSpan( + children: [ + TextSpan( + text: '${AppLocalizations.of(context)!.pinSuspendedWindows} ', + ), + WidgetSpan( + child: Tooltip( + message: + AppLocalizations.of(context)!.pinSuspendedWindowsTooltip, + child: const Icon( + Icons.help_outline, + size: 18, + ), + ), + ), + ], + ), + ), + leading: const Icon(Icons.push_pin_outlined), + trailing: BlocBuilder( + builder: (context, state) { + return Switch( + value: state.pinSuspendedWindows, + onChanged: (value) async { + await settingsCubit.updatePinSuspendedWindows(value); + }, + ); + }, + ), + ); + } +} + class ShowHiddenTile extends StatelessWidget { const ShowHiddenTile({super.key}); @@ -125,7 +167,9 @@ class ShowHiddenTile extends StatelessWidget { return Switch( value: state.showHiddenWindows, onChanged: (value) async { + final appsListCubit = context.read(); await settingsCubit.updateShowHiddenWindows(value); + await appsListCubit.manualRefresh(); }, ); }, diff --git a/test/apps_list/cubit/apps_list_cubit_test.dart b/test/apps_list/cubit/apps_list_cubit_test.dart index cfec87f9..0901c680 100644 --- a/test/apps_list/cubit/apps_list_cubit_test.dart +++ b/test/apps_list/cubit/apps_list_cubit_test.dart @@ -111,6 +111,7 @@ void main() { closeToTray: false, hotKey: HotKey(KeyCode.again), minimizeWindows: true, + pinSuspendedWindows: false, refreshInterval: 5, showHiddenWindows: false, startHiddenInTray: false,