Chat unread count

This commit is contained in:
LittleSheep 2025-02-23 01:49:07 +08:00
parent 0424f98eb5
commit 78516abf2e
2 changed files with 70 additions and 54 deletions

View File

@ -6,9 +6,9 @@ import 'package:provider/provider.dart';
import 'package:surface/database/database.dart'; import 'package:surface/database/database.dart';
import 'package:surface/providers/database.dart'; import 'package:surface/providers/database.dart';
import 'package:surface/providers/sn_network.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/providers/user_directory.dart';
import 'package:surface/types/chat.dart'; import 'package:surface/types/chat.dart';
import 'package:surface/types/realm.dart';
class ChatChannelProvider extends ChangeNotifier { class ChatChannelProvider extends ChangeNotifier {
static const kChatChannelBoxName = 'nex_chat_channels'; static const kChatChannelBoxName = 'nex_chat_channels';
@ -16,11 +16,13 @@ class ChatChannelProvider extends ChangeNotifier {
late final SnNetworkProvider _sn; late final SnNetworkProvider _sn;
late final UserDirectoryProvider _ud; late final UserDirectoryProvider _ud;
late final DatabaseProvider _dt; late final DatabaseProvider _dt;
late final SnRealmProvider _rels;
ChatChannelProvider(BuildContext context) { ChatChannelProvider(BuildContext context) {
_sn = context.read<SnNetworkProvider>(); _sn = context.read<SnNetworkProvider>();
_ud = context.read<UserDirectoryProvider>(); _ud = context.read<UserDirectoryProvider>();
_dt = context.read<DatabaseProvider>(); _dt = context.read<DatabaseProvider>();
_rels = context.read<SnRealmProvider>();
} }
Future<void> _saveChannelToLocal(Iterable<SnChannel> channels) async { Future<void> _saveChannelToLocal(Iterable<SnChannel> channels) async {
@ -44,16 +46,9 @@ class ChatChannelProvider extends ChangeNotifier {
} }
Future<List<SnChannel>> _fetchChannelsFromServer({ Future<List<SnChannel>> _fetchChannelsFromServer({
String scope = 'global',
bool direct = false,
bool doNotSave = false, bool doNotSave = false,
}) async { }) async {
final resp = await _sn.client.get( final resp = await _sn.client.get('/cgi/im/channels/me/available');
'/cgi/im/channels/$scope/me/available',
queryParameters: {
'direct': direct,
},
);
final out = List<SnChannel>.from( final out = List<SnChannel>.from(
resp.data?.map((e) => SnChannel.fromJson(e)) ?? [], resp.data?.map((e) => SnChannel.fromJson(e)) ?? [],
); );
@ -68,7 +63,10 @@ class ChatChannelProvider extends ChangeNotifier {
final local = await (_dt.db.snLocalChatChannel.select() final local = await (_dt.db.snLocalChatChannel.select()
..where((e) => e.alias.equals(key))) ..where((e) => e.alias.equals(key)))
.getSingleOrNull(); .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 = var resp =
await _sn.client.get('/cgi/im/channels/${key.replaceAll(':', '/')}'); await _sn.client.get('/cgi/im/channels/${key.replaceAll(':', '/')}');
@ -76,8 +74,7 @@ class ChatChannelProvider extends ChangeNotifier {
// Preload realm of the channel // Preload realm of the channel
if (out.realmId != null) { if (out.realmId != null) {
resp = await _sn.client.get('/cgi/id/realms/${out.realmId}'); out = out.copyWith(realm: await _rels.getRealm(out.realmId!));
out = out.copyWith(realm: SnRealm.fromJson(resp.data));
} }
_saveChannelToLocal([out]); _saveChannelToLocal([out]);
@ -98,44 +95,30 @@ class ChatChannelProvider extends ChangeNotifier {
OrderingTerm(expression: e.createdAt, mode: OrderingMode.desc) OrderingTerm(expression: e.createdAt, mode: OrderingMode.desc)
])) ]))
.get(); .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; if (noRemote) return;
var resp = await _sn.client.get('/cgi/id/realms/me/available');
final realms = List<SnRealm>.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<SnChannel> result = List.empty(growable: true); final List<SnChannel> result = List.empty(growable: true);
final directMessages = await _fetchChannelsFromServer( final channels = await _fetchChannelsFromServer();
scope: scopeToFetch.first, for (var idx = 0; idx < channels.length; idx++) {
direct: true, final channel = channels[idx];
); if (channel.realmId != null) {
result.addAll(directMessages); channels[idx] = channels[idx].copyWith(
realm: await _rels.getRealm(channel.realmId!),
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);
} }
result.addAll(channels);
yield result; yield result;
} }

View File

@ -33,6 +33,16 @@ class _ChatScreenState extends State<ChatScreen> {
List<SnChannel>? _channels; List<SnChannel>? _channels;
Map<int, SnChatMessage>? _lastMessages; Map<int, SnChatMessage>? _lastMessages;
Map<int, int>? _unreadCounts;
Future<void> _fetchWhatsNew() async {
final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/im/whats-new');
final List<dynamic> out = resp.data;
setState(() {
_unreadCounts = {for (var v in out) v['channel_id']: v['count']};
});
}
void _refreshChannels({bool noRemote = false}) { void _refreshChannels({bool noRemote = false}) {
final ua = context.read<UserProvider>(); final ua = context.read<UserProvider>();
@ -117,6 +127,7 @@ class _ChatScreenState extends State<ChatScreen> {
void initState() { void initState() {
super.initState(); super.initState();
_refreshChannels(); _refreshChannels();
_fetchWhatsNew();
} }
@override @override
@ -211,7 +222,10 @@ class _ChatScreenState extends State<ChatScreen> {
context: context, context: context,
removeTop: true, removeTop: true,
child: RefreshIndicator( child: RefreshIndicator(
onRefresh: () => Future.sync(() => _refreshChannels()), onRefresh: () => Future.wait([
Future.sync(() => _refreshChannels()),
_fetchWhatsNew(),
]),
child: ListView.builder( child: ListView.builder(
itemCount: _channels?.length ?? 0, itemCount: _channels?.length ?? 0,
itemBuilder: (context, idx) { itemBuilder: (context, idx) {
@ -226,10 +240,22 @@ class _ChatScreenState extends State<ChatScreen> {
); );
return ListTile( return ListTile(
title: Text(ud title: Row(
.getAccountFromCache(otherMember?.accountId) children: [
?.nick ?? Expanded(
channel.name), 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 subtitle: lastMessage != null
? Text( ? Text(
'${ud.getAccountFromCache(lastMessage.sender.accountId)?.nick}: ${lastMessage.body['text'] ?? 'Unable preview'}', '${ud.getAccountFromCache(lastMessage.sender.accountId)?.nick}: ${lastMessage.body['text'] ?? 'Unable preview'}',
@ -237,9 +263,7 @@ class _ChatScreenState extends State<ChatScreen> {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
) )
: Text( : Text(
'channelDirectMessageDescription'.tr(args: [ channel.description,
'@${ud.getAccountFromCache(otherMember?.accountId)?.name}',
]),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@ -265,7 +289,16 @@ class _ChatScreenState extends State<ChatScreen> {
} }
return ListTile( 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 subtitle: lastMessage != null
? Text( ? Text(
'${ud.getAccountFromCache(lastMessage.sender.accountId)?.nick}: ${lastMessage.body['text'] ?? 'Unable preview'}', '${ud.getAccountFromCache(lastMessage.sender.accountId)?.nick}: ${lastMessage.body['text'] ?? 'Unable preview'}',