✨ Typing indicator
This commit is contained in:
parent
b2a118bbd0
commit
20e6cc4283
@ -353,5 +353,9 @@
|
|||||||
"authDeviceLabelTitle": "Edit Device Label",
|
"authDeviceLabelTitle": "Edit Device Label",
|
||||||
"authDeviceLabelHint": "Enter a name for this device",
|
"authDeviceLabelHint": "Enter a name for this device",
|
||||||
"authDeviceSwipeEditHint": "Swipe left to edit label",
|
"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 int notify,
|
||||||
required DateTime? joinedAt,
|
required DateTime? joinedAt,
|
||||||
required bool isBot,
|
required bool isBot,
|
||||||
|
DateTime? lastTyped,
|
||||||
}) = _SnChatMember;
|
}) = _SnChatMember;
|
||||||
|
|
||||||
factory SnChatMember.fromJson(Map<String, dynamic> json) =>
|
factory SnChatMember.fromJson(Map<String, dynamic> json) =>
|
||||||
|
@ -663,7 +663,7 @@ $SnChatMemberCopyWith<$Res> get sender {
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SnChatMember {
|
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
|
/// Create a copy of SnChatMember
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@ -676,16 +676,16 @@ $SnChatMemberCopyWith<SnChatMember> get copyWith => _$SnChatMemberCopyWithImpl<S
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
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)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@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
|
@override
|
||||||
String toString() {
|
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;
|
factory $SnChatMemberCopyWith(SnChatMember value, $Res Function(SnChatMember) _then) = _$SnChatMemberCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$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
|
/// Create a copy of SnChatMember
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// 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(
|
return _then(_self.copyWith(
|
||||||
createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
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
|
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,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 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 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
|
/// Create a copy of SnChatMember
|
||||||
@ -760,7 +761,7 @@ $SnAccountCopyWith<$Res> get account {
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _SnChatMember implements SnChatMember {
|
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);
|
factory _SnChatMember.fromJson(Map<String, dynamic> json) => _$SnChatMemberFromJson(json);
|
||||||
|
|
||||||
@override final DateTime createdAt;
|
@override final DateTime createdAt;
|
||||||
@ -776,6 +777,7 @@ class _SnChatMember implements SnChatMember {
|
|||||||
@override final int notify;
|
@override final int notify;
|
||||||
@override final DateTime? joinedAt;
|
@override final DateTime? joinedAt;
|
||||||
@override final bool isBot;
|
@override final bool isBot;
|
||||||
|
@override final DateTime? lastTyped;
|
||||||
|
|
||||||
/// Create a copy of SnChatMember
|
/// Create a copy of SnChatMember
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@ -790,16 +792,16 @@ Map<String, dynamic> toJson() {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
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)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@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
|
@override
|
||||||
String toString() {
|
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;
|
factory _$SnChatMemberCopyWith(_SnChatMember value, $Res Function(_SnChatMember) _then) = __$SnChatMemberCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$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
|
/// Create a copy of SnChatMember
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// 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(
|
return _then(_SnChatMember(
|
||||||
createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
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
|
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,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 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 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
|
? null
|
||||||
: DateTime.parse(json['joined_at'] as String),
|
: DateTime.parse(json['joined_at'] as String),
|
||||||
isBot: json['is_bot'] as bool,
|
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) =>
|
Map<String, dynamic> _$SnChatMemberToJson(_SnChatMember instance) =>
|
||||||
@ -184,6 +188,7 @@ Map<String, dynamic> _$SnChatMemberToJson(_SnChatMember instance) =>
|
|||||||
'notify': instance.notify,
|
'notify': instance.notify,
|
||||||
'joined_at': instance.joinedAt?.toIso8601String(),
|
'joined_at': instance.joinedAt?.toIso8601String(),
|
||||||
'is_bot': instance.isBot,
|
'is_bot': instance.isBot,
|
||||||
|
'last_typed': instance.lastTyped?.toIso8601String(),
|
||||||
};
|
};
|
||||||
|
|
||||||
_SnChatSummary _$SnChatSummaryFromJson(Map<String, dynamic> json) =>
|
_SnChatSummary _$SnChatSummaryFromJson(Map<String, dynamic> json) =>
|
||||||
|
@ -329,7 +329,7 @@ class ChatListRouteArgs {
|
|||||||
/// [_i7.ChatRoomScreen]
|
/// [_i7.ChatRoomScreen]
|
||||||
class ChatRoomRoute extends _i27.PageRouteInfo<ChatRoomRouteArgs> {
|
class ChatRoomRoute extends _i27.PageRouteInfo<ChatRoomRouteArgs> {
|
||||||
ChatRoomRoute({
|
ChatRoomRoute({
|
||||||
_i28.Key? key,
|
_i29.Key? key,
|
||||||
required String id,
|
required String id,
|
||||||
List<_i27.PageRouteInfo>? children,
|
List<_i27.PageRouteInfo>? children,
|
||||||
}) : super(
|
}) : super(
|
||||||
@ -356,7 +356,7 @@ class ChatRoomRoute extends _i27.PageRouteInfo<ChatRoomRouteArgs> {
|
|||||||
class ChatRoomRouteArgs {
|
class ChatRoomRouteArgs {
|
||||||
const ChatRoomRouteArgs({this.key, required this.id});
|
const ChatRoomRouteArgs({this.key, required this.id});
|
||||||
|
|
||||||
final _i28.Key? key;
|
final _i29.Key? key;
|
||||||
|
|
||||||
final String id;
|
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);
|
final wsState = ref.read(websocketStateProvider.notifier);
|
||||||
wsState.sendMessage(
|
wsState.sendMessage(
|
||||||
jsonEncode(
|
jsonEncode(
|
||||||
WebSocketPacket(type: 'messages.typing', data: {'chat_room_id': id}),
|
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;
|
var isLoading = false;
|
||||||
|
|
||||||
// Add scroll listener for pagination
|
// Add scroll listener for pagination
|
||||||
@ -352,6 +383,28 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
void onMessage(WebSocketPacket pkt) {
|
void onMessage(WebSocketPacket pkt) {
|
||||||
if (!pkt.type.startsWith('messages')) return;
|
if (!pkt.type.startsWith('messages')) return;
|
||||||
if (['messages.read'].contains(pkt.type)) 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!);
|
final message = SnChatMessage.fromJson(pkt.data!);
|
||||||
if (message.chatRoomId != chatRoom.value?.id) return;
|
if (message.chatRoomId != chatRoom.value?.id) return;
|
||||||
switch (pkt.type) {
|
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);
|
final compactHeader = isWideScreen(context);
|
||||||
|
|
||||||
@ -666,7 +729,81 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
chatRoom.when(
|
chatRoom.when(
|
||||||
data:
|
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,
|
messageController: messageController,
|
||||||
chatRoom: room!,
|
chatRoom: room!,
|
||||||
onSend: sendMessage,
|
onSend: sendMessage,
|
||||||
@ -697,7 +834,9 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
final attachment = attachments.value[index];
|
final attachment = attachments.value[index];
|
||||||
if (attachment.isOnCloud) {
|
if (attachment.isOnCloud) {
|
||||||
final client = ref.watch(apiClientProvider);
|
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);
|
final clone = List.of(attachments.value);
|
||||||
clone.removeAt(index);
|
clone.removeAt(index);
|
||||||
@ -716,6 +855,8 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
attachments.value = newAttachments;
|
attachments.value = newAttachments;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
error: (_, _) => const SizedBox.shrink(),
|
error: (_, _) => const SizedBox.shrink(),
|
||||||
loading: () => const SizedBox.shrink(),
|
loading: () => const SizedBox.shrink(),
|
||||||
),
|
),
|
||||||
|
@ -6,7 +6,7 @@ part of 'account_session_sheet.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$authDevicesHash() => r'9b8101167653991314efd37788d8416f414cb9e8';
|
String _$authDevicesHash() => r'19807110962206a9637075d03cd372233cae2f49';
|
||||||
|
|
||||||
/// See also [authDevices].
|
/// See also [authDevices].
|
||||||
@ProviderFor(authDevices)
|
@ProviderFor(authDevices)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user