From f511612a532eaa5a7d74ac7b335180efe3b7dd42 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Fri, 27 Jun 2025 17:54:29 +0800 Subject: [PATCH] :sparkles: Chat rooms in realm detail page --- lib/screens/realm/detail.dart | 140 +++++++++++++++++++---------- lib/screens/realm/detail.g.dart | 122 +++++++++++++++++++++++++ lib/widgets/chat/message_item.dart | 37 +++++--- 3 files changed, 240 insertions(+), 59 deletions(-) diff --git a/lib/screens/realm/detail.dart b/lib/screens/realm/detail.dart index e4cd046..4e918ca 100644 --- a/lib/screens/realm/detail.dart +++ b/lib/screens/realm/detail.dart @@ -1,6 +1,8 @@ import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:island/screens/chat/chat.dart'; import 'package:flutter/material.dart'; +import 'package:island/models/chat.dart'; import 'package:island/services/color.dart'; import 'package:palette_generator/palette_generator.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; @@ -44,6 +46,13 @@ Future realmIdentity(Ref ref, String realmSlug) async { return SnRealmMember.fromJson(response.data); } +@riverpod +Future> realmChatRooms(Ref ref, String realmSlug) async { + final apiClient = ref.watch(apiClientProvider); + final response = await apiClient.get('/realms/$realmSlug/chat'); + return (response.data as List).map((e) => SnChatRoom.fromJson(e)).toList(); +} + class RealmDetailScreen extends HookConsumerWidget { final String slug; @@ -111,59 +120,94 @@ class RealmDetailScreen extends HookConsumerWidget { ], ), SliverToBoxAdapter( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - ref - .watch(realmIdentityProvider(slug)) - .when( - loading: () => const SizedBox.shrink(), - error: (_, _) => const SizedBox.shrink(), - data: - (identity) => Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: ref + .watch(realmIdentityProvider(slug)) + .when( + loading: () => const SizedBox.shrink(), + error: (_, _) => const SizedBox.shrink(), + data: + (identity) => Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ExpansionTile( + title: const Text('description').tr(), + initiallyExpanded: identity == null, + tilePadding: EdgeInsets.symmetric( + horizontal: 20, + ), children: [ - ExpansionTile( - title: const Text('description').tr(), - initiallyExpanded: identity == null, - children: [ - Text( - realm.description, - style: const TextStyle(fontSize: 16), - ).padding(horizontal: 16, vertical: 16), - ], + Text( + realm.description, + style: const TextStyle(fontSize: 16), + ).padding( + horizontal: 16, + bottom: 16, + top: 8, ), - const Gap(4), - if (identity != null && realm.isPublic) - FilledButton.tonalIcon( - onPressed: () async { - try { - final apiClient = ref.read( - apiClientProvider, - ); - await apiClient.post( - '/realms/$slug/members/me', - ); - ref.invalidate( - realmIdentityProvider(slug), - ); - ref.invalidate( - realmsJoinedProvider, - ); - } catch (err) { - showErrorAlert(err); - } - }, - icon: const Icon(Symbols.add), - label: const Text('joinRealm').tr(), - ).padding(horizontal: 16) - else - const SizedBox.shrink(), ], ), + if (identity == null && realm.isPublic) + FilledButton.tonalIcon( + onPressed: () async { + try { + final apiClient = ref.read( + apiClientProvider, + ); + await apiClient.post( + '/realms/$slug/members/me', + ); + ref.invalidate( + realmIdentityProvider(slug), + ); + ref.invalidate(realmsJoinedProvider); + } catch (err) { + showErrorAlert(err); + } + }, + icon: const Icon(Symbols.add), + label: const Text('joinRealm').tr(), + ).padding(horizontal: 16, vertical: 4) + else + const SizedBox.shrink(), + ], + ), + ), + ), + const SliverToBoxAdapter(child: Divider(height: 1)), + Consumer( + builder: (context, ref, _) { + final chatRooms = ref.watch(realmChatRoomsProvider(slug)); + return chatRooms.when( + loading: + () => const SliverToBoxAdapter( + child: Center(child: CircularProgressIndicator()), ), - ], - ), + error: + (error, _) => SliverToBoxAdapter( + child: Center(child: Text('Error: $error')), + ), + data: (rooms) { + if (rooms.isEmpty) { + return const SliverToBoxAdapter( + child: SizedBox.shrink(), + ); + } + return SliverList( + delegate: SliverChildBuilderDelegate(( + context, + index, + ) { + return ChatRoomListTile( + room: rooms[index], + onTap: () { + context.push('/chat/${rooms[index].id}'); + }, + ); + }, childCount: rooms.length), + ); + }, + ); + }, ), ], ), diff --git a/lib/screens/realm/detail.g.dart b/lib/screens/realm/detail.g.dart index fdb0e5e..8ce028a 100644 --- a/lib/screens/realm/detail.g.dart +++ b/lib/screens/realm/detail.g.dart @@ -276,6 +276,128 @@ class _RealmIdentityProviderElement String get realmSlug => (origin as RealmIdentityProvider).realmSlug; } +String _$realmChatRoomsHash() => r'8207c1e6f0922323967f208efeed027e943039cc'; + +/// See also [realmChatRooms]. +@ProviderFor(realmChatRooms) +const realmChatRoomsProvider = RealmChatRoomsFamily(); + +/// See also [realmChatRooms]. +class RealmChatRoomsFamily extends Family>> { + /// See also [realmChatRooms]. + const RealmChatRoomsFamily(); + + /// See also [realmChatRooms]. + RealmChatRoomsProvider call(String realmSlug) { + return RealmChatRoomsProvider(realmSlug); + } + + @override + RealmChatRoomsProvider getProviderOverride( + covariant RealmChatRoomsProvider provider, + ) { + return call(provider.realmSlug); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'realmChatRoomsProvider'; +} + +/// See also [realmChatRooms]. +class RealmChatRoomsProvider + extends AutoDisposeFutureProvider> { + /// See also [realmChatRooms]. + RealmChatRoomsProvider(String realmSlug) + : this._internal( + (ref) => realmChatRooms(ref as RealmChatRoomsRef, realmSlug), + from: realmChatRoomsProvider, + name: r'realmChatRoomsProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$realmChatRoomsHash, + dependencies: RealmChatRoomsFamily._dependencies, + allTransitiveDependencies: + RealmChatRoomsFamily._allTransitiveDependencies, + realmSlug: realmSlug, + ); + + RealmChatRoomsProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.realmSlug, + }) : super.internal(); + + final String realmSlug; + + @override + Override overrideWith( + FutureOr> Function(RealmChatRoomsRef provider) create, + ) { + return ProviderOverride( + origin: this, + override: RealmChatRoomsProvider._internal( + (ref) => create(ref as RealmChatRoomsRef), + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + realmSlug: realmSlug, + ), + ); + } + + @override + AutoDisposeFutureProviderElement> createElement() { + return _RealmChatRoomsProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is RealmChatRoomsProvider && other.realmSlug == realmSlug; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, realmSlug.hashCode); + + return _SystemHash.finish(hash); + } +} + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +mixin RealmChatRoomsRef on AutoDisposeFutureProviderRef> { + /// The parameter `realmSlug` of this provider. + String get realmSlug; +} + +class _RealmChatRoomsProviderElement + extends AutoDisposeFutureProviderElement> + with RealmChatRoomsRef { + _RealmChatRoomsProviderElement(super.provider); + + @override + String get realmSlug => (origin as RealmChatRoomsProvider).realmSlug; +} + String _$realmMemberListNotifierHash() => r'b2e3eefc62a597f45df9470b2058fdda62f8853f'; diff --git a/lib/widgets/chat/message_item.dart b/lib/widgets/chat/message_item.dart index e4da507..10ce7d8 100644 --- a/lib/widgets/chat/message_item.dart +++ b/lib/widgets/chat/message_item.dart @@ -233,16 +233,27 @@ class MessageItem extends HookConsumerWidget { if (remoteMessage.meta['embeds'] != null) ...((remoteMessage.meta['embeds'] as List) .where((embed) => embed['Type'] == 'link') - .map((embed) => SnEmbedLink.fromJson(embed as Map)) - .map((link) => LayoutBuilder( - builder: (context, constraints) { - return EmbedLinkWidget( - link: link, - maxWidth: math.min(constraints.maxWidth, 480), - margin: const EdgeInsets.symmetric(vertical: 4), - ); - }, - )) + .map( + (embed) => SnEmbedLink.fromJson( + embed as Map, + ), + ) + .map( + (link) => LayoutBuilder( + builder: (context, constraints) { + return EmbedLinkWidget( + link: link, + maxWidth: math.min( + constraints.maxWidth, + 480, + ), + margin: const EdgeInsets.symmetric( + vertical: 4, + ), + ); + }, + ), + ) .toList()), if (progress != null && progress!.isNotEmpty) Column( @@ -482,7 +493,11 @@ class _MessageItemContent extends StatelessWidget { ); case 'text': default: - return MarkdownTextContent(content: item.content!, isSelectable: true); + return MarkdownTextContent( + content: item.content!, + isSelectable: true, + linesMargin: EdgeInsets.zero, + ); } }