✨ Typing indicator
This commit is contained in:
parent
b2a118bbd0
commit
20e6cc4283
@ -353,5 +353,9 @@
|
||||
"authDeviceLabelTitle": "Edit Device Label",
|
||||
"authDeviceLabelHint": "Enter a name for this device",
|
||||
"authDeviceSwipeEditHint": "Swipe left to edit label",
|
||||
"authDeviceSwipeLogoutHint": "Swipe right to logout device"
|
||||
"authDeviceSwipeLogoutHint": "Swipe right to logout device",
|
||||
"typingHint": {
|
||||
"one": "{} is typing...",
|
||||
"other": "{} are typing..."
|
||||
}
|
||||
}
|
||||
|
@ -91,6 +91,7 @@ sealed class SnChatMember with _$SnChatMember {
|
||||
required int notify,
|
||||
required DateTime? joinedAt,
|
||||
required bool isBot,
|
||||
DateTime? lastTyped,
|
||||
}) = _SnChatMember;
|
||||
|
||||
factory SnChatMember.fromJson(Map<String, dynamic> json) =>
|
||||
|
@ -663,7 +663,7 @@ $SnChatMemberCopyWith<$Res> get sender {
|
||||
/// @nodoc
|
||||
mixin _$SnChatMember {
|
||||
|
||||
DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; String get id; String get chatRoomId; SnChatRoom? get chatRoom; String get accountId; SnAccount get account; String? get nick; int get role; int get notify; DateTime? get joinedAt; bool get isBot;
|
||||
DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; String get id; String get chatRoomId; SnChatRoom? get chatRoom; String get accountId; SnAccount get account; String? get nick; int get role; int get notify; DateTime? get joinedAt; bool get isBot; DateTime? get lastTyped;
|
||||
/// Create a copy of SnChatMember
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@ -676,16 +676,16 @@ $SnChatMemberCopyWith<SnChatMember> get copyWith => _$SnChatMemberCopyWithImpl<S
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatMember&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)&&(identical(other.chatRoom, chatRoom) || other.chatRoom == chatRoom)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.role, role) || other.role == role)&&(identical(other.notify, notify) || other.notify == notify)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.isBot, isBot) || other.isBot == isBot));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatMember&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)&&(identical(other.chatRoom, chatRoom) || other.chatRoom == chatRoom)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.role, role) || other.role == role)&&(identical(other.notify, notify) || other.notify == notify)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.isBot, isBot) || other.isBot == isBot)&&(identical(other.lastTyped, lastTyped) || other.lastTyped == lastTyped));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,chatRoom,accountId,account,nick,role,notify,joinedAt,isBot);
|
||||
int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,chatRoom,accountId,account,nick,role,notify,joinedAt,isBot,lastTyped);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnChatMember(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, chatRoomId: $chatRoomId, chatRoom: $chatRoom, accountId: $accountId, account: $account, nick: $nick, role: $role, notify: $notify, joinedAt: $joinedAt, isBot: $isBot)';
|
||||
return 'SnChatMember(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, chatRoomId: $chatRoomId, chatRoom: $chatRoom, accountId: $accountId, account: $account, nick: $nick, role: $role, notify: $notify, joinedAt: $joinedAt, isBot: $isBot, lastTyped: $lastTyped)';
|
||||
}
|
||||
|
||||
|
||||
@ -696,7 +696,7 @@ abstract mixin class $SnChatMemberCopyWith<$Res> {
|
||||
factory $SnChatMemberCopyWith(SnChatMember value, $Res Function(SnChatMember) _then) = _$SnChatMemberCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String chatRoomId, SnChatRoom? chatRoom, String accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, bool isBot
|
||||
DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String chatRoomId, SnChatRoom? chatRoom, String accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, bool isBot, DateTime? lastTyped
|
||||
});
|
||||
|
||||
|
||||
@ -713,7 +713,7 @@ class _$SnChatMemberCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of SnChatMember
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? chatRoomId = null,Object? chatRoom = freezed,Object? accountId = null,Object? account = null,Object? nick = freezed,Object? role = null,Object? notify = null,Object? joinedAt = freezed,Object? isBot = null,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? chatRoomId = null,Object? chatRoom = freezed,Object? accountId = null,Object? account = null,Object? nick = freezed,Object? role = null,Object? notify = null,Object? joinedAt = freezed,Object? isBot = null,Object? lastTyped = freezed,}) {
|
||||
return _then(_self.copyWith(
|
||||
createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||
@ -728,7 +728,8 @@ as String?,role: null == role ? _self.role : role // ignore: cast_nullable_to_no
|
||||
as int,notify: null == notify ? _self.notify : notify // ignore: cast_nullable_to_non_nullable
|
||||
as int,joinedAt: freezed == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,isBot: null == isBot ? _self.isBot : isBot // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
as bool,lastTyped: freezed == lastTyped ? _self.lastTyped : lastTyped // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
));
|
||||
}
|
||||
/// Create a copy of SnChatMember
|
||||
@ -760,7 +761,7 @@ $SnAccountCopyWith<$Res> get account {
|
||||
@JsonSerializable()
|
||||
|
||||
class _SnChatMember implements SnChatMember {
|
||||
const _SnChatMember({required this.createdAt, required this.updatedAt, required this.deletedAt, required this.id, required this.chatRoomId, required this.chatRoom, required this.accountId, required this.account, required this.nick, required this.role, required this.notify, required this.joinedAt, required this.isBot});
|
||||
const _SnChatMember({required this.createdAt, required this.updatedAt, required this.deletedAt, required this.id, required this.chatRoomId, required this.chatRoom, required this.accountId, required this.account, required this.nick, required this.role, required this.notify, required this.joinedAt, required this.isBot, this.lastTyped});
|
||||
factory _SnChatMember.fromJson(Map<String, dynamic> json) => _$SnChatMemberFromJson(json);
|
||||
|
||||
@override final DateTime createdAt;
|
||||
@ -776,6 +777,7 @@ class _SnChatMember implements SnChatMember {
|
||||
@override final int notify;
|
||||
@override final DateTime? joinedAt;
|
||||
@override final bool isBot;
|
||||
@override final DateTime? lastTyped;
|
||||
|
||||
/// Create a copy of SnChatMember
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@ -790,16 +792,16 @@ Map<String, dynamic> toJson() {
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatMember&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)&&(identical(other.chatRoom, chatRoom) || other.chatRoom == chatRoom)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.role, role) || other.role == role)&&(identical(other.notify, notify) || other.notify == notify)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.isBot, isBot) || other.isBot == isBot));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatMember&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)&&(identical(other.chatRoom, chatRoom) || other.chatRoom == chatRoom)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.role, role) || other.role == role)&&(identical(other.notify, notify) || other.notify == notify)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.isBot, isBot) || other.isBot == isBot)&&(identical(other.lastTyped, lastTyped) || other.lastTyped == lastTyped));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,chatRoom,accountId,account,nick,role,notify,joinedAt,isBot);
|
||||
int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,chatRoom,accountId,account,nick,role,notify,joinedAt,isBot,lastTyped);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnChatMember(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, chatRoomId: $chatRoomId, chatRoom: $chatRoom, accountId: $accountId, account: $account, nick: $nick, role: $role, notify: $notify, joinedAt: $joinedAt, isBot: $isBot)';
|
||||
return 'SnChatMember(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, chatRoomId: $chatRoomId, chatRoom: $chatRoom, accountId: $accountId, account: $account, nick: $nick, role: $role, notify: $notify, joinedAt: $joinedAt, isBot: $isBot, lastTyped: $lastTyped)';
|
||||
}
|
||||
|
||||
|
||||
@ -810,7 +812,7 @@ abstract mixin class _$SnChatMemberCopyWith<$Res> implements $SnChatMemberCopyWi
|
||||
factory _$SnChatMemberCopyWith(_SnChatMember value, $Res Function(_SnChatMember) _then) = __$SnChatMemberCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String chatRoomId, SnChatRoom? chatRoom, String accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, bool isBot
|
||||
DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String chatRoomId, SnChatRoom? chatRoom, String accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, bool isBot, DateTime? lastTyped
|
||||
});
|
||||
|
||||
|
||||
@ -827,7 +829,7 @@ class __$SnChatMemberCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of SnChatMember
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? chatRoomId = null,Object? chatRoom = freezed,Object? accountId = null,Object? account = null,Object? nick = freezed,Object? role = null,Object? notify = null,Object? joinedAt = freezed,Object? isBot = null,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? chatRoomId = null,Object? chatRoom = freezed,Object? accountId = null,Object? account = null,Object? nick = freezed,Object? role = null,Object? notify = null,Object? joinedAt = freezed,Object? isBot = null,Object? lastTyped = freezed,}) {
|
||||
return _then(_SnChatMember(
|
||||
createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||
@ -842,7 +844,8 @@ as String?,role: null == role ? _self.role : role // ignore: cast_nullable_to_no
|
||||
as int,notify: null == notify ? _self.notify : notify // ignore: cast_nullable_to_non_nullable
|
||||
as int,joinedAt: freezed == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,isBot: null == isBot ? _self.isBot : isBot // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
as bool,lastTyped: freezed == lastTyped ? _self.lastTyped : lastTyped // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -167,6 +167,10 @@ _SnChatMember _$SnChatMemberFromJson(Map<String, dynamic> json) =>
|
||||
? null
|
||||
: DateTime.parse(json['joined_at'] as String),
|
||||
isBot: json['is_bot'] as bool,
|
||||
lastTyped:
|
||||
json['last_typed'] == null
|
||||
? null
|
||||
: DateTime.parse(json['last_typed'] as String),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SnChatMemberToJson(_SnChatMember instance) =>
|
||||
@ -184,6 +188,7 @@ Map<String, dynamic> _$SnChatMemberToJson(_SnChatMember instance) =>
|
||||
'notify': instance.notify,
|
||||
'joined_at': instance.joinedAt?.toIso8601String(),
|
||||
'is_bot': instance.isBot,
|
||||
'last_typed': instance.lastTyped?.toIso8601String(),
|
||||
};
|
||||
|
||||
_SnChatSummary _$SnChatSummaryFromJson(Map<String, dynamic> json) =>
|
||||
|
@ -329,7 +329,7 @@ class ChatListRouteArgs {
|
||||
/// [_i7.ChatRoomScreen]
|
||||
class ChatRoomRoute extends _i27.PageRouteInfo<ChatRoomRouteArgs> {
|
||||
ChatRoomRoute({
|
||||
_i28.Key? key,
|
||||
_i29.Key? key,
|
||||
required String id,
|
||||
List<_i27.PageRouteInfo>? children,
|
||||
}) : super(
|
||||
@ -356,7 +356,7 @@ class ChatRoomRoute extends _i27.PageRouteInfo<ChatRoomRouteArgs> {
|
||||
class ChatRoomRouteArgs {
|
||||
const ChatRoomRouteArgs({this.key, required this.id});
|
||||
|
||||
final _i28.Key? key;
|
||||
final _i29.Key? key;
|
||||
|
||||
final String id;
|
||||
|
||||
|
@ -321,15 +321,46 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
void sendTypingStatus() async {
|
||||
// Members who are typing
|
||||
final typingStatuses = useState<List<SnChatMember>>([]);
|
||||
final typingDebouncer = useState<Timer?>(null);
|
||||
|
||||
void sendTypingStatus() {
|
||||
// Don't send if we're already in a cooldown period
|
||||
if (typingDebouncer.value != null) return;
|
||||
|
||||
// Send typing status immediately
|
||||
final wsState = ref.read(websocketStateProvider.notifier);
|
||||
wsState.sendMessage(
|
||||
jsonEncode(
|
||||
WebSocketPacket(type: 'messages.typing', data: {'chat_room_id': id}),
|
||||
),
|
||||
);
|
||||
|
||||
typingDebouncer.value = Timer(const Duration(milliseconds: 1000), () {
|
||||
typingDebouncer.value = null;
|
||||
});
|
||||
}
|
||||
|
||||
// Add timer to remove typing status after inactivity
|
||||
useEffect(() {
|
||||
final removeTypingTimer = Timer.periodic(const Duration(seconds: 5), (_) {
|
||||
if (typingStatuses.value.isNotEmpty) {
|
||||
// Remove typing statuses older than 5 seconds
|
||||
final now = DateTime.now();
|
||||
typingStatuses.value =
|
||||
typingStatuses.value.where((member) {
|
||||
final lastTyped =
|
||||
member.lastTyped ??
|
||||
DateTime.now().subtract(const Duration(milliseconds: 1350));
|
||||
return now.difference(lastTyped).inSeconds < 5;
|
||||
}).toList();
|
||||
}
|
||||
});
|
||||
|
||||
return () => removeTypingTimer.cancel();
|
||||
}, []);
|
||||
|
||||
var isLoading = false;
|
||||
|
||||
// Add scroll listener for pagination
|
||||
@ -352,6 +383,28 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
void onMessage(WebSocketPacket pkt) {
|
||||
if (!pkt.type.startsWith('messages')) return;
|
||||
if (['messages.read'].contains(pkt.type)) return;
|
||||
|
||||
if (pkt.type == 'messages.typing') {
|
||||
final sender = SnChatMember.fromJson(
|
||||
pkt.data!['sender'],
|
||||
).copyWith(lastTyped: DateTime.now());
|
||||
|
||||
// Check if the sender is already in the typing list
|
||||
final existingIndex = typingStatuses.value.indexWhere(
|
||||
(member) => member.id == sender.id,
|
||||
);
|
||||
if (existingIndex >= 0) {
|
||||
// Update the existing entry with new timestamp
|
||||
final updatedList = [...typingStatuses.value];
|
||||
updatedList[existingIndex] = sender;
|
||||
typingStatuses.value = updatedList;
|
||||
} else {
|
||||
// Add new typing status
|
||||
typingStatuses.value = [...typingStatuses.value, sender];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final message = SnChatMessage.fromJson(pkt.data!);
|
||||
if (message.chatRoomId != chatRoom.value?.id) return;
|
||||
switch (pkt.type) {
|
||||
@ -425,7 +478,17 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() {});
|
||||
// Add listener to message controller for typing status
|
||||
useEffect(() {
|
||||
void onTextChange() {
|
||||
if (messageController.text.isNotEmpty) {
|
||||
sendTypingStatus();
|
||||
}
|
||||
}
|
||||
|
||||
messageController.addListener(onTextChange);
|
||||
return () => messageController.removeListener(onTextChange);
|
||||
}, [messageController]);
|
||||
|
||||
final compactHeader = isWideScreen(context);
|
||||
|
||||
@ -666,7 +729,81 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
),
|
||||
chatRoom.when(
|
||||
data:
|
||||
(room) => _ChatInput(
|
||||
(room) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
switchInCurve: Curves.fastEaseInToSlowEaseOut,
|
||||
switchOutCurve: Curves.fastEaseInToSlowEaseOut,
|
||||
transitionBuilder: (
|
||||
Widget child,
|
||||
Animation<double> animation,
|
||||
) {
|
||||
return SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(0, -0.3),
|
||||
end: Offset.zero,
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: Curves.easeOutCubic,
|
||||
),
|
||||
),
|
||||
child: SizeTransition(
|
||||
sizeFactor: animation,
|
||||
axisAlignment: -1.0,
|
||||
child: FadeTransition(
|
||||
opacity: animation,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child:
|
||||
typingStatuses.value.isNotEmpty
|
||||
? Container(
|
||||
key: const ValueKey('typing-indicator'),
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 4,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Symbols.more_horiz,
|
||||
size: 16,
|
||||
).padding(horizontal: 8),
|
||||
const Gap(8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'typingHint'.plural(
|
||||
typingStatuses.value.length,
|
||||
args: [
|
||||
typingStatuses.value
|
||||
.map(
|
||||
(x) =>
|
||||
x.nick ??
|
||||
x.account.nick,
|
||||
)
|
||||
.join(', '),
|
||||
],
|
||||
),
|
||||
style:
|
||||
Theme.of(
|
||||
context,
|
||||
).textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(
|
||||
key: ValueKey('no_typing'),
|
||||
),
|
||||
),
|
||||
_ChatInput(
|
||||
messageController: messageController,
|
||||
chatRoom: room!,
|
||||
onSend: sendMessage,
|
||||
@ -697,7 +834,9 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
final attachment = attachments.value[index];
|
||||
if (attachment.isOnCloud) {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
await client.delete('/files/${attachment.data.id}');
|
||||
await client.delete(
|
||||
'/files/${attachment.data.id}',
|
||||
);
|
||||
}
|
||||
final clone = List.of(attachments.value);
|
||||
clone.removeAt(index);
|
||||
@ -716,6 +855,8 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
attachments.value = newAttachments;
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
error: (_, _) => const SizedBox.shrink(),
|
||||
loading: () => const SizedBox.shrink(),
|
||||
),
|
||||
|
@ -6,7 +6,7 @@ part of 'account_session_sheet.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$authDevicesHash() => r'9b8101167653991314efd37788d8416f414cb9e8';
|
||||
String _$authDevicesHash() => r'19807110962206a9637075d03cd372233cae2f49';
|
||||
|
||||
/// See also [authDevices].
|
||||
@ProviderFor(authDevices)
|
||||
|
Loading…
x
Reference in New Issue
Block a user