From c41fb53d9c7aba7dd1426f2ab84c8df24eef903a Mon Sep 17 00:00:00 2001 From: JHubi1 Date: Tue, 20 Aug 2024 02:14:05 +0200 Subject: [PATCH] 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",