From 27d45add6069738e513ddaf3b65f7b44a7f9b5bd Mon Sep 17 00:00:00 2001 From: JHubi1 Date: Tue, 20 Aug 2024 00:51:03 +0200 Subject: [PATCH 01/13] Fixed html import --- lib/settings/export.dart | 3 +- pubspec.lock | 62 ++++++++++++++++++++++++++++------------ pubspec.yaml | 2 +- 3 files changed, 45 insertions(+), 22 deletions(-) diff --git a/lib/settings/export.dart b/lib/settings/export.dart index fc31956..4ddc9f1 100644 --- a/lib/settings/export.dart +++ b/lib/settings/export.dart @@ -1,7 +1,6 @@ import 'dart:io'; import 'dart:convert'; -// ignore: avoid_web_libraries_in_flutter -import 'dart:html' as html; +import 'package:universal_html/html.dart' as html; import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; diff --git a/pubspec.lock b/pubspec.lock index 175338e..98ac637 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -81,6 +81,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + charcode: + dependency: transitive + description: + name: charcode + sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 + url: "https://pub.dev" + source: hosted + version: "1.3.1" clock: dependency: transitive description: @@ -101,10 +109,10 @@ packages: dependency: transitive description: name: cross_file - sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32" + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" url: "https://pub.dev" source: hosted - version: "0.3.4+1" + version: "0.3.4+2" crypto: dependency: transitive description: @@ -213,10 +221,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: "29c90806ac5f5fb896547720b73b17ee9aed9bba540dc5d91fe29f8c5745b10a" + sha256: "167bb619cdddaa10ef2907609feb8a79c16dfa479d3afaf960f8e223f754bf12" url: "https://pub.dev" source: hosted - version: "8.0.3" + version: "8.1.2" file_selector_linux: dependency: transitive description: @@ -377,10 +385,10 @@ packages: dependency: "direct main" description: name: http - sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" http_parser: dependency: transitive description: @@ -409,10 +417,10 @@ packages: dependency: transitive description: name: image_picker_for_web - sha256: "5d6eb13048cd47b60dbf1a5495424dea226c5faf3950e20bf8120a58efb5b5f3" + sha256: "65d94623e15372c5c51bebbcb820848d7bcb323836e12dfdba60b5d3a8b39e50" url: "https://pub.dev" source: hosted - version: "3.0.4" + version: "3.0.5" image_picker_ios: dependency: transitive description: @@ -577,18 +585,18 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: b93d8b4d624b4ea19b0a5a208b2d6eff06004bc3ce74c06040b120eeadd00ce0 + sha256: a75164ade98cb7d24cfd0a13c6408927c6b217fa60dee5a7ff5c116a58f28918 url: "https://pub.dev" source: hosted - version: "8.0.0" + version: "8.0.2" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: f49918f3433a3146047372f9d4f1f847511f2acd5cd030e1f44fe5a50036b70e + sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66 url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.0.1" path: dependency: transitive description: @@ -761,10 +769,10 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" + sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.2.1" shared_preferences_windows: dependency: transitive description: @@ -898,6 +906,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + universal_html: + dependency: "direct main" + description: + name: universal_html + sha256: "56536254004e24d9d8cfdb7dbbf09b74cf8df96729f38a2f5c238163e3d58971" + url: "https://pub.dev" + source: hosted + version: "2.2.4" + universal_io: + dependency: transitive + description: + name: universal_io + sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" + url: "https://pub.dev" + source: hosted + version: "2.2.2" url_launcher: dependency: "direct main" description: @@ -950,10 +974,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a" + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.3" url_launcher_windows: dependency: transitive description: @@ -1014,10 +1038,10 @@ packages: dependency: transitive description: name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "1.0.0" win32: dependency: transitive description: @@ -1036,4 +1060,4 @@ packages: version: "1.0.4" sdks: dart: ">=3.4.0 <4.0.0" - flutter: ">=3.19.0" + flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index ab19d47..9e3d92a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -40,7 +40,7 @@ dependencies: datetime_loop: ^1.2.0 dynamic_color: ^1.7.0 volume_controller: ^2.0.7 - + universal_html: ^2.2.4 dev_dependencies: flutter_test: sdk: flutter From 9b6259a580ec6e5da80b85bec7f2fb2a08cb71be Mon Sep 17 00:00:00 2001 From: JHubi1 Date: Tue, 20 Aug 2024 01:18:43 +0200 Subject: [PATCH 02/13] Added license page --- lib/l10n/app_en.arb | 6 +++--- lib/settings/about.dart | 36 ++++++++++++++++++++++++++++++------ untranslated_messages.json | 12 ++++++++---- 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index be75d5c..44d817c 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -660,9 +660,9 @@ "description": "Text displayed as description for report issue button", "context": "Visible in the settings view" }, - "settingsMainDeveloper": "Main Developer", - "@settingsMainDeveloper": { - "description": "Text displayed as description for main developer button", + "settingsLicenses": " Licenses", + "@settingsLicenses": { + "description": "Text displayed as description for licenses button", "context": "Visible in the settings view" }, "settingsVersion": "Ollama App v{version}", diff --git a/lib/settings/about.dart b/lib/settings/about.dart index b5b3a81..854bfc2 100644 --- a/lib/settings/about.dart +++ b/lib/settings/about.dart @@ -123,13 +123,37 @@ class _ScreenSettingsAboutState extends State { mode: LaunchMode.inAppBrowserView, Uri.parse("$repoUrl/issues")); }), - button(AppLocalizations.of(context)!.settingsMainDeveloper, - Icons.developer_board_rounded, () { + button(AppLocalizations.of(context)!.settingsLicenses, + Icons.gavel_rounded, () { selectionHaptic(); - launchUrl( - mode: LaunchMode.inAppBrowserView, - Uri.parse( - repoUrl.substring(0, repoUrl.lastIndexOf('/')))); + String legal = "Copyright 2024 JHubi1"; + Widget icon = const Padding( + padding: EdgeInsets.all(16), + child: ImageIcon(AssetImage("assets/logo512.png"), + size: 48), + ); + if (desktopFeature()) { + showDialog( + context: context, + builder: (context) { + return Dialog( + child: ClipRRect( + borderRadius: BorderRadius.circular(28), + child: LicensePage( + applicationName: "Ollama App", + applicationVersion: currentVersion, + applicationIcon: icon, + applicationLegalese: legal), + )); + }); + } else { + showLicensePage( + context: context, + applicationName: "Ollama App", + applicationVersion: currentVersion, + applicationIcon: icon, + applicationLegalese: legal); + } }), const SizedBox(height: 16) ]), diff --git a/untranslated_messages.json b/untranslated_messages.json index c3704c3..685c551 100644 --- a/untranslated_messages.json +++ b/untranslated_messages.json @@ -16,7 +16,8 @@ "settingsTemporaryFixesNoFixes", "settingsVoiceTtsNotSupported", "settingsVoiceTtsNotSupportedDescription", - "settingsVoiceNotEnabled" + "settingsVoiceNotEnabled", + "settingsLicenses" ], "it": [ @@ -36,7 +37,8 @@ "settingsTemporaryFixesNoFixes", "settingsVoiceTtsNotSupported", "settingsVoiceTtsNotSupportedDescription", - "settingsVoiceNotEnabled" + "settingsVoiceNotEnabled", + "settingsLicenses" ], "tr": [ @@ -56,7 +58,8 @@ "settingsTemporaryFixesNoFixes", "settingsVoiceTtsNotSupported", "settingsVoiceTtsNotSupportedDescription", - "settingsVoiceNotEnabled" + "settingsVoiceNotEnabled", + "settingsLicenses" ], "zh": [ @@ -76,6 +79,7 @@ "settingsTemporaryFixesNoFixes", "settingsVoiceTtsNotSupported", "settingsVoiceTtsNotSupportedDescription", - "settingsVoiceNotEnabled" + "settingsVoiceNotEnabled", + "settingsLicenses" ] } From 96d7761f19d762a3dc054167f3caa9e5485b9a86 Mon Sep 17 00:00:00 2001 From: JHubi1 Date: Tue, 20 Aug 2024 01:21:25 +0200 Subject: [PATCH 03/13] Correct unneeded space in translation file --- lib/l10n/app_en.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 44d817c..ed39e7f 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -660,7 +660,7 @@ "description": "Text displayed as description for report issue button", "context": "Visible in the settings view" }, - "settingsLicenses": " Licenses", + "settingsLicenses": "Licenses", "@settingsLicenses": { "description": "Text displayed as description for licenses button", "context": "Visible in the settings view" From 5d68e9893d4eeb821a78bb86813086d3165f6c76 Mon Sep 17 00:00:00 2001 From: JHubi1 Date: Tue, 20 Aug 2024 01:41:56 +0200 Subject: [PATCH 04/13] Added predictive back gesture --- android/app/src/main/AndroidManifest.xml | 3 ++- lib/worker/theme.dart | 21 +++++++++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 1069bb9..a548855 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -2,7 +2,8 @@ + android:icon="@mipmap/ic_launcher" + android:enableOnBackInvokedCallback="true"> { + TargetPlatform.android: PredictiveBackPageTransitionsBuilder(), + }, + )); +} + ThemeData themeLight() { if (!(prefs?.getBool("useDeviceTheme") ?? false) || colorSchemeLight == null) { - return ThemeData.from( + return themeModifier(ThemeData.from( colorScheme: const ColorScheme( brightness: Brightness.light, primary: Colors.black, @@ -67,15 +76,15 @@ ThemeData themeLight() { error: Colors.red, onError: Colors.white, surface: Colors.white, - onSurface: Colors.black)); + onSurface: Colors.black))); } else { - return ThemeData.from(colorScheme: colorSchemeLight!); + return themeModifier(ThemeData.from(colorScheme: colorSchemeLight!)); } } ThemeData themeDark() { if (!(prefs?.getBool("useDeviceTheme") ?? false) || colorSchemeDark == null) { - return ThemeData.from( + return themeModifier(ThemeData.from( colorScheme: const ColorScheme( brightness: Brightness.dark, primary: Colors.white, @@ -85,9 +94,9 @@ ThemeData themeDark() { error: Colors.red, onError: Colors.black, surface: Colors.black, - onSurface: Colors.white)); + onSurface: Colors.white))); } else { - return ThemeData.from(colorScheme: colorSchemeDark!); + return themeModifier(ThemeData.from(colorScheme: colorSchemeDark!)); } } From c41fb53d9c7aba7dd1426f2ab84c8df24eef903a Mon Sep 17 00:00:00 2001 From: JHubi1 Date: Tue, 20 Aug 2024 02:14:05 +0200 Subject: [PATCH 05/13] Added double pull to exit --- lib/l10n/app_en.arb | 5 + lib/main.dart | 1746 ++++++++++++++++++------------------ untranslated_messages.json | 4 + 3 files changed, 900 insertions(+), 855 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index ed39e7f..67c0942 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -5,6 +5,11 @@ "description": "Title of the application", "context": "Visible in the side bar" }, + "backToExit": "Press back again to exit", + "@backToExit": { + "description": "Text displayed when the back button is pressed to exit the app", + "context": "Visible in the chat view" + }, "optionNewChat": "New Chat", "@optionNewChat": { "description": "Text displayed for new chat option", diff --git a/lib/main.dart b/lib/main.dart index dab98b3..1c198f6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,6 +4,7 @@ import 'dart:math'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -69,6 +70,7 @@ String hoveredChat = ""; final user = types.User(id: const Uuid().v4()); final assistant = types.User(id: const Uuid().v4()); +int backPullTimestamp = 0; bool settingsOpen = false; bool desktopTitleVisible = true; bool logoVisible = true; @@ -112,6 +114,16 @@ class _AppState extends State { void initState() { super.initState(); + SystemChannels.lifecycle.setMessageHandler((msg) async { + if (msg == AppLifecycleState.resumed.toString()) { + // reset back pull timestamp + setState(() { + backPullTimestamp = 0; + }); + } + return null; + }); + void load() async { try { await FlutterDisplayMode.setHighRefreshRate(); @@ -971,301 +983,359 @@ class _MainAppState extends State { return WindowBorder( color: Theme.of(context).colorScheme.surface, - child: Scaffold( - appBar: AppBar( - titleSpacing: 0, - title: Row( - children: desktopFeature() - ? desktopLayoutRequired(context) - ? [ - SizedBox( - width: 304, height: 200, child: MoveWindow()), - SizedBox( - height: 200, - child: AnimatedOpacity( - opacity: menuVisible ? 1.0 : 0.0, - duration: - const Duration(milliseconds: 300), - child: VerticalDivider( - width: 2, - color: Theme.of(context) - .colorScheme - .onSurface - .withAlpha(20)))), - AnimatedOpacity( - opacity: desktopTitleVisible ? 1.0 : 0.0, - duration: desktopTitleVisible - ? const Duration(milliseconds: 300) - : const Duration(milliseconds: 0), - child: Padding( - padding: const EdgeInsets.all(16), - child: selector, + child: PopScope( + canPop: + DateTime.now().millisecondsSinceEpoch < (backPullTimestamp + 2000), + onPopInvoked: (didPop) { + if (!didPop) { + setState(() { + backPullTimestamp = DateTime.now().microsecondsSinceEpoch; + }); + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(AppLocalizations.of(context)!.backToExit), + duration: const Duration(seconds: 2), + showCloseIcon: true)); + } + }, + child: Scaffold( + appBar: AppBar( + titleSpacing: 0, + title: Row( + children: desktopFeature() + ? desktopLayoutRequired(context) + ? [ + SizedBox( + width: 304, + height: 200, + child: MoveWindow()), + SizedBox( + height: 200, + child: AnimatedOpacity( + opacity: menuVisible ? 1.0 : 0.0, + duration: + const Duration(milliseconds: 300), + child: VerticalDivider( + width: 2, + color: Theme.of(context) + .colorScheme + .onSurface + .withAlpha(20)))), + AnimatedOpacity( + opacity: desktopTitleVisible ? 1.0 : 0.0, + duration: desktopTitleVisible + ? const Duration(milliseconds: 300) + : const Duration(milliseconds: 0), + child: Padding( + padding: const EdgeInsets.all(16), + child: selector, + ), ), - ), - Expanded( - child: SizedBox( - height: 200, child: MoveWindow())) - ] - : [ - SizedBox( - width: 90, height: 200, child: MoveWindow()), - Expanded( - child: SizedBox( - height: 200, child: MoveWindow())), - selector, - Expanded( - child: SizedBox( - height: 200, child: MoveWindow())) - ] - : desktopLayoutRequired(context) - ? [ - // bottom left tile - const SizedBox(width: 304, height: 200), - SizedBox( - height: 200, - child: AnimatedOpacity( - opacity: menuVisible ? 1.0 : 0.0, - duration: - const Duration(milliseconds: 300), - child: VerticalDivider( - width: 2, - color: Theme.of(context) - .colorScheme - .onSurface - .withAlpha(20)))), - AnimatedOpacity( - opacity: desktopTitleVisible ? 1.0 : 0.0, - duration: desktopTitleVisible - ? const Duration(milliseconds: 300) - : const Duration(milliseconds: 0), - child: Padding( - padding: const EdgeInsets.all(16), - child: selector, + Expanded( + child: SizedBox( + height: 200, child: MoveWindow())) + ] + : [ + SizedBox( + width: 90, + height: 200, + child: MoveWindow()), + Expanded( + child: SizedBox( + height: 200, child: MoveWindow())), + selector, + Expanded( + child: SizedBox( + height: 200, child: MoveWindow())) + ] + : desktopLayoutRequired(context) + ? [ + // bottom left tile + const SizedBox(width: 304, height: 200), + SizedBox( + height: 200, + child: AnimatedOpacity( + opacity: menuVisible ? 1.0 : 0.0, + duration: + const Duration(milliseconds: 300), + child: VerticalDivider( + width: 2, + color: Theme.of(context) + .colorScheme + .onSurface + .withAlpha(20)))), + AnimatedOpacity( + opacity: desktopTitleVisible ? 1.0 : 0.0, + duration: desktopTitleVisible + ? const Duration(milliseconds: 300) + : const Duration(milliseconds: 0), + child: Padding( + padding: const EdgeInsets.all(16), + child: selector, + ), ), - ), - const Expanded(child: SizedBox(height: 200)) - ] - : [Expanded(child: selector)]), - actions: desktopControlsActions(context, [ - const SizedBox(width: 4), - allowMultipleChats - ? IconButton( - onPressed: () { - selectionHaptic(); - if (!chatAllowed) return; + const Expanded(child: SizedBox(height: 200)) + ] + : [Expanded(child: selector)]), + actions: desktopControlsActions(context, [ + const SizedBox(width: 4), + allowMultipleChats + ? IconButton( + onPressed: () { + selectionHaptic(); + if (!chatAllowed) return; - if (prefs!.getBool("askBeforeDeletion") ?? - // ignore: dead_code - false && messages.isNotEmpty) { - showDialog( - context: context, - builder: (context) { - return StatefulBuilder( - builder: (context, setLocalState) { - return AlertDialog( - title: Text( - AppLocalizations.of(context)! - .deleteDialogTitle), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text(AppLocalizations.of(context)! - .deleteDialogDescription), - ]), - actions: [ - TextButton( - onPressed: () { - selectionHaptic(); - Navigator.of(context).pop(); - }, - child: Text( - AppLocalizations.of(context)! - .deleteDialogCancel)), - TextButton( - onPressed: () { - selectionHaptic(); - Navigator.of(context).pop(); + if (prefs!.getBool("askBeforeDeletion") ?? + // ignore: dead_code + false && messages.isNotEmpty) { + showDialog( + context: context, + builder: (context) { + return StatefulBuilder( + builder: (context, setLocalState) { + return AlertDialog( + title: Text( + AppLocalizations.of(context)! + .deleteDialogTitle), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(AppLocalizations.of( + context)! + .deleteDialogDescription), + ]), + actions: [ + TextButton( + onPressed: () { + selectionHaptic(); + Navigator.of(context).pop(); + }, + child: Text(AppLocalizations.of( + context)! + .deleteDialogCancel)), + TextButton( + onPressed: () { + selectionHaptic(); + Navigator.of(context).pop(); - for (var i = 0; - i < - (prefs!.getStringList( + for (var i = 0; + i < + (prefs!.getStringList( + "chats") ?? + []) + .length; + i++) { + if (jsonDecode((prefs! + .getStringList( "chats") ?? - []) - .length; - i++) { - if (jsonDecode((prefs! - .getStringList( - "chats") ?? - [])[i])["uuid"] == - chatUuid) { - List tmp = prefs! - .getStringList( - "chats")!; - tmp.removeAt(i); - prefs!.setStringList( - "chats", tmp); - break; + [])[i])["uuid"] == + chatUuid) { + List tmp = prefs! + .getStringList( + "chats")!; + tmp.removeAt(i); + prefs!.setStringList( + "chats", tmp); + break; + } } - } - messages = []; - chatUuid = null; - setState(() {}); - }, - child: Text( - AppLocalizations.of(context)! - .deleteDialogDelete)) - ]); + messages = []; + chatUuid = null; + setState(() {}); + }, + child: Text(AppLocalizations.of( + context)! + .deleteDialogDelete)) + ]); + }); }); - }); - } else { - for (var i = 0; - i < - (prefs!.getStringList("chats") ?? []) - .length; - i++) { - if (jsonDecode((prefs!.getStringList("chats") ?? - [])[i])["uuid"] == - chatUuid) { - List tmp = - prefs!.getStringList("chats")!; - tmp.removeAt(i); - prefs!.setStringList("chats", tmp); - break; - } - } - messages = []; - chatUuid = null; - } - setState(() {}); - }, - icon: const Icon(Icons.restart_alt_rounded)) - : const SizedBox.shrink() - ]), - bottom: PreferredSize( - preferredSize: const Size.fromHeight(1), - child: (!chatAllowed && model != null) - ? const LinearProgressIndicator() - : desktopLayout(context) - ? AnimatedOpacity( - opacity: menuVisible ? 1.0 : 0.0, - duration: const Duration(milliseconds: 300), - child: Divider( - height: 2, - color: Theme.of(context) - .colorScheme - .onSurface - .withAlpha(20))) - : const SizedBox.shrink()), - automaticallyImplyLeading: !desktopLayoutRequired(context)), - body: Row( - children: [ - desktopLayoutRequired(context) - ? SizedBox( - width: 304, - height: double.infinity, - child: VisibilityDetector( - key: const Key("menuVisible"), - onVisibilityChanged: (VisibilityInfo info) { - if (settingsOpen) return; - menuVisible = info.visibleFraction > 0; - try { - setState(() {}); - } catch (_) {} - }, - child: AnimatedOpacity( - opacity: menuVisible ? 1.0 : 0.0, - duration: const Duration(milliseconds: 300), - child: ListView( - children: sidebar(context, setState))))) - : const SizedBox.shrink(), - desktopLayout(context) - ? AnimatedOpacity( - opacity: menuVisible ? 1.0 : 0.0, - duration: const Duration(milliseconds: 300), - child: VerticalDivider( - width: 2, - color: Theme.of(context) - .colorScheme - .onSurface - .withAlpha(20))) - : const SizedBox.shrink(), - Expanded( - child: Center( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.max, - children: [ - Flexible( - child: Container( - constraints: const BoxConstraints(maxWidth: 1000), - child: Chat( - messages: messages, - textMessageBuilder: (p0, - {required messageWidth, required showName}) { - var white = - const TextStyle(color: Colors.white); - bool greyed = false; - String text = p0.text; - if (text.trim() == "") { - text = - "_Empty AI response, try restarting conversation_"; - greyed = true; + } else { + for (var i = 0; + i < + (prefs!.getStringList("chats") ?? []) + .length; + i++) { + if (jsonDecode((prefs!.getStringList("chats") ?? + [])[i])["uuid"] == + chatUuid) { + List tmp = + prefs!.getStringList("chats")!; + tmp.removeAt(i); + prefs!.setStringList("chats", tmp); + break; } - return Padding( - padding: const EdgeInsets.only( - left: 20, - right: 23, - top: 17, - bottom: 17), - child: Theme( - data: Theme.of(context).copyWith( - scrollbarTheme: - const ScrollbarThemeData( - thumbColor: - WidgetStatePropertyAll( - Colors.grey))), - child: MarkdownBody( - data: text, - onTapLink: (text, href, title) async { - selectionHaptic(); - try { - var url = Uri.parse(href!); - if (await canLaunchUrl(url)) { - launchUrl( - mode: LaunchMode - .inAppBrowserView, - url); - } else { - throw Exception(); + } + messages = []; + chatUuid = null; + } + setState(() {}); + }, + icon: const Icon(Icons.restart_alt_rounded)) + : const SizedBox.shrink() + ]), + bottom: PreferredSize( + preferredSize: const Size.fromHeight(1), + child: (!chatAllowed && model != null) + ? const LinearProgressIndicator() + : desktopLayout(context) + ? AnimatedOpacity( + opacity: menuVisible ? 1.0 : 0.0, + duration: const Duration(milliseconds: 300), + child: Divider( + height: 2, + color: Theme.of(context) + .colorScheme + .onSurface + .withAlpha(20))) + : const SizedBox.shrink()), + automaticallyImplyLeading: !desktopLayoutRequired(context)), + body: Row( + children: [ + desktopLayoutRequired(context) + ? SizedBox( + width: 304, + height: double.infinity, + child: VisibilityDetector( + key: const Key("menuVisible"), + onVisibilityChanged: (VisibilityInfo info) { + if (settingsOpen) return; + menuVisible = info.visibleFraction > 0; + try { + setState(() {}); + } catch (_) {} + }, + child: AnimatedOpacity( + opacity: menuVisible ? 1.0 : 0.0, + duration: const Duration(milliseconds: 300), + child: ListView( + children: sidebar(context, setState))))) + : const SizedBox.shrink(), + desktopLayout(context) + ? AnimatedOpacity( + opacity: menuVisible ? 1.0 : 0.0, + duration: const Duration(milliseconds: 300), + child: VerticalDivider( + width: 2, + color: Theme.of(context) + .colorScheme + .onSurface + .withAlpha(20))) + : const SizedBox.shrink(), + Expanded( + child: Center( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + Flexible( + child: Container( + constraints: const BoxConstraints(maxWidth: 1000), + child: Chat( + messages: messages, + textMessageBuilder: (p0, + {required messageWidth, + required showName}) { + var white = + const TextStyle(color: Colors.white); + bool greyed = false; + String text = p0.text; + if (text.trim() == "") { + text = + "_Empty AI response, try restarting conversation_"; + greyed = true; + } + return Padding( + padding: const EdgeInsets.only( + left: 20, + right: 23, + top: 17, + bottom: 17), + child: Theme( + data: Theme.of(context).copyWith( + scrollbarTheme: + const ScrollbarThemeData( + thumbColor: + WidgetStatePropertyAll( + Colors.grey))), + child: MarkdownBody( + data: text, + onTapLink: (text, href, title) async { + selectionHaptic(); + try { + var url = Uri.parse(href!); + if (await canLaunchUrl(url)) { + launchUrl( + mode: LaunchMode + .inAppBrowserView, + url); + } else { + throw Exception(); + } + } catch (_) { + // ignore: use_build_context_synchronously + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar( + content: Text( + AppLocalizations.of( + // ignore: use_build_context_synchronously + context)! + .settingsHostInvalid( + "url")), + showCloseIcon: true)); } - } catch (_) { - // ignore: use_build_context_synchronously - ScaffoldMessenger.of(context) - .showSnackBar(SnackBar( - content: Text( - AppLocalizations.of( - // ignore: use_build_context_synchronously - context)! - .settingsHostInvalid( - "url")), - showCloseIcon: true)); - } - }, - extensionSet: md.ExtensionSet( - md.ExtensionSet.gitHubFlavored - .blockSyntaxes, - [ - md.EmojiSyntax(), - ...md.ExtensionSet.gitHubFlavored - .inlineSyntaxes - ], - ), - imageBuilder: (uri, title, alt) { - if (uri.isAbsolute) { - return Image.network( - uri.toString(), errorBuilder: - (context, error, - stackTrace) { + }, + extensionSet: md.ExtensionSet( + md.ExtensionSet.gitHubFlavored + .blockSyntaxes, + [ + md.EmojiSyntax(), + ...md + .ExtensionSet + .gitHubFlavored + .inlineSyntaxes + ], + ), + imageBuilder: (uri, title, alt) { + if (uri.isAbsolute) { + return Image.network( + uri.toString(), + errorBuilder: (context, + error, stackTrace) { + return InkWell( + onTap: () { + selectionHaptic(); + ScaffoldMessenger.of( + context) + .showSnackBar(SnackBar( + content: Text( + AppLocalizations.of( + context)! + .notAValidImage), + showCloseIcon: + true)); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: + BorderRadius + .circular( + 8), + color: Theme.of(context) + .brightness == + Brightness + .light + ? Colors.white + : Colors + .black), + padding: + const EdgeInsets.only( + left: 100, + right: 100, + top: 32), + child: const Image( + image: AssetImage( + "assets/logo512error.png")))); + }); + } else { return InkWell( onTap: () { selectionHaptic(); @@ -1300,592 +1370,558 @@ class _MainAppState extends State { child: const Image( image: AssetImage( "assets/logo512error.png")))); - }); - } else { - return InkWell( - onTap: () { - selectionHaptic(); - ScaffoldMessenger.of( - context) - .showSnackBar(SnackBar( - content: Text( - AppLocalizations.of( - context)! - .notAValidImage), - showCloseIcon: - true)); - }, - child: Container( - decoration: BoxDecoration( + } + }, + styleSheet: (p0.author == user) + ? MarkdownStyleSheet( + p: const TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: + FontWeight.w500), + blockquoteDecoration: + BoxDecoration( + color: Colors.grey[800], + borderRadius: + BorderRadius.circular( + 8), + ), + code: const TextStyle( + color: Colors.black, + backgroundColor: + Colors.white), + codeblockDecoration: + BoxDecoration( + color: Colors.white, + borderRadius: + BorderRadius.circular( + 8)), + h1: white, + h2: white, + h3: white, + h4: white, + h5: white, + h6: white, + listBullet: white, + horizontalRuleDecoration: BoxDecoration( + border: Border( + top: BorderSide( + color: Colors + .grey[800]!, + width: 1))), + tableBorder: TableBorder.all( + color: Colors.white), + tableBody: white) + : (Theme.of(context).brightness == + Brightness.light) + ? MarkdownStyleSheet( + p: TextStyle( + color: greyed ? Colors.grey : Colors.black, + fontSize: 16, + fontWeight: FontWeight.w500), + blockquoteDecoration: BoxDecoration( + color: + Colors.grey[200], borderRadius: BorderRadius .circular(8), - color: Theme.of(context) - .brightness == - Brightness - .light - ? Colors.white - : Colors.black), - padding: - const EdgeInsets.only( - left: 100, - right: 100, - top: 32), - child: const Image( - image: AssetImage( - "assets/logo512error.png")))); - } - }, - styleSheet: (p0.author == user) - ? MarkdownStyleSheet( - p: const TextStyle( - color: Colors.white, - fontSize: 16, - fontWeight: - FontWeight.w500), - blockquoteDecoration: - BoxDecoration( - color: Colors.grey[800], - borderRadius: - BorderRadius.circular( - 8), - ), - code: const TextStyle( - color: Colors.black, - backgroundColor: - Colors.white), - codeblockDecoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular( - 8)), - h1: white, - h2: white, - h3: white, - h4: white, - h5: white, - h6: white, - listBullet: white, - horizontalRuleDecoration: BoxDecoration( - border: Border( - top: BorderSide( - color: Colors - .grey[800]!, - width: 1))), - tableBorder: TableBorder.all( - color: Colors.white), - tableBody: white) - : (Theme.of(context).brightness == - Brightness.light) - ? MarkdownStyleSheet( - p: TextStyle( - color: greyed - ? Colors.grey - : Colors.black, - fontSize: 16, - fontWeight: - FontWeight.w500), - blockquoteDecoration: - BoxDecoration( - color: Colors.grey[200], - borderRadius: - BorderRadius - .circular(8), - ), - code: const TextStyle( - color: Colors.white, - backgroundColor: Colors.black), - codeblockDecoration: BoxDecoration(color: Colors.black, borderRadius: BorderRadius.circular(8)), - horizontalRuleDecoration: BoxDecoration(border: Border(top: BorderSide(color: Colors.grey[200]!, width: 1)))) - : MarkdownStyleSheet( - p: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w500), - blockquoteDecoration: BoxDecoration( - color: - Colors.grey[800]!, - borderRadius: - BorderRadius - .circular(8), - ), - code: const TextStyle(color: Colors.black, backgroundColor: Colors.white), - codeblockDecoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)), - horizontalRuleDecoration: BoxDecoration(border: Border(top: BorderSide(color: Colors.grey[200]!, width: 1))))), - )); - }, - imageMessageBuilder: (p0, - {required messageWidth}) { - return SizedBox( - width: - desktopLayout(context) ? 360.0 : 160.0, - child: MarkdownBody( - data: "![${p0.name}](${p0.uri})")); - }, - disableImageGallery: true, - emptyState: Center( - child: VisibilityDetector( - key: const Key("logoVisible"), - onVisibilityChanged: - (VisibilityInfo info) { - if (settingsOpen) return; - logoVisible = info.visibleFraction > 0; - try { - setState(() {}); - } catch (_) {} - }, - child: AnimatedOpacity( - opacity: logoVisible ? 1.0 : 0.0, - duration: - const Duration(milliseconds: 500), - child: const ImageIcon( - AssetImage("assets/logo512.png"), - size: 44)))), - onSendPressed: (p0) { - send(p0.text, context, setState); - }, - onMessageDoubleTap: (context, p1) { - selectionHaptic(); - if (!chatAllowed) return; - if (p1.author == assistant) return; - for (var i = 0; i < messages.length; i++) { - if (messages[i].id == p1.id) { - List messageList = - (jsonDecode(jsonEncode(messages)) - as List) - .reversed - .toList(); - bool found = false; - List index = []; - for (var j = 0; - j < messageList.length; - j++) { - if (messageList[j]["id"] == p1.id) { - found = true; - } - if (found) { - index.add(messageList[j]["id"]); - } - } - for (var j = 0; j < index.length; j++) { - for (var k = 0; - k < messages.length; - k++) { - if (messages[k].id == index[j]) { - messages.removeAt(k); + ), + code: const TextStyle(color: Colors.white, backgroundColor: Colors.black), + codeblockDecoration: BoxDecoration(color: Colors.black, borderRadius: BorderRadius.circular(8)), + horizontalRuleDecoration: BoxDecoration(border: Border(top: BorderSide(color: Colors.grey[200]!, width: 1)))) + : MarkdownStyleSheet( + p: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w500), + blockquoteDecoration: BoxDecoration( + color: + Colors.grey[800]!, + borderRadius: + BorderRadius + .circular(8), + ), + code: const TextStyle(color: Colors.black, backgroundColor: Colors.white), + codeblockDecoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)), + horizontalRuleDecoration: BoxDecoration(border: Border(top: BorderSide(color: Colors.grey[200]!, width: 1))))), + )); + }, + imageMessageBuilder: (p0, + {required messageWidth}) { + return SizedBox( + width: desktopLayout(context) + ? 360.0 + : 160.0, + child: MarkdownBody( + data: "![${p0.name}](${p0.uri})")); + }, + disableImageGallery: true, + emptyState: Center( + child: VisibilityDetector( + key: const Key("logoVisible"), + onVisibilityChanged: + (VisibilityInfo info) { + if (settingsOpen) return; + logoVisible = + info.visibleFraction > 0; + try { + setState(() {}); + } catch (_) {} + }, + child: AnimatedOpacity( + opacity: logoVisible ? 1.0 : 0.0, + duration: const Duration( + milliseconds: 500), + child: const ImageIcon(AssetImage("assets/logo512.png"), + size: 44)))), + onSendPressed: (p0) { + send(p0.text, context, setState); + }, + onMessageDoubleTap: (context, p1) { + selectionHaptic(); + if (!chatAllowed) return; + if (p1.author == assistant) return; + for (var i = 0; i < messages.length; i++) { + if (messages[i].id == p1.id) { + List messageList = + (jsonDecode(jsonEncode(messages)) + as List) + .reversed + .toList(); + bool found = false; + List index = []; + for (var j = 0; + j < messageList.length; + j++) { + if (messageList[j]["id"] == p1.id) { + found = true; + } + if (found) { + index.add(messageList[j]["id"]); } } - } - break; - } - } - saveChat(chatUuid!, setState); - setState(() {}); - }, - onMessageLongPress: (context, p1) async { - selectionHaptic(); - - if (!(prefs!.getBool("enableEditing") ?? - true)) { - return; - } - - var index = -1; - if (!chatAllowed) return; - for (var i = 0; i < messages.length; i++) { - if (messages[i].id == p1.id) { - index = i; - break; - } - } - - var text = - (messages[index] as types.TextMessage).text; - var input = await prompt( - context, - title: AppLocalizations.of(context)! - .dialogEditMessageTitle, - value: text, - keyboard: TextInputType.multiline, - maxLines: (text.length >= 100) - ? 10 - : ((text.length >= 50) ? 5 : 3), - ); - if (input == "") return; - - messages[index] = types.TextMessage( - author: p1.author, - createdAt: p1.createdAt, - id: p1.id, - text: input, - ); - setState(() {}); - }, - onAttachmentPressed: (!multimodal) - ? (prefs?.getBool("voiceModeEnabled") ?? - false) - ? (model != null) - ? () { - selectionHaptic(); - setMainState = setState; - settingsOpen = true; - logoVisible = false; - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => - const ScreenVoice())); - } - : null - : null - : () { - selectionHaptic(); - if (!chatAllowed || model == null) return; - if (desktopFeature()) { - FilePicker.platform - .pickFiles(type: FileType.image) - .then((value) async { - if (value == null) return; - if (!multimodal) return; - - var encoded = base64.encode( - await File( - value.files.first.path!) - .readAsBytes()); - messages.insert( - 0, - types.ImageMessage( - author: user, - id: const Uuid().v4(), - name: value.files.first.name, - size: value.files.first.size, - uri: - "data:image/png;base64,$encoded")); - - setState(() {}); - }); - - return; + for (var j = 0; j < index.length; j++) { + for (var k = 0; + k < messages.length; + k++) { + if (messages[k].id == index[j]) { + messages.removeAt(k); + } + } } - showModalBottomSheet( - context: context, - builder: (context) { - return Container( - width: double.infinity, - padding: const EdgeInsets.only( - left: 16, - right: 16, - top: 16), - child: Column( - mainAxisSize: - MainAxisSize.min, - children: [ - (prefs?.getBool( - "voiceModeEnabled") ?? - false) - ? SizedBox( - width: double - .infinity, - child: OutlinedButton - .icon( - onPressed: - () async { - selectionHaptic(); - Navigator.of(context) - .pop(); - setMainState = - setState; - settingsOpen = - true; - logoVisible = - false; - Navigator.of(context).push(MaterialPageRoute( - builder: (context) => - const ScreenVoice())); - }, - icon: const Icon( - Icons - .headphones_rounded), - label: Text( - AppLocalizations.of(context)! - .settingsTitleVoice))) - : const SizedBox - .shrink(), - (prefs?.getBool( - "voiceModeEnabled") ?? - false) - ? const SizedBox( - height: 8) - : const SizedBox - .shrink(), - SizedBox( - width: - double.infinity, - child: OutlinedButton - .icon( - onPressed: - () async { - selectionHaptic(); + break; + } + } + saveChat(chatUuid!, setState); + setState(() {}); + }, + onMessageLongPress: (context, p1) async { + selectionHaptic(); - Navigator.of( - context) - .pop(); - final result = - await ImagePicker() - .pickImage( - source: ImageSource - .camera, - ); - if (result == - null) { - return; - } + if (!(prefs!.getBool("enableEditing") ?? + true)) { + return; + } - final bytes = - await result - .readAsBytes(); - final image = - await decodeImageFromList( - bytes); + var index = -1; + if (!chatAllowed) return; + for (var i = 0; i < messages.length; i++) { + if (messages[i].id == p1.id) { + index = i; + break; + } + } - final message = - types - .ImageMessage( - author: - user, - createdAt: - DateTime.now() - .millisecondsSinceEpoch, - height: image - .height - .toDouble(), - id: const Uuid() - .v4(), - name: result - .name, - size: bytes - .length, - uri: result - .path, - width: image - .width - .toDouble(), - ); + var text = + (messages[index] as types.TextMessage) + .text; + var input = await prompt( + context, + title: AppLocalizations.of(context)! + .dialogEditMessageTitle, + value: text, + keyboard: TextInputType.multiline, + maxLines: (text.length >= 100) + ? 10 + : ((text.length >= 50) ? 5 : 3), + ); + if (input == "") return; - messages.insert( - 0, - message); - setState( - () {}); - selectionHaptic(); - }, - icon: const Icon( - Icons - .photo_camera_rounded), - label: Text(AppLocalizations.of( - context)! - .takeImage))), - const SizedBox(height: 8), - SizedBox( - width: - double.infinity, - child: OutlinedButton - .icon( - onPressed: - () async { - selectionHaptic(); + messages[index] = types.TextMessage( + author: p1.author, + createdAt: p1.createdAt, + id: p1.id, + text: input, + ); + setState(() {}); + }, + onAttachmentPressed: (!multimodal) + ? (prefs?.getBool("voiceModeEnabled") ?? + false) + ? (model != null) + ? () { + selectionHaptic(); + setMainState = setState; + settingsOpen = true; + logoVisible = false; + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => + const ScreenVoice())); + } + : null + : null + : () { + selectionHaptic(); + if (!chatAllowed || model == null) + return; + if (desktopFeature()) { + FilePicker.platform + .pickFiles(type: FileType.image) + .then((value) async { + if (value == null) return; + if (!multimodal) return; - Navigator.of( - context) - .pop(); - final result = - await ImagePicker() - .pickImage( - source: ImageSource - .gallery, - ); - if (result == - null) { - return; - } + var encoded = base64.encode( + await File( + value.files.first.path!) + .readAsBytes()); + messages.insert( + 0, + types.ImageMessage( + author: user, + id: const Uuid().v4(), + name: + value.files.first.name, + size: + value.files.first.size, + uri: + "data:image/png;base64,$encoded")); - final bytes = - await result - .readAsBytes(); - final image = - await decodeImageFromList( - bytes); - - final message = - types - .ImageMessage( - author: - user, - createdAt: - DateTime.now() - .millisecondsSinceEpoch, - height: image - .height - .toDouble(), - id: const Uuid() - .v4(), - name: result - .name, - size: bytes - .length, - uri: result - .path, - width: image - .width - .toDouble(), - ); - - messages.insert( - 0, - message); - setState( - () {}); - selectionHaptic(); - }, - icon: const Icon( - Icons - .image_rounded), - label: Text(AppLocalizations.of( - context)! - .uploadImage))) - ])); + setState(() {}); }); + + return; + } + showModalBottomSheet( + context: context, + builder: (context) { + return Container( + width: double.infinity, + padding: + const EdgeInsets.only( + left: 16, + right: 16, + top: 16), + child: Column( + mainAxisSize: + MainAxisSize.min, + children: [ + (prefs?.getBool( + "voiceModeEnabled") ?? + false) + ? SizedBox( + width: double + .infinity, + child: OutlinedButton + .icon( + onPressed: + () async { + selectionHaptic(); + Navigator.of(context) + .pop(); + setMainState = + setState; + settingsOpen = + true; + logoVisible = + false; + Navigator.of(context) + .push(MaterialPageRoute(builder: (context) => const ScreenVoice())); + }, + icon: const Icon(Icons + .headphones_rounded), + label: Text(AppLocalizations.of(context)! + .settingsTitleVoice))) + : const SizedBox + .shrink(), + (prefs?.getBool( + "voiceModeEnabled") ?? + false) + ? const SizedBox( + height: 8) + : const SizedBox + .shrink(), + SizedBox( + width: + double.infinity, + child: OutlinedButton + .icon( + onPressed: + () async { + selectionHaptic(); + + Navigator.of( + context) + .pop(); + final result = + await ImagePicker() + .pickImage( + source: + ImageSource.camera, + ); + if (result == + null) { + return; + } + + final bytes = + await result + .readAsBytes(); + final image = + await decodeImageFromList( + bytes); + + final message = + types + .ImageMessage( + author: + user, + createdAt: + DateTime.now().millisecondsSinceEpoch, + height: image + .height + .toDouble(), + id: const Uuid() + .v4(), + name: result + .name, + size: bytes + .length, + uri: result + .path, + width: image + .width + .toDouble(), + ); + + messages.insert( + 0, + message); + setState( + () {}); + selectionHaptic(); + }, + icon: const Icon( + Icons + .photo_camera_rounded), + label: Text( + AppLocalizations.of(context)! + .takeImage))), + const SizedBox( + height: 8), + SizedBox( + width: + double.infinity, + child: OutlinedButton + .icon( + onPressed: + () async { + selectionHaptic(); + + Navigator.of( + context) + .pop(); + final result = + await ImagePicker() + .pickImage( + source: + ImageSource.gallery, + ); + if (result == + null) { + return; + } + + final bytes = + await result + .readAsBytes(); + final image = + await decodeImageFromList( + bytes); + + final message = + types + .ImageMessage( + author: + user, + createdAt: + DateTime.now().millisecondsSinceEpoch, + height: image + .height + .toDouble(), + id: const Uuid() + .v4(), + name: result + .name, + size: bytes + .length, + uri: result + .path, + width: image + .width + .toDouble(), + ); + + messages.insert( + 0, + message); + setState( + () {}); + selectionHaptic(); + }, + icon: const Icon( + Icons + .image_rounded), + label: Text( + AppLocalizations.of(context)! + .uploadImage))) + ])); + }); + }, + l10n: ChatL10nEn( + inputPlaceholder: AppLocalizations.of(context)! + .messageInputPlaceholder, + attachmentButtonAccessibilityLabel: + AppLocalizations.of(context)! + .tooltipAttachment, + sendButtonAccessibilityLabel: + AppLocalizations.of(context)! + .tooltipSend), + inputOptions: InputOptions( + keyboardType: TextInputType.multiline, + onTextChanged: (p0) { + setState(() { + sendable = p0.trim().isNotEmpty; + }); }, - l10n: ChatL10nEn( - inputPlaceholder: AppLocalizations.of(context)! - .messageInputPlaceholder, - attachmentButtonAccessibilityLabel: - AppLocalizations.of(context)! - .tooltipAttachment, - sendButtonAccessibilityLabel: - AppLocalizations.of(context)! - .tooltipSend), - inputOptions: InputOptions( - keyboardType: TextInputType.multiline, - onTextChanged: (p0) { - setState(() { - sendable = p0.trim().isNotEmpty; - }); - }, - sendButtonVisibilityMode: desktopFeature() - ? SendButtonVisibilityMode.always - : (sendable) - ? SendButtonVisibilityMode.always - : SendButtonVisibilityMode.hidden), - user: user, - hideBackgroundOnEmojiMessages: false, - theme: (Theme.of(context).brightness == - Brightness.light) - ? DefaultChatTheme( - backgroundColor: - themeLight().colorScheme.surface, - primaryColor: - themeLight().colorScheme.primary, - attachmentButtonIcon: !multimodal - ? (prefs?.getBool("voiceModeEnabled") ?? false) - ? Icon(Icons.headphones_rounded, color: Theme.of(context).iconTheme.color) - : null - : Icon(Icons.add_a_photo_rounded, color: Theme.of(context).iconTheme.color), - sendButtonIcon: SizedBox( - height: 24, - child: CircleAvatar( - backgroundColor: Theme.of(context) - .iconTheme - .color, - radius: 12, - child: Icon( - Icons.arrow_upward_rounded, - color: (prefs?.getBool( - "useDeviceTheme") ?? - false) - ? Theme.of(context) - .colorScheme - .surface - : null)), - ), - sendButtonMargin: EdgeInsets.zero, - attachmentButtonMargin: EdgeInsets.zero, - inputBackgroundColor: themeLight().colorScheme.onSurface.withAlpha(10), - inputTextColor: themeLight().colorScheme.onSurface, - inputBorderRadius: BorderRadius.circular(32), - inputPadding: const EdgeInsets.all(16), - inputMargin: EdgeInsets.only(left: (MediaQuery.of(context).viewInsets.bottom == 0.0 && !desktopFeature()) ? 8 : 6, right: (MediaQuery.of(context).viewInsets.bottom == 0.0 && !desktopFeature()) ? 8 : 6, bottom: (MediaQuery.of(context).viewInsets.bottom == 0.0 && !desktopFeature()) ? 0 : 8), - messageMaxWidth: (MediaQuery.of(context).size.width >= 1000) - ? (MediaQuery.of(context).size.width >= 1600) - ? (MediaQuery.of(context).size.width >= 2200) - ? 1900 - : 1300 - : 700 - : 440) - : DarkChatTheme( - backgroundColor: themeDark().colorScheme.surface, - primaryColor: themeDark().colorScheme.primary.withAlpha(40), - secondaryColor: themeDark().colorScheme.primary.withAlpha(20), - attachmentButtonIcon: !multimodal - ? (prefs?.getBool("voiceModeEnabled") ?? false) - ? Icon(Icons.headphones_rounded, color: Theme.of(context).iconTheme.color) - : null - : Icon(Icons.add_a_photo_rounded, color: Theme.of(context).iconTheme.color), - sendButtonIcon: SizedBox( - height: 24, - child: CircleAvatar( - backgroundColor: Theme.of(context) - .iconTheme - .color, - radius: 12, - child: Icon( - Icons.arrow_upward_rounded, - color: (prefs?.getBool( - "useDeviceTheme") ?? - false) - ? Theme.of(context) - .colorScheme - .surface - : null)), - ), - sendButtonMargin: EdgeInsets.zero, - attachmentButtonMargin: EdgeInsets.zero, - inputBackgroundColor: themeDark().colorScheme.onSurface.withAlpha(40), - inputTextColor: themeDark().colorScheme.onSurface, - inputBorderRadius: const BorderRadius.all(Radius.circular(64)), - inputPadding: const EdgeInsets.all(16), - inputMargin: EdgeInsets.only(left: 8, right: 8, bottom: (MediaQuery.of(context).viewInsets.bottom == 0.0 && !desktopFeature()) ? 0 : 8), - messageMaxWidth: (MediaQuery.of(context).size.width >= 1000) - ? (MediaQuery.of(context).size.width >= 1600) - ? (MediaQuery.of(context).size.width >= 2200) - ? 1900 - : 1300 - : 700 - : 440)), + sendButtonVisibilityMode: desktopFeature() + ? SendButtonVisibilityMode.always + : (sendable) + ? SendButtonVisibilityMode.always + : SendButtonVisibilityMode.hidden), + user: user, + hideBackgroundOnEmojiMessages: false, + theme: (Theme.of(context).brightness == + Brightness.light) + ? DefaultChatTheme( + backgroundColor: + themeLight().colorScheme.surface, + primaryColor: + themeLight().colorScheme.primary, + attachmentButtonIcon: !multimodal + ? (prefs?.getBool("voiceModeEnabled") ?? false) + ? Icon(Icons.headphones_rounded, color: Theme.of(context).iconTheme.color) + : null + : Icon(Icons.add_a_photo_rounded, color: Theme.of(context).iconTheme.color), + sendButtonIcon: SizedBox( + height: 24, + child: CircleAvatar( + backgroundColor: Theme.of(context) + .iconTheme + .color, + radius: 12, + child: Icon( + Icons.arrow_upward_rounded, + color: (prefs?.getBool( + "useDeviceTheme") ?? + false) + ? Theme.of(context) + .colorScheme + .surface + : null)), + ), + sendButtonMargin: EdgeInsets.zero, + attachmentButtonMargin: EdgeInsets.zero, + inputBackgroundColor: themeLight().colorScheme.onSurface.withAlpha(10), + inputTextColor: themeLight().colorScheme.onSurface, + inputBorderRadius: BorderRadius.circular(32), + inputPadding: const EdgeInsets.all(16), + inputMargin: EdgeInsets.only(left: (MediaQuery.of(context).viewInsets.bottom == 0.0 && !desktopFeature()) ? 8 : 6, right: (MediaQuery.of(context).viewInsets.bottom == 0.0 && !desktopFeature()) ? 8 : 6, bottom: (MediaQuery.of(context).viewInsets.bottom == 0.0 && !desktopFeature()) ? 0 : 8), + messageMaxWidth: (MediaQuery.of(context).size.width >= 1000) + ? (MediaQuery.of(context).size.width >= 1600) + ? (MediaQuery.of(context).size.width >= 2200) + ? 1900 + : 1300 + : 700 + : 440) + : DarkChatTheme( + backgroundColor: themeDark().colorScheme.surface, + primaryColor: themeDark().colorScheme.primary.withAlpha(40), + secondaryColor: themeDark().colorScheme.primary.withAlpha(20), + attachmentButtonIcon: !multimodal + ? (prefs?.getBool("voiceModeEnabled") ?? false) + ? Icon(Icons.headphones_rounded, color: Theme.of(context).iconTheme.color) + : null + : Icon(Icons.add_a_photo_rounded, color: Theme.of(context).iconTheme.color), + sendButtonIcon: SizedBox( + height: 24, + child: CircleAvatar( + backgroundColor: Theme.of(context) + .iconTheme + .color, + radius: 12, + child: Icon( + Icons.arrow_upward_rounded, + color: (prefs?.getBool( + "useDeviceTheme") ?? + false) + ? Theme.of(context) + .colorScheme + .surface + : null)), + ), + sendButtonMargin: EdgeInsets.zero, + attachmentButtonMargin: EdgeInsets.zero, + inputBackgroundColor: themeDark().colorScheme.onSurface.withAlpha(40), + inputTextColor: themeDark().colorScheme.onSurface, + inputBorderRadius: const BorderRadius.all(Radius.circular(64)), + inputPadding: const EdgeInsets.all(16), + inputMargin: EdgeInsets.only(left: 8, right: 8, bottom: (MediaQuery.of(context).viewInsets.bottom == 0.0 && !desktopFeature()) ? 0 : 8), + messageMaxWidth: (MediaQuery.of(context).size.width >= 1000) + ? (MediaQuery.of(context).size.width >= 1600) + ? (MediaQuery.of(context).size.width >= 2200) + ? 1900 + : 1300 + : 700 + : 440)), + ), ), - ), - ], + ], + ), ), ), - ), - ], - ), - drawerEdgeDragWidth: (prefs?.getBool("fixCodeblockScroll") ?? false) - ? null - : (desktopLayout(context) - ? null - : MediaQuery.of(context).size.width), - drawer: Builder(builder: (context) { - if (desktopLayoutRequired(context) && !settingsOpen) { - WidgetsBinding.instance.addPostFrameCallback((_) { - if (Navigator.of(context).canPop()) { - Navigator.of(context).pop(); - } - }); - } - return NavigationDrawer( - onDestinationSelected: (value) { - if (value == 1) { - } else if (value == 2) {} - }, - selectedIndex: 1, - children: sidebar(context, setState)); - })), + ], + ), + drawerEdgeDragWidth: (prefs?.getBool("fixCodeblockScroll") ?? false) + ? null + : (desktopLayout(context) + ? null + : MediaQuery.of(context).size.width), + drawer: Builder(builder: (context) { + if (desktopLayoutRequired(context) && !settingsOpen) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (Navigator.of(context).canPop()) { + Navigator.of(context).pop(); + } + }); + } + return NavigationDrawer( + onDestinationSelected: (value) { + if (value == 1) { + } else if (value == 2) {} + }, + selectedIndex: 1, + children: sidebar(context, setState)); + })), + ), ); } } diff --git a/untranslated_messages.json b/untranslated_messages.json index 685c551..3c340eb 100644 --- a/untranslated_messages.json +++ b/untranslated_messages.json @@ -1,5 +1,6 @@ { "de": [ + "backToExit", "deleteChat", "renameChat", "settingsDescriptionBehavior", @@ -21,6 +22,7 @@ ], "it": [ + "backToExit", "deleteChat", "renameChat", "settingsDescriptionBehavior", @@ -42,6 +44,7 @@ ], "tr": [ + "backToExit", "deleteChat", "renameChat", "settingsDescriptionBehavior", @@ -63,6 +66,7 @@ ], "zh": [ + "backToExit", "deleteChat", "renameChat", "settingsDescriptionBehavior", From 354e82cacdd54f1c725bead3f393f4b67c99a13f Mon Sep 17 00:00:00 2001 From: JHubi1 Date: Tue, 20 Aug 2024 02:18:48 +0200 Subject: [PATCH 06/13] Added reference --- lib/main.dart | 3 ++- lib/worker/theme.dart | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/main.dart b/lib/main.dart index 1c198f6..928c35a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1570,8 +1570,9 @@ class _MainAppState extends State { : null : () { selectionHaptic(); - if (!chatAllowed || model == null) + if (!chatAllowed || model == null) { return; + } if (desktopFeature()) { FilePicker.platform .pickFiles(type: FileType.image) diff --git a/lib/worker/theme.dart b/lib/worker/theme.dart index f7f04b2..b2827f8 100644 --- a/lib/worker/theme.dart +++ b/lib/worker/theme.dart @@ -56,6 +56,7 @@ void resetSystemNavigation(BuildContext context, ThemeData themeModifier(ThemeData theme) { return theme.copyWith( + // https://docs.flutter.dev/platform-integration/android/predictive-back#set-up-your-app pageTransitionsTheme: const PageTransitionsTheme( builders: { TargetPlatform.android: PredictiveBackPageTransitionsBuilder(), From 33f656997caebad25d0e2cb8670c7b8429e9638b Mon Sep 17 00:00:00 2001 From: JHubi1 Date: Tue, 20 Aug 2024 02:20:43 +0200 Subject: [PATCH 07/13] Disabled double back --- lib/main.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 928c35a..da89cad 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -984,8 +984,9 @@ class _MainAppState extends State { return WindowBorder( color: Theme.of(context).colorScheme.surface, child: PopScope( - canPop: - DateTime.now().millisecondsSinceEpoch < (backPullTimestamp + 2000), + // disabled for now, shows buggy behavior if enabled + canPop: true, + // DateTime.now().millisecondsSinceEpoch < (backPullTimestamp + 2000), onPopInvoked: (didPop) { if (!didPop) { setState(() { From 1f31d51e30dbed5fba1e33ff5a8718f097f42497 Mon Sep 17 00:00:00 2001 From: JHubi1 Date: Tue, 20 Aug 2024 17:54:46 +0200 Subject: [PATCH 08/13] Added linux to build script, linux compatibility improvements --- .github/workflows/build.yaml | 48 +++++++++++++++++--- lib/settings/export.dart | 88 +++++++++++++++++++++++++----------- pubspec.lock | 32 +++++++++++++ pubspec.yaml | 1 + windows_installer/arm64.iss | 4 +- windows_installer/x64.iss | 2 +- 6 files changed, 139 insertions(+), 36 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 814bf01..61c716f 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -12,6 +12,11 @@ on: required: true default: false type: boolean + buildLinux: + description: Build for Linux + required: true + default: false + type: boolean jobs: analyze: @@ -52,15 +57,13 @@ jobs: run: flutter build apk --obfuscate --split-debug-info=build/debugAndroid - name: Preparing files run: | - cp build/app/outputs/flutter-apk/app-release.apk build/app/outputs/flutter-apk/ollama.apk - cp build/app/outputs/flutter-apk/app-release.apk.sha1 build/app/outputs/flutter-apk/ollama.apk.sha1 + cp build/app/outputs/flutter-apk/app-release.apk build/app/outputs/flutter-apk/ollama-android-v${{ steps.get_flutter_version.outputs.version_number }}.apk - name: Uploading APK uses: actions/upload-artifact@v4 with: name: ollama-android path: | - build/app/outputs/flutter-apk/ollama.apk - build/app/outputs/flutter-apk/ollama.apk.sha1 + build/app/outputs/flutter-apk/ollama-android-v${{ steps.get_flutter_version.outputs.version_number }}.apk build-windows-x64: name: Building for Windows x64 if: ${{ github.event.inputs.buildWindowsX64 == 'true' }} @@ -88,12 +91,43 @@ jobs: uses: actions/upload-artifact@v4 with: name: ollama-windows-x64 - path: build\windows\x64\runner\ollama-v${{ steps.get_flutter_version.outputs.version_number }}-x64.exe + path: build\windows\x64\runner\ollama-windows-x64-v${{ steps.get_flutter_version.outputs.version_number }}.exe + build-linux: + name: Building for Linux + if: ${{ github.event.inputs.buildLinux == 'true' }} + runs-on: ubuntu-latest + needs: analyze + steps: + - uses: actions/checkout@v4 + - uses: subosito/flutter-action@v2 + with: + flutter-version: 3.22.1 + - name: Get flutter version + id: get_flutter_version + uses: its404/get-flutter-version@v1.0.0 + - name: Disabling flutter analytics + run: flutter config --no-analytics + - name: Installing linux dependencies + run: | + sudo apt-get install ninja-build + sudo apt-get install build-essential libgtk-3-dev + - name: Running build + id: compile + run: flutter build linux --obfuscate --split-debug-info=build/debugLinux + - name: Creating archive + run: | + cd build/linux/x64/release/bundle + tar -czf ollama-linux-x64-v${{ steps.get_flutter_version.outputs.version_number }}.tar.gz * + - name: Uploading archive + uses: actions/upload-artifact@v4 + with: + name: ollama-linux-x64 + path: build/linux/x64/release/bundle/ollama-linux-x64-v${{ steps.get_flutter_version.outputs.version_number }}.tar.gz bundle: name: Creating bundle runs-on: ubuntu-latest if: ${{ always() }} - needs: [build-android, build-windows-x64] + needs: [build-android, build-windows-x64, build-linux] steps: - name: Adding builds uses: actions/download-artifact@v4 @@ -105,4 +139,4 @@ jobs: uses: actions/upload-artifact@v4 with: name: ollama - path: ./ + path: ./ \ No newline at end of file diff --git a/lib/settings/export.dart b/lib/settings/export.dart index 4ddc9f1..2a73f55 100644 --- a/lib/settings/export.dart +++ b/lib/settings/export.dart @@ -13,6 +13,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:file_picker/file_picker.dart'; +import 'package:file_selector/file_selector.dart' as file_selector; import 'package:intl/intl.dart'; import 'package:dynamic_color/dynamic_color.dart'; @@ -49,6 +50,7 @@ class _ScreenSettingsExportState extends State { var content = jsonEncode(prefs!.getStringList("chats") ?? []); if (kIsWeb) { + // web fallback final bytes = utf8.encode(content); final blob = html.Blob([bytes]); final url = html.Url.createObjectUrlFromBlob(blob); @@ -64,12 +66,22 @@ class _ScreenSettingsExportState extends State { html.document.body!.children.remove(anchor); html.Url.revokeObjectUrl(url); } else { - var path = await FilePicker.platform.saveFile( - type: FileType.custom, - allowedExtensions: ["json"], - fileName: name, - bytes: utf8.encode(jsonEncode( - prefs!.getStringList("chats") ?? []))); + String? path = ""; + try { + path = (await file_selector + .getSaveLocation(acceptedTypeGroups: [ + const file_selector.XTypeGroup( + label: "Ollama App File", extensions: ["json"]) + ], suggestedName: name)) + ?.path; + } catch (_) { + path = await FilePicker.platform.saveFile( + type: FileType.custom, + allowedExtensions: ["json"], + fileName: name, + bytes: utf8.encode(jsonEncode( + prefs!.getStringList("chats") ?? []))); + } selectionHaptic(); if (path == null) return; if (desktopFeature()) { @@ -103,30 +115,54 @@ class _ScreenSettingsExportState extends State { TextButton( onPressed: () async { selectionHaptic(); - FilePickerResult? result = - await FilePicker.platform - .pickFiles( - type: FileType.custom, - allowedExtensions: [ - "json" - ]); - if (result == null) { - // ignore: use_build_context_synchronously - Navigator.of(context).pop(); - return; - } - String content; try { - File file = File( - result.files.single.path!); + if (kIsWeb) { + throw Exception( + "web must use file picker"); + } + file_selector.XFile? result = + await file_selector.openFile( + acceptedTypeGroups: [ + const file_selector + .XTypeGroup( + label: + "Ollama App File", + extensions: ["json"]) + ]); + if (result == null) { + // ignore: use_build_context_synchronously + Navigator.of(context).pop(); + return; + } content = - await file.readAsString(); + await result.readAsString(); } catch (_) { - content = utf8.decode(result - .files - .single - .bytes as List); + FilePickerResult? result = + await FilePicker.platform + .pickFiles( + type: + FileType.custom, + allowedExtensions: [ + "json" + ]); + if (result == null) { + // ignore: use_build_context_synchronously + Navigator.of(context).pop(); + return; + } + try { + File file = File(result + .files.single.path!); + content = + await file.readAsString(); + } catch (_) { + // web fallback + content = utf8.decode(result + .files + .single + .bytes as List); + } } List tmpHistory = jsonDecode(content); diff --git a/pubspec.lock b/pubspec.lock index 98ac637..a886f4c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -225,6 +225,30 @@ packages: url: "https://pub.dev" source: hosted version: "8.1.2" + file_selector: + dependency: "direct main" + description: + name: file_selector + sha256: "5019692b593455127794d5718304ff1ae15447dea286cdda9f0db2a796a1b828" + url: "https://pub.dev" + source: hosted + version: "1.0.3" + file_selector_android: + dependency: transitive + description: + name: file_selector_android + sha256: "77f23eb5916fd0875946720d1f286f809a28a867d4882db6ac2cf053e2d5f7c6" + url: "https://pub.dev" + source: hosted + version: "0.5.1+6" + file_selector_ios: + dependency: transitive + description: + name: file_selector_ios + sha256: "38ebf91ecbcfa89a9639a0854ccaed8ab370c75678938eebca7d34184296f0bb" + url: "https://pub.dev" + source: hosted + version: "0.5.3" file_selector_linux: dependency: transitive description: @@ -249,6 +273,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.6.2" + file_selector_web: + dependency: transitive + description: + name: file_selector_web + sha256: c4c0ea4224d97a60a7067eca0c8fd419e708ff830e0c83b11a48faf566cec3e7 + url: "https://pub.dev" + source: hosted + version: "0.9.4+2" file_selector_windows: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 9e3d92a..bfec6db 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -28,6 +28,7 @@ dependencies: restart_app: ^1.2.1 flutter_markdown: ^0.7.1 file_picker: ^8.0.3 + file_selector: ^1.0.3 bitsdojo_window: ^0.1.6 install_referrer: ^1.2.1 package_info_plus: ^8.0.0 diff --git a/windows_installer/arm64.iss b/windows_installer/arm64.iss index 8d8cc8f..c1c1122 100644 --- a/windows_installer/arm64.iss +++ b/windows_installer/arm64.iss @@ -1,7 +1,7 @@ ; Script generated by the Inno Setup Script Wizard. ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! -; #define AppVersion "1.0.1" +;#define AppVersion "1.0.1" #define AppName "Ollama App" #define AppPublisher "JHubi1" @@ -27,7 +27,7 @@ UninstallDisplayName={#AppName} DefaultDirName={autopf}\OllamaApp OutputDir=build\windows\{#AppArchitectures}\runner -OutputBaseFilename=ollama-v{#AppVersion}-{#AppArchitectures} +OutputBaseFilename=ollama-windows-{#AppArchitectures}-v{#AppVersion} AppSupportURL=https://github.com/JHubi1/ollama-app/issues AppUpdatesURL=https://github.com/JHubi1/ollama-app/releases diff --git a/windows_installer/x64.iss b/windows_installer/x64.iss index 97d3cf0..e8b25c2 100644 --- a/windows_installer/x64.iss +++ b/windows_installer/x64.iss @@ -28,7 +28,7 @@ UninstallDisplayName={#AppName} DefaultDirName={autopf}\OllamaApp OutputDir=build\windows\{#AppArchitectures}\runner -OutputBaseFilename=ollama-v{#AppVersion}-{#AppArchitectures} +OutputBaseFilename=ollama-windows-{#AppArchitectures}-v{#AppVersion} AppSupportURL=https://github.com/JHubi1/ollama-app/issues AppUpdatesURL=https://github.com/JHubi1/ollama-app/releases From cf92fbcabfe391c53920513e2b71d696d3238bf9 Mon Sep 17 00:00:00 2001 From: JHubi1 Date: Tue, 20 Aug 2024 17:56:20 +0200 Subject: [PATCH 09/13] Added architecture to linux name --- .github/workflows/build.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 61c716f..d5e5a8e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -12,8 +12,8 @@ on: required: true default: false type: boolean - buildLinux: - description: Build for Linux + buildLinuxX64: + description: Build for Linux x64 required: true default: false type: boolean @@ -92,9 +92,9 @@ jobs: with: name: ollama-windows-x64 path: build\windows\x64\runner\ollama-windows-x64-v${{ steps.get_flutter_version.outputs.version_number }}.exe - build-linux: + build-linux-x64: name: Building for Linux - if: ${{ github.event.inputs.buildLinux == 'true' }} + if: ${{ github.event.inputs.buildLinuxX64 == 'true' }} runs-on: ubuntu-latest needs: analyze steps: @@ -127,7 +127,7 @@ jobs: name: Creating bundle runs-on: ubuntu-latest if: ${{ always() }} - needs: [build-android, build-windows-x64, build-linux] + needs: [build-android, build-windows-x64, build-linux-x64] steps: - name: Adding builds uses: actions/download-artifact@v4 From 732ad10b144453daed1c7b03ff9af42c42211332 Mon Sep 17 00:00:00 2001 From: JHubi1 Date: Tue, 20 Aug 2024 18:08:47 +0200 Subject: [PATCH 10/13] Fixed android version --- .github/workflows/build.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d5e5a8e..f97016e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -50,6 +50,9 @@ jobs: echo $'storePassword=${{ secrets.ANDROID_KEYSTORE_PASSPHRASE }}\nkeyPassword=${{ secrets.ANDROID_KEYSTORE_PASSPHRASE }}\nkeyAlias=upload\nstoreFile=upload-keystore.jks\n' > ./android/key.properties echo "${{ secrets.ANDROID_KEYSTORE }}" > ./android/app/upload-keystore.jks.asc gpg -d --passphrase "${{ secrets.ANDROID_KEYSTORE_PASSPHRASE }}" --batch ./android/app/upload-keystore.jks.asc > ./android/app/upload-keystore.jks + - name: Get flutter version + id: get_flutter_version + uses: its404/get-flutter-version@v1.0.0 - name: Disabling flutter analytics run: flutter config --no-analytics - name: Running build From 68d2bc343f4b97f9e10f20fb8ceefd69296eb383 Mon Sep 17 00:00:00 2001 From: JHubi1 Date: Tue, 20 Aug 2024 18:27:46 +0200 Subject: [PATCH 11/13] Removed back to exit Need feature in theory, but the implementation is lacking and will block the android app. I may revisit this in the future --- lib/l10n/app_en.arb | 5 - lib/main.dart | 1770 ++++++++++++++++++------------------ scripts/build.dart | 22 +- untranslated_messages.json | 4 - 4 files changed, 878 insertions(+), 923 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 67c0942..ed39e7f 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -5,11 +5,6 @@ "description": "Title of the application", "context": "Visible in the side bar" }, - "backToExit": "Press back again to exit", - "@backToExit": { - "description": "Text displayed when the back button is pressed to exit the app", - "context": "Visible in the chat view" - }, "optionNewChat": "New Chat", "@optionNewChat": { "description": "Text displayed for new chat option", diff --git a/lib/main.dart b/lib/main.dart index da89cad..6d34343 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,7 +4,6 @@ import 'dart:math'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -70,7 +69,6 @@ String hoveredChat = ""; final user = types.User(id: const Uuid().v4()); final assistant = types.User(id: const Uuid().v4()); -int backPullTimestamp = 0; bool settingsOpen = false; bool desktopTitleVisible = true; bool logoVisible = true; @@ -114,16 +112,6 @@ class _AppState extends State { void initState() { super.initState(); - SystemChannels.lifecycle.setMessageHandler((msg) async { - if (msg == AppLifecycleState.resumed.toString()) { - // reset back pull timestamp - setState(() { - backPullTimestamp = 0; - }); - } - return null; - }); - void load() async { try { await FlutterDisplayMode.setHighRefreshRate(); @@ -983,360 +971,301 @@ class _MainAppState extends State { return WindowBorder( color: Theme.of(context).colorScheme.surface, - child: PopScope( - // disabled for now, shows buggy behavior if enabled - canPop: true, - // DateTime.now().millisecondsSinceEpoch < (backPullTimestamp + 2000), - onPopInvoked: (didPop) { - if (!didPop) { - setState(() { - backPullTimestamp = DateTime.now().microsecondsSinceEpoch; - }); - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(AppLocalizations.of(context)!.backToExit), - duration: const Duration(seconds: 2), - showCloseIcon: true)); - } - }, - child: Scaffold( - appBar: AppBar( - titleSpacing: 0, - title: Row( - children: desktopFeature() - ? desktopLayoutRequired(context) - ? [ - SizedBox( - width: 304, - height: 200, - child: MoveWindow()), - SizedBox( - height: 200, - child: AnimatedOpacity( - opacity: menuVisible ? 1.0 : 0.0, - duration: - const Duration(milliseconds: 300), - child: VerticalDivider( - width: 2, - color: Theme.of(context) - .colorScheme - .onSurface - .withAlpha(20)))), - AnimatedOpacity( - opacity: desktopTitleVisible ? 1.0 : 0.0, - duration: desktopTitleVisible - ? const Duration(milliseconds: 300) - : const Duration(milliseconds: 0), - child: Padding( - padding: const EdgeInsets.all(16), - child: selector, - ), + child: Scaffold( + appBar: AppBar( + titleSpacing: 0, + title: Row( + children: desktopFeature() + ? desktopLayoutRequired(context) + ? [ + SizedBox( + width: 304, height: 200, child: MoveWindow()), + SizedBox( + height: 200, + child: AnimatedOpacity( + opacity: menuVisible ? 1.0 : 0.0, + duration: + const Duration(milliseconds: 300), + child: VerticalDivider( + width: 2, + color: Theme.of(context) + .colorScheme + .onSurface + .withAlpha(20)))), + AnimatedOpacity( + opacity: desktopTitleVisible ? 1.0 : 0.0, + duration: desktopTitleVisible + ? const Duration(milliseconds: 300) + : const Duration(milliseconds: 0), + child: Padding( + padding: const EdgeInsets.all(16), + child: selector, ), - Expanded( - child: SizedBox( - height: 200, child: MoveWindow())) - ] - : [ - SizedBox( - width: 90, - height: 200, - child: MoveWindow()), - Expanded( - child: SizedBox( - height: 200, child: MoveWindow())), - selector, - Expanded( - child: SizedBox( - height: 200, child: MoveWindow())) - ] - : desktopLayoutRequired(context) - ? [ - // bottom left tile - const SizedBox(width: 304, height: 200), - SizedBox( - height: 200, - child: AnimatedOpacity( - opacity: menuVisible ? 1.0 : 0.0, - duration: - const Duration(milliseconds: 300), - child: VerticalDivider( - width: 2, - color: Theme.of(context) - .colorScheme - .onSurface - .withAlpha(20)))), - AnimatedOpacity( - opacity: desktopTitleVisible ? 1.0 : 0.0, - duration: desktopTitleVisible - ? const Duration(milliseconds: 300) - : const Duration(milliseconds: 0), - child: Padding( - padding: const EdgeInsets.all(16), - child: selector, - ), + ), + Expanded( + child: SizedBox( + height: 200, child: MoveWindow())) + ] + : [ + SizedBox( + width: 90, height: 200, child: MoveWindow()), + Expanded( + child: SizedBox( + height: 200, child: MoveWindow())), + selector, + Expanded( + child: SizedBox( + height: 200, child: MoveWindow())) + ] + : desktopLayoutRequired(context) + ? [ + // bottom left tile + const SizedBox(width: 304, height: 200), + SizedBox( + height: 200, + child: AnimatedOpacity( + opacity: menuVisible ? 1.0 : 0.0, + duration: + const Duration(milliseconds: 300), + child: VerticalDivider( + width: 2, + color: Theme.of(context) + .colorScheme + .onSurface + .withAlpha(20)))), + AnimatedOpacity( + opacity: desktopTitleVisible ? 1.0 : 0.0, + duration: desktopTitleVisible + ? const Duration(milliseconds: 300) + : const Duration(milliseconds: 0), + child: Padding( + padding: const EdgeInsets.all(16), + child: selector, ), - const Expanded(child: SizedBox(height: 200)) - ] - : [Expanded(child: selector)]), - actions: desktopControlsActions(context, [ - const SizedBox(width: 4), - allowMultipleChats - ? IconButton( - onPressed: () { - selectionHaptic(); - if (!chatAllowed) return; + ), + const Expanded(child: SizedBox(height: 200)) + ] + : [Expanded(child: selector)]), + actions: desktopControlsActions(context, [ + const SizedBox(width: 4), + allowMultipleChats + ? IconButton( + onPressed: () { + selectionHaptic(); + if (!chatAllowed) return; - if (prefs!.getBool("askBeforeDeletion") ?? - // ignore: dead_code - false && messages.isNotEmpty) { - showDialog( - context: context, - builder: (context) { - return StatefulBuilder( - builder: (context, setLocalState) { - return AlertDialog( - title: Text( - AppLocalizations.of(context)! - .deleteDialogTitle), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text(AppLocalizations.of( - context)! - .deleteDialogDescription), - ]), - actions: [ - TextButton( - onPressed: () { - selectionHaptic(); - Navigator.of(context).pop(); - }, - child: Text(AppLocalizations.of( - context)! - .deleteDialogCancel)), - TextButton( - onPressed: () { - selectionHaptic(); - Navigator.of(context).pop(); + if (prefs!.getBool("askBeforeDeletion") ?? + // ignore: dead_code + false && messages.isNotEmpty) { + showDialog( + context: context, + builder: (context) { + return StatefulBuilder( + builder: (context, setLocalState) { + return AlertDialog( + title: Text( + AppLocalizations.of(context)! + .deleteDialogTitle), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(AppLocalizations.of(context)! + .deleteDialogDescription), + ]), + actions: [ + TextButton( + onPressed: () { + selectionHaptic(); + Navigator.of(context).pop(); + }, + child: Text( + AppLocalizations.of(context)! + .deleteDialogCancel)), + TextButton( + onPressed: () { + selectionHaptic(); + Navigator.of(context).pop(); - for (var i = 0; - i < - (prefs!.getStringList( - "chats") ?? - []) - .length; - i++) { - if (jsonDecode((prefs! - .getStringList( + for (var i = 0; + i < + (prefs!.getStringList( "chats") ?? - [])[i])["uuid"] == - chatUuid) { - List tmp = prefs! - .getStringList( - "chats")!; - tmp.removeAt(i); - prefs!.setStringList( - "chats", tmp); - break; - } + []) + .length; + i++) { + if (jsonDecode((prefs! + .getStringList( + "chats") ?? + [])[i])["uuid"] == + chatUuid) { + List tmp = prefs! + .getStringList( + "chats")!; + tmp.removeAt(i); + prefs!.setStringList( + "chats", tmp); + break; } - messages = []; - chatUuid = null; - setState(() {}); - }, - child: Text(AppLocalizations.of( - context)! - .deleteDialogDelete)) - ]); - }); - }); - } else { - for (var i = 0; - i < - (prefs!.getStringList("chats") ?? []) - .length; - i++) { - if (jsonDecode((prefs!.getStringList("chats") ?? - [])[i])["uuid"] == - chatUuid) { - List tmp = - prefs!.getStringList("chats")!; - tmp.removeAt(i); - prefs!.setStringList("chats", tmp); - break; - } - } - messages = []; - chatUuid = null; - } - setState(() {}); - }, - icon: const Icon(Icons.restart_alt_rounded)) - : const SizedBox.shrink() - ]), - bottom: PreferredSize( - preferredSize: const Size.fromHeight(1), - child: (!chatAllowed && model != null) - ? const LinearProgressIndicator() - : desktopLayout(context) - ? AnimatedOpacity( - opacity: menuVisible ? 1.0 : 0.0, - duration: const Duration(milliseconds: 300), - child: Divider( - height: 2, - color: Theme.of(context) - .colorScheme - .onSurface - .withAlpha(20))) - : const SizedBox.shrink()), - automaticallyImplyLeading: !desktopLayoutRequired(context)), - body: Row( - children: [ - desktopLayoutRequired(context) - ? SizedBox( - width: 304, - height: double.infinity, - child: VisibilityDetector( - key: const Key("menuVisible"), - onVisibilityChanged: (VisibilityInfo info) { - if (settingsOpen) return; - menuVisible = info.visibleFraction > 0; - try { - setState(() {}); - } catch (_) {} - }, - child: AnimatedOpacity( - opacity: menuVisible ? 1.0 : 0.0, - duration: const Duration(milliseconds: 300), - child: ListView( - children: sidebar(context, setState))))) - : const SizedBox.shrink(), - desktopLayout(context) - ? AnimatedOpacity( - opacity: menuVisible ? 1.0 : 0.0, - duration: const Duration(milliseconds: 300), - child: VerticalDivider( - width: 2, - color: Theme.of(context) - .colorScheme - .onSurface - .withAlpha(20))) - : const SizedBox.shrink(), - Expanded( - child: Center( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.max, - children: [ - Flexible( - child: Container( - constraints: const BoxConstraints(maxWidth: 1000), - child: Chat( - messages: messages, - textMessageBuilder: (p0, - {required messageWidth, - required showName}) { - var white = - const TextStyle(color: Colors.white); - bool greyed = false; - String text = p0.text; - if (text.trim() == "") { - text = - "_Empty AI response, try restarting conversation_"; - greyed = true; - } - return Padding( - padding: const EdgeInsets.only( - left: 20, - right: 23, - top: 17, - bottom: 17), - child: Theme( - data: Theme.of(context).copyWith( - scrollbarTheme: - const ScrollbarThemeData( - thumbColor: - WidgetStatePropertyAll( - Colors.grey))), - child: MarkdownBody( - data: text, - onTapLink: (text, href, title) async { - selectionHaptic(); - try { - var url = Uri.parse(href!); - if (await canLaunchUrl(url)) { - launchUrl( - mode: LaunchMode - .inAppBrowserView, - url); - } else { - throw Exception(); } - } catch (_) { - // ignore: use_build_context_synchronously - ScaffoldMessenger.of(context) - .showSnackBar(SnackBar( - content: Text( - AppLocalizations.of( - // ignore: use_build_context_synchronously - context)! - .settingsHostInvalid( - "url")), - showCloseIcon: true)); - } - }, - extensionSet: md.ExtensionSet( - md.ExtensionSet.gitHubFlavored - .blockSyntaxes, - [ - md.EmojiSyntax(), - ...md - .ExtensionSet - .gitHubFlavored - .inlineSyntaxes - ], - ), - imageBuilder: (uri, title, alt) { - if (uri.isAbsolute) { - return Image.network( - uri.toString(), - errorBuilder: (context, - error, stackTrace) { - return InkWell( - onTap: () { - selectionHaptic(); - ScaffoldMessenger.of( - context) - .showSnackBar(SnackBar( - content: Text( - AppLocalizations.of( - context)! - .notAValidImage), - showCloseIcon: - true)); - }, - child: Container( - decoration: BoxDecoration( - borderRadius: - BorderRadius - .circular( - 8), - color: Theme.of(context) - .brightness == - Brightness - .light - ? Colors.white - : Colors - .black), - padding: - const EdgeInsets.only( - left: 100, - right: 100, - top: 32), - child: const Image( - image: AssetImage( - "assets/logo512error.png")))); - }); + messages = []; + chatUuid = null; + setState(() {}); + }, + child: Text( + AppLocalizations.of(context)! + .deleteDialogDelete)) + ]); + }); + }); + } else { + for (var i = 0; + i < + (prefs!.getStringList("chats") ?? []) + .length; + i++) { + if (jsonDecode((prefs!.getStringList("chats") ?? + [])[i])["uuid"] == + chatUuid) { + List tmp = + prefs!.getStringList("chats")!; + tmp.removeAt(i); + prefs!.setStringList("chats", tmp); + break; + } + } + messages = []; + chatUuid = null; + } + setState(() {}); + }, + icon: const Icon(Icons.restart_alt_rounded)) + : const SizedBox.shrink() + ]), + bottom: PreferredSize( + preferredSize: const Size.fromHeight(1), + child: (!chatAllowed && model != null) + ? const LinearProgressIndicator() + : desktopLayout(context) + ? AnimatedOpacity( + opacity: menuVisible ? 1.0 : 0.0, + duration: const Duration(milliseconds: 300), + child: Divider( + height: 2, + color: Theme.of(context) + .colorScheme + .onSurface + .withAlpha(20))) + : const SizedBox.shrink()), + automaticallyImplyLeading: !desktopLayoutRequired(context)), + body: Row( + children: [ + desktopLayoutRequired(context) + ? SizedBox( + width: 304, + height: double.infinity, + child: VisibilityDetector( + key: const Key("menuVisible"), + onVisibilityChanged: (VisibilityInfo info) { + if (settingsOpen) return; + menuVisible = info.visibleFraction > 0; + try { + setState(() {}); + } catch (_) {} + }, + child: AnimatedOpacity( + opacity: menuVisible ? 1.0 : 0.0, + duration: const Duration(milliseconds: 300), + child: ListView( + children: sidebar(context, setState))))) + : const SizedBox.shrink(), + desktopLayout(context) + ? AnimatedOpacity( + opacity: menuVisible ? 1.0 : 0.0, + duration: const Duration(milliseconds: 300), + child: VerticalDivider( + width: 2, + color: Theme.of(context) + .colorScheme + .onSurface + .withAlpha(20))) + : const SizedBox.shrink(), + Expanded( + child: Center( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + Flexible( + child: Container( + constraints: const BoxConstraints(maxWidth: 1000), + child: Chat( + messages: messages, + textMessageBuilder: (p0, + {required messageWidth, required showName}) { + var white = + const TextStyle(color: Colors.white); + bool greyed = false; + String text = p0.text; + if (text.trim() == "") { + text = + "_Empty AI response, try restarting conversation_"; + greyed = true; + } + return Padding( + padding: const EdgeInsets.only( + left: 20, + right: 23, + top: 17, + bottom: 17), + child: Theme( + data: Theme.of(context).copyWith( + scrollbarTheme: + const ScrollbarThemeData( + thumbColor: + WidgetStatePropertyAll( + Colors.grey))), + child: MarkdownBody( + data: text, + onTapLink: (text, href, title) async { + selectionHaptic(); + try { + var url = Uri.parse(href!); + if (await canLaunchUrl(url)) { + launchUrl( + mode: LaunchMode + .inAppBrowserView, + url); } else { + throw Exception(); + } + } catch (_) { + // ignore: use_build_context_synchronously + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar( + content: Text( + AppLocalizations.of( + // ignore: use_build_context_synchronously + context)! + .settingsHostInvalid( + "url")), + showCloseIcon: true)); + } + }, + extensionSet: md.ExtensionSet( + md.ExtensionSet.gitHubFlavored + .blockSyntaxes, + [ + md.EmojiSyntax(), + ...md.ExtensionSet.gitHubFlavored + .inlineSyntaxes + ], + ), + imageBuilder: (uri, title, alt) { + if (uri.isAbsolute) { + return Image.network( + uri.toString(), errorBuilder: + (context, error, + stackTrace) { return InkWell( onTap: () { selectionHaptic(); @@ -1371,559 +1300,594 @@ class _MainAppState extends State { child: const Image( image: AssetImage( "assets/logo512error.png")))); - } - }, - styleSheet: (p0.author == user) - ? MarkdownStyleSheet( - p: const TextStyle( - color: Colors.white, - fontSize: 16, - fontWeight: - FontWeight.w500), - blockquoteDecoration: - BoxDecoration( - color: Colors.grey[800], - borderRadius: - BorderRadius.circular( - 8), - ), - code: const TextStyle( - color: Colors.black, - backgroundColor: - Colors.white), - codeblockDecoration: - BoxDecoration( - color: Colors.white, - borderRadius: - BorderRadius.circular( - 8)), - h1: white, - h2: white, - h3: white, - h4: white, - h5: white, - h6: white, - listBullet: white, - horizontalRuleDecoration: BoxDecoration( - border: Border( - top: BorderSide( - color: Colors - .grey[800]!, - width: 1))), - tableBorder: TableBorder.all( - color: Colors.white), - tableBody: white) - : (Theme.of(context).brightness == - Brightness.light) - ? MarkdownStyleSheet( - p: TextStyle( - color: greyed ? Colors.grey : Colors.black, - fontSize: 16, - fontWeight: FontWeight.w500), - blockquoteDecoration: BoxDecoration( - color: - Colors.grey[200], + }); + } else { + return InkWell( + onTap: () { + selectionHaptic(); + ScaffoldMessenger.of( + context) + .showSnackBar(SnackBar( + content: Text( + AppLocalizations.of( + context)! + .notAValidImage), + showCloseIcon: + true)); + }, + child: Container( + decoration: BoxDecoration( borderRadius: BorderRadius .circular(8), - ), - code: const TextStyle(color: Colors.white, backgroundColor: Colors.black), - codeblockDecoration: BoxDecoration(color: Colors.black, borderRadius: BorderRadius.circular(8)), - horizontalRuleDecoration: BoxDecoration(border: Border(top: BorderSide(color: Colors.grey[200]!, width: 1)))) - : MarkdownStyleSheet( - p: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w500), - blockquoteDecoration: BoxDecoration( - color: - Colors.grey[800]!, - borderRadius: - BorderRadius - .circular(8), - ), - code: const TextStyle(color: Colors.black, backgroundColor: Colors.white), - codeblockDecoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)), - horizontalRuleDecoration: BoxDecoration(border: Border(top: BorderSide(color: Colors.grey[200]!, width: 1))))), - )); - }, - imageMessageBuilder: (p0, - {required messageWidth}) { - return SizedBox( - width: desktopLayout(context) - ? 360.0 - : 160.0, - child: MarkdownBody( - data: "![${p0.name}](${p0.uri})")); - }, - disableImageGallery: true, - emptyState: Center( - child: VisibilityDetector( - key: const Key("logoVisible"), - onVisibilityChanged: - (VisibilityInfo info) { - if (settingsOpen) return; - logoVisible = - info.visibleFraction > 0; - try { - setState(() {}); - } catch (_) {} - }, - child: AnimatedOpacity( - opacity: logoVisible ? 1.0 : 0.0, - duration: const Duration( - milliseconds: 500), - child: const ImageIcon(AssetImage("assets/logo512.png"), - size: 44)))), - onSendPressed: (p0) { - send(p0.text, context, setState); - }, - onMessageDoubleTap: (context, p1) { - selectionHaptic(); - if (!chatAllowed) return; - if (p1.author == assistant) return; - for (var i = 0; i < messages.length; i++) { - if (messages[i].id == p1.id) { - List messageList = - (jsonDecode(jsonEncode(messages)) - as List) - .reversed - .toList(); - bool found = false; - List index = []; - for (var j = 0; - j < messageList.length; - j++) { - if (messageList[j]["id"] == p1.id) { - found = true; - } - if (found) { - index.add(messageList[j]["id"]); - } - } - for (var j = 0; j < index.length; j++) { - for (var k = 0; - k < messages.length; - k++) { - if (messages[k].id == index[j]) { - messages.removeAt(k); - } - } - } - break; - } - } - saveChat(chatUuid!, setState); - setState(() {}); - }, - onMessageLongPress: (context, p1) async { - selectionHaptic(); - - if (!(prefs!.getBool("enableEditing") ?? - true)) { - return; - } - - var index = -1; - if (!chatAllowed) return; - for (var i = 0; i < messages.length; i++) { - if (messages[i].id == p1.id) { - index = i; - break; - } - } - - var text = - (messages[index] as types.TextMessage) - .text; - var input = await prompt( - context, - title: AppLocalizations.of(context)! - .dialogEditMessageTitle, - value: text, - keyboard: TextInputType.multiline, - maxLines: (text.length >= 100) - ? 10 - : ((text.length >= 50) ? 5 : 3), - ); - if (input == "") return; - - messages[index] = types.TextMessage( - author: p1.author, - createdAt: p1.createdAt, - id: p1.id, - text: input, - ); - setState(() {}); - }, - onAttachmentPressed: (!multimodal) - ? (prefs?.getBool("voiceModeEnabled") ?? - false) - ? (model != null) - ? () { - selectionHaptic(); - setMainState = setState; - settingsOpen = true; - logoVisible = false; - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => - const ScreenVoice())); - } - : null - : null - : () { - selectionHaptic(); - if (!chatAllowed || model == null) { - return; - } - if (desktopFeature()) { - FilePicker.platform - .pickFiles(type: FileType.image) - .then((value) async { - if (value == null) return; - if (!multimodal) return; - - var encoded = base64.encode( - await File( - value.files.first.path!) - .readAsBytes()); - messages.insert( - 0, - types.ImageMessage( - author: user, - id: const Uuid().v4(), - name: - value.files.first.name, - size: - value.files.first.size, - uri: - "data:image/png;base64,$encoded")); - - setState(() {}); - }); - - return; - } - showModalBottomSheet( - context: context, - builder: (context) { - return Container( - width: double.infinity, - padding: - const EdgeInsets.only( - left: 16, - right: 16, - top: 16), - child: Column( - mainAxisSize: - MainAxisSize.min, - children: [ - (prefs?.getBool( - "voiceModeEnabled") ?? - false) - ? SizedBox( - width: double - .infinity, - child: OutlinedButton - .icon( - onPressed: - () async { - selectionHaptic(); - Navigator.of(context) - .pop(); - setMainState = - setState; - settingsOpen = - true; - logoVisible = - false; - Navigator.of(context) - .push(MaterialPageRoute(builder: (context) => const ScreenVoice())); - }, - icon: const Icon(Icons - .headphones_rounded), - label: Text(AppLocalizations.of(context)! - .settingsTitleVoice))) - : const SizedBox - .shrink(), - (prefs?.getBool( - "voiceModeEnabled") ?? - false) - ? const SizedBox( - height: 8) - : const SizedBox - .shrink(), - SizedBox( - width: - double.infinity, - child: OutlinedButton - .icon( - onPressed: - () async { - selectionHaptic(); - - Navigator.of( - context) - .pop(); - final result = - await ImagePicker() - .pickImage( - source: - ImageSource.camera, - ); - if (result == - null) { - return; - } - - final bytes = - await result - .readAsBytes(); - final image = - await decodeImageFromList( - bytes); - - final message = - types - .ImageMessage( - author: - user, - createdAt: - DateTime.now().millisecondsSinceEpoch, - height: image - .height - .toDouble(), - id: const Uuid() - .v4(), - name: result - .name, - size: bytes - .length, - uri: result - .path, - width: image - .width - .toDouble(), - ); - - messages.insert( - 0, - message); - setState( - () {}); - selectionHaptic(); - }, - icon: const Icon( - Icons - .photo_camera_rounded), - label: Text( - AppLocalizations.of(context)! - .takeImage))), - const SizedBox( - height: 8), - SizedBox( - width: - double.infinity, - child: OutlinedButton - .icon( - onPressed: - () async { - selectionHaptic(); - - Navigator.of( - context) - .pop(); - final result = - await ImagePicker() - .pickImage( - source: - ImageSource.gallery, - ); - if (result == - null) { - return; - } - - final bytes = - await result - .readAsBytes(); - final image = - await decodeImageFromList( - bytes); - - final message = - types - .ImageMessage( - author: - user, - createdAt: - DateTime.now().millisecondsSinceEpoch, - height: image - .height - .toDouble(), - id: const Uuid() - .v4(), - name: result - .name, - size: bytes - .length, - uri: result - .path, - width: image - .width - .toDouble(), - ); - - messages.insert( - 0, - message); - setState( - () {}); - selectionHaptic(); - }, - icon: const Icon( - Icons - .image_rounded), - label: Text( - AppLocalizations.of(context)! - .uploadImage))) - ])); - }); + color: Theme.of(context) + .brightness == + Brightness + .light + ? Colors.white + : Colors.black), + padding: + const EdgeInsets.only( + left: 100, + right: 100, + top: 32), + child: const Image( + image: AssetImage( + "assets/logo512error.png")))); + } + }, + styleSheet: (p0.author == user) + ? MarkdownStyleSheet( + p: const TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: + FontWeight.w500), + blockquoteDecoration: + BoxDecoration( + color: Colors.grey[800], + borderRadius: + BorderRadius.circular( + 8), + ), + code: const TextStyle( + color: Colors.black, + backgroundColor: + Colors.white), + codeblockDecoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular( + 8)), + h1: white, + h2: white, + h3: white, + h4: white, + h5: white, + h6: white, + listBullet: white, + horizontalRuleDecoration: BoxDecoration( + border: Border( + top: BorderSide( + color: Colors + .grey[800]!, + width: 1))), + tableBorder: TableBorder.all( + color: Colors.white), + tableBody: white) + : (Theme.of(context).brightness == + Brightness.light) + ? MarkdownStyleSheet( + p: TextStyle( + color: greyed + ? Colors.grey + : Colors.black, + fontSize: 16, + fontWeight: + FontWeight.w500), + blockquoteDecoration: + BoxDecoration( + color: Colors.grey[200], + borderRadius: + BorderRadius + .circular(8), + ), + code: const TextStyle( + color: Colors.white, + backgroundColor: Colors.black), + codeblockDecoration: BoxDecoration(color: Colors.black, borderRadius: BorderRadius.circular(8)), + horizontalRuleDecoration: BoxDecoration(border: Border(top: BorderSide(color: Colors.grey[200]!, width: 1)))) + : MarkdownStyleSheet( + p: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w500), + blockquoteDecoration: BoxDecoration( + color: + Colors.grey[800]!, + borderRadius: + BorderRadius + .circular(8), + ), + code: const TextStyle(color: Colors.black, backgroundColor: Colors.white), + codeblockDecoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)), + horizontalRuleDecoration: BoxDecoration(border: Border(top: BorderSide(color: Colors.grey[200]!, width: 1))))), + )); + }, + imageMessageBuilder: (p0, + {required messageWidth}) { + return SizedBox( + width: + desktopLayout(context) ? 360.0 : 160.0, + child: MarkdownBody( + data: "![${p0.name}](${p0.uri})")); + }, + disableImageGallery: true, + emptyState: Center( + child: VisibilityDetector( + key: const Key("logoVisible"), + onVisibilityChanged: + (VisibilityInfo info) { + if (settingsOpen) return; + logoVisible = info.visibleFraction > 0; + try { + setState(() {}); + } catch (_) {} }, - l10n: ChatL10nEn( - inputPlaceholder: AppLocalizations.of(context)! - .messageInputPlaceholder, - attachmentButtonAccessibilityLabel: - AppLocalizations.of(context)! - .tooltipAttachment, - sendButtonAccessibilityLabel: - AppLocalizations.of(context)! - .tooltipSend), - inputOptions: InputOptions( - keyboardType: TextInputType.multiline, - onTextChanged: (p0) { - setState(() { - sendable = p0.trim().isNotEmpty; - }); + child: AnimatedOpacity( + opacity: logoVisible ? 1.0 : 0.0, + duration: + const Duration(milliseconds: 500), + child: const ImageIcon( + AssetImage("assets/logo512.png"), + size: 44)))), + onSendPressed: (p0) { + send(p0.text, context, setState); + }, + onMessageDoubleTap: (context, p1) { + selectionHaptic(); + if (!chatAllowed) return; + if (p1.author == assistant) return; + for (var i = 0; i < messages.length; i++) { + if (messages[i].id == p1.id) { + List messageList = + (jsonDecode(jsonEncode(messages)) + as List) + .reversed + .toList(); + bool found = false; + List index = []; + for (var j = 0; + j < messageList.length; + j++) { + if (messageList[j]["id"] == p1.id) { + found = true; + } + if (found) { + index.add(messageList[j]["id"]); + } + } + for (var j = 0; j < index.length; j++) { + for (var k = 0; + k < messages.length; + k++) { + if (messages[k].id == index[j]) { + messages.removeAt(k); + } + } + } + break; + } + } + saveChat(chatUuid!, setState); + setState(() {}); + }, + onMessageLongPress: (context, p1) async { + selectionHaptic(); + + if (!(prefs!.getBool("enableEditing") ?? + true)) { + return; + } + + var index = -1; + if (!chatAllowed) return; + for (var i = 0; i < messages.length; i++) { + if (messages[i].id == p1.id) { + index = i; + break; + } + } + + var text = + (messages[index] as types.TextMessage).text; + var input = await prompt( + context, + title: AppLocalizations.of(context)! + .dialogEditMessageTitle, + value: text, + keyboard: TextInputType.multiline, + maxLines: (text.length >= 100) + ? 10 + : ((text.length >= 50) ? 5 : 3), + ); + if (input == "") return; + + messages[index] = types.TextMessage( + author: p1.author, + createdAt: p1.createdAt, + id: p1.id, + text: input, + ); + setState(() {}); + }, + onAttachmentPressed: (!multimodal) + ? (prefs?.getBool("voiceModeEnabled") ?? + false) + ? (model != null) + ? () { + selectionHaptic(); + setMainState = setState; + settingsOpen = true; + logoVisible = false; + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => + const ScreenVoice())); + } + : null + : null + : () { + selectionHaptic(); + if (!chatAllowed || model == null) { + return; + } + if (desktopFeature()) { + FilePicker.platform + .pickFiles(type: FileType.image) + .then((value) async { + if (value == null) return; + if (!multimodal) return; + + var encoded = base64.encode( + await File( + value.files.first.path!) + .readAsBytes()); + messages.insert( + 0, + types.ImageMessage( + author: user, + id: const Uuid().v4(), + name: value.files.first.name, + size: value.files.first.size, + uri: + "data:image/png;base64,$encoded")); + + setState(() {}); + }); + + return; + } + showModalBottomSheet( + context: context, + builder: (context) { + return Container( + width: double.infinity, + padding: const EdgeInsets.only( + left: 16, + right: 16, + top: 16), + child: Column( + mainAxisSize: + MainAxisSize.min, + children: [ + (prefs?.getBool( + "voiceModeEnabled") ?? + false) + ? SizedBox( + width: double + .infinity, + child: OutlinedButton + .icon( + onPressed: + () async { + selectionHaptic(); + Navigator.of(context) + .pop(); + setMainState = + setState; + settingsOpen = + true; + logoVisible = + false; + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => + const ScreenVoice())); + }, + icon: const Icon( + Icons + .headphones_rounded), + label: Text( + AppLocalizations.of(context)! + .settingsTitleVoice))) + : const SizedBox + .shrink(), + (prefs?.getBool( + "voiceModeEnabled") ?? + false) + ? const SizedBox( + height: 8) + : const SizedBox + .shrink(), + SizedBox( + width: + double.infinity, + child: OutlinedButton + .icon( + onPressed: + () async { + selectionHaptic(); + + Navigator.of( + context) + .pop(); + final result = + await ImagePicker() + .pickImage( + source: ImageSource + .camera, + ); + if (result == + null) { + return; + } + + final bytes = + await result + .readAsBytes(); + final image = + await decodeImageFromList( + bytes); + + final message = + types + .ImageMessage( + author: + user, + createdAt: + DateTime.now() + .millisecondsSinceEpoch, + height: image + .height + .toDouble(), + id: const Uuid() + .v4(), + name: result + .name, + size: bytes + .length, + uri: result + .path, + width: image + .width + .toDouble(), + ); + + messages.insert( + 0, + message); + setState( + () {}); + selectionHaptic(); + }, + icon: const Icon( + Icons + .photo_camera_rounded), + label: Text(AppLocalizations.of( + context)! + .takeImage))), + const SizedBox(height: 8), + SizedBox( + width: + double.infinity, + child: OutlinedButton + .icon( + onPressed: + () async { + selectionHaptic(); + + Navigator.of( + context) + .pop(); + final result = + await ImagePicker() + .pickImage( + source: ImageSource + .gallery, + ); + if (result == + null) { + return; + } + + final bytes = + await result + .readAsBytes(); + final image = + await decodeImageFromList( + bytes); + + final message = + types + .ImageMessage( + author: + user, + createdAt: + DateTime.now() + .millisecondsSinceEpoch, + height: image + .height + .toDouble(), + id: const Uuid() + .v4(), + name: result + .name, + size: bytes + .length, + uri: result + .path, + width: image + .width + .toDouble(), + ); + + messages.insert( + 0, + message); + setState( + () {}); + selectionHaptic(); + }, + icon: const Icon( + Icons + .image_rounded), + label: Text(AppLocalizations.of( + context)! + .uploadImage))) + ])); + }); }, - sendButtonVisibilityMode: desktopFeature() - ? SendButtonVisibilityMode.always - : (sendable) - ? SendButtonVisibilityMode.always - : SendButtonVisibilityMode.hidden), - user: user, - hideBackgroundOnEmojiMessages: false, - theme: (Theme.of(context).brightness == - Brightness.light) - ? DefaultChatTheme( - backgroundColor: - themeLight().colorScheme.surface, - primaryColor: - themeLight().colorScheme.primary, - attachmentButtonIcon: !multimodal - ? (prefs?.getBool("voiceModeEnabled") ?? false) - ? Icon(Icons.headphones_rounded, color: Theme.of(context).iconTheme.color) - : null - : Icon(Icons.add_a_photo_rounded, color: Theme.of(context).iconTheme.color), - sendButtonIcon: SizedBox( - height: 24, - child: CircleAvatar( - backgroundColor: Theme.of(context) - .iconTheme - .color, - radius: 12, - child: Icon( - Icons.arrow_upward_rounded, - color: (prefs?.getBool( - "useDeviceTheme") ?? - false) - ? Theme.of(context) - .colorScheme - .surface - : null)), - ), - sendButtonMargin: EdgeInsets.zero, - attachmentButtonMargin: EdgeInsets.zero, - inputBackgroundColor: themeLight().colorScheme.onSurface.withAlpha(10), - inputTextColor: themeLight().colorScheme.onSurface, - inputBorderRadius: BorderRadius.circular(32), - inputPadding: const EdgeInsets.all(16), - inputMargin: EdgeInsets.only(left: (MediaQuery.of(context).viewInsets.bottom == 0.0 && !desktopFeature()) ? 8 : 6, right: (MediaQuery.of(context).viewInsets.bottom == 0.0 && !desktopFeature()) ? 8 : 6, bottom: (MediaQuery.of(context).viewInsets.bottom == 0.0 && !desktopFeature()) ? 0 : 8), - messageMaxWidth: (MediaQuery.of(context).size.width >= 1000) - ? (MediaQuery.of(context).size.width >= 1600) - ? (MediaQuery.of(context).size.width >= 2200) - ? 1900 - : 1300 - : 700 - : 440) - : DarkChatTheme( - backgroundColor: themeDark().colorScheme.surface, - primaryColor: themeDark().colorScheme.primary.withAlpha(40), - secondaryColor: themeDark().colorScheme.primary.withAlpha(20), - attachmentButtonIcon: !multimodal - ? (prefs?.getBool("voiceModeEnabled") ?? false) - ? Icon(Icons.headphones_rounded, color: Theme.of(context).iconTheme.color) - : null - : Icon(Icons.add_a_photo_rounded, color: Theme.of(context).iconTheme.color), - sendButtonIcon: SizedBox( - height: 24, - child: CircleAvatar( - backgroundColor: Theme.of(context) - .iconTheme - .color, - radius: 12, - child: Icon( - Icons.arrow_upward_rounded, - color: (prefs?.getBool( - "useDeviceTheme") ?? - false) - ? Theme.of(context) - .colorScheme - .surface - : null)), - ), - sendButtonMargin: EdgeInsets.zero, - attachmentButtonMargin: EdgeInsets.zero, - inputBackgroundColor: themeDark().colorScheme.onSurface.withAlpha(40), - inputTextColor: themeDark().colorScheme.onSurface, - inputBorderRadius: const BorderRadius.all(Radius.circular(64)), - inputPadding: const EdgeInsets.all(16), - inputMargin: EdgeInsets.only(left: 8, right: 8, bottom: (MediaQuery.of(context).viewInsets.bottom == 0.0 && !desktopFeature()) ? 0 : 8), - messageMaxWidth: (MediaQuery.of(context).size.width >= 1000) - ? (MediaQuery.of(context).size.width >= 1600) - ? (MediaQuery.of(context).size.width >= 2200) - ? 1900 - : 1300 - : 700 - : 440)), - ), + l10n: ChatL10nEn( + inputPlaceholder: AppLocalizations.of(context)! + .messageInputPlaceholder, + attachmentButtonAccessibilityLabel: + AppLocalizations.of(context)! + .tooltipAttachment, + sendButtonAccessibilityLabel: + AppLocalizations.of(context)! + .tooltipSend), + inputOptions: InputOptions( + keyboardType: TextInputType.multiline, + onTextChanged: (p0) { + setState(() { + sendable = p0.trim().isNotEmpty; + }); + }, + sendButtonVisibilityMode: desktopFeature() + ? SendButtonVisibilityMode.always + : (sendable) + ? SendButtonVisibilityMode.always + : SendButtonVisibilityMode.hidden), + user: user, + hideBackgroundOnEmojiMessages: false, + theme: (Theme.of(context).brightness == + Brightness.light) + ? DefaultChatTheme( + backgroundColor: + themeLight().colorScheme.surface, + primaryColor: + themeLight().colorScheme.primary, + attachmentButtonIcon: !multimodal + ? (prefs?.getBool("voiceModeEnabled") ?? false) + ? Icon(Icons.headphones_rounded, color: Theme.of(context).iconTheme.color) + : null + : Icon(Icons.add_a_photo_rounded, color: Theme.of(context).iconTheme.color), + sendButtonIcon: SizedBox( + height: 24, + child: CircleAvatar( + backgroundColor: Theme.of(context) + .iconTheme + .color, + radius: 12, + child: Icon( + Icons.arrow_upward_rounded, + color: (prefs?.getBool( + "useDeviceTheme") ?? + false) + ? Theme.of(context) + .colorScheme + .surface + : null)), + ), + sendButtonMargin: EdgeInsets.zero, + attachmentButtonMargin: EdgeInsets.zero, + inputBackgroundColor: themeLight().colorScheme.onSurface.withAlpha(10), + inputTextColor: themeLight().colorScheme.onSurface, + inputBorderRadius: BorderRadius.circular(32), + inputPadding: const EdgeInsets.all(16), + inputMargin: EdgeInsets.only(left: (MediaQuery.of(context).viewInsets.bottom == 0.0 && !desktopFeature()) ? 8 : 6, right: (MediaQuery.of(context).viewInsets.bottom == 0.0 && !desktopFeature()) ? 8 : 6, bottom: (MediaQuery.of(context).viewInsets.bottom == 0.0 && !desktopFeature()) ? 0 : 8), + messageMaxWidth: (MediaQuery.of(context).size.width >= 1000) + ? (MediaQuery.of(context).size.width >= 1600) + ? (MediaQuery.of(context).size.width >= 2200) + ? 1900 + : 1300 + : 700 + : 440) + : DarkChatTheme( + backgroundColor: themeDark().colorScheme.surface, + primaryColor: themeDark().colorScheme.primary.withAlpha(40), + secondaryColor: themeDark().colorScheme.primary.withAlpha(20), + attachmentButtonIcon: !multimodal + ? (prefs?.getBool("voiceModeEnabled") ?? false) + ? Icon(Icons.headphones_rounded, color: Theme.of(context).iconTheme.color) + : null + : Icon(Icons.add_a_photo_rounded, color: Theme.of(context).iconTheme.color), + sendButtonIcon: SizedBox( + height: 24, + child: CircleAvatar( + backgroundColor: Theme.of(context) + .iconTheme + .color, + radius: 12, + child: Icon( + Icons.arrow_upward_rounded, + color: (prefs?.getBool( + "useDeviceTheme") ?? + false) + ? Theme.of(context) + .colorScheme + .surface + : null)), + ), + sendButtonMargin: EdgeInsets.zero, + attachmentButtonMargin: EdgeInsets.zero, + inputBackgroundColor: themeDark().colorScheme.onSurface.withAlpha(40), + inputTextColor: themeDark().colorScheme.onSurface, + inputBorderRadius: const BorderRadius.all(Radius.circular(64)), + inputPadding: const EdgeInsets.all(16), + inputMargin: EdgeInsets.only(left: 8, right: 8, bottom: (MediaQuery.of(context).viewInsets.bottom == 0.0 && !desktopFeature()) ? 0 : 8), + messageMaxWidth: (MediaQuery.of(context).size.width >= 1000) + ? (MediaQuery.of(context).size.width >= 1600) + ? (MediaQuery.of(context).size.width >= 2200) + ? 1900 + : 1300 + : 700 + : 440)), ), - ], - ), + ), + ], ), ), - ], - ), - drawerEdgeDragWidth: (prefs?.getBool("fixCodeblockScroll") ?? false) - ? null - : (desktopLayout(context) - ? null - : MediaQuery.of(context).size.width), - drawer: Builder(builder: (context) { - if (desktopLayoutRequired(context) && !settingsOpen) { - WidgetsBinding.instance.addPostFrameCallback((_) { - if (Navigator.of(context).canPop()) { - Navigator.of(context).pop(); - } - }); - } - return NavigationDrawer( - onDestinationSelected: (value) { - if (value == 1) { - } else if (value == 2) {} - }, - selectedIndex: 1, - children: sidebar(context, setState)); - })), - ), + ), + ], + ), + drawerEdgeDragWidth: (prefs?.getBool("fixCodeblockScroll") ?? false) + ? null + : (desktopLayout(context) + ? null + : MediaQuery.of(context).size.width), + drawer: Builder(builder: (context) { + if (desktopLayoutRequired(context) && !settingsOpen) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (Navigator.of(context).canPop()) { + Navigator.of(context).pop(); + } + }); + } + return NavigationDrawer( + onDestinationSelected: (value) { + if (value == 1) { + } else if (value == 2) {} + }, + selectedIndex: 1, + children: sidebar(context, setState)); + })), ); } } diff --git a/scripts/build.dart b/scripts/build.dart index 773d7f0..c4711e8 100644 --- a/scripts/build.dart +++ b/scripts/build.dart @@ -32,18 +32,18 @@ void main() async { // ---------- - await execute('Windows x64', flutterExecutable, [ - 'build', - 'windows', - '--obfuscate', - '--split-debug-info=build\\debugWindows' - ]); + // await execute('Windows x64', flutterExecutable, [ + // 'build', + // 'windows', + // '--obfuscate', + // '--split-debug-info=build\\debugWindows' + // ]); - await execute( - 'Windows x64 installer', - 'iscc.exe', - ['windows_installer/x64.iss', '/qp', '/dAppVersion=$version'], - " > Inno Setup is not installed. Please install it from https://www.jrsoftware.org/isdl.php#stable\n Then add the Inno Setup directory to your PATH environment variable."); + // await execute( + // 'Windows x64 installer', + // 'iscc.exe', + // ['windows_installer/x64.iss', '/qp', '/dAppVersion=$version'], + // " > Inno Setup is not installed. Please install it from https://www.jrsoftware.org/isdl.php#stable\n Then add the Inno Setup directory to your PATH environment variable."); // ---------- diff --git a/untranslated_messages.json b/untranslated_messages.json index 3c340eb..685c551 100644 --- a/untranslated_messages.json +++ b/untranslated_messages.json @@ -1,6 +1,5 @@ { "de": [ - "backToExit", "deleteChat", "renameChat", "settingsDescriptionBehavior", @@ -22,7 +21,6 @@ ], "it": [ - "backToExit", "deleteChat", "renameChat", "settingsDescriptionBehavior", @@ -44,7 +42,6 @@ ], "tr": [ - "backToExit", "deleteChat", "renameChat", "settingsDescriptionBehavior", @@ -66,7 +63,6 @@ ], "zh": [ - "backToExit", "deleteChat", "renameChat", "settingsDescriptionBehavior", From e8820162b030f98c8111d6dd017f75161281b38e Mon Sep 17 00:00:00 2001 From: JHubi1 Date: Tue, 20 Aug 2024 19:14:01 +0200 Subject: [PATCH 12/13] Smaller fixes --- l10n.yaml | 1 + lib/main.dart | 97 +++++++++++++---------------------------- lib/settings/voice.dart | 11 ++--- lib/worker/sender.dart | 87 ++++++++++++++++++------------------ pubspec.yaml | 1 + 5 files changed, 81 insertions(+), 116 deletions(-) diff --git a/l10n.yaml b/l10n.yaml index b49b5df..3dee198 100644 --- a/l10n.yaml +++ b/l10n.yaml @@ -1,4 +1,5 @@ arb-dir: lib/l10n template-arb-file: app_en.arb +preferred-supported-locales: en output-localization-file: app_localizations.dart untranslated-messages-file: untranslated_messages.json diff --git a/lib/main.dart b/lib/main.dart index 6d34343..bbcf489 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1261,79 +1261,44 @@ class _MainAppState extends State { ], ), imageBuilder: (uri, title, alt) { + Widget errorImage = InkWell( + onTap: () { + selectionHaptic(); + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar( + content: Text( + AppLocalizations.of( + context)! + .notAValidImage), + showCloseIcon: true)); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: + BorderRadius + .circular(8), + color: Theme.of(context) + .brightness == + Brightness.light + ? Colors.white + : Colors.black), + padding: + const EdgeInsets.only( + left: 100, + right: 100, + top: 32), + child: const Image( + image: AssetImage( + "assets/logo512error.png")))); if (uri.isAbsolute) { return Image.network( uri.toString(), errorBuilder: (context, error, stackTrace) { - return InkWell( - onTap: () { - selectionHaptic(); - ScaffoldMessenger.of( - context) - .showSnackBar(SnackBar( - content: Text( - AppLocalizations.of( - context)! - .notAValidImage), - showCloseIcon: - true)); - }, - child: Container( - decoration: BoxDecoration( - borderRadius: - BorderRadius - .circular( - 8), - color: Theme.of(context) - .brightness == - Brightness - .light - ? Colors.white - : Colors.black), - padding: - const EdgeInsets - .only( - left: 100, - right: 100, - top: 32), - child: const Image( - image: AssetImage( - "assets/logo512error.png")))); + return errorImage; }); } else { - return InkWell( - onTap: () { - selectionHaptic(); - ScaffoldMessenger.of( - context) - .showSnackBar(SnackBar( - content: Text( - AppLocalizations.of( - context)! - .notAValidImage), - showCloseIcon: - true)); - }, - child: Container( - decoration: BoxDecoration( - borderRadius: - BorderRadius - .circular(8), - color: Theme.of(context) - .brightness == - Brightness - .light - ? Colors.white - : Colors.black), - padding: - const EdgeInsets.only( - left: 100, - right: 100, - top: 32), - child: const Image( - image: AssetImage( - "assets/logo512error.png")))); + return errorImage; } }, styleSheet: (p0.author == user) diff --git a/lib/settings/voice.dart b/lib/settings/voice.dart index 0af758a..0de3fe3 100644 --- a/lib/settings/voice.dart +++ b/lib/settings/voice.dart @@ -95,14 +95,10 @@ class _ScreenSettingsVoiceState extends State { : !(permissionBluetooth && permissionRecord) ? AppLocalizations.of(context)! .settingsVoicePermissionNot - : !(prefs!.getBool( - "voiceModeEnabled") ?? - false) - ? AppLocalizations.of(context)! - .settingsVoiceNotEnabled - : AppLocalizations.of(context)! - .settingsVoiceNotSupported, + : AppLocalizations.of(context)! + .settingsVoiceNotSupported, Icons.info_rounded, () { + selectionHaptic(); if (permissionLoading) return; if (!(permissionBluetooth && permissionRecord)) { void load() async { @@ -139,7 +135,6 @@ class _ScreenSettingsVoiceState extends State { } else if (!voiceLanguageOptions.contains( (prefs!.getString("voiceLanguage") ?? "en_US"))) { - selectionHaptic(); ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text(AppLocalizations.of(context)! .settingsVoiceTtsNotSupportedDescription), diff --git a/lib/worker/sender.dart b/lib/worker/sender.dart index d6711df..4759c14 100644 --- a/lib/worker/sender.dart +++ b/lib/worker/sender.dart @@ -79,6 +79,9 @@ Future getTitleAi(List history) async { .replaceAll("_", "") .replaceAll("\n", " ") .trim(); + while (title.contains(" ")) { + title = title.replaceAll(" ", " "); + } return title; } @@ -169,58 +172,58 @@ Future send(String value, BuildContext context, Function setState, baseUrl: "$host/api"); try { - if ((prefs!.getString("requestType") ?? "stream") == "stream") { - final stream = client - .generateChatCompletionStream( - request: llama.GenerateChatCompletionRequest( - model: model!, - messages: history, - keepAlive: int.parse(prefs!.getString("keepAlive") ?? "300")), - ) - .timeout(const Duration(seconds: 30)); + if ((prefs!.getString("requestType") ?? "stream") == "stream") { + final stream = client + .generateChatCompletionStream( + request: llama.GenerateChatCompletionRequest( + model: model!, + messages: history, + keepAlive: int.parse(prefs!.getString("keepAlive") ?? "300")), + ) + .timeout(const Duration(seconds: 30)); - await for (final res in stream) { - text += (res.message?.content ?? ""); - for (var i = 0; i < messages.length; i++) { - if (messages[i].id == newId) { - messages.removeAt(i); - break; + await for (final res in stream) { + text += (res.message?.content ?? ""); + for (var i = 0; i < messages.length; i++) { + if (messages[i].id == newId) { + messages.removeAt(i); + break; + } } + if (chatAllowed) return ""; + // if (text.trim() == "") { + // throw Exception(); + // } + messages.insert( + 0, types.TextMessage(author: assistant, id: newId, text: text)); + if (onStream != null) { + onStream(text, false); + } + setState(() {}); + heavyHaptic(); } + } else { + llama.GenerateChatCompletionResponse request; + request = await client + .generateChatCompletion( + request: llama.GenerateChatCompletionRequest( + model: model!, + messages: history, + keepAlive: int.parse(prefs!.getString("keepAlive") ?? "300")), + ) + .timeout(const Duration(seconds: 30)); if (chatAllowed) return ""; - // if (text.trim() == "") { + // if (request.message!.content.trim() == "") { // throw Exception(); // } messages.insert( - 0, types.TextMessage(author: assistant, id: newId, text: text)); - if (onStream != null) { - onStream(text, false); - } + 0, + types.TextMessage( + author: assistant, id: newId, text: request.message!.content)); + text = request.message!.content; setState(() {}); heavyHaptic(); } - } else { - llama.GenerateChatCompletionResponse request; - request = await client - .generateChatCompletion( - request: llama.GenerateChatCompletionRequest( - model: model!, - messages: history, - keepAlive: int.parse(prefs!.getString("keepAlive") ?? "300")), - ) - .timeout(const Duration(seconds: 30)); - if (chatAllowed) return ""; - // if (request.message!.content.trim() == "") { - // throw Exception(); - // } - messages.insert( - 0, - types.TextMessage( - author: assistant, id: newId, text: request.message!.content)); - text = request.message!.content; - setState(() {}); - heavyHaptic(); - } } catch (e) { for (var i = 0; i < messages.length; i++) { if (messages[i].id == newId) { diff --git a/pubspec.yaml b/pubspec.yaml index bfec6db..f78b8c8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,6 +42,7 @@ dependencies: dynamic_color: ^1.7.0 volume_controller: ^2.0.7 universal_html: ^2.2.4 + dev_dependencies: flutter_test: sdk: flutter From e7a1d93196e77db81f96d586a2236660e791c544 Mon Sep 17 00:00:00 2001 From: JHubi1 Date: Tue, 20 Aug 2024 20:23:57 +0200 Subject: [PATCH 13/13] Fixed title generation, added info message for imported chats --- lib/l10n/app_en.arb | 5 +++++ lib/settings/export.dart | 6 ++++++ lib/worker/setter.dart | 22 +++++++++------------- untranslated_messages.json | 4 ++++ 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index ed39e7f..72e60d1 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -544,6 +544,11 @@ "description": "Text displayed as description for export chats button", "context": "Visible in the settings view" }, + "settingsExportChatsSuccess": "Chats exported successfully", + "@settingsExportChatsSuccess": { + "description": "Text displayed when chats are exported successfully", + "context": "Visible in the settings view" + }, "settingsImportChats": "Import chats", "@settingsImportChats": { "description": "Text displayed as description for import chats button", diff --git a/lib/settings/export.dart b/lib/settings/export.dart index 2a73f55..c5c344d 100644 --- a/lib/settings/export.dart +++ b/lib/settings/export.dart @@ -88,6 +88,12 @@ class _ScreenSettingsExportState extends State { File(path).writeAsString(content); } } + // ignore: use_build_context_synchronously + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + // ignore: use_build_context_synchronously + content: Text(AppLocalizations.of(context)! + .settingsExportChatsSuccess), + showCloseIcon: true)); }), allowMultipleChats ? button( diff --git a/lib/worker/setter.dart b/lib/worker/setter.dart index 3427b00..ea16aad 100644 --- a/lib/worker/setter.dart +++ b/lib/worker/setter.dart @@ -216,12 +216,8 @@ void setModel(BuildContext context, Function setState) { ? ((MediaQuery.of(context) .platformBrightness == Brightness.light) - ? themeLight() - .colorScheme - .secondary - : themeDark() - .colorScheme - .secondary) + ? themeLight().colorScheme.secondary + : themeDark().colorScheme.secondary) : null, labelStyle: (usedIndex == index && !(prefs?.getBool( @@ -245,12 +241,8 @@ void setModel(BuildContext context, Function setState) { : (MediaQuery.of(context) .platformBrightness == Brightness.light) - ?themeLight() - .colorScheme - .primary - : themeDark() - .colorScheme - .primary, + ? themeLight().colorScheme.primary + : themeDark().colorScheme.primary, onSelected: (bool selected) { selectionHaptic(); if (addIndex == index) { @@ -539,7 +531,11 @@ Future prompt(BuildContext context, uuid) { try { var title = await getTitleAi( - await getHistory()); + jsonDecode(jsonDecode( + (prefs!.getStringList( + "chats") ?? + [])[ + i])["messages"])); controller.text = title; setLocalState(() { loading = false; diff --git a/untranslated_messages.json b/untranslated_messages.json index 685c551..c098fe4 100644 --- a/untranslated_messages.json +++ b/untranslated_messages.json @@ -17,6 +17,7 @@ "settingsVoiceTtsNotSupported", "settingsVoiceTtsNotSupportedDescription", "settingsVoiceNotEnabled", + "settingsExportChatsSuccess", "settingsLicenses" ], @@ -38,6 +39,7 @@ "settingsVoiceTtsNotSupported", "settingsVoiceTtsNotSupportedDescription", "settingsVoiceNotEnabled", + "settingsExportChatsSuccess", "settingsLicenses" ], @@ -59,6 +61,7 @@ "settingsVoiceTtsNotSupported", "settingsVoiceTtsNotSupportedDescription", "settingsVoiceNotEnabled", + "settingsExportChatsSuccess", "settingsLicenses" ], @@ -80,6 +83,7 @@ "settingsVoiceTtsNotSupported", "settingsVoiceTtsNotSupportedDescription", "settingsVoiceNotEnabled", + "settingsExportChatsSuccess", "settingsLicenses" ] }