diff --git a/bonfire/lib/features/forum/views/components/card/card.dart b/bonfire/lib/features/forum/views/components/card/card.dart index 2ed414c7..e2825465 100644 --- a/bonfire/lib/features/forum/views/components/card/card.dart +++ b/bonfire/lib/features/forum/views/components/card/card.dart @@ -4,15 +4,16 @@ import 'package:bonfire/theme/theme.dart'; import 'package:firebridge/firebridge.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; class ThreadCard extends ConsumerStatefulWidget { + final Snowflake threadId; final Snowflake channelId; - final ScrollController scrollController; const ThreadCard({ super.key, + required this.threadId, required this.channelId, - required this.scrollController, }); @override @@ -23,27 +24,26 @@ class _ThreadCardState extends ConsumerState { @override void initState() { super.initState(); - widget.scrollController.addListener(_onScroll); + // widget.scrollController.addListener(_onScroll); } - void _onScroll() { - if (widget.scrollController.position.pixels >= - widget.scrollController.position.maxScrollExtent - 200) { - final forumPosts = - ref.read(forumPostsProvider(widget.channelId).notifier); - if (forumPosts.hasMore) { - forumPosts.loadMore(); - } - } - } + // void _onScroll() { + // if (widget.scrollController.position.pixels >= + // widget.scrollController.position.maxScrollExtent - 200) { + // final forumPosts = ref.read(forumPostsProvider(widget.threadId).notifier); + // if (forumPosts.hasMore) { + // forumPosts.loadMore(); + // } + // } + // } @override Widget build(BuildContext context) { // a bit of a hack, this can probably be done using other thread types so that should be handled PublicThread thread = - ref.watch(threadChannelProvider(widget.channelId)) as PublicThread; + ref.watch(threadChannelProvider(widget.threadId)) as PublicThread; - Message? previewMessage = ref.watch(firstMessageProvider(widget.channelId)); + Message? previewMessage = ref.watch(firstMessageProvider(widget.threadId)); return Container( decoration: BoxDecoration( @@ -51,54 +51,63 @@ class _ThreadCardState extends ConsumerState { borderRadius: BorderRadius.circular(8), ), child: InkWell( + // hoverColor: Theme.of(context).custom.colorTheme.foreground, + splashColor: Colors.white, + borderRadius: BorderRadius.circular(8), onTap: () { - print("tapped forum"); - // Navigator.of(context).pushNamed( - // '/forum/thread', - // arguments: widget.postId, - // ); + print("should navigate"); + print("${widget.channelId}/${thread.id}"); + // print(thread.); + // context.go("/channels/${thread.guildId}/${thread.id}/"); + context.go( + "/channels/${thread.guildId}/${widget.channelId}/threads/${thread.id}/"); }, - borderRadius: BorderRadius.circular(8), - child: Padding( - padding: const EdgeInsets.all(12), - child: Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - thread.name, - style: Theme.of(context).custom.textTheme.titleSmall, - ), - if (previewMessage != null) - ConstrainedBox( - constraints: - const BoxConstraints(maxHeight: 50, minHeight: 0), - child: Text( - previewMessage.content, - style: Theme.of(context).custom.textTheme.bodyText2, - overflow: TextOverflow.ellipsis, - maxLines: 2, - ), - ), - ], - ), + child: Container( + decoration: BoxDecoration( + // color: Theme.of(context).custom.colorTheme.foreground, + // borderRadius: BorderRadius.circular(8), ), - if (previewMessage?.attachments.isNotEmpty == true && - previewMessage!.attachments.first.contentType - ?.split("/")[0] == - "image") - ClipRRect( - borderRadius: BorderRadius.circular(8), - child: Image.network( - previewMessage.attachments.first.url.toString(), - width: 80, - height: 80, - fit: BoxFit.cover, + child: Padding( + padding: const EdgeInsets.all(12), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + thread.name, + style: Theme.of(context).custom.textTheme.titleSmall, + ), + if (previewMessage != null) + ConstrainedBox( + constraints: + const BoxConstraints(maxHeight: 50, minHeight: 0), + child: Text( + previewMessage.content, + style: Theme.of(context).custom.textTheme.bodyText2, + overflow: TextOverflow.ellipsis, + maxLines: 2, + ), + ), + ], ), ), - ], + if (previewMessage?.attachments.isNotEmpty == true && + previewMessage!.attachments.first.contentType + ?.split("/")[0] == + "image") + ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Image.network( + previewMessage.attachments.first.url.toString(), + width: 80, + height: 80, + fit: BoxFit.cover, + ), + ), + ], + ), ), ), ), @@ -107,7 +116,7 @@ class _ThreadCardState extends ConsumerState { @override void dispose() { - widget.scrollController.removeListener(_onScroll); + // widget.scrollController.removeListener(_onScroll); super.dispose(); } } diff --git a/bonfire/lib/features/forum/views/forum.dart b/bonfire/lib/features/forum/views/forum.dart index 529d1da9..9b247935 100644 --- a/bonfire/lib/features/forum/views/forum.dart +++ b/bonfire/lib/features/forum/views/forum.dart @@ -1,6 +1,7 @@ import 'package:bonfire/features/forum/repositories/forum_posts.dart'; import 'package:bonfire/features/forum/repositories/forums.dart'; import 'package:bonfire/features/forum/views/components/card/card.dart'; +import 'package:bonfire/features/messaging/views/components/box/channel_header.dart'; import 'package:firebridge/firebridge.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -66,21 +67,29 @@ class _ForumViewState extends ConsumerState { return (threadList?.threads ?? []) as List; }).toList(); - return ListView.builder( - controller: _scrollController, - itemCount: threads.length, - itemBuilder: (context, index) { - return Padding( - padding: const EdgeInsets.only(bottom: 8.0), - child: ThreadCard( - channelId: threads[index].id, - scrollController: _scrollController, + return Stack( + children: [ + ListView.builder( + controller: _scrollController, + itemCount: threads.length, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: ThreadCard( + threadId: threads[index].id, + channelId: channel.id, + ), + ); + }, + padding: EdgeInsets.only( + top: MediaQuery.of(context).padding.top + 60, + bottom: MediaQuery.of(context).padding.bottom, ), - ); - }, - padding: const EdgeInsets.only( - top: 8, - ), + ), + ChannelHeader( + channelName: channel.name, + ), + ], ); }, loading: () => threads.isEmpty @@ -93,7 +102,7 @@ class _ForumViewState extends ConsumerState { padding: const EdgeInsets.only(bottom: 8.0), child: ThreadCard( channelId: threads[index].id, - scrollController: _scrollController, + threadId: threads[index].id, ), ); }, diff --git a/bonfire/lib/features/me/views/components/messages.dart b/bonfire/lib/features/me/views/components/messages.dart index cd74837c..69b21883 100644 --- a/bonfire/lib/features/me/views/components/messages.dart +++ b/bonfire/lib/features/me/views/components/messages.dart @@ -9,8 +9,13 @@ import 'package:google_fonts/google_fonts.dart'; class MessageView extends ConsumerStatefulWidget { final Snowflake guildId; final Snowflake channelId; - const MessageView( - {super.key, required this.guildId, required this.channelId}); + final Snowflake? threadId; + const MessageView({ + super.key, + required this.guildId, + required this.channelId, + this.threadId, + }); @override ConsumerState createState() => _MessageViewState(); @@ -19,13 +24,21 @@ class MessageView extends ConsumerStatefulWidget { class _MessageViewState extends ConsumerState { @override Widget build(BuildContext context) { + var channelId = widget.channelId; + if (widget.threadId != null) { + channelId = widget.threadId!; + } Channel? channel = - ref.watch(channelControllerProvider(widget.channelId)).valueOrNull; + ref.watch(channelControllerProvider(channelId)).valueOrNull; if (channel is TextChannel) { - return MessageList(guildId: widget.guildId, channelId: widget.channelId); + return MessageList( + guildId: widget.guildId, + channelId: channelId, + threadId: widget.threadId, + ); } else if (channel is ForumChannel) { - return ForumView(guildId: widget.guildId, channelId: widget.channelId); + return ForumView(guildId: widget.guildId, channelId: channelId); } else if (channel == null) { return const Center(child: CircularProgressIndicator()); } diff --git a/bonfire/lib/features/messaging/repositories/messages.dart b/bonfire/lib/features/messaging/repositories/messages.dart index 5257410e..107978aa 100644 --- a/bonfire/lib/features/messaging/repositories/messages.dart +++ b/bonfire/lib/features/messaging/repositories/messages.dart @@ -54,6 +54,7 @@ class Messages extends _$Messages { Future?> getMessages({ Snowflake? before, + Snowflake? around, int? count, bool disableAck = false, }) async { @@ -93,9 +94,9 @@ class Messages extends _$Messages { var messages = await (channel as TextChannel) .messages - .fetchMany(limit: count ?? 50, before: before); + .fetchMany(limit: count ?? 50, before: before, around: around); - if (before == null) { + if (before == null && around == null) { loadedMessages = messages.toList(); } else { loadedMessages.addAll(messages.toList()); @@ -119,7 +120,11 @@ class Messages extends _$Messages { } } - Future> fetchMessages({Message? before, int? limit}) async { + Future> fetchMessages({ + Message? before, + int? limit, + Snowflake? around, + }) async { Channel channel = ref.watch(channelControllerProvider(channelId)).valueOrNull!; List messages = []; @@ -127,6 +132,7 @@ class Messages extends _$Messages { messages.addAll(loadedMessages); messages.addAll(await getMessages( before: before?.id, + around: around, count: limit, ) ?? []); diff --git a/bonfire/lib/features/messaging/views/components/box/channel_header.dart b/bonfire/lib/features/messaging/views/components/box/channel_header.dart index 213df75f..a1547506 100644 --- a/bonfire/lib/features/messaging/views/components/box/channel_header.dart +++ b/bonfire/lib/features/messaging/views/components/box/channel_header.dart @@ -55,7 +55,7 @@ class _ChannelHeaderState extends ConsumerState { style: GoogleFonts.publicSans( fontSize: 16, letterSpacing: 0.4, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, color: Colors.white, ), ), diff --git a/bonfire/lib/features/messaging/views/components/message_list.dart b/bonfire/lib/features/messaging/views/components/message_list.dart index ab930f29..dae7c21c 100644 --- a/bonfire/lib/features/messaging/views/components/message_list.dart +++ b/bonfire/lib/features/messaging/views/components/message_list.dart @@ -17,8 +17,13 @@ import 'package:bonfire/shared/utils/platform.dart'; class MessageList extends ConsumerStatefulWidget { final Snowflake guildId; final Snowflake channelId; - const MessageList( - {super.key, required this.guildId, required this.channelId}); + final Snowflake? threadId; + const MessageList({ + super.key, + required this.guildId, + required this.channelId, + this.threadId, + }); @override ConsumerState createState() => _MessageViewState(); @@ -40,6 +45,7 @@ class _MessageViewState extends ConsumerState @override void initState() { super.initState(); + _scrollController.addListener(_scrollListener); _fadeController = AnimationController( @@ -81,7 +87,11 @@ class _MessageViewState extends ConsumerState lastScrollMessage = lastScrollMessage ?? firstBatchLastMessage!; List? recents = await ref .read(messagesProvider(widget.channelId).notifier) - .fetchMessages(before: lastScrollMessage!, limit: 50); + .fetchMessages( + before: lastScrollMessage!, + limit: 50, + around: widget.threadId, + ); if (recents.isNotEmpty) { if (lastScrollMessage?.id != recents.last.id) { diff --git a/bonfire/lib/features/overview/views/home.dart b/bonfire/lib/features/overview/views/home.dart index ffdc64cc..e3ce0cf7 100644 --- a/bonfire/lib/features/overview/views/home.dart +++ b/bonfire/lib/features/overview/views/home.dart @@ -11,8 +11,13 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; class GuildMessagingOverview extends ConsumerStatefulWidget { final Snowflake guildId; final Snowflake channelId; - const GuildMessagingOverview( - {super.key, required this.guildId, required this.channelId}); + final Snowflake? threadId; + const GuildMessagingOverview({ + super.key, + required this.guildId, + required this.channelId, + this.threadId, + }); @override ConsumerState createState() => _HomeState(); @@ -29,14 +34,17 @@ class _HomeState extends ConsumerState { @override Widget build(BuildContext context) { + print("GuildMessagingOverview.build"); return (shouldUseMobileLayout(context)) ? HomeMobile( guildId: widget.guildId, channelId: widget.channelId, + threadId: widget.threadId, ) : HomeDesktop( guildId: widget.guildId, channelId: widget.channelId, + threadId: widget.threadId, ); } } diff --git a/bonfire/lib/features/overview/views/home_desktop.dart b/bonfire/lib/features/overview/views/home_desktop.dart index b7d8105d..0ed31be6 100644 --- a/bonfire/lib/features/overview/views/home_desktop.dart +++ b/bonfire/lib/features/overview/views/home_desktop.dart @@ -11,8 +11,13 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; class HomeDesktop extends ConsumerStatefulWidget { final Snowflake guildId; final Snowflake channelId; - const HomeDesktop( - {super.key, required this.guildId, required this.channelId}); + final Snowflake? threadId; + const HomeDesktop({ + super.key, + required this.guildId, + required this.channelId, + this.threadId, + }); @override ConsumerState createState() => _HomeState(); @@ -48,6 +53,7 @@ class _HomeState extends ConsumerState { child: MessageView( guildId: widget.guildId, channelId: widget.channelId, + threadId: widget.threadId, ), ), if (isVisible) diff --git a/bonfire/lib/features/overview/views/home_mobile.dart b/bonfire/lib/features/overview/views/home_mobile.dart index b5085f41..4ecdfd35 100644 --- a/bonfire/lib/features/overview/views/home_mobile.dart +++ b/bonfire/lib/features/overview/views/home_mobile.dart @@ -13,7 +13,13 @@ import 'package:bonfire/shared/utils/platform.dart'; class HomeMobile extends ConsumerStatefulWidget { final Snowflake guildId; final Snowflake channelId; - const HomeMobile({super.key, required this.guildId, required this.channelId}); + final Snowflake? threadId; + const HomeMobile({ + super.key, + required this.guildId, + required this.channelId, + this.threadId, + }); @override ConsumerState createState() => _HomeState(); @@ -105,6 +111,7 @@ class _HomeState extends ConsumerState main: MessageView( guildId: widget.guildId, channelId: widget.channelId, + threadId: widget.threadId, ), right: MemberList( guildId: widget.guildId, diff --git a/bonfire/lib/router/controller.dart b/bonfire/lib/router/controller.dart index 8f210eb0..8f17ecec 100644 --- a/bonfire/lib/router/controller.dart +++ b/bonfire/lib/router/controller.dart @@ -84,6 +84,31 @@ final routerController = GoRouter( ), ); }, + routes: [ + GoRoute( + path: 'threads/:threadId', + pageBuilder: (context, state) { + print("threadId: ${state.pathParameters['threadId']}"); + print("full path: ${state.fullPath}"); + final guildId = state.pathParameters['guildId']!; + final channelId = state.pathParameters['channelId']!; + final threadId = state.pathParameters['threadId']!; + var lastLocation = Hive.box("last-location"); + lastLocation.put("guildId", guildId); + lastLocation.put("channelId", channelId); + lastLocation.put("threadId", threadId); + return buildPageWithNoTransition( + context: context, + state: state, + child: GuildMessagingOverview( + guildId: Snowflake.parse(guildId), + channelId: Snowflake.parse(channelId), + threadId: Snowflake.parse(threadId), + ), + ); + }, + ), + ], ), ], ), diff --git a/firebridge b/firebridge index e1619d3f..e7741d15 160000 --- a/firebridge +++ b/firebridge @@ -1 +1 @@ -Subproject commit e1619d3f8a653128a0cef786f74a965c464e6ad6 +Subproject commit e7741d152a6f0fe7b3e39b638b9291ff0cdc4b8e