From 4d92dec45c77ae8ed3aac96d8ff826a776490b08 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Thu, 1 Jan 2026 23:59:21 +0800 Subject: [PATCH] :alien: Changes to API path since sphere no longer handle messages --- ios/Runner/NotifyDelegate.swift | 2 +- .../Services/NetworkService.swift | 14 +- ios/Solian Watch App/Views/ChatViews.swift | 2 +- lib/pods/chat/call.dart | 2 +- lib/pods/chat/chat_online_count.dart | 2 +- lib/pods/chat/chat_room.dart | 14 +- lib/pods/chat/chat_subscribe.dart | 14 +- lib/pods/chat/chat_summary.dart | 4 +- lib/pods/chat/messages_notifier.dart | 16 +- lib/screens/account/profile.dart | 4 +- lib/screens/chat/chat.dart | 8 +- lib/screens/chat/chat_form.dart | 59 +- lib/screens/chat/public_room_preview.dart | 92 ++- lib/screens/chat/room.dart | 555 +++++++++--------- lib/screens/chat/room_detail.dart | 14 +- lib/services/autocomplete_service.dart | 2 +- lib/widgets/chat/call_button.dart | 6 +- lib/widgets/chat/call_overlay.dart | 2 +- lib/widgets/chat/public_room_preview.dart | 92 ++- lib/widgets/share/share_sheet.dart | 452 +++++++------- 20 files changed, 644 insertions(+), 712 deletions(-) diff --git a/ios/Runner/NotifyDelegate.swift b/ios/Runner/NotifyDelegate.swift index 6bdcbc3b..4609608a 100644 --- a/ios/Runner/NotifyDelegate.swift +++ b/ios/Runner/NotifyDelegate.swift @@ -34,7 +34,7 @@ class NotifyDelegate: UIResponder, UNUserNotificationCenterDelegate { } let serverUrl = UserDefaults.standard.getServerUrl() - let url = "\(serverUrl)/sphere/chat/\(metadata["room_id"] ?? "")/messages" + let url = "\(serverUrl)/messager/chat/\(metadata["room_id"] ?? "")/messages" let parameters: [String: Any?] = [ "content": textResponse.userText, diff --git a/ios/Solian Watch App/Services/NetworkService.swift b/ios/Solian Watch App/Services/NetworkService.swift index b2ba11fa..a7e19253 100644 --- a/ios/Solian Watch App/Services/NetworkService.swift +++ b/ios/Solian Watch App/Services/NetworkService.swift @@ -261,7 +261,7 @@ class NetworkService { guard let baseURL = URL(string: serverUrl) else { throw URLError(.badURL) } - let url = baseURL.appendingPathComponent("/sphere/chat") + let url = baseURL.appendingPathComponent("/messager/chat") var request = URLRequest(url: url) request.httpMethod = "GET" @@ -283,7 +283,7 @@ class NetworkService { guard let baseURL = URL(string: serverUrl) else { throw URLError(.badURL) } - let url = baseURL.appendingPathComponent("/sphere/chat/\(identifier)") + let url = baseURL.appendingPathComponent("/messager/chat/\(identifier)") var request = URLRequest(url: url) request.httpMethod = "GET" @@ -308,7 +308,7 @@ class NetworkService { guard let baseURL = URL(string: serverUrl) else { throw URLError(.badURL) } - let url = baseURL.appendingPathComponent("/sphere/chat/invites") + let url = baseURL.appendingPathComponent("/messager/chat/invites") var request = URLRequest(url: url) request.httpMethod = "GET" @@ -330,7 +330,7 @@ class NetworkService { guard let baseURL = URL(string: serverUrl) else { throw URLError(.badURL) } - let url = baseURL.appendingPathComponent("/sphere/chat/invites/\(chatRoomId)/accept") + let url = baseURL.appendingPathComponent("/messager/chat/invites/\(chatRoomId)/accept") var request = URLRequest(url: url) request.httpMethod = "POST" @@ -351,7 +351,7 @@ class NetworkService { guard let baseURL = URL(string: serverUrl) else { throw URLError(.badURL) } - let url = baseURL.appendingPathComponent("/sphere/chat/invites/\(chatRoomId)/decline") + let url = baseURL.appendingPathComponent("/messager/chat/invites/\(chatRoomId)/decline") var request = URLRequest(url: url) request.httpMethod = "POST" @@ -375,9 +375,9 @@ class NetworkService { throw URLError(.badURL) } - // Try a different pattern: /sphere/chat/messages with roomId as query param + // Try a different pattern: /messager/chat/messages with roomId as query param var components = URLComponents( - url: baseURL.appendingPathComponent("/sphere/chat/\(chatRoomId)/messages"), + url: baseURL.appendingPathComponent("/messager/chat/\(chatRoomId)/messages"), resolvingAgainstBaseURL: false )! var queryItems = [ diff --git a/ios/Solian Watch App/Views/ChatViews.swift b/ios/Solian Watch App/Views/ChatViews.swift index a90a5417..46f9d8b9 100644 --- a/ios/Solian Watch App/Views/ChatViews.swift +++ b/ios/Solian Watch App/Views/ChatViews.swift @@ -455,7 +455,7 @@ struct ChatRoomView: View { ] // Create the URL - guard let url = URL(string: "\(serverUrl)/sphere/chat/\(room.id)/messages") else { + guard let url = URL(string: "\(serverUrl)/messager/chat/\(room.id)/messages") else { throw URLError(.badURL) } diff --git a/lib/pods/chat/call.dart b/lib/pods/chat/call.dart index cc67c3ae..09f585d4 100644 --- a/lib/pods/chat/call.dart +++ b/lib/pods/chat/call.dart @@ -245,7 +245,7 @@ class CallNotifier extends _$CallNotifier { final apiClient = ref.read(apiClientProvider); final ongoingCall = await ref.read(ongoingCallProvider(roomId).future); final response = await apiClient.get( - '/sphere/chat/realtime/$roomId/join', + '/messager/chat/realtime/$roomId/join', ); if (response.statusCode == 200 && response.data != null) { final data = response.data; diff --git a/lib/pods/chat/chat_online_count.dart b/lib/pods/chat/chat_online_count.dart index 070ed996..7c7bd974 100644 --- a/lib/pods/chat/chat_online_count.dart +++ b/lib/pods/chat/chat_online_count.dart @@ -15,7 +15,7 @@ class ChatOnlineCountNotifier extends _$ChatOnlineCountNotifier { // Fetch initial online count final response = await apiClient.get( - '/sphere/chat/$chatroomId/members/online', + '/messager/chat/$chatroomId/members/online', ); final initialCount = response.data as int; diff --git a/lib/pods/chat/chat_room.dart b/lib/pods/chat/chat_room.dart index 067819db..96c491da 100644 --- a/lib/pods/chat/chat_room.dart +++ b/lib/pods/chat/chat_room.dart @@ -105,7 +105,7 @@ class ChatRoomJoinedNotifier extends _$ChatRoomJoinedNotifier { Future(() async { try { final client = ref.read(apiClientProvider); - final resp = await client.get('/sphere/chat'); + final resp = await client.get('/messager/chat'); final remoteRooms = resp.data .map((e) => SnChatRoom.fromJson(e)) .cast() @@ -122,7 +122,7 @@ class ChatRoomJoinedNotifier extends _$ChatRoomJoinedNotifier { // Fallback to API final client = ref.watch(apiClientProvider); - final resp = await client.get('/sphere/chat'); + final resp = await client.get('/messager/chat'); final rooms = resp.data .map((e) => SnChatRoom.fromJson(e)) .cast() @@ -306,7 +306,7 @@ class ChatRoomNotifier extends _$ChatRoomNotifier { Future(() async { try { final client = ref.read(apiClientProvider); - final resp = await client.get('/sphere/chat/$identifier'); + final resp = await client.get('/messager/chat/$identifier'); final remoteRoom = SnChatRoom.fromJson(resp.data); // Update state with fresh data directly without saving to DB // DB will be updated by ChatRoomJoinedNotifier's full sync @@ -321,7 +321,7 @@ class ChatRoomNotifier extends _$ChatRoomNotifier { // Fallback to API try { final client = ref.watch(apiClientProvider); - final resp = await client.get('/sphere/chat/$identifier'); + final resp = await client.get('/messager/chat/$identifier'); final room = SnChatRoom.fromJson(resp.data); await db.saveChatRooms([room]); return room; @@ -375,7 +375,7 @@ class ChatRoomIdentityNotifier extends _$ChatRoomIdentityNotifier { try { final client = ref.read(apiClientProvider); final resp = await client.get( - '/sphere/chat/$identifier/members/me', + '/messager/chat/$identifier/members/me', ); final remoteMember = SnChatMember.fromJson(resp.data); await db.saveMember(remoteMember); @@ -396,7 +396,7 @@ class ChatRoomIdentityNotifier extends _$ChatRoomIdentityNotifier { // Fallback to API try { final client = ref.watch(apiClientProvider); - final resp = await client.get('/sphere/chat/$identifier/members/me'); + final resp = await client.get('/messager/chat/$identifier/members/me'); final member = SnChatMember.fromJson(resp.data); await db.saveMember(member); return member; @@ -444,7 +444,7 @@ class ChatRoomIdentityNotifier extends _$ChatRoomIdentityNotifier { @riverpod Future> chatroomInvites(Ref ref) async { final client = ref.watch(apiClientProvider); - final resp = await client.get('/sphere/chat/invites'); + final resp = await client.get('/messager/chat/invites'); return resp.data .map((e) => SnChatMember.fromJson(e)) .cast() diff --git a/lib/pods/chat/chat_subscribe.dart b/lib/pods/chat/chat_subscribe.dart index 7c6a9b88..3aa6574f 100644 --- a/lib/pods/chat/chat_subscribe.dart +++ b/lib/pods/chat/chat_subscribe.dart @@ -61,7 +61,7 @@ class ChatSubscribeNotifier extends _$ChatSubscribeNotifier { WebSocketPacket( type: 'messages.subscribe', data: {'chat_room_id': roomId}, - endpoint: 'sphere', + endpoint: 'messager', ), ), ); @@ -98,7 +98,7 @@ class ChatSubscribeNotifier extends _$ChatSubscribeNotifier { WebSocketPacket( type: 'messages.subscribe', data: {'chat_room_id': roomId}, - endpoint: 'sphere', + endpoint: 'messager', ), ), ); @@ -116,7 +116,7 @@ class ChatSubscribeNotifier extends _$ChatSubscribeNotifier { WebSocketPacket( type: 'messages.unsubscribe', data: {'chat_room_id': roomId}, - endpoint: 'sphere', + endpoint: 'messager', ), ), ); @@ -128,7 +128,7 @@ class ChatSubscribeNotifier extends _$ChatSubscribeNotifier { WebSocketPacket( type: 'messages.subscribe', data: {'chat_room_id': roomId}, - endpoint: 'sphere', + endpoint: 'messager', ), ), ); @@ -143,7 +143,7 @@ class ChatSubscribeNotifier extends _$ChatSubscribeNotifier { WebSocketPacket( type: 'messages.unsubscribe', data: {'chat_room_id': roomId}, - endpoint: 'sphere', + endpoint: 'messager', ), ), ); @@ -207,7 +207,7 @@ class ChatSubscribeNotifier extends _$ChatSubscribeNotifier { WebSocketPacket( type: 'messages.read', data: {'chat_room_id': roomId}, - endpoint: 'sphere', + endpoint: 'messager', ), ), ); @@ -224,7 +224,7 @@ class ChatSubscribeNotifier extends _$ChatSubscribeNotifier { WebSocketPacket( type: 'messages.typing', data: {'chat_room_id': roomId}, - endpoint: 'sphere', + endpoint: 'messager', ), ), ); diff --git a/lib/pods/chat/chat_summary.dart b/lib/pods/chat/chat_summary.dart index b70c0031..d8b9ef79 100644 --- a/lib/pods/chat/chat_summary.dart +++ b/lib/pods/chat/chat_summary.dart @@ -24,7 +24,7 @@ class ChatUnreadCountNotifier extends _$ChatUnreadCountNotifier { try { final client = ref.read(apiClientProvider); - final response = await client.get('/sphere/chat/unread'); + final response = await client.get('/messager/chat/unread'); return (response.data as num).toInt(); } catch (_) { return 0; @@ -65,7 +65,7 @@ class ChatSummary extends _$ChatSummary { @override Future> build() async { final client = ref.watch(apiClientProvider); - final resp = await client.get('/sphere/chat/summary'); + final resp = await client.get('/messager/chat/summary'); final Map data = resp.data; final summaries = data.map( diff --git a/lib/pods/chat/messages_notifier.dart b/lib/pods/chat/messages_notifier.dart index e4be525f..b64cdeb6 100644 --- a/lib/pods/chat/messages_notifier.dart +++ b/lib/pods/chat/messages_notifier.dart @@ -281,7 +281,7 @@ class MessagesNotifier extends _$MessagesNotifier { talker.log('Fetching messages from API, offset $offset, take $take'); if (_totalCount == null) { final response = await _apiClient.get( - '/sphere/chat/$roomId/messages', + '/messager/chat/$roomId/messages', queryParameters: {'offset': 0, 'take': 1}, ); _totalCount = int.parse(response.headers['x-total']?.firstOrNull ?? '0'); @@ -293,7 +293,7 @@ class MessagesNotifier extends _$MessagesNotifier { } final response = await _apiClient.get( - '/sphere/chat/$roomId/messages', + '/messager/chat/$roomId/messages', queryParameters: {'offset': offset, 'take': take}, ); @@ -373,7 +373,7 @@ class MessagesNotifier extends _$MessagesNotifier { do { final resp = await _apiClient.post( - '/sphere/chat/${_room.id}/sync', + '/messager/chat/${_room.id}/sync', data: {'last_sync_timestamp': lastSyncTimestamp}, ); @@ -603,8 +603,8 @@ class MessagesNotifier extends _$MessagesNotifier { final response = await _apiClient.request( editingTo == null - ? '/sphere/chat/$roomId/messages' - : '/sphere/chat/$roomId/messages/${editingTo.id}', + ? '/messager/chat/$roomId/messages' + : '/messager/chat/$roomId/messages/${editingTo.id}', data: { 'content': content, 'attachments_id': cloudAttachments.map((e) => e.id).toList(), @@ -688,7 +688,7 @@ class MessagesNotifier extends _$MessagesNotifier { try { var remoteMessage = message.toRemoteMessage(); final response = await _apiClient.post( - '/sphere/chat/${message.roomId}/messages', + '/messager/chat/${message.roomId}/messages', data: { 'content': remoteMessage.content, 'attachments_id': remoteMessage.attachments, @@ -925,7 +925,7 @@ class MessagesNotifier extends _$MessagesNotifier { } try { - await _apiClient.delete('/sphere/chat/$roomId/messages/$messageId'); + await _apiClient.delete('/messager/chat/$roomId/messages/$messageId'); await receiveMessageDeletion(messageId); } catch (err, stackTrace) { talker.log( @@ -1033,7 +1033,7 @@ class MessagesNotifier extends _$MessagesNotifier { } final response = await _apiClient.get( - '/sphere/chat/$roomId/messages/$messageId', + '/messager/chat/$roomId/messages/$messageId', ); final remoteMessage = SnChatMessage.fromJson(response.data); final message = LocalChatMessage.fromRemoteMessage( diff --git a/lib/screens/account/profile.dart b/lib/screens/account/profile.dart index 3057a2f2..3c070300 100644 --- a/lib/screens/account/profile.dart +++ b/lib/screens/account/profile.dart @@ -678,7 +678,7 @@ Future accountDirectChat(Ref ref, String uname) async { final account = await ref.watch(accountProvider(uname).future); final apiClient = ref.watch(apiClientProvider); try { - final resp = await apiClient.get("/sphere/chat/direct/${account.id}"); + final resp = await apiClient.get("/messager/chat/direct/${account.id}"); return SnChatRoom.fromJson(resp.data); } catch (err) { if (err is DioException && err.response?.statusCode == 404) { @@ -807,7 +807,7 @@ class AccountProfileScreen extends HookConsumerWidget { try { final client = ref.watch(apiClientProvider); final resp = await client.post( - '/sphere/chat/direct', + '/messager/chat/direct', data: {'related_user_id': account.value!.id}, ); final chat = SnChatRoom.fromJson(resp.data); diff --git a/lib/screens/chat/chat.dart b/lib/screens/chat/chat.dart index 93ba8901..fbc71442 100644 --- a/lib/screens/chat/chat.dart +++ b/lib/screens/chat/chat.dart @@ -505,7 +505,7 @@ class ChatListScreen extends HookConsumerWidget { final client = ref.read(apiClientProvider); try { await client.post( - '/sphere/chat/direct', + '/messager/chat/direct', data: {'related_user_id': result.id}, ); eventBus.fire(const ChatRoomsRefreshEvent()); @@ -622,7 +622,9 @@ class _ChatInvitesSheet extends HookConsumerWidget { Future acceptInvite(SnChatMember invite) async { try { final client = ref.read(apiClientProvider); - await client.post('/sphere/chat/invites/${invite.chatRoom!.id}/accept'); + await client.post( + '/messager/chat/invites/${invite.chatRoom!.id}/accept', + ); ref.invalidate(chatroomInvitesProvider); ref.invalidate(chatRoomJoinedProvider); } catch (err) { @@ -634,7 +636,7 @@ class _ChatInvitesSheet extends HookConsumerWidget { try { final client = ref.read(apiClientProvider); await client.post( - '/sphere/chat/invites/${invite.chatRoom!.id}/decline', + '/messager/chat/invites/${invite.chatRoom!.id}/decline', ); ref.invalidate(chatroomInvitesProvider); } catch (err) { diff --git a/lib/screens/chat/chat_form.dart b/lib/screens/chat/chat_form.dart index 7de5604a..d7163895 100644 --- a/lib/screens/chat/chat_form.dart +++ b/lib/screens/chat/chat_form.dart @@ -97,14 +97,10 @@ class EditChatScreen extends HookConsumerWidget { submitting.value = true; try { - final cloudFile = - await FileUploader.createCloudFile( - ref: ref, - fileData: UniversalFile( - data: result, - type: UniversalFileType.image, - ), - ).future; + final cloudFile = await FileUploader.createCloudFile( + ref: ref, + fileData: UniversalFile(data: result, type: UniversalFileType.image), + ).future; if (cloudFile == null) { throw ArgumentError('Failed to upload the file...'); } @@ -129,7 +125,7 @@ class EditChatScreen extends HookConsumerWidget { try { final client = ref.watch(apiClientProvider); final resp = await client.request( - id == null ? '/sphere/chat' : '/sphere/chat/$id', + id == null ? '/messager/chat' : '/messager/chat/$id', data: { 'name': nameController.text, 'description': descriptionController.text, @@ -166,13 +162,12 @@ class EditChatScreen extends HookConsumerWidget { GestureDetector( child: Container( color: Theme.of(context).colorScheme.surfaceContainerHigh, - child: - background.value != null - ? CloudFileWidget( - item: background.value!, - fit: BoxFit.cover, - ) - : const SizedBox.shrink(), + child: background.value != null + ? CloudFileWidget( + item: background.value!, + fit: BoxFit.cover, + ) + : const SizedBox.shrink(), ), onTap: () { setPicture('background'); @@ -208,8 +203,8 @@ class EditChatScreen extends HookConsumerWidget { borderRadius: BorderRadius.circular(12), ), ), - onTapOutside: - (_) => FocusManager.instance.primaryFocus?.unfocus(), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), ), const SizedBox(height: 16), TextFormField( @@ -223,8 +218,8 @@ class EditChatScreen extends HookConsumerWidget { ), minLines: 3, maxLines: null, - onTapOutside: - (_) => FocusManager.instance.primaryFocus?.unfocus(), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), ), const SizedBox(height: 16), DropdownButtonFormField( @@ -241,22 +236,20 @@ class EditChatScreen extends HookConsumerWidget { child: Text('none'.tr()), ), ...joinedRealms.maybeWhen( - data: - (realms) => realms.map( - (realm) => DropdownMenuItem( - value: realm, - child: Text(realm.name), - ), - ), + data: (realms) => realms.map( + (realm) => DropdownMenuItem( + value: realm, + child: Text(realm.name), + ), + ), orElse: () => [], ), ], - onChanged: - joinedRealms.isLoading - ? null - : (SnRealm? value) { - currentRealm.value = value; - }, + onChanged: joinedRealms.isLoading + ? null + : (SnRealm? value) { + currentRealm.value = value; + }, ), const SizedBox(height: 16), Card( diff --git a/lib/screens/chat/public_room_preview.dart b/lib/screens/chat/public_room_preview.dart index 7dbd8a2c..40f17e8c 100644 --- a/lib/screens/chat/public_room_preview.dart +++ b/lib/screens/chat/public_room_preview.dart @@ -65,8 +65,9 @@ class PublicRoomPreview extends HookConsumerWidget { extentEstimation: (_, _) => 40, itemBuilder: (context, index) { final message = messageList[index]; - final nextMessage = - index < messageList.length - 1 ? messageList[index + 1] : null; + final nextMessage = index < messageList.length - 1 + ? messageList[index + 1] + : null; final isLastInGroup = nextMessage == null || nextMessage.senderId != message.senderId || @@ -97,25 +98,23 @@ class PublicRoomPreview extends HookConsumerWidget { SizedBox( height: 26, width: 26, - child: - (room.type == 1 && room.picture?.id == null) - ? SplitAvatarWidget( - filesId: - room.members! - .map((e) => e.account.profile.picture?.id) - .toList(), - ) - : room.picture?.id != null - ? ProfilePictureWidget( - fileId: room.picture?.id, - fallbackIcon: Symbols.chat, - ) - : CircleAvatar( - child: Text( - room.name![0].toUpperCase(), - style: const TextStyle(fontSize: 12), - ), + child: (room.type == 1 && room.picture?.id == null) + ? SplitAvatarWidget( + filesId: room.members! + .map((e) => e.account.profile.picture?.id) + .toList(), + ) + : room.picture?.id != null + ? ProfilePictureWidget( + fileId: room.picture?.id, + fallbackIcon: Symbols.chat, + ) + : CircleAvatar( + child: Text( + room.name![0].toUpperCase(), + style: const TextStyle(fontSize: 12), ), + ), ), Text( (room.type == 1 && room.name == null) @@ -132,25 +131,23 @@ class PublicRoomPreview extends HookConsumerWidget { SizedBox( height: 26, width: 26, - child: - (room.type == 1 && room.picture?.id == null) - ? SplitAvatarWidget( - filesId: - room.members! - .map((e) => e.account.profile.picture?.id) - .toList(), - ) - : room.picture?.id != null - ? ProfilePictureWidget( - fileId: room.picture?.id, - fallbackIcon: Symbols.chat, - ) - : CircleAvatar( - child: Text( - room.name![0].toUpperCase(), - style: const TextStyle(fontSize: 12), - ), + child: (room.type == 1 && room.picture?.id == null) + ? SplitAvatarWidget( + filesId: room.members! + .map((e) => e.account.profile.picture?.id) + .toList(), + ) + : room.picture?.id != null + ? ProfilePictureWidget( + fileId: room.picture?.id, + fallbackIcon: Symbols.chat, + ) + : CircleAvatar( + child: Text( + room.name![0].toUpperCase(), + style: const TextStyle(fontSize: 12), ), + ), ), Text( (room.type == 1 && room.name == null) @@ -181,17 +178,14 @@ class PublicRoomPreview extends HookConsumerWidget { children: [ Expanded( child: messages.when( - data: - (messageList) => - messageList.isEmpty - ? Center(child: Text('No messages yet'.tr())) - : chatMessageListWidget(messageList), + data: (messageList) => messageList.isEmpty + ? Center(child: Text('No messages yet'.tr())) + : chatMessageListWidget(messageList), loading: () => const Center(child: CircularProgressIndicator()), - error: - (error, _) => ResponseErrorWidget( - error: error, - onRetry: () => messagesNotifier.loadInitial(), - ), + error: (error, _) => ResponseErrorWidget( + error: error, + onRetry: () => messagesNotifier.loadInitial(), + ), ), ), // Join button at the bottom for public rooms @@ -202,7 +196,7 @@ class PublicRoomPreview extends HookConsumerWidget { try { showLoadingModal(context); final apiClient = ref.read(apiClientProvider); - await apiClient.post('/sphere/chat/${room.id}/members/me'); + await apiClient.post('/messager/chat/${room.id}/members/me'); ref.invalidate(chatRoomIdentityProvider(id)); } catch (err) { showErrorAlert(err); diff --git a/lib/screens/chat/room.dart b/lib/screens/chat/room.dart index 6d6a332f..243ea783 100644 --- a/lib/screens/chat/room.dart +++ b/lib/screens/chat/room.dart @@ -71,67 +71,64 @@ class ChatRoomScreen extends HookConsumerWidget { return AppScaffold( appBar: AppBar(leading: const PageBackButton()), body: Center( - child: - ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 280), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - room.isCommunity == true - ? Symbols.person_add - : Symbols.person_remove, - size: 36, - fill: 1, - ).padding(bottom: 4), - Text('chatNotJoined').tr(), - if (room.isCommunity != true) - Text( - 'chatUnableJoin', - textAlign: TextAlign.center, - ).tr().bold() - else - FilledButton.tonalIcon( - onPressed: () async { - try { - showLoadingModal(context); - final apiClient = ref.read(apiClientProvider); - await apiClient.post( - '/sphere/chat/${room.id}/members/me', - ); - ref.invalidate(chatRoomIdentityProvider(id)); - } catch (err) { - showErrorAlert(err); - } finally { - if (context.mounted) { - hideLoadingModal(context); - } - } - }, - label: Text('chatJoin').tr(), - icon: const Icon(Icons.add), - ).padding(top: 8), - ], - ), - ).center(), + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 280), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + room.isCommunity == true + ? Symbols.person_add + : Symbols.person_remove, + size: 36, + fill: 1, + ).padding(bottom: 4), + Text('chatNotJoined').tr(), + if (room.isCommunity != true) + Text( + 'chatUnableJoin', + textAlign: TextAlign.center, + ).tr().bold() + else + FilledButton.tonalIcon( + onPressed: () async { + try { + showLoadingModal(context); + final apiClient = ref.read(apiClientProvider); + await apiClient.post( + '/messager/chat/${room.id}/members/me', + ); + ref.invalidate(chatRoomIdentityProvider(id)); + } catch (err) { + showErrorAlert(err); + } finally { + if (context.mounted) { + hideLoadingModal(context); + } + } + }, + label: Text('chatJoin').tr(), + icon: const Icon(Icons.add), + ).padding(top: 8), + ], + ), + ).center(), ), ); } }, - loading: - () => AppScaffold( - appBar: AppBar(leading: const PageBackButton()), - body: CircularProgressIndicator().center(), - ), - error: - (error, _) => AppScaffold( - appBar: AppBar(leading: const PageBackButton()), - body: ResponseErrorWidget( - error: error, - onRetry: () => ref.refresh(chatRoomProvider(id)), - ), - ), + loading: () => AppScaffold( + appBar: AppBar(leading: const PageBackButton()), + body: CircularProgressIndicator().center(), + ), + error: (error, _) => AppScaffold( + appBar: AppBar(leading: const PageBackButton()), + body: ResponseErrorWidget( + error: error, + onRetry: () => ref.refresh(chatRoomProvider(id)), + ), + ), ); } @@ -186,10 +183,9 @@ class ChatRoomScreen extends HookConsumerWidget { case MessageItemAction.edit: messageEditingTo.value = message.toRemoteMessage(); messageController.text = messageEditingTo.value?.content ?? ''; - attachments.value = - messageEditingTo.value!.attachments - .map((e) => UniversalFile.fromAttachment(e)) - .toList(); + attachments.value = messageEditingTo.value!.attachments + .map((e) => UniversalFile.fromAttachment(e)) + .toList(); case MessageItemAction.forward: messageForwardingTo.value = message.toRemoteMessage(); case MessageItemAction.reply: @@ -424,38 +420,37 @@ class ChatRoomScreen extends HookConsumerWidget { label: Text('${(onlineCount.value ?? 0)}'), textStyle: GoogleFonts.robotoMono(fontSize: 10), textColor: Colors.white, - backgroundColor: - (onlineCount.value ?? 0) > 1 ? Colors.green : Colors.grey, + backgroundColor: (onlineCount.value ?? 0) > 1 + ? Colors.green + : Colors.grey, offset: Offset(6, 14), child: SizedBox( height: 26, width: 26, - child: - (room!.type == 1 && room.picture?.id == null) - ? SplitAvatarWidget( - filesId: - getValidMembers( - room.members!, - ).map((e) => e.account.profile.picture?.id).toList(), - ) - : room.picture?.id != null - ? ProfilePictureWidget( - fileId: room.picture?.id, - fallbackIcon: Symbols.chat, - ) - : CircleAvatar( - child: Text( - room.name![0].toUpperCase(), - style: const TextStyle(fontSize: 12), - ), + child: (room!.type == 1 && room.picture?.id == null) + ? SplitAvatarWidget( + filesId: getValidMembers( + room.members!, + ).map((e) => e.account.profile.picture?.id).toList(), + ) + : room.picture?.id != null + ? ProfilePictureWidget( + fileId: room.picture?.id, + fallbackIcon: Symbols.chat, + ) + : CircleAvatar( + child: Text( + room.name![0].toUpperCase(), + style: const TextStyle(fontSize: 12), ), + ), ), ), Text( (room.type == 1 && room.name == null) ? getValidMembers( - room.members!, - ).map((e) => e.account.nick).join(', ') + room.members!, + ).map((e) => e.account.nick).join(', ') : room.name!, ).fontSize(15), ], @@ -470,39 +465,38 @@ class ChatRoomScreen extends HookConsumerWidget { isLabelVisible: (onlineCount.value ?? 0) > 1, label: Text('${(onlineCount.value ?? 0)}'), textStyle: GoogleFonts.robotoMono(fontSize: 10), - backgroundColor: - (onlineCount.value ?? 0) > 1 ? Colors.green : Colors.grey, + backgroundColor: (onlineCount.value ?? 0) > 1 + ? Colors.green + : Colors.grey, textColor: Colors.white, offset: Offset(6, 14), child: SizedBox( height: 28, width: 28, - child: - (room!.type == 1 && room.picture?.id == null) - ? SplitAvatarWidget( - filesId: - getValidMembers( - room.members!, - ).map((e) => e.account.profile.picture?.id).toList(), - ) - : room.picture?.id != null - ? ProfilePictureWidget( - fileId: room.picture?.id, - fallbackIcon: Symbols.chat, - ) - : CircleAvatar( - child: Text( - room.name![0].toUpperCase(), - style: const TextStyle(fontSize: 12), - ), + child: (room!.type == 1 && room.picture?.id == null) + ? SplitAvatarWidget( + filesId: getValidMembers( + room.members!, + ).map((e) => e.account.profile.picture?.id).toList(), + ) + : room.picture?.id != null + ? ProfilePictureWidget( + fileId: room.picture?.id, + fallbackIcon: Symbols.chat, + ) + : CircleAvatar( + child: Text( + room.name![0].toUpperCase(), + style: const TextStyle(fontSize: 12), ), + ), ), ), Text( (room.type == 1 && room.name == null) ? getValidMembers( - room.members!, - ).map((e) => e.account.nick).join(', ') + room.members!, + ).map((e) => e.account.nick).join(', ') : room.name!, ).fontSize(19), ], @@ -531,11 +525,9 @@ class ChatRoomScreen extends HookConsumerWidget { index: index, scrollController: scrollController, alignment: 0.5, - duration: - (estimatedDistance) => Duration( - milliseconds: - (estimatedDistance * 0.5).clamp(200, 800).toInt(), - ), + duration: (estimatedDistance) => Duration( + milliseconds: (estimatedDistance * 0.5).clamp(200, 800).toInt(), + ), curve: (estimatedDistance) => Curves.easeOutCubic, ); @@ -605,12 +597,11 @@ class ChatRoomScreen extends HookConsumerWidget { final config = await showModalBottomSheet( context: context, isScrollControlled: true, - builder: - (context) => AttachmentUploaderSheet( - ref: ref, - attachments: attachments.value, - index: index, - ), + builder: (context) => AttachmentUploaderSheet( + ref: ref, + attachments: attachments.value, + index: index, + ), ); if (config == null) return; @@ -621,22 +612,20 @@ class ChatRoomScreen extends HookConsumerWidget { 'chat-upload': {index: 0}, }; - final cloudFile = - await FileUploader.createCloudFile( - ref: ref, - fileData: attachment, - poolId: config.poolId, - mode: - attachment.type == UniversalFileType.file - ? FileUploadMode.generic - : FileUploadMode.mediaSafe, - onProgress: (progress, _) { - attachmentProgress.value = { - ...attachmentProgress.value, - 'chat-upload': {index: progress ?? 0.0}, - }; - }, - ).future; + final cloudFile = await FileUploader.createCloudFile( + ref: ref, + fileData: attachment, + poolId: config.poolId, + mode: attachment.type == UniversalFileType.file + ? FileUploadMode.generic + : FileUploadMode.mediaSafe, + onProgress: (progress, _) { + attachmentProgress.value = { + ...attachmentProgress.value, + 'chat-upload': {index: progress ?? 0.0}, + }; + }, + ).future; if (cloudFile == null) { throw ArgumentError('Failed to upload the file...'); @@ -690,10 +679,9 @@ class ChatRoomScreen extends HookConsumerWidget { extentEstimation: (_, _) => 40, itemBuilder: (context, index) { final message = messageList[index]; - final nextMessage = - index < messageList.length - 1 - ? messageList[index + 1] - : null; + final nextMessage = index < messageList.length - 1 + ? messageList[index + 1] + : null; final isLastInGroup = nextMessage == null || nextMessage.senderId != message.senderId || @@ -718,15 +706,14 @@ class ChatRoomScreen extends HookConsumerWidget { toggleSelectionMode: toggleSelectionMode, toggleMessageSelection: toggleMessageSelection, onMessageAction: onMessageAction, - onJump: - (messageId) => scrollToMessage( - messageId: messageId, - messageList: messageList, - messagesNotifier: messagesNotifier, - listController: listController, - scrollController: scrollController, - ref: ref, - ), + onJump: (messageId) => scrollToMessage( + messageId: messageId, + messageList: messageList, + messagesNotifier: messagesNotifier, + listController: listController, + scrollController: scrollController, + ref: ref, + ), attachmentProgress: attachmentProgress.value, disableAnimation: settings.disableAnimation, roomOpenTime: roomOpenTime, @@ -744,17 +731,14 @@ class ChatRoomScreen extends HookConsumerWidget { automaticallyImplyLeading: false, toolbarHeight: compactHeader ? null : 74, title: chatRoom.when( - data: - (room) => - compactHeader - ? compactHeaderWidget(room) - : comfortHeaderWidget(room), + data: (room) => compactHeader + ? compactHeaderWidget(room) + : comfortHeaderWidget(room), loading: () => const Text('Loading...'), - error: - (err, _) => ResponseErrorWidget( - error: err, - onRetry: () => messagesNotifier.loadInitial(), - ), + error: (err, _) => ResponseErrorWidget( + error: err, + onRetry: () => messagesNotifier.loadInitial(), + ), ), actions: [ chatRoom.when( @@ -787,13 +771,11 @@ class ChatRoomScreen extends HookConsumerWidget { index: index, scrollController: scrollController, alignment: 0.5, - duration: - (estimatedDistance) => Duration( - milliseconds: - (estimatedDistance * 0.5) - .clamp(200, 800) - .toInt(), - ), + duration: (estimatedDistance) => Duration( + milliseconds: (estimatedDistance * 0.5) + .clamp(200, 800) + .toInt(), + ), curve: (estimatedDistance) => Curves.easeOutCubic, ); } catch (e) { @@ -819,38 +801,35 @@ class ChatRoomScreen extends HookConsumerWidget { duration: const Duration(milliseconds: 300), switchInCurve: Curves.easeOutCubic, switchOutCurve: Curves.easeInCubic, - transitionBuilder: ( - Widget child, - Animation animation, - ) { - return SlideTransition( - position: Tween( - begin: const Offset(0, 0.05), - end: Offset.zero, - ).animate(animation), - child: FadeTransition(opacity: animation, child: child), - ); - }, + transitionBuilder: + (Widget child, Animation animation) { + return SlideTransition( + position: Tween( + begin: const Offset(0, 0.05), + end: Offset.zero, + ).animate(animation), + child: FadeTransition( + opacity: animation, + child: child, + ), + ); + }, child: messages.when( - data: - (messageList) => - messageList.isEmpty - ? Center( - key: const ValueKey('empty-messages'), - child: Text('No messages yet'.tr()), - ) - : chatMessageListWidget(messageList), - loading: - () => const Center( - key: ValueKey('loading-messages'), - child: CircularProgressIndicator(), - ), - error: - (error, _) => ResponseErrorWidget( - key: const ValueKey('error-messages'), - error: error, - onRetry: () => messagesNotifier.loadInitial(), - ), + data: (messageList) => messageList.isEmpty + ? Center( + key: const ValueKey('empty-messages'), + child: Text('No messages yet'.tr()), + ) + : chatMessageListWidget(messageList), + loading: () => const Center( + key: ValueKey('loading-messages'), + child: CircularProgressIndicator(), + ), + error: (error, _) => ResponseErrorWidget( + key: const ValueKey('error-messages'), + error: error, + onRetry: () => messagesNotifier.loadInitial(), + ), ), ), ), @@ -862,10 +841,8 @@ class ChatRoomScreen extends HookConsumerWidget { right: 0, top: 0, child: chatRoom.when( - data: - (data) => CallOverlayBar( - room: data!, - ).padding(horizontal: 8, top: 12), + data: (data) => + CallOverlayBar(room: data!).padding(horizontal: 8, top: 12), error: (_, _) => const SizedBox.shrink(), loading: () => const SizedBox.shrink(), ), @@ -903,35 +880,34 @@ class ChatRoomScreen extends HookConsumerWidget { if (!isSelectionMode.value) AnimatedBuilder( animation: bottomGradientNotifier.value, - builder: - (context, child) => Positioned( - left: 0, - right: 0, - bottom: 0, - child: Opacity( - opacity: bottomGradientNotifier.value.value, - child: Container( - height: math.min( - MediaQuery.of(context).size.height * 0.1, - 128, - ), - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.bottomCenter, - end: Alignment.topCenter, - colors: [ - Theme.of( - context, - ).colorScheme.surfaceContainer.withOpacity(0.8), - Theme.of( - context, - ).colorScheme.surfaceContainer.withOpacity(0.0), - ], - ), - ), + builder: (context, child) => Positioned( + left: 0, + right: 0, + bottom: 0, + child: Opacity( + opacity: bottomGradientNotifier.value.value, + child: Container( + height: math.min( + MediaQuery.of(context).size.height * 0.1, + 128, + ), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + colors: [ + Theme.of( + context, + ).colorScheme.surfaceContainer.withOpacity(0.8), + Theme.of( + context, + ).colorScheme.surfaceContainer.withOpacity(0.0), + ], ), ), ), + ), + ), ), // Chat Input positioned above gradient (higher z-index) if (!isSelectionMode.value) @@ -940,74 +916,73 @@ class ChatRoomScreen extends HookConsumerWidget { right: 0, bottom: 0, // At the very bottom, above gradient child: chatRoom.when( - data: - (room) => Column( - key: inputKey, - mainAxisSize: MainAxisSize.min, - children: [ - ChatInput( - messageController: messageController, - chatRoom: room!, - onSend: sendMessage, - onClear: () { - if (messageEditingTo.value != null) { - attachments.value.clear(); - messageController.clear(); - } - messageEditingTo.value = null; - messageReplyingTo.value = null; - messageForwardingTo.value = null; - selectedPoll.value = null; - selectedFund.value = null; - }, - messageEditingTo: messageEditingTo.value, - messageReplyingTo: messageReplyingTo.value, - messageForwardingTo: messageForwardingTo.value, - selectedPoll: selectedPoll.value, - onPollSelected: (poll) => selectedPoll.value = poll, - selectedFund: selectedFund.value, - onFundSelected: (fund) => selectedFund.value = fund, - onPickFile: (bool isPhoto) { - if (isPhoto) { - pickPhotoMedia(); - } else { - pickVideoMedia(); - } - }, - onPickAudio: pickAudioMedia, - onPickGeneralFile: pickGeneralFile, - onLinkAttachment: linkAttachment, - attachments: attachments.value, - onUploadAttachment: uploadAttachment, - onDeleteAttachment: (index) async { - final attachment = attachments.value[index]; - if (attachment.isOnCloud && !attachment.isLink) { - final client = ref.watch(apiClientProvider); - await client.delete( - '/drive/files/${attachment.data.id}', - ); - } - final clone = List.of(attachments.value); - clone.removeAt(index); - attachments.value = clone; - }, - onMoveAttachment: (idx, delta) { - if (idx + delta < 0 || - idx + delta >= attachments.value.length) { - return; - } - final clone = List.of(attachments.value); - clone.insert(idx + delta, clone.removeAt(idx)); - attachments.value = clone; - }, - onAttachmentsChanged: (newAttachments) { - attachments.value = newAttachments; - }, - attachmentProgress: attachmentProgress.value, - ), - Gap(MediaQuery.of(context).padding.bottom), - ], + data: (room) => Column( + key: inputKey, + mainAxisSize: MainAxisSize.min, + children: [ + ChatInput( + messageController: messageController, + chatRoom: room!, + onSend: sendMessage, + onClear: () { + if (messageEditingTo.value != null) { + attachments.value.clear(); + messageController.clear(); + } + messageEditingTo.value = null; + messageReplyingTo.value = null; + messageForwardingTo.value = null; + selectedPoll.value = null; + selectedFund.value = null; + }, + messageEditingTo: messageEditingTo.value, + messageReplyingTo: messageReplyingTo.value, + messageForwardingTo: messageForwardingTo.value, + selectedPoll: selectedPoll.value, + onPollSelected: (poll) => selectedPoll.value = poll, + selectedFund: selectedFund.value, + onFundSelected: (fund) => selectedFund.value = fund, + onPickFile: (bool isPhoto) { + if (isPhoto) { + pickPhotoMedia(); + } else { + pickVideoMedia(); + } + }, + onPickAudio: pickAudioMedia, + onPickGeneralFile: pickGeneralFile, + onLinkAttachment: linkAttachment, + attachments: attachments.value, + onUploadAttachment: uploadAttachment, + onDeleteAttachment: (index) async { + final attachment = attachments.value[index]; + if (attachment.isOnCloud && !attachment.isLink) { + final client = ref.watch(apiClientProvider); + await client.delete( + '/drive/files/${attachment.data.id}', + ); + } + final clone = List.of(attachments.value); + clone.removeAt(index); + attachments.value = clone; + }, + onMoveAttachment: (idx, delta) { + if (idx + delta < 0 || + idx + delta >= attachments.value.length) { + return; + } + final clone = List.of(attachments.value); + clone.insert(idx + delta, clone.removeAt(idx)); + attachments.value = clone; + }, + onAttachmentsChanged: (newAttachments) { + attachments.value = newAttachments; + }, + attachmentProgress: attachmentProgress.value, ), + Gap(MediaQuery.of(context).padding.bottom), + ], + ), error: (_, _) => const SizedBox.shrink(), loading: () => const SizedBox.shrink(), ), diff --git a/lib/screens/chat/room_detail.dart b/lib/screens/chat/room_detail.dart index be3e35d9..0a851437 100644 --- a/lib/screens/chat/room_detail.dart +++ b/lib/screens/chat/room_detail.dart @@ -67,7 +67,7 @@ class ChatDetailScreen extends HookConsumerWidget { try { final client = ref.watch(apiClientProvider); await client.patch( - '/sphere/chat/$id/members/me/notify', + '/messager/chat/$id/members/me/notify', data: {'notify_level': level}, ); ref.invalidate(chatRoomIdentityProvider(id)); @@ -85,7 +85,7 @@ class ChatDetailScreen extends HookConsumerWidget { try { final client = ref.watch(apiClientProvider); await client.patch( - '/sphere/chat/$id/members/me/notify', + '/messager/chat/$id/members/me/notify', data: {'break_until': until.toUtc().toIso8601String()}, ); ref.invalidate(chatRoomIdentityProvider(id)); @@ -529,7 +529,7 @@ class _ChatRoomActionMenu extends HookConsumerWidget { ).then((confirm) async { if (confirm) { final client = ref.watch(apiClientProvider); - await client.delete('/sphere/chat/$id'); + await client.delete('/messager/chat/$id'); ref.invalidate(chatRoomJoinedProvider); if (context.mounted) { context.pop(); @@ -560,7 +560,7 @@ class _ChatRoomActionMenu extends HookConsumerWidget { ).then((confirm) async { if (confirm) { final client = ref.watch(apiClientProvider); - await client.delete('/sphere/chat/$id/members/me'); + await client.delete('/messager/chat/$id/members/me'); ref.invalidate(chatRoomJoinedProvider); if (context.mounted) { context.pop(); @@ -600,7 +600,7 @@ class ChatMemberListNotifier Future> fetch() async { final apiClient = ref.watch(apiClientProvider); final response = await apiClient.get( - '/sphere/chat/$arg/members', + '/messager/chat/$arg/members', queryParameters: { 'offset': fetchedCount.toString(), 'take': pageSize, @@ -645,7 +645,7 @@ class _ChatMemberListSheet extends HookConsumerWidget { try { final apiClient = ref.watch(apiClientProvider); await apiClient.post( - '/sphere/chat/invites/$roomId', + '/messager/chat/invites/$roomId', data: {'related_user_id': result.id, 'role': 0}, ); memberNotifier.refresh(); @@ -735,7 +735,7 @@ class _ChatMemberListSheet extends HookConsumerWidget { try { final apiClient = ref.watch(apiClientProvider); await apiClient.delete( - '/sphere/chat/$roomId/members/${member.accountId}', + '/messager/chat/$roomId/members/${member.accountId}', ); // Refresh both providers memberNotifier.refresh(); diff --git a/lib/services/autocomplete_service.dart b/lib/services/autocomplete_service.dart index 6d1013c1..d6c833df 100644 --- a/lib/services/autocomplete_service.dart +++ b/lib/services/autocomplete_service.dart @@ -18,7 +18,7 @@ class AutocompleteService { String content, ) async { final response = await _client.post( - '/sphere/chat/$roomId/autocomplete', + '/messager/chat/$roomId/autocomplete', data: {'content': content}, ); diff --git a/lib/widgets/chat/call_button.dart b/lib/widgets/chat/call_button.dart index 76a6481d..39117995 100644 --- a/lib/widgets/chat/call_button.dart +++ b/lib/widgets/chat/call_button.dart @@ -16,7 +16,7 @@ Future ongoingCall(Ref ref, String roomId) async { if (roomId.isEmpty) return null; try { final apiClient = ref.watch(apiClientProvider); - final resp = await apiClient.get('/sphere/chat/realtime/$roomId'); + final resp = await apiClient.get('/messager/chat/realtime/$roomId'); return SnRealtimeCall.fromJson(resp.data); } catch (e) { if (e is DioException && e.response?.statusCode == 404) { @@ -42,7 +42,7 @@ class AudioCallButton extends HookConsumerWidget { Future handleJoin() async { isLoading.value = true; try { - await apiClient.post('/sphere/chat/realtime/${room.id}'); + await apiClient.post('/messager/chat/realtime/${room.id}'); ref.invalidate(ongoingCallProvider(room.id)); // Just join the room, the overlay will handle the UI await callNotifier.joinRoom(room); @@ -56,7 +56,7 @@ class AudioCallButton extends HookConsumerWidget { Future handleEnd() async { isLoading.value = true; try { - await apiClient.delete('/sphere/chat/realtime/${room.id}'); + await apiClient.delete('/messager/chat/realtime/${room.id}'); callNotifier.dispose(); // Clean up call resources } catch (e) { showErrorAlert(e); diff --git a/lib/widgets/chat/call_overlay.dart b/lib/widgets/chat/call_overlay.dart index 86246753..0bc69020 100644 --- a/lib/widgets/chat/call_overlay.dart +++ b/lib/widgets/chat/call_overlay.dart @@ -117,7 +117,7 @@ class CallControlsBar extends HookConsumerWidget { try { showLoadingModal(context); await apiClient.delete( - '/sphere/chat/realtime/${callNotifier.roomId}', + '/messager/chat/realtime/${callNotifier.roomId}', ); callNotifier.dispose(); if (context.mounted && popOnLeaves) { diff --git a/lib/widgets/chat/public_room_preview.dart b/lib/widgets/chat/public_room_preview.dart index 39ab7686..c57cfcad 100644 --- a/lib/widgets/chat/public_room_preview.dart +++ b/lib/widgets/chat/public_room_preview.dart @@ -66,8 +66,9 @@ class PublicRoomPreview extends HookConsumerWidget { extentEstimation: (_, _) => 40, itemBuilder: (context, index) { final message = messageList[index]; - final nextMessage = - index < messageList.length - 1 ? messageList[index + 1] : null; + final nextMessage = index < messageList.length - 1 + ? messageList[index + 1] + : null; final isLastInGroup = nextMessage == null || nextMessage.senderId != message.senderId || @@ -98,25 +99,23 @@ class PublicRoomPreview extends HookConsumerWidget { SizedBox( height: 26, width: 26, - child: - (room.type == 1 && room.picture?.id == null) - ? SplitAvatarWidget( - filesId: - room.members! - .map((e) => e.account.profile.picture?.id) - .toList(), - ) - : room.picture?.id != null - ? ProfilePictureWidget( - fileId: room.picture?.id, - fallbackIcon: Symbols.chat, - ) - : CircleAvatar( - child: Text( - room.name![0].toUpperCase(), - style: const TextStyle(fontSize: 12), - ), + child: (room.type == 1 && room.picture?.id == null) + ? SplitAvatarWidget( + filesId: room.members! + .map((e) => e.account.profile.picture?.id) + .toList(), + ) + : room.picture?.id != null + ? ProfilePictureWidget( + fileId: room.picture?.id, + fallbackIcon: Symbols.chat, + ) + : CircleAvatar( + child: Text( + room.name![0].toUpperCase(), + style: const TextStyle(fontSize: 12), ), + ), ), Text( (room.type == 1 && room.name == null) @@ -133,25 +132,23 @@ class PublicRoomPreview extends HookConsumerWidget { SizedBox( height: 26, width: 26, - child: - (room.type == 1 && room.picture?.id == null) - ? SplitAvatarWidget( - filesId: - room.members! - .map((e) => e.account.profile.picture?.id) - .toList(), - ) - : room.picture?.id != null - ? ProfilePictureWidget( - fileId: room.picture?.id, - fallbackIcon: Symbols.chat, - ) - : CircleAvatar( - child: Text( - room.name![0].toUpperCase(), - style: const TextStyle(fontSize: 12), - ), + child: (room.type == 1 && room.picture?.id == null) + ? SplitAvatarWidget( + filesId: room.members! + .map((e) => e.account.profile.picture?.id) + .toList(), + ) + : room.picture?.id != null + ? ProfilePictureWidget( + fileId: room.picture?.id, + fallbackIcon: Symbols.chat, + ) + : CircleAvatar( + child: Text( + room.name![0].toUpperCase(), + style: const TextStyle(fontSize: 12), ), + ), ), Text( (room.type == 1 && room.name == null) @@ -182,17 +179,14 @@ class PublicRoomPreview extends HookConsumerWidget { children: [ Expanded( child: messages.when( - data: - (messageList) => - messageList.isEmpty - ? Center(child: Text('No messages yet'.tr())) - : chatMessageListWidget(messageList), + data: (messageList) => messageList.isEmpty + ? Center(child: Text('No messages yet'.tr())) + : chatMessageListWidget(messageList), loading: () => const Center(child: CircularProgressIndicator()), - error: - (error, _) => ResponseErrorWidget( - error: error, - onRetry: () => messagesNotifier.loadInitial(), - ), + error: (error, _) => ResponseErrorWidget( + error: error, + onRetry: () => messagesNotifier.loadInitial(), + ), ), ), // Join button at the bottom for public rooms @@ -203,7 +197,7 @@ class PublicRoomPreview extends HookConsumerWidget { try { showLoadingModal(context); final apiClient = ref.read(apiClientProvider); - await apiClient.post('/sphere/chat/${room.id}/members/me'); + await apiClient.post('/messager/chat/${room.id}/members/me'); ref.invalidate(chatRoomIdentityProvider(id)); } catch (err) { showErrorAlert(err); diff --git a/lib/widgets/share/share_sheet.dart b/lib/widgets/share/share_sheet.dart index 0e1a6ba2..dd98aca3 100644 --- a/lib/widgets/share/share_sheet.dart +++ b/lib/widgets/share/share_sheet.dart @@ -218,20 +218,19 @@ class _ShareSheetState extends ConsumerState { case ShareContentType.file: // Upload files to cloud storage if (widget.content.files?.isNotEmpty == true) { - final universalFiles = - widget.content.files!.map((file) { - UniversalFileType fileType; - if (file.mimeType?.startsWith('image/') == true) { - fileType = UniversalFileType.image; - } else if (file.mimeType?.startsWith('video/') == true) { - fileType = UniversalFileType.video; - } else if (file.mimeType?.startsWith('audio/') == true) { - fileType = UniversalFileType.audio; - } else { - fileType = UniversalFileType.file; - } - return UniversalFile(data: file, type: fileType); - }).toList(); + final universalFiles = widget.content.files!.map((file) { + UniversalFileType fileType; + if (file.mimeType?.startsWith('image/') == true) { + fileType = UniversalFileType.image; + } else if (file.mimeType?.startsWith('video/') == true) { + fileType = UniversalFileType.video; + } else if (file.mimeType?.startsWith('audio/') == true) { + fileType = UniversalFileType.audio; + } else { + fileType = UniversalFileType.file; + } + return UniversalFile(data: file, type: fileType); + }).toList(); // Initialize progress tracking final messageId = DateTime.now().millisecondsSinceEpoch.toString(); @@ -243,19 +242,17 @@ class _ShareSheetState extends ConsumerState { // Upload each file for (var idx = 0; idx < universalFiles.length; idx++) { final file = universalFiles[idx]; - final cloudFile = - await FileUploader.createCloudFile( - ref: ref, - fileData: file, - onProgress: (progress, _) { - if (mounted) { - setState(() { - _fileUploadProgress[messageId]?[idx] = - progress ?? 0.0; - }); - } - }, - ).future; + final cloudFile = await FileUploader.createCloudFile( + ref: ref, + fileData: file, + onProgress: (progress, _) { + if (mounted) { + setState(() { + _fileUploadProgress[messageId]?[idx] = progress ?? 0.0; + }); + } + }, + ).future; if (cloudFile == null) { throw Exception('Failed to upload file: ${file.data.name}'); @@ -272,7 +269,7 @@ class _ShareSheetState extends ConsumerState { // Send message to chat room await apiClient.post( - '/sphere/chat/${chatRoom.id}/messages', + '/messager/chat/${chatRoom.id}/messages', data: {'content': content, 'attachments_id': attachmentIds, 'meta': {}}, ); @@ -359,20 +356,19 @@ class _ShareSheetState extends ConsumerState { setState(() => _isLoading = true); try { - final universalFiles = - widget.content.files!.map((file) { - UniversalFileType fileType; - if (file.mimeType?.startsWith('image/') == true) { - fileType = UniversalFileType.image; - } else if (file.mimeType?.startsWith('video/') == true) { - fileType = UniversalFileType.video; - } else if (file.mimeType?.startsWith('audio/') == true) { - fileType = UniversalFileType.audio; - } else { - fileType = UniversalFileType.file; - } - return UniversalFile(data: file, type: fileType); - }).toList(); + final universalFiles = widget.content.files!.map((file) { + UniversalFileType fileType; + if (file.mimeType?.startsWith('image/') == true) { + fileType = UniversalFileType.image; + } else if (file.mimeType?.startsWith('video/') == true) { + fileType = UniversalFileType.video; + } else if (file.mimeType?.startsWith('audio/') == true) { + fileType = UniversalFileType.audio; + } else { + fileType = UniversalFileType.file; + } + return UniversalFile(data: file, type: fileType); + }).toList(); // Initialize progress tracking final messageId = DateTime.now().millisecondsSinceEpoch.toString(); @@ -383,18 +379,17 @@ class _ShareSheetState extends ConsumerState { // Upload each file for (var idx = 0; idx < universalFiles.length; idx++) { final file = universalFiles[idx]; - final cloudFile = - await FileUploader.createCloudFile( - ref: ref, - fileData: file, - onProgress: (progress, _) { - if (mounted) { - setState(() { - _fileUploadProgress[messageId]?[idx] = progress ?? 0.0; - }); - } - }, - ).future; + final cloudFile = await FileUploader.createCloudFile( + ref: ref, + fileData: file, + onProgress: (progress, _) { + if (mounted) { + setState(() { + _fileUploadProgress[messageId]?[idx] = progress ?? 0.0; + }); + } + }, + ).future; if (cloudFile == null) { throw Exception('Failed to upload file: ${file.data.name}'); @@ -481,8 +476,9 @@ class _ShareSheetState extends ConsumerState { margin: const EdgeInsets.all(16), padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: - Theme.of(context).colorScheme.surfaceContainerHighest, + color: Theme.of( + context, + ).colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(12), ), child: Column( @@ -490,12 +486,12 @@ class _ShareSheetState extends ConsumerState { children: [ Text( 'contentToShare'.tr(), - style: Theme.of( - context, - ).textTheme.labelMedium?.copyWith( - color: - Theme.of(context).colorScheme.onSurfaceVariant, - ), + style: Theme.of(context).textTheme.labelMedium + ?.copyWith( + color: Theme.of( + context, + ).colorScheme.onSurfaceVariant, + ), ), const SizedBox(height: 8), _ContentPreview(content: widget.content), @@ -510,12 +506,12 @@ class _ShareSheetState extends ConsumerState { children: [ Text( 'quickActions'.tr(), - style: Theme.of( - context, - ).textTheme.titleSmall?.copyWith( - color: - Theme.of(context).colorScheme.onSurfaceVariant, - ), + style: Theme.of(context).textTheme.titleSmall + ?.copyWith( + color: Theme.of( + context, + ).colorScheme.onSurfaceVariant, + ), ), const SizedBox(height: 12), SizedBox( @@ -568,12 +564,12 @@ class _ShareSheetState extends ConsumerState { children: [ Text( 'sendToChat'.tr(), - style: Theme.of( - context, - ).textTheme.titleSmall?.copyWith( - color: - Theme.of(context).colorScheme.onSurfaceVariant, - ), + style: Theme.of(context).textTheme.titleSmall + ?.copyWith( + color: Theme.of( + context, + ).colorScheme.onSurfaceVariant, + ), ), const SizedBox(height: 12), @@ -592,10 +588,8 @@ class _ShareSheetState extends ConsumerState { vertical: 12, ), ), - onTapOutside: - (_) => - FocusManager.instance.primaryFocus - ?.unfocus(), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), maxLines: 3, minLines: 1, enabled: !_isLoading, @@ -603,8 +597,9 @@ class _ShareSheetState extends ConsumerState { ), _ChatRoomsList( - onChatSelected: - _isLoading ? null : _shareToSpecificChat, + onChatSelected: _isLoading + ? null + : _shareToSpecificChat, ), ], ), @@ -627,11 +622,9 @@ class _ShareSheetState extends ConsumerState { if (_fileUploadProgress.isNotEmpty) ..._fileUploadProgress.entries.map((entry) { final progress = entry.value; - final averageProgress = - progress.isEmpty - ? 0.0 - : progress.reduce((a, b) => a + b) / - progress.length; + final averageProgress = progress.isEmpty + ? 0.0 + : progress.reduce((a, b) => a + b) / progress.length; return Column( children: [ Text( @@ -699,39 +692,38 @@ class _ChatRoomsList extends ConsumerWidget { final room = rooms[index]; return _ChatRoomOption( room: room, - onTap: - onChatSelected != null ? () => onChatSelected!(room) : null, + onTap: onChatSelected != null + ? () => onChatSelected!(room) + : null, ); }, ), ); }, - loading: - () => SizedBox( - height: 80, - child: Center( - child: CircularProgressIndicator( - strokeWidth: 2, - color: Theme.of(context).colorScheme.primary, - ), - ), - ), - error: - (error, stack) => Container( - height: 80, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.errorContainer, - borderRadius: BorderRadius.circular(12), - ), - child: Center( - child: Text( - 'failedToLoadChats'.tr(), - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).colorScheme.onErrorContainer, - ), - ), + loading: () => SizedBox( + height: 80, + child: Center( + child: CircularProgressIndicator( + strokeWidth: 2, + color: Theme.of(context).colorScheme.primary, + ), + ), + ), + error: (error, stack) => Container( + height: 80, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.errorContainer, + borderRadius: BorderRadius.circular(12), + ), + child: Center( + child: Text( + 'failedToLoadChats'.tr(), + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).colorScheme.onErrorContainer, ), ), + ), + ), ); } } @@ -746,10 +738,9 @@ class _ChatRoomOption extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final userInfo = ref.watch(userInfoProvider); - final validMembers = - (room.members ?? []) - .where((m) => m.accountId != userInfo.value?.id) - .toList(); + final validMembers = (room.members ?? []) + .where((m) => m.accountId != userInfo.value?.id) + .toList(); final isDirect = room.type == 1; // Assuming type 1 is direct chat final displayName = @@ -764,12 +755,11 @@ class _ChatRoomOption extends HookConsumerWidget { width: 72, padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: - onTap != null - ? Theme.of(context).colorScheme.surfaceContainerHighest - : Theme.of( - context, - ).colorScheme.surfaceContainerHighest.withOpacity(0.5), + color: onTap != null + ? Theme.of(context).colorScheme.surfaceContainerHighest + : Theme.of( + context, + ).colorScheme.surfaceContainerHighest.withOpacity(0.5), borderRadius: BorderRadius.circular(12), border: Border.all( color: Theme.of(context).colorScheme.outline.withOpacity(0.2), @@ -786,36 +776,30 @@ class _ChatRoomOption extends HookConsumerWidget { color: Theme.of(context).colorScheme.primary.withOpacity(0.1), borderRadius: BorderRadius.circular(16), ), - child: - (isDirect && room.picture?.id == null) - ? SplitAvatarWidget( - filesId: - validMembers - .map((e) => e.account.profile.picture?.id) - .toList(), - radius: 16, - ) - : room.picture?.id == null - ? CircleAvatar( - radius: 16, - child: Text(room.name![0].toUpperCase()), - ) - : ProfilePictureWidget( - fileId: room.picture?.id, - radius: 16, - ), + child: (isDirect && room.picture?.id == null) + ? SplitAvatarWidget( + filesId: validMembers + .map((e) => e.account.profile.picture?.id) + .toList(), + radius: 16, + ) + : room.picture?.id == null + ? CircleAvatar( + radius: 16, + child: Text(room.name![0].toUpperCase()), + ) + : ProfilePictureWidget(fileId: room.picture?.id, radius: 16), ), const SizedBox(height: 4), // Chat room name Text( displayName, style: Theme.of(context).textTheme.labelSmall?.copyWith( - color: - onTap != null - ? Theme.of(context).colorScheme.onSurface - : Theme.of( - context, - ).colorScheme.onSurfaceVariant.withOpacity(0.5), + color: onTap != null + ? Theme.of(context).colorScheme.onSurface + : Theme.of( + context, + ).colorScheme.onSurfaceVariant.withOpacity(0.5), ), textAlign: TextAlign.center, maxLines: 1, @@ -847,12 +831,11 @@ class _CompactShareOption extends StatelessWidget { width: 72, padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: - onTap != null - ? Theme.of(context).colorScheme.surfaceContainerHighest - : Theme.of( - context, - ).colorScheme.surfaceContainerHighest.withOpacity(0.5), + color: onTap != null + ? Theme.of(context).colorScheme.surfaceContainerHighest + : Theme.of( + context, + ).colorScheme.surfaceContainerHighest.withOpacity(0.5), borderRadius: BorderRadius.circular(12), border: Border.all( color: Theme.of(context).colorScheme.outline.withOpacity(0.2), @@ -864,23 +847,21 @@ class _CompactShareOption extends StatelessWidget { Icon( icon, size: 24, - color: - onTap != null - ? Theme.of(context).colorScheme.primary - : Theme.of( - context, - ).colorScheme.onSurfaceVariant.withOpacity(0.5), + color: onTap != null + ? Theme.of(context).colorScheme.primary + : Theme.of( + context, + ).colorScheme.onSurfaceVariant.withOpacity(0.5), ), const SizedBox(height: 4), Text( title, style: Theme.of(context).textTheme.labelSmall?.copyWith( - color: - onTap != null - ? Theme.of(context).colorScheme.onSurface - : Theme.of( - context, - ).colorScheme.onSurfaceVariant.withOpacity(0.5), + color: onTap != null + ? Theme.of(context).colorScheme.onSurface + : Theme.of( + context, + ).colorScheme.onSurfaceVariant.withOpacity(0.5), ), textAlign: TextAlign.center, maxLines: 1, @@ -937,26 +918,25 @@ class _LinkPreview extends ConsumerWidget { final linkPreviewAsync = ref.watch(linkPreviewProvider(link)); return linkPreviewAsync.when( - loading: - () => Container( - constraints: const BoxConstraints(maxHeight: kPreviewMaxHeight), - child: Row( - children: [ - const SizedBox( - width: 16, - height: 16, - child: CircularProgressIndicator(strokeWidth: 2), - ), - const SizedBox(width: 8), - Text( - 'Loading link preview...', - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - ], + loading: () => Container( + constraints: const BoxConstraints(maxHeight: kPreviewMaxHeight), + child: Row( + children: [ + const SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator(strokeWidth: 2), ), - ), + const SizedBox(width: 8), + Text( + 'Loading link preview...', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ], + ), + ), error: (error, stackTrace) => _buildFallbackPreview(context), data: (embed) { if (embed == null) { @@ -979,27 +959,27 @@ class _LinkPreview extends ConsumerWidget { margin: const EdgeInsets.only(right: 12), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), - color: - Theme.of(context).colorScheme.surfaceContainerHighest, + color: Theme.of( + context, + ).colorScheme.surfaceContainerHighest, ), child: ClipRRect( borderRadius: BorderRadius.circular(8), - child: - embed.imageUrl != null - ? Image.network( - embed.imageUrl!, - fit: BoxFit.cover, - errorBuilder: (context, error, stackTrace) { - return _buildFaviconFallback( - context, - embed.faviconUrl ?? '', - ); - }, - ) - : _buildFaviconFallback( - context, - embed.faviconUrl ?? '', - ), + child: embed.imageUrl != null + ? Image.network( + embed.imageUrl!, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return _buildFaviconFallback( + context, + embed.faviconUrl ?? '', + ); + }, + ) + : _buildFaviconFallback( + context, + embed.faviconUrl ?? '', + ), ), ), // Content @@ -1033,12 +1013,12 @@ class _LinkPreview extends ConsumerWidget { padding: const EdgeInsets.only(top: 4), child: Text( embed.description!, - style: Theme.of( - context, - ).textTheme.bodySmall?.copyWith( - color: - Theme.of(context).colorScheme.onSurfaceVariant, - ), + style: Theme.of(context).textTheme.bodySmall + ?.copyWith( + color: Theme.of( + context, + ).colorScheme.onSurfaceVariant, + ), maxLines: 2, overflow: TextOverflow.ellipsis, ), @@ -1231,17 +1211,15 @@ class _FilePreview extends StatelessWidget { return Container( width: 40, height: 40, - color: - Theme.of( - context, - ).colorScheme.surfaceVariant, + color: Theme.of( + context, + ).colorScheme.surfaceVariant, child: Icon( Symbols.broken_image, size: 20, - color: - Theme.of( - context, - ).colorScheme.onSurfaceVariant, + color: Theme.of( + context, + ).colorScheme.onSurfaceVariant, ), ); }, @@ -1252,17 +1230,17 @@ class _FilePreview extends StatelessWidget { width: 40, height: 40, decoration: BoxDecoration( - color: - Theme.of(context).colorScheme.primaryContainer, + color: Theme.of( + context, + ).colorScheme.primaryContainer, borderRadius: BorderRadius.circular(4), ), child: Icon( _getFileIcon(file.name), size: 20, - color: - Theme.of( - context, - ).colorScheme.onPrimaryContainer, + color: Theme.of( + context, + ).colorScheme.onPrimaryContainer, ), ), const SizedBox(width: 12), @@ -1281,22 +1259,19 @@ class _FilePreview extends StatelessWidget { builder: (context, snapshot) { if (snapshot.hasData) { final size = snapshot.data!; - final sizeStr = - size < 1024 - ? '${size}B' - : size < 1024 * 1024 - ? '${(size / 1024).toStringAsFixed(1)}KB' - : '${(size / (1024 * 1024)).toStringAsFixed(1)}MB'; + final sizeStr = size < 1024 + ? '${size}B' + : size < 1024 * 1024 + ? '${(size / 1024).toStringAsFixed(1)}KB' + : '${(size / (1024 * 1024)).toStringAsFixed(1)}MB'; return Text( sizeStr, - style: Theme.of( - context, - ).textTheme.bodySmall?.copyWith( - color: - Theme.of( + style: Theme.of(context).textTheme.bodySmall + ?.copyWith( + color: Theme.of( context, ).colorScheme.onSurfaceVariant, - ), + ), ); } return const SizedBox.shrink(); @@ -1329,13 +1304,12 @@ void showShareSheet({ context: context, isScrollControlled: true, useRootNavigator: true, - builder: - (context) => ShareSheet( - content: content, - title: title, - toSystem: toSystem, - onClose: onClose, - ), + builder: (context) => ShareSheet( + content: content, + title: title, + toSystem: toSystem, + onClose: onClose, + ), ); }