diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 5977de3..c2f2b08 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -232,6 +232,16 @@ "description": "Text displayed as description for reset on model change toggle", "context": "Visible in the settings view" }, + "settingsRequestTypeStream": "Stream", + "@settingsRequestTypeStream": { + "description": "Text displayed as description for stream request type. Do not translate if not required!", + "context": "Visible in the settings view" + }, + "settingsRequestTypeRequest": "Request", + "@settingsRequestTypeRequest": { + "description": "Text displayed as description for request request type. Do not translate if not required!", + "context": "Visible in the settings view" + }, "settingsGenerateTitles": "Generate titles", "@settingsGenerateTitles": { "description": "Text displayed as description for generate titles toggle", @@ -252,14 +262,14 @@ "description": "Text displayed as description for show tips toggle", "context": "Visible in the settings view" }, - "settingsRequestTypeStream": "Stream", - "@settingsRequestTypeStream": { - "description": "Text displayed as description for stream request type. Do not translate if not required!", + "settingsEnableHapticFeedback": "Enable haptic feedback", + "@settingsEnableHapticFeedback": { + "description": "Text displayed as description for enable haptic feedback toggle", "context": "Visible in the settings view" }, - "settingsRequestTypeRequest": "Request", - "@settingsRequestTypeRequest": { - "description": "Text displayed as description for request request type. Do not translate if not required!", + "settingsMaximizeOnStart": "Maximize on start", + "@settingsMaximizeOnStart": { + "description": "Text displayed as description for maximize on start toggle", "context": "Visible in the settings view" }, "settingsBrightnessSystem": "System", diff --git a/lib/main.dart b/lib/main.dart index 7a0d271..e516175 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -11,7 +11,8 @@ import 'package:url_launcher/url_launcher.dart'; import 'screen_settings.dart'; import 'screen_welcome.dart'; -import 'worker_setter.dart'; +import 'worker/setter.dart'; +import 'worker/haptic.dart'; import 'package:shared_preferences/shared_preferences.dart'; // ignore: depend_on_referenced_packages @@ -74,6 +75,9 @@ void main() { appWindow.minSize = const Size(600, 450); appWindow.size = const Size(1200, 650); appWindow.alignment = Alignment.center; + if (prefs!.getBool("maximizeOnStart") ?? false) { + appWindow.maximize(); + } appWindow.show(); }); } @@ -218,7 +222,7 @@ class _MainAppState extends State { customBorder: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(50))), onTap: () { - HapticFeedback.selectionClick(); + selectionHaptic(); if (!(Platform.isWindows || Platform.isLinux || Platform.isMacOS) && @@ -254,7 +258,7 @@ class _MainAppState extends State { customBorder: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(50))), onTap: () { - HapticFeedback.selectionClick(); + selectionHaptic(); if (!(Platform.isWindows || Platform.isLinux || Platform.isMacOS) && @@ -302,7 +306,7 @@ class _MainAppState extends State { customBorder: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(50))), onTap: () { - HapticFeedback.selectionClick(); + selectionHaptic(); }, child: Padding( padding: const EdgeInsets.only(top: 16, bottom: 16), @@ -344,7 +348,7 @@ class _MainAppState extends State { enableFeedback: false, hoverColor: Colors.transparent, onTap: () { - HapticFeedback.selectionClick(); + selectionHaptic(); setState(() { tipId = Random().nextInt(5); }); @@ -398,7 +402,7 @@ class _MainAppState extends State { actions: [ TextButton( onPressed: () { - HapticFeedback.selectionClick(); + selectionHaptic(); Navigator.of(context).pop(); returnValue = false; }, @@ -406,7 +410,7 @@ class _MainAppState extends State { .deleteDialogCancel)), TextButton( onPressed: () { - HapticFeedback.selectionClick(); + selectionHaptic(); Navigator.of(context).pop(); returnValue = true; }, @@ -421,7 +425,7 @@ class _MainAppState extends State { return returnValue; }, onDismissed: (direction) { - HapticFeedback.selectionClick(); + selectionHaptic(); for (var i = 0; i < (prefs!.getStringList("chats") ?? []).length; i++) { @@ -452,7 +456,7 @@ class _MainAppState extends State { customBorder: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(50))), onTap: () { - HapticFeedback.selectionClick(); + selectionHaptic(); if (!(Platform.isWindows || Platform.isLinux || Platform.isMacOS) && @@ -464,7 +468,7 @@ class _MainAppState extends State { chatUuid = jsonDecode(item)["uuid"]; }, onLongPress: () async { - HapticFeedback.selectionClick(); + selectionHaptic(); if (!chatAllowed) return; if (!allowSettings) return; String oldTitle = jsonDecode(item)["title"]; @@ -717,7 +721,7 @@ class _MainAppState extends State { const SizedBox(width: 4), IconButton( onPressed: () { - HapticFeedback.selectionClick(); + selectionHaptic(); if (!chatAllowed) return; if (prefs!.getBool("askBeforeDeletion") ?? @@ -881,7 +885,7 @@ class _MainAppState extends State { child: MarkdownBody( data: p0.text, onTapLink: (text, href, title) async { - HapticFeedback.selectionClick(); + selectionHaptic(); try { var url = Uri.parse(href!); if (await canLaunchUrl(url)) { @@ -918,7 +922,7 @@ class _MainAppState extends State { (context, error, stackTrace) { return InkWell( onTap: () { - HapticFeedback.selectionClick(); + selectionHaptic(); ScaffoldMessenger.of(context) .showSnackBar(SnackBar( content: Text( @@ -947,7 +951,7 @@ class _MainAppState extends State { } else { return InkWell( onTap: () { - HapticFeedback.selectionClick(); + selectionHaptic(); ScaffoldMessenger.of(context) .showSnackBar(SnackBar( content: Text( @@ -1078,7 +1082,7 @@ class _MainAppState extends State { AssetImage("assets/logo512.png"), size: 44)))), onSendPressed: (p0) async { - HapticFeedback.selectionClick(); + selectionHaptic(); setState(() { sendable = false; }); @@ -1208,7 +1212,7 @@ class _MainAppState extends State { id: newId, text: text)); setState(() {}); - HapticFeedback.lightImpact(); + lightHaptic(); } } else { llama.GenerateChatCompletionResponse request; @@ -1232,7 +1236,7 @@ class _MainAppState extends State { id: newId, text: request.message!.content)); setState(() {}); - HapticFeedback.lightImpact(); + lightHaptic(); } } catch (e) { for (var i = 0; i < messages.length; i++) { @@ -1324,7 +1328,7 @@ class _MainAppState extends State { chatAllowed = true; }, onMessageDoubleTap: (context, p1) { - HapticFeedback.selectionClick(); + selectionHaptic(); if (!chatAllowed) return; if (p1.author == assistant) return; for (var i = 0; i < messages.length; i++) { @@ -1357,7 +1361,7 @@ class _MainAppState extends State { setState(() {}); }, onMessageLongPress: (context, p1) async { - HapticFeedback.selectionClick(); + selectionHaptic(); if (!(prefs!.getBool("enableEditing") ?? true)) { return; @@ -1396,12 +1400,12 @@ class _MainAppState extends State { onAttachmentPressed: (!multimodal) ? null : () { - HapticFeedback.selectionClick(); + selectionHaptic(); if (!chatAllowed || model == null) return; if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { - HapticFeedback.selectionClick(); + selectionHaptic(); FilePicker.platform .pickFiles(type: FileType.image) @@ -1423,7 +1427,7 @@ class _MainAppState extends State { "data:image/png;base64,$encoded")); setState(() {}); - HapticFeedback.selectionClick(); + selectionHaptic(); }); return; diff --git a/lib/screen_settings.dart b/lib/screen_settings.dart index efa6f84..bd2d86b 100644 --- a/lib/screen_settings.dart +++ b/lib/screen_settings.dart @@ -2,10 +2,10 @@ import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'main.dart'; -import 'package:ollama_app/worker_setter.dart'; +import 'worker/haptic.dart'; +import 'package:ollama_app/worker/setter.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'settings/behavior.dart'; @@ -166,7 +166,7 @@ class _ScreenSettingsState extends State { hostLoading = false; }); } - HapticFeedback.selectionClick(); + selectionHaptic(); } @override @@ -247,7 +247,7 @@ class _ScreenSettingsState extends State { keyboardType: TextInputType.url, readOnly: useHost, onSubmitted: (value) { - HapticFeedback.selectionClick(); + selectionHaptic(); checkHost(); }, decoration: InputDecoration( @@ -256,7 +256,7 @@ class _ScreenSettingsState extends State { hintText: "http://localhost:11434", prefixIcon: IconButton( onPressed: () async { - HapticFeedback.selectionClick(); + selectionHaptic(); String tmp = await prompt(context, placeholder: "{\"Authorization\": \"Bearer ...\"}", @@ -290,7 +290,7 @@ class _ScreenSettingsState extends State { const CircularProgressIndicator()) : IconButton( onPressed: () { - HapticFeedback.selectionClick(); + selectionHaptic(); checkHost(); }, icon: const Icon( @@ -300,7 +300,7 @@ class _ScreenSettingsState extends State { error: (hostInvalidHost || hostInvalidUrl) ? InkWell( onTap: () { - HapticFeedback.selectionClick(); + selectionHaptic(); ScaffoldMessenger.of(context) .showSnackBar(SnackBar( content: Text(AppLocalizations @@ -331,7 +331,7 @@ class _ScreenSettingsState extends State { : null, helper: InkWell( onTap: () { - HapticFeedback.selectionClick(); + selectionHaptic(); }, highlightColor: Colors.transparent, splashFactory: NoSplash.splashFactory, @@ -371,7 +371,7 @@ class _ScreenSettingsState extends State { AppLocalizations.of(context)! .settingsTitleBehavior, Icons.psychology_rounded, () { - HapticFeedback.selectionClick(); + selectionHaptic(); Navigator.push( context, MaterialPageRoute( @@ -382,7 +382,7 @@ class _ScreenSettingsState extends State { AppLocalizations.of(context)! .settingsTitleInterface, Icons.web_asset_rounded, () { - HapticFeedback.selectionClick(); + selectionHaptic(); Navigator.push( context, MaterialPageRoute( @@ -392,7 +392,7 @@ class _ScreenSettingsState extends State { button( AppLocalizations.of(context)!.settingsTitleExport, Icons.share_rounded, () { - HapticFeedback.selectionClick(); + selectionHaptic(); Navigator.push( context, MaterialPageRoute( @@ -402,7 +402,7 @@ class _ScreenSettingsState extends State { button( AppLocalizations.of(context)!.settingsTitleAbout, Icons.help_rounded, () { - HapticFeedback.selectionClick(); + selectionHaptic(); Navigator.push( context, MaterialPageRoute( diff --git a/lib/settings/about.dart b/lib/settings/about.dart index 8ac9ece..67ab3b1 100644 --- a/lib/settings/about.dart +++ b/lib/settings/about.dart @@ -1,11 +1,11 @@ import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import '../main.dart'; import '../screen_settings.dart'; -import '../worker_update.dart'; +import '../worker/haptic.dart'; +import '../worker/update.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:bitsdojo_window/bitsdojo_window.dart'; @@ -97,7 +97,7 @@ class _ScreenSettingsAboutState extends State { .settingsCheckForUpdates, (prefs!.getBool("checkUpdateOnSettingsOpen") ?? false), (value) { - HapticFeedback.selectionClick(); + selectionHaptic(); prefs!.setBool("checkUpdateOnSettingsOpen", value); setState(() {}); }), @@ -134,7 +134,7 @@ class _ScreenSettingsAboutState extends State { ? Icons.info_outline_rounded : Icons.update_rounded), () { if (updateLoading) return; - HapticFeedback.selectionClick(); + selectionHaptic(); if ((Version.parse(latestVersion ?? "1.0.0") > Version.parse(currentVersion ?? "2.0.0")) && (updateStatus == "ok")) { @@ -147,21 +147,21 @@ class _ScreenSettingsAboutState extends State { titleDivider(), button(AppLocalizations.of(context)!.settingsGithub, SimpleIcons.github, () { - HapticFeedback.selectionClick(); + selectionHaptic(); launchUrl( mode: LaunchMode.inAppBrowserView, Uri.parse(repoUrl)); }), button(AppLocalizations.of(context)!.settingsReportIssue, Icons.report_rounded, () { - HapticFeedback.selectionClick(); + selectionHaptic(); launchUrl( mode: LaunchMode.inAppBrowserView, Uri.parse("$repoUrl/issues")); }), button(AppLocalizations.of(context)!.settingsMainDeveloper, Icons.developer_board_rounded, () { - HapticFeedback.selectionClick(); + selectionHaptic(); launchUrl( mode: LaunchMode.inAppBrowserView, Uri.parse( diff --git a/lib/settings/behavior.dart b/lib/settings/behavior.dart index d074bad..d467980 100644 --- a/lib/settings/behavior.dart +++ b/lib/settings/behavior.dart @@ -1,9 +1,9 @@ import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import '../main.dart'; +import '../worker/haptic.dart'; import '../screen_settings.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -89,7 +89,7 @@ class _ScreenSettingsBehaviorState extends State { hintText: "You are a helpful assistant", suffixIcon: IconButton( onPressed: () { - HapticFeedback.selectionClick(); + selectionHaptic(); prefs?.setString( "system", (systemInputController.text.isNotEmpty) @@ -104,7 +104,7 @@ class _ScreenSettingsBehaviorState extends State { context, AppLocalizations.of(context)!.settingsDisableMarkdown, (prefs!.getBool("noMarkdown") ?? false), (value) { - HapticFeedback.selectionClick(); + selectionHaptic(); prefs!.setBool("noMarkdown", value); setState(() {}); }) diff --git a/lib/settings/export.dart b/lib/settings/export.dart index d980ac1..aeb99ed 100644 --- a/lib/settings/export.dart +++ b/lib/settings/export.dart @@ -2,9 +2,9 @@ import 'dart:io'; import 'dart:convert'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import '../main.dart'; +import '../worker/haptic.dart'; import '../screen_settings.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -75,7 +75,7 @@ class _ScreenSettingsExportState extends State { // const SizedBox(height: 16), button(AppLocalizations.of(context)!.settingsExportChats, Icons.upload_rounded, () async { - HapticFeedback.selectionClick(); + selectionHaptic(); var path = await FilePicker.platform.saveFile( type: FileType.custom, allowedExtensions: ["json"], @@ -83,7 +83,7 @@ class _ScreenSettingsExportState extends State { "ollama-export-${DateFormat('yyyy-MM-dd-H-m-s').format(DateTime.now())}.json", bytes: utf8.encode( jsonEncode(prefs!.getStringList("chats") ?? []))); - HapticFeedback.selectionClick(); + selectionHaptic(); if (path == null) return; if (Platform.isWindows || Platform.isLinux || @@ -94,7 +94,7 @@ class _ScreenSettingsExportState extends State { }), button(AppLocalizations.of(context)!.settingsImportChats, Icons.download_rounded, () { - HapticFeedback.selectionClick(); + selectionHaptic(); showDialog( context: context, builder: (context) { @@ -106,14 +106,14 @@ class _ScreenSettingsExportState extends State { actions: [ TextButton( onPressed: () { - HapticFeedback.selectionClick(); + selectionHaptic(); Navigator.of(context).pop(); }, child: Text(AppLocalizations.of(context)! .settingsImportChatsCancel)), TextButton( onPressed: () async { - HapticFeedback.selectionClick(); + selectionHaptic(); FilePickerResult? result = await FilePicker.platform.pickFiles( type: FileType.custom, diff --git a/lib/settings/interface.dart b/lib/settings/interface.dart index b3b8d24..89357b0 100644 --- a/lib/settings/interface.dart +++ b/lib/settings/interface.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../main.dart'; +import '../worker/haptic.dart'; import '../screen_settings.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -76,7 +77,7 @@ class _ScreenSettingsInterfaceState extends State { context, AppLocalizations.of(context)!.settingsShowModelTags, (prefs!.getBool("modelTags") ?? false), (value) { - HapticFeedback.selectionClick(); + selectionHaptic(); prefs!.setBool("modelTags", value); setState(() {}); }), @@ -86,44 +87,10 @@ class _ScreenSettingsInterfaceState extends State { .settingsResetOnModelChange, (prefs!.getBool("resetOnModelSelect") ?? true), (value) { - HapticFeedback.selectionClick(); + selectionHaptic(); prefs!.setBool("resetOnModelSelect", value); setState(() {}); }), - titleDivider(), - toggle( - context, - AppLocalizations.of(context)!.settingsGenerateTitles, - (prefs!.getBool("generateTitles") ?? true), (value) { - HapticFeedback.selectionClick(); - prefs!.setBool("generateTitles", value); - setState(() {}); - }), - toggle( - context, - AppLocalizations.of(context)!.settingsEnableEditing, - (prefs!.getBool("enableEditing") ?? true), (value) { - HapticFeedback.selectionClick(); - prefs!.setBool("enableEditing", value); - setState(() {}); - }), - toggle( - context, - AppLocalizations.of(context)!.settingsAskBeforeDelete, - (prefs!.getBool("askBeforeDeletion") ?? false), - (value) { - HapticFeedback.selectionClick(); - prefs!.setBool("askBeforeDeletion", value); - setState(() {}); - }), - toggle( - context, - AppLocalizations.of(context)!.settingsShowTips, - (prefs!.getBool("tips") ?? true), (value) { - HapticFeedback.selectionClick(); - prefs!.setBool("tips", value); - setState(() {}); - }), titleDivider(bottom: 20), SegmentedButton( segments: [ @@ -142,12 +109,68 @@ class _ScreenSettingsInterfaceState extends State { prefs!.getString("requestType") ?? "stream" }, onSelectionChanged: (p0) { - HapticFeedback.selectionClick(); + 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(), + toggle( + context, + AppLocalizations.of(context)! + .settingsEnableHapticFeedback, + (prefs!.getBool("enableHaptic") ?? true), (value) { + prefs!.setBool("enableHaptic", value); + selectionHaptic(); + setState(() {}); + }), + (Platform.isWindows || Platform.isLinux || Platform.isMacOS) + ? toggle( + context, + AppLocalizations.of(context)! + .settingsMaximizeOnStart, + (prefs!.getBool("maximizeOnStart") ?? false), + (value) { + selectionHaptic(); + prefs!.setBool("maximizeOnStart", value); + setState(() {}); + }) + : const SizedBox.shrink(), + const SizedBox(height: 16), SegmentedButton( segments: [ ButtonSegment( @@ -170,7 +193,7 @@ class _ScreenSettingsInterfaceState extends State { prefs!.getString("brightness") ?? "system" }, onSelectionChanged: (p0) { - HapticFeedback.selectionClick(); + selectionHaptic(); var tmp = prefs!.getString("brightness") ?? "system"; prefs!.setString("brightness", p0.elementAt(0)); setState(() {}); diff --git a/lib/worker/haptic.dart b/lib/worker/haptic.dart new file mode 100644 index 0000000..37ffb52 --- /dev/null +++ b/lib/worker/haptic.dart @@ -0,0 +1,22 @@ +import 'package:flutter/services.dart'; +import '../main.dart'; + +void lightHaptic() { + if (!(prefs!.getBool("enableHaptic") ?? true)) return; + HapticFeedback.lightImpact(); +} + +void mediumHaptic() { + if (!(prefs!.getBool("enableHaptic") ?? true)) return; + HapticFeedback.mediumImpact(); +} + +void heavyHaptic() { + if (!(prefs!.getBool("enableHaptic") ?? true)) return; + HapticFeedback.heavyImpact(); +} + +void selectionHaptic() { + if (!(prefs!.getBool("enableHaptic") ?? true)) return; + HapticFeedback.selectionClick(); +} diff --git a/lib/worker_setter.dart b/lib/worker/setter.dart similarity index 99% rename from lib/worker_setter.dart rename to lib/worker/setter.dart index 4f0e3d1..86c4cd7 100644 --- a/lib/worker_setter.dart +++ b/lib/worker/setter.dart @@ -1,10 +1,10 @@ import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'main.dart'; +import 'haptic.dart'; +import '../main.dart'; import 'package:dartx/dartx.dart'; import 'package:ollama_dart/ollama_dart.dart' as llama; @@ -63,7 +63,7 @@ void setModel(BuildContext context, Function setState) { load(); if (useModel) return; - HapticFeedback.selectionClick(); + selectionHaptic(); var content = StatefulBuilder(builder: (context, setLocalState) { setModalState = setLocalState; diff --git a/lib/worker_update.dart b/lib/worker/update.dart similarity index 96% rename from lib/worker_update.dart rename to lib/worker/update.dart index cd3de30..ab7ae5e 100644 --- a/lib/worker_update.dart +++ b/lib/worker/update.dart @@ -2,8 +2,8 @@ import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; +import 'haptic.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:http/http.dart' as http; @@ -132,14 +132,14 @@ void updateDialog(BuildContext context, Function title) { actions: [ TextButton( onPressed: () { - HapticFeedback.selectionClick(); + selectionHaptic(); Navigator.of(context).pop(); }, child: Text(AppLocalizations.of(context)! .settingsUpdateDialogCancel)), TextButton( onPressed: () { - HapticFeedback.selectionClick(); + selectionHaptic(); Navigator.of(context).pop(); launchUrl( mode: LaunchMode.inAppBrowserView,