Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,8 @@
"@settingLinesExpanded": { "description": "Lines expanded label" },
"settingHideOnDeactivate": "Hide on deactivate",
"@settingHideOnDeactivate": { "description": "Hide on deactivate label" },
"settingRememberWindowPosition": "Remember window position",
"@settingRememberWindowPosition": { "description": "Remember window position toggle label" },
"settingScrollToTopOnOpen": "Scroll to top on open",
"@settingScrollToTopOnOpen": { "description": "Scroll to top on open label" },
"settingClearSearchOnOpen": "Clear search on open",
Expand All @@ -293,6 +295,8 @@
"@subtitleStartupDesc": { "description": "Startup subtitle" },
"subtitleHideOnDeactivate": "Close window when clicking outside",
"@subtitleHideOnDeactivate": { "description": "Hide on deactivate subtitle" },
"subtitleRememberWindowPosition": "Reopen the window where you left it last time",
"@subtitleRememberWindowPosition": { "description": "Remember window position subtitle" },
"subtitleScrollToTopOnOpen": "Resets scroll and selects latest item",
"@subtitleScrollToTopOnOpen": { "description": "Scroll to top on open subtitle" },
"subtitleClearSearchOnOpen": "Clears the search text each time",
Expand Down
2 changes: 2 additions & 0 deletions app/lib/l10n/app_es.arb
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@
"settingLinesCollapsed": "L\u00edneas contra\u00eddas",
"settingLinesExpanded": "L\u00edneas expandidas",
"settingHideOnDeactivate": "Ocultar al hacer clic fuera",
"settingRememberWindowPosition": "Recordar posición de la ventana",
"settingScrollToTopOnOpen": "Ir al inicio al abrir",
"settingClearSearchOnOpen": "Limpiar b\u00fasqueda al abrir",
"settingRetentionDaysLabel": "D\u00edas de retenci\u00f3n (0 = sin l\u00edmite)",
Expand All @@ -147,6 +148,7 @@

"subtitleStartupDesc": "Se inicia en segundo plano al iniciar sesi\u00f3n",
"subtitleHideOnDeactivate": "Cerrar la ventana al hacer clic fuera",
"subtitleRememberWindowPosition": "Reabrir la ventana donde la dejaste la última vez",
"subtitleScrollToTopOnOpen": "Restablece el desplazamiento y selecciona el \u00faltimo elemento",
"subtitleClearSearchOnOpen": "Borra el texto de b\u00fasqueda cada vez",
"subtitlePasteSpeed": "Ajustar tiempos de restauraci\u00f3n y pegado",
Expand Down
12 changes: 12 additions & 0 deletions app/lib/l10n/app_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,12 @@ abstract class AppLocalizations {
/// **'Hide on deactivate'**
String get settingHideOnDeactivate;

/// Remember window position toggle label
///
/// In en, this message translates to:
/// **'Remember window position'**
String get settingRememberWindowPosition;

/// Scroll to top on open label
///
/// In en, this message translates to:
Expand Down Expand Up @@ -848,6 +854,12 @@ abstract class AppLocalizations {
/// **'Close window when clicking outside'**
String get subtitleHideOnDeactivate;

/// Remember window position subtitle
///
/// In en, this message translates to:
/// **'Reopen the window where you left it last time'**
String get subtitleRememberWindowPosition;

/// Scroll to top on open subtitle
///
/// In en, this message translates to:
Expand Down
7 changes: 7 additions & 0 deletions app/lib/l10n/app_localizations_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get settingHideOnDeactivate => 'Hide on deactivate';

@override
String get settingRememberWindowPosition => 'Remember window position';

@override
String get settingScrollToTopOnOpen => 'Scroll to top on open';

Expand All @@ -397,6 +400,10 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get subtitleHideOnDeactivate => 'Close window when clicking outside';

@override
String get subtitleRememberWindowPosition =>
'Reopen the window where you left it last time';

@override
String get subtitleScrollToTopOnOpen =>
'Resets scroll and selects latest item';
Expand Down
7 changes: 7 additions & 0 deletions app/lib/l10n/app_localizations_es.dart
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,9 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get settingHideOnDeactivate => 'Ocultar al hacer clic fuera';

@override
String get settingRememberWindowPosition => 'Recordar posición de la ventana';

@override
String get settingScrollToTopOnOpen => 'Ir al inicio al abrir';

Expand All @@ -400,6 +403,10 @@ class AppLocalizationsEs extends AppLocalizations {
String get subtitleHideOnDeactivate =>
'Cerrar la ventana al hacer clic fuera';

@override
String get subtitleRememberWindowPosition =>
'Reabrir la ventana donde la dejaste la última vez';

@override
String get subtitleScrollToTopOnOpen =>
'Restablece el desplazamiento y selecciona el último elemento';
Expand Down
80 changes: 52 additions & 28 deletions app/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ class _CopyPasteAppState extends State<CopyPasteApp>
final _navigatorKey = GlobalKey<NavigatorState>();
StreamSubscription<ClipboardEvent>? _listenerSubscription;
String? _lastTrayLocale;
Future<void>? _pendingConfigSave;
bool _showPermissionGate = false;
bool _showOnboarding = false;
bool _showWaylandUnsupported = false;
Expand All @@ -246,6 +247,14 @@ class _CopyPasteAppState extends State<CopyPasteApp>
showInTaskbar: false,
popupWidth: _config.popupWidth.toDouble(),
popupHeight: _config.popupHeight.toDouble(),
rememberPositionEnabled: () => _config.rememberWindowPosition,
savedPositionProvider: () {
final x = _config.lastWindowX;
final y = _config.lastWindowY;
if (x == null || y == null) return null;
return (x, y);
},
onPositionPersist: _onPositionPersist,
);
_trayIcon = TrayIcon(onToggle: _toggleWindow, onExit: _exitApp);
_hotkeyHandler = HotkeyHandler(config: _config, onHotkey: _onHotkey);
Expand Down Expand Up @@ -386,9 +395,8 @@ class _CopyPasteAppState extends State<CopyPasteApp>
await _appWindow.enterGateMode();
} else {
if (!_config.accessibilityWasGranted) {
_config = _config.copyWith(accessibilityWasGranted: true);
unawaited(
_config.save('${widget.storage.configPath}/${AppConfig.fileName}'),
_persistConfig((c) => c.copyWith(accessibilityWasGranted: true)),
);
}
if (isFirstRun) {
Expand All @@ -411,9 +419,10 @@ class _CopyPasteAppState extends State<CopyPasteApp>
widget.storage.markAsInitialized();
}
if (isUpdate && Platform.isWindows) {
_config = _config.copyWith(lastRunVersion: AppConfig.appVersion);
unawaited(
_config.save('${widget.storage.configPath}/${AppConfig.fileName}'),
_persistConfig(
(c) => c.copyWith(lastRunVersion: AppConfig.appVersion),
),
);
}
}
Expand All @@ -434,9 +443,8 @@ class _CopyPasteAppState extends State<CopyPasteApp>
AppLogger.error('Classifier migration failed: $e\n$s');
return; // version not saved → retries on next startup
}
_config = _config.copyWith(lastRunVersion: AppConfig.appVersion);
unawaited(
_config.save('${widget.storage.configPath}/${AppConfig.fileName}'),
_persistConfig((c) => c.copyWith(lastRunVersion: AppConfig.appVersion)),
);
}

Expand Down Expand Up @@ -728,13 +736,23 @@ class _CopyPasteAppState extends State<CopyPasteApp>

void _dismissHint() {
if (_config.hasSeenHint) return;
_config = _config.copyWith(hasSeenHint: true);
unawaited(
_config.save('${widget.storage.configPath}/${AppConfig.fileName}'),
);
unawaited(_persistConfig((c) => c.copyWith(hasSeenHint: true)));
if (mounted) setState(() {});
}

Future<void> _persistConfig(AppConfig Function(AppConfig) update) {
_config = update(_config);
final path = '${widget.storage.configPath}/${AppConfig.fileName}';
final next = (_pendingConfigSave ?? Future<void>.value())
.catchError((Object _) {})
.then((_) => _config.save(path));
_pendingConfigSave = next;
next.catchError((Object e) {
AppLogger.warn('config save failed: $e');
});
return next;
}

Future<void> _updateLinuxConfig(AppConfig Function(AppConfig) update) async {
final next = update(_config);
if (identical(next, _config)) return;
Expand All @@ -761,6 +779,13 @@ class _CopyPasteAppState extends State<CopyPasteApp>
}
}

void _onPositionPersist(double x, double y) {
if (_config.lastWindowX == x && _config.lastWindowY == y) return;
unawaited(
_persistConfig((c) => c.copyWith(lastWindowX: x, lastWindowY: y)),
);
}

Future<void> _onPasteItem(
ClipboardItem item, {
bool plainText = false,
Expand Down Expand Up @@ -1047,24 +1072,22 @@ class _CopyPasteAppState extends State<CopyPasteApp>
}

Future<void> _onPermissionGranted() async {
_config = _config.copyWith(accessibilityWasGranted: true);
unawaited(
_config.save('${widget.storage.configPath}/${AppConfig.fileName}'),
);
unawaited(_persistConfig((c) => c.copyWith(accessibilityWasGranted: true)));
await _appWindow.exitGateMode();
if (mounted) setState(() => _showPermissionGate = false);
}

Future<void> _onOnboardingDismissed(AppConfig fromOnboarding) async {
_config = fromOnboarding.copyWith(
hasSeenOnboarding: true,
hasCompletedOnboarding: true,
lastRunVersion: AppConfig.appVersion,
);
_applyOnboardingPersistence();
unawaited(
_config.save('${widget.storage.configPath}/${AppConfig.fileName}'),
_persistConfig(
(_) => fromOnboarding.copyWith(
hasSeenOnboarding: true,
hasCompletedOnboarding: true,
lastRunVersion: AppConfig.appVersion,
),
),
);
_applyOnboardingPersistence();
setState(() => _showOnboarding = false);
await _appWindow.exitGateMode();
unawaited(_showStartupBalloon());
Expand All @@ -1074,15 +1097,16 @@ class _CopyPasteAppState extends State<CopyPasteApp>
BuildContext ctx,
AppConfig fromOnboarding,
) async {
_config = fromOnboarding.copyWith(
hasSeenOnboarding: true,
hasCompletedOnboarding: true,
lastRunVersion: AppConfig.appVersion,
);
_applyOnboardingPersistence();
unawaited(
_config.save('${widget.storage.configPath}/${AppConfig.fileName}'),
_persistConfig(
(_) => fromOnboarding.copyWith(
hasSeenOnboarding: true,
hasCompletedOnboarding: true,
lastRunVersion: AppConfig.appVersion,
),
),
);
_applyOnboardingPersistence();
setState(() => _showOnboarding = false);
await _appWindow.exitGateMode();
await Future<void>.delayed(const Duration(milliseconds: 150));
Expand Down
16 changes: 16 additions & 0 deletions app/lib/screens/settings_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
late String _themeMode;

late bool _hideOnDeactivate;
late bool _rememberWindowPosition;
late bool _resetScrollOnShow;
late bool _resetSearchOnShow;
late bool _resetFiltersOnShow;
Expand Down Expand Up @@ -131,6 +132,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
_cardMaxLines = widget.config.cardMaxLines;
_themeMode = widget.config.themeMode;
_hideOnDeactivate = widget.config.hideOnDeactivate;
_rememberWindowPosition = widget.config.rememberWindowPosition;
_resetScrollOnShow = widget.config.resetScrollOnShow;
_resetSearchOnShow = widget.config.resetSearchOnShow;
_resetFiltersOnShow = widget.config.resetFiltersOnShow;
Expand Down Expand Up @@ -182,6 +184,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
cardMaxLines: _cardMaxLines,
themeMode: _themeMode,
hideOnDeactivate: _hideOnDeactivate,
rememberWindowPosition: _rememberWindowPosition,
lastWindowX: _rememberWindowPosition ? widget.config.lastWindowX : null,
lastWindowY: _rememberWindowPosition ? widget.config.lastWindowY : null,
resetScrollOnShow: _resetScrollOnShow,
resetSearchOnShow: _resetSearchOnShow,
resetFiltersOnShow: _resetFiltersOnShow,
Expand Down Expand Up @@ -251,6 +256,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
_cardMaxLines = d.cardMaxLines;
_themeMode = d.themeMode;
_hideOnDeactivate = d.hideOnDeactivate;
_rememberWindowPosition = d.rememberWindowPosition;
_resetScrollOnShow = d.resetScrollOnShow;
_resetSearchOnShow = d.resetSearchOnShow;
_resetFiltersOnShow = d.resetFiltersOnShow;
Expand Down Expand Up @@ -803,6 +809,16 @@ class _SettingsScreenState extends State<SettingsScreen> {
_markChanged();
},
),
_ToggleRow(
label: l.settingRememberWindowPosition,
subtitle: l.subtitleRememberWindowPosition,
value: _rememberWindowPosition,
colors: colors,
onChanged: (v) {
setState(() => _rememberWindowPosition = v);
_markChanged();
},
),
_ToggleRow(
label: l.settingScrollToTopOnOpen,
subtitle: l.subtitleScrollToTopOnOpen,
Expand Down
Loading
Loading