From a816cf8f652d6f22ed435bf72d7600215edb44f8 Mon Sep 17 00:00:00 2001 From: JHubi1 Date: Tue, 3 Sep 2024 17:08:25 +0200 Subject: [PATCH] Check for update on app start, improved theme --- lib/l10n/app_en.arb | 20 +++++ lib/main.dart | 42 +++++++--- lib/screen_settings.dart | 159 ++++++++++++++++++++---------------- lib/settings/about.dart | 17 ++-- lib/settings/export.dart | 11 ++- lib/settings/interface.dart | 9 +- lib/worker/setter.dart | 12 ++- lib/worker/update.dart | 35 ++++---- untranslated_messages.json | 16 ++++ 9 files changed, 213 insertions(+), 108 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index cd9eea2..832870d 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -110,6 +110,11 @@ "description": "Tooltip for add host headers button", "context": "Visible in settings view" }, + "tooltipReset": "Reset current chat", + "@tooltipReset": { + "description": "Tooltip for reset button", + "context": "Visible in the chat view" + }, "noModelSelected": "No model selected", "@noModelSelected": { "description": "Text displayed when no model is selected", @@ -328,6 +333,21 @@ "description": "Text displayed when a feature is in beta", "context": "Visible in the settings view" }, + "settingsExperimentalDeprecated": "deprecated", + "@settingsExperimentalDeprecated": { + "description": "Text displayed when a feature is deprecated", + "context": "Visible in the settings view" + }, + "settingsExperimentalDeprecatedDescription": "This feature is deprecated and will be removed in a future version.\nIt may not work as intended or expected. Use at your own risk.", + "@settingsExperimentalDeprecatedDescription": { + "description": "Description of the deprecated feature", + "context": "Visible in the settings view" + }, + "settingsExperimentalDeprecatedFeature": "Deprecated feature, hold to learn more", + "@settingsExperimentalDeprecatedFeature": { + "description": "Text displayed when a feature is deprecated", + "context": "Visible in the settings view" + }, "settingsHost": "Host", "@settingsHost": { "description": "Text displayed as description for host input", diff --git a/lib/main.dart b/lib/main.dart index 746cc2e..e15f264 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -15,6 +15,7 @@ import 'worker/haptic.dart'; import 'worker/sender.dart'; import 'worker/desktop.dart'; import 'worker/theme.dart'; +import 'worker/update.dart'; import 'package:shared_preferences/shared_preferences.dart'; // ignore: depend_on_referenced_packages @@ -33,6 +34,7 @@ import 'package:flutter_tts/flutter_tts.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:dynamic_color/dynamic_color.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:version/version.dart'; // client configuration @@ -75,6 +77,7 @@ bool desktopTitleVisible = true; bool logoVisible = true; bool menuVisible = false; bool sendable = false; +bool updateDetectedOnStart = false; double sidebarIconSize = 1; SpeechToText speech = SpeechToText(); @@ -165,7 +168,9 @@ class _AppState extends State { } return const Locale("en"); }, - title: "Ollama", + onGenerateTitle: (context) { + return AppLocalizations.of(context)!.appTitle; + }, theme: themeLight(), darkTheme: themeDark(), themeMode: themeMode(), @@ -299,9 +304,15 @@ class _MainAppState extends State { child: Padding( padding: const EdgeInsets.only(top: 16, bottom: 16), child: Row(children: [ - const Padding( - padding: EdgeInsets.only(left: 16, right: 12), - child: Icon(Icons.dns_rounded)), + Padding( + padding: const EdgeInsets.only(left: 16, right: 12), + child: (updateStatus == "ok" && + updateDetectedOnStart && + (Version.parse(latestVersion ?? "1.0.0") > + Version.parse( + currentVersion ?? "2.0.0"))) + ? const Badge(child: Icon(Icons.dns_rounded)) + : const Icon(Icons.dns_rounded)), Expanded( child: Text( AppLocalizations.of(context)!.optionSettings, @@ -486,6 +497,8 @@ class _MainAppState extends State { height: 24, width: 24, child: IconButton( + tooltip: AppLocalizations.of(context)! + .tooltipReset, onPressed: () { if (!chatAllowed && chatUuid == @@ -685,14 +698,6 @@ class _MainAppState extends State { })); } - // prefs!.remove("welcomeFinished"); - if (!(prefs!.getBool("welcomeFinished") ?? false) && allowSettings) { - // ignore: use_build_context_synchronously - Navigator.of(context).pushReplacement( - MaterialPageRoute(builder: (context) => const ScreenWelcome())); - return; - } - if (!(allowSettings || useHost)) { // ignore: use_build_context_synchronously resetSystemNavigation(context, @@ -720,6 +725,14 @@ class _MainAppState extends State { }); } + // prefs!.remove("welcomeFinished"); + if (!(prefs!.getBool("welcomeFinished") ?? false) && allowSettings) { + // ignore: use_build_context_synchronously + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (context) => const ScreenWelcome())); + return; + } + if (!allowMultipleChats && (prefs!.getStringList("chats") ?? []).isNotEmpty) { chatUuid = @@ -741,6 +754,11 @@ class _MainAppState extends State { content: Text(AppLocalizations.of(context)!.noHostSelected), showCloseIcon: true)); } + + setState(() {}); + if (prefs!.getBool("checkUpdateOnSettingsOpen") ?? false) { + updateDetectedOnStart = await checkUpdate(setState); + } }, ); } diff --git a/lib/screen_settings.dart b/lib/screen_settings.dart index 29a54a3..a754c8c 100644 --- a/lib/screen_settings.dart +++ b/lib/screen_settings.dart @@ -21,6 +21,7 @@ import 'package:http/http.dart' as http; import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:dynamic_color/dynamic_color.dart'; import 'package:transparent_image/transparent_image.dart'; +import 'package:version/version.dart'; Widget toggle(BuildContext context, String text, bool value, Function(bool value) onChanged, @@ -158,6 +159,7 @@ Widget button(String text, IconData? icon, void Function()? onPressed, bool onlyDesktopDescription = true, bool alwaysMobileDescription = false, String? badge, + String? iconBadge, void Function()? onDisabledTap, void Function()? onLongTap, void Function()? onDoubleTap}) { @@ -202,65 +204,72 @@ Widget button(String text, IconData? icon, void Function()? onPressed, borderRadius: BorderRadius.circular(8), child: Padding( padding: const EdgeInsets.all(8), - child: Row(children: [ - (icon != null || replaceIconIfNull) + child: Builder(builder: (context) { + var iconContent = (icon != null || replaceIconIfNull) ? replaceIconIfNull ? ImageIcon(MemoryImage(kTransparentImage)) : Icon(icon, color: disabled ? Colors.grey : color) - : const SizedBox.shrink(), - (icon != null || replaceIconIfNull) - ? const SizedBox(width: 16, height: 42) - : const SizedBox.shrink(), - Expanded(child: Builder(builder: (context) { - Widget textWidget = Text(text, - style: TextStyle(color: disabled ? Colors.grey : color)); - if (badge != null) { - textWidget = Badge( - label: Text(badge), - offset: const Offset(20, -4), - backgroundColor: Theme.of(context).colorScheme.primary, - textColor: Theme.of(context).colorScheme.onPrimary, - child: textWidget); - } - if (description == null || description!.startsWith("\n")) { - description = description?.removePrefix("\n"); - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - textWidget, - (description != null && - !alwaysMobileDescription && - (desktopLayoutNotRequired(context) || - !onlyDesktopDescription)) - ? Text(description!, - style: const TextStyle( - color: Colors.grey, - overflow: TextOverflow.ellipsis)) - : const SizedBox.shrink() - ]); - } else { - return Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - textWidget, - (description != null && - !alwaysMobileDescription && - (desktopLayoutNotRequired(context) || - !onlyDesktopDescription)) - ? Expanded( - child: Text(description!, - style: const TextStyle( - color: Colors.grey, - overflow: TextOverflow.ellipsis)), - ) - : const SizedBox.shrink() - ]); - } - })) - ]), + : const SizedBox.shrink(); + return Row(children: [ + (iconBadge == null) + ? iconContent + : Badge( + label: (iconBadge != "") ? Text(iconBadge) : null, + child: iconContent), + (icon != null || replaceIconIfNull) + ? const SizedBox(width: 16, height: 42) + : const SizedBox.shrink(), + Expanded(child: Builder(builder: (context) { + Widget textWidget = Text(text, + style: TextStyle(color: disabled ? Colors.grey : color)); + if (badge != null) { + textWidget = Badge( + label: Text(badge), + offset: const Offset(20, -4), + backgroundColor: Theme.of(context).colorScheme.primary, + textColor: Theme.of(context).colorScheme.onPrimary, + child: textWidget); + } + if (description == null || description!.startsWith("\n")) { + description = description?.removePrefix("\n"); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + textWidget, + (description != null && + !alwaysMobileDescription && + (desktopLayoutNotRequired(context) || + !onlyDesktopDescription)) + ? Text(description!, + style: const TextStyle( + color: Colors.grey, + overflow: TextOverflow.ellipsis)) + : const SizedBox.shrink() + ]); + } else { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + textWidget, + (description != null && + !alwaysMobileDescription && + (desktopLayoutNotRequired(context) || + !onlyDesktopDescription)) + ? Expanded( + child: Text(description!, + style: const TextStyle( + color: Colors.grey, + overflow: TextOverflow.ellipsis)), + ) + : const SizedBox.shrink() + ]); + } + })) + ]); + }), )), ); } @@ -353,7 +362,6 @@ class _ScreenSettingsState extends State { fixedHost)) { checkHost(); } - updatesSupported(setState, true); } @override @@ -608,19 +616,30 @@ class _ScreenSettingsState extends State { context: context, description: "\n${AppLocalizations.of(context)!.settingsDescriptionExport}"), - button( - AppLocalizations.of(context)!.settingsTitleAbout, - Icons.help_rounded, () { - selectionHaptic(); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - const ScreenSettingsAbout())); - }, - context: context, - description: - "\n${AppLocalizations.of(context)!.settingsDescriptionAbout}") + Builder(builder: (context) { + return button( + AppLocalizations.of(context)! + .settingsTitleAbout, + Icons.help_rounded, () { + selectionHaptic(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + const ScreenSettingsAbout())); + }, + context: context, + description: + "\n${AppLocalizations.of(context)!.settingsDescriptionAbout}", + iconBadge: (updateStatus == "ok" && + updateDetectedOnStart && + (Version.parse( + latestVersion ?? "1.0.0") > + Version.parse( + currentVersion ?? "2.0.0"))) + ? "" + : null); + }) ]); animatedDesktop = desktopLayoutNotRequired(context); return Column(children: [ diff --git a/lib/settings/about.dart b/lib/settings/about.dart index 849bfa1..82466f0 100644 --- a/lib/settings/about.dart +++ b/lib/settings/about.dart @@ -26,9 +26,6 @@ class _ScreenSettingsAboutState extends State { WidgetsFlutterBinding.ensureInitialized(); updatesSupported(setState, true); setState(() {}); - if (prefs!.getBool("checkUpdateOnSettingsOpen") ?? false) { - checkUpdate(setState); - } } @override @@ -75,12 +72,10 @@ class _ScreenSettingsAboutState extends State { Version.parse( currentVersion ?? "2.0.0")) - ? AppLocalizations.of( - context)! + ? AppLocalizations.of(context)! .settingsUpdateAvailable( latestVersion!) - : AppLocalizations.of( - context)! + : AppLocalizations.of(context)! .settingsUpdateLatest), ((updateStatus != "ok") ? Icons.warning_rounded @@ -100,7 +95,13 @@ class _ScreenSettingsAboutState extends State { checkUpdate(setState); return; } - }), + }, + iconBadge: (updateStatus == "ok" && + updateDetectedOnStart && + (Version.parse(latestVersion ?? "1.0.0") > + Version.parse(currentVersion ?? "2.0.0"))) + ? "" + : null), (updateStatus == "notAvailable") ? const SizedBox.shrink() : toggle( diff --git a/lib/settings/export.dart b/lib/settings/export.dart index 955eafd..6d2a082 100644 --- a/lib/settings/export.dart +++ b/lib/settings/export.dart @@ -8,6 +8,7 @@ import 'package:flutter/foundation.dart'; import '../main.dart'; import '../worker/haptic.dart'; import '../worker/desktop.dart'; +import '../worker/theme.dart'; import '../screen_settings.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -101,9 +102,13 @@ class _ScreenSettingsExportState extends State { allowMultipleChats ? button( AppLocalizations.of(context)!.settingsImportChats, - Icons.download_rounded, () { + Icons.download_rounded, () async { selectionHaptic(); - showDialog( + resetSystemNavigation(context, + systemNavigationBarColor: Color.alphaBlend( + Colors.black54, + Theme.of(context).colorScheme.surface)); + await showDialog( context: context, builder: (context) { return AlertDialog( @@ -214,6 +219,8 @@ class _ScreenSettingsExportState extends State { .settingsImportChatsImport)) ]); }); + // ignore: use_build_context_synchronously + resetSystemNavigation(context); }) : const SizedBox.shrink() ]), diff --git a/lib/settings/interface.dart b/lib/settings/interface.dart index 04b7251..cda3189 100644 --- a/lib/settings/interface.dart +++ b/lib/settings/interface.dart @@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart'; import '../main.dart'; import '../worker/haptic.dart'; import '../worker/desktop.dart'; +import '../worker/theme.dart'; import '../screen_settings.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -190,6 +191,10 @@ class _ScreenSettingsInterfaceState extends State { .settingsKeepModelLoadedFor, Icons.snooze_rounded, () async { selectionHaptic(); + resetSystemNavigation(context, + systemNavigationBarColor: Color.alphaBlend( + Colors.black54, + Theme.of(context).colorScheme.surface)); bool loaded = false; await showDialog( context: context, @@ -276,6 +281,8 @@ class _ScreenSettingsInterfaceState extends State { ); })); }); + // ignore: use_build_context_synchronously + resetSystemNavigation(context); }), titleDivider(context: context), button( @@ -307,7 +314,7 @@ class _ScreenSettingsInterfaceState extends State { null, onlyDesktopDescription: false, description: - "\n${(prefs!.getDouble("timeoutMultiplier") ?? 1)} x 30s = ${((prefs!.getDouble("timeoutMultiplier") ?? 1) * 30).round()}s ${secondsBeautify((prefs!.getDouble("timeoutMultiplier") ?? 1) * 30)}"), + "\n${((prefs!.getDouble("timeoutMultiplier") ?? 1) == 10) ? "${(prefs!.getDouble("timeoutMultiplier") ?? 1).round()}." : (prefs!.getDouble("timeoutMultiplier") ?? 1)} x 30s = ${((prefs!.getDouble("timeoutMultiplier") ?? 1) * 30).round()}s ${secondsBeautify((prefs!.getDouble("timeoutMultiplier") ?? 1) * 30)}"), titleDivider(context: context), toggle( context, diff --git a/lib/worker/setter.dart b/lib/worker/setter.dart index 5218beb..2fe4ea8 100644 --- a/lib/worker/setter.dart +++ b/lib/worker/setter.dart @@ -366,6 +366,9 @@ void addModel(BuildContext context, Function setState) async { } if (response.statusCode == 200) { bool returnValue = false; + resetSystemNavigation(mainContext!, + systemNavigationBarColor: Color.alphaBlend( + Colors.black54, Theme.of(mainContext!).colorScheme.surface)); await showDialog( context: mainContext!, barrierDismissible: false, @@ -392,6 +395,7 @@ void addModel(BuildContext context, Function setState) async { .modelDialogAddAssuranceAdd)) ]); }); + resetSystemNavigation(mainContext!); return returnValue; } return false; @@ -665,8 +669,12 @@ Future deleteChatDialog(BuildContext context, Function setState, } if ((prefs!.getBool("askBeforeDeletion") ?? false) && additionalCondition) { + // ignore: use_build_context_synchronously resetSystemNavigation(context, - systemNavigationBarColor: Colors.grey.withOpacity(0.2)); + systemNavigationBarColor: Color.alphaBlend( + Colors.black54, + // ignore: use_build_context_synchronously + Theme.of(context).colorScheme.surface)); await showDialog( context: context, builder: (context) { @@ -695,6 +703,8 @@ Future deleteChatDialog(BuildContext context, Function setState, ]); }); }); + // ignore: use_build_context_synchronously + resetSystemNavigation(context); } else { delete(context); } diff --git a/lib/worker/update.dart b/lib/worker/update.dart index b928c15..ae7f661 100644 --- a/lib/worker/update.dart +++ b/lib/worker/update.dart @@ -1,9 +1,11 @@ +import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:ollama_app/worker/desktop.dart'; import 'haptic.dart'; +import 'theme.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import '../main.dart'; @@ -13,6 +15,7 @@ import 'package:install_referrer/install_referrer.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:version/version.dart'; const repoUrl = "https://github.com/JHubi1/ollama-app"; @@ -25,10 +28,6 @@ String? currentVersion; String? updateChangeLog; Future updatesSupported(Function setState, [bool takeAction = false]) async { - var tmp = (await PackageInfo.fromPlatform()).version; - setState(() { - currentVersion = tmp; - }); bool returnValue = true; var installerApps = [ "org.fdroid.fdroid", @@ -45,10 +44,10 @@ Future updatesSupported(Function setState, (installerApps .contains((await InstallReferrer.app).packageName ?? ""))) { returnValue = false; - // if (await InstallReferrer.referrer == - // InstallationAppReferrer.androidDebug) { - // returnValue = true; - // } + if (await InstallReferrer.referrer == + InstallationAppReferrer.androidDebug) { + returnValue = true; + } } if (!repoUrl.startsWith("https://github.com")) { returnValue = false; @@ -64,7 +63,7 @@ Future updatesSupported(Function setState, return returnValue; } -void checkUpdate(Function setState) async { +Future checkUpdate(Function setState) async { try { setState(() { updateChecked = true; @@ -76,7 +75,7 @@ void checkUpdate(Function setState) async { updateStatus = "notAvailable"; updateLoading = false; }); - return; + return false; } var repo = repoUrl.split("/"); @@ -98,7 +97,7 @@ void checkUpdate(Function setState) async { updateStatus = "rateLimit"; updateLoading = false; }); - return; + return false; } version = jsonDecode(request.body)[0]["tag_name"]; updateChangeLog = jsonDecode(request.body)[0]["body"]; @@ -108,7 +107,7 @@ void checkUpdate(Function setState) async { updateStatus = "error"; updateLoading = false; }); - return; + return false; } latestVersion = version; @@ -123,10 +122,16 @@ void checkUpdate(Function setState) async { updateLoading = false; }); } + return (updateStatus == "ok" && + (Version.parse(latestVersion ?? "1.0.0") > + Version.parse(currentVersion ?? "2.0.0"))); } -void updateDialog(BuildContext context, Function title) { - showDialog( +void updateDialog(BuildContext context, Function title) async { + resetSystemNavigation(context, + systemNavigationBarColor: Color.alphaBlend( + Colors.black54, Theme.of(context).colorScheme.surface)); + await showDialog( context: context, builder: (context) { return AlertDialog( @@ -165,4 +170,6 @@ void updateDialog(BuildContext context, Function title) { AppLocalizations.of(context)!.settingsUpdateDialogUpdate)) ]); }); + // ignore: use_build_context_synchronously + resetSystemNavigation(context); } diff --git a/untranslated_messages.json b/untranslated_messages.json index 01b3869..439a607 100644 --- a/untranslated_messages.json +++ b/untranslated_messages.json @@ -2,6 +2,7 @@ "de": [ "deleteChat", "renameChat", + "tooltipReset", "modelDialogAddPromptTitle", "modelDialogAddPromptDescription", "modelDialogAddPromptAlreadyExists", @@ -19,6 +20,9 @@ "settingsDescriptionVoice", "settingsDescriptionExport", "settingsDescriptionAbout", + "settingsExperimentalDeprecated", + "settingsExperimentalDeprecatedDescription", + "settingsExperimentalDeprecatedFeature", "settingsUseSystem", "settingsUseSystemDescription", "settingsPreloadModels", @@ -39,6 +43,7 @@ "it": [ "deleteChat", "renameChat", + "tooltipReset", "modelDialogAddPromptTitle", "modelDialogAddPromptDescription", "modelDialogAddPromptAlreadyExists", @@ -56,6 +61,9 @@ "settingsDescriptionVoice", "settingsDescriptionExport", "settingsDescriptionAbout", + "settingsExperimentalDeprecated", + "settingsExperimentalDeprecatedDescription", + "settingsExperimentalDeprecatedFeature", "settingsUseSystem", "settingsUseSystemDescription", "settingsPreloadModels", @@ -76,6 +84,7 @@ "tr": [ "deleteChat", "renameChat", + "tooltipReset", "modelDialogAddPromptTitle", "modelDialogAddPromptDescription", "modelDialogAddPromptAlreadyExists", @@ -93,6 +102,9 @@ "settingsDescriptionVoice", "settingsDescriptionExport", "settingsDescriptionAbout", + "settingsExperimentalDeprecated", + "settingsExperimentalDeprecatedDescription", + "settingsExperimentalDeprecatedFeature", "settingsUseSystem", "settingsUseSystemDescription", "settingsPreloadModels", @@ -113,6 +125,7 @@ "zh": [ "deleteChat", "renameChat", + "tooltipReset", "modelDialogAddPromptTitle", "modelDialogAddPromptDescription", "modelDialogAddPromptAlreadyExists", @@ -130,6 +143,9 @@ "settingsDescriptionVoice", "settingsDescriptionExport", "settingsDescriptionAbout", + "settingsExperimentalDeprecated", + "settingsExperimentalDeprecatedDescription", + "settingsExperimentalDeprecatedFeature", "settingsUseSystem", "settingsUseSystemDescription", "settingsPreloadModels",