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
26 changes: 25 additions & 1 deletion app/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -498,5 +498,29 @@
"placeholders": {
"hotkey": { "type": "String" }
}
}
},

"onboardingTitle": "Welcome to CopyPaste",
"@onboardingTitle": { "description": "Onboarding screen title" },

"onboardingSubtitle": "Everything you copy, saved.",
"@onboardingSubtitle": { "description": "Onboarding screen subtitle" },

"onboardingPrivacyBadge": "No cloud · No tracking · 100% local",
"@onboardingPrivacyBadge": { "description": "Onboarding privacy badge chip" },

"onboardingDescription": "Runs silently in the background. Press {hotkey} anytime to open your clipboard history.",
"@onboardingDescription": {
"description": "Onboarding main description",
"placeholders": { "hotkey": { "type": "String" } }
},

"onboardingTrayHint": "Look for the CP icon next to your clock.",
"@onboardingTrayHint": { "description": "Onboarding tray location hint" },

"onboardingSettingsButton": "Settings",
"@onboardingSettingsButton": { "description": "Onboarding settings button" },

"onboardingDismissButton": "Get started",
"@onboardingDismissButton": { "description": "Onboarding dismiss button" }
}
10 changes: 9 additions & 1 deletion app/lib/l10n/app_es.arb
Original file line number Diff line number Diff line change
Expand Up @@ -224,5 +224,13 @@

"balloonStartupBody": "Ejecut\u00e1ndose en segundo plano. Presiona {hotkey} o haz clic en el \u00edcono de la bandeja.",
"balloonWakeupTitle": "CopyPaste ya est\u00e1 abierto",
"balloonWakeupBody": "Presiona {hotkey} o haz clic en el \u00edcono de la bandeja para abrirlo."
"balloonWakeupBody": "Presiona {hotkey} o haz clic en el \u00edcono de la bandeja para abrirlo.",

"onboardingTitle": "Bienvenido a CopyPaste",
"onboardingSubtitle": "Todo lo que copias, guardado.",
"onboardingPrivacyBadge": "Sin nube \u00b7 Sin rastreo \u00b7 100% local",
"onboardingDescription": "Corre en segundo plano sin que lo notes. Presiona {hotkey} cuando quieras para abrir tu historial.",
"onboardingTrayHint": "Encu\u00e9ntralo junto al reloj, abajo a la derecha.",
"onboardingSettingsButton": "Configuraci\u00f3n",
"onboardingDismissButton": "Empezar"
}
42 changes: 42 additions & 0 deletions app/lib/l10n/app_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1207,6 +1207,48 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'Press {hotkey} or click the tray icon to bring it up.'**
String balloonWakeupBody(String hotkey);

/// Onboarding screen title
///
/// In en, this message translates to:
/// **'Welcome to CopyPaste'**
String get onboardingTitle;

/// Onboarding screen subtitle
///
/// In en, this message translates to:
/// **'Everything you copy, saved.'**
String get onboardingSubtitle;

/// Onboarding privacy badge chip
///
/// In en, this message translates to:
/// **'No cloud · No tracking · 100% local'**
String get onboardingPrivacyBadge;

/// Onboarding main description
///
/// In en, this message translates to:
/// **'Runs silently in the background. Press {hotkey} anytime to open your clipboard history.'**
String onboardingDescription(String hotkey);

/// Onboarding tray location hint
///
/// In en, this message translates to:
/// **'Look for the CP icon next to your clock.'**
String get onboardingTrayHint;

/// Onboarding settings button
///
/// In en, this message translates to:
/// **'Settings'**
String get onboardingSettingsButton;

/// Onboarding dismiss button
///
/// In en, this message translates to:
/// **'Get started'**
String get onboardingDismissButton;
}

class _AppLocalizationsDelegate
Expand Down
23 changes: 23 additions & 0 deletions app/lib/l10n/app_localizations_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -610,4 +610,27 @@ class AppLocalizationsEn extends AppLocalizations {
String balloonWakeupBody(String hotkey) {
return 'Press $hotkey or click the tray icon to bring it up.';
}

@override
String get onboardingTitle => 'Welcome to CopyPaste';

@override
String get onboardingSubtitle => 'Everything you copy, saved.';

@override
String get onboardingPrivacyBadge => 'No cloud · No tracking · 100% local';

@override
String onboardingDescription(String hotkey) {
return 'Runs silently in the background. Press $hotkey anytime to open your clipboard history.';
}

@override
String get onboardingTrayHint => 'Look for the CP icon next to your clock.';

@override
String get onboardingSettingsButton => 'Settings';

@override
String get onboardingDismissButton => 'Get started';
}
24 changes: 24 additions & 0 deletions app/lib/l10n/app_localizations_es.dart
Original file line number Diff line number Diff line change
Expand Up @@ -614,4 +614,28 @@ class AppLocalizationsEs extends AppLocalizations {
String balloonWakeupBody(String hotkey) {
return 'Presiona $hotkey o haz clic en el ícono de la bandeja para abrirlo.';
}

@override
String get onboardingTitle => 'Bienvenido a CopyPaste';

@override
String get onboardingSubtitle => 'Todo lo que copias, guardado.';

@override
String get onboardingPrivacyBadge => 'Sin nube · Sin rastreo · 100% local';

@override
String onboardingDescription(String hotkey) {
return 'Corre en segundo plano sin que lo notes. Presiona $hotkey cuando quieras para abrir tu historial.';
}

@override
String get onboardingTrayHint =>
'Encuéntralo junto al reloj, abajo a la derecha.';

@override
String get onboardingSettingsButton => 'Configuración';

@override
String get onboardingDismissButton => 'Empezar';
}
99 changes: 55 additions & 44 deletions app/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import 'theme/compact_theme.dart';
import 'theme/theme_provider.dart';
import 'l10n/app_localizations.dart';
import 'screens/permission_gate_screen.dart';
import 'screens/windows_onboarding_screen.dart';

bool _isMicaDark(String themeMode) => switch (themeMode) {
'dark' => true,
Expand All @@ -47,6 +48,11 @@ bool isWaylandSession() {

void main() async {
WidgetsFlutterBinding.ensureInitialized();

if (!SingleInstance.acquire()) {
exit(0);
}

await windowManager.ensureInitialized();

if (Platform.isWindows || Platform.isMacOS) {
Expand All @@ -57,10 +63,6 @@ void main() async {
}
}

if (!SingleInstance.acquire()) {
exit(0);
}

final storage = await StorageConfig.create();
await storage.ensureDirectories();
AppLogger.initialize(storage.logsPath);
Expand Down Expand Up @@ -159,6 +161,7 @@ class _CopyPasteAppState extends State<CopyPasteApp>
StreamSubscription<ClipboardEvent>? _listenerSubscription;
String? _lastTrayLocale;
bool _showPermissionGate = false;
bool _showWindowsOnboarding = false;
String? _availableUpdateVersion;
bool _programmaticRestore = false;

Expand Down Expand Up @@ -214,15 +217,11 @@ class _CopyPasteAppState extends State<CopyPasteApp>
}

final showOnStart =
isFirstRun &&
(Platform.isLinux ||
Platform.isWindows ||
(Platform.isMacOS && macosGranted));
isFirstRun && (Platform.isLinux || (Platform.isMacOS && macosGranted));
await _appWindow.init(startVisible: showOnStart);
SingleInstance.listenForWakeup(() {
unawaited(_safeShow());
if (Platform.isWindows) unawaited(_showWakeupBalloon());
_showWakeupHint();
});

try {
Expand All @@ -241,14 +240,10 @@ class _CopyPasteAppState extends State<CopyPasteApp>
AppLogger.error('trayIcon.init failed: $e');
}

if (Platform.isWindows) {
if (showOnStart) {
Future<void>.delayed(const Duration(seconds: 2), _showStartupBalloon);
} else {
WidgetsBinding.instance.addPostFrameCallback(
(_) => unawaited(_showStartupBalloon()),
);
}
if (Platform.isWindows && !isFirstRun && _config.hasSeenWindowsOnboarding) {
WidgetsBinding.instance.addPostFrameCallback(
(_) => unawaited(_showStartupBalloon()),
);
}

await _registerHotkeyWithFeedback();
Expand All @@ -269,7 +264,11 @@ class _CopyPasteAppState extends State<CopyPasteApp>
}
}
} else {
if (isFirstRun) {
if (Platform.isWindows && !_config.hasSeenWindowsOnboarding) {
if (isFirstRun) widget.storage.markAsInitialized();
setState(() => _showWindowsOnboarding = true);
await _appWindow.enterGateMode();
} else if (isFirstRun) {
widget.storage.markAsInitialized();
}
}
Expand Down Expand Up @@ -356,32 +355,6 @@ class _CopyPasteAppState extends State<CopyPasteApp>
}
}

void _showWakeupHint() {
WidgetsBinding.instance.addPostFrameCallback((_) {
final ctx = _navigatorKey.currentContext;
if (ctx == null || !ctx.mounted) return;
if (_navigatorKey.currentState?.canPop() ?? false) return;
final messenger = ScaffoldMessenger.maybeOf(ctx);
if (messenger == null) return;
final binding = HotkeyBinding(
virtualKey: _config.hotkeyVirtualKey,
keyName: _config.hotkeyKeyName,
useCtrl: _config.hotkeyUseCtrl,
useWin: _config.hotkeyUseWin,
useAlt: _config.hotkeyUseAlt,
useShift: _config.hotkeyUseShift,
);
messenger
..clearSnackBars()
..showSnackBar(
SnackBar(
content: Text(AppLocalizations.of(ctx).wakeupHint(binding.label())),
duration: const Duration(seconds: 10),
),
);
});
}

void _showLinuxNotice(String Function(AppLocalizations l) messageBuilder) {
WidgetsBinding.instance.addPostFrameCallback((_) {
final ctx = _navigatorKey.currentContext;
Expand Down Expand Up @@ -840,6 +813,28 @@ class _CopyPasteAppState extends State<CopyPasteApp>
if (mounted) setState(() => _showPermissionGate = false);
}

Future<void> _onOnboardingDismissed() async {
_config = _config.copyWith(hasSeenWindowsOnboarding: true);
unawaited(
_config.save('${widget.storage.configPath}/${AppConfig.fileName}'),
);
setState(() => _showWindowsOnboarding = false);
await _appWindow.exitGateMode();
unawaited(_showStartupBalloon());
}

Future<void> _onOnboardingGoSettings(BuildContext ctx) async {
_config = _config.copyWith(hasSeenWindowsOnboarding: true);
unawaited(
_config.save('${widget.storage.configPath}/${AppConfig.fileName}'),
);
setState(() => _showWindowsOnboarding = false);
await _appWindow.exitGateMode();
await Future<void>.delayed(const Duration(milliseconds: 150));
if (ctx.mounted) await _openSettings(ctx);
unawaited(_showStartupBalloon());
}

Future<void> _restartApp() async {
await _cleanup();
SingleInstance.release();
Expand Down Expand Up @@ -915,6 +910,22 @@ class _CopyPasteAppState extends State<CopyPasteApp>
}
}

if (_showWindowsOnboarding) {
final binding = HotkeyBinding(
virtualKey: _config.hotkeyVirtualKey,
keyName: _config.hotkeyKeyName,
useCtrl: _config.hotkeyUseCtrl,
useWin: _config.hotkeyUseWin,
useAlt: _config.hotkeyUseAlt,
useShift: _config.hotkeyUseShift,
);
return WindowsOnboardingScreen(
hotkey: binding.label(),
onDismiss: () => unawaited(_onOnboardingDismissed()),
onSettings: () => unawaited(_onOnboardingGoSettings(ctx)),
);
}

if (_showPermissionGate) {
return PermissionGateScreen(
previouslyGranted: _config.accessibilityWasGranted,
Expand Down
Loading
Loading