Check for update on app start, improved theme

This commit is contained in:
JHubi1 2024-09-03 17:08:25 +02:00
parent b78ce302b5
commit a816cf8f65
No known key found for this signature in database
GPG Key ID: 7BF82570CBBBD050
9 changed files with 213 additions and 108 deletions

View File

@ -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",

View File

@ -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<App> {
}
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<MainApp> {
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<MainApp> {
height: 24,
width: 24,
child: IconButton(
tooltip: AppLocalizations.of(context)!
.tooltipReset,
onPressed: () {
if (!chatAllowed &&
chatUuid ==
@ -685,14 +698,6 @@ class _MainAppState extends State<MainApp> {
}));
}
// 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<MainApp> {
});
}
// 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<MainApp> {
content: Text(AppLocalizations.of(context)!.noHostSelected),
showCloseIcon: true));
}
setState(() {});
if (prefs!.getBool("checkUpdateOnSettingsOpen") ?? false) {
updateDetectedOnStart = await checkUpdate(setState);
}
},
);
}

View File

@ -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<ScreenSettings> {
fixedHost)) {
checkHost();
}
updatesSupported(setState, true);
}
@override
@ -608,19 +616,30 @@ class _ScreenSettingsState extends State<ScreenSettings> {
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: [

View File

@ -26,9 +26,6 @@ class _ScreenSettingsAboutState extends State<ScreenSettingsAbout> {
WidgetsFlutterBinding.ensureInitialized();
updatesSupported(setState, true);
setState(() {});
if (prefs!.getBool("checkUpdateOnSettingsOpen") ?? false) {
checkUpdate(setState);
}
}
@override
@ -75,12 +72,10 @@ class _ScreenSettingsAboutState extends State<ScreenSettingsAbout> {
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<ScreenSettingsAbout> {
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(

View File

@ -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<ScreenSettingsExport> {
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<ScreenSettingsExport> {
.settingsImportChatsImport))
]);
});
// ignore: use_build_context_synchronously
resetSystemNavigation(context);
})
: const SizedBox.shrink()
]),

View File

@ -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<ScreenSettingsInterface> {
.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<ScreenSettingsInterface> {
);
}));
});
// ignore: use_build_context_synchronously
resetSystemNavigation(context);
}),
titleDivider(context: context),
button(
@ -307,7 +314,7 @@ class _ScreenSettingsInterfaceState extends State<ScreenSettingsInterface> {
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,

View File

@ -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<bool> 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<bool> deleteChatDialog(BuildContext context, Function setState,
]);
});
});
// ignore: use_build_context_synchronously
resetSystemNavigation(context);
} else {
delete(context);
}

View File

@ -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<bool> 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<bool> 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<bool> updatesSupported(Function setState,
return returnValue;
}
void checkUpdate(Function setState) async {
Future<bool> 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);
}

View File

@ -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",