diff --git a/lib/main.dart b/lib/main.dart index 3f57d17..9163a30 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -823,587 +823,658 @@ class _MainAppState extends State { : Colors.grey[900])) : const SizedBox.shrink(), Expanded( - child: Chat( - messages: messages, - textMessageBuilder: (p0, - {required messageWidth, required showName}) { - var white = const TextStyle(color: Colors.white); - return Padding( - padding: const EdgeInsets.only( - left: 20, right: 23, top: 17, bottom: 17), - child: MarkdownBody( - data: p0.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( - // ignore: use_build_context_synchronously - AppLocalizations.of(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(); + 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); + return Padding( + padding: const EdgeInsets.only( + left: 20, + right: 23, + top: 17, + bottom: 17), + child: MarkdownBody( + data: p0.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( + // ignore: use_build_context_synchronously AppLocalizations.of( context)! - .notAValidImage), + .settingsHostInvalid( + "url")), showCloseIcon: true)); - }, - child: Container( - decoration: BoxDecoration( + } + }, + 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(); + 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")))); + } + }, + 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), - 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(); - 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")))); - } - }, - 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: const TextStyle( - color: 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, - // keyboardDismissBehavior: - // ScrollViewKeyboardDismissBehavior.onDrag, - emptyState: Center( - child: VisibilityDetector( - key: const Key("logoVisible"), - onVisibilityChanged: (VisibilityInfo info) { - if (settingsOpen) return; - logoVisible = info.visibleFraction > 0; - try { - setState(() {}); - } catch (_) {} + ), + 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: const TextStyle( + color: 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)))))); }, - 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())); + imageMessageBuilder: (p0, + {required messageWidth}) { + return SizedBox( + width: + desktopLayout(context) ? 360.0 : 160.0, + child: MarkdownBody( + data: "![${p0.name}](${p0.uri})")); + }, + disableImageGallery: true, + // keyboardDismissBehavior: + // ScrollViewKeyboardDismissBehavior.onDrag, + 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"]); + } } - : 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; + 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(); - 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")); + if (!(prefs!.getBool("enableEditing") ?? + true)) { + return; + } - setState(() {}); - }); + var index = -1; + if (!chatAllowed) return; + for (var i = 0; i < messages.length; i++) { + if (messages[i].id == p1.id) { + index = i; + break; + } + } - 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(); + 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; - 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; - }); - }, - sendButtonVisibilityMode: desktopFeature() - ? SendButtonVisibilityMode.always - : (sendable) - ? SendButtonVisibilityMode.always - : SendButtonVisibilityMode.hidden), - user: user, - hideBackgroundOnEmojiMessages: false, - theme: (Theme.of(context).brightness == Brightness.light) - ? DefaultChatTheme( - backgroundColor: - (theme ?? ThemeData()).colorScheme.surface, - primaryColor: - (theme ?? ThemeData()).colorScheme.primary, - attachmentButtonIcon: !multimodal + messages[index] = types.TextMessage( + author: p1.author, + createdAt: p1.createdAt, + id: p1.id, + text: input, + ); + setState(() {}); + }, + onAttachmentPressed: (!multimodal) ? (prefs?.getBool("voiceModeEnabled") ?? false) - ? Icon(Icons.headphones_rounded, - color: - Theme.of(context).iconTheme.color) + ? (model != null) + ? () { + selectionHaptic(); + setMainState = setState; + settingsOpen = true; + logoVisible = false; + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => + const ScreenVoice())); + } + : null : 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: (theme ?? ThemeData()) - .colorScheme - .onSurface - .withAlpha(10), - inputTextColor: - (theme ?? ThemeData()).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) - : DarkChatTheme( - backgroundColor: (themeDark ?? ThemeData.dark()).colorScheme.surface, - primaryColor: (themeDark ?? ThemeData.dark()).colorScheme.primary.withAlpha(40), - secondaryColor: (themeDark ?? ThemeData.dark()).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 ?? ThemeData()).colorScheme.onSurface.withAlpha(40), - inputTextColor: (themeDark ?? ThemeData()).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))), + : () { + 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))) + ])); + }); + }, + 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: (theme ?? ThemeData()) + .colorScheme + .surface, + primaryColor: (theme ?? ThemeData()).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: (theme ?? ThemeData()).colorScheme.onSurface.withAlpha(10), + inputTextColor: (theme ?? ThemeData()).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) + : DarkChatTheme( + backgroundColor: (themeDark ?? ThemeData.dark()).colorScheme.surface, + primaryColor: (themeDark ?? ThemeData.dark()).colorScheme.primary.withAlpha(40), + secondaryColor: (themeDark ?? ThemeData.dark()).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 ?? ThemeData()).colorScheme.onSurface.withAlpha(40), + inputTextColor: (themeDark ?? ThemeData()).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: diff --git a/lib/worker/desktop.dart b/lib/worker/desktop.dart index e6e7992..a5a7d69 100644 --- a/lib/worker/desktop.dart +++ b/lib/worker/desktop.dart @@ -5,28 +5,23 @@ import 'package:flutter/foundation.dart'; import 'package:bitsdojo_window/bitsdojo_window.dart'; -bool desktopFeature() { - return (Platform.isWindows || Platform.isLinux || Platform.isMacOS); +bool desktopFeature({bool web = false}) { + return (Platform.isWindows || + Platform.isLinux || + Platform.isMacOS || + (web ? kIsWeb : false)); } -bool desktopWebFeature() { - return (Platform.isWindows || Platform.isLinux || Platform.isMacOS || kIsWeb); +bool desktopLayout(BuildContext context, + {bool web = false, double? value, double valueCap = 1000}) { + value ??= MediaQuery.of(context).size.width; + return (desktopFeature(web: web) || value >= valueCap); } -bool desktopLayout(BuildContext context) { - return (desktopFeature() || MediaQuery.of(context).size.width >= 1000); -} - -bool desktopLayoutRequired(BuildContext context) { - return (desktopFeature() && MediaQuery.of(context).size.width >= 1000); -} - -bool desktopWebLayout(BuildContext context) { - return (desktopWebFeature() || MediaQuery.of(context).size.width >= 1000); -} - -bool desktopWebLayoutRequired(BuildContext context) { - return (desktopWebFeature() && MediaQuery.of(context).size.width >= 1000); +bool desktopLayoutRequired(BuildContext context, + {bool web = false, double? value, double valueCap = 1000}) { + value ??= MediaQuery.of(context).size.width; + return (desktopFeature(web: web) && value >= valueCap); } Widget desktopControls(BuildContext context) {