Skip to content
Open
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
15 changes: 15 additions & 0 deletions ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
265BA9BA2F7BDB1E00287691 /* SyncWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 265BA9A92F7BDB1D00287691 /* SyncWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
C0DE1B5001AB12CD34EF0004 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C0DE1B5001AB12CD34EF0003 /* InfoPlist.strings */; };
42257B77B623F2ABAD2A1912 /* DynamicIslandManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A4D4F9C0A149C751B796E19 /* DynamicIslandManager.swift */; };
5736417E0A4E184E8572693A /* WalletPathResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5736417D0A4E184E8572693A /* WalletPathResolver.swift */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
Expand Down Expand Up @@ -104,6 +105,8 @@
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
C0DE1B5001AB12CD34EF0001 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
C0DE1B5001AB12CD34EF0002 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/InfoPlist.strings; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
BGSYNC000ED2DC5600515810 /* BackgroundSyncManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackgroundSyncManager.swift; sourceTree = "<group>"; };
BIOUNLK00ED2DC5600515810 /* BiometricUnlockHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BiometricUnlockHandler.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -246,6 +249,7 @@
8A0F5C502F8B001200A1C001 /* AppIcon.icon */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
C0DE1B5001AB12CD34EF0003 /* InfoPlist.strings */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
Expand Down Expand Up @@ -360,6 +364,7 @@
hasScannedForEncodings = 0;
knownRegions = (
en,
ko,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
Expand Down Expand Up @@ -395,6 +400,7 @@
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
C0DE1B5001AB12CD34EF0004 /* InfoPlist.strings in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
8A0F5C512F8B001200A1C001 /* AppIcon.icon in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
Expand Down Expand Up @@ -548,6 +554,15 @@
/* End PBXTargetDependency section */

/* Begin PBXVariantGroup section */
C0DE1B5001AB12CD34EF0003 /* InfoPlist.strings */ = {
isa = PBXVariantGroup;
children = (
C0DE1B5001AB12CD34EF0001 /* en */,
C0DE1B5001AB12CD34EF0002 /* ko */,
);
name = InfoPlist.strings;
sourceTree = "<group>";
};
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
Expand Down
5 changes: 5 additions & 0 deletions ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>ko</string>
</array>
<key>CFBundleDisplayName</key>
<string>Vizor</string>
<key>CFBundleExecutable</key>
Expand Down
2 changes: 2 additions & 0 deletions ios/Runner/en.lproj/InfoPlist.strings
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"NSCameraUsageDescription" = "Vizor uses the camera to scan Keystone QR codes.";
"NSFaceIDUsageDescription" = "Vizor uses Face ID to unlock your wallet and confirm sensitive actions.";
2 changes: 2 additions & 0 deletions ios/Runner/ko.lproj/InfoPlist.strings
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"NSCameraUsageDescription" = "Vizor는 Keystone QR 코드를 스캔하기 위해 카메라를 사용합니다.";
"NSFaceIDUsageDescription" = "Vizor는 지갑 잠금 해제와 민감한 작업 확인을 위해 Face ID를 사용합니다.";
4 changes: 4 additions & 0 deletions l10n.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
nullable-getter: false
89 changes: 57 additions & 32 deletions lib/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:go_router/go_router.dart';
import 'package:desktop_window_bootstrap/desktop_window_bootstrap.dart';
import 'package:url_launcher/url_launcher.dart';

import 'l10n/app_localizations.dart';
import 'src/app_bootstrap.dart';
import 'src/core/config/swap_feature_config.dart';
import 'src/core/layout/app_layout.dart';
Expand Down Expand Up @@ -73,6 +74,7 @@ import 'src/features/voting/screens/voting_review_screen.dart';
import 'src/features/voting/screens/voting_software_account_guard.dart';
import 'src/features/voting/screens/voting_status_screen.dart';
import 'src/features/voting/screens/voting_submission_confirmation_screen.dart';
import 'src/providers/locale_provider.dart';
import 'src/providers/theme_mode_provider.dart';
import 'src/providers/app_security_provider.dart';
import 'src/providers/linux_update_provider.dart';
Expand Down Expand Up @@ -379,16 +381,18 @@ List<RouteBase> appAuthRoutes(
),
GoRoute(
path: '/terms',
builder: (_, state) => kAppFormFactor == AppFormFactor.mobile
? const MobileLegalScreen(title: 'Terms of Use')
builder: (context, state) => kAppFormFactor == AppFormFactor.mobile
? MobileLegalScreen(title: AppLocalizations.of(context).legalTermsOfUse)
: TermsScreen(
forceFullPane: state.uri.queryParameters['from'] == 'onboarding',
),
),
GoRoute(
path: '/privacy',
builder: (_, state) => kAppFormFactor == AppFormFactor.mobile
? const MobileLegalScreen(title: 'Privacy Policy')
builder: (context, state) => kAppFormFactor == AppFormFactor.mobile
? MobileLegalScreen(
title: AppLocalizations.of(context).aboutPrivacyPolicy,
)
: PrivacyPolicyScreen(
forceFullPane: state.uri.queryParameters['from'] == 'onboarding',
),
Expand Down Expand Up @@ -867,6 +871,7 @@ class ZcashWalletApp extends ConsumerWidget {
final appRouter = ref.watch(_routerProvider);
final router = appRouter.router;
final themeMode = ref.watch(themeModeProvider);
final locale = ref.watch(localeProvider);

return MaterialApp.router(
title: 'Vizor',
Expand All @@ -879,6 +884,14 @@ class ZcashWalletApp extends ConsumerWidget {
routerDelegate: router.routerDelegate,
backButtonDispatcher: appRouter.backButtonDispatcher,
onNavigationNotification: appRouter.onNavigationNotification,
// No stored preference (null) follows the OS locale, clamped to
// supportedLocales with an English fallback — the whole app is
// translated, so Korean-system users get Korean out of the box
// (Keplr parity). A saved Language choice always wins. The
// kLanguageFeatureEnabled kill switch pins English outright.
locale: kLanguageFeatureEnabled ? locale : kEnglishLocale,
supportedLocales: AppLocalizations.supportedLocales,
localizationsDelegates: AppLocalizations.localizationsDelegates,
builder: (context, child) {
return AppThemeHost(
themeMode: themeMode,
Expand Down Expand Up @@ -1115,7 +1128,8 @@ class _WindowsUpdatePrompt extends StatelessWidget {
Widget build(BuildContext context) {
final colors = context.colors;
final isDark = AppTheme.of(context) == AppThemeData.dark;
final action = _primaryAction();
final l10n = AppLocalizations.of(context);
final action = _primaryAction(l10n);

return DefaultTextStyle.merge(
style: const TextStyle(decoration: TextDecoration.none),
Expand Down Expand Up @@ -1154,7 +1168,7 @@ class _WindowsUpdatePrompt extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_title(),
_title(l10n),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: AppTypography.labelLarge.copyWith(
Expand All @@ -1164,7 +1178,7 @@ class _WindowsUpdatePrompt extends StatelessWidget {
),
const SizedBox(height: 2),
Text(
_message(),
_message(l10n),
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: AppTypography.bodySmall.copyWith(
Expand All @@ -1191,7 +1205,7 @@ class _WindowsUpdatePrompt extends StatelessWidget {
onPressed: onLater,
variant: AppButtonVariant.ghost,
size: AppButtonSize.small,
child: const Text('Later'),
child: Text(l10n.updateActionLater),
),
const SizedBox(width: AppSpacing.xxs),
],
Expand All @@ -1211,24 +1225,26 @@ class _WindowsUpdatePrompt extends StatelessWidget {
);
}

String _title() {
String _title(AppLocalizations l10n) {
return switch (state.status) {
WindowsUpdateStatus.available =>
'Update ${state.availableVersion} available',
WindowsUpdateStatus.downloading => 'Downloading update',
WindowsUpdateStatus.ready => 'Update ready',
WindowsUpdateStatus.applying => 'Restarting Vizor',
_ => 'Update available',
WindowsUpdateStatus.available => l10n.updateTitleAvailableVersion(
state.availableVersion,
),
WindowsUpdateStatus.downloading => l10n.updateTitleDownloading,
WindowsUpdateStatus.ready => l10n.updateTitleReady,
WindowsUpdateStatus.applying => l10n.updateTitleApplying,
_ => l10n.updateTitleAvailable,
};
}

String _message() {
String _message(AppLocalizations l10n) {
return switch (state.status) {
WindowsUpdateStatus.available => 'Download now or keep working.',
WindowsUpdateStatus.downloading =>
'${state.downloadProgress}% downloaded.',
WindowsUpdateStatus.ready => 'Restart when you are ready.',
WindowsUpdateStatus.applying => 'Applying after Vizor closes.',
WindowsUpdateStatus.available => l10n.updateBodyAvailable,
WindowsUpdateStatus.downloading => l10n.updateBodyDownloading(
state.downloadProgress,
),
WindowsUpdateStatus.ready => l10n.updateBodyReady,
WindowsUpdateStatus.applying => l10n.updateBodyApplying,
_ => '',
};
}
Expand All @@ -1238,23 +1254,23 @@ class _WindowsUpdatePrompt extends StatelessWidget {
state.status == WindowsUpdateStatus.ready;
}

_WindowsUpdatePromptAction _primaryAction() {
_WindowsUpdatePromptAction _primaryAction(AppLocalizations l10n) {
return switch (state.status) {
WindowsUpdateStatus.available => _WindowsUpdatePromptAction(
label: 'Download',
label: l10n.updateActionDownload,
onPressed: onDownload,
),
WindowsUpdateStatus.ready => _WindowsUpdatePromptAction(
label: 'Restart',
label: l10n.updateActionRestart,
onPressed: onRestart,
),
WindowsUpdateStatus.downloading => const _WindowsUpdatePromptAction(
label: 'Downloading',
WindowsUpdateStatus.downloading => _WindowsUpdatePromptAction(
label: l10n.updateActionDownloading,
),
WindowsUpdateStatus.applying => const _WindowsUpdatePromptAction(
label: 'Restarting',
WindowsUpdateStatus.applying => _WindowsUpdatePromptAction(
label: l10n.updateActionRestarting,
),
_ => const _WindowsUpdatePromptAction(label: 'Update'),
_ => _WindowsUpdatePromptAction(label: l10n.updateActionUpdate),
};
}
}
Expand Down Expand Up @@ -1343,10 +1359,14 @@ class _LinuxUpdateNoticeListener extends ConsumerWidget {
messenger.hideCurrentSnackBar();
messenger.showSnackBar(
SnackBar(
content: Text('Vizor ${update.assetVersion} is available.'),
content: Text(
AppLocalizations.of(
context,
).updateLinuxAvailable(update.assetVersion),
),
duration: const Duration(seconds: 8),
action: SnackBarAction(
label: 'View Release',
label: AppLocalizations.of(context).updateViewRelease,
onPressed: () => unawaited(_openLinuxUpdateRelease(update)),
),
),
Expand Down Expand Up @@ -1406,7 +1426,12 @@ class _RpcEndpointFailoverToastBridge extends ConsumerWidget {
if (!context.mounted) return;
showNetworkFallbackToast(
context,
next.message,
switch (next.kind) {
RpcEndpointFailoverEventKind.switchedToFallback =>
AppLocalizations.of(context).endpointFailoverSwitched,
RpcEndpointFailoverEventKind.switchedToPrimary =>
AppLocalizations.of(context).endpointFailoverRecovered,
},
duration: const Duration(seconds: 4),
);
});
Expand Down
Loading