From 78516abf2ee4ec037852b99a746d574d49b540c4 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 23 Feb 2025 01:49:07 +0800 Subject: [PATCH] :sparkles: Chat unread count --- lib/providers/channel.dart | 73 +++++++++++++++----------------------- lib/screens/chat.dart | 51 +++++++++++++++++++++----- 2 files changed, 70 insertions(+), 54 deletions(-) diff --git a/lib/providers/channel.dart b/lib/providers/channel.dart index ddca9ad..5de6e6a 100644 --- a/lib/providers/channel.dart +++ b/lib/providers/channel.dart @@ -6,9 +6,9 @@ import 'package:provider/provider.dart'; import 'package:surface/database/database.dart'; import 'package:surface/providers/database.dart'; import 'package:surface/providers/sn_network.dart'; +import 'package:surface/providers/sn_realm.dart'; import 'package:surface/providers/user_directory.dart'; import 'package:surface/types/chat.dart'; -import 'package:surface/types/realm.dart'; class ChatChannelProvider extends ChangeNotifier { static const kChatChannelBoxName = 'nex_chat_channels'; @@ -16,11 +16,13 @@ class ChatChannelProvider extends ChangeNotifier { late final SnNetworkProvider _sn; late final UserDirectoryProvider _ud; late final DatabaseProvider _dt; + late final SnRealmProvider _rels; ChatChannelProvider(BuildContext context) { _sn = context.read(); _ud = context.read(); _dt = context.read(); + _rels = context.read(); } Future _saveChannelToLocal(Iterable channels) async { @@ -44,16 +46,9 @@ class ChatChannelProvider extends ChangeNotifier { } Future> _fetchChannelsFromServer({ - String scope = 'global', - bool direct = false, bool doNotSave = false, }) async { - final resp = await _sn.client.get( - '/cgi/im/channels/$scope/me/available', - queryParameters: { - 'direct': direct, - }, - ); + final resp = await _sn.client.get('/cgi/im/channels/me/available'); final out = List.from( resp.data?.map((e) => SnChannel.fromJson(e)) ?? [], ); @@ -68,7 +63,10 @@ class ChatChannelProvider extends ChangeNotifier { final local = await (_dt.db.snLocalChatChannel.select() ..where((e) => e.alias.equals(key))) .getSingleOrNull(); - if (local != null) return local.content; + if (local != null) { + final out = local.content; + return out.copyWith(realm: await _rels.getRealm(out.realmId!)); + } var resp = await _sn.client.get('/cgi/im/channels/${key.replaceAll(':', '/')}'); @@ -76,8 +74,7 @@ class ChatChannelProvider extends ChangeNotifier { // Preload realm of the channel if (out.realmId != null) { - resp = await _sn.client.get('/cgi/id/realms/${out.realmId}'); - out = out.copyWith(realm: SnRealm.fromJson(resp.data)); + out = out.copyWith(realm: await _rels.getRealm(out.realmId!)); } _saveChannelToLocal([out]); @@ -98,44 +95,30 @@ class ChatChannelProvider extends ChangeNotifier { OrderingTerm(expression: e.createdAt, mode: OrderingMode.desc) ])) .get(); - yield local.map((e) => e.content).toList(); + final out = local.map((e) => e.content).toList(); + for (var idx = 0; idx < out.length; idx++) { + final channel = out[idx]; + if (channel.realmId != null) { + out[idx] = out[idx].copyWith( + realm: await _rels.getRealm(channel.realmId!), + ); + } + } + yield out; } if (noRemote) return; - - var resp = await _sn.client.get('/cgi/id/realms/me/available'); - final realms = List.from( - resp.data?.map((e) => SnRealm.fromJson(e)) ?? [], - ); - final realmMap = { - for (final realm in realms) realm.alias: realm, - }; - - final scopeToFetch = {'global', ...realms.map((e) => e.alias)}; - final List result = List.empty(growable: true); - final directMessages = await _fetchChannelsFromServer( - scope: scopeToFetch.first, - direct: true, - ); - result.addAll(directMessages); - - final nonBelongsChannels = await _fetchChannelsFromServer( - scope: scopeToFetch.first, - direct: false, - ); - result.addAll(nonBelongsChannels); - - for (final scope in scopeToFetch.skip(1)) { - final channel = await _fetchChannelsFromServer( - scope: scope, - direct: false, - doNotSave: true, - ); - final out = channel.map((ele) => ele.copyWith(realm: realmMap[scope])); - _saveChannelToLocal(out); - result.addAll(out); + final channels = await _fetchChannelsFromServer(); + for (var idx = 0; idx < channels.length; idx++) { + final channel = channels[idx]; + if (channel.realmId != null) { + channels[idx] = channels[idx].copyWith( + realm: await _rels.getRealm(channel.realmId!), + ); + } } + result.addAll(channels); yield result; } diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart index 6af1c5b..15e0a9e 100644 --- a/lib/screens/chat.dart +++ b/lib/screens/chat.dart @@ -33,6 +33,16 @@ class _ChatScreenState extends State { List? _channels; Map? _lastMessages; + Map? _unreadCounts; + + Future _fetchWhatsNew() async { + final sn = context.read(); + final resp = await sn.client.get('/cgi/im/whats-new'); + final List out = resp.data; + setState(() { + _unreadCounts = {for (var v in out) v['channel_id']: v['count']}; + }); + } void _refreshChannels({bool noRemote = false}) { final ua = context.read(); @@ -117,6 +127,7 @@ class _ChatScreenState extends State { void initState() { super.initState(); _refreshChannels(); + _fetchWhatsNew(); } @override @@ -211,7 +222,10 @@ class _ChatScreenState extends State { context: context, removeTop: true, child: RefreshIndicator( - onRefresh: () => Future.sync(() => _refreshChannels()), + onRefresh: () => Future.wait([ + Future.sync(() => _refreshChannels()), + _fetchWhatsNew(), + ]), child: ListView.builder( itemCount: _channels?.length ?? 0, itemBuilder: (context, idx) { @@ -226,10 +240,22 @@ class _ChatScreenState extends State { ); return ListTile( - title: Text(ud - .getAccountFromCache(otherMember?.accountId) - ?.nick ?? - channel.name), + title: Row( + children: [ + Expanded( + child: Text(ud + .getAccountFromCache( + otherMember?.accountId) + ?.nick ?? + channel.name), + ), + const Gap(8), + if (_unreadCounts?[channel.id] != null) + Badge( + label: Text('${_unreadCounts![channel.id]}'), + ), + ], + ), subtitle: lastMessage != null ? Text( '${ud.getAccountFromCache(lastMessage.sender.accountId)?.nick}: ${lastMessage.body['text'] ?? 'Unable preview'}', @@ -237,9 +263,7 @@ class _ChatScreenState extends State { overflow: TextOverflow.ellipsis, ) : Text( - 'channelDirectMessageDescription'.tr(args: [ - '@${ud.getAccountFromCache(otherMember?.accountId)?.name}', - ]), + channel.description, maxLines: 1, overflow: TextOverflow.ellipsis, ), @@ -265,7 +289,16 @@ class _ChatScreenState extends State { } return ListTile( - title: Text(channel.name), + title: Row( + children: [ + Expanded(child: Text(channel.name)), + const Gap(8), + if (_unreadCounts?[channel.id] != null) + Badge( + label: Text('${_unreadCounts![channel.id]}'), + ), + ], + ), subtitle: lastMessage != null ? Text( '${ud.getAccountFromCache(lastMessage.sender.accountId)?.nick}: ${lastMessage.body['text'] ?? 'Unable preview'}',