pretty cool

This commit is contained in:
Henry Hiles 2025-11-10 18:55:31 -05:00
commit 40bad1e06e
No known key found for this signature in database
7 changed files with 539 additions and 53 deletions

View file

@ -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<Client> {
@override
Future<Client> 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, Client>(
ClientController.new,
);
}

View file

@ -0,0 +1,48 @@
import "package:flutter_chat_core/flutter_chat_core.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
class RoomChatController extends Notifier<ChatController> {
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, ChatController, String>(
RoomChatController.new,
);
}

View file

@ -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(),
),
);
},
),
);
}

View file

@ -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),
);
}
}

75
lib/widgets/sidebar.dart Normal file
View file

@ -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,
),
),
),
],
),
);
}
}