✨ Chat message uploading file progress
This commit is contained in:
parent
81d5083908
commit
3737de6936
@ -236,5 +236,6 @@
|
|||||||
},
|
},
|
||||||
"levelingProgress": "Leveling Progress",
|
"levelingProgress": "Leveling Progress",
|
||||||
"levelingProgressExperience": "{} EXP",
|
"levelingProgressExperience": "{} EXP",
|
||||||
"levelingProgressLevel": "Level {}"
|
"levelingProgressLevel": "Level {}",
|
||||||
|
"fileUploadingProgress": "Uploading file #{}: {}%"
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:island/models/chat.dart';
|
import 'package:island/models/chat.dart';
|
||||||
|
import 'package:island/models/file.dart';
|
||||||
|
|
||||||
class ChatMessages extends Table {
|
class ChatMessages extends Table {
|
||||||
TextColumn get id => text()();
|
TextColumn get id => text()();
|
||||||
@ -23,6 +24,7 @@ class LocalChatMessage {
|
|||||||
final DateTime createdAt;
|
final DateTime createdAt;
|
||||||
MessageStatus status;
|
MessageStatus status;
|
||||||
final String? nonce;
|
final String? nonce;
|
||||||
|
List<UniversalFile>? localAttachments;
|
||||||
|
|
||||||
LocalChatMessage({
|
LocalChatMessage({
|
||||||
required this.id,
|
required this.id,
|
||||||
@ -30,8 +32,9 @@ class LocalChatMessage {
|
|||||||
required this.senderId,
|
required this.senderId,
|
||||||
required this.data,
|
required this.data,
|
||||||
required this.createdAt,
|
required this.createdAt,
|
||||||
|
required this.nonce,
|
||||||
required this.status,
|
required this.status,
|
||||||
this.nonce,
|
this.localAttachments,
|
||||||
});
|
});
|
||||||
|
|
||||||
SnChatMessage toRemoteMessage() {
|
SnChatMessage toRemoteMessage() {
|
||||||
|
@ -14,6 +14,7 @@ class MessageRepository {
|
|||||||
final AppDatabase _database;
|
final AppDatabase _database;
|
||||||
|
|
||||||
final Map<String, LocalChatMessage> pendingMessages = {};
|
final Map<String, LocalChatMessage> pendingMessages = {};
|
||||||
|
final Map<String, Map<int, double>> fileUploadProgress = {};
|
||||||
|
|
||||||
MessageRepository(this.room, this.identity, this._apiClient, this._database);
|
MessageRepository(this.room, this.identity, this._apiClient, this._database);
|
||||||
|
|
||||||
@ -181,6 +182,7 @@ class MessageRepository {
|
|||||||
SnChatMessage? forwardingTo,
|
SnChatMessage? forwardingTo,
|
||||||
SnChatMessage? editingTo,
|
SnChatMessage? editingTo,
|
||||||
Function(LocalChatMessage)? onPending,
|
Function(LocalChatMessage)? onPending,
|
||||||
|
Function(String, Map<int, double>)? onProgress,
|
||||||
}) async {
|
}) async {
|
||||||
// Generate a unique nonce for this message
|
// Generate a unique nonce for this message
|
||||||
final nonce = const Uuid().v4();
|
final nonce = const Uuid().v4();
|
||||||
@ -204,6 +206,7 @@ class MessageRepository {
|
|||||||
|
|
||||||
// Store in memory and database
|
// Store in memory and database
|
||||||
pendingMessages[localMessage.id] = localMessage;
|
pendingMessages[localMessage.id] = localMessage;
|
||||||
|
fileUploadProgress[localMessage.id] = {};
|
||||||
await _database.saveMessage(_database.messageToCompanion(localMessage));
|
await _database.saveMessage(_database.messageToCompanion(localMessage));
|
||||||
onPending?.call(localMessage);
|
onPending?.call(localMessage);
|
||||||
|
|
||||||
@ -225,6 +228,13 @@ class MessageRepository {
|
|||||||
UniversalFileType.audio => 'audio/unknown',
|
UniversalFileType.audio => 'audio/unknown',
|
||||||
UniversalFileType.file => 'application/octet-stream',
|
UniversalFileType.file => 'application/octet-stream',
|
||||||
},
|
},
|
||||||
|
onProgress: (progress, _) {
|
||||||
|
fileUploadProgress[localMessage.id]?[idx] = progress;
|
||||||
|
onProgress?.call(
|
||||||
|
localMessage.id,
|
||||||
|
fileUploadProgress[localMessage.id] ?? {},
|
||||||
|
);
|
||||||
|
},
|
||||||
).future;
|
).future;
|
||||||
if (cloudFile == null) {
|
if (cloudFile == null) {
|
||||||
throw ArgumentError('Failed to upload the file...');
|
throw ArgumentError('Failed to upload the file...');
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -23,6 +24,10 @@ import 'package:styled_widget/styled_widget.dart';
|
|||||||
import 'package:super_context_menu/super_context_menu.dart';
|
import 'package:super_context_menu/super_context_menu.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
import 'chat.dart';
|
import 'chat.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
part 'room.g.dart';
|
||||||
|
|
||||||
final messageRepositoryProvider =
|
final messageRepositoryProvider =
|
||||||
FutureProvider.family<MessageRepository, String>((ref, roomId) async {
|
FutureProvider.family<MessageRepository, String>((ref, roomId) async {
|
||||||
@ -33,29 +38,22 @@ final messageRepositoryProvider =
|
|||||||
return MessageRepository(room!, identity!, apiClient, database);
|
return MessageRepository(room!, identity!, apiClient, database);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Provider for messages with pagination
|
@riverpod
|
||||||
final messagesProvider = StateNotifierProvider.family<
|
class MessagesNotifier extends _$MessagesNotifier {
|
||||||
MessagesNotifier,
|
late final String _roomId;
|
||||||
AsyncValue<List<LocalChatMessage>>,
|
|
||||||
String
|
|
||||||
>((ref, roomId) => MessagesNotifier(ref, roomId));
|
|
||||||
|
|
||||||
class MessagesNotifier
|
|
||||||
extends StateNotifier<AsyncValue<List<LocalChatMessage>>> {
|
|
||||||
final Ref _ref;
|
|
||||||
final String _roomId;
|
|
||||||
int _currentPage = 0;
|
int _currentPage = 0;
|
||||||
static const int _pageSize = 20;
|
static const int _pageSize = 20;
|
||||||
bool _hasMore = true;
|
bool _hasMore = true;
|
||||||
|
|
||||||
MessagesNotifier(this._ref, this._roomId)
|
@override
|
||||||
: super(const AsyncValue.loading()) {
|
FutureOr<List<LocalChatMessage>> build(String roomId) async {
|
||||||
loadInitial();
|
_roomId = roomId;
|
||||||
|
return await loadInitial();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadInitial() async {
|
Future<List<LocalChatMessage>> loadInitial() async {
|
||||||
try {
|
try {
|
||||||
final repository = await _ref.read(
|
final repository = await ref.read(
|
||||||
messageRepositoryProvider(_roomId).future,
|
messageRepositoryProvider(_roomId).future,
|
||||||
);
|
);
|
||||||
final synced = await repository.syncMessages();
|
final synced = await repository.syncMessages();
|
||||||
@ -64,11 +62,11 @@ class MessagesNotifier
|
|||||||
take: _pageSize,
|
take: _pageSize,
|
||||||
synced: synced,
|
synced: synced,
|
||||||
);
|
);
|
||||||
state = AsyncValue.data(messages);
|
|
||||||
_currentPage = 0;
|
_currentPage = 0;
|
||||||
_hasMore = messages.length == _pageSize;
|
_hasMore = messages.length == _pageSize;
|
||||||
} catch (e, stack) {
|
return messages;
|
||||||
state = AsyncValue.error(e, stack);
|
} catch (_) {
|
||||||
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,7 +76,7 @@ class MessagesNotifier
|
|||||||
try {
|
try {
|
||||||
final currentMessages = state.value ?? [];
|
final currentMessages = state.value ?? [];
|
||||||
_currentPage++;
|
_currentPage++;
|
||||||
final repository = await _ref.read(
|
final repository = await ref.read(
|
||||||
messageRepositoryProvider(_roomId).future,
|
messageRepositoryProvider(_roomId).future,
|
||||||
);
|
);
|
||||||
final newMessages = await repository.listMessages(
|
final newMessages = await repository.listMessages(
|
||||||
@ -100,61 +98,49 @@ class MessagesNotifier
|
|||||||
Future<void> sendMessage(
|
Future<void> sendMessage(
|
||||||
String content,
|
String content,
|
||||||
List<UniversalFile> attachments, {
|
List<UniversalFile> attachments, {
|
||||||
SnChatMessage? replyingTo,
|
|
||||||
SnChatMessage? forwardingTo,
|
|
||||||
SnChatMessage? editingTo,
|
SnChatMessage? editingTo,
|
||||||
|
SnChatMessage? forwardingTo,
|
||||||
|
SnChatMessage? replyingTo,
|
||||||
|
Function(String, Map<int, double>)? onProgress,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final repository = await _ref.read(
|
final repository = await ref.read(
|
||||||
messageRepositoryProvider(_roomId).future,
|
messageRepositoryProvider(_roomId).future,
|
||||||
);
|
);
|
||||||
|
final baseUrl = ref.read(serverUrlProvider);
|
||||||
final nonce = const Uuid().v4();
|
|
||||||
|
|
||||||
final baseUrl = _ref.read(serverUrlProvider);
|
|
||||||
final atk = await getFreshAtk(
|
final atk = await getFreshAtk(
|
||||||
_ref.watch(tokenPairProvider),
|
ref.watch(tokenPairProvider),
|
||||||
baseUrl,
|
baseUrl,
|
||||||
onRefreshed: (atk, rtk) {
|
onRefreshed: (atk, rtk) {
|
||||||
setTokenPair(_ref.watch(sharedPreferencesProvider), atk, rtk);
|
setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk);
|
||||||
_ref.invalidate(tokenPairProvider);
|
ref.invalidate(tokenPairProvider);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (atk == null) throw Exception("Unauthorized");
|
if (atk == null) throw ArgumentError('Access token is null');
|
||||||
|
|
||||||
LocalChatMessage? pendingMessage;
|
final currentMessages = state.value ?? [];
|
||||||
final messageTask = repository.sendMessage(
|
await repository.sendMessage(
|
||||||
atk,
|
atk,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
_roomId,
|
_roomId,
|
||||||
content,
|
content,
|
||||||
nonce,
|
const Uuid().v4(),
|
||||||
attachments: attachments,
|
attachments: attachments,
|
||||||
replyingTo: replyingTo,
|
|
||||||
forwardingTo: forwardingTo,
|
|
||||||
editingTo: editingTo,
|
editingTo: editingTo,
|
||||||
|
forwardingTo: forwardingTo,
|
||||||
|
replyingTo: replyingTo,
|
||||||
onPending: (pending) {
|
onPending: (pending) {
|
||||||
pendingMessage = pending;
|
|
||||||
final currentMessages = state.value ?? [];
|
|
||||||
state = AsyncValue.data([pending, ...currentMessages]);
|
state = AsyncValue.data([pending, ...currentMessages]);
|
||||||
},
|
},
|
||||||
|
onProgress: onProgress,
|
||||||
);
|
);
|
||||||
|
|
||||||
final message = await messageTask;
|
// Refresh messages
|
||||||
|
final messages = await repository.listMessages(
|
||||||
final updatedMessages = state.value ?? [];
|
offset: 0,
|
||||||
if (pendingMessage != null) {
|
take: _pageSize,
|
||||||
final index = updatedMessages.indexWhere(
|
);
|
||||||
(m) => m.id == pendingMessage!.id,
|
state = AsyncValue.data(messages);
|
||||||
);
|
|
||||||
if (index >= 0) {
|
|
||||||
final newList = [...updatedMessages];
|
|
||||||
newList[index] = message;
|
|
||||||
state = AsyncValue.data(newList);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
state = AsyncValue.data([message, ...updatedMessages]);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showErrorAlert(err);
|
showErrorAlert(err);
|
||||||
}
|
}
|
||||||
@ -162,7 +148,7 @@ class MessagesNotifier
|
|||||||
|
|
||||||
Future<void> retryMessage(String pendingMessageId) async {
|
Future<void> retryMessage(String pendingMessageId) async {
|
||||||
try {
|
try {
|
||||||
final repository = await _ref.read(
|
final repository = await ref.read(
|
||||||
messageRepositoryProvider(_roomId).future,
|
messageRepositoryProvider(_roomId).future,
|
||||||
);
|
);
|
||||||
final updatedMessage = await repository.retryMessage(pendingMessageId);
|
final updatedMessage = await repository.retryMessage(pendingMessageId);
|
||||||
@ -182,7 +168,7 @@ class MessagesNotifier
|
|||||||
|
|
||||||
Future<void> receiveMessage(SnChatMessage remoteMessage) async {
|
Future<void> receiveMessage(SnChatMessage remoteMessage) async {
|
||||||
try {
|
try {
|
||||||
final repository = await _ref.read(
|
final repository = await ref.read(
|
||||||
messageRepositoryProvider(_roomId).future,
|
messageRepositoryProvider(_roomId).future,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -217,7 +203,7 @@ class MessagesNotifier
|
|||||||
|
|
||||||
Future<void> receiveMessageUpdate(SnChatMessage remoteMessage) async {
|
Future<void> receiveMessageUpdate(SnChatMessage remoteMessage) async {
|
||||||
try {
|
try {
|
||||||
final repository = await _ref.read(
|
final repository = await ref.read(
|
||||||
messageRepositoryProvider(_roomId).future,
|
messageRepositoryProvider(_roomId).future,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -246,7 +232,7 @@ class MessagesNotifier
|
|||||||
|
|
||||||
Future<void> receiveMessageDeletion(String messageId) async {
|
Future<void> receiveMessageDeletion(String messageId) async {
|
||||||
try {
|
try {
|
||||||
final repository = await _ref.read(
|
final repository = await ref.read(
|
||||||
messageRepositoryProvider(_roomId).future,
|
messageRepositoryProvider(_roomId).future,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -265,41 +251,9 @@ class MessagesNotifier
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateMessage(
|
|
||||||
String messageId,
|
|
||||||
String content, {
|
|
||||||
List<SnCloudFile>? attachments,
|
|
||||||
Map<String, dynamic>? meta,
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
final repository = await _ref.read(
|
|
||||||
messageRepositoryProvider(_roomId).future,
|
|
||||||
);
|
|
||||||
|
|
||||||
final updatedMessage = await repository.updateMessage(
|
|
||||||
messageId,
|
|
||||||
content,
|
|
||||||
attachments: attachments,
|
|
||||||
meta: meta,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update the message in the list
|
|
||||||
final currentMessages = state.value ?? [];
|
|
||||||
final index = currentMessages.indexWhere((m) => m.id == messageId);
|
|
||||||
|
|
||||||
if (index >= 0) {
|
|
||||||
final newList = [...currentMessages];
|
|
||||||
newList[index] = updatedMessage;
|
|
||||||
state = AsyncValue.data(newList);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
showErrorAlert(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> deleteMessage(String messageId) async {
|
Future<void> deleteMessage(String messageId) async {
|
||||||
try {
|
try {
|
||||||
final repository = await _ref.read(
|
final repository = await ref.read(
|
||||||
messageRepositoryProvider(_roomId).future,
|
messageRepositoryProvider(_roomId).future,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -320,7 +274,7 @@ class MessagesNotifier
|
|||||||
|
|
||||||
Future<LocalChatMessage?> fetchMessageById(String messageId) async {
|
Future<LocalChatMessage?> fetchMessageById(String messageId) async {
|
||||||
try {
|
try {
|
||||||
final repository = await _ref.read(
|
final repository = await ref.read(
|
||||||
messageRepositoryProvider(_roomId).future,
|
messageRepositoryProvider(_roomId).future,
|
||||||
);
|
);
|
||||||
return await repository.getMessageById(messageId);
|
return await repository.getMessageById(messageId);
|
||||||
@ -340,8 +294,8 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final chatRoom = ref.watch(chatroomProvider(id));
|
final chatRoom = ref.watch(chatroomProvider(id));
|
||||||
final chatIdentity = ref.watch(chatroomIdentityProvider(id));
|
final chatIdentity = ref.watch(chatroomIdentityProvider(id));
|
||||||
final messages = ref.watch(messagesProvider(id));
|
final messages = ref.watch(messagesNotifierProvider(id));
|
||||||
final messagesNotifier = ref.read(messagesProvider(id).notifier);
|
final messagesNotifier = ref.read(messagesNotifierProvider(id).notifier);
|
||||||
final ws = ref.watch(websocketProvider);
|
final ws = ref.watch(websocketProvider);
|
||||||
|
|
||||||
final messageController = useTextEditingController();
|
final messageController = useTextEditingController();
|
||||||
@ -350,6 +304,8 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
final messageReplyingTo = useState<SnChatMessage?>(null);
|
final messageReplyingTo = useState<SnChatMessage?>(null);
|
||||||
final messageForwardingTo = useState<SnChatMessage?>(null);
|
final messageForwardingTo = useState<SnChatMessage?>(null);
|
||||||
final messageEditingTo = useState<SnChatMessage?>(null);
|
final messageEditingTo = useState<SnChatMessage?>(null);
|
||||||
|
final attachments = useState<List<UniversalFile>>([]);
|
||||||
|
final attachmentProgress = useState<Map<String, Map<int, double>>>({});
|
||||||
|
|
||||||
// Add scroll listener for pagination
|
// Add scroll listener for pagination
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
@ -385,8 +341,6 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
return () => subscription.cancel();
|
return () => subscription.cancel();
|
||||||
}, [ws, chatRoom]);
|
}, [ws, chatRoom]);
|
||||||
|
|
||||||
final attachments = useState<List<UniversalFile>>([]);
|
|
||||||
|
|
||||||
Future<void> pickPhotoMedia() async {
|
Future<void> pickPhotoMedia() async {
|
||||||
final result = await ref
|
final result = await ref
|
||||||
.watch(imagePickerProvider)
|
.watch(imagePickerProvider)
|
||||||
@ -420,6 +374,12 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
editingTo: messageEditingTo.value,
|
editingTo: messageEditingTo.value,
|
||||||
forwardingTo: messageForwardingTo.value,
|
forwardingTo: messageForwardingTo.value,
|
||||||
replyingTo: messageReplyingTo.value,
|
replyingTo: messageReplyingTo.value,
|
||||||
|
onProgress: (messageId, progress) {
|
||||||
|
attachmentProgress.value = {
|
||||||
|
...attachmentProgress.value,
|
||||||
|
messageId: progress,
|
||||||
|
};
|
||||||
|
},
|
||||||
);
|
);
|
||||||
messageController.clear();
|
messageController.clear();
|
||||||
messageEditingTo.value = null;
|
messageEditingTo.value = null;
|
||||||
@ -542,12 +502,15 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
message.toRemoteMessage();
|
message.toRemoteMessage();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
progress:
|
||||||
|
attachmentProgress.value[message.id],
|
||||||
),
|
),
|
||||||
loading:
|
loading:
|
||||||
() => _MessageBubble(
|
() => _MessageBubble(
|
||||||
message: message,
|
message: message,
|
||||||
isCurrentUser: false,
|
isCurrentUser: false,
|
||||||
onAction: null,
|
onAction: null,
|
||||||
|
progress: null,
|
||||||
),
|
),
|
||||||
error: (_, __) => const SizedBox.shrink(),
|
error: (_, __) => const SizedBox.shrink(),
|
||||||
);
|
);
|
||||||
@ -804,11 +767,13 @@ class _MessageBubble extends HookConsumerWidget {
|
|||||||
final LocalChatMessage message;
|
final LocalChatMessage message;
|
||||||
final bool isCurrentUser;
|
final bool isCurrentUser;
|
||||||
final Function(String action)? onAction;
|
final Function(String action)? onAction;
|
||||||
|
final Map<int, double>? progress;
|
||||||
|
|
||||||
const _MessageBubble({
|
const _MessageBubble({
|
||||||
required this.message,
|
required this.message,
|
||||||
required this.isCurrentUser,
|
required this.isCurrentUser,
|
||||||
required this.onAction,
|
required this.onAction,
|
||||||
|
required this.progress,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -914,9 +879,58 @@ class _MessageBubble extends HookConsumerWidget {
|
|||||||
style: TextStyle(color: textColor),
|
style: TextStyle(color: textColor),
|
||||||
),
|
),
|
||||||
if (message.toRemoteMessage().attachments.isNotEmpty)
|
if (message.toRemoteMessage().attachments.isNotEmpty)
|
||||||
CloudFileList(
|
Column(
|
||||||
files: message.toRemoteMessage().attachments,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
CloudFileList(
|
||||||
|
files: message.toRemoteMessage().attachments,
|
||||||
|
maxWidth: MediaQuery.of(context).size.width * 0.8,
|
||||||
|
),
|
||||||
|
],
|
||||||
).padding(top: 4),
|
).padding(top: 4),
|
||||||
|
if (progress != null && progress!.isNotEmpty)
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
if ((message
|
||||||
|
.toRemoteMessage()
|
||||||
|
.content
|
||||||
|
?.isNotEmpty ??
|
||||||
|
false))
|
||||||
|
const Gap(0),
|
||||||
|
for (var entry in progress!.entries)
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'fileUploadingProgress'.tr(
|
||||||
|
args: [
|
||||||
|
(entry.key + 1).toString(),
|
||||||
|
entry.value.toStringAsFixed(1),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: textColor.withOpacity(0.8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(4),
|
||||||
|
LinearProgressIndicator(
|
||||||
|
value: entry.value / 100,
|
||||||
|
backgroundColor:
|
||||||
|
Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.surfaceVariant,
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(
|
||||||
|
Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Gap(0),
|
||||||
|
],
|
||||||
|
),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
Row(
|
Row(
|
||||||
spacing: 4,
|
spacing: 4,
|
||||||
@ -978,7 +992,7 @@ class _MessageBubble extends HookConsumerWidget {
|
|||||||
(context, ref, _) => GestureDetector(
|
(context, ref, _) => GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
ref
|
ref
|
||||||
.read(messagesProvider(message.roomId).notifier)
|
.read(messagesNotifierProvider(message.roomId).notifier)
|
||||||
.retryMessage(message.id);
|
.retryMessage(message.id);
|
||||||
},
|
},
|
||||||
child: const Icon(
|
child: const Icon(
|
||||||
@ -1006,7 +1020,7 @@ class _MessageQuoteWidget extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final messagesNotifier = ref.watch(
|
final messagesNotifier = ref.watch(
|
||||||
messagesProvider(message.roomId).notifier,
|
messagesNotifierProvider(message.roomId).notifier,
|
||||||
);
|
);
|
||||||
|
|
||||||
return FutureBuilder<LocalChatMessage?>(
|
return FutureBuilder<LocalChatMessage?>(
|
||||||
|
179
lib/screens/chat/room.g.dart
Normal file
179
lib/screens/chat/room.g.dart
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'room.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$messagesNotifierHash() => r'71a9fc1c6d024f6203f06225384c19335b9b6f2c';
|
||||||
|
|
||||||
|
/// 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _$MessagesNotifier
|
||||||
|
extends BuildlessAutoDisposeAsyncNotifier<List<LocalChatMessage>> {
|
||||||
|
late final String roomId;
|
||||||
|
|
||||||
|
FutureOr<List<LocalChatMessage>> build(String roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [MessagesNotifier].
|
||||||
|
@ProviderFor(MessagesNotifier)
|
||||||
|
const messagesNotifierProvider = MessagesNotifierFamily();
|
||||||
|
|
||||||
|
/// See also [MessagesNotifier].
|
||||||
|
class MessagesNotifierFamily
|
||||||
|
extends Family<AsyncValue<List<LocalChatMessage>>> {
|
||||||
|
/// See also [MessagesNotifier].
|
||||||
|
const MessagesNotifierFamily();
|
||||||
|
|
||||||
|
/// See also [MessagesNotifier].
|
||||||
|
MessagesNotifierProvider call(String roomId) {
|
||||||
|
return MessagesNotifierProvider(roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
MessagesNotifierProvider getProviderOverride(
|
||||||
|
covariant MessagesNotifierProvider provider,
|
||||||
|
) {
|
||||||
|
return call(provider.roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||||
|
_allTransitiveDependencies;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get name => r'messagesNotifierProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [MessagesNotifier].
|
||||||
|
class MessagesNotifierProvider
|
||||||
|
extends
|
||||||
|
AutoDisposeAsyncNotifierProviderImpl<
|
||||||
|
MessagesNotifier,
|
||||||
|
List<LocalChatMessage>
|
||||||
|
> {
|
||||||
|
/// See also [MessagesNotifier].
|
||||||
|
MessagesNotifierProvider(String roomId)
|
||||||
|
: this._internal(
|
||||||
|
() => MessagesNotifier()..roomId = roomId,
|
||||||
|
from: messagesNotifierProvider,
|
||||||
|
name: r'messagesNotifierProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$messagesNotifierHash,
|
||||||
|
dependencies: MessagesNotifierFamily._dependencies,
|
||||||
|
allTransitiveDependencies:
|
||||||
|
MessagesNotifierFamily._allTransitiveDependencies,
|
||||||
|
roomId: roomId,
|
||||||
|
);
|
||||||
|
|
||||||
|
MessagesNotifierProvider._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
|
||||||
|
FutureOr<List<LocalChatMessage>> runNotifierBuild(
|
||||||
|
covariant MessagesNotifier notifier,
|
||||||
|
) {
|
||||||
|
return notifier.build(roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Override overrideWith(MessagesNotifier Function() create) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: MessagesNotifierProvider._internal(
|
||||||
|
() => create()..roomId = roomId,
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
roomId: roomId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AutoDisposeAsyncNotifierProviderElement<
|
||||||
|
MessagesNotifier,
|
||||||
|
List<LocalChatMessage>
|
||||||
|
>
|
||||||
|
createElement() {
|
||||||
|
return _MessagesNotifierProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is MessagesNotifierProvider && other.roomId == roomId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, roomId.hashCode);
|
||||||
|
|
||||||
|
return _SystemHash.finish(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
mixin MessagesNotifierRef
|
||||||
|
on AutoDisposeAsyncNotifierProviderRef<List<LocalChatMessage>> {
|
||||||
|
/// The parameter `roomId` of this provider.
|
||||||
|
String get roomId;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MessagesNotifierProviderElement
|
||||||
|
extends
|
||||||
|
AutoDisposeAsyncNotifierProviderElement<
|
||||||
|
MessagesNotifier,
|
||||||
|
List<LocalChatMessage>
|
||||||
|
>
|
||||||
|
with MessagesNotifierRef {
|
||||||
|
_MessagesNotifierProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get roomId => (origin as MessagesNotifierProvider).roomId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:island/models/file.dart';
|
import 'package:island/models/file.dart';
|
||||||
@ -7,7 +9,13 @@ import 'package:styled_widget/styled_widget.dart';
|
|||||||
class CloudFileList extends StatelessWidget {
|
class CloudFileList extends StatelessWidget {
|
||||||
final List<SnCloudFile> files;
|
final List<SnCloudFile> files;
|
||||||
final double maxHeight;
|
final double maxHeight;
|
||||||
const CloudFileList({super.key, required this.files, this.maxHeight = 360});
|
final double maxWidth;
|
||||||
|
const CloudFileList({
|
||||||
|
super.key,
|
||||||
|
required this.files,
|
||||||
|
this.maxHeight = 360,
|
||||||
|
this.maxWidth = double.infinity,
|
||||||
|
});
|
||||||
|
|
||||||
double calculateAspectRatio() {
|
double calculateAspectRatio() {
|
||||||
double total = 0;
|
double total = 0;
|
||||||
@ -44,14 +52,14 @@ class CloudFileList extends StatelessWidget {
|
|||||||
|
|
||||||
if (allImages) {
|
if (allImages) {
|
||||||
return ConstrainedBox(
|
return ConstrainedBox(
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(maxHeight: maxHeight, minWidth: maxWidth),
|
||||||
maxHeight: maxHeight,
|
|
||||||
minWidth: double.infinity,
|
|
||||||
),
|
|
||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: calculateAspectRatio(),
|
aspectRatio: calculateAspectRatio(),
|
||||||
child: CarouselView(
|
child: CarouselView(
|
||||||
itemExtent: MediaQuery.of(context).size.width * 0.85,
|
itemExtent: math.min(
|
||||||
|
MediaQuery.of(context).size.width * 0.85,
|
||||||
|
maxWidth * 0.85,
|
||||||
|
),
|
||||||
itemSnapping: true,
|
itemSnapping: true,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||||
@ -63,10 +71,7 @@ class CloudFileList extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return ConstrainedBox(
|
return ConstrainedBox(
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(maxHeight: maxHeight, minWidth: maxWidth),
|
||||||
maxHeight: maxHeight,
|
|
||||||
minWidth: double.infinity,
|
|
||||||
),
|
|
||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: calculateAspectRatio(),
|
aspectRatio: calculateAspectRatio(),
|
||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user