diff --git a/lib/main.dart b/lib/main.dart index bbcf489..afead9e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -424,7 +424,8 @@ class _MainAppState extends State { ? null : () async { selectionHaptic(); - if (!chatAllowed) return; + if (!chatAllowed && + chatUuid == jsonDecode(item)["uuid"]) return; if (!allowSettings) return; String oldTitle = jsonDecode(item)["title"]; var newTitle = await prompt(context, @@ -480,6 +481,10 @@ class _MainAppState extends State { width: 24, child: IconButton( onPressed: () { + if (!chatAllowed && + chatUuid == + jsonDecode(item)["uuid"]) + return; if (!allowMultipleChats) { for (var i = 0; i < @@ -511,124 +516,14 @@ class _MainAppState extends State { return; } if (!allowSettings) { - if (prefs!.getBool( - "askBeforeDeletion") ?? - false) { - 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: - () { - Navigator.of( - context) - .pop(); - }, - child: Text(AppLocalizations.of( - context)! - .deleteDialogCancel)), - TextButton( - onPressed: - () { - Navigator.of( - context) - .pop(); - for (var i = - 0; - i < (prefs!.getStringList("chats") ?? []).length; - i++) { - if (jsonDecode((prefs!.getStringList("chats") ?? [])[i])[ - "uuid"] == - jsonDecode( - item)["uuid"]) { - List - tmp = - prefs!.getStringList("chats")!; - tmp.removeAt( - i); - prefs!.setStringList( - "chats", - tmp); - break; - } - } - if (chatUuid == - jsonDecode( - item)["uuid"]) { - messages = - []; - chatUuid = - null; - if (!desktopLayoutRequired( - context)) { - Navigator.of(context) - .pop(); - } - } - 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"] == - jsonDecode( - item)["uuid"]) { - List tmp = prefs! - .getStringList( - "chats")!; - tmp.removeAt(i); - prefs!.setStringList( - "chats", tmp); - break; - } - } - if (chatUuid == - jsonDecode(item)["uuid"]) { - messages = []; - chatUuid = null; - if (!desktopLayoutRequired( - context)) { - Navigator.of(context).pop(); - } - } - setState(() {}); - } + deleteChatDialog( + context, setState, + additionalCondition: false, + uuid: + jsonDecode(item)["uuid"], + popSidebar: true); return; } - if (!chatAllowed) return; if (!desktopLayoutRequired( context)) { Navigator.of(context).pop(); @@ -636,7 +531,22 @@ class _MainAppState extends State { showModalBottomSheet( context: context, builder: (context) { - return Padding( + return Container( + decoration: (Theme.of(context) + .brightness == + Brightness.dark) + ? BoxDecoration( + border: Border.all( + color: Colors + .white), + borderRadius: const BorderRadius.only( + topLeft: + Radius.circular( + 26), + topRight: + Radius.circular( + 26))) + : null, padding: const EdgeInsets.only( left: 16, @@ -655,68 +565,11 @@ class _MainAppState extends State { () { Navigator.of(context) .pop(); - if (prefs!.getBool("askBeforeDeletion") ?? - false) { - 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: () { - Navigator.of(context).pop(); - }, - child: Text(AppLocalizations.of(context)!.deleteDialogCancel)), - TextButton( - onPressed: () { - Navigator.of(context).pop(); - for (var i = 0; i < (prefs!.getStringList("chats") ?? []).length; i++) { - if (jsonDecode((prefs!.getStringList("chats") ?? [])[i])["uuid"] == jsonDecode(item)["uuid"]) { - List tmp = prefs!.getStringList("chats")!; - tmp.removeAt(i); - prefs!.setStringList("chats", tmp); - break; - } - } - if (chatUuid == jsonDecode(item)["uuid"]) { - messages = []; - chatUuid = null; - if (!desktopLayoutRequired(context)) { - Navigator.of(context).pop(); - } - } - 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"] == jsonDecode(item)["uuid"]) { - List tmp = prefs!.getStringList("chats")!; - tmp.removeAt(i); - prefs!.setStringList("chats", tmp); - break; - } - } - if (chatUuid == - jsonDecode(item)["uuid"]) { - messages = []; - chatUuid = null; - if (!desktopLayoutRequired(context)) { - Navigator.of(context).pop(); - } - } - setState(() {}); - } + deleteChatDialog( + context, + setState, + uuid: jsonDecode(item)["uuid"], + popSidebar: true); }, icon: const Icon(Icons .delete_forever_rounded), @@ -793,48 +646,10 @@ class _MainAppState extends State { ? DismissDirection.startToEnd : DismissDirection.none, confirmDismiss: (direction) async { - bool returnValue = false; - if (!chatAllowed) return false; - - if (prefs!.getBool("askBeforeDeletion") ?? false) { - await 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(); - returnValue = false; - }, - child: Text(AppLocalizations.of(context)! - .deleteDialogCancel)), - TextButton( - onPressed: () { - selectionHaptic(); - Navigator.of(context).pop(); - returnValue = true; - }, - child: Text(AppLocalizations.of(context)! - .deleteDialogDelete)) - ]); - }); - }); - } else { - returnValue = true; - } - return returnValue; + if (!chatAllowed && chatUuid == jsonDecode(item)["uuid"]) + return false; + return await deleteChatDialog(context, setState, + takeAction: false); }, onDismissed: (direction) { selectionHaptic(); @@ -937,15 +752,18 @@ class _MainAppState extends State { resetSystemNavigation(context); Widget selector = InkWell( - onTap: () { - if (host == null) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(AppLocalizations.of(context)!.noHostSelected), - showCloseIcon: true)); - return; - } - setModel(context, setState); - }, + onTap: !useModel + ? () { + if (host == null) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: + Text(AppLocalizations.of(context)!.noHostSelected), + showCloseIcon: true)); + return; + } + setModel(context, setState); + } + : null, splashFactory: NoSplash.splashFactory, highlightColor: Colors.transparent, enableFeedback: false, @@ -1053,90 +871,8 @@ class _MainAppState extends State { 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(); - - 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(() {}); - }, - 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(() {}); + deleteChatDialog(context, setState, + additionalCondition: messages.isNotEmpty); }, icon: const Icon(Icons.restart_alt_rounded)) : const SizedBox.shrink() @@ -1533,54 +1269,33 @@ class _MainAppState extends State { context: context, builder: (context) { return Container( + decoration: (Theme.of(context) + .brightness == + Brightness.dark) + ? BoxDecoration( + border: Border.all( + color: + Colors.white), + borderRadius: + const BorderRadius.only( + topLeft: Radius + .circular( + 26), + topRight: + Radius.circular( + 26))) + : null, 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( + child: + Column(mainAxisSize: MainAxisSize.min, children: [ + (prefs?.getBool( + "voiceModeEnabled") ?? + false) + ? SizedBox( width: double.infinity, child: OutlinedButton @@ -1588,134 +1303,164 @@ class _MainAppState extends State { 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(); + setMainState = + setState; + settingsOpen = + true; + logoVisible = + false; + Navigator.of( + context) + .push(MaterialPageRoute( + builder: (context) => + const ScreenVoice())); }, icon: const Icon( Icons - .photo_camera_rounded), + .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(); + 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 + Navigator.of( + context) + .pop(); + final result = + await ImagePicker() + .pickImage( + source: + ImageSource .gallery, - ); - if (result == - null) { - return; - } + ); + if (result == + null) { + return; + } - final bytes = - await result - .readAsBytes(); - final image = - await decodeImageFromList( - bytes); + 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(), - ); + 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( + messages.insert( + 0, message); + setState(() {}); + selectionHaptic(); + }, + icon: const Icon(Icons + .image_rounded), + label: Text( + AppLocalizations.of( context)! .uploadImage))) - ])); + ])); }); }, l10n: ChatL10nEn( diff --git a/lib/screen_settings.dart b/lib/screen_settings.dart index 7bf0b92..3675c53 100644 --- a/lib/screen_settings.dart +++ b/lib/screen_settings.dart @@ -125,14 +125,15 @@ Widget title(String text, {double top = 16, double bottom = 16}) { Padding( padding: const EdgeInsets.only(left: 24, right: 24), child: Text(text)), - const Expanded(child: Divider()) + const Expanded(child: Divider(height: 1)) ])); } Widget titleDivider({double? top, double? bottom, BuildContext? context}) { top ??= (context != null && desktopLayoutNotRequired(context)) ? 32 : 16; bottom ??= (context != null && desktopLayoutNotRequired(context)) ? 32 : 16; - return Padding( + return AnimatedContainer( + duration: const Duration(milliseconds: 200), padding: EdgeInsets.only(left: 8, right: 8, top: top, bottom: bottom), child: const Row( mainAxisSize: MainAxisSize.max, @@ -143,13 +144,12 @@ Widget verticalTitleDivider( {double? left, double? right, BuildContext? context}) { left ??= (context != null && desktopLayoutNotRequired(context)) ? 32 : 16; right ??= (context != null && desktopLayoutNotRequired(context)) ? 32 : 16; - return Padding( + return AnimatedContainer( + duration: const Duration(milliseconds: 200), padding: EdgeInsets.only(left: left, right: right, top: 8, bottom: 8), - child: const Row(mainAxisSize: MainAxisSize.max, children: [ - // Expanded(child: - VerticalDivider() - // ), - ])); + child: const Row( + mainAxisSize: MainAxisSize.max, + children: [VerticalDivider(width: 1)])); } Widget button(String text, IconData? icon, void Function()? onPressed, @@ -359,6 +359,7 @@ class _ScreenSettingsState extends State { TextField( controller: hostInputController, keyboardType: TextInputType.url, + autofillHints: const [AutofillHints.url], readOnly: useHost, onSubmitted: (value) { selectionHaptic(); diff --git a/lib/settings/about.dart b/lib/settings/about.dart index 854bfc2..849bfa1 100644 --- a/lib/settings/about.dart +++ b/lib/settings/about.dart @@ -42,123 +42,130 @@ class _ScreenSettingsAboutState extends State { Expanded(child: SizedBox(height: 200, child: MoveWindow())) ]), actions: desktopControlsActions(context)), - body: Padding( - padding: const EdgeInsets.only(left: 16, right: 16), - child: Column(children: [ - Expanded( - child: ListView(children: [ - // const SizedBox(height: 8), - button( - AppLocalizations.of(context)! - .settingsVersion(currentVersion ?? ""), - Icons.verified_rounded, - null), - (updateStatus == "notAvailable") - ? const SizedBox.shrink() - : button( - (!updateChecked - ? AppLocalizations.of(context)! - .settingsUpdateCheck - : updateLoading - ? AppLocalizations.of(context)! - .settingsUpdateChecking - : (updateStatus == "rateLimit") - ? AppLocalizations.of(context)! - .settingsUpdateRateLimit - : (updateStatus != "ok") - ? AppLocalizations.of(context)! - .settingsUpdateIssue - : (Version.parse(latestVersion ?? - "1.0.0") > - Version.parse( - currentVersion ?? - "2.0.0")) - ? AppLocalizations.of(context)! - .settingsUpdateAvailable( - latestVersion!) - : AppLocalizations.of(context)! - .settingsUpdateLatest), - ((updateStatus != "ok") - ? Icons.warning_rounded - : (Version.parse(latestVersion ?? "1.0.0") > - Version.parse( - currentVersion ?? "2.0.0")) - ? Icons.info_outline_rounded - : Icons.update_rounded), () { - if (updateLoading) return; - selectionHaptic(); - if ((Version.parse(latestVersion ?? "1.0.0") > - Version.parse(currentVersion ?? "2.0.0")) && - (updateStatus == "ok")) { - updateDialog(context, title); - } else { - checkUpdate(setState); - return; - } - }), - (updateStatus == "notAvailable") - ? const SizedBox.shrink() - : toggle( - context, - AppLocalizations.of(context)! - .settingsCheckForUpdates, - (prefs!.getBool("checkUpdateOnSettingsOpen") ?? - false), (value) { - selectionHaptic(); - prefs!.setBool("checkUpdateOnSettingsOpen", value); - setState(() {}); - }), - titleDivider(context: context), - button(AppLocalizations.of(context)!.settingsGithub, - SimpleIcons.github, () { - selectionHaptic(); - launchUrl( - mode: LaunchMode.inAppBrowserView, - Uri.parse(repoUrl)); - }), - button(AppLocalizations.of(context)!.settingsReportIssue, - Icons.report_rounded, () { - selectionHaptic(); - launchUrl( - mode: LaunchMode.inAppBrowserView, - Uri.parse("$repoUrl/issues")); - }), - button(AppLocalizations.of(context)!.settingsLicenses, - Icons.gavel_rounded, () { - selectionHaptic(); - 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) - ]), - ) - ]))), + body: Center( + child: Container( + constraints: const BoxConstraints(maxWidth: 1000), + padding: const EdgeInsets.only(left: 16, right: 16), + child: Column(children: [ + Expanded( + child: ListView(children: [ + // const SizedBox(height: 8), + button( + AppLocalizations.of(context)! + .settingsVersion(currentVersion ?? ""), + Icons.verified_rounded, + null), + (updateStatus == "notAvailable") + ? const SizedBox.shrink() + : button( + (!updateChecked + ? AppLocalizations.of(context)! + .settingsUpdateCheck + : updateLoading + ? AppLocalizations.of(context)! + .settingsUpdateChecking + : (updateStatus == "rateLimit") + ? AppLocalizations.of(context)! + .settingsUpdateRateLimit + : (updateStatus != "ok") + ? AppLocalizations.of(context)! + .settingsUpdateIssue + : (Version.parse(latestVersion ?? + "1.0.0") > + Version.parse( + currentVersion ?? + "2.0.0")) + ? AppLocalizations.of( + context)! + .settingsUpdateAvailable( + latestVersion!) + : AppLocalizations.of( + context)! + .settingsUpdateLatest), + ((updateStatus != "ok") + ? Icons.warning_rounded + : (Version.parse(latestVersion ?? "1.0.0") > + Version.parse( + currentVersion ?? "2.0.0")) + ? Icons.info_outline_rounded + : Icons.update_rounded), () { + if (updateLoading) return; + selectionHaptic(); + if ((Version.parse(latestVersion ?? "1.0.0") > + Version.parse( + currentVersion ?? "2.0.0")) && + (updateStatus == "ok")) { + updateDialog(context, title); + } else { + checkUpdate(setState); + return; + } + }), + (updateStatus == "notAvailable") + ? const SizedBox.shrink() + : toggle( + context, + AppLocalizations.of(context)! + .settingsCheckForUpdates, + (prefs!.getBool("checkUpdateOnSettingsOpen") ?? + false), (value) { + selectionHaptic(); + prefs! + .setBool("checkUpdateOnSettingsOpen", value); + setState(() {}); + }), + titleDivider(context: context), + button(AppLocalizations.of(context)!.settingsGithub, + SimpleIcons.github, () { + selectionHaptic(); + launchUrl( + mode: LaunchMode.inAppBrowserView, + Uri.parse(repoUrl)); + }), + button(AppLocalizations.of(context)!.settingsReportIssue, + Icons.report_rounded, () { + selectionHaptic(); + launchUrl( + mode: LaunchMode.inAppBrowserView, + Uri.parse("$repoUrl/issues")); + }), + button(AppLocalizations.of(context)!.settingsLicenses, + Icons.gavel_rounded, () { + selectionHaptic(); + 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/lib/settings/behavior.dart b/lib/settings/behavior.dart index aa5220d..7b56957 100644 --- a/lib/settings/behavior.dart +++ b/lib/settings/behavior.dart @@ -37,71 +37,75 @@ class _ScreenSettingsBehaviorState extends State { Expanded(child: SizedBox(height: 200, child: MoveWindow())) ]), actions: desktopControlsActions(context)), - body: Padding( - padding: const EdgeInsets.only(left: 16, right: 16), - child: Column(children: [ - Expanded( - child: ListView(children: [ - const SizedBox(height: 8), - TextField( - controller: systemInputController, - keyboardType: TextInputType.multiline, - maxLines: desktopLayoutNotRequired(context) ? 5 : 2, - decoration: InputDecoration( - labelText: AppLocalizations.of(context)! - .settingsSystemMessage, - hintText: "You are a helpful assistant", - suffixIcon: IconButton( - tooltip: - AppLocalizations.of(context)!.tooltipSave, - onPressed: () { - selectionHaptic(); - prefs?.setString( - "system", - (systemInputController.text.isNotEmpty) - ? systemInputController.text - : "You are a helpful assistant"); - }, - icon: const Icon(Icons.save_rounded), - ), - border: const OutlineInputBorder())), - const SizedBox(height: 16), - toggle( - context, - AppLocalizations.of(context)!.settingsUseSystem, - (prefs!.getBool("useSystem") ?? true), - (value) { - selectionHaptic(); - prefs!.setBool("useSystem", value); - setState(() {}); - }, - icon: const Icon(Icons.info_outline_rounded), - onLongTap: () { - selectionHaptic(); - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(AppLocalizations.of(context)! - .settingsUseSystemDescription), - showCloseIcon: true)); - }), - toggle( - context, - AppLocalizations.of(context)!.settingsDisableMarkdown, - (prefs!.getBool("noMarkdown") ?? false), (value) { - selectionHaptic(); - prefs!.setBool("noMarkdown", value); - setState(() {}); - }) - ]), - ), - const SizedBox(height: 8), - button( - AppLocalizations.of(context)! - .settingsBehaviorNotUpdatedForOlderChats, - Icons.info_rounded, - null, - color: Colors.grey - .harmonizeWith(Theme.of(context).colorScheme.primary)) - ]))), + body: Center( + child: Container( + constraints: const BoxConstraints(maxWidth: 1000), + padding: const EdgeInsets.only(left: 16, right: 16), + child: Column(children: [ + Expanded( + child: ListView(children: [ + const SizedBox(height: 8), + TextField( + controller: systemInputController, + keyboardType: TextInputType.multiline, + maxLines: desktopLayoutNotRequired(context) ? 5 : 2, + decoration: InputDecoration( + labelText: AppLocalizations.of(context)! + .settingsSystemMessage, + alignLabelWithHint: true, + hintText: "You are a helpful assistant", + suffixIcon: IconButton( + tooltip: + AppLocalizations.of(context)!.tooltipSave, + onPressed: () { + selectionHaptic(); + prefs?.setString( + "system", + (systemInputController.text.isNotEmpty) + ? systemInputController.text + : "You are a helpful assistant"); + }, + icon: const Icon(Icons.save_rounded), + ), + border: const OutlineInputBorder())), + const SizedBox(height: 16), + toggle( + context, + AppLocalizations.of(context)!.settingsUseSystem, + (prefs!.getBool("useSystem") ?? true), + (value) { + selectionHaptic(); + prefs!.setBool("useSystem", value); + setState(() {}); + }, + icon: const Icon(Icons.info_outline_rounded), + onLongTap: () { + selectionHaptic(); + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(AppLocalizations.of(context)! + .settingsUseSystemDescription), + showCloseIcon: true)); + }), + toggle( + context, + AppLocalizations.of(context)!.settingsDisableMarkdown, + (prefs!.getBool("noMarkdown") ?? false), (value) { + selectionHaptic(); + prefs!.setBool("noMarkdown", value); + setState(() {}); + }) + ]), + ), + const SizedBox(height: 8), + button( + AppLocalizations.of(context)! + .settingsBehaviorNotUpdatedForOlderChats, + Icons.info_rounded, + null, + color: Colors.grey + .harmonizeWith(Theme.of(context).colorScheme.primary)) + ])), + )), ); } } diff --git a/lib/settings/export.dart b/lib/settings/export.dart index c5c344d..c38ab96 100644 --- a/lib/settings/export.dart +++ b/lib/settings/export.dart @@ -36,192 +36,204 @@ class _ScreenSettingsExportState extends State { Expanded(child: SizedBox(height: 200, child: MoveWindow())) ]), actions: desktopControlsActions(context)), - body: Padding( - padding: const EdgeInsets.only(left: 16, right: 16), - child: Column(children: [ - Expanded( - child: ListView(children: [ - // const SizedBox(height: 8), - button(AppLocalizations.of(context)!.settingsExportChats, - Icons.upload_rounded, () async { - selectionHaptic(); - var name = - "ollama-export-${DateFormat('yyyy-MM-dd-H-m-s').format(DateTime.now())}.json"; - 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); - final anchor = html.document.createElement("a") - as html.AnchorElement - ..href = url - ..style.display = "none" - ..download = name; - html.document.body!.children.add(anchor); + body: Center( + child: Container( + constraints: const BoxConstraints(maxWidth: 1000), + padding: const EdgeInsets.only(left: 16, right: 16), + child: Column(children: [ + Expanded( + child: ListView(children: [ + // const SizedBox(height: 8), + button(AppLocalizations.of(context)!.settingsExportChats, + Icons.upload_rounded, () async { + selectionHaptic(); + var name = + "ollama-export-${DateFormat('yyyy-MM-dd-H-m-s').format(DateTime.now())}.json"; + 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); + final anchor = html.document.createElement("a") + as html.AnchorElement + ..href = url + ..style.display = "none" + ..download = name; + html.document.body!.children.add(anchor); - anchor.click(); + anchor.click(); - html.document.body!.children.remove(anchor); - html.Url.revokeObjectUrl(url); - } else { - 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()) { - File(path).writeAsString(content); + html.document.body!.children.remove(anchor); + html.Url.revokeObjectUrl(url); + } else { + 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()) { + 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( - AppLocalizations.of(context)!.settingsImportChats, - Icons.download_rounded, () { - selectionHaptic(); - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: Text(AppLocalizations.of(context)! - .settingsImportChatsTitle), - content: Text( - AppLocalizations.of(context)! - .settingsImportChatsDescription), - actions: [ - TextButton( - onPressed: () { - selectionHaptic(); - Navigator.of(context).pop(); - }, - child: Text(AppLocalizations.of( - context)! - .settingsImportChatsCancel)), - TextButton( - onPressed: () async { - selectionHaptic(); - String content; - try { - 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 result.readAsString(); - } catch (_) { - FilePickerResult? result = - await FilePicker.platform - .pickFiles( - type: - FileType.custom, - allowedExtensions: [ - "json" - ]); - if (result == null) { - // ignore: use_build_context_synchronously - Navigator.of(context).pop(); - return; - } + // 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( + AppLocalizations.of(context)!.settingsImportChats, + Icons.download_rounded, () { + selectionHaptic(); + showDialog( + context: context, + builder: (context) { + return AlertDialog( + surfaceTintColor: + (Theme.of(context).brightness == + Brightness.dark) + ? Colors.grey[800] + : null, + title: Text( + AppLocalizations.of(context)! + .settingsImportChatsTitle), + content: Text(AppLocalizations.of( + context)! + .settingsImportChatsDescription), + actions: [ + TextButton( + onPressed: () { + selectionHaptic(); + Navigator.of(context).pop(); + }, + child: Text(AppLocalizations.of( + context)! + .settingsImportChatsCancel)), + TextButton( + onPressed: () async { + selectionHaptic(); + String content; try { - File file = File(result - .files.single.path!); - content = - await file.readAsString(); + 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 result + .readAsString(); } catch (_) { - // web fallback - 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); - List history = []; + List tmpHistory = + jsonDecode(content); + List history = []; - for (var i = 0; - i < tmpHistory.length; - i++) { - history.add(tmpHistory[i]); - } + for (var i = 0; + i < tmpHistory.length; + i++) { + history.add(tmpHistory[i]); + } - prefs!.setStringList( - "chats", history); + prefs!.setStringList( + "chats", history); - messages = []; - chatUuid = null; + messages = []; + chatUuid = null; - setState(() {}); + setState(() {}); - // ignore: use_build_context_synchronously - Navigator.of(context).pop(); - // ignore: use_build_context_synchronously - Navigator.of(context).pop(); - // ignore: use_build_context_synchronously - Navigator.of(context).pop(); - // ignore: use_build_context_synchronously - ScaffoldMessenger.of(context) - .showSnackBar(SnackBar( - content: Text(AppLocalizations - // ignore: use_build_context_synchronously - .of(context)! - .settingsImportChatsSuccess), - showCloseIcon: true)); - }, - child: Text( - AppLocalizations.of(context)! - .settingsImportChatsImport)) - ]); - }); - }) - : const SizedBox.shrink() - ]), - ), - const SizedBox(height: 8), - button(AppLocalizations.of(context)!.settingsExportInfo, - Icons.info_rounded, null, - color: Colors.grey - .harmonizeWith(Theme.of(context).colorScheme.primary)), - button(AppLocalizations.of(context)!.settingsExportWarning, - Icons.warning_rounded, null, - color: Colors.orange - .harmonizeWith(Theme.of(context).colorScheme.primary)) - ]))), + // ignore: use_build_context_synchronously + Navigator.of(context).pop(); + // ignore: use_build_context_synchronously + Navigator.of(context).pop(); + // ignore: use_build_context_synchronously + Navigator.of(context).pop(); + // ignore: use_build_context_synchronously + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar( + content: Text(AppLocalizations + // ignore: use_build_context_synchronously + .of(context)! + .settingsImportChatsSuccess), + showCloseIcon: true)); + }, + child: Text(AppLocalizations.of( + context)! + .settingsImportChatsImport)) + ]); + }); + }) + : const SizedBox.shrink() + ]), + ), + const SizedBox(height: 8), + button(AppLocalizations.of(context)!.settingsExportInfo, + Icons.info_rounded, null, + color: Colors.grey.harmonizeWith( + Theme.of(context).colorScheme.primary)), + button(AppLocalizations.of(context)!.settingsExportWarning, + Icons.warning_rounded, null, + color: Colors.orange + .harmonizeWith(Theme.of(context).colorScheme.primary)) + ])), + )), ); } } diff --git a/lib/settings/interface.dart b/lib/settings/interface.dart index 2f84efe..41d9e26 100644 --- a/lib/settings/interface.dart +++ b/lib/settings/interface.dart @@ -36,460 +36,487 @@ class _ScreenSettingsInterfaceState extends State { Expanded(child: SizedBox(height: 200, child: MoveWindow())) ]), actions: desktopControlsActions(context)), - body: Padding( - padding: const EdgeInsets.only(left: 16, right: 16), - child: Column(children: [ - Expanded( - child: ListView(children: [ - // const SizedBox(height: 8), - toggle( - context, - AppLocalizations.of(context)!.settingsShowModelTags, - (prefs!.getBool("modelTags") ?? false), (value) { - selectionHaptic(); - prefs!.setBool("modelTags", value); - setState(() {}); - }), - toggle( - context, - AppLocalizations.of(context)!.settingsPreloadModels, - (prefs!.getBool("preloadModel") ?? true), (value) { - selectionHaptic(); - prefs!.setBool("preloadModel", value); - setState(() {}); - }), - toggle( - context, - AppLocalizations.of(context)! - .settingsResetOnModelChange, - (prefs!.getBool("resetOnModelSelect") ?? true), - (value) { - selectionHaptic(); - prefs!.setBool("resetOnModelSelect", value); - setState(() {}); - }), - titleDivider( - bottom: desktopLayoutNotRequired(context) ? 38 : 20, - context: context), - SegmentedButton( - segments: [ - ButtonSegment( - value: "stream", - label: Text(AppLocalizations.of(context)! - .settingsRequestTypeStream), - icon: const Icon(Icons.stream_rounded)), - ButtonSegment( - value: "request", - label: Text(AppLocalizations.of(context)! - .settingsRequestTypeRequest), - icon: const Icon(Icons.send_rounded)) - ], - selected: { - prefs!.getString("requestType") ?? "stream" - }, - onSelectionChanged: (p0) { - selectionHaptic(); - setState(() { - prefs!.setString("requestType", p0.elementAt(0)); - }); - }), - const SizedBox(height: 16), - toggle( - context, - AppLocalizations.of(context)!.settingsGenerateTitles, - (prefs!.getBool("generateTitles") ?? true), (value) { - selectionHaptic(); - prefs!.setBool("generateTitles", value); - setState(() {}); - }), - toggle( - context, - AppLocalizations.of(context)!.settingsEnableEditing, - (prefs!.getBool("enableEditing") ?? true), (value) { - selectionHaptic(); - prefs!.setBool("enableEditing", value); - setState(() {}); - }), - toggle( - context, - AppLocalizations.of(context)!.settingsAskBeforeDelete, - (prefs!.getBool("askBeforeDeletion") ?? false), - (value) { - selectionHaptic(); - prefs!.setBool("askBeforeDeletion", value); - setState(() {}); - }), - toggle( - context, - AppLocalizations.of(context)!.settingsShowTips, - (prefs!.getBool("tips") ?? true), (value) { - selectionHaptic(); - prefs!.setBool("tips", value); - setState(() {}); - }), - titleDivider(context: context), - toggle( - context, - AppLocalizations.of(context)! - .settingsKeepModelLoadedAlways, - int.parse(prefs!.getString("keepAlive") ?? "300") == -1, - (value) { - selectionHaptic(); - setState(() { - if (value) { - prefs!.setString("keepAlive", "-1"); - } else { - prefs!.setString("keepAlive", "300"); - } - }); - }), - toggle( - context, - AppLocalizations.of(context)! - .settingsKeepModelLoadedNever, - int.parse(prefs!.getString("keepAlive") ?? "300") == 0, - (value) { - selectionHaptic(); - setState(() { - if (value) { - prefs!.setString("keepAlive", "0"); - } else { - prefs!.setString("keepAlive", "300"); - } - }); - }), - button( - (int.parse(prefs!.getString("keepAlive") ?? "300") > 0) - ? AppLocalizations.of(context)! - .settingsKeepModelLoadedSet((int.parse( - prefs!.getString("keepAlive") ?? - "300") ~/ - 60) - .toString()) - : AppLocalizations.of(context)! - .settingsKeepModelLoadedFor, - Icons.snooze_rounded, () async { - selectionHaptic(); - bool loaded = false; - await showDialog( - context: context, - builder: (context) { - return Dialog( - alignment: desktopLayout(context) - ? null - : Alignment.bottomRight, - child: StatefulBuilder( - builder: (context, setLocalState) { - if (int.parse(prefs!.getString("keepAlive") ?? - "0") <= - 0 && - loaded == false) { - prefs!.setString("keepAlive", "0"); - WidgetsBinding.instance - .addPostFrameCallback((timeStamp) { - setLocalState(() {}); - void load() async { - try { - while (int.parse(prefs! - .getString("keepAlive")!) < - 300) { - await Future.delayed(const Duration( - milliseconds: 5)); - prefs!.setString( - "keepAlive", - (int.parse(prefs!.getString( - "keepAlive")!) + - 30) - .toString()); - setLocalState(() {}); - setState(() {}); + body: Center( + child: Container( + constraints: const BoxConstraints(maxWidth: 1000), + padding: const EdgeInsets.only(left: 16, right: 16), + child: Column(children: [ + Expanded( + child: ListView(children: [ + // const SizedBox(height: 8), + toggle( + context, + AppLocalizations.of(context)!.settingsShowModelTags, + (prefs!.getBool("modelTags") ?? false), (value) { + selectionHaptic(); + prefs!.setBool("modelTags", value); + setState(() {}); + }), + toggle( + context, + AppLocalizations.of(context)!.settingsPreloadModels, + (prefs!.getBool("preloadModel") ?? true), (value) { + selectionHaptic(); + prefs!.setBool("preloadModel", value); + setState(() {}); + }), + toggle( + context, + AppLocalizations.of(context)! + .settingsResetOnModelChange, + (prefs!.getBool("resetOnModelSelect") ?? true), + (value) { + selectionHaptic(); + prefs!.setBool("resetOnModelSelect", value); + setState(() {}); + }), + titleDivider( + bottom: desktopLayoutNotRequired(context) ? 38 : 20, + context: context), + SegmentedButton( + segments: [ + ButtonSegment( + value: "stream", + label: Text(AppLocalizations.of(context)! + .settingsRequestTypeStream), + icon: const Icon(Icons.stream_rounded)), + ButtonSegment( + value: "request", + label: Text(AppLocalizations.of(context)! + .settingsRequestTypeRequest), + icon: const Icon(Icons.send_rounded)) + ], + selected: { + prefs!.getString("requestType") ?? "stream" + }, + onSelectionChanged: (p0) { + selectionHaptic(); + setState(() { + prefs!.setString("requestType", p0.elementAt(0)); + }); + }), + const SizedBox(height: 16), + toggle( + context, + AppLocalizations.of(context)!.settingsGenerateTitles, + (prefs!.getBool("generateTitles") ?? true), (value) { + selectionHaptic(); + prefs!.setBool("generateTitles", value); + setState(() {}); + }), + toggle( + context, + AppLocalizations.of(context)!.settingsEnableEditing, + (prefs!.getBool("enableEditing") ?? true), (value) { + selectionHaptic(); + prefs!.setBool("enableEditing", value); + setState(() {}); + }), + toggle( + context, + AppLocalizations.of(context)!.settingsAskBeforeDelete, + (prefs!.getBool("askBeforeDeletion") ?? false), + (value) { + selectionHaptic(); + prefs!.setBool("askBeforeDeletion", value); + setState(() {}); + }), + toggle( + context, + AppLocalizations.of(context)!.settingsShowTips, + (prefs!.getBool("tips") ?? true), (value) { + selectionHaptic(); + prefs!.setBool("tips", value); + setState(() {}); + }), + titleDivider(context: context), + toggle( + context, + AppLocalizations.of(context)! + .settingsKeepModelLoadedAlways, + int.parse(prefs!.getString("keepAlive") ?? "300") == + -1, (value) { + selectionHaptic(); + setState(() { + if (value) { + prefs!.setString("keepAlive", "-1"); + } else { + prefs!.setString("keepAlive", "300"); + } + }); + }), + toggle( + context, + AppLocalizations.of(context)! + .settingsKeepModelLoadedNever, + int.parse(prefs!.getString("keepAlive") ?? "300") == + 0, (value) { + selectionHaptic(); + setState(() { + if (value) { + prefs!.setString("keepAlive", "0"); + } else { + prefs!.setString("keepAlive", "300"); + } + }); + }), + button( + (int.parse(prefs!.getString("keepAlive") ?? "300") > + 0) + ? AppLocalizations.of(context)! + .settingsKeepModelLoadedSet((int.parse( + prefs!.getString("keepAlive") ?? + "300") ~/ + 60) + .toString()) + : AppLocalizations.of(context)! + .settingsKeepModelLoadedFor, + Icons.snooze_rounded, () async { + selectionHaptic(); + bool loaded = false; + await showDialog( + context: context, + builder: (context) { + return Dialog( + surfaceTintColor: (Theme.of(context).brightness == Brightness.dark) ? Colors.grey[800] : null, + alignment: desktopLayout(context) + ? null + : Alignment.bottomRight, + child: StatefulBuilder( + builder: (context, setLocalState) { + if (int.parse( + prefs!.getString("keepAlive") ?? + "0") <= + 0 && + loaded == false) { + prefs!.setString("keepAlive", "0"); + WidgetsBinding.instance + .addPostFrameCallback((timeStamp) { + setLocalState(() {}); + void load() async { + try { + while (int.parse(prefs! + .getString("keepAlive")!) < + 300) { + await Future.delayed( + const Duration( + milliseconds: 5)); + prefs!.setString( + "keepAlive", + (int.parse(prefs!.getString( + "keepAlive")!) + + 30) + .toString()); + setLocalState(() {}); + setState(() {}); + } + prefs! + .setString("keepAlive", "300"); + loaded = true; + } catch (_) { + prefs! + .setString("keepAlive", "300"); + loaded = true; } - prefs!.setString("keepAlive", "300"); - loaded = true; - } catch (_) { - prefs!.setString("keepAlive", "300"); - loaded = true; } - } - load(); - }); - } else { - loaded = true; - } - return Padding( - padding: const EdgeInsets.all(16), - child: Theme( - data: (prefs?.getBool("useDeviceTheme") ?? - false) - ? Theme.of(context) - : ThemeData.from( - colorScheme: ColorScheme.fromSeed( - seedColor: Colors.black)), - child: DurationPicker( - duration: Duration( - seconds: int.parse(prefs! - .getString("keepAlive") ?? - "300")), - baseUnit: BaseUnit.minute, - lowerBound: - const Duration(minutes: 1), - upperBound: - const Duration(minutes: 60), - onChange: (value) { - if (!loaded) return; - if (value.inSeconds == 0) return; - prefs!.setString("keepAlive", - value.inSeconds.toString()); - setLocalState(() {}); - setState(() {}); - }), - ), - ); - })); - }); - }), - titleDivider(context: context), - toggle( - context, - AppLocalizations.of(context)! - .settingsEnableHapticFeedback, - (prefs!.getBool("enableHaptic") ?? true), (value) { - prefs!.setBool("enableHaptic", value); - selectionHaptic(); - setState(() {}); - }), - desktopFeature() - ? toggle( - context, - AppLocalizations.of(context)! - .settingsMaximizeOnStart, - (prefs!.getBool("maximizeOnStart") ?? false), - (value) { + load(); + }); + } else { + loaded = true; + } + return Padding( + padding: const EdgeInsets.all(16), + child: Theme( + data: + (prefs?.getBool("useDeviceTheme") ?? + false) + ? Theme.of(context) + : ThemeData.from( + colorScheme: + ColorScheme.fromSeed( + seedColor: + Colors.black)), + child: DurationPicker( + duration: Duration( + seconds: int.parse(prefs! + .getString( + "keepAlive") ?? + "300")), + baseUnit: BaseUnit.minute, + lowerBound: + const Duration(minutes: 1), + upperBound: + const Duration(minutes: 60), + onChange: (value) { + if (!loaded) return; + if (value.inSeconds == 0) return; + prefs!.setString("keepAlive", + value.inSeconds.toString()); + setLocalState(() {}); + setState(() {}); + }), + ), + ); + })); + }); + }), + titleDivider(context: context), + toggle( + context, + AppLocalizations.of(context)! + .settingsEnableHapticFeedback, + (prefs!.getBool("enableHaptic") ?? true), (value) { + prefs!.setBool("enableHaptic", value); + selectionHaptic(); + setState(() {}); + }), + desktopFeature() + ? toggle( + context, + AppLocalizations.of(context)! + .settingsMaximizeOnStart, + (prefs!.getBool("maximizeOnStart") ?? false), + (value) { + selectionHaptic(); + prefs!.setBool("maximizeOnStart", value); + setState(() {}); + }) + : const SizedBox.shrink(), + const SizedBox(height: 8), + SegmentedButton( + segments: [ + ButtonSegment( + value: "dark", + label: Text(AppLocalizations.of(context)! + .settingsBrightnessDark), + icon: const Icon(Icons.brightness_4_rounded)), + ButtonSegment( + value: "system", + label: Text(AppLocalizations.of(context)! + .settingsBrightnessSystem), + icon: + const Icon(Icons.brightness_auto_rounded)), + ButtonSegment( + value: "light", + label: Text(AppLocalizations.of(context)! + .settingsBrightnessLight), + icon: const Icon(Icons.brightness_high_rounded)) + ], + selected: { + prefs!.getString("brightness") ?? "system" + }, + onSelectionChanged: (p0) { selectionHaptic(); - prefs!.setBool("maximizeOnStart", value); + var tmp = + prefs!.getString("brightness") ?? "system"; + prefs!.setString("brightness", p0.elementAt(0)); setState(() {}); - }) - : const SizedBox.shrink(), - const SizedBox(height: 8), - SegmentedButton( - segments: [ - ButtonSegment( - value: "dark", - label: Text(AppLocalizations.of(context)! - .settingsBrightnessDark), - icon: const Icon(Icons.brightness_4_rounded)), - ButtonSegment( - value: "system", - label: Text(AppLocalizations.of(context)! - .settingsBrightnessSystem), - icon: const Icon(Icons.brightness_auto_rounded)), - ButtonSegment( - value: "light", - label: Text(AppLocalizations.of(context)! - .settingsBrightnessLight), - icon: const Icon(Icons.brightness_high_rounded)) - ], - selected: { - prefs!.getString("brightness") ?? "system" - }, - onSelectionChanged: (p0) { - selectionHaptic(); - var tmp = prefs!.getString("brightness") ?? "system"; - prefs!.setString("brightness", p0.elementAt(0)); - setState(() {}); - showDialog( - context: context, - builder: (context) { - return StatefulBuilder( - builder: (context, setLocalState) { - return PopScope( - onPopInvoked: (didPop) { - prefs!.setString("brightness", tmp); - setState(() {}); - }, - child: AlertDialog( - title: Text(AppLocalizations.of( - context)! - .settingsBrightnessRestartTitle), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text(AppLocalizations.of( - context)! - .settingsBrightnessRestartDescription), - ]), - actions: [ - TextButton( - onPressed: () { - selectionHaptic(); - Navigator.of(context).pop(); - }, - child: Text(AppLocalizations.of( - context)! - .settingsBrightnessRestartCancel)), - TextButton( - onPressed: () async { - selectionHaptic(); - await prefs!.setString( - "brightness", - p0.elementAt(0)); - if (desktopFeature()) { - exit(0); - } else { - Restart.restartApp(); - } - }, - child: Text(AppLocalizations.of( - context)! - .settingsBrightnessRestartRestart)) - ])); + showDialog( + context: context, + builder: (context) { + return StatefulBuilder( + builder: (context, setLocalState) { + return PopScope( + onPopInvoked: (didPop) { + prefs!.setString("brightness", tmp); + setState(() {}); + }, + child: AlertDialog( + surfaceTintColor: (Theme.of(context).brightness == Brightness.dark) ? Colors.grey[800] : null, + title: Text(AppLocalizations.of( + context)! + .settingsBrightnessRestartTitle), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(AppLocalizations.of( + context)! + .settingsBrightnessRestartDescription), + ]), + actions: [ + TextButton( + onPressed: () { + selectionHaptic(); + Navigator.of(context).pop(); + }, + child: Text(AppLocalizations + .of(context)! + .settingsBrightnessRestartCancel)), + TextButton( + onPressed: () async { + selectionHaptic(); + await prefs!.setString( + "brightness", + p0.elementAt(0)); + if (desktopFeature()) { + exit(0); + } else { + Restart.restartApp(); + } + }, + child: Text(AppLocalizations + .of(context)! + .settingsBrightnessRestartRestart)) + ])); + }); }); - }); - }), - const SizedBox(height: 8), - !kIsWeb - ? SegmentedButton( - segments: [ - ButtonSegment( - value: "device", - label: Text(AppLocalizations.of(context)! - .settingsThemeDevice), - icon: const Icon(Icons.devices_rounded)), - ButtonSegment( - value: "ollama", - label: Text(AppLocalizations.of(context)! - .settingsThemeOllama), - icon: const ImageIcon( - AssetImage("assets/logo512.png"))) - ], - selected: { - (prefs?.getBool("useDeviceTheme") ?? false) - ? "device" - : "ollama" - }, - onSelectionChanged: (p0) { - selectionHaptic(); - showDialog( - context: context, - builder: (context) { - return StatefulBuilder( - builder: (context, setLocalState) { - return AlertDialog( - title: Text( - AppLocalizations.of(context)! - .settingsThemeRestartTitle), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text(AppLocalizations.of( - context)! - .settingsThemeRestartDescription), - ]), - actions: [ - TextButton( - onPressed: () { - selectionHaptic(); - Navigator.of(context).pop(); - }, - child: Text(AppLocalizations.of( - context)! - .settingsThemeRestartCancel)), - TextButton( - onPressed: () async { - selectionHaptic(); - await prefs!.setBool( - "useDeviceTheme", - p0.elementAt(0) == - "device"); - if (desktopFeature()) { - exit(0); - } else { - Restart.restartApp(); - } - }, - child: Text(AppLocalizations.of( - context)! - .settingsThemeRestartRestart)) - ]); + }), + const SizedBox(height: 8), + !kIsWeb + ? SegmentedButton( + segments: [ + ButtonSegment( + value: "device", + label: Text(AppLocalizations.of(context)! + .settingsThemeDevice), + icon: const Icon(Icons.devices_rounded)), + ButtonSegment( + value: "ollama", + label: Text(AppLocalizations.of(context)! + .settingsThemeOllama), + icon: const ImageIcon( + AssetImage("assets/logo512.png"))) + ], + selected: { + (prefs?.getBool("useDeviceTheme") ?? false) + ? "device" + : "ollama" + }, + onSelectionChanged: (p0) { + selectionHaptic(); + showDialog( + context: context, + builder: (context) { + return StatefulBuilder( + builder: (context, setLocalState) { + return AlertDialog( + surfaceTintColor: (Theme.of(context).brightness == Brightness.dark) ? Colors.grey[800] : null, + title: Text( + AppLocalizations.of(context)! + .settingsThemeRestartTitle), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(AppLocalizations.of( + context)! + .settingsThemeRestartDescription), + ]), + actions: [ + TextButton( + onPressed: () { + selectionHaptic(); + Navigator.of(context).pop(); + }, + child: Text(AppLocalizations + .of(context)! + .settingsThemeRestartCancel)), + TextButton( + onPressed: () async { + selectionHaptic(); + await prefs!.setBool( + "useDeviceTheme", + p0.elementAt(0) == + "device"); + if (desktopFeature()) { + exit(0); + } else { + Restart.restartApp(); + } + }, + child: Text(AppLocalizations + .of(context)! + .settingsThemeRestartRestart)) + ]); + }); }); - }); - }) - : const SizedBox.shrink(), - titleDivider(), - button(AppLocalizations.of(context)!.settingsTemporaryFixes, - Icons.fast_forward_rounded, () { - selectionHaptic(); - showModalBottomSheet( - context: context, - builder: (context) { - return StatefulBuilder( - builder: (context, setState) { - return Container( - width: double.infinity, - padding: EdgeInsets.only( - left: 16, - right: 16, - top: 16, - bottom: desktopLayout(context) ? 16 : 0), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - button( - AppLocalizations.of(context)! - .settingsTemporaryFixesDescription, - Icons.info_rounded, - null, - color: Colors.grey.harmonizeWith( - Theme.of(context) - .colorScheme - .primary)), - button( - AppLocalizations.of(context)! - .settingsTemporaryFixesInstructions, - Icons.warning_rounded, - null, - color: Colors.orange.harmonizeWith( - Theme.of(context) - .colorScheme - .primary)), - titleDivider(), - // Text( - // AppLocalizations.of(context)! - // .settingsTemporaryFixesNoFixes, - // style: const TextStyle( - // color: Colors.grey)), - toggle( - context, - "Fixing code block not scrollable", - (prefs!.getBool( - "fixCodeblockScroll") ?? - false), (value) { - selectionHaptic(); - prefs!.setBool( - "fixCodeblockScroll", value); - if ((prefs!.getBool( + }) + : const SizedBox.shrink(), + titleDivider(), + button( + AppLocalizations.of(context)!.settingsTemporaryFixes, + Icons.fast_forward_rounded, () { + selectionHaptic(); + showModalBottomSheet( + context: context, + builder: (context) { + return StatefulBuilder( + builder: (context, setState) { + return Container( + width: double.infinity, + decoration: (Theme.of(context).brightness == + Brightness.dark) + ? BoxDecoration( + border: + Border.all(color: Colors.white), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(26), + topRight: Radius.circular(26))) + : null, + padding: EdgeInsets.only( + left: 16, + right: 16, + top: 16, + bottom: desktopLayout(context) ? 16 : 0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + button( + AppLocalizations.of(context)! + .settingsTemporaryFixesDescription, + Icons.info_rounded, + null, + color: Colors.grey.harmonizeWith( + Theme.of(context) + .colorScheme + .primary)), + button( + AppLocalizations.of(context)! + .settingsTemporaryFixesInstructions, + Icons.warning_rounded, + null, + color: Colors.orange.harmonizeWith( + Theme.of(context) + .colorScheme + .primary)), + titleDivider(), + // Text( + // AppLocalizations.of(context)! + // .settingsTemporaryFixesNoFixes, + // style: const TextStyle( + // color: Colors.grey)), + toggle( + context, + "Fixing code block not scrollable", + (prefs!.getBool( "fixCodeblockScroll") ?? - false) == - false) { - prefs!.remove("fixCodeblockScroll"); - } - setState(() {}); - }, onLongTap: () { - selectionHaptic(); - launchUrl(Uri.parse( - "https://github.com/JHubi1/ollama-app/issues/26")); - }), - const SizedBox(height: 16) - ]), - ); + false), (value) { + selectionHaptic(); + prefs!.setBool( + "fixCodeblockScroll", value); + if ((prefs!.getBool( + "fixCodeblockScroll") ?? + false) == + false) { + prefs!.remove("fixCodeblockScroll"); + } + setState(() {}); + }, onLongTap: () { + selectionHaptic(); + launchUrl(Uri.parse( + "https://github.com/JHubi1/ollama-app/issues/26")); + }), + const SizedBox(height: 16) + ]), + ); + }); }); - }); - }), - const SizedBox(height: 16) - ]), - ) - ]))), + }), + const SizedBox(height: 16) + ]), + ) + ])), + )), ); } } diff --git a/lib/settings/voice.dart b/lib/settings/voice.dart index 0de3fe3..ced9a52 100644 --- a/lib/settings/voice.dart +++ b/lib/settings/voice.dart @@ -216,6 +216,21 @@ class _ScreenSettingsVoiceState extends State { }); }, child: Container( + decoration: (Theme.of(context) + .brightness == + Brightness.dark) + ? BoxDecoration( + border: Border.all( + color: Colors.white), + borderRadius: + const BorderRadius.only( + topLeft: + Radius.circular( + 26), + topRight: + Radius.circular( + 26))) + : null, width: double.infinity, padding: const EdgeInsets.only( left: 16, diff --git a/lib/worker/sender.dart b/lib/worker/sender.dart index 64566d7..d306593 100644 --- a/lib/worker/sender.dart +++ b/lib/worker/sender.dart @@ -80,7 +80,6 @@ List getHistoryString([String? uuid]) { } Future getTitleAi(List history) async { - print(history); final generated = await (llama.OllamaClient( headers: (jsonDecode(prefs!.getString("hostHeaders") ?? "{}") as Map) .cast(), @@ -92,7 +91,7 @@ Future getTitleAi(List history) async { const llama.Message( role: llama.MessageRole.system, content: - "Generate a three to six word title for the conversation provided by the user. If an object or person is very important in the conversation, put it in the title as well; keep the focus on the main subject. You must not put the assistant in the focus and you must not put the word 'assistant' in the title! Do preferably use title case. Use a formal tone, don't use dramatic words, like 'mystery' Use spaces between words, do not use camel case! You must not use markdown or any other formatting language! You must not use emojis or any other symbols! You must not use general clauses like 'assistance', 'help' or 'session' in your title! \n\n~~User Introduces Themselves~~ -> User Introduction\n~~User Asks for Help with a Problem~~ -> Problem Help\n~~User has a _**big**_ Problem~~ -> Big Problem\n~~Conversation~~ -> Conversation about Fireflies"), + "Generate a three to six word title for the conversation provided by the user. If an object or person is very important in the conversation, put it in the title as well; keep the focus on the main subject. You must not put the assistant in the focus and you must not put the word 'assistant' in the title! Do preferably use title case. Use a formal tone, don't use dramatic words, like 'mystery' Use spaces between words, do not use camel case! You must not use markdown or any other formatting language! You must not use emojis or any other symbols! You must not use general clauses like 'assistance', 'help' or 'session' in your title! \n\n~~User Introduces Themselves~~ -> User Introduction\n~~User Asks for Help with a Problem~~ -> Problem Help\n~~User has a _**big**_ Problem~~ -> Big Problem"), llama.Message( role: llama.MessageRole.user, content: "```\n${jsonEncode(history)}\n```") diff --git a/lib/worker/setter.dart b/lib/worker/setter.dart index 0075c2b..c94a029 100644 --- a/lib/worker/setter.dart +++ b/lib/worker/setter.dart @@ -278,6 +278,10 @@ void setModel(BuildContext context, Function setState) { ? const Offset(289, 0) : const Offset(0, 0), child: Dialog( + surfaceTintColor: + (Theme.of(context).brightness == Brightness.dark) + ? Colors.grey[800] + : null, alignment: desktopLayoutRequired(context) ? Alignment.topLeft : Alignment.topCenter, @@ -285,7 +289,17 @@ void setModel(BuildContext context, Function setState) { ); }); } else { - showModalBottomSheet(context: context, builder: (context) => content); + showModalBottomSheet( + context: context, + builder: (context) => Container( + decoration: (Theme.of(context).brightness == Brightness.dark) + ? BoxDecoration( + border: Border.all(color: Colors.white), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(26), + topRight: Radius.circular(26))) + : null, + child: content)); } } @@ -406,6 +420,79 @@ void loadChat(String uuid, Function setState) { setState(() {}); } +Future deleteChatDialog(BuildContext context, Function setState, + {bool takeAction = true, + bool? additionalCondition, + String? uuid, + bool popSidebar = false}) async { + additionalCondition ??= true; + uuid ??= chatUuid; + + bool returnValue = false; + void delete() { + returnValue = true; + if (takeAction) { + for (var i = 0; i < (prefs!.getStringList("chats") ?? []).length; i++) { + if (jsonDecode((prefs!.getStringList("chats") ?? [])[i])["uuid"] == + uuid) { + List tmp = prefs!.getStringList("chats")!; + tmp.removeAt(i); + prefs!.setStringList("chats", tmp); + break; + } + } + if (chatUuid == uuid) { + messages = []; + chatUuid = null; + if (!desktopLayoutRequired(context) && + Navigator.of(context).canPop() && + popSidebar) { + Navigator.of(context).pop(); + } + } + } + } + + if ((prefs!.getBool("askBeforeDeletion") ?? false) && additionalCondition) { + await showDialog( + context: context, + builder: (context) { + return StatefulBuilder(builder: (context, setLocalState) { + return AlertDialog( + surfaceTintColor: + (Theme.of(context).brightness == Brightness.dark) + ? Colors.grey[800] + : null, + 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(); + delete(); + }, + child: Text( + AppLocalizations.of(context)!.deleteDialogDelete)) + ]); + }); + }); + } else { + delete(); + } + setState(() {}); + return returnValue; +} + Future prompt(BuildContext context, {String description = "", String value = "", @@ -431,6 +518,13 @@ Future prompt(BuildContext context, return StatefulBuilder(builder: (context, setLocalState) { return PopScope( child: Container( + decoration: (Theme.of(context).brightness == Brightness.dark) + ? BoxDecoration( + border: Border.all(color: Colors.white), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(26), + topRight: Radius.circular(26))) + : null, padding: EdgeInsets.only( left: 16, right: 16, diff --git a/lib/worker/update.dart b/lib/worker/update.dart index 943728b..32169d3 100644 --- a/lib/worker/update.dart +++ b/lib/worker/update.dart @@ -125,6 +125,7 @@ void updateDialog(BuildContext context, Function title) { context: context, builder: (context) { return AlertDialog( + surfaceTintColor: (Theme.of(context).brightness == Brightness.dark) ? Colors.grey[800] : null, title: Text(AppLocalizations.of(context)!.settingsUpdateDialogTitle), content: Column(mainAxisSize: MainAxisSize.min, children: [ @@ -133,9 +134,12 @@ void updateDialog(BuildContext context, Function title) { title(AppLocalizations.of(context)!.settingsUpdateChangeLog), Flexible( child: SingleChildScrollView( - child: MarkdownBody( - data: updateChangeLog ?? "No changelog given.", - shrinkWrap: true))) + child: Container( + constraints: const BoxConstraints(maxWidth: 1000), + child: MarkdownBody( + data: updateChangeLog ?? "No changelog given.", + shrinkWrap: true), + ))) ]), actions: [ TextButton(