diff --git a/assets/l10n/intl_fr.arb b/assets/l10n/intl_fr.arb index bfdf87bd01..f38215d6fa 100644 --- a/assets/l10n/intl_fr.arb +++ b/assets/l10n/intl_fr.arb @@ -2727,7 +2727,7 @@ "@errorPreviewingFile": {}, "youAreUploadingPhotosDoYouWantToCancelOrContinue": "Erreur de téléchargement d'image ! Voulez-vous toujours continuer à créer une discussion ?", "@youAreUploadingPhotosDoYouWantToCancelOrContinue": {}, - "unread": "Non lu", + "unread": "Ne pas lire", "@unread": {}, "company": "Entreprise", "@company": {}, diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 7739982246..f58f615644 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -256,7 +256,7 @@ class ChatListController extends State }); } - Future toggleUnread() async { + Future toggleUnreadSelections() async { await TwakeDialog.showFutureLoadingDialogFullScreen( future: () async { final markUnread = anySelectedRoomNotMarkedUnread; @@ -514,7 +514,7 @@ class ChatListController extends State ) async { switch (chatListBottomNavigatorBar) { case ChatListSelectionActions.read: - await actionWithToggleSelectMode(toggleUnread); + await actionWithToggleSelectMode(toggleUnreadSelections); return; case ChatListSelectionActions.mute: await actionWithToggleSelectMode(toggleMuted); @@ -576,11 +576,7 @@ class ChatListController extends State ) async { switch (action) { case ChatListSelectionActions.read: - await TwakeDialog.showFutureLoadingDialogFullScreen( - future: () async { - await client.getRoomById(room.id)!.markUnread(!room.markedUnread); - }, - ); + await toggleRead(room); return; case ChatListSelectionActions.pin: await togglePin(room); @@ -601,6 +597,22 @@ class ChatListController extends State } } + Future toggleRead(Room room) async { + await TwakeDialog.showFutureLoadingDialogFullScreen( + future: () async { + if (room.isUnread) { + await room.markUnread(false); + await room.setReadMarker( + room.lastEvent!.eventId, + mRead: room.lastEvent!.eventId, + ); + } else { + await room.markUnread(true); + } + }, + ); + } + Future togglePin(Room room) async { await TwakeDialog.showFutureLoadingDialogFullScreen( future: () async { diff --git a/lib/pages/chat_list/chat_list_body_view.dart b/lib/pages/chat_list/chat_list_body_view.dart index 3c0861b328..4607502a00 100644 --- a/lib/pages/chat_list/chat_list_body_view.dart +++ b/lib/pages/chat_list/chat_list_body_view.dart @@ -11,6 +11,7 @@ import 'package:fluffychat/utils/stream_extension.dart'; import 'package:fluffychat/widgets/connection_status_header.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:linagora_design_flutter/linagora_design_flutter.dart'; import 'package:matrix/matrix.dart'; @@ -40,163 +41,166 @@ class ChatListBodyView extends StatelessWidget { child: child, ); }, - child: StreamBuilder( - key: ValueKey( - controller.client.userID.toString() + - controller.activeFilter.toString() + - controller.activeSpaceId.toString(), - ), - stream: controller.client.onSync.stream - .where((s) => s.hasRoomUpdate) - .rateLimit(const Duration(seconds: 1)), - builder: (context, _) { - if (controller.activeFilter == ActiveFilter.spaces) { - return SpaceView( - controller, - scrollController: controller.scrollController, - key: Key(controller.activeSpaceId ?? 'Spaces'), - ); - } - if (controller.waitForFirstSync && - controller.client.prevBatch != null) { - if (controller.chatListBodyIsEmpty) { - return Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Padding( - padding: ChatListBodyViewStyle.paddingIconSkeletons, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SvgPicture.asset( - ImagePaths.icSkeletons, - ), - ], - ), - ), - Padding( - padding: ChatListBodyViewStyle.paddingOwnProfile, - child: FutureBuilder( - future: controller.client - .fetchOwnProfile(getFromRooms: false), - builder: (context, snapshotProfile) { - if (snapshotProfile.connectionState != - ConnectionState.done) { - return const SizedBox(); - } - final name = - snapshotProfile.data?.displayName ?? '👋'; - return Column( - children: [ - Text( - L10n.of(context)!.welcomeToTwake(name), - style: Theme.of(context).textTheme.titleLarge, - textAlign: TextAlign.center, - ), - Padding( - padding: ChatListBodyViewStyle - .paddingTextStartNewChatMessage, - child: Text( - L10n.of(context)!.startNewChatMessage, - style: Theme.of(context).textTheme.bodyMedium, - textAlign: TextAlign.center, - ), - ), - ], - ); - }, - ), - ), - ], + child: SlidableAutoCloseBehavior( + child: StreamBuilder( + key: ValueKey( + controller.client.userID.toString() + + controller.activeFilter.toString() + + controller.activeSpaceId.toString(), + ), + stream: controller.client.onSync.stream + .where((s) => s.hasRoomUpdate) + .rateLimit(const Duration(seconds: 1)), + builder: (context, _) { + if (controller.activeFilter == ActiveFilter.spaces) { + return SpaceView( + controller, + scrollController: controller.scrollController, + key: Key(controller.activeSpaceId ?? 'Spaces'), ); } - return SingleChildScrollView( - controller: controller.scrollController, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const ConnectionStatusHeader(), - AnimatedContainer( - height: ChatListBodyViewStyle.heightIsTorBrowser( - controller.isTorBrowser, - ), - duration: TwakeThemes.animationDuration, - curve: TwakeThemes.animationCurve, - clipBehavior: Clip.hardEdge, - decoration: const BoxDecoration(), - child: Material( - color: Theme.of(context).colorScheme.surface, - child: ListTile( - leading: const Icon(Icons.vpn_key), - title: Text(L10n.of(context)!.dehydrateTor), - subtitle: Text(L10n.of(context)!.dehydrateTorLong), - trailing: const Icon(Icons.chevron_right_outlined), - onTap: controller.dehydrate, + if (controller.waitForFirstSync && + controller.client.prevBatch != null) { + if (controller.chatListBodyIsEmpty) { + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: ChatListBodyViewStyle.paddingIconSkeletons, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset( + ImagePaths.icSkeletons, + ), + ], ), ), - ), - if (!controller.filteredRoomsForPinIsEmpty) - ValueListenableBuilder( - valueListenable: controller.expandRoomsForPinNotifier, - builder: (context, isExpanded, child) { - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ExpandableTitleBuilder( - title: L10n.of(context)!.countPinChat( - controller.filteredRoomsForPin.length, + Padding( + padding: ChatListBodyViewStyle.paddingOwnProfile, + child: FutureBuilder( + future: controller.client + .fetchOwnProfile(getFromRooms: false), + builder: (context, snapshotProfile) { + if (snapshotProfile.connectionState != + ConnectionState.done) { + return const SizedBox(); + } + final name = + snapshotProfile.data?.displayName ?? '👋'; + return Column( + children: [ + Text( + L10n.of(context)!.welcomeToTwake(name), + style: Theme.of(context).textTheme.titleLarge, + textAlign: TextAlign.center, ), - isExpanded: isExpanded, - onTap: - controller.expandRoomsForPinNotifier.toggle, - ), - if (isExpanded) child!, - ], - ); - }, - child: ChatListViewBuilder( - controller: controller, - rooms: controller.filteredRoomsForPin, + Padding( + padding: ChatListBodyViewStyle + .paddingTextStartNewChatMessage, + child: Text( + L10n.of(context)!.startNewChatMessage, + style: + Theme.of(context).textTheme.bodyMedium, + textAlign: TextAlign.center, + ), + ), + ], + ); + }, + ), + ), + ], + ); + } + return SingleChildScrollView( + controller: controller.scrollController, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const ConnectionStatusHeader(), + AnimatedContainer( + height: ChatListBodyViewStyle.heightIsTorBrowser( + controller.isTorBrowser, + ), + duration: TwakeThemes.animationDuration, + curve: TwakeThemes.animationCurve, + clipBehavior: Clip.hardEdge, + decoration: const BoxDecoration(), + child: Material( + color: Theme.of(context).colorScheme.surface, + child: ListTile( + leading: const Icon(Icons.vpn_key), + title: Text(L10n.of(context)!.dehydrateTor), + subtitle: Text(L10n.of(context)!.dehydrateTorLong), + trailing: const Icon(Icons.chevron_right_outlined), + onTap: controller.dehydrate, + ), ), ), - if (!controller.filteredRoomsForAllIsEmpty) - ValueListenableBuilder( - valueListenable: controller.expandRoomsForAllNotifier, - builder: (context, isExpanded, child) { - return Padding( - padding: ChatListBodyViewStyle - .paddingTopExpandableTitleBuilder, - child: Column( + if (!controller.filteredRoomsForPinIsEmpty) + ValueListenableBuilder( + valueListenable: controller.expandRoomsForPinNotifier, + builder: (context, isExpanded, child) { + return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ ExpandableTitleBuilder( - title: L10n.of(context)!.countAllChat( - controller.filteredRoomsForAll.length, + title: L10n.of(context)!.countPinChat( + controller.filteredRoomsForPin.length, ), isExpanded: isExpanded, onTap: controller - .expandRoomsForAllNotifier.toggle, + .expandRoomsForPinNotifier.toggle, ), if (isExpanded) child!, ], - ), - ); - }, - child: ChatListViewBuilder( - controller: controller, - rooms: controller.filteredRoomsForAll, + ); + }, + child: ChatListViewBuilder( + controller: controller, + rooms: controller.filteredRoomsForPin, + ), ), - ), - ], - ), - ); - } - return const SizedBox.shrink(); - }, + if (!controller.filteredRoomsForAllIsEmpty) + ValueListenableBuilder( + valueListenable: controller.expandRoomsForAllNotifier, + builder: (context, isExpanded, child) { + return Padding( + padding: ChatListBodyViewStyle + .paddingTopExpandableTitleBuilder, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ExpandableTitleBuilder( + title: L10n.of(context)!.countAllChat( + controller.filteredRoomsForAll.length, + ), + isExpanded: isExpanded, + onTap: controller + .expandRoomsForAllNotifier.toggle, + ), + if (isExpanded) child!, + ], + ), + ); + }, + child: ChatListViewBuilder( + controller: controller, + rooms: controller.filteredRoomsForAll, + ), + ), + ], + ), + ); + } + return const SizedBox.shrink(); + }, + ), ), ), ); diff --git a/lib/pages/chat_list/chat_list_view_builder.dart b/lib/pages/chat_list/chat_list_view_builder.dart index e8643bf9ff..15d944a79e 100644 --- a/lib/pages/chat_list/chat_list_view_builder.dart +++ b/lib/pages/chat_list/chat_list_view_builder.dart @@ -6,6 +6,7 @@ import 'package:fluffychat/pages/chat_list/chat_list_view_style.dart'; import 'package:fluffychat/presentation/enum/chat_list/chat_list_enum.dart'; import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; +import 'package:linagora_design_flutter/linagora_design_flutter.dart'; import 'package:matrix/matrix.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -21,35 +22,32 @@ class ChatListViewBuilder extends StatelessWidget { @override Widget build(BuildContext context) { - return SlidableAutoCloseBehavior( - child: ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: rooms.length, - itemBuilder: (BuildContext context, int index) { - return ValueListenableBuilder( - valueListenable: controller.selectModeNotifier, - builder: (context, selectMode, child) { - final slidables = _getSlidables(context, rooms[index]); - if (ChatListViewStyle.responsiveUtils.isMobileOrTablet(context) && - !selectMode.isSelectMode && - slidables.isNotEmpty) { - return _SlidableChatListItem( - controller: controller, - slidables: slidables, - chatListItem: child!, - ); - } - - return child!; - }, - child: _CommonChatListItem( - controller: controller, - room: rooms[index], - ), - ); - }, - ), + return ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: rooms.length, + itemBuilder: (BuildContext context, int index) { + return ValueListenableBuilder( + valueListenable: controller.selectModeNotifier, + builder: (context, selectMode, child) { + final slidables = _getSlidables(context, rooms[index]); + if (ChatListViewStyle.responsiveUtils.isMobileOrTablet(context) && + !selectMode.isSelectMode && + slidables.isNotEmpty) { + return _SlidableChatListItem( + controller: controller, + slidables: slidables, + chatListItem: child!, + ); + } + return child!; + }, + child: _CommonChatListItem( + controller: controller, + room: rooms[index], + ), + ); + }, ); } @@ -58,6 +56,18 @@ class ChatListViewBuilder extends StatelessWidget { if (!room.isInvitation) ...[ SlidableAction( autoClose: true, + padding: ChatListViewStyle.slidablePadding, + label: + room.isUnread ? L10n.of(context)!.read : L10n.of(context)!.unread, + icon: room.isUnread ? Icons.mark_chat_read : Icons.mark_chat_unread, + onPressed: (_) => controller.toggleRead(room), + foregroundColor: Theme.of(context).colorScheme.onPrimary, + backgroundColor: LinagoraRefColors.material().neutral[70] ?? + ChatListViewStyle.readSlidableColorRaw, + ), + SlidableAction( + autoClose: true, + padding: ChatListViewStyle.slidablePadding, label: room.isFavourite ? L10n.of(context)!.unpin : L10n.of(context)!.pin, @@ -139,7 +149,7 @@ class _SlidableChatListItem extends StatelessWidget { groupTag: 'slidable_list', endActionPane: ActionPane( motion: const ScrollMotion(), - extentRatio: ChatListViewStyle.slidableExtentRatio, + extentRatio: ChatListViewStyle.slidableExtentRatio(slidables.length), children: slidables, ), child: chatListItem, diff --git a/lib/pages/chat_list/chat_list_view_style.dart b/lib/pages/chat_list/chat_list_view_style.dart index 69637e608d..99c4e45bee 100644 --- a/lib/pages/chat_list/chat_list_view_style.dart +++ b/lib/pages/chat_list/chat_list_view_style.dart @@ -5,12 +5,17 @@ import 'package:flutter/material.dart'; class ChatListViewStyle { static final responsiveUtils = getIt.get(); - static const editIconSize = 18.0; + static const double editIconSize = 18.0; static Size preferredSizeAppBar(BuildContext context) => const Size.fromHeight(120); // Between 0 and 1, scale on actions length - static const double slidableExtentRatio = 0.25; + static const double slidableSizeRatio = 0.23; + static double slidableExtentRatio(int slidablesLength) { + return slidableSizeRatio * slidablesLength; + } + static const Color pinSlidableColorRaw = Color(0xFF00C853); + static const Color readSlidableColorRaw = Color(0xFFAEAAAE); }