♻️ Migrated to riverpod v3

This commit is contained in:
2025-12-06 13:00:30 +08:00
parent fd79c11d18
commit 9d03faf594
158 changed files with 6834 additions and 10357 deletions

View File

@@ -22,8 +22,8 @@ class CallScreen extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final ongoingCall = ref.watch(ongoingCallProvider(room.id));
final callState = ref.watch(callNotifierProvider);
final callNotifier = ref.watch(callNotifierProvider.notifier);
final callState = ref.watch(callProvider);
final callNotifier = ref.watch(callProvider.notifier);
useEffect(() {
talker.info('[Call] Joining the call...');

View File

@@ -191,7 +191,7 @@ class ChatListBodyWidget extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final chats = ref.watch(chatRoomJoinedNotifierProvider);
final chats = ref.watch(chatRoomJoinedProvider);
Widget bodyWidget = Column(
children: [
@@ -214,7 +214,7 @@ class ChatListBodyWidget extends HookConsumerWidget {
(items) => RefreshIndicator(
onRefresh:
() => Future.sync(() {
ref.invalidate(chatRoomJoinedNotifierProvider);
ref.invalidate(chatRoomJoinedProvider);
}),
child: SuperListView.builder(
padding: EdgeInsets.only(bottom: 96),
@@ -264,7 +264,7 @@ class ChatListBodyWidget extends HookConsumerWidget {
(error, stack) => ResponseErrorWidget(
error: error,
onRetry: () {
ref.invalidate(chatRoomJoinedNotifierProvider);
ref.invalidate(chatRoomJoinedProvider);
},
),
),
@@ -341,7 +341,7 @@ class ChatListScreen extends HookConsumerWidget {
// Listen for chat rooms refresh events
final subscription = eventBus.on<ChatRoomsRefreshEvent>().listen((event) {
ref.invalidate(chatRoomJoinedNotifierProvider);
ref.invalidate(chatRoomJoinedProvider);
});
return () {
@@ -353,13 +353,14 @@ class ChatListScreen extends HookConsumerWidget {
// Set FAB type to chat
final fabMenuNotifier = ref.read(fabMenuTypeProvider.notifier);
Future(() {
fabMenuNotifier.state = FabMenuType.chat;
fabMenuNotifier.setMenuType(FabMenuType.chat);
});
return () {
// Clean up: reset FAB type to main
final fabMenu = ref.read(fabMenuTypeProvider);
WidgetsBinding.instance.addPostFrameCallback((_) {
if (fabMenuNotifier.state == FabMenuType.chat) {
fabMenuNotifier.state = FabMenuType.main;
if (fabMenu == FabMenuType.chat) {
fabMenuNotifier.setMenuType(FabMenuType.main);
}
});
};
@@ -521,7 +522,7 @@ class _ChatInvitesSheet extends HookConsumerWidget {
final client = ref.read(apiClientProvider);
await client.post('/sphere/chat/invites/${invite.chatRoom!.id}/accept');
ref.invalidate(chatroomInvitesProvider);
ref.invalidate(chatRoomJoinedNotifierProvider);
ref.invalidate(chatRoomJoinedProvider);
} catch (err) {
showErrorAlert(err);
}

View File

@@ -47,7 +47,7 @@ class EditChatScreen extends HookConsumerWidget {
final isPublic = useState(true);
final isCommunity = useState(false);
final chat = ref.watch(ChatRoomNotifierProvider(id));
final chat = ref.watch(chatRoomProvider(id));
final joinedRealms = ref.watch(realmsJoinedProvider);
final currentRealm = useState<SnRealm?>(null);

View File

@@ -27,8 +27,8 @@ class PublicRoomPreview extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final messages = ref.watch(messagesNotifierProvider(id));
final messagesNotifier = ref.read(messagesNotifierProvider(id).notifier);
final messages = ref.watch(messagesProvider(id));
final messagesNotifier = ref.read(messagesProvider(id).notifier);
final scrollController = useScrollController();
final listController = useMemoized(() => ListController(), []);
@@ -203,7 +203,7 @@ class PublicRoomPreview extends HookConsumerWidget {
showLoadingModal(context);
final apiClient = ref.read(apiClientProvider);
await apiClient.post('/sphere/chat/${room.id}/members/me');
ref.invalidate(ChatRoomIdentityNotifierProvider(id));
ref.invalidate(chatRoomIdentityProvider(id));
} catch (err) {
showErrorAlert(err);
} finally {

View File

@@ -48,11 +48,11 @@ class ChatRoomScreen extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final chatRoom = ref.watch(ChatRoomNotifierProvider(id));
final chatIdentity = ref.watch(ChatRoomIdentityNotifierProvider(id));
final isSyncing = ref.watch(isSyncingProvider);
final onlineCount = ref.watch(chatOnlineCountNotifierProvider(id));
final settings = ref.watch(appSettingsNotifierProvider);
final chatRoom = ref.watch(chatRoomProvider(id));
final chatIdentity = ref.watch(chatRoomIdentityProvider(id));
final isSyncing = ref.watch(chatSyncingProvider);
final onlineCount = ref.watch(chatOnlineCountProvider(id));
final settings = ref.watch(appSettingsProvider);
if (chatIdentity.isLoading || chatRoom.isLoading) {
return AppScaffold(
@@ -100,9 +100,7 @@ class ChatRoomScreen extends HookConsumerWidget {
await apiClient.post(
'/sphere/chat/${room.id}/members/me',
);
ref.invalidate(
ChatRoomIdentityNotifierProvider(id),
);
ref.invalidate(chatRoomIdentityProvider(id));
} catch (err) {
showErrorAlert(err);
} finally {
@@ -131,17 +129,15 @@ class ChatRoomScreen extends HookConsumerWidget {
appBar: AppBar(leading: const PageBackButton()),
body: ResponseErrorWidget(
error: error,
onRetry: () => ref.refresh(ChatRoomNotifierProvider(id)),
onRetry: () => ref.refresh(chatRoomProvider(id)),
),
),
);
}
final messages = ref.watch(messagesNotifierProvider(id));
final messagesNotifier = ref.read(messagesNotifierProvider(id).notifier);
final chatSubscribeNotifier = ref.read(
chatSubscribeNotifierProvider(id).notifier,
);
final messages = ref.watch(messagesProvider(id));
final messagesNotifier = ref.read(messagesProvider(id).notifier);
final chatSubscribeNotifier = ref.read(chatSubscribeProvider(id).notifier);
final messageController = useTextEditingController();
final scrollController = useScrollController();
@@ -384,7 +380,7 @@ class ChatRoomScreen extends HookConsumerWidget {
// Convert selected message IDs to message data
final selectedMessageData =
messages.valueOrNull
messages.value
?.where((msg) => selectedMessages.value.contains(msg.id))
.map(
(msg) => {
@@ -773,8 +769,7 @@ class ChatRoomScreen extends HookConsumerWidget {
'chatDetail',
pathParameters: {'id': id},
);
if (result is SearchMessagesResult &&
messages.valueOrNull != null) {
if (result is SearchMessagesResult && messages.value != null) {
final messageId = result.messageId;
// Jump to the message and trigger flash effect

View File

@@ -1,8 +1,6 @@
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -40,8 +38,8 @@ class ChatDetailScreen extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final roomState = ref.watch(ChatRoomNotifierProvider(id));
final roomIdentity = ref.watch(ChatRoomIdentityNotifierProvider(id));
final roomState = ref.watch(chatRoomProvider(id));
final roomIdentity = ref.watch(chatRoomIdentityProvider(id));
final totalMessages = ref.watch(totalMessagesCountProvider(id));
const kNotifyLevelText = [
@@ -57,7 +55,7 @@ class ChatDetailScreen extends HookConsumerWidget {
'/sphere/chat/$id/members/me/notify',
data: {'notify_level': level},
);
ref.invalidate(ChatRoomIdentityNotifierProvider(id));
ref.invalidate(chatRoomIdentityProvider(id));
if (context.mounted) {
showSnackBar(
'chatNotifyLevelUpdated'.tr(args: [kNotifyLevelText[level].tr()]),
@@ -75,7 +73,7 @@ class ChatDetailScreen extends HookConsumerWidget {
'/sphere/chat/$id/members/me/notify',
data: {'break_until': until.toUtc().toIso8601String()},
);
ref.invalidate(ChatRoomNotifierProvider(id));
ref.invalidate(chatRoomIdentityProvider(id));
} catch (err) {
showErrorAlert(err);
}
@@ -440,8 +438,8 @@ class _ChatRoomActionMenu extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final chatIdentity = ref.watch(ChatRoomIdentityNotifierProvider(id));
final chatRoom = ref.watch(ChatRoomNotifierProvider(id));
final chatIdentity = ref.watch(chatRoomIdentityProvider(id));
final chatRoom = ref.watch(chatRoomProvider(id));
final isManagable =
chatIdentity.value?.accountId == chatRoom.value?.accountId ||
@@ -462,7 +460,7 @@ class _ChatRoomActionMenu extends HookConsumerWidget {
).then((value) {
if (value != null) {
// Invalidate to refresh room data after edit
ref.invalidate(ChatRoomNotifierProvider(id));
ref.invalidate(chatMemberListProvider(id));
}
});
},
@@ -498,7 +496,7 @@ class _ChatRoomActionMenu extends HookConsumerWidget {
if (confirm) {
final client = ref.watch(apiClientProvider);
await client.delete('/sphere/chat/$id');
ref.invalidate(chatRoomJoinedNotifierProvider);
ref.invalidate(chatRoomJoinedProvider);
if (context.mounted) {
context.pop();
}
@@ -531,7 +529,7 @@ class _ChatRoomActionMenu extends HookConsumerWidget {
if (confirm) {
final client = ref.watch(apiClientProvider);
await client.delete('/sphere/chat/$id/members/me');
ref.invalidate(chatRoomJoinedNotifierProvider);
ref.invalidate(chatRoomJoinedProvider);
if (context.mounted) {
context.pop();
}
@@ -554,63 +552,17 @@ sealed class ChatRoomMemberState with _$ChatRoomMemberState {
}) = _ChatRoomMemberState;
}
final chatMemberStateProvider = StateNotifierProvider.family<
ChatMemberNotifier,
ChatRoomMemberState,
String
>((ref, roomId) {
final apiClient = ref.watch(apiClientProvider);
return ChatMemberNotifier(apiClient, roomId);
});
final chatMemberListProvider = AsyncNotifierProvider.autoDispose.family(
ChatMemberListNotifier.new,
);
class ChatMemberNotifier extends StateNotifier<ChatRoomMemberState> {
final String roomId;
final Dio _apiClient;
ChatMemberNotifier(this._apiClient, this.roomId)
: super(const ChatRoomMemberState(members: [], isLoading: false, total: 0));
Future<void> loadMore({int offset = 0, int take = 20}) async {
if (state.isLoading) return;
if (state.total > 0 && state.members.length >= state.total) return;
state = state.copyWith(isLoading: true, error: null);
try {
final response = await _apiClient.get(
'/sphere/chat/$roomId/members',
queryParameters: {'offset': offset, 'take': take},
);
final total = int.parse(response.headers.value('X-Total') ?? '0');
final List<dynamic> data = response.data;
final members = data.map((e) => SnChatMember.fromJson(e)).toList();
state = state.copyWith(
members: [...state.members, ...members],
total: total,
isLoading: false,
);
} catch (e) {
state = state.copyWith(error: e.toString(), isLoading: false);
}
}
void reset() {
state = const ChatRoomMemberState(members: [], isLoading: false, total: 0);
}
}
final chatMemberListNotifierProvider = AsyncNotifierProvider.autoDispose
.family<ChatMemberListNotifier, List<SnChatMember>, String>(
ChatMemberListNotifier.new,
);
class ChatMemberListNotifier
extends AutoDisposeFamilyAsyncNotifier<List<SnChatMember>, String>
with FamilyAsyncPaginationController<SnChatMember, String> {
class ChatMemberListNotifier extends AsyncNotifier<List<SnChatMember>>
with AsyncPaginationController<SnChatMember> {
static const pageSize = 20;
final String arg;
ChatMemberListNotifier(this.arg);
@override
Future<List<SnChatMember>> fetch() async {
final apiClient = ref.watch(apiClientProvider);
@@ -640,26 +592,15 @@ class _ChatMemberListSheet extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final memberListProvider = chatMemberListNotifierProvider(roomId);
final memberNotifier = ref.read(chatMemberListProvider(roomId).notifier);
// For backward compatibility and to show total count in the header
final memberState = ref.watch(chatMemberStateProvider(roomId));
final memberNotifier = ref.read(chatMemberStateProvider(roomId).notifier);
final roomIdentity = ref.watch(ChatRoomIdentityNotifierProvider(roomId));
final chatRoom = ref.watch(ChatRoomNotifierProvider(roomId));
final roomIdentity = ref.watch(chatRoomIdentityProvider(roomId));
final chatRoom = ref.watch(chatRoomProvider(roomId));
final isManagable =
chatRoom.value?.accountId == roomIdentity.value?.accountId ||
chatRoom.value?.type == 1;
useEffect(() {
Future(() {
memberNotifier.loadMore();
});
return null;
}, []);
Future<void> invitePerson() async {
final result = await showModalBottomSheet(
context: context,
@@ -674,10 +615,7 @@ class _ChatMemberListSheet extends HookConsumerWidget {
'/sphere/chat/invites/$roomId',
data: {'related_user_id': result.id, 'role': 0},
);
// Refresh both providers
memberNotifier.reset();
await memberNotifier.loadMore();
ref.invalidate(memberListProvider);
memberNotifier.refresh();
} catch (err) {
showErrorAlert(err);
}
@@ -694,7 +632,7 @@ class _ChatMemberListSheet extends HookConsumerWidget {
child: Row(
children: [
Text(
'members'.plural(memberState.total),
'members'.plural(memberNotifier.totalCount ?? 0),
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.w600,
letterSpacing: -0.5,
@@ -709,10 +647,7 @@ class _ChatMemberListSheet extends HookConsumerWidget {
IconButton(
icon: const Icon(Symbols.refresh),
onPressed: () {
// Refresh both providers
memberNotifier.reset();
memberNotifier.loadMore();
ref.invalidate(memberListProvider);
memberNotifier.refresh();
},
),
IconButton(
@@ -726,8 +661,8 @@ class _ChatMemberListSheet extends HookConsumerWidget {
const Divider(height: 1),
Expanded(
child: PaginationList(
provider: memberListProvider,
notifier: memberListProvider.notifier,
provider: chatMemberListProvider(roomId),
notifier: chatMemberListProvider(roomId).notifier,
itemBuilder: (context, idx, member) {
return ListTile(
contentPadding: EdgeInsets.only(left: 16, right: 12),
@@ -770,9 +705,7 @@ class _ChatMemberListSheet extends HookConsumerWidget {
'/sphere/chat/$roomId/members/${member.accountId}',
);
// Refresh both providers
memberNotifier.reset();
memberNotifier.loadMore();
ref.invalidate(memberListProvider);
memberNotifier.refresh();
} catch (err) {
showErrorAlert(err);
}

View File

@@ -6,148 +6,75 @@ part of 'room_detail.dart';
// RiverpodGenerator
// **************************************************************************
String _$totalMessagesCountHash() =>
r'd55f1507aba2acdce5e468c1c2e15dba7640c571';
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
/// See also [totalMessagesCount].
@ProviderFor(totalMessagesCount)
const totalMessagesCountProvider = TotalMessagesCountFamily();
const totalMessagesCountProvider = TotalMessagesCountFamily._();
/// See also [totalMessagesCount].
class TotalMessagesCountFamily extends Family<AsyncValue<int>> {
/// See also [totalMessagesCount].
const TotalMessagesCountFamily();
final class TotalMessagesCountProvider
extends $FunctionalProvider<AsyncValue<int>, int, FutureOr<int>>
with $FutureModifier<int>, $FutureProvider<int> {
const TotalMessagesCountProvider._({
required TotalMessagesCountFamily super.from,
required String super.argument,
}) : super(
retry: null,
name: r'totalMessagesCountProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
/// See also [totalMessagesCount].
TotalMessagesCountProvider call(String roomId) {
return TotalMessagesCountProvider(roomId);
@override
String debugGetCreateSourceHash() => _$totalMessagesCountHash();
@override
String toString() {
return r'totalMessagesCountProvider'
''
'($argument)';
}
@$internal
@override
TotalMessagesCountProvider getProviderOverride(
covariant TotalMessagesCountProvider provider,
) {
return call(provider.roomId);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
$FutureProviderElement<int> $createElement($ProviderPointer pointer) =>
$FutureProviderElement(pointer);
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'totalMessagesCountProvider';
}
/// See also [totalMessagesCount].
class TotalMessagesCountProvider extends AutoDisposeFutureProvider<int> {
/// See also [totalMessagesCount].
TotalMessagesCountProvider(String roomId)
: this._internal(
(ref) => totalMessagesCount(ref as TotalMessagesCountRef, roomId),
from: totalMessagesCountProvider,
name: r'totalMessagesCountProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$totalMessagesCountHash,
dependencies: TotalMessagesCountFamily._dependencies,
allTransitiveDependencies:
TotalMessagesCountFamily._allTransitiveDependencies,
roomId: roomId,
);
TotalMessagesCountProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.roomId,
}) : super.internal();
final String roomId;
@override
Override overrideWith(
FutureOr<int> Function(TotalMessagesCountRef provider) create,
) {
return ProviderOverride(
origin: this,
override: TotalMessagesCountProvider._internal(
(ref) => create(ref as TotalMessagesCountRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
roomId: roomId,
),
);
}
@override
AutoDisposeFutureProviderElement<int> createElement() {
return _TotalMessagesCountProviderElement(this);
FutureOr<int> create(Ref ref) {
final argument = this.argument as String;
return totalMessagesCount(ref, argument);
}
@override
bool operator ==(Object other) {
return other is TotalMessagesCountProvider && other.roomId == roomId;
return other is TotalMessagesCountProvider && other.argument == argument;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, roomId.hashCode);
return _SystemHash.finish(hash);
return argument.hashCode;
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin TotalMessagesCountRef on AutoDisposeFutureProviderRef<int> {
/// The parameter `roomId` of this provider.
String get roomId;
}
String _$totalMessagesCountHash() =>
r'd55f1507aba2acdce5e468c1c2e15dba7640c571';
class _TotalMessagesCountProviderElement
extends AutoDisposeFutureProviderElement<int>
with TotalMessagesCountRef {
_TotalMessagesCountProviderElement(super.provider);
final class TotalMessagesCountFamily extends $Family
with $FunctionalFamilyOverride<FutureOr<int>, String> {
const TotalMessagesCountFamily._()
: super(
retry: null,
name: r'totalMessagesCountProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
TotalMessagesCountProvider call(String roomId) =>
TotalMessagesCountProvider._(argument: roomId, from: this);
@override
String get roomId => (origin as TotalMessagesCountProvider).roomId;
String toString() => r'totalMessagesCountProvider';
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@@ -124,9 +124,7 @@ class SearchMessagesScreen extends HookConsumerWidget {
// Debounce timer for search optimization
final debounceTimer = useRef<Timer?>(null);
final messagesNotifier = ref.read(
messagesNotifierProvider(roomId).notifier,
);
final messagesNotifier = ref.read(messagesProvider(roomId).notifier);
// Optimized search function with debouncing
void performSearch(String query) async {
@@ -180,7 +178,7 @@ class SearchMessagesScreen extends HookConsumerWidget {
useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((_) {
// Clear flashing messages when entering search screen
ref.read(flashingMessagesProvider.notifier).state = {};
ref.read(flashingMessagesProvider.notifier).clear();
});
return null;
}, []);

View File

@@ -1,14 +1,28 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:island/database/message.dart';
import 'package:island/models/chat.dart';
import 'package:island/widgets/chat/message_item.dart';
// Provider to track animated messages to prevent replay
final animatedMessagesProvider = StateProvider<Set<String>>((ref) => {});
final animatedMessagesProvider =
NotifierProvider<AnimatedMessagesNotifier, Set<String>>(
AnimatedMessagesNotifier.new,
);
class MessageItemWrapper extends HookConsumerWidget {
class AnimatedMessagesNotifier extends Notifier<Set<String>> {
@override
Set<String> build() {
return {};
}
void addMessage(String messageId) {
state = {...state, messageId};
}
}
class MessageItemWrapper extends ConsumerWidget {
final LocalChatMessage message;
final int index;
final bool isLastInGroup;
@@ -78,9 +92,7 @@ class MessageItemWrapper extends HookConsumerWidget {
onEnd: () {
// Mark as animated
WidgetsBinding.instance.addPostFrameCallback((_) {
ref
.read(animatedMessagesProvider.notifier)
.update((state) => {...state, message.id});
ref.read(animatedMessagesProvider.notifier).addMessage(message.id);
});
},
child: child,