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",
"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": {
"description": "Title of the interface settings section",
"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": {
"description": "Title of the voice settings section. Do not translate if not required!",
"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": {
"description": "Title of the export settings section",
"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": {
"description": "Title of the about settings section",
"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": {
"description": "Text displayed when settings are saved automatically",

View File

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

View File

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

View File

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

View File

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

View File

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