diff --git a/lib/controllers/client_controller.dart b/lib/controllers/client_controller.dart new file mode 100644 index 0000000..066c618 --- /dev/null +++ b/lib/controllers/client_controller.dart @@ -0,0 +1,33 @@ +import "dart:io"; + +import "package:matrix/matrix.dart"; +import "package:nexusbot/controllers/settings_controller.dart"; +import "package:riverpod/riverpod.dart"; +import "package:sqflite_common_ffi/sqflite_ffi.dart"; + +class ClientController extends AsyncNotifier { + @override + Future build() async { + final settings = ref.watch(SettingsController.provider)!; + final client = Client( + "nexusbot", + database: await MatrixSdkDatabase.init( + "NexusBot", + database: await databaseFactoryFfi.openDatabase(inMemoryDatabasePath), + ), + ); + + await client.checkHomeserver(settings.homeserver); + await client.login( + LoginType.mLoginPassword, + identifier: AuthenticationUserIdentifier(user: settings.name), + password: (await File(settings.botPasswordFile).readAsString()).trim(), + ); + + return client; + } + + static final provider = AsyncNotifierProvider( + ClientController.new, + ); +} diff --git a/lib/controllers/room_chat_controller.dart b/lib/controllers/room_chat_controller.dart new file mode 100644 index 0000000..fab7e29 --- /dev/null +++ b/lib/controllers/room_chat_controller.dart @@ -0,0 +1,48 @@ +import "package:flutter_chat_core/flutter_chat_core.dart"; +import "package:flutter_riverpod/flutter_riverpod.dart"; + +class RoomChatController extends Notifier { + RoomChatController(this.id); + final String id; + + @override + InMemoryChatController build() => InMemoryChatController( + messages: [ + Message.text(id: "foo2", authorId: "foo", text: "**Some** text"), + Message.text( + id: "foo3", + authorId: "foo5", + text: "Some text 2 https://federated.nexus", + ), + Message.text( + id: "aksdjflkasdjf", + authorId: "foo", + text: "Some text 2 https://github.com/Henry-hiles/nixos", + ), + Message.system(id: "foo4", authorId: "", text: "system"), + Message.text(id: "foo6", authorId: "foo5", text: "Some text 2"), + Message.image( + id: "foo5", + authorId: "foobar3", + source: + "https://henryhiles.com/_astro/federatedNexus.BvZmkdyc_2b28Im.webp", + ), + Message.text(id: "foo7", authorId: "foobar3", text: "this has an image"), + ], + ); + + void send(String message) { + state.insertMessage( + Message.text( + id: DateTime.now().millisecondsSinceEpoch.toString(), + authorId: "foo", + text: message, + ), + ); + } + + static final provider = + NotifierProvider.family( + RoomChatController.new, + ); +} diff --git a/lib/main.dart b/lib/main.dart index c781996..b31a0d9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,8 +1,10 @@ import "dart:io"; +import "package:flutter_riverpod/flutter_riverpod.dart"; +import "package:nexus/widgets/room_chat.dart"; +import "package:nexus/widgets/sidebar.dart"; import "package:window_manager/window_manager.dart"; import "package:flutter/material.dart"; import "package:dynamic_system_colors/dynamic_system_colors.dart"; -import "package:flutter_hooks/flutter_hooks.dart"; import "package:window_size/window_size.dart"; void main() async { @@ -15,65 +17,66 @@ void main() async { setWindowMinSize(const Size.square(500)); - runApp(const App()); + runApp(ProviderScope(child: const App())); } -class App extends HookWidget { +class App extends StatelessWidget { const App({super.key}); @override - Widget build(BuildContext context) { - final index = useState(0); - final drawer = Drawer( - child: Row( - children: [ - NavigationRail( - useIndicator: false, - labelType: NavigationRailLabelType.none, - onDestinationSelected: (value) => index.value = value, - destinations: [ - NavigationRailDestination( - icon: Icon(Icons.home), - label: Text("Home"), - padding: EdgeInsets.symmetric(vertical: 8), - ), - NavigationRailDestination( - icon: Image.file(File("assets/icon.png"), width: 35), - label: Text("Space 1"), - ), - ], - selectedIndex: index.value, + Widget build(BuildContext context) => DynamicColorBuilder( + builder: (lightDynamic, darkDynamic) => LayoutBuilder( + builder: (context, constraints) { + final isDesktop = constraints.maxWidth > 650; + + return MaterialApp( + debugShowCheckedModeBanner: false, + theme: ThemeData.from( + colorScheme: + lightDynamic ?? ColorScheme.fromSeed(seedColor: Colors.indigo), ), - ], - ), - ); - - return DynamicColorBuilder( - builder: (lightDynamic, darkDynamic) => LayoutBuilder( - builder: (context, constraints) { - final isDesktop = constraints.maxWidth > 650; - - return MaterialApp( - debugShowCheckedModeBanner: false, - theme: ThemeData.from( - colorScheme: lightDynamic ?? ColorScheme.light(), - ), - darkTheme: ThemeData.from( - colorScheme: darkDynamic ?? ColorScheme.dark(), - ), - home: Scaffold( - appBar: isDesktop ? null : AppBar(), - body: Row( + darkTheme: ThemeData.from( + colorScheme: + darkDynamic ?? + ColorScheme.fromSeed( + seedColor: Colors.indigo, + brightness: Brightness.dark, + ), + ), + home: Scaffold( + body: Builder( + builder: (context) => Row( children: [ - if (isDesktop) drawer, - Expanded(child: Column()), + if (isDesktop) Sidebar(), + Expanded( + child: Scaffold( + appBar: AppBar( + leading: isDesktop + ? null + : DrawerButton( + onPressed: () => + Scaffold.of(context).openDrawer(), + ), + actionsPadding: EdgeInsets.symmetric(horizontal: 8), + title: Text("Some Chat Name"), + actions: [ + if (!(Platform.isAndroid || Platform.isIOS)) + IconButton( + onPressed: () => exit(0), + icon: Icon(Icons.close), + ), + ], + ), + body: RoomChat(), + ), + ), ], ), - drawer: isDesktop ? null : drawer, ), - ); - }, - ), - ); - } + drawer: isDesktop ? null : Sidebar(), + ), + ); + }, + ), + ); } diff --git a/lib/widgets/room_chat.dart b/lib/widgets/room_chat.dart new file mode 100644 index 0000000..926fae3 --- /dev/null +++ b/lib/widgets/room_chat.dart @@ -0,0 +1,89 @@ +import "package:flutter/material.dart"; +import "package:flutter_chat_core/flutter_chat_core.dart"; +import "package:flutter_chat_ui/flutter_chat_ui.dart"; +import "package:flutter_link_previewer/flutter_link_previewer.dart"; +import "package:flyer_chat_image_message/flyer_chat_image_message.dart"; +import "package:flyer_chat_system_message/flyer_chat_system_message.dart"; +import "package:flyer_chat_text_message/flyer_chat_text_message.dart"; +import "package:hooks_riverpod/hooks_riverpod.dart"; +import "package:nexus/controllers/room_chat_controller.dart"; + +class RoomChat extends HookConsumerWidget { + const RoomChat({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final controller = RoomChatController.provider("1"); + final theme = Theme.of(context); + return Chat( + currentUserId: "foo", + theme: ChatTheme.fromThemeData(theme).copyWith( + colors: ChatColors.fromThemeData(theme).copyWith( + primary: theme.colorScheme.primaryContainer, + onPrimary: theme.colorScheme.onPrimaryContainer, + ), + ), + builders: Builders( + composerBuilder: (_) => Composer(), + textMessageBuilder: + ( + context, + message, + index, { + required bool isSentByMe, + MessageGroupStatus? groupStatus, + }) => FlyerChatTextMessage( + message: message.copyWith( + text: message.text.replaceAllMapped( + RegExp( + r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+", + ), + (match) => "[${match.group(0)}](${match.group(0)})", + ), + ), + index: index, + linksDecoration: TextDecoration.underline, + ), + linkPreviewBuilder: (_, message, isSentByMe) { + return LinkPreview( + text: message.text, + backgroundColor: isSentByMe + ? theme.colorScheme.inversePrimary + : theme.colorScheme.surfaceContainerLow, + insidePadding: EdgeInsets.symmetric(vertical: 8, horizontal: 16), + linkPreviewData: message.linkPreviewData, + onLinkPreviewDataFetched: (linkPreviewData) { + ref + .watch(controller) + .updateMessage( + message, + message.copyWith(linkPreviewData: linkPreviewData), + ); + }, + // You can still customize the appearance + parentContent: message.text, + ); + }, + imageMessageBuilder: + ( + _, + message, + index, { + required bool isSentByMe, + MessageGroupStatus? groupStatus, + }) => FlyerChatImageMessage(message: message, index: index), + systemMessageBuilder: + ( + _, + message, + index, { + required bool isSentByMe, + MessageGroupStatus? groupStatus, + }) => FlyerChatSystemMessage(message: message, index: index), + ), + onMessageSend: ref.watch(controller.notifier).send, + resolveUser: (id) async => User(id: id, imageSource: "foo"), + chatController: ref.watch(controller), + ); + } +} diff --git a/lib/widgets/sidebar.dart b/lib/widgets/sidebar.dart new file mode 100644 index 0000000..b2d5389 --- /dev/null +++ b/lib/widgets/sidebar.dart @@ -0,0 +1,75 @@ +import "dart:io"; + +import "package:flutter/material.dart"; +import "package:flutter_hooks/flutter_hooks.dart"; + +class Sidebar extends HookWidget { + const Sidebar({super.key}); + + @override + Widget build(BuildContext context) { + final index = useState(0); + return Drawer( + shape: Border(), + child: Row( + children: [ + NavigationRail( + useIndicator: false, + labelType: NavigationRailLabelType.none, + onDestinationSelected: (value) => index.value = value, + destinations: [ + NavigationRailDestination( + icon: Icon(Icons.home), + label: Text("Home"), + padding: EdgeInsets.only(top: 12), + ), + NavigationRailDestination( + icon: Icon(Icons.person), + label: Text("Messages"), + padding: EdgeInsets.only(top: 12), + ), + NavigationRailDestination( + icon: Image.file(File("assets/icon.png"), width: 40), + label: Text("Space 1"), + padding: EdgeInsets.only(top: 12), + ), + ], + selectedIndex: index.value, + ), + Expanded( + child: Scaffold( + backgroundColor: Colors.transparent, + appBar: AppBar( + title: Text("Some Space"), + backgroundColor: Colors.transparent, + ), + body: NavigationRail( + backgroundColor: Colors.transparent, + extended: true, + destinations: [ + NavigationRailDestination( + icon: Icon(Icons.numbers), + label: Text("Room 1"), + ), + NavigationRailDestination( + icon: Icon(Icons.numbers), + label: Text("Room 2"), + ), + NavigationRailDestination( + icon: Icon(Icons.numbers), + label: Text("Room 3"), + ), + NavigationRailDestination( + icon: Icon(Icons.numbers), + label: Text("Room 4"), + ), + ], + selectedIndex: 0, + ), + ), + ), + ], + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 71f6cd2..a46382a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -89,6 +89,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.13.0" + blurhash_dart: + dependency: transitive + description: + name: blurhash_dart + sha256: "43955b6c2e30a7d440028d1af0fa185852f3534b795cc6eb81fbf397b464409f" + url: "https://pub.dev" + source: hosted + version: "1.2.1" boolean_selector: dependency: transitive description: @@ -241,6 +249,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.15.0" + cross_cache: + dependency: transitive + description: + name: cross_cache + sha256: ddf99059e2174d141efa03bea6d51eb7c8983f4a4af43ddee7556bbc43399522 + url: "https://pub.dev" + source: hosted + version: "1.0.5" cross_file: dependency: transitive description: @@ -257,6 +273,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.7" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" custom_lint: dependency: "direct dev" description: @@ -305,6 +329,30 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.11" + diffutil_dart: + dependency: transitive + description: + name: diffutil_dart + sha256: "5e74883aedf87f3b703cb85e815bdc1ed9208b33501556e4a8a5572af9845c81" + url: "https://pub.dev" + source: hosted + version: "4.0.1" + dio: + dependency: transitive + description: + name: dio + sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 + url: "https://pub.dev" + source: hosted + version: "5.9.0" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + url: "https://pub.dev" + source: hosted + version: "2.1.1" dynamic_system_colors: dependency: "direct main" description: @@ -398,6 +446,22 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_chat_core: + dependency: "direct main" + description: + name: flutter_chat_core + sha256: "113f9b0c2c9f8fb77a40d298084d99b28ff4876f99598ad01bea8b731f1acc20" + url: "https://pub.dev" + source: hosted + version: "2.8.0" + flutter_chat_ui: + dependency: "direct main" + description: + name: flutter_chat_ui + sha256: "08a13577ea943d7965e3b1766d3a2119b07b56aced5336900ab7fb4c377d4ca0" + url: "https://pub.dev" + source: hosted + version: "2.9.1" flutter_hooks: dependency: "direct main" description: @@ -414,6 +478,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.14.4" + flutter_link_previewer: + dependency: "direct main" + description: + name: flutter_link_previewer + sha256: "62520ee224515f826dd40e4ccbb95e6d65ac060fbcf94d4620ce840ee1a15b83" + url: "https://pub.dev" + source: hosted + version: "4.1.2" flutter_lints: dependency: "direct dev" description: @@ -427,6 +499,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_math_fork: + dependency: transitive + description: + name: flutter_math_fork + sha256: "6d5f2f1aa57ae539ffb0a04bb39d2da67af74601d685a161aff7ce5bda5fa407" + url: "https://pub.dev" + source: hosted + version: "0.7.4" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -443,6 +523,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + flutter_svg: + dependency: transitive + description: + name: flutter_svg + sha256: "055de8921be7b8e8b98a233c7a5ef84b3a6fcc32f46f1ebf5b9bb3576d108355" + url: "https://pub.dev" + source: hosted + version: "2.2.2" flutter_test: dependency: transitive description: flutter @@ -453,6 +541,30 @@ packages: description: flutter source: sdk version: "0.0.0" + flyer_chat_image_message: + dependency: "direct main" + description: + name: flyer_chat_image_message + sha256: "258a60de2cd04d108aad5884a817b578d639d8586fb80a270a3a169b96a5dfc6" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + flyer_chat_system_message: + dependency: "direct main" + description: + name: flyer_chat_system_message + sha256: "75f962e25db3325c58ba87c7801ca5664b939bcb9ceaf3d292467eedbf04da07" + url: "https://pub.dev" + source: hosted + version: "2.1.13" + flyer_chat_text_message: + dependency: "direct main" + description: + name: flyer_chat_text_message + sha256: dad7a0c29803233ca55cf8318ed9962c657864dc0a464d8cb76469b9e4da07e7 + url: "https://pub.dev" + source: hosted + version: "2.5.2" freezed: dependency: "direct dev" description: @@ -485,6 +597,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.3" + gpt_markdown: + dependency: transitive + description: + name: gpt_markdown + sha256: "8174983f2ed7d8576d25810913e3afe3f8ffdaa3172c0c823b7cfc289b67f380" + url: "https://pub.dev" + source: hosted + version: "1.1.4" graphs: dependency: transitive description: @@ -517,6 +637,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.3.0" + html: + dependency: transitive + description: + name: html + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.dev" + source: hosted + version: "0.15.6" http: dependency: transitive description: @@ -541,6 +669,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + idb_shim: + dependency: transitive + description: + name: idb_shim + sha256: "071f3b05032fa62e60ca15db9939f8afbaf403b37e67747ac88f858c3e999228" + url: "https://pub.dev" + source: hosted + version: "2.6.7+1" image: dependency: transitive description: @@ -725,6 +861,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" node_preamble: dependency: transitive description: @@ -749,6 +893,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + url: "https://pub.dev" + source: hosted + version: "1.1.0" path_provider: dependency: "direct main" description: @@ -885,6 +1037,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.3" + provider: + dependency: transitive + description: + name: provider + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" + url: "https://pub.dev" + source: hosted + version: "6.1.5+1" pub_semver: dependency: transitive description: @@ -901,6 +1061,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.0" + punycode: + dependency: transitive + description: + name: punycode + sha256: "39b874cc1f78b94e57db17e74b3f2ba2a96e25c0bebdcc8a571614dccda0ff0c" + url: "https://pub.dev" + source: hosted + version: "1.0.0" riverpod: dependency: transitive description: @@ -973,6 +1141,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.0" + scrollview_observer: + dependency: transitive + description: + name: scrollview_observer + sha256: c2f713509f18f88f637b2084b47a90c91fb1ef066d5d82d2cf3194d8509dc6ab + url: "https://pub.dev" + source: hosted + version: "1.26.2" + sembast: + dependency: transitive + description: + name: sembast + sha256: c8063c3146c3c8d5f5b04230de7682c768440a575fbda2634f14d22f263197c3 + url: "https://pub.dev" + source: hosted + version: "3.8.5+2" shared_preferences: dependency: "direct main" description: @@ -1146,6 +1330,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 + url: "https://pub.dev" + source: hosted + version: "3.4.0" term_glyph: dependency: transitive description: @@ -1178,6 +1370,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.11" + thumbhash: + dependency: transitive + description: + name: thumbhash + sha256: "5f6d31c5279ca0b5caa81ec10aae8dcaab098d82cb699ea66ada4ed09c794a37" + url: "https://pub.dev" + source: hosted + version: "0.1.0+1" timing: dependency: transitive description: @@ -1186,6 +1386,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" + tuple: + dependency: transitive + description: + name: tuple + sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 + url: "https://pub.dev" + source: hosted + version: "2.0.2" typed_data: dependency: transitive description: @@ -1266,6 +1474,30 @@ packages: url: "https://pub.dev" source: hosted version: "4.5.2" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 + url: "https://pub.dev" + source: hosted + version: "1.1.19" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" + url: "https://pub.dev" + source: hosted + version: "1.1.13" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc + url: "https://pub.dev" + source: hosted + version: "1.1.19" vector_math: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index dc83fbd..ee4c289 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,7 +39,13 @@ dependencies: git: url: https://github.com/google/flutter-desktop-embedding path: plugins/window_size - + flutter_chat_core: ^2.0.0 + flutter_chat_ui: ^2.0.0 + flyer_chat_image_message: ^2.0.0 + flyer_chat_system_message: ^2.0.0 + flutter_link_previewer: ^4.0.0 + flyer_chat_text_message: ^2.0.0 + dev_dependencies: build_runner: ^2.4.11 custom_lint: ^0.8.0