Improved desktop mode

This commit is contained in:
JHubi1 2024-06-26 16:07:02 +02:00
parent 4cc9e7d4c3
commit 34044e74bb
No known key found for this signature in database
GPG Key ID: 7BF82570CBBBD050
6 changed files with 442 additions and 253 deletions

View File

@ -165,26 +165,51 @@
"description": "Title of the behavior settings section", "description": "Title of the behavior settings section",
"context": "Visible in the settings view" "context": "Visible in the settings view"
}, },
"settingsDescriptionBehavior": "Change the behavior of the AI to your liking.",
"@settingsDescriptionBehavior": {
"description": "Description of the behavior settings section",
"context": "Visible in the settings view"
},
"settingsTitleInterface": "Interface", "settingsTitleInterface": "Interface",
"@settingsTitleInterface": { "@settingsTitleInterface": {
"description": "Title of the interface settings section", "description": "Title of the interface settings section",
"context": "Visible in the settings view" "context": "Visible in the settings view"
}, },
"settingsDescriptionInterface": "Edit how Ollama App looks and behaves.",
"@settingsDescriptionInterface": {
"description": "Description of the interface settings section",
"context": "Visible in the settings view"
},
"settingsTitleVoice": "Voice", "settingsTitleVoice": "Voice",
"@settingsTitleVoice": { "@settingsTitleVoice": {
"description": "Title of the voice settings section. Do not translate if not required!", "description": "Title of the voice settings section. Do not translate if not required!",
"context": "Visible in the settings view" "context": "Visible in the settings view"
}, },
"settingsDescriptionVoice": "Enable voice mode and configure voice settings.",
"@settingsDescriptionVoice": {
"description": "Description of the voice settings section",
"context": "Visible in the settings view"
},
"settingsTitleExport": "Export", "settingsTitleExport": "Export",
"@settingsTitleExport": { "@settingsTitleExport": {
"description": "Title of the export settings section", "description": "Title of the export settings section",
"context": "Visible in the settings view" "context": "Visible in the settings view"
}, },
"settingsDescriptionExport": "Export and import your chat history.",
"@settingsDescriptionExport": {
"description": "Description of the export settings section",
"context": "Visible in the settings view"
},
"settingsTitleAbout": "About", "settingsTitleAbout": "About",
"@settingsTitleAbout": { "@settingsTitleAbout": {
"description": "Title of the about settings section", "description": "Title of the about settings section",
"context": "Visible in the settings view" "context": "Visible in the settings view"
}, },
"settingsDescriptionAbout": "Check for updates and learn more about Ollama App.",
"@settingsDescriptionAbout": {
"description": "Description of the about settings section",
"context": "Visible in the settings view"
},
"settingsSavedAutomatically": "Settings are saved automatically", "settingsSavedAutomatically": "Settings are saved automatically",
"@settingsSavedAutomatically": { "@settingsSavedAutomatically": {
"description": "Text displayed when settings are saved automatically", "description": "Text displayed when settings are saved automatically",

View File

@ -259,7 +259,8 @@ class _MainAppState extends State<MainApp> {
left: desktopLayoutRequired(context) ? 17 : 12, left: desktopLayoutRequired(context) ? 17 : 12,
right: desktopLayoutRequired(context) ? 17 : 12); right: desktopLayoutRequired(context) ? 17 : 12);
return List.from([ return List.from([
desktopLayout(context) desktopFeature() ? const SizedBox(height: 8) : const SizedBox.shrink(),
desktopLayoutNotRequired(context)
? const SizedBox.shrink() ? const SizedBox.shrink()
: (Padding( : (Padding(
padding: padding, padding: padding,
@ -282,11 +283,14 @@ class _MainAppState extends State<MainApp> {
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
]))))), ]))))),
desktopLayout(context) desktopLayoutNotRequired(context)
? const SizedBox.shrink() ? const SizedBox.shrink()
: (!allowMultipleChats && !allowSettings) : (!allowMultipleChats && !allowSettings)
? const SizedBox.shrink() ? const SizedBox.shrink()
: const Divider(), : Divider(
color: desktopLayout(context)
? Theme.of(context).colorScheme.onSurface.withAlpha(20)
: null),
(allowMultipleChats) (allowMultipleChats)
? (Padding( ? (Padding(
padding: padding, padding: padding,
@ -1513,7 +1517,7 @@ class _MainAppState extends State<MainApp> {
drawerEdgeDragWidth: drawerEdgeDragWidth:
desktopLayout(context) ? null : MediaQuery.of(context).size.width, desktopLayout(context) ? null : MediaQuery.of(context).size.width,
drawer: Builder(builder: (context) { drawer: Builder(builder: (context) {
if (desktopLayoutRequired(context)) { if (desktopLayoutRequired(context) && !settingsOpen) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (Navigator.of(context).canPop()) { if (Navigator.of(context).canPop()) {
Navigator.of(context).pop(); Navigator.of(context).pop();

View File

@ -19,6 +19,7 @@ import 'package:dartx/dartx.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:dynamic_color/dynamic_color.dart'; import 'package:dynamic_color/dynamic_color.dart';
import 'package:transparent_image/transparent_image.dart';
Widget toggle(BuildContext context, String text, bool value, Widget toggle(BuildContext context, String text, bool value,
Function(bool value) onChanged, Function(bool value) onChanged,
@ -120,24 +121,48 @@ Widget title(String text, {double top = 16, double bottom = 16}) {
])); ]));
} }
Widget titleDivider({double top = 16, double bottom = 16}) { 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 Padding(
padding: EdgeInsets.only(left: 8, right: 8, top: top, bottom: bottom), padding: EdgeInsets.only(left: 8, right: 8, top: top, bottom: bottom),
child: const Row( child: const Row(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
children: [ children: [Expanded(child: Divider())]));
Expanded(child: Divider()), }
],
)); Widget verticalTitleDivider(
{double? left, double? right, BuildContext? context}) {
left ??= (context != null && desktopLayoutNotRequired(context)) ? 32 : 16;
right ??= (context != null && desktopLayoutNotRequired(context)) ? 32 : 16;
return Padding(
padding: EdgeInsets.only(left: left, right: right, top: 8, bottom: 8),
child: const Row(mainAxisSize: MainAxisSize.max, children: [
// Expanded(child:
VerticalDivider()
// ),
]));
} }
Widget button(String text, IconData? icon, void Function()? onPressed, Widget button(String text, IconData? icon, void Function()? onPressed,
{Color? color, {BuildContext? context,
Color? color,
bool disabled = false, bool disabled = false,
bool replaceIconIfNull = false,
String? description,
void Function()? onDisabledTap, void Function()? onDisabledTap,
void Function()? onLongTap, void Function()? onLongTap,
void Function()? onDoubleTap}) { void Function()? onDoubleTap}) {
return InkWell( if (description != null &&
(context != null && desktopLayoutNotRequired(context)) &&
!description.startsWith("\n")) {
description = "$description";
}
return Padding(
padding: (context != null && desktopLayoutNotRequired(context))
? const EdgeInsets.only(top: 8, bottom: 8)
: EdgeInsets.zero,
child: InkWell(
onTap: disabled onTap: disabled
? () { ? () {
selectionHaptic(); selectionHaptic();
@ -146,23 +171,49 @@ Widget button(String text, IconData? icon, void Function()? onPressed,
} }
} }
: onPressed, : onPressed,
onLongPress: onLongTap, onLongPress: (description != null && context != null)
? desktopLayoutNotRequired(context)
? null
: () {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(description!.trim()),
showCloseIcon: true));
}
: onLongTap,
onDoubleTap: onDoubleTap, onDoubleTap: onDoubleTap,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
child: Padding( child: Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: Row(children: [ child: Row(children: [
(icon != null) (icon != null || replaceIconIfNull)
? Icon(icon, color: disabled ? Colors.grey : color) ? replaceIconIfNull
? ImageIcon(MemoryImage(kTransparentImage))
: Icon(icon, color: disabled ? Colors.grey : color)
: const SizedBox.shrink(), : const SizedBox.shrink(),
(icon != null) (icon != null || replaceIconIfNull)
? const SizedBox(width: 16, height: 42) ? const SizedBox(width: 16, height: 42)
: const SizedBox.shrink(), : const SizedBox.shrink(),
Expanded( Expanded(
child: Text(text, child: (context != null)
style: TextStyle(color: disabled ? Colors.grey : color))) ? RichText(
text: TextSpan(
text: text,
style: DefaultTextStyle.of(context).style.copyWith(
color: disabled ? Colors.grey : color),
children: [
(description != null &&
desktopLayoutNotRequired(context))
? TextSpan(
text: description,
style: const TextStyle(color: Colors.grey))
: const TextSpan()
]))
: Text(text,
style:
TextStyle(color: disabled ? Colors.grey : color)))
]), ]),
)); )),
);
} }
class ScreenSettings extends StatefulWidget { class ScreenSettings extends StatefulWidget {
@ -233,6 +284,10 @@ class _ScreenSettingsState extends State<ScreenSettings> {
selectionHaptic(); selectionHaptic();
} }
double iconSize = 1;
bool animatedInitialized = false;
bool animatedDesktop = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -249,6 +304,10 @@ class _ScreenSettingsState extends State<ScreenSettings> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (!animatedInitialized) {
animatedInitialized = true;
animatedDesktop = desktopLayoutNotRequired(context);
}
return PopScope( return PopScope(
canPop: !hostLoading, canPop: !hostLoading,
onPopInvoked: (didPop) { onPopInvoked: (didPop) {
@ -267,9 +326,13 @@ class _ScreenSettingsState extends State<ScreenSettings> {
actions: desktopControlsActions(context)), actions: desktopControlsActions(context)),
body: Padding( body: Padding(
padding: const EdgeInsets.only(left: 16, right: 16), padding: const EdgeInsets.only(left: 16, right: 16),
child: Column(children: [ child: LayoutBuilder(builder: (context, constraints) {
Expanded( var column1 =
child: ListView(children: [ Column(mainAxisSize: MainAxisSize.min, children: [
AnimatedContainer(
duration: const Duration(milliseconds: 200),
height: animatedDesktop ? 8 : 0,
child: const SizedBox.shrink()),
const SizedBox(height: 8), const SizedBox(height: 8),
TextField( TextField(
controller: hostInputController, controller: hostInputController,
@ -280,8 +343,8 @@ class _ScreenSettingsState extends State<ScreenSettings> {
checkHost(); checkHost();
}, },
decoration: InputDecoration( decoration: InputDecoration(
labelText: AppLocalizations.of(context)! labelText:
.settingsHost, AppLocalizations.of(context)!.settingsHost,
hintText: "http://localhost:11434", hintText: "http://localhost:11434",
prefixIcon: IconButton( prefixIcon: IconButton(
tooltip: AppLocalizations.of(context)! tooltip: AppLocalizations.of(context)!
@ -331,8 +394,8 @@ class _ScreenSettingsState extends State<ScreenSettings> {
selectionHaptic(); selectionHaptic();
checkHost(); checkHost();
}, },
icon: const Icon( icon:
Icons.save_rounded), const Icon(Icons.save_rounded),
)), )),
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
error: (hostInvalidHost || hostInvalidUrl) error: (hostInvalidHost || hostInvalidUrl)
@ -349,8 +412,9 @@ class _ScreenSettingsState extends State<ScreenSettings> {
: "url")), : "url")),
showCloseIcon: true)); showCloseIcon: true));
}, },
highlightColor: Colors.transparent,
splashFactory: NoSplash.splashFactory, splashFactory: NoSplash.splashFactory,
highlightColor: Colors.transparent,
hoverColor: Colors.transparent,
child: Row( child: Row(
children: [ children: [
Icon(Icons.error_rounded, Icon(Icons.error_rounded,
@ -375,8 +439,9 @@ class _ScreenSettingsState extends State<ScreenSettings> {
onTap: () { onTap: () {
selectionHaptic(); selectionHaptic();
}, },
highlightColor: Colors.transparent,
splashFactory: NoSplash.splashFactory, splashFactory: NoSplash.splashFactory,
highlightColor: Colors.transparent,
hoverColor: Colors.transparent,
child: hostLoading child: hostLoading
? Row( ? Row(
children: [ children: [
@ -384,13 +449,11 @@ class _ScreenSettingsState extends State<ScreenSettings> {
color: Colors.grey), color: Colors.grey),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
AppLocalizations.of( AppLocalizations.of(context)!
context)!
.settingsHostChecking, .settingsHostChecking,
style: const TextStyle( style: const TextStyle(
color: Colors.grey, color: Colors.grey,
fontFamily: fontFamily: "monospace"))
"monospace"))
], ],
) )
: Row( : Row(
@ -403,24 +466,22 @@ class _ScreenSettingsState extends State<ScreenSettings> {
.primary)), .primary)),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
AppLocalizations.of( AppLocalizations.of(context)!
context)!
.settingsHostValid, .settingsHostValid,
style: TextStyle( style: TextStyle(
color: Colors.green color: Colors.green
.harmonizeWith( .harmonizeWith(
Theme.of( Theme.of(context)
context)
.colorScheme .colorScheme
.primary), .primary),
fontFamily: fontFamily: "monospace"))
"monospace"))
], ],
)))), ))))
titleDivider(bottom: 4), ]);
var column2 =
Column(mainAxisSize: MainAxisSize.min, children: [
button( button(
AppLocalizations.of(context)! AppLocalizations.of(context)!.settingsTitleBehavior,
.settingsTitleBehavior,
Icons.psychology_rounded, () { Icons.psychology_rounded, () {
selectionHaptic(); selectionHaptic();
Navigator.push( Navigator.push(
@ -428,7 +489,10 @@ class _ScreenSettingsState extends State<ScreenSettings> {
MaterialPageRoute( MaterialPageRoute(
builder: (context) => builder: (context) =>
const ScreenSettingsBehavior())); const ScreenSettingsBehavior()));
}), },
context: context,
description:
"\n${AppLocalizations.of(context)!.settingsDescriptionBehavior}"),
button( button(
AppLocalizations.of(context)! AppLocalizations.of(context)!
.settingsTitleInterface, .settingsTitleInterface,
@ -439,7 +503,10 @@ class _ScreenSettingsState extends State<ScreenSettings> {
MaterialPageRoute( MaterialPageRoute(
builder: (context) => builder: (context) =>
const ScreenSettingsInterface())); const ScreenSettingsInterface()));
}), },
context: context,
description:
"\n${AppLocalizations.of(context)!.settingsDescriptionInterface}"),
(!desktopFeature()) (!desktopFeature())
? button( ? button(
AppLocalizations.of(context)! AppLocalizations.of(context)!
@ -451,7 +518,10 @@ class _ScreenSettingsState extends State<ScreenSettings> {
MaterialPageRoute( MaterialPageRoute(
builder: (context) => builder: (context) =>
const ScreenSettingsVoice())); const ScreenSettingsVoice()));
}) },
context: context,
description:
"\n${AppLocalizations.of(context)!.settingsDescriptionVoice}")
: const SizedBox.shrink(), : const SizedBox.shrink(),
button( button(
AppLocalizations.of(context)!.settingsTitleExport, AppLocalizations.of(context)!.settingsTitleExport,
@ -462,9 +532,11 @@ class _ScreenSettingsState extends State<ScreenSettings> {
MaterialPageRoute( MaterialPageRoute(
builder: (context) => builder: (context) =>
const ScreenSettingsExport())); const ScreenSettingsExport()));
}), },
button( context: context,
AppLocalizations.of(context)!.settingsTitleAbout, description:
"\n${AppLocalizations.of(context)!.settingsDescriptionExport}"),
button(AppLocalizations.of(context)!.settingsTitleAbout,
Icons.help_rounded, () { Icons.help_rounded, () {
selectionHaptic(); selectionHaptic();
Navigator.push( Navigator.push(
@ -472,17 +544,105 @@ class _ScreenSettingsState extends State<ScreenSettings> {
MaterialPageRoute( MaterialPageRoute(
builder: (context) => builder: (context) =>
const ScreenSettingsAbout())); const ScreenSettingsAbout()));
}) },
]), context: context,
description:
"\n${AppLocalizations.of(context)!.settingsDescriptionAbout}")
]);
animatedDesktop = desktopLayoutNotRequired(context);
return Column(children: [
Expanded(
child: desktopLayoutNotRequired(context)
? Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
column1,
Expanded(
child: Center(
child: InkWell(
splashFactory:
NoSplash.splashFactory,
highlightColor:
Colors.transparent,
enableFeedback: false,
hoverColor: Colors.transparent,
onTap: () async {
if (iconSize != 1) return;
heavyHaptic();
setState(() {
iconSize = 0.8;
});
await Future.delayed(
const Duration(
milliseconds: 200));
setState(() {
iconSize = 1.2;
});
await Future.delayed(
const Duration(
milliseconds: 200));
setState(() {
iconSize = 1;
});
},
child: AnimatedScale(
scale: iconSize,
duration: const Duration(
milliseconds: 400),
child: const ImageIcon(
AssetImage(
"assets/logo512.png"),
size: 44),
), ),
))),
Transform.translate(
offset: const Offset(0, 8),
child: button(
AppLocalizations.of(
context)!
.settingsSavedAutomatically,
Icons.info_rounded,
null,
color: Colors.grey
.harmonizeWith(
Theme.of(context)
.colorScheme
.primary)),
)
])),
verticalTitleDivider(context: context),
Expanded(child: column2)
])
: ListView(children: [
column1,
AnimatedOpacity(
opacity: animatedDesktop ? 0 : 1,
duration:
const Duration(milliseconds: 200),
child: titleDivider(bottom: 4)),
AnimatedOpacity(
opacity: animatedDesktop ? 0 : 1,
duration:
const Duration(milliseconds: 200),
child: column2)
])),
const SizedBox(height: 8), const SizedBox(height: 8),
button( desktopLayoutNotRequired(context)
? const SizedBox.shrink()
: button(
AppLocalizations.of(context)! AppLocalizations.of(context)!
.settingsSavedAutomatically, .settingsSavedAutomatically,
Icons.info_rounded, Icons.info_rounded,
null, null,
color: Colors.grey.harmonizeWith( color: Colors.grey.harmonizeWith(
Theme.of(context).colorScheme.primary)) Theme.of(context).colorScheme.primary))
]))))); ]);
})))));
} }
} }

View File

@ -41,9 +41,7 @@ class _ScreenSettingsAboutState extends State<ScreenSettingsAbout> {
Text(AppLocalizations.of(context)!.settingsTitleAbout), Text(AppLocalizations.of(context)!.settingsTitleAbout),
Expanded(child: SizedBox(height: 200, child: MoveWindow())) Expanded(child: SizedBox(height: 200, child: MoveWindow()))
]), ]),
actions: actions: desktopControlsActions(context)),
desktopControlsActions(context)
),
body: Padding( body: Padding(
padding: const EdgeInsets.only(left: 16, right: 16), padding: const EdgeInsets.only(left: 16, right: 16),
child: Column(children: [ child: Column(children: [
@ -110,7 +108,7 @@ class _ScreenSettingsAboutState extends State<ScreenSettingsAbout> {
prefs!.setBool("checkUpdateOnSettingsOpen", value); prefs!.setBool("checkUpdateOnSettingsOpen", value);
setState(() {}); setState(() {});
}), }),
titleDivider(), titleDivider(context: context),
button(AppLocalizations.of(context)!.settingsGithub, button(AppLocalizations.of(context)!.settingsGithub,
SimpleIcons.github, () { SimpleIcons.github, () {
selectionHaptic(); selectionHaptic();

View File

@ -46,7 +46,7 @@ class _ScreenSettingsBehaviorState extends State<ScreenSettingsBehavior> {
TextField( TextField(
controller: systemInputController, controller: systemInputController,
keyboardType: TextInputType.multiline, keyboardType: TextInputType.multiline,
maxLines: 2, maxLines: desktopLayoutNotRequired(context) ? 5 : 2,
decoration: InputDecoration( decoration: InputDecoration(
labelText: AppLocalizations.of(context)! labelText: AppLocalizations.of(context)!
.settingsSystemMessage, .settingsSystemMessage,

View File

@ -64,7 +64,9 @@ class _ScreenSettingsInterfaceState extends State<ScreenSettingsInterface> {
prefs!.setBool("resetOnModelSelect", value); prefs!.setBool("resetOnModelSelect", value);
setState(() {}); setState(() {});
}), }),
titleDivider(bottom: 20), titleDivider(
bottom: desktopLayoutNotRequired(context) ? 38 : 20,
context: context),
SegmentedButton( SegmentedButton(
segments: [ segments: [
ButtonSegment( ButtonSegment(
@ -121,7 +123,7 @@ class _ScreenSettingsInterfaceState extends State<ScreenSettingsInterface> {
prefs!.setBool("tips", value); prefs!.setBool("tips", value);
setState(() {}); setState(() {});
}), }),
titleDivider(), titleDivider(context: context),
toggle( toggle(
context, context,
AppLocalizations.of(context)! AppLocalizations.of(context)!
@ -240,7 +242,7 @@ class _ScreenSettingsInterfaceState extends State<ScreenSettingsInterface> {
})); }));
}); });
}), }),
titleDivider(), titleDivider(context: context),
toggle( toggle(
context, context,
AppLocalizations.of(context)! AppLocalizations.of(context)!