Added chat ui, general ui improvements
This commit is contained in:
parent
13ea97968c
commit
1f1ef19743
|
@ -1,3 +1,5 @@
|
|||
# ollama
|
||||
# Ollama App
|
||||
|
||||
A new Flutter project.
|
||||
A modern and easy-to-use client for Ollama. Currently, not production-ready.
|
||||
|
||||
> Important: This app does not host a Ollama server on device, but rather connects to one and plays with it.
|
||||
|
|
291
lib/main.dart
291
lib/main.dart
|
@ -5,6 +5,9 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||
import 'package:flutter_chat_types/flutter_chat_types.dart' as types;
|
||||
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:visibility_detector/visibility_detector.dart';
|
||||
|
||||
SharedPreferences? prefs;
|
||||
ThemeData? theme;
|
||||
|
@ -75,16 +78,16 @@ class _AppState extends State<App> {
|
|||
systemNavigationBarColor:
|
||||
(MediaQuery.of(context).platformBrightness ==
|
||||
Brightness.light)
|
||||
? themeDark!.colorScheme.background
|
||||
: theme!.colorScheme.background));
|
||||
? (themeDark ?? ThemeData.dark()).colorScheme.background
|
||||
: (theme ?? ThemeData()).colorScheme.background));
|
||||
};
|
||||
// brightness changed function not run at first startup
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
systemNavigationBarColor:
|
||||
(MediaQuery.of(context).platformBrightness ==
|
||||
Brightness.light)
|
||||
? theme!.colorScheme.background
|
||||
: themeDark!.colorScheme.background));
|
||||
systemNavigationBarColor: (MediaQuery.of(context)
|
||||
.platformBrightness ==
|
||||
Brightness.light)
|
||||
? (theme ?? ThemeData()).colorScheme.background
|
||||
: (themeDark ?? ThemeData.dark()).colorScheme.background));
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
|
@ -106,9 +109,11 @@ class MainApp extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _MainAppState extends State<MainApp> {
|
||||
final List<types.Message> _messages = [];
|
||||
List<types.Message> _messages = [];
|
||||
final _user = types.User(id: const Uuid().v4());
|
||||
|
||||
bool logoVisible = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
@ -118,58 +123,234 @@ class _MainAppState extends State<MainApp> {
|
|||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Row(children: [
|
||||
IconButton(
|
||||
onPressed: () {}, icon: const Icon(Icons.menu_open_rounded)),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: const Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [Text("data")]));
|
||||
});
|
||||
},
|
||||
splashFactory: NoSplash.splashFactory,
|
||||
highlightColor: Colors.transparent,
|
||||
enableFeedback: false,
|
||||
child: const SizedBox(
|
||||
height: 72,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text("<none>",
|
||||
overflow: TextOverflow.fade,
|
||||
style: TextStyle(
|
||||
fontFamily: "monospace",
|
||||
fontSize: 16))),
|
||||
SizedBox(width: 4),
|
||||
Icon(Icons.expand_more_rounded)
|
||||
])))),
|
||||
const SizedBox(width: 16),
|
||||
IconButton(
|
||||
onPressed: () {}, icon: const Icon(Icons.restart_alt_rounded))
|
||||
])),
|
||||
title: InkWell(
|
||||
onTap: () {
|
||||
HapticFeedback.selectionClick();
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: const Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [Text("data")]));
|
||||
});
|
||||
},
|
||||
splashFactory: NoSplash.splashFactory,
|
||||
highlightColor: Colors.transparent,
|
||||
enableFeedback: false,
|
||||
child: const SizedBox(
|
||||
height: 72,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text("<none>",
|
||||
overflow: TextOverflow.fade,
|
||||
style: TextStyle(
|
||||
fontFamily: "monospace", fontSize: 16))),
|
||||
SizedBox(width: 4),
|
||||
Icon(Icons.expand_more_rounded)
|
||||
]))),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
_messages = [];
|
||||
HapticFeedback.selectionClick();
|
||||
setState(() {});
|
||||
},
|
||||
icon: const Icon(Icons.restart_alt_rounded))
|
||||
],
|
||||
),
|
||||
body: SizedBox.expand(
|
||||
child: Chat(
|
||||
messages: _messages,
|
||||
onSendPressed: (p0) {},
|
||||
emptyState: Center(
|
||||
child: VisibilityDetector(
|
||||
key: const Key("logoVisible"),
|
||||
onVisibilityChanged: (VisibilityInfo info) {
|
||||
logoVisible = info.visibleFraction > 0;
|
||||
setState(() {});
|
||||
},
|
||||
child: AnimatedOpacity(
|
||||
opacity: logoVisible ? 1.0 : 0.0,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
child: const ImageIcon(AssetImage("assets/logo512.png"),
|
||||
size: 44)))),
|
||||
onSendPressed: (p0) {
|
||||
_messages.insert(
|
||||
0,
|
||||
types.TextMessage(
|
||||
author: _user, id: const Uuid().v4(), text: p0.text));
|
||||
setState(() {});
|
||||
HapticFeedback.selectionClick();
|
||||
},
|
||||
onMessageDoubleTap: (context, p1) {
|
||||
for (var i = 0; i < _messages.length; i++) {
|
||||
if (_messages[i].id == p1.id) {
|
||||
_messages.removeAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
setState(() {});
|
||||
HapticFeedback.selectionClick();
|
||||
},
|
||||
onAttachmentPressed: () {
|
||||
HapticFeedback.selectionClick();
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () async {
|
||||
HapticFeedback.selectionClick();
|
||||
|
||||
Navigator.of(context).pop();
|
||||
final result =
|
||||
await ImagePicker().pickImage(
|
||||
source: ImageSource.gallery,
|
||||
);
|
||||
if (result == null) return;
|
||||
|
||||
final bytes =
|
||||
await result.readAsBytes();
|
||||
final image =
|
||||
await decodeImageFromList(
|
||||
bytes);
|
||||
|
||||
final message = types.ImageMessage(
|
||||
author: _user,
|
||||
createdAt: DateTime.now()
|
||||
.millisecondsSinceEpoch,
|
||||
height: image.height.toDouble(),
|
||||
id: const Uuid().v4(),
|
||||
name: result.name,
|
||||
size: bytes.length,
|
||||
uri: result.path,
|
||||
width: image.width.toDouble(),
|
||||
);
|
||||
|
||||
_messages.insert(0, message);
|
||||
setState(() {});
|
||||
HapticFeedback.selectionClick();
|
||||
},
|
||||
icon: const Icon(Icons.image_rounded),
|
||||
label: const Text("Upload Image"))),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () async {
|
||||
HapticFeedback.selectionClick();
|
||||
|
||||
Navigator.of(context).pop();
|
||||
final result = await FilePicker
|
||||
.platform
|
||||
.pickFiles(
|
||||
type: FileType.custom,
|
||||
allowedExtensions: ["pdf"]);
|
||||
if (result == null ||
|
||||
result.files.single.path ==
|
||||
null) return;
|
||||
|
||||
final message = types.FileMessage(
|
||||
author: _user,
|
||||
createdAt: DateTime.now()
|
||||
.millisecondsSinceEpoch,
|
||||
id: const Uuid().v4(),
|
||||
name: result.files.single.name,
|
||||
size: result.files.single.size,
|
||||
uri: result.files.single.path!,
|
||||
);
|
||||
|
||||
_messages.insert(0, message);
|
||||
setState(() {});
|
||||
HapticFeedback.selectionClick();
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.file_copy_rounded),
|
||||
label: const Text("Upload File")))
|
||||
]));
|
||||
});
|
||||
},
|
||||
l10n: const ChatL10nEn(),
|
||||
inputOptions: const InputOptions(
|
||||
keyboardType: TextInputType.text,
|
||||
sendButtonVisibilityMode: SendButtonVisibilityMode.always),
|
||||
user: _user,
|
||||
theme: (MediaQuery.of(context).platformBrightness ==
|
||||
Brightness.light)
|
||||
hideBackgroundOnEmojiMessages: false,
|
||||
theme: (MediaQuery.of(context).platformBrightness == Brightness.light)
|
||||
? DefaultChatTheme(
|
||||
backgroundColor: theme!.colorScheme.background,
|
||||
primaryColor: theme!.colorScheme.primary)
|
||||
backgroundColor:
|
||||
(theme ?? ThemeData()).colorScheme.background,
|
||||
primaryColor:
|
||||
(theme ?? ThemeData()).colorScheme.primary,
|
||||
attachmentButtonIcon:
|
||||
const Icon(Icons.file_upload_rounded),
|
||||
sendButtonIcon: const Icon(Icons.send_rounded),
|
||||
inputBackgroundColor: (theme ?? ThemeData())
|
||||
.colorScheme
|
||||
.onBackground
|
||||
.withAlpha(10),
|
||||
inputTextColor:
|
||||
(theme ?? ThemeData()).colorScheme.onBackground,
|
||||
inputBorderRadius:
|
||||
const BorderRadius.all(Radius.circular(64)),
|
||||
inputPadding: const EdgeInsets.all(16),
|
||||
inputMargin: EdgeInsets.only(
|
||||
left: 8,
|
||||
right: 8,
|
||||
bottom:
|
||||
(MediaQuery.of(context).viewInsets.bottom == 0.0)
|
||||
? 0
|
||||
: 8))
|
||||
: DarkChatTheme(
|
||||
backgroundColor: themeDark!.colorScheme.background,
|
||||
primaryColor: themeDark!.colorScheme.primary))));
|
||||
backgroundColor:
|
||||
(themeDark ?? ThemeData.dark()).colorScheme.background,
|
||||
primaryColor: (themeDark ?? ThemeData.dark()).colorScheme.primary.withAlpha(40),
|
||||
attachmentButtonIcon: const Icon(Icons.file_upload_rounded),
|
||||
sendButtonIcon: const Icon(Icons.send_rounded),
|
||||
inputBackgroundColor: (themeDark ?? ThemeData()).colorScheme.onBackground.withAlpha(40),
|
||||
inputTextColor: (themeDark ?? ThemeData()).colorScheme.onBackground,
|
||||
inputBorderRadius: const BorderRadius.all(Radius.circular(64)),
|
||||
inputPadding: const EdgeInsets.all(16),
|
||||
inputMargin: EdgeInsets.only(left: 8, right: 8, bottom: (MediaQuery.of(context).viewInsets.bottom == 0.0) ? 0 : 8)))),
|
||||
drawer: NavigationDrawer(
|
||||
onDestinationSelected: (value) {
|
||||
if (value == 1) {
|
||||
HapticFeedback.selectionClick();
|
||||
Navigator.of(context).pop();
|
||||
_messages = [];
|
||||
setState(() {});
|
||||
} else if (value == 2) {
|
||||
HapticFeedback.selectionClick();
|
||||
Navigator.of(context).pop();
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
|
||||
content: Text("Settings not implemented yet."),
|
||||
showCloseIcon: true));
|
||||
}
|
||||
},
|
||||
selectedIndex: 1,
|
||||
children: const [
|
||||
NavigationDrawerDestination(
|
||||
icon: ImageIcon(AssetImage("assets/logo512.png")),
|
||||
label: Text("Ollama"),
|
||||
),
|
||||
Divider(),
|
||||
NavigationDrawerDestination(
|
||||
icon: Icon(Icons.add_rounded), label: Text("New Chat")),
|
||||
NavigationDrawerDestination(
|
||||
icon: Icon(Icons.settings_rounded), label: Text("Settings"))
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
|
138
pubspec.lock
138
pubspec.lock
|
@ -1,6 +1,14 @@
|
|||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
animated_text_kit:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: animated_text_kit
|
||||
sha256: "37392a5376c9a1a503b02463c38bc0342ef814ddbb8f9977bc90f2a84b22fa92"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.2"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -41,6 +49,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
cross_file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cross_file
|
||||
sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.4+1"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -97,6 +113,46 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
file_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: file_picker
|
||||
sha256: "29c90806ac5f5fb896547720b73b17ee9aed9bba540dc5d91fe29f8c5745b10a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.0.3"
|
||||
file_selector_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_linux
|
||||
sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.2+1"
|
||||
file_selector_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_macos
|
||||
sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.4"
|
||||
file_selector_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_platform_interface
|
||||
sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.2"
|
||||
file_selector_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_windows
|
||||
sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.3+1"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -158,6 +214,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: "8cf40eebf5dec866a6d1956ad7b4f7016e6c0cc69847ab946833b7d43743809f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.19"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
|
@ -192,6 +256,70 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
image_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: image_picker
|
||||
sha256: "33974eca2e87e8b4e3727f1b94fa3abcb25afe80b6bc2c4d449a0e150aedf720"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
image_picker_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_android
|
||||
sha256: "0f57fee1e8bfadf8cc41818bbcd7f72e53bb768a54d9496355d5e8a5681a19f1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.12+1"
|
||||
image_picker_for_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_for_web
|
||||
sha256: "5d6eb13048cd47b60dbf1a5495424dea226c5faf3950e20bf8120a58efb5b5f3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.4"
|
||||
image_picker_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_ios
|
||||
sha256: "4824d8c7f6f89121ef0122ff79bb00b009607faecc8545b86bca9ab5ce1e95bf"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.11+2"
|
||||
image_picker_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_linux
|
||||
sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1+1"
|
||||
image_picker_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_macos
|
||||
sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1+1"
|
||||
image_picker_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_platform_interface
|
||||
sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.10.0"
|
||||
image_picker_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_windows
|
||||
sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1+1"
|
||||
intl:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -272,6 +400,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -542,7 +678,7 @@ packages:
|
|||
source: hosted
|
||||
version: "2.1.4"
|
||||
visibility_detector:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: visibility_detector
|
||||
sha256: dd5cc11e13494f432d15939c3aa8ae76844c42b723398643ce9addb88a5ed420
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
name: ollama
|
||||
description: "A new Flutter project."
|
||||
description: "A modern and easy-to-use client for Ollama"
|
||||
publish_to: 'none'
|
||||
version: 0.1.0
|
||||
|
||||
|
@ -12,6 +12,10 @@ dependencies:
|
|||
shared_preferences: ^2.2.3
|
||||
flutter_chat_ui: ^1.6.13
|
||||
uuid: ^4.4.0
|
||||
animated_text_kit: ^4.2.2
|
||||
image_picker: ^1.1.1
|
||||
file_picker: ^8.0.3
|
||||
visibility_detector: ^0.4.0+2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
Loading…
Reference in New Issue