From 973210d8d8f81da07aca9f2c9e407d06e5107509 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Wed, 6 Nov 2024 13:30:10 -0500 Subject: [PATCH 1/4] chore: add more localization strings --- packages/dart/npt_flutter/lib/localization/app_en.arb | 10 +++++++++- packages/dart/npt_flutter/lib/localization/app_es.arb | 8 ++++++++ packages/dart/npt_flutter/lib/localization/app_pt.arb | 8 ++++++++ .../dart/npt_flutter/lib/localization/app_pt_BR.arb | 8 ++++++++ packages/dart/npt_flutter/lib/localization/app_zh.arb | 10 ++++++++-- .../npt_flutter/lib/localization/app_zh_Hans_CH.arb | 8 ++++++++ .../npt_flutter/lib/localization/app_zh_Hant_HK.arb | 8 ++++++++ 7 files changed, 57 insertions(+), 3 deletions(-) diff --git a/packages/dart/npt_flutter/lib/localization/app_en.arb b/packages/dart/npt_flutter/lib/localization/app_en.arb index 97ac69964..7dbe956a6 100644 --- a/packages/dart/npt_flutter/lib/localization/app_en.arb +++ b/packages/dart/npt_flutter/lib/localization/app_en.arb @@ -1,4 +1,5 @@ { + "addNew" : "Add New", "addNewProfile" : "Add New Profile", "advanced": "Advanced", @@ -14,6 +15,7 @@ "backupYourKey" : "Backup Your Key", "cancel" : "Cancel", "confirm" : "Confirm", + "connected" : "connected", "dashboard" : "Dashboard", "dashboardView" : "Dashboard View", "defaultRelaySelection" : "Default Relay Selection", @@ -22,6 +24,7 @@ "deviceAtsignDescription" : "This is the atSign associated with you device", "deviceName": "Device Name", "deviceNameDescription" : "This is the name of your remote device", + "disconnected" : "disconnected", "discord" : "Discord", "done": "Done", "edit" : "Edit", @@ -32,12 +35,14 @@ "export" : "Export", "exportLogs" : "Export Logs", "failed": "Failed", + "failedToLoad" : "failed to load", "faq" : "faq", "feedback" : "Feedback", "getStarted" : "Get Started", "import" : "Import", "json" : "JSON", "language" : "Language", + "loading" : "loading", "localPort" : "Local Port", "localPortDescription" : "", "logs" : "Logs", @@ -64,6 +69,7 @@ "profileName" : "Profile Name", "profileNameDescription" : "This will be the name of you configurations", "profileRunningActionDeniedMessage" : "Cannot perform this action while profile is running", + "quit": "Quit", "refresh" : "Refresh", "relay" : "Relay", "relayDescription" : "You can pick from our existing RV''s or create a new one", @@ -72,16 +78,18 @@ "remotePort" : "Remote Port", "remotePortDescription" : "", "resetAtsign" : "Reset Atsign", - "rootDomainDefault" : "Default (Prod)", "rootDomainDemo" : "Demo (VE)", "selectExportFile": "Please select a file to export to:", "selectRootDomain" : "Select Root Domain", "serviceMapping" : "Service Mapping", "settings" : "Settings", + "showWindow" : "Show Window", "signout" : "Sign Out", "sshStyle" : "Advanced", + "starting" : "starting", "status" : "Status", + "stopping" : "stopping", "submit" : "Submit", "validationErrorAtsignField" : "Field must be a valid atsign that starts with @", "validationErrorDeviceNameField" : "Field can only contain lowercase letters, digits, underscores.", diff --git a/packages/dart/npt_flutter/lib/localization/app_es.arb b/packages/dart/npt_flutter/lib/localization/app_es.arb index 3b769b07e..282827885 100644 --- a/packages/dart/npt_flutter/lib/localization/app_es.arb +++ b/packages/dart/npt_flutter/lib/localization/app_es.arb @@ -14,6 +14,7 @@ "backupYourKey" : "Respalda tu clave", "cancel" : "Cancelar", "confirm" : "Confirmar", + "connected" : "conectado", "dashboard" : "Tablero", "dashboardView" : "Vista del tablero", "defaultRelaySelection" : "Selección de relé predeterminada", @@ -22,6 +23,7 @@ "deviceAtsignDescription" : "Este es el atSign asociado con su dispositivo", "deviceName": "Nombre del dispositivo", "deviceNameDescription" : "Este es el nombre de su dispositivo remoto", + "disconnected" : "desconectado", "discord" : "Discord", "done": "Hecho", "edit" : "Editar", @@ -32,12 +34,14 @@ "export" : "Exportar", "exportLogs" : "Exportar registros", "failed": "Fallido", + "failedToLoad" : "falló al cargar", "faq" : "Preguntas frecuentes", "feedback" : "Comentarios", "getStarted" : "Empezar", "import" : "Importar", "json" : "JSON", "language" : "Idioma", + "loading" : "cargando", "localPort" : "Puerto local", "localPortDescription" : "", "logs" : "Registros", @@ -64,6 +68,7 @@ "profileName" : "Nombre del perfil", "profileNameDescription" : "Este será el nombre de sus configuraciones", "profileRunningActionDeniedMessage" : "No se puede realizar esta acción mientras el perfil está en ejecución", + "quit": "Salir", "refresh" : "Actualizar", "relay" : "Relé", "relayDescription" : "Puede elegir entre nuestros RV existentes o crear uno nuevo", @@ -78,9 +83,12 @@ "selectRootDomain" : "Seleccionar dominio raíz", "serviceMapping" : "Mapeo de servicios", "settings" : "Configuraciones", + "showWindow" : "Mostrar ventana", "signout" : "Cerrar sesión", "sshStyle" : "Avanzado", + "starting" : "iniciando", "status" : "Estado", + "stopping" : "deteniendo", "submit" : "Enviar", "validationErrorAtsignField" : "El campo debe ser un atsign válido que comience con @", "validationErrorDeviceNameField" : "El campo solo puede contener letras minúsculas, dígitos, guiones bajos.", diff --git a/packages/dart/npt_flutter/lib/localization/app_pt.arb b/packages/dart/npt_flutter/lib/localization/app_pt.arb index d2a3d660a..c0f5f52ba 100644 --- a/packages/dart/npt_flutter/lib/localization/app_pt.arb +++ b/packages/dart/npt_flutter/lib/localization/app_pt.arb @@ -14,6 +14,7 @@ "backupYourKey" : "Faça Backup da Sua Chave", "cancel" : "Cancelar", "confirm" : "Confirmar", + "connected" : "conectado", "dashboard" : "Painel", "dashboardView" : "Visualização do Painel", "defaultRelaySelection" : "Seleção de Relay Padrão", @@ -22,6 +23,7 @@ "deviceAtsignDescription" : "Este é o atSign associado ao seu dispositivo", "deviceName": "Nome do Dispositivo", "deviceNameDescription" : "Este é o nome do seu dispositivo remoto", + "disconnected" : "desconectado", "discord" : "Discord", "done": "Concluído", "edit" : "Editar", @@ -32,12 +34,14 @@ "export" : "Exportar", "exportLogs" : "Exportar Registros", "failed": "Falhou", + "failedToLoad" : "falha ao carregar", "faq" : "faq", "feedback" : "Feedback", "getStarted" : "Começar", "import" : "Importar", "json" : "JSON", "language" : "Idioma", + "loading" : "carregando", "localPort" : "Porta Local", "localPortDescription" : "", "logs" : "Registros", @@ -64,6 +68,7 @@ "profileName" : "Nome do Perfil", "profileNameDescription" : "Este será o nome das suas configurações", "profileRunningActionDeniedMessage" : "Não é possível realizar esta ação enquanto o perfil está em execução", + "quit": "Sair", "refresh" : "Atualizar", "relay" : "Relay", "relayDescription" : "Você pode escolher entre nossos RVs existentes ou criar um novo", @@ -78,9 +83,12 @@ "selectRootDomain" : "Selecione o Domínio Raiz", "serviceMapping" : "Mapeamento de Serviço", "settings" : "Configurações", + "showWindow" : "Mostrar Janela", "signout" : "Sair", "sshStyle" : "Avançado", + "starting" : "iniciando", "status" : "Status", + "stopping" : "parando", "submit" : "Enviar", "validationErrorAtsignField" : "O campo deve ser um atsign válido que começa com @", "validationErrorDeviceNameField" : "O campo só pode conter letras minúsculas, dígitos, sublinhados.", diff --git a/packages/dart/npt_flutter/lib/localization/app_pt_BR.arb b/packages/dart/npt_flutter/lib/localization/app_pt_BR.arb index d2a3d660a..c0f5f52ba 100644 --- a/packages/dart/npt_flutter/lib/localization/app_pt_BR.arb +++ b/packages/dart/npt_flutter/lib/localization/app_pt_BR.arb @@ -14,6 +14,7 @@ "backupYourKey" : "Faça Backup da Sua Chave", "cancel" : "Cancelar", "confirm" : "Confirmar", + "connected" : "conectado", "dashboard" : "Painel", "dashboardView" : "Visualização do Painel", "defaultRelaySelection" : "Seleção de Relay Padrão", @@ -22,6 +23,7 @@ "deviceAtsignDescription" : "Este é o atSign associado ao seu dispositivo", "deviceName": "Nome do Dispositivo", "deviceNameDescription" : "Este é o nome do seu dispositivo remoto", + "disconnected" : "desconectado", "discord" : "Discord", "done": "Concluído", "edit" : "Editar", @@ -32,12 +34,14 @@ "export" : "Exportar", "exportLogs" : "Exportar Registros", "failed": "Falhou", + "failedToLoad" : "falha ao carregar", "faq" : "faq", "feedback" : "Feedback", "getStarted" : "Começar", "import" : "Importar", "json" : "JSON", "language" : "Idioma", + "loading" : "carregando", "localPort" : "Porta Local", "localPortDescription" : "", "logs" : "Registros", @@ -64,6 +68,7 @@ "profileName" : "Nome do Perfil", "profileNameDescription" : "Este será o nome das suas configurações", "profileRunningActionDeniedMessage" : "Não é possível realizar esta ação enquanto o perfil está em execução", + "quit": "Sair", "refresh" : "Atualizar", "relay" : "Relay", "relayDescription" : "Você pode escolher entre nossos RVs existentes ou criar um novo", @@ -78,9 +83,12 @@ "selectRootDomain" : "Selecione o Domínio Raiz", "serviceMapping" : "Mapeamento de Serviço", "settings" : "Configurações", + "showWindow" : "Mostrar Janela", "signout" : "Sair", "sshStyle" : "Avançado", + "starting" : "iniciando", "status" : "Status", + "stopping" : "parando", "submit" : "Enviar", "validationErrorAtsignField" : "O campo deve ser um atsign válido que começa com @", "validationErrorDeviceNameField" : "O campo só pode conter letras minúsculas, dígitos, sublinhados.", diff --git a/packages/dart/npt_flutter/lib/localization/app_zh.arb b/packages/dart/npt_flutter/lib/localization/app_zh.arb index adffa2b42..d8f8ed6e6 100644 --- a/packages/dart/npt_flutter/lib/localization/app_zh.arb +++ b/packages/dart/npt_flutter/lib/localization/app_zh.arb @@ -1,5 +1,4 @@ { - "addNew" : "新增", "addNewProfile" : "新增配置文件", "advanced": "高级", @@ -15,6 +14,7 @@ "backupYourKey" : "备份您的密钥", "cancel" : "取消", "confirm" : "确认", + "connected" : "已连接", "dashboard" : "仪表板", "dashboardView" : "仪表板视图", "defaultRelaySelection" : "默认中继选择", @@ -23,6 +23,7 @@ "deviceAtsignDescription" : "这是与您设备关联的atSign", "deviceName": "设备名称", "deviceNameDescription" : "这是您的远程设备的名称", + "disconnected" : "断开连接", "discord" : "Discord", "done": "完成", "edit" : "编辑", @@ -33,12 +34,14 @@ "export" : "导出", "exportLogs" : "导出日志", "failed": "失败", + "failedToLoad" : "加载失败", "faq" : "常见问题", "feedback" : "反馈", "getStarted" : "开始", "import" : "导入", "json" : "JSON", "language" : "语言", + "loading" : "加载中", "localPort" : "本地端口", "localPortDescription" : "", "logs" : "日志", @@ -65,6 +68,7 @@ "profileName" : "配置文件名称", "profileNameDescription" : "这将是您的配置名称", "profileRunningActionDeniedMessage" : "配置文件运行时无法执行此操作", + "quit": "退出", "refresh" : "刷新", "relay" : "中继", "relayDescription" : "您可以从我们现有的RV中选择或创建一个新的", @@ -79,9 +83,12 @@ "selectRootDomain" : "选择根域", "serviceMapping" : "服务映射", "settings" : "设置", + "showWindow" : "显示窗口", "signout" : "登出", "sshStyle" : "高级", + "starting" : "启动中", "status" : "状态", + "stopping" : "停止中", "submit" : "提交", "validationErrorAtsignField" : "字段必须是以@开头的有效atSign", "validationErrorDeviceNameField" : "字段只能包含小写字母、数字、下划线。", @@ -91,5 +98,4 @@ "validationErrorRemoteHostField" : "字段必须是部分或完全合格的主机名或IP地址", "validationErrorRemotePortField" : "字段必须在1-65535之间", "yaml" : "YAML" - } diff --git a/packages/dart/npt_flutter/lib/localization/app_zh_Hans_CH.arb b/packages/dart/npt_flutter/lib/localization/app_zh_Hans_CH.arb index 912ebd1f9..a30dfc586 100644 --- a/packages/dart/npt_flutter/lib/localization/app_zh_Hans_CH.arb +++ b/packages/dart/npt_flutter/lib/localization/app_zh_Hans_CH.arb @@ -15,6 +15,7 @@ "backupYourKey" : "备份您的密钥", "cancel" : "取消", "confirm" : "确认", + "connected" : "已连接", "dashboard" : "仪表板", "dashboardView" : "仪表板视图", "defaultRelaySelection" : "默认中继选择", @@ -23,6 +24,7 @@ "deviceAtsignDescription" : "这是与您设备关联的atSign", "deviceName": "设备名称", "deviceNameDescription" : "这是您的远程设备的名称", + "disconnected" : "断开连接", "discord" : "Discord", "done": "完成", "edit" : "编辑", @@ -33,12 +35,14 @@ "export" : "导出", "exportLogs" : "导出日志", "failed": "失败", + "failedToLoad" : "加载失败", "faq" : "常见问题", "feedback" : "反馈", "getStarted" : "开始", "import" : "导入", "json" : "JSON", "language" : "语言", + "loading" : "加载中", "localPort" : "本地端口", "localPortDescription" : "", "logs" : "日志", @@ -65,6 +69,7 @@ "profileName" : "配置文件名称", "profileNameDescription" : "这将是您的配置名称", "profileRunningActionDeniedMessage" : "配置文件运行时无法执行此操作", + "quit": "退出", "refresh" : "刷新", "relay" : "中继", "relayDescription" : "您可以从我们现有的RV中选择或创建一个新的", @@ -79,9 +84,12 @@ "selectRootDomain" : "选择根域", "serviceMapping" : "服务映射", "settings" : "设置", + "showWindow" : "显示窗口", "signout" : "登出", "sshStyle" : "高级", + "starting" : "启动中", "status" : "状态", + "stopping" : "停止中", "submit" : "提交", "validationErrorAtsignField" : "字段必须是以@开头的有效atSign", "validationErrorDeviceNameField" : "字段只能包含小写字母、数字、下划线。", diff --git a/packages/dart/npt_flutter/lib/localization/app_zh_Hant_HK.arb b/packages/dart/npt_flutter/lib/localization/app_zh_Hant_HK.arb index dc3a34968..c1e17f5d0 100644 --- a/packages/dart/npt_flutter/lib/localization/app_zh_Hant_HK.arb +++ b/packages/dart/npt_flutter/lib/localization/app_zh_Hant_HK.arb @@ -14,6 +14,7 @@ "backupYourKey" : "備份你的密鑰", "cancel" : "取消", "confirm" : "確認", + "connected" : "已連接", "dashboard" : "儀表板", "dashboardView" : "儀表板視圖", "defaultRelaySelection" : "默認中繼選擇", @@ -22,6 +23,7 @@ "deviceAtsignDescription" : "這是與你設備相關聯的atSign", "deviceName": "設備名稱", "deviceNameDescription" : "這是你的遠程設備的名稱", + "disconnected" : "已斷開", "discord" : "Discord", "done": "完成", "edit" : "編輯", @@ -32,12 +34,14 @@ "export" : "導出", "exportLogs" : "導出日誌", "failed": "失敗", + "failedToLoad" : "加載失敗", "faq" : "常見問題", "feedback" : "反饋", "getStarted" : "開始", "import" : "導入", "json" : "JSON", "language" : "語言", + "loading" : "正在加載", "localPort" : "本地端口", "localPortDescription" : "", "logs" : "日誌", @@ -64,6 +68,7 @@ "profileName" : "配置文件名稱", "profileNameDescription" : "這將是你的配置名稱", "profileRunningActionDeniedMessage" : "配置文件運行時無法執行此操作", + "quit": "退出", "refresh" : "刷新", "relay" : "中繼", "relayDescription" : "你可以從我們現有的RV中選擇或創建一個新的", @@ -78,9 +83,12 @@ "selectRootDomain" : "選擇根域", "serviceMapping" : "服務映射", "settings" : "設置", + "showWindow" : "顯示窗口", "signout" : "登出", "sshStyle" : "高級", + "starting" : "正在啟動", "status" : "狀態", + "stopping" : "正在停止", "submit" : "提交", "validationErrorAtsignField" : "字段必須是以@開頭的有效atSign", "validationErrorDeviceNameField" : "字段只能包含小寫字母、數字、下劃線。", From 4178ab0abbdda35b03e5eaf337eab678e9216a73 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Wed, 6 Nov 2024 14:43:07 -0500 Subject: [PATCH 2/4] fix: reload tray on language change properly --- packages/dart/npt_flutter/lib/app.dart | 12 +-- .../tray_manager/cubit/tray_cubit.dart | 99 ++++++++++--------- .../tray_manager/widgets/tray_manager.dart | 29 +++++- 3 files changed, 81 insertions(+), 59 deletions(-) diff --git a/packages/dart/npt_flutter/lib/app.dart b/packages/dart/npt_flutter/lib/app.dart index 56170cf10..d548a4caa 100644 --- a/packages/dart/npt_flutter/lib/app.dart +++ b/packages/dart/npt_flutter/lib/app.dart @@ -101,19 +101,15 @@ class App extends StatelessWidget { return null; }, builder: (context, language) { - Locale? defaultLocal; - if (language == null) { - //check if the device language is supported, if not use english as the default. - - defaultLocal = LanguageUtil.getLanguageFromLocale(Locale(Platform.localeName)).locale; - } - + Locale locale = language?.locale ?? LanguageUtil.getLanguageFromLocale(Locale(Platform.localeName)).locale; return TrayManager( + locale: locale, child: MaterialApp( + key: const Key("MaterialApp"), theme: AppTheme.light(), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, - locale: language != null ? language.locale : defaultLocal, + locale: locale, localeResolutionCallback: (locale, supportedLocales) { return language != null ? language.locale : locale; }, diff --git a/packages/dart/npt_flutter/lib/features/tray_manager/cubit/tray_cubit.dart b/packages/dart/npt_flutter/lib/features/tray_manager/cubit/tray_cubit.dart index 3ed3c2beb..ea2bbbc14 100644 --- a/packages/dart/npt_flutter/lib/features/tray_manager/cubit/tray_cubit.dart +++ b/packages/dart/npt_flutter/lib/features/tray_manager/cubit/tray_cubit.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:npt_flutter/app.dart'; import 'package:npt_flutter/constants.dart'; @@ -15,34 +16,6 @@ import 'package:window_manager/window_manager.dart'; part 'tray_cubit.g.dart'; part 'tray_state.dart'; -(String, void Function(MenuItem)) getAction(TrayAction action) => switch (action) { - TrayAction.showDashboard => ('Show Window', (_) => windowManager.focus()), - TrayAction.showSettings => ( - 'Settings', - (_) { - windowManager.focus().then((_) { - var context = App.navState.currentContext; - if (context == null) return; - if (context.mounted) { - var cubit = context.read(); - if (cubit.getStatus() != OnboardingStatus.onboarded) return; - Navigator.of(context).pushNamedAndRemoveUntil( - Routes.settings, - (route) => route.isFirst, - ); - } - }); - } - ), - TrayAction.quitApp => ( - 'Quit', - (_) async { - await windowManager.destroy(); - exit(0); - } - ), - }; - @JsonEnum(alwaysCreate: true) enum TrayAction { showDashboard, @@ -52,22 +25,13 @@ enum TrayAction { static bool isTrayAction(String key) { return _$TrayActionEnumMap.values.contains(key); } - - MenuItem get menuItem { - final (label, callback) = getAction(this); - return MenuItem( - key: _$TrayActionEnumMap[this], - label: label, - onClick: callback, - ); - } } class TrayCubit extends LoggingCubit { TrayCubit() : super(const TrayInitial()); - Future initialize() async { - if (state is! TrayInitial) return; + Future initialize({AppLocalizations? localizations}) async { + if (state is! TrayInitial || localizations == null) return; var context = App.navState.currentContext; if (context == null) return; var showSettings = context.read().getStatus() == OnboardingStatus.onboarded; @@ -76,9 +40,9 @@ class TrayCubit extends LoggingCubit { await trayManager.setContextMenu(Menu( items: [ - TrayAction.showDashboard.menuItem, - if (showSettings) TrayAction.showSettings.menuItem, - TrayAction.quitApp.menuItem, + _getMenuItem(TrayAction.showDashboard, localizations), + if (showSettings) _getMenuItem(TrayAction.showSettings, localizations), + _getMenuItem(TrayAction.quitApp, localizations), ], )); emit(const TrayLoaded()); @@ -92,10 +56,51 @@ class TrayCubit extends LoggingCubit { }); } - Future reload() async { + MenuItem _getMenuItem(TrayAction action, AppLocalizations localizations) { + final (label, callback) = _getAction(action, localizations); + return MenuItem( + key: _$TrayActionEnumMap[action], + label: label, + onClick: callback, + ); + } + + (String, void Function(MenuItem)) _getAction(TrayAction action, AppLocalizations localizations) { + return switch (action) { + TrayAction.showDashboard => (localizations.showWindow, (_) => windowManager.focus()), + TrayAction.showSettings => ( + localizations.settings, + (_) { + windowManager.focus().then((_) { + var context = App.navState.currentContext; + if (context == null) return; + if (context.mounted) { + var cubit = context.read(); + if (cubit.getStatus() != OnboardingStatus.onboarded) return; + Navigator.of(context).pushNamedAndRemoveUntil( + Routes.settings, + (route) => route.isFirst, + ); + } + }); + } + ), + TrayAction.quitApp => ( + localizations.quit, + (_) async { + await windowManager.destroy(); + exit(0); + } + ), + }; + } + + Future reload({AppLocalizations? localizations}) async { var context = App.navState.currentContext; if (context == null) return; - var init = initialize(); + localizations ??= AppLocalizations.of(context); + if (localizations == null) return; + var init = initialize(localizations: localizations); /// Access the context before any awaited function calls var showSettings = context.read().getStatus() == OnboardingStatus.onboarded; @@ -138,9 +143,9 @@ class TrayCubit extends LoggingCubit { items: [ ...favMenuItems, MenuItem.separator(), - TrayAction.showDashboard.menuItem, - if (showSettings) TrayAction.showSettings.menuItem, - TrayAction.quitApp.menuItem, + _getMenuItem(TrayAction.showDashboard, localizations), + if (showSettings) _getMenuItem(TrayAction.showSettings, localizations), + _getMenuItem(TrayAction.quitApp, localizations), ], )); emit(TrayLoaded(favorites: favorites)); diff --git a/packages/dart/npt_flutter/lib/features/tray_manager/widgets/tray_manager.dart b/packages/dart/npt_flutter/lib/features/tray_manager/widgets/tray_manager.dart index 2bff4d109..76a980b63 100644 --- a/packages/dart/npt_flutter/lib/features/tray_manager/widgets/tray_manager.dart +++ b/packages/dart/npt_flutter/lib/features/tray_manager/widgets/tray_manager.dart @@ -1,11 +1,14 @@ import 'package:flutter/scheduler.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:npt_flutter/app.dart'; import 'package:npt_flutter/features/favorite/favorite.dart'; import 'package:npt_flutter/features/profile/profile.dart'; import 'package:npt_flutter/features/profile_list/profile_list.dart'; +import 'package:npt_flutter/features/settings/settings.dart'; import 'package:npt_flutter/features/tray_manager/tray_manager.dart'; +import 'package:npt_flutter/util/language.dart'; import 'package:tray_manager/tray_manager.dart'; import 'package:window_manager/window_manager.dart'; @@ -13,7 +16,8 @@ import 'package:window_manager/window_manager.dart'; /// It wraps the whole [MaterialApp] so that it can be used from anywhere class TrayManager extends StatefulWidget { final Widget child; - const TrayManager({required this.child, super.key}); + final Locale locale; + const TrayManager({required this.child, required this.locale, super.key}); @override State createState() => _TrayManagerState(); @@ -22,15 +26,21 @@ class TrayManager extends StatefulWidget { class _TrayManagerState extends State with TrayListener, WindowListener { /// Must strongly type [context] here or Dart will infer the wrong type for /// the [.read()] extension which causes an error - void reloadTray(BuildContext context, _) { - context.read().reload(); + void reloadTray(BuildContext context, Loggable state) async { + var cubit = context.read(); + if (state is SettingsLoadedState) { + var localizations = await AppLocalizations.delegate.load(state.settings.language.locale); + cubit.reload(localizations: localizations); + } else { + cubit.reload(localizations: AppLocalizations.of(context)); + } } @override Widget build(BuildContext context) { var trayCubit = context.read(); if (trayCubit.state is TrayInitial) { - trayCubit.initialize(); + trayCubit.initialize(localizations: AppLocalizations.of(context)); } var profileCacheCubit = context.read(); @@ -55,6 +65,17 @@ class _TrayManagerState extends State with TrayListener, WindowList BlocListener( listener: reloadTray, ), + BlocListener( + listener: reloadTray, + // Only call listener when the language changes in settings + listenWhen: (prev, next) { + if (prev is SettingsLoadedState && next is SettingsLoadedState) { + return prev.settings.language != next.settings.language; + } + // This may cause some extra reloading (very occasionally, settings shouldn't change often) + // but it should catch all of the edge cases + return prev is SettingsLoadedState || next is SettingsLoadedState; + }), /// Yeah I really hate this... an indefinite list of listeners /// but it's the only way to decouple the profiles from having to know From 63b1ea8aeccfebda9dab3d9e6fad4ea1b3e0fb33 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Wed, 6 Nov 2024 15:11:56 -0500 Subject: [PATCH 3/4] fix: reload tray with immediate state instead of trying to grab from possibly old context --- .../features/favorite/models/favorite.dart | 22 +++++----- .../tray_manager/cubit/tray_cubit.dart | 40 ++++++++++++------- .../tray_manager/cubit/tray_state.dart | 7 ++-- .../tray_manager/widgets/tray_manager.dart | 19 ++++++--- 4 files changed, 56 insertions(+), 32 deletions(-) diff --git a/packages/dart/npt_flutter/lib/features/favorite/models/favorite.dart b/packages/dart/npt_flutter/lib/features/favorite/models/favorite.dart index 5b17a6261..16b89f012 100644 --- a/packages/dart/npt_flutter/lib/features/favorite/models/favorite.dart +++ b/packages/dart/npt_flutter/lib/features/favorite/models/favorite.dart @@ -22,7 +22,7 @@ enum FavoriteType { } } -sealed class Favorite extends Loggable { +sealed class Favorite extends Loggable { final String uuid; final FavoriteType type; @@ -30,6 +30,8 @@ sealed class Favorite extends Loggable { String? get status; Iterable get profileIds; + String resolveStatus(T state); + /// These 4 functions are for interacting with favorites in the tray, /// since the implementation may differ by type of favoritable bool isFavoriteMatch(Favoritable favoritable); @@ -61,9 +63,8 @@ sealed class Favorite extends Loggable { } @JsonSerializable() -class FavoriteProfile extends Favorite { - const FavoriteProfile({required super.uuid}) - : super(type: FavoriteType.profile); +class FavoriteProfile extends Favorite { + const FavoriteProfile({required super.uuid}) : super(type: FavoriteType.profile); @override List get props => [uuid]; @@ -73,8 +74,7 @@ class FavoriteProfile extends Favorite { return 'FavoriteProfile(uuid: $uuid)'; } - factory FavoriteProfile.fromJson(Map json) => - _$FavoriteProfileFromJson(json); + factory FavoriteProfile.fromJson(Map json) => _$FavoriteProfileFromJson(json); @override Map toJson() { @@ -98,11 +98,15 @@ class FavoriteProfile extends Favorite { var context = App.navState.currentContext; if (context == null) return null; var bloc = context.read().getProfileBloc(uuid); - return switch (bloc.state) { + return resolveStatus(bloc.state); + } + + @override + String resolveStatus(ProfileState state) { + return switch (state) { ProfileLoaded _ || ProfileFailedSave _ || ProfileFailedStart _ => '[Off]', ProfileStarting _ => '[Starting]', - ProfileStarted _ => - '[On - ${(bloc.state as ProfileLoadedState).profile.localPort}]', + ProfileStarted _ => '[On - ${(state as ProfileLoadedState).profile.localPort}]', ProfileStopping _ => '[Stopping]', ProfileInitial _ || ProfileLoading _ => '[Loading]', ProfileFailedLoad _ => '[Failed to load]' diff --git a/packages/dart/npt_flutter/lib/features/tray_manager/cubit/tray_cubit.dart b/packages/dart/npt_flutter/lib/features/tray_manager/cubit/tray_cubit.dart index ea2bbbc14..fe6350c73 100644 --- a/packages/dart/npt_flutter/lib/features/tray_manager/cubit/tray_cubit.dart +++ b/packages/dart/npt_flutter/lib/features/tray_manager/cubit/tray_cubit.dart @@ -8,6 +8,7 @@ import 'package:npt_flutter/app.dart'; import 'package:npt_flutter/constants.dart'; import 'package:npt_flutter/features/favorite/favorite.dart'; import 'package:npt_flutter/features/onboarding/onboarding.dart'; +import 'package:npt_flutter/features/profile/profile.dart'; import 'package:npt_flutter/features/profile_list/profile_list.dart'; import 'package:npt_flutter/routes.dart'; import 'package:tray_manager/tray_manager.dart'; @@ -95,35 +96,46 @@ class TrayCubit extends LoggingCubit { }; } - Future reload({AppLocalizations? localizations}) async { + Future reload({ + AppLocalizations? localizations, + FavoritesState? favoriteState, + ProfileListState? profileListState, + ProfilesRunningState? profilesRunningState, + OnboardingState? onboardingState, + ProfileState? profileState, + }) async { var context = App.navState.currentContext; if (context == null) return; + localizations ??= AppLocalizations.of(context); if (localizations == null) return; var init = initialize(localizations: localizations); /// Access the context before any awaited function calls - var showSettings = context.read().getStatus() == OnboardingStatus.onboarded; - var favoriteBloc = context.read(); - var profilesList = context.read(); + favoriteState ??= context.read().state; + profileListState ??= context.read().state; + onboardingState ??= context.read().state; + var showSettings = onboardingState.status == OnboardingStatus.onboarded; await init; - /// Get favorites - if (favoriteBloc.state is! FavoritesLoaded) return; - var favorites = (favoriteBloc.state as FavoritesLoaded).favorites; - - /// Get profiles uuid list - if (profilesList.state is! ProfileListLoaded) return; - var profiles = (profilesList.state as ProfileListLoaded).profiles; + // Guard against empty values + if (favoriteState is! FavoritesLoaded) return; + if (profileListState is! ProfileListLoaded) return; /// Generate the new menu based on current state var favMenuItems = await Future.wait( - favorites.where((fav) => fav.isLoadedInProfiles(profiles)).map((fav) async { + favoriteState.favorites + .where((fav) => fav.isLoadedInProfiles((profileListState as ProfileListLoaded).profiles)) + .map((fav) async { /// Make sure to call [e.displayName] and [e.isRunning] only once to /// ensure good performance - these getters call a bunch of nested /// information from elsewhere in the app state - var displayName = await fav.displayName; + + var displayName = (profileState != null && profileState is ProfileLoadedState && profileState.uuid == fav.uuid) + ? profileState.profile.displayName + : await fav.displayName; + var status = fav.status; var label = '$displayName $status'; return MenuItem( @@ -148,6 +160,6 @@ class TrayCubit extends LoggingCubit { _getMenuItem(TrayAction.quitApp, localizations), ], )); - emit(TrayLoaded(favorites: favorites)); + emit(const TrayLoaded()); } } diff --git a/packages/dart/npt_flutter/lib/features/tray_manager/cubit/tray_state.dart b/packages/dart/npt_flutter/lib/features/tray_manager/cubit/tray_state.dart index 57c0de5ab..7ed12610a 100644 --- a/packages/dart/npt_flutter/lib/features/tray_manager/cubit/tray_state.dart +++ b/packages/dart/npt_flutter/lib/features/tray_manager/cubit/tray_state.dart @@ -16,14 +16,13 @@ final class TrayInitial extends TrayState { } final class TrayLoaded extends TrayState { - final Iterable favorites; - const TrayLoaded({this.favorites = const []}); + const TrayLoaded(); @override - List get props => [favorites]; + List get props => []; @override String toString() { - return 'TrayLoaded(favorites: $favorites)'; + return 'TrayLoaded'; } } diff --git a/packages/dart/npt_flutter/lib/features/tray_manager/widgets/tray_manager.dart b/packages/dart/npt_flutter/lib/features/tray_manager/widgets/tray_manager.dart index 76a980b63..84b9a9e40 100644 --- a/packages/dart/npt_flutter/lib/features/tray_manager/widgets/tray_manager.dart +++ b/packages/dart/npt_flutter/lib/features/tray_manager/widgets/tray_manager.dart @@ -28,11 +28,20 @@ class _TrayManagerState extends State with TrayListener, WindowList /// the [.read()] extension which causes an error void reloadTray(BuildContext context, Loggable state) async { var cubit = context.read(); - if (state is SettingsLoadedState) { - var localizations = await AppLocalizations.delegate.load(state.settings.language.locale); - cubit.reload(localizations: localizations); - } else { - cubit.reload(localizations: AppLocalizations.of(context)); + switch (state) { + case FavoritesState _: + cubit.reload(favoriteState: state); + case ProfileListState _: + cubit.reload(profileListState: state); + case ProfilesRunningState _: + cubit.reload(profilesRunningState: state); + case SettingsLoadedState _: + var localizations = await AppLocalizations.delegate.load(state.settings.language.locale); + cubit.reload(localizations: localizations); + case ProfileState _: + cubit.reload(profileState: state); + default: + cubit.reload(localizations: AppLocalizations.of(context)); } } From 5d398449ccf0caa436066776a8a7157babba0147 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Wed, 6 Nov 2024 15:13:34 -0500 Subject: [PATCH 4/4] chore: bump dependency --- packages/dart/npt_flutter/pubspec.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/dart/npt_flutter/pubspec.lock b/packages/dart/npt_flutter/pubspec.lock index 6da893626..3a7b544e4 100644 --- a/packages/dart/npt_flutter/pubspec.lock +++ b/packages/dart/npt_flutter/pubspec.lock @@ -1549,10 +1549,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.4" + version: "14.2.5" watcher: dependency: transitive description: