Chat rooms in realm detail page

This commit is contained in:
LittleSheep 2025-06-27 17:54:29 +08:00
parent 180fbcc558
commit f511612a53
3 changed files with 240 additions and 59 deletions

View File

@ -1,6 +1,8 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:island/screens/chat/chat.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:island/models/chat.dart';
import 'package:island/services/color.dart'; import 'package:island/services/color.dart';
import 'package:palette_generator/palette_generator.dart'; import 'package:palette_generator/palette_generator.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
@ -44,6 +46,13 @@ Future<SnRealmMember?> realmIdentity(Ref ref, String realmSlug) async {
return SnRealmMember.fromJson(response.data); return SnRealmMember.fromJson(response.data);
} }
@riverpod
Future<List<SnChatRoom>> 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 { class RealmDetailScreen extends HookConsumerWidget {
final String slug; final String slug;
@ -111,30 +120,33 @@ class RealmDetailScreen extends HookConsumerWidget {
], ],
), ),
SliverToBoxAdapter( SliverToBoxAdapter(
child: Column( child: ref
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ref
.watch(realmIdentityProvider(slug)) .watch(realmIdentityProvider(slug))
.when( .when(
loading: () => const SizedBox.shrink(), loading: () => const SizedBox.shrink(),
error: (_, _) => const SizedBox.shrink(), error: (_, _) => const SizedBox.shrink(),
data: data:
(identity) => Column( (identity) => Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
ExpansionTile( ExpansionTile(
title: const Text('description').tr(), title: const Text('description').tr(),
initiallyExpanded: identity == null, initiallyExpanded: identity == null,
tilePadding: EdgeInsets.symmetric(
horizontal: 20,
),
children: [ children: [
Text( Text(
realm.description, realm.description,
style: const TextStyle(fontSize: 16), style: const TextStyle(fontSize: 16),
).padding(horizontal: 16, vertical: 16), ).padding(
horizontal: 16,
bottom: 16,
top: 8,
),
], ],
), ),
const Gap(4), if (identity == null && realm.isPublic)
if (identity != null && realm.isPublic)
FilledButton.tonalIcon( FilledButton.tonalIcon(
onPressed: () async { onPressed: () async {
try { try {
@ -147,23 +159,55 @@ class RealmDetailScreen extends HookConsumerWidget {
ref.invalidate( ref.invalidate(
realmIdentityProvider(slug), realmIdentityProvider(slug),
); );
ref.invalidate( ref.invalidate(realmsJoinedProvider);
realmsJoinedProvider,
);
} catch (err) { } catch (err) {
showErrorAlert(err); showErrorAlert(err);
} }
}, },
icon: const Icon(Symbols.add), icon: const Icon(Symbols.add),
label: const Text('joinRealm').tr(), label: const Text('joinRealm').tr(),
).padding(horizontal: 16) ).padding(horizontal: 16, vertical: 4)
else else
const SizedBox.shrink(), 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),
);
},
);
},
), ),
], ],
), ),

View File

@ -276,6 +276,128 @@ class _RealmIdentityProviderElement
String get realmSlug => (origin as RealmIdentityProvider).realmSlug; 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<AsyncValue<List<SnChatRoom>>> {
/// 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<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'realmChatRoomsProvider';
}
/// See also [realmChatRooms].
class RealmChatRoomsProvider
extends AutoDisposeFutureProvider<List<SnChatRoom>> {
/// 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<List<SnChatRoom>> 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<List<SnChatRoom>> 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<List<SnChatRoom>> {
/// The parameter `realmSlug` of this provider.
String get realmSlug;
}
class _RealmChatRoomsProviderElement
extends AutoDisposeFutureProviderElement<List<SnChatRoom>>
with RealmChatRoomsRef {
_RealmChatRoomsProviderElement(super.provider);
@override
String get realmSlug => (origin as RealmChatRoomsProvider).realmSlug;
}
String _$realmMemberListNotifierHash() => String _$realmMemberListNotifierHash() =>
r'b2e3eefc62a597f45df9470b2058fdda62f8853f'; r'b2e3eefc62a597f45df9470b2058fdda62f8853f';

View File

@ -233,16 +233,27 @@ class MessageItem extends HookConsumerWidget {
if (remoteMessage.meta['embeds'] != null) if (remoteMessage.meta['embeds'] != null)
...((remoteMessage.meta['embeds'] as List<dynamic>) ...((remoteMessage.meta['embeds'] as List<dynamic>)
.where((embed) => embed['Type'] == 'link') .where((embed) => embed['Type'] == 'link')
.map((embed) => SnEmbedLink.fromJson(embed as Map<String, dynamic>)) .map(
.map((link) => LayoutBuilder( (embed) => SnEmbedLink.fromJson(
embed as Map<String, dynamic>,
),
)
.map(
(link) => LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
return EmbedLinkWidget( return EmbedLinkWidget(
link: link, link: link,
maxWidth: math.min(constraints.maxWidth, 480), maxWidth: math.min(
margin: const EdgeInsets.symmetric(vertical: 4), constraints.maxWidth,
480,
),
margin: const EdgeInsets.symmetric(
vertical: 4,
),
); );
}, },
)) ),
)
.toList()), .toList()),
if (progress != null && progress!.isNotEmpty) if (progress != null && progress!.isNotEmpty)
Column( Column(
@ -482,7 +493,11 @@ class _MessageItemContent extends StatelessWidget {
); );
case 'text': case 'text':
default: default:
return MarkdownTextContent(content: item.content!, isSelectable: true); return MarkdownTextContent(
content: item.content!,
isSelectable: true,
linesMargin: EdgeInsets.zero,
);
} }
} }