added talk notification levels for chats
This commit is contained in:
@@ -4,6 +4,7 @@ import 'package:http/http.dart' as http;
|
||||
|
||||
import '../../../api_params.dart';
|
||||
import '../../../api_response.dart';
|
||||
import '../room/get_room_response.dart';
|
||||
import '../talk_api.dart';
|
||||
|
||||
/// Small POST/DELETE-only Talk endpoints that have no response payload.
|
||||
@@ -105,3 +106,21 @@ class DeleteRoomAvatar extends TalkApi {
|
||||
Map<String, String>? headers,
|
||||
) => http.delete(uri, headers: headers);
|
||||
}
|
||||
|
||||
class SetNotificationLevel extends TalkApi {
|
||||
final String chatToken;
|
||||
final GetRoomResponseObjectParticipantNotificationLevel level;
|
||||
|
||||
SetNotificationLevel(this.chatToken, this.level)
|
||||
: super('v4/room/$chatToken/notify', null);
|
||||
|
||||
@override
|
||||
ApiResponse? assemble(String raw) => null;
|
||||
|
||||
@override
|
||||
Future<http.Response> request(
|
||||
Uri uri,
|
||||
ApiParams? body,
|
||||
Map<String, String>? headers,
|
||||
) => http.post(uri, headers: headers, body: {'level': '${level.apiValue}'});
|
||||
}
|
||||
|
||||
@@ -130,6 +130,12 @@ enum GetRoomResponseObjectParticipantNotificationLevel {
|
||||
neverNotify,
|
||||
}
|
||||
|
||||
extension NotificationLevelApiValue
|
||||
on GetRoomResponseObjectParticipantNotificationLevel {
|
||||
int get apiValue =>
|
||||
_$GetRoomResponseObjectParticipantNotificationLevelEnumMap[this]!;
|
||||
}
|
||||
|
||||
enum GetRoomResponseObjectMessageActorType {
|
||||
@JsonValue('deleted_users')
|
||||
deletedUsers,
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import 'get_room_response.dart';
|
||||
|
||||
typedef NotificationLevel = GetRoomResponseObjectParticipantNotificationLevel;
|
||||
|
||||
/// The levels a user can pick in the UI; [NotificationLevel.defaultLevel]
|
||||
/// is only ever reported by the server, never chosen.
|
||||
const selectableNotificationLevels = [
|
||||
NotificationLevel.alwaysNotify,
|
||||
NotificationLevel.notifyOnMention,
|
||||
NotificationLevel.neverNotify,
|
||||
];
|
||||
|
||||
extension RoomNotificationLevel on GetRoomResponseObject {
|
||||
/// Resolves "default" the way Spreed does: 1:1 chats notify on every
|
||||
/// message, group chats only on mentions (`default_group_notification`).
|
||||
NotificationLevel get effectiveNotificationLevel {
|
||||
if (notificationLevel != NotificationLevel.defaultLevel) {
|
||||
return notificationLevel;
|
||||
}
|
||||
return type == GetRoomResponseObjectConversationType.oneToOne
|
||||
? NotificationLevel.alwaysNotify
|
||||
: NotificationLevel.notifyOnMention;
|
||||
}
|
||||
|
||||
bool get isMuted =>
|
||||
effectiveNotificationLevel == NotificationLevel.neverNotify;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import 'dart:developer';
|
||||
|
||||
import 'package:flutter_app_badge/flutter_app_badge.dart';
|
||||
|
||||
import '../../../../../api/marianumcloud/talk/actions/talk_actions.dart';
|
||||
import '../../../../../api/marianumcloud/talk/chat/get_chat_response.dart';
|
||||
import '../../../../../api/marianumcloud/talk/room/get_room_response.dart';
|
||||
import '../../../infrastructure/utility_widgets/loadable_hydrated_bloc/loadable_hydrated_bloc.dart';
|
||||
@@ -126,6 +127,18 @@ class ChatListBloc
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> setNotificationLevel(
|
||||
String token,
|
||||
GetRoomResponseObjectParticipantNotificationLevel level,
|
||||
) async {
|
||||
await SetNotificationLevel(token, level).run();
|
||||
_mutateRoom(token, (r) {
|
||||
if (r.notificationLevel == level) return false;
|
||||
r.notificationLevel = level;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/// Clears unread too — long-poll only feeds this in for an actively-open chat.
|
||||
void applyIncomingMessage(String token, GetChatResponseObject message) {
|
||||
_mutateRoom(token, (r) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import '../../../../api/marianumcloud/talk/get_participants/get_participants_cac
|
||||
import '../../../../api/marianumcloud/talk/get_participants/get_participants_response.dart';
|
||||
import '../../../../api/marianumcloud/talk/get_shared_items/get_shared_items_overview_response.dart';
|
||||
import '../../../../api/marianumcloud/talk/room/get_room_response.dart';
|
||||
import '../../../../api/marianumcloud/talk/room/notification_level.dart';
|
||||
import '../../../../state/app/modules/chat_list/bloc/chat_list_bloc.dart';
|
||||
import '../../../../widget/app_progress_indicator.dart';
|
||||
import '../../../../widget/async_action_button.dart';
|
||||
@@ -14,6 +15,7 @@ import '../../../../widget/confirm_dialog.dart';
|
||||
import '../../../../widget/large_profile_picture_view.dart';
|
||||
import '../../../../widget/user_avatar.dart';
|
||||
import '../talk_navigator.dart';
|
||||
import '../widgets/notification_level_sheet.dart';
|
||||
import 'participants_list_view.dart';
|
||||
import 'shared_items_view.dart';
|
||||
|
||||
@@ -29,6 +31,7 @@ class _ChatInfoState extends State<ChatInfo> {
|
||||
GetParticipantsResponse? participants;
|
||||
GetSharedItemsOverviewResponse? _sharesOverview;
|
||||
late bool _isFavorite;
|
||||
late NotificationLevel _notificationLevel;
|
||||
int _avatarVersion = 0;
|
||||
bool _avatarBusy = false;
|
||||
|
||||
@@ -36,6 +39,7 @@ class _ChatInfoState extends State<ChatInfo> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
_isFavorite = widget.room.isFavorite;
|
||||
_notificationLevel = widget.room.effectiveNotificationLevel;
|
||||
GetParticipantsCache(
|
||||
chatToken: widget.room.token,
|
||||
onUpdate: (GetParticipantsResponse data) {
|
||||
@@ -67,6 +71,16 @@ class _ChatInfoState extends State<ChatInfo> {
|
||||
_refreshList();
|
||||
}
|
||||
|
||||
Future<void> _editNotificationLevel() async {
|
||||
final level = await showNotificationLevelSheet(
|
||||
context,
|
||||
widget.room,
|
||||
current: _notificationLevel,
|
||||
);
|
||||
if (level == null || !mounted) return;
|
||||
setState(() => _notificationLevel = level);
|
||||
}
|
||||
|
||||
Future<void> _confirmLeave() async {
|
||||
final closed = await showDialog<bool>(
|
||||
context: context,
|
||||
@@ -243,6 +257,11 @@ class _ChatInfoState extends State<ChatInfo> {
|
||||
title: const Text('Zu Favoriten hinzufügen'),
|
||||
onPressed: _toggleFavorite,
|
||||
),
|
||||
NotificationLevelTile(
|
||||
level: _notificationLevel,
|
||||
trailing: const Icon(Icons.arrow_right),
|
||||
onTap: _editNotificationLevel,
|
||||
),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../../../api/marianumcloud/talk/actions/talk_actions.dart';
|
||||
import '../../../../api/marianumcloud/talk/chat/rich_object_string_processor.dart';
|
||||
import '../../../../api/marianumcloud/talk/room/get_room_response.dart';
|
||||
import '../../../../api/marianumcloud/talk/room/notification_level.dart';
|
||||
import '../../../../api/marianumcloud/talk/set_read_marker/set_read_marker.dart';
|
||||
import '../../../../extensions/date_time.dart';
|
||||
import '../../../../model/account_data.dart';
|
||||
@@ -22,6 +23,7 @@ import '../../../../widget/details_bottom_sheet.dart';
|
||||
import '../../../../widget/user_avatar.dart';
|
||||
import '../data/talk_markdown.dart';
|
||||
import '../talk_navigator.dart';
|
||||
import 'notification_level_sheet.dart';
|
||||
|
||||
class ChatTile extends StatefulWidget {
|
||||
final GetRoomResponseObject data;
|
||||
@@ -151,6 +153,15 @@ class _ChatTileState extends State<ChatTile> {
|
||||
semanticLabel: A11yLabels.draft,
|
||||
),
|
||||
],
|
||||
if (widget.data.isMuted) ...[
|
||||
const SizedBox(width: 5),
|
||||
Icon(
|
||||
NotificationLevel.neverNotify.icon,
|
||||
size: 15,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
semanticLabel: A11yLabels.muted,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
subtitle: Text(
|
||||
@@ -238,6 +249,17 @@ class _ChatTileState extends State<ChatTile> {
|
||||
if (mounted) _refreshList();
|
||||
},
|
||||
),
|
||||
NotificationLevelTile(
|
||||
level: widget.data.effectiveNotificationLevel,
|
||||
onTap: () {
|
||||
Navigator.of(sheetCtx).pop();
|
||||
showNotificationLevelSheet(
|
||||
context,
|
||||
widget.data,
|
||||
current: widget.data.effectiveNotificationLevel,
|
||||
);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.delete_outline),
|
||||
title: const Text('Talk-Chat verlassen'),
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../../api/marianumcloud/talk/room/get_room_response.dart';
|
||||
import '../../../../api/marianumcloud/talk/room/notification_level.dart';
|
||||
import '../../../../state/app/modules/chat_list/bloc/chat_list_bloc.dart';
|
||||
import '../../../../widget/async_action_button.dart';
|
||||
import '../../../../widget/details_bottom_sheet.dart';
|
||||
|
||||
extension NotificationLevelPresentation on NotificationLevel {
|
||||
// defaultLevel never reaches the UI (see effectiveNotificationLevel), but
|
||||
// the switch has to stay exhaustive.
|
||||
({String label, String description, IconData icon}) get _presentation =>
|
||||
switch (this) {
|
||||
NotificationLevel.alwaysNotify || NotificationLevel.defaultLevel => (
|
||||
label: 'Alle Nachrichten',
|
||||
description: 'Bei jeder neuen Nachricht',
|
||||
icon: Icons.notifications_outlined,
|
||||
),
|
||||
NotificationLevel.notifyOnMention => (
|
||||
label: 'Nur Erwähnungen',
|
||||
description: 'Nur wenn du erwähnt wirst',
|
||||
icon: Icons.alternate_email,
|
||||
),
|
||||
NotificationLevel.neverNotify => (
|
||||
label: 'Aus',
|
||||
description: 'Keine Benachrichtigungen',
|
||||
icon: Icons.notifications_off_outlined,
|
||||
),
|
||||
};
|
||||
|
||||
String get label => _presentation.label;
|
||||
String get description => _presentation.description;
|
||||
IconData get icon => _presentation.icon;
|
||||
}
|
||||
|
||||
/// Lets the user pick the room's Talk notification level. Nextcloud applies
|
||||
/// it server-side, so no push for the room is sent at all when muted.
|
||||
/// Returns the new level, or null when nothing changed.
|
||||
Future<NotificationLevel?> showNotificationLevelSheet(
|
||||
BuildContext context,
|
||||
GetRoomResponseObject room, {
|
||||
required NotificationLevel current,
|
||||
}) async {
|
||||
final bloc = context.read<ChatListBloc>();
|
||||
NotificationLevel? selected;
|
||||
await showDetailsBottomSheet(
|
||||
context,
|
||||
header: ListTile(
|
||||
leading: const Icon(Icons.notifications_outlined),
|
||||
title: const Text('Benachrichtigungen'),
|
||||
subtitle: Text(room.displayName, overflow: TextOverflow.ellipsis),
|
||||
),
|
||||
children: (_) => [
|
||||
for (final level in selectableNotificationLevels)
|
||||
AsyncListTile(
|
||||
leading: Icon(
|
||||
level == current
|
||||
? Icons.radio_button_checked
|
||||
: Icons.radio_button_unchecked,
|
||||
),
|
||||
title: Text(level.label),
|
||||
subtitle: Text(level.description),
|
||||
onPressed: () async {
|
||||
if (level == current) return;
|
||||
await bloc.setNotificationLevel(room.token, level);
|
||||
selected = level;
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
return selected;
|
||||
}
|
||||
|
||||
class NotificationLevelTile extends StatelessWidget {
|
||||
final NotificationLevel level;
|
||||
final VoidCallback onTap;
|
||||
final Widget? trailing;
|
||||
|
||||
const NotificationLevelTile({
|
||||
required this.level,
|
||||
required this.onTap,
|
||||
this.trailing,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => ListTile(
|
||||
leading: Icon(level.icon),
|
||||
title: const Text('Benachrichtigungen'),
|
||||
subtitle: Text(level.label),
|
||||
trailing: trailing,
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
@@ -14,6 +14,7 @@ abstract final class A11yLabels {
|
||||
static const unread = 'Ungelesen';
|
||||
static const favorite = 'Favorit';
|
||||
static const draft = 'Entwurf';
|
||||
static const muted = 'Stummgeschaltet';
|
||||
static const yourReaction = 'deine Reaktion';
|
||||
|
||||
// Bilder / Kontakte
|
||||
|
||||
Reference in New Issue
Block a user