From c368fb6fe5f2a820a20156f653111a8f764d6b53 Mon Sep 17 00:00:00 2001 From: Nutcake Date: Thu, 25 May 2023 20:19:03 +0200 Subject: [PATCH] Improve message list visuals --- lib/clients/messaging_client.dart | 4 +- lib/widgets/friends/friends_list.dart | 1 + lib/widgets/messages/messages_list.dart | 108 +++++++++++++++--------- 3 files changed, 71 insertions(+), 42 deletions(-) diff --git a/lib/clients/messaging_client.dart b/lib/clients/messaging_client.dart index 110a050..0108ac5 100644 --- a/lib/clients/messaging_client.dart +++ b/lib/clients/messaging_client.dart @@ -72,6 +72,7 @@ class MessagingClient extends ChangeNotifier { box.delete(_lastUpdateKey); await refreshFriendsListWithErrorHandler(); await _refreshUnreads(); + _unreadSafeguard = Timer.periodic(_unreadSafeguardDuration, (timer) => _refreshUnreads()); }); _startWebsocket(); _notifyOnlineTimer = Timer.periodic(const Duration(seconds: 60), (timer) async { @@ -85,6 +86,7 @@ class MessagingClient extends ChangeNotifier { void dispose() { _autoRefresh?.cancel(); _notifyOnlineTimer?.cancel(); + _unreadSafeguard?.cancel(); _wsChannel?.close(); super.dispose(); } @@ -217,12 +219,10 @@ class MessagingClient extends ChangeNotifier { } Future _refreshUnreads() async { - _unreadSafeguard?.cancel(); try { final unreadMessages = await MessageApi.getUserMessages(_apiClient, unreadOnly: true); updateAllUnreads(unreadMessages.toList()); } catch (_) {} - _unreadSafeguard = Timer(_unreadSafeguardDuration, _refreshUnreads); } void _sortFriendsCache() { diff --git a/lib/widgets/friends/friends_list.dart b/lib/widgets/friends/friends_list.dart index b67331c..d9e3dad 100644 --- a/lib/widgets/friends/friends_list.dart +++ b/lib/widgets/friends/friends_list.dart @@ -252,6 +252,7 @@ class _FriendsListState extends State { friends.sort((a, b) => a.username.length.compareTo(b.username.length)); } return ListView.builder( + physics: const BouncingScrollPhysics(), itemCount: friends.length, itemBuilder: (context, index) { final friend = friends[index]; diff --git a/lib/widgets/messages/messages_list.dart b/lib/widgets/messages/messages_list.dart index 17768da..5e5857e 100644 --- a/lib/widgets/messages/messages_list.dart +++ b/lib/widgets/messages/messages_list.dart @@ -1,4 +1,3 @@ -import 'package:contacts_plus_plus/auxiliary.dart'; import 'package:contacts_plus_plus/clients/audio_cache_client.dart'; import 'package:contacts_plus_plus/clients/messaging_client.dart'; import 'package:contacts_plus_plus/models/friend.dart'; @@ -24,6 +23,7 @@ class _MessagesListState extends State with SingleTickerProviderSt final ScrollController _sessionListScrollController = ScrollController(); bool _showSessionListScrollChevron = false; + bool _sessionListOpen = false; double get _shevronOpacity => _showSessionListScrollChevron ? 1.0 : 0.0; @@ -51,7 +51,6 @@ class _MessagesListState extends State with SingleTickerProviderSt }); } - @override Widget build(BuildContext context) { final sessions = widget.friend.userStatus.activeSessions; @@ -81,50 +80,78 @@ class _MessagesListState extends State with SingleTickerProviderSt ), ], ), + bottom: sessions.isNotEmpty && _sessionListOpen ? null : PreferredSize( + preferredSize: const Size.fromHeight(1), + child: Container( + height: 1, + color: Colors.black, + ), + ), + actions: [ + if (sessions.isNotEmpty) AnimatedRotation( + turns: _sessionListOpen ? -1/4 : 1/4, + duration: const Duration(milliseconds: 200), + child: IconButton( + onPressed: () { + setState(() { + _sessionListOpen = !_sessionListOpen; + }); + }, + icon: const Icon(Icons.chevron_right), + ), + ), + const SizedBox(width: 4,) + ], scrolledUnderElevation: 0.0, backgroundColor: appBarColor, + surfaceTintColor: Colors.transparent, + shadowColor: Colors.transparent, ), body: Column( children: [ - if (sessions.isNotEmpty) Container( - constraints: const BoxConstraints(maxHeight: 64), - decoration: BoxDecoration( - color: appBarColor, - border: const Border(bottom: BorderSide(width: 1, color: Colors.black),) - ), - child: Stack( - children: [ - ListView.builder( - controller: _sessionListScrollController, - scrollDirection: Axis.horizontal, - itemCount: sessions.length, - itemBuilder: (context, index) => SessionTile(session: sessions[index]), - ), - AnimatedOpacity( - opacity: _shevronOpacity, - curve: Curves.easeOut, - duration: const Duration(milliseconds: 200), - child: Align( - alignment: Alignment.centerRight, - child: Container( - padding: const EdgeInsets.only(left: 16, right: 4, top: 1, bottom: 1), - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.centerLeft, - end: Alignment.centerRight, - colors: [ - appBarColor.withOpacity(0), - appBarColor, - appBarColor, - ], - ), - ), - height: double.infinity, - child: const Icon(Icons.chevron_right), - ), + if (sessions.isNotEmpty) AnimatedSwitcher( + duration: const Duration(milliseconds: 200), + transitionBuilder: (child, animation) => SizeTransition(sizeFactor: animation, axis: Axis.vertical, child: child), + child: sessions.isEmpty || !_sessionListOpen ? null : Container( + constraints: const BoxConstraints(maxHeight: 64), + decoration: BoxDecoration( + color: appBarColor, + border: const Border(bottom: BorderSide(width: 1, color: Colors.black),) + ), + child: Stack( + children: [ + ListView.builder( + controller: _sessionListScrollController, + scrollDirection: Axis.horizontal, + itemCount: sessions.length, + itemBuilder: (context, index) => SessionTile(session: sessions[index]), ), - ) - ], + AnimatedOpacity( + opacity: _shevronOpacity, + curve: Curves.easeOut, + duration: const Duration(milliseconds: 200), + child: Align( + alignment: Alignment.centerRight, + child: Container( + padding: const EdgeInsets.only(left: 16, right: 4, top: 1, bottom: 1), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + appBarColor.withOpacity(0), + appBarColor, + appBarColor, + ], + ), + ), + height: double.infinity, + child: const Icon(Icons.chevron_right), + ), + ), + ) + ], + ), ), ), Expanded( @@ -176,6 +203,7 @@ class _MessagesListState extends State with SingleTickerProviderSt create: (BuildContext context) => AudioCacheClient(), child: ListView.builder( reverse: true, + physics: const BouncingScrollPhysics(), itemCount: cache.messages.length, itemBuilder: (context, index) { final entry = cache.messages[index];