✨ Chat message uploading file progress
This commit is contained in:
parent
81d5083908
commit
3737de6936
@ -236,5 +236,6 @@
|
||||
},
|
||||
"levelingProgress": "Leveling Progress",
|
||||
"levelingProgressExperience": "{} EXP",
|
||||
"levelingProgressLevel": "Level {}"
|
||||
"levelingProgressLevel": "Level {}",
|
||||
"fileUploadingProgress": "Uploading file #{}: {}%"
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:island/models/chat.dart';
|
||||
import 'package:island/models/file.dart';
|
||||
|
||||
class ChatMessages extends Table {
|
||||
TextColumn get id => text()();
|
||||
@ -23,6 +24,7 @@ class LocalChatMessage {
|
||||
final DateTime createdAt;
|
||||
MessageStatus status;
|
||||
final String? nonce;
|
||||
List<UniversalFile>? localAttachments;
|
||||
|
||||
LocalChatMessage({
|
||||
required this.id,
|
||||
@ -30,8 +32,9 @@ class LocalChatMessage {
|
||||
required this.senderId,
|
||||
required this.data,
|
||||
required this.createdAt,
|
||||
required this.nonce,
|
||||
required this.status,
|
||||
this.nonce,
|
||||
this.localAttachments,
|
||||
});
|
||||
|
||||
SnChatMessage toRemoteMessage() {
|
||||
|
@ -14,6 +14,7 @@ class MessageRepository {
|
||||
final AppDatabase _database;
|
||||
|
||||
final Map<String, LocalChatMessage> pendingMessages = {};
|
||||
final Map<String, Map<int, double>> fileUploadProgress = {};
|
||||
|
||||
MessageRepository(this.room, this.identity, this._apiClient, this._database);
|
||||
|
||||
@ -181,6 +182,7 @@ class MessageRepository {
|
||||
SnChatMessage? forwardingTo,
|
||||
SnChatMessage? editingTo,
|
||||
Function(LocalChatMessage)? onPending,
|
||||
Function(String, Map<int, double>)? onProgress,
|
||||
}) async {
|
||||
// Generate a unique nonce for this message
|
||||
final nonce = const Uuid().v4();
|
||||
@ -204,6 +206,7 @@ class MessageRepository {
|
||||
|
||||
// Store in memory and database
|
||||
pendingMessages[localMessage.id] = localMessage;
|
||||
fileUploadProgress[localMessage.id] = {};
|
||||
await _database.saveMessage(_database.messageToCompanion(localMessage));
|
||||
onPending?.call(localMessage);
|
||||
|
||||
@ -225,6 +228,13 @@ class MessageRepository {
|
||||
UniversalFileType.audio => 'audio/unknown',
|
||||
UniversalFileType.file => 'application/octet-stream',
|
||||
},
|
||||
onProgress: (progress, _) {
|
||||
fileUploadProgress[localMessage.id]?[idx] = progress;
|
||||
onProgress?.call(
|
||||
localMessage.id,
|
||||
fileUploadProgress[localMessage.id] ?? {},
|
||||
);
|
||||
},
|
||||
).future;
|
||||
if (cloudFile == null) {
|
||||
throw ArgumentError('Failed to upload the file...');
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.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:uuid/uuid.dart';
|
||||
import 'chat.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'room.g.dart';
|
||||
|
||||
final messageRepositoryProvider =
|
||||
FutureProvider.family<MessageRepository, String>((ref, roomId) async {
|
||||
@ -33,29 +38,22 @@ final messageRepositoryProvider =
|
||||
return MessageRepository(room!, identity!, apiClient, database);
|
||||
});
|
||||
|
||||
// Provider for messages with pagination
|
||||
final messagesProvider = StateNotifierProvider.family<
|
||||
MessagesNotifier,
|
||||
AsyncValue<List<LocalChatMessage>>,
|
||||
String
|
||||
>((ref, roomId) => MessagesNotifier(ref, roomId));
|
||||
|
||||
class MessagesNotifier
|
||||
extends StateNotifier<AsyncValue<List<LocalChatMessage>>> {
|
||||
final Ref _ref;
|
||||
final String _roomId;
|
||||
@riverpod
|
||||
class MessagesNotifier extends _$MessagesNotifier {
|
||||
late final String _roomId;
|
||||
int _currentPage = 0;
|
||||
static const int _pageSize = 20;
|
||||
bool _hasMore = true;
|
||||
|
||||
MessagesNotifier(this._ref, this._roomId)
|
||||
: super(const AsyncValue.loading()) {
|
||||
loadInitial();
|
||||
@override
|
||||
FutureOr<List<LocalChatMessage>> build(String roomId) async {
|
||||
_roomId = roomId;
|
||||
return await loadInitial();
|
||||
}
|
||||
|
||||
Future<void> loadInitial() async {
|
||||
Future<List<LocalChatMessage>> loadInitial() async {
|
||||
try {
|
||||
final repository = await _ref.read(
|
||||
final repository = await ref.read(
|
||||
messageRepositoryProvider(_roomId).future,
|
||||
);
|
||||
final synced = await repository.syncMessages();
|
||||
@ -64,11 +62,11 @@ class MessagesNotifier
|
||||
take: _pageSize,
|
||||
synced: synced,
|
||||
);
|
||||
state = AsyncValue.data(messages);
|
||||
_currentPage = 0;
|
||||
_hasMore = messages.length == _pageSize;
|
||||
} catch (e, stack) {
|
||||
state = AsyncValue.error(e, stack);
|
||||
return messages;
|
||||
} catch (_) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,7 +76,7 @@ class MessagesNotifier
|
||||
try {
|
||||
final currentMessages = state.value ?? [];
|
||||
_currentPage++;
|
||||
final repository = await _ref.read(
|
||||
final repository = await ref.read(
|
||||
messageRepositoryProvider(_roomId).future,
|
||||
);
|
||||
final newMessages = await repository.listMessages(
|
||||
@ -100,61 +98,49 @@ class MessagesNotifier
|
||||
Future<void> sendMessage(
|
||||
String content,
|
||||
List<UniversalFile> attachments, {
|
||||
SnChatMessage? replyingTo,
|
||||
SnChatMessage? forwardingTo,
|
||||
SnChatMessage? editingTo,
|
||||
SnChatMessage? forwardingTo,
|
||||
SnChatMessage? replyingTo,
|
||||
Function(String, Map<int, double>)? onProgress,
|
||||
}) async {
|
||||
try {
|
||||
final repository = await _ref.read(
|
||||
final repository = await ref.read(
|
||||
messageRepositoryProvider(_roomId).future,
|
||||
);
|
||||
|
||||
final nonce = const Uuid().v4();
|
||||
|
||||
final baseUrl = _ref.read(serverUrlProvider);
|
||||
final baseUrl = ref.read(serverUrlProvider);
|
||||
final atk = await getFreshAtk(
|
||||
_ref.watch(tokenPairProvider),
|
||||
ref.watch(tokenPairProvider),
|
||||
baseUrl,
|
||||
onRefreshed: (atk, rtk) {
|
||||
setTokenPair(_ref.watch(sharedPreferencesProvider), atk, rtk);
|
||||
_ref.invalidate(tokenPairProvider);
|
||||
setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk);
|
||||
ref.invalidate(tokenPairProvider);
|
||||
},
|
||||
);
|
||||
if (atk == null) throw Exception("Unauthorized");
|
||||
if (atk == null) throw ArgumentError('Access token is null');
|
||||
|
||||
LocalChatMessage? pendingMessage;
|
||||
final messageTask = repository.sendMessage(
|
||||
final currentMessages = state.value ?? [];
|
||||
await repository.sendMessage(
|
||||
atk,
|
||||
baseUrl,
|
||||
_roomId,
|
||||
content,
|
||||
nonce,
|
||||
const Uuid().v4(),
|
||||
attachments: attachments,
|
||||
replyingTo: replyingTo,
|
||||
forwardingTo: forwardingTo,
|
||||
editingTo: editingTo,
|
||||
forwardingTo: forwardingTo,
|
||||
replyingTo: replyingTo,
|
||||
onPending: (pending) {
|
||||
pendingMessage = pending;
|
||||
final currentMessages = state.value ?? [];
|
||||
state = AsyncValue.data([pending, ...currentMessages]);
|
||||
},
|
||||
onProgress: onProgress,
|
||||
);
|
||||
|
||||
final message = await messageTask;
|
||||
|
||||
final updatedMessages = state.value ?? [];
|
||||
if (pendingMessage != null) {
|
||||
final index = updatedMessages.indexWhere(
|
||||
(m) => m.id == pendingMessage!.id,
|
||||
// Refresh messages
|
||||
final messages = await repository.listMessages(
|
||||
offset: 0,
|
||||
take: _pageSize,
|
||||
);
|
||||
if (index >= 0) {
|
||||
final newList = [...updatedMessages];
|
||||
newList[index] = message;
|
||||
state = AsyncValue.data(newList);
|
||||
}
|
||||
} else {
|
||||
state = AsyncValue.data([message, ...updatedMessages]);
|
||||
}
|
||||
state = AsyncValue.data(messages);
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
}
|
||||
@ -162,7 +148,7 @@ class MessagesNotifier
|
||||
|
||||
Future<void> retryMessage(String pendingMessageId) async {
|
||||
try {
|
||||
final repository = await _ref.read(
|
||||
final repository = await ref.read(
|
||||
messageRepositoryProvider(_roomId).future,
|
||||
);
|
||||
final updatedMessage = await repository.retryMessage(pendingMessageId);
|
||||
@ -182,7 +168,7 @@ class MessagesNotifier
|
||||
|
||||
Future<void> receiveMessage(SnChatMessage remoteMessage) async {
|
||||
try {
|
||||
final repository = await _ref.read(
|
||||
final repository = await ref.read(
|
||||
messageRepositoryProvider(_roomId).future,
|
||||
);
|
||||
|
||||
@ -217,7 +203,7 @@ class MessagesNotifier
|
||||
|
||||
Future<void> receiveMessageUpdate(SnChatMessage remoteMessage) async {
|
||||
try {
|
||||
final repository = await _ref.read(
|
||||
final repository = await ref.read(
|
||||
messageRepositoryProvider(_roomId).future,
|
||||
);
|
||||
|
||||
@ -246,7 +232,7 @@ class MessagesNotifier
|
||||
|
||||
Future<void> receiveMessageDeletion(String messageId) async {
|
||||
try {
|
||||
final repository = await _ref.read(
|
||||
final repository = await ref.read(
|
||||
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 {
|
||||
try {
|
||||
final repository = await _ref.read(
|
||||
final repository = await ref.read(
|
||||
messageRepositoryProvider(_roomId).future,
|
||||
);
|
||||
|
||||
@ -320,7 +274,7 @@ class MessagesNotifier
|
||||
|
||||
Future<LocalChatMessage?> fetchMessageById(String messageId) async {
|
||||
try {
|
||||
final repository = await _ref.read(
|
||||
final repository = await ref.read(
|
||||
messageRepositoryProvider(_roomId).future,
|
||||
);
|
||||
return await repository.getMessageById(messageId);
|
||||
@ -340,8 +294,8 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final chatRoom = ref.watch(chatroomProvider(id));
|
||||
final chatIdentity = ref.watch(chatroomIdentityProvider(id));
|
||||
final messages = ref.watch(messagesProvider(id));
|
||||
final messagesNotifier = ref.read(messagesProvider(id).notifier);
|
||||
final messages = ref.watch(messagesNotifierProvider(id));
|
||||
final messagesNotifier = ref.read(messagesNotifierProvider(id).notifier);
|
||||
final ws = ref.watch(websocketProvider);
|
||||
|
||||
final messageController = useTextEditingController();
|
||||
@ -350,6 +304,8 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
final messageReplyingTo = useState<SnChatMessage?>(null);
|
||||
final messageForwardingTo = 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
|
||||
useEffect(() {
|
||||
@ -385,8 +341,6 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
return () => subscription.cancel();
|
||||
}, [ws, chatRoom]);
|
||||
|
||||
final attachments = useState<List<UniversalFile>>([]);
|
||||
|
||||
Future<void> pickPhotoMedia() async {
|
||||
final result = await ref
|
||||
.watch(imagePickerProvider)
|
||||
@ -420,6 +374,12 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
editingTo: messageEditingTo.value,
|
||||
forwardingTo: messageForwardingTo.value,
|
||||
replyingTo: messageReplyingTo.value,
|
||||
onProgress: (messageId, progress) {
|
||||
attachmentProgress.value = {
|
||||
...attachmentProgress.value,
|
||||
messageId: progress,
|
||||
};
|
||||
},
|
||||
);
|
||||
messageController.clear();
|
||||
messageEditingTo.value = null;
|
||||
@ -542,12 +502,15 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
message.toRemoteMessage();
|
||||
}
|
||||
},
|
||||
progress:
|
||||
attachmentProgress.value[message.id],
|
||||
),
|
||||
loading:
|
||||
() => _MessageBubble(
|
||||
message: message,
|
||||
isCurrentUser: false,
|
||||
onAction: null,
|
||||
progress: null,
|
||||
),
|
||||
error: (_, __) => const SizedBox.shrink(),
|
||||
);
|
||||
@ -804,11 +767,13 @@ class _MessageBubble extends HookConsumerWidget {
|
||||
final LocalChatMessage message;
|
||||
final bool isCurrentUser;
|
||||
final Function(String action)? onAction;
|
||||
final Map<int, double>? progress;
|
||||
|
||||
const _MessageBubble({
|
||||
required this.message,
|
||||
required this.isCurrentUser,
|
||||
required this.onAction,
|
||||
required this.progress,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -914,9 +879,58 @@ class _MessageBubble extends HookConsumerWidget {
|
||||
style: TextStyle(color: textColor),
|
||||
),
|
||||
if (message.toRemoteMessage().attachments.isNotEmpty)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
CloudFileList(
|
||||
files: message.toRemoteMessage().attachments,
|
||||
maxWidth: MediaQuery.of(context).size.width * 0.8,
|
||||
),
|
||||
],
|
||||
).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),
|
||||
Row(
|
||||
spacing: 4,
|
||||
@ -978,7 +992,7 @@ class _MessageBubble extends HookConsumerWidget {
|
||||
(context, ref, _) => GestureDetector(
|
||||
onTap: () {
|
||||
ref
|
||||
.read(messagesProvider(message.roomId).notifier)
|
||||
.read(messagesNotifierProvider(message.roomId).notifier)
|
||||
.retryMessage(message.id);
|
||||
},
|
||||
child: const Icon(
|
||||
@ -1006,7 +1020,7 @@ class _MessageQuoteWidget extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final messagesNotifier = ref.watch(
|
||||
messagesProvider(message.roomId).notifier,
|
||||
messagesNotifierProvider(message.roomId).notifier,
|
||||
);
|
||||
|
||||
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:gap/gap.dart';
|
||||
import 'package:island/models/file.dart';
|
||||
@ -7,7 +9,13 @@ import 'package:styled_widget/styled_widget.dart';
|
||||
class CloudFileList extends StatelessWidget {
|
||||
final List<SnCloudFile> files;
|
||||
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 total = 0;
|
||||
@ -44,14 +52,14 @@ class CloudFileList extends StatelessWidget {
|
||||
|
||||
if (allImages) {
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: maxHeight,
|
||||
minWidth: double.infinity,
|
||||
),
|
||||
constraints: BoxConstraints(maxHeight: maxHeight, minWidth: maxWidth),
|
||||
child: AspectRatio(
|
||||
aspectRatio: calculateAspectRatio(),
|
||||
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,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||
@ -63,10 +71,7 @@ class CloudFileList extends StatelessWidget {
|
||||
}
|
||||
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: maxHeight,
|
||||
minWidth: double.infinity,
|
||||
),
|
||||
constraints: BoxConstraints(maxHeight: maxHeight, minWidth: maxWidth),
|
||||
child: AspectRatio(
|
||||
aspectRatio: calculateAspectRatio(),
|
||||
child: ListView.separated(
|
||||
|
Loading…
x
Reference in New Issue
Block a user