Chat break and notify level

This commit is contained in:
LittleSheep 2025-06-10 01:09:28 +08:00
parent 2a7876e22f
commit 044fb983d6
9 changed files with 336 additions and 30 deletions

View File

@ -282,6 +282,7 @@
"one": "{} unread message",
"other": "{} unread messages"
},
"chatBreakNone": "None",
"settingsRealmCompactView": "Compact Realm View",
"settingsMixedFeed": "Mixed Feed",
"settingsAutoTranslate": "Auto Translate",
@ -396,5 +397,20 @@
"contactMethodPrimary": "Primary",
"contactMethodSetPrimary": "Set as Primary",
"contactMethodSetPrimaryHint": "Set this contact method as your primary contact method for account recovery and notifications",
"contactMethodDeleteHint": "Are you sure to delete this contact method? This action cannot be undone."
"contactMethodDeleteHint": "Are you sure to delete this contact method? This action cannot be undone.",
"chatNotifyLevel": "Notify Level",
"chatNotifyLevelDescription": "Decide how many notifications you will receive.",
"chatNotifyLevelAll": "All",
"chatNotifyLevelMention": "Mentions",
"chatNotifyLevelNone": "None",
"chatNotifyLevelUpdated": "The notify level has been updated to {}.",
"chatBreak": "Take a Break",
"chatBreakDescription": "Set a time, before that time, your notification level will be metions only, to take a break of the current topic they're talking about.",
"chatBreakClear": "Clear the break time",
"chatBreakHour": "{} break",
"chatBreakDay": "{} day break",
"chatBreakSet": "Break set for {}",
"chatBreakCleared": "Chat break has been cleared.",
"chatBreakCustom": "Custom duration",
"chatBreakEnterMinutes": "Enter minutes"
}

View File

@ -291,5 +291,21 @@
"postVisibilityPublic": "公开",
"postVisibilityFriends": "仅好友可见",
"postVisibilityUnlisted": "不公开",
"postVisibilityPrivate": "私密"
"postVisibilityPrivate": "私密",
"chatNotifyLevel": "通知级别",
"chatNotifyLevelDescription": "决定您将收到多少通知。",
"chatNotifyLevelAll": "全部",
"chatNotifyLevelMention": "提及",
"chatNotifyLevelNone": "无",
"chatNotifyLevelUpdated": "通知级别已更新为 {}。",
"chatBreak": "暂停聊天",
"chatBreakDescription": "设置一个时间,在该时间之前,您的通知级别将仅为提及,以暂时休息当前讨论的话题。",
"chatBreakClear": "清除暂停时间",
"chatBreakHour": "暂停 {} 分钟",
"chatBreakDay": "暂停 {} 天",
"chatBreakSet": "已设置暂停 {}",
"chatBreakCleared": "聊天暂停已清除。",
"chatBreakCustom": "自定义时长",
"chatBreakEnterMinutes": "输入分钟数",
"chatBreakNone": "无"
}

View File

@ -68,7 +68,7 @@
"createRealmHint": "結識志同道合的朋友、建立社群等等。",
"editRealm": "編輯領域",
"deleteRealm": "刪除領域",
"deleteRealmHint": "確定要刪除此領域嗎?這也將刪除領域下的所有頻道、發佈者和貼文。",
"deleteRealmHint": "確定要刪除此領域嗎?這也將刪除領域下的所有頻道、發佈者和貼文。",
"explore": "探索",
"account": "帳號",
"name": "名稱",
@ -291,5 +291,21 @@
"postVisibilityPublic": "公開",
"postVisibilityFriends": "僅好友可見",
"postVisibilityUnlisted": "不公開",
"postVisibilityPrivate": "私密"
"postVisibilityPrivate": "私密",
"chatNotifyLevel": "通知等級",
"chatNotifyLevelDescription": "決定您將收到多少通知。",
"chatNotifyLevelAll": "全部",
"chatNotifyLevelMention": "提及",
"chatNotifyLevelNone": "無",
"chatNotifyLevelUpdated": "通知等級已更新為 {}。",
"chatBreak": "暫停聊天",
"chatBreakDescription": "設定一個時間,在該時間之前,您的通知等級將僅為提及,以暫時休息當前討論的話題。",
"chatBreakClear": "清除暫停時間",
"chatBreakHour": "暫停 {} 分鐘",
"chatBreakDay": "暫停 {} 天",
"chatBreakSet": "已設定暫停 {}",
"chatBreakCleared": "聊天暫停已清除。",
"chatBreakCustom": "自訂時長",
"chatBreakEnterMinutes": "輸入分鐘數",
"chatBreakNone": "無"
}

View File

@ -90,7 +90,10 @@ sealed class SnChatMember with _$SnChatMember {
required int role,
required int notify,
required DateTime? joinedAt,
required DateTime? breakUntil,
required DateTime? timeoutUntil,
required bool isBot,
// Frontend data
DateTime? lastTyped,
}) = _SnChatMember;

View File

@ -663,7 +663,8 @@ $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 lastTyped;
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; DateTime? get breakUntil; DateTime? get timeoutUntil; bool get isBot;// Frontend data
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 +677,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)&&(identical(other.lastTyped, lastTyped) || other.lastTyped == lastTyped));
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.breakUntil, breakUntil) || other.breakUntil == breakUntil)&&(identical(other.timeoutUntil, timeoutUntil) || other.timeoutUntil == timeoutUntil)&&(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,lastTyped);
int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,chatRoom,accountId,account,nick,role,notify,joinedAt,breakUntil,timeoutUntil,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, lastTyped: $lastTyped)';
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, breakUntil: $breakUntil, timeoutUntil: $timeoutUntil, isBot: $isBot, lastTyped: $lastTyped)';
}
@ -696,7 +697,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? lastTyped
DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String chatRoomId, SnChatRoom? chatRoom, String accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, DateTime? breakUntil, DateTime? timeoutUntil, bool isBot, DateTime? lastTyped
});
@ -713,7 +714,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,Object? lastTyped = freezed,}) {
@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? breakUntil = freezed,Object? timeoutUntil = 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
@ -727,6 +728,8 @@ as SnAccount,nick: freezed == nick ? _self.nick : nick // ignore: cast_nullable_
as String?,role: null == role ? _self.role : role // 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 DateTime?,breakUntil: freezed == breakUntil ? _self.breakUntil : breakUntil // ignore: cast_nullable_to_non_nullable
as DateTime?,timeoutUntil: freezed == timeoutUntil ? _self.timeoutUntil : timeoutUntil // ignore: cast_nullable_to_non_nullable
as DateTime?,isBot: null == isBot ? _self.isBot : isBot // ignore: cast_nullable_to_non_nullable
as bool,lastTyped: freezed == lastTyped ? _self.lastTyped : lastTyped // ignore: cast_nullable_to_non_nullable
as DateTime?,
@ -761,7 +764,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, this.lastTyped});
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.breakUntil, required this.timeoutUntil, required this.isBot, this.lastTyped});
factory _SnChatMember.fromJson(Map<String, dynamic> json) => _$SnChatMemberFromJson(json);
@override final DateTime createdAt;
@ -776,7 +779,10 @@ class _SnChatMember implements SnChatMember {
@override final int role;
@override final int notify;
@override final DateTime? joinedAt;
@override final DateTime? breakUntil;
@override final DateTime? timeoutUntil;
@override final bool isBot;
// Frontend data
@override final DateTime? lastTyped;
/// Create a copy of SnChatMember
@ -792,16 +798,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)&&(identical(other.lastTyped, lastTyped) || other.lastTyped == lastTyped));
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.breakUntil, breakUntil) || other.breakUntil == breakUntil)&&(identical(other.timeoutUntil, timeoutUntil) || other.timeoutUntil == timeoutUntil)&&(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,lastTyped);
int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,chatRoom,accountId,account,nick,role,notify,joinedAt,breakUntil,timeoutUntil,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, lastTyped: $lastTyped)';
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, breakUntil: $breakUntil, timeoutUntil: $timeoutUntil, isBot: $isBot, lastTyped: $lastTyped)';
}
@ -812,7 +818,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? lastTyped
DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String chatRoomId, SnChatRoom? chatRoom, String accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, DateTime? breakUntil, DateTime? timeoutUntil, bool isBot, DateTime? lastTyped
});
@ -829,7 +835,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,Object? lastTyped = freezed,}) {
@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? breakUntil = freezed,Object? timeoutUntil = 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
@ -843,6 +849,8 @@ as SnAccount,nick: freezed == nick ? _self.nick : nick // ignore: cast_nullable_
as String?,role: null == role ? _self.role : role // 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 DateTime?,breakUntil: freezed == breakUntil ? _self.breakUntil : breakUntil // ignore: cast_nullable_to_non_nullable
as DateTime?,timeoutUntil: freezed == timeoutUntil ? _self.timeoutUntil : timeoutUntil // ignore: cast_nullable_to_non_nullable
as DateTime?,isBot: null == isBot ? _self.isBot : isBot // ignore: cast_nullable_to_non_nullable
as bool,lastTyped: freezed == lastTyped ? _self.lastTyped : lastTyped // ignore: cast_nullable_to_non_nullable
as DateTime?,

View File

@ -166,6 +166,14 @@ _SnChatMember _$SnChatMemberFromJson(Map<String, dynamic> json) =>
json['joined_at'] == null
? null
: DateTime.parse(json['joined_at'] as String),
breakUntil:
json['break_until'] == null
? null
: DateTime.parse(json['break_until'] as String),
timeoutUntil:
json['timeout_until'] == null
? null
: DateTime.parse(json['timeout_until'] as String),
isBot: json['is_bot'] as bool,
lastTyped:
json['last_typed'] == null
@ -187,6 +195,8 @@ Map<String, dynamic> _$SnChatMemberToJson(_SnChatMember instance) =>
'role': instance.role,
'notify': instance.notify,
'joined_at': instance.joinedAt?.toIso8601String(),
'break_until': instance.breakUntil?.toIso8601String(),
'timeout_until': instance.timeoutUntil?.toIso8601String(),
'is_bot': instance.isBot,
'last_typed': instance.lastTyped?.toIso8601String(),
};

View File

@ -6,6 +6,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:intl/intl.dart';
import 'package:island/models/chat.dart';
import 'package:island/pods/network.dart';
import 'package:island/route.gr.dart';
@ -14,7 +15,7 @@ import 'package:island/widgets/account/account_picker.dart';
import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/content/cloud_files.dart';
import 'package:island/widgets/content/paging_helper_ext.dart';
import 'package:island/widgets/content/sheet.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
@ -31,6 +32,206 @@ class ChatDetailScreen extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final roomState = ref.watch(chatroomProvider(id));
final roomIdentity = ref.watch(chatroomIdentityProvider(id));
const kNotifyLevelText = [
'chatNotifyLevelAll',
'chatNotifyLevelMention',
'chatNotifyLevelNone',
];
void setNotifyLevel(int level) async {
try {
final client = ref.watch(apiClientProvider);
await client.patch(
'/chat/$id/members/me/notify',
data: {'notify_level': level},
);
ref.invalidate(chatroomIdentityProvider(id));
if (context.mounted) {
showSnackBar(
context,
'chatNotifyLevelUpdated'.tr(args: [kNotifyLevelText[level].tr()]),
);
}
} catch (err) {
showErrorAlert(err);
}
}
void setChatBreak(DateTime until) async {
try {
final client = ref.watch(apiClientProvider);
await client.patch(
'/chat/$id/members/me/notify',
data: {'break_until': until.toUtc().toIso8601String()},
);
ref.invalidate(chatroomProvider(id));
} catch (err) {
showErrorAlert(err);
}
}
void showNotifyLevelBottomSheet(SnChatMember identity) {
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder:
(context) => SheetScaffold(
height: 320,
titleText: 'chatNotifyLevel'.tr(),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
title: const Text('chatNotifyLevelAll').tr(),
subtitle: const Text('chatNotifyLevelDescription').tr(),
leading: const Icon(Icons.notifications_active),
selected: identity.notify == 0,
onTap: () {
setNotifyLevel(0);
Navigator.pop(context);
},
),
ListTile(
title: const Text('chatNotifyLevelMention').tr(),
subtitle: const Text('chatNotifyLevelDescription').tr(),
leading: const Icon(Icons.alternate_email),
selected: identity.notify == 1,
onTap: () {
setNotifyLevel(1);
Navigator.pop(context);
},
),
ListTile(
title: const Text('chatNotifyLevelNone').tr(),
subtitle: const Text('chatNotifyLevelDescription').tr(),
leading: const Icon(Icons.notifications_off),
selected: identity.notify == 2,
onTap: () {
setNotifyLevel(2);
Navigator.pop(context);
},
),
],
),
),
);
}
void showChatBreakDialog() {
final now = DateTime.now();
final durationController = TextEditingController();
showDialog(
context: context,
builder:
(context) => AlertDialog(
title: const Text('chatBreak').tr(),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('chatBreakDescription').tr(),
const Gap(16),
ListTile(
title: const Text('Clear').tr(),
subtitle: const Text('chatBreakClear').tr(),
leading: const Icon(Icons.notifications_active),
onTap: () {
setChatBreak(now);
Navigator.pop(context);
if (context.mounted) {
showSnackBar(context, 'chatBreakCleared'.tr());
}
},
),
ListTile(
title: const Text('5m'),
subtitle: const Text('chatBreakHour').tr(args: ['5m']),
leading: const Icon(Symbols.circle),
onTap: () {
setChatBreak(now.add(const Duration(minutes: 5)));
Navigator.pop(context);
if (context.mounted) {
showSnackBar(context, 'chatBreakSet'.tr(args: ['5m']));
}
},
),
ListTile(
title: const Text('10m'),
subtitle: const Text('chatBreakHour').tr(args: ['10m']),
leading: const Icon(Symbols.circle),
onTap: () {
setChatBreak(now.add(const Duration(minutes: 10)));
Navigator.pop(context);
if (context.mounted) {
showSnackBar(context, 'chatBreakSet'.tr(args: ['10m']));
}
},
),
ListTile(
title: const Text('15m'),
subtitle: const Text('chatBreakHour').tr(args: ['15m']),
leading: const Icon(Symbols.timer_3),
onTap: () {
setChatBreak(now.add(const Duration(minutes: 15)));
Navigator.pop(context);
if (context.mounted) {
showSnackBar(context, 'chatBreakSet'.tr(args: ['15m']));
}
},
),
ListTile(
title: const Text('30m'),
subtitle: const Text('chatBreakHour').tr(args: ['30m']),
leading: const Icon(Symbols.timer),
onTap: () {
setChatBreak(now.add(const Duration(minutes: 30)));
Navigator.pop(context);
if (context.mounted) {
showSnackBar(context, 'chatBreakSet'.tr(args: ['30m']));
}
},
),
const Gap(8),
TextField(
controller: durationController,
decoration: InputDecoration(
labelText: 'Custom (minutes)'.tr(),
hintText: 'Enter minutes'.tr(),
border: const OutlineInputBorder(),
suffixIcon: IconButton(
icon: const Icon(Icons.check),
onPressed: () {
final minutes = int.tryParse(durationController.text);
if (minutes != null && minutes > 0) {
setChatBreak(now.add(Duration(minutes: minutes)));
Navigator.pop(context);
if (context.mounted) {
showSnackBar(
context,
'chatBreakSet'.tr(args: ['${minutes}m']),
);
}
}
},
),
),
keyboardType: TextInputType.number,
onTapOutside:
(_) => FocusManager.instance.primaryFocus?.unfocus(),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('cancel').tr(),
),
],
),
);
}
const iconShadow = Shadow(
color: Colors.black54,
@ -114,17 +315,51 @@ class ChatDetailScreen extends HookConsumerWidget {
],
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
currentRoom.description ?? 'descriptionNone'.tr(),
style: const TextStyle(fontSize: 16),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
currentRoom.description ?? 'descriptionNone'.tr(),
style: const TextStyle(fontSize: 16),
).padding(all: 24),
const Divider(height: 1),
roomIdentity.when(
data:
(identity) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListTile(
contentPadding: EdgeInsets.symmetric(
horizontal: 24,
),
leading: const Icon(Symbols.notifications),
trailing: const Icon(Symbols.chevron_right),
title: const Text('chatNotifyLevel').tr(),
subtitle: Text(
kNotifyLevelText[identity!.notify].tr(),
),
onTap:
() =>
showNotifyLevelBottomSheet(identity),
),
ListTile(
contentPadding: EdgeInsets.symmetric(
horizontal: 24,
),
leading: const Icon(Icons.timer),
trailing: const Icon(Symbols.chevron_right),
title: const Text('chatBreak').tr(),
subtitle: identity!.breakUntil != null && identity.breakUntil!.isAfter(DateTime.now())
? Text(DateFormat('yyyy-MM-dd HH:mm').format(identity.breakUntil!))
: const Text('chatBreakNone').tr(),
onTap: () => showChatBreakDialog(),
),
],
),
error: (_, _) => const SizedBox.shrink(),
loading: () => const SizedBox.shrink(),
),
],
),
),
],

View File

@ -7,7 +7,7 @@ part of 'explore.dart';
// **************************************************************************
String _$activityListNotifierHash() =>
r'1baf0bb961bc02bfc8a5b5f515981072c6ce1750';
r'2ca8fe14686d7f4fb09ab26f2978eb2de7184565';
/// See also [ActivityListNotifier].
@ProviderFor(ActivityListNotifier)

View File

@ -7,6 +7,7 @@ class SheetScaffold extends StatelessWidget {
final List<Widget> actions;
final Widget child;
final double heightFactor;
final double? height;
const SheetScaffold({
super.key,
this.title,
@ -14,6 +15,7 @@ class SheetScaffold extends StatelessWidget {
required this.child,
this.actions = const [],
this.heightFactor = 0.8,
this.height,
});
@override
@ -32,7 +34,7 @@ class SheetScaffold extends StatelessWidget {
return Container(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * heightFactor,
maxHeight: height ?? MediaQuery.of(context).size.height * heightFactor,
),
child: Column(
children: [