From 4ef21a362bad58f52816ac0c60840be76118611a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= <elias@elias-mueller.com> Date: Sun, 4 Jun 2023 18:28:15 +0200 Subject: [PATCH] Added Chat context menu for chat 'mark as read', 'mark as unread', 'add to favorites', 'remove from favorites', 'leave conversation' added Chat favorite badge and mark chat as read when entering it --- .../talk/leaveRoom/leaveRoom.dart | 19 +++ .../talk/setFavorite/setFavorite.dart | 27 ++++ .../talk/setReadMarker/setReadMarker.dart | 32 ++++ .../setReadMarker/setReadMarkerParams.dart | 16 ++ .../setReadMarker/setReadMarkerParams.g.dart | 18 +++ lib/view/pages/talk/chatList.dart | 147 +++++++++++++++--- lib/widget/confirmDialog.dart | 4 +- 7 files changed, 239 insertions(+), 24 deletions(-) create mode 100644 lib/api/marianumcloud/talk/leaveRoom/leaveRoom.dart create mode 100644 lib/api/marianumcloud/talk/setFavorite/setFavorite.dart create mode 100644 lib/api/marianumcloud/talk/setReadMarker/setReadMarker.dart create mode 100644 lib/api/marianumcloud/talk/setReadMarker/setReadMarkerParams.dart create mode 100644 lib/api/marianumcloud/talk/setReadMarker/setReadMarkerParams.g.dart diff --git a/lib/api/marianumcloud/talk/leaveRoom/leaveRoom.dart b/lib/api/marianumcloud/talk/leaveRoom/leaveRoom.dart new file mode 100644 index 0000000..7f22874 --- /dev/null +++ b/lib/api/marianumcloud/talk/leaveRoom/leaveRoom.dart @@ -0,0 +1,19 @@ + +import 'package:http/http.dart' as http; +import 'package:http/http.dart'; + +import '../talkApi.dart'; +class LeaveRoom extends TalkApi<void> { + String chatToken; + + LeaveRoom(this.chatToken) : super("v4/room/$chatToken/participants/self", null); + + @override + assemble(String raw) { + } + + @override + Future<Response> request(Uri uri, Object? body, Map<String, String>? headers) { + return http.delete(uri, headers: headers); + } +} \ No newline at end of file diff --git a/lib/api/marianumcloud/talk/setFavorite/setFavorite.dart b/lib/api/marianumcloud/talk/setFavorite/setFavorite.dart new file mode 100644 index 0000000..ce331cf --- /dev/null +++ b/lib/api/marianumcloud/talk/setFavorite/setFavorite.dart @@ -0,0 +1,27 @@ + +import 'package:http/http.dart' as http; +import 'package:http/http.dart'; + +import '../talkApi.dart'; + +class SetFavorite extends TalkApi<void> { + String chatToken; + bool favoriteState; + + SetFavorite(this.chatToken, this.favoriteState) : super("v4/room/$chatToken/favorite", null); + + @override + assemble(String raw) { + + } + + @override + Future<Response> request(Uri uri, Object? body, Map<String, String>? headers) { + if(favoriteState) { + return http.post(uri, headers: headers); + } else { + return http.delete(uri, headers: headers); + } + } + +} \ No newline at end of file diff --git a/lib/api/marianumcloud/talk/setReadMarker/setReadMarker.dart b/lib/api/marianumcloud/talk/setReadMarker/setReadMarker.dart new file mode 100644 index 0000000..68593f4 --- /dev/null +++ b/lib/api/marianumcloud/talk/setReadMarker/setReadMarker.dart @@ -0,0 +1,32 @@ + +import 'package:http/http.dart' as http; +import 'package:http/http.dart'; +import 'package:marianum_mobile/api/marianumcloud/talk/setReadMarker/setReadMarkerParams.dart'; + +import '../talkApi.dart'; + +class SetReadMarker extends TalkApi<void> { + String chatToken; + bool readState; + SetReadMarkerParams? setReadMarkerParams; + + SetReadMarker(this.chatToken, this.readState, {this.setReadMarkerParams}) : super("v1/chat/$chatToken/read", null, getParameters: setReadMarkerParams?.toJson()) { + if(readState) assert(setReadMarkerParams?.lastReadMessage != null); + } + + @override + assemble(String raw) { + + } + + @override + Future<Response> request(Uri uri, Object? body, Map<String, String>? headers) { + if(readState) { + + return http.post(uri, headers: headers); + } else { + return http.delete(uri, headers: headers); + } + } + +} \ No newline at end of file diff --git a/lib/api/marianumcloud/talk/setReadMarker/setReadMarkerParams.dart b/lib/api/marianumcloud/talk/setReadMarker/setReadMarkerParams.dart new file mode 100644 index 0000000..7bacd17 --- /dev/null +++ b/lib/api/marianumcloud/talk/setReadMarker/setReadMarkerParams.dart @@ -0,0 +1,16 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:marianum_mobile/api/apiParams.dart'; + +part 'setReadMarkerParams.g.dart'; + +@JsonSerializable() +class SetReadMarkerParams extends ApiParams { + int? lastReadMessage; + + SetReadMarkerParams({ + this.lastReadMessage + }); + + factory SetReadMarkerParams.fromJson(Map<String, dynamic> json) => _$SetReadMarkerParamsFromJson(json); + Map<String, dynamic> toJson() => _$SetReadMarkerParamsToJson(this); +} \ No newline at end of file diff --git a/lib/api/marianumcloud/talk/setReadMarker/setReadMarkerParams.g.dart b/lib/api/marianumcloud/talk/setReadMarker/setReadMarkerParams.g.dart new file mode 100644 index 0000000..fa5f7fe --- /dev/null +++ b/lib/api/marianumcloud/talk/setReadMarker/setReadMarkerParams.g.dart @@ -0,0 +1,18 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'setReadMarkerParams.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +SetReadMarkerParams _$SetReadMarkerParamsFromJson(Map<String, dynamic> json) => + SetReadMarkerParams( + lastReadMessage: json['lastReadMessage'] as int?, + ); + +Map<String, dynamic> _$SetReadMarkerParamsToJson( + SetReadMarkerParams instance) => + <String, dynamic>{ + 'lastReadMessage': instance.lastReadMessage, + }; diff --git a/lib/view/pages/talk/chatList.dart b/lib/view/pages/talk/chatList.dart index bd50424..b674b4a 100644 --- a/lib/view/pages/talk/chatList.dart +++ b/lib/view/pages/talk/chatList.dart @@ -2,13 +2,18 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; +import 'package:marianum_mobile/api/marianumcloud/talk/leaveRoom/leaveRoom.dart'; +import 'package:marianum_mobile/api/marianumcloud/talk/setReadMarker/setReadMarker.dart'; +import 'package:marianum_mobile/api/marianumcloud/talk/setReadMarker/setReadMarkerParams.dart'; import 'package:persistent_bottom_nav_bar/persistent_tab_view.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../../api/marianumcloud/talk/chat/richObjectStringProcessor.dart'; import '../../../api/marianumcloud/talk/room/getRoomResponse.dart'; +import '../../../api/marianumcloud/talk/setFavorite/setFavorite.dart'; import '../../../model/chatList/chatListProps.dart'; +import '../../../widget/confirmDialog.dart'; import '../../../widget/unimplementedDialog.dart'; import 'chatView.dart'; @@ -31,10 +36,14 @@ class _ChatListState extends State<ChatList> { }); WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - Provider.of<ChatListProps>(context, listen: false).run(); + _query(); }); } + void _query({bool renew = false}) { + Provider.of<ChatListProps>(context, listen: false).run(renew: renew); + } + @override Widget build(BuildContext context) { @@ -60,6 +69,16 @@ class _ChatListState extends State<ChatList> { List<ListTile> chats = List<ListTile>.empty(growable: true); for (var chatRoom in data.getRoomsResponse.sortByLastActivity()) { + + setCurrentAsRead() { + SetReadMarker( + chatRoom.token, + true, + setReadMarkerParams: SetReadMarkerParams( + lastReadMessage: chatRoom.lastMessage.id + ) + ).run().then((value) => _query(renew: true)); + } CircleAvatar circleAvatar = CircleAvatar( foregroundImage: chatRoom.type == GetRoomResponseObjectConversationType.oneToOne ? Image.network("https://cloud.marianum-fulda.de/avatar/${chatRoom.name}/128").image : null, @@ -69,45 +88,129 @@ class _ChatListState extends State<ChatList> { ); chats.add(ListTile( + leading: Stack( + children: [ + circleAvatar, + Visibility( + visible: chatRoom.isFavorite, + child: Positioned( + right: 0, + bottom: 0, + child: Container( + padding: const EdgeInsets.all(1), + decoration: BoxDecoration( + color: Theme.of(context).primaryColor.withAlpha(200), + borderRadius: BorderRadius.circular(90.0), + ), + child: const Icon(Icons.star, color: Colors.amberAccent, size: 15), + ), + ), + ) + ], + ), title: Text(chatRoom.displayName), subtitle: Text("${Jiffy.parseFromMillisecondsSinceEpoch(chatRoom.lastMessage.timestamp * 1000).fromNow()}: ${RichObjectStringProcessor.parseToString(chatRoom.lastMessage.message.replaceAll("\n", " "), chatRoom.lastMessage.messageParameters)}", overflow: TextOverflow.ellipsis), - trailing: Visibility( - visible: chatRoom.unreadMessages > 0, - child: Container( - padding: const EdgeInsets.all(1), - decoration: BoxDecoration( - color: Theme.of(context).primaryColor, - borderRadius: BorderRadius.circular(30), - ), - constraints: const BoxConstraints( - minWidth: 20, - minHeight: 20, - ), - child: Text( - "${chatRoom.unreadMessages}", - style: const TextStyle( - color: Colors.white, - fontSize: 15, + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Visibility( + visible: chatRoom.unreadMessages > 0, + child: Container( + padding: const EdgeInsets.all(1), + decoration: BoxDecoration( + color: Theme.of(context).primaryColor, + borderRadius: BorderRadius.circular(30), + ), + constraints: const BoxConstraints( + minWidth: 20, + minHeight: 20, + ), + child: Text( + "${chatRoom.unreadMessages}", + style: const TextStyle( + color: Colors.white, + fontSize: 15, + ), + textAlign: TextAlign.center, + ), ), - textAlign: TextAlign.center, ), - ), + ], ), - leading: circleAvatar, onTap: () async { + setCurrentAsRead(); PersistentNavBarNavigator.pushNewScreen( context, screen: ChatView(room: chatRoom, selfId: username, avatar: circleAvatar), withNavBar: false ); }, + onLongPress: () { + showDialog(context: context, builder: (context) => SimpleDialog( + children: [ + Visibility( + visible: chatRoom.unreadMessages > 0, + replacement: ListTile( + leading: const Icon(Icons.mark_chat_unread_outlined), + title: const Text("Als ungelesen markieren"), + onTap: () { + SetReadMarker(chatRoom.token, false).run().then((value) => _query(renew: true)); + Navigator.of(context).pop(); + }, + ), + child: ListTile( + leading: const Icon(Icons.mark_chat_read_outlined), + title: const Text("Als gelesen markieren"), + onTap: () { + setCurrentAsRead(); + Navigator.of(context).pop(); + }, + ), + ), + Visibility( + visible: chatRoom.isFavorite, + replacement: ListTile( + leading: const Icon(Icons.star_outline), + title: const Text("Zu favoriten hinzufügen"), + onTap: () { + SetFavorite(chatRoom.token, true).run().then((value) => _query(renew: true)); + Navigator.of(context).pop(); + }, + ), + child: ListTile( + leading: const Icon(Icons.stars_outlined), + title: const Text("Von favoriten entfernen"), + onTap: () { + SetFavorite(chatRoom.token, false).run().then((value) => _query(renew: true)); + Navigator.of(context).pop(); + }, + ), + ), + ListTile( + leading: const Icon(Icons.delete_outline), + title: const Text("Konversation verlassen"), + onTap: () { + ConfirmDialog( + title: "Chat verlassen", + content: "Du benötigst ggf. eine Einladung um erneut beizutreten.", + confirmButton: "Löschen", + onConfirm: () { + LeaveRoom(chatRoom.token).run().then((value) => _query(renew: true)); + Navigator.of(context).pop(); + }, + ).asDialog(context); + }, + ), + ], + )); + }, )); } return RefreshIndicator( color: Theme.of(context).primaryColor, onRefresh: () { - Provider.of<ChatListProps>(context, listen: false).run(renew: true); + _query(renew: true); return Future.delayed(const Duration(seconds: 3)); }, child: ListView(children: chats), diff --git a/lib/widget/confirmDialog.dart b/lib/widget/confirmDialog.dart index b6705a4..36d8e0c 100644 --- a/lib/widget/confirmDialog.dart +++ b/lib/widget/confirmDialog.dart @@ -25,8 +25,8 @@ class ConfirmDialog extends StatelessWidget { Navigator.of(context).pop(); }, child: Text(cancelButton)), TextButton(onPressed: () { - onConfirm(); Navigator.of(context).pop(); + onConfirm(); }, child: Text(confirmButton)), ], ); @@ -37,7 +37,7 @@ class ConfirmDialog extends StatelessWidget { context: context, builder: (context) => ConfirmDialog( title: "Link öffnen", - content: "Möchtest du den folgenden Link öffnen?\n${url}", + content: "Möchtest du den folgenden Link öffnen?\n$url", confirmButton: "Öffnen", onConfirm: () => launchUrl(Uri.parse(url), mode: LaunchMode.externalApplication), ),