⚡ Better last message preview
This commit is contained in:
		| @@ -1,3 +1,6 @@ | ||||
| import 'dart:convert'; | ||||
|  | ||||
| import 'package:collection/collection.dart'; | ||||
| import 'package:drift/drift.dart'; | ||||
| import 'package:get/get.dart' hide Value; | ||||
| import 'package:solian/exceptions/request.dart'; | ||||
| @@ -182,4 +185,26 @@ class MessagesFetchingProvider extends GetxController { | ||||
|           ..orderBy([(t) => OrderingTerm.desc(t.id)])) | ||||
|         .getSingleOrNull(); | ||||
|   } | ||||
|  | ||||
|   Future<Map<int, List<LocalMessageEventTableData>>> | ||||
|       getLastInAllChannels() async { | ||||
|     final database = Get.find<DatabaseProvider>().database; | ||||
|     final rows = await database.customSelect(''' | ||||
|     SELECT id, channel_id, data, created_at | ||||
|     FROM ${database.localMessageEventTable.actualTableName} | ||||
|     WHERE (channel_id, created_at) IN ( | ||||
|       SELECT channel_id, MAX(created_at) | ||||
|       FROM ${database.localMessageEventTable.actualTableName} | ||||
|       GROUP BY channel_id | ||||
|     ) | ||||
|     ''', readsFrom: {database.localMessageEventTable}).get(); | ||||
|     return rows.map((row) { | ||||
|       return LocalMessageEventTableData( | ||||
|         id: row.read<int>('id'), | ||||
|         channelId: row.read<int>('channel_id'), | ||||
|         data: Event.fromJson(jsonDecode(row.read<String>('data'))), | ||||
|         createdAt: row.read<DateTime>('created_at'), | ||||
|       ); | ||||
|     }).groupListsBy((x) => x.channelId); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -44,7 +44,16 @@ class _ChatScreenState extends State<ChatScreen> { | ||||
|       color: Theme.of(context).colorScheme.surface, | ||||
|       child: Scaffold( | ||||
|         appBar: AppBar( | ||||
|           leading: AppBarLeadingButton.adaptive(context), | ||||
|           leading: Obx(() { | ||||
|             final adaptive = AppBarLeadingButton.adaptive(context); | ||||
|             if (adaptive != null) return adaptive; | ||||
|             if (_channels.isLoading.value) { | ||||
|               return const CircularProgressIndicator( | ||||
|                 strokeWidth: 3, | ||||
|               ).paddingAll(18); | ||||
|             } | ||||
|             return const SizedBox.shrink(); | ||||
|           }), | ||||
|           title: AppBarTitle('chat'.tr), | ||||
|           centerTitle: true, | ||||
|           toolbarHeight: AppTheme.toolbarHeight(context), | ||||
| @@ -110,13 +119,6 @@ class _ChatScreenState extends State<ChatScreen> { | ||||
|  | ||||
|           return Column( | ||||
|             children: [ | ||||
|               Obx(() { | ||||
|                 if (_channels.isLoading.isFalse) { | ||||
|                   return const SizedBox.shrink(); | ||||
|                 } else { | ||||
|                   return const LinearProgressIndicator(); | ||||
|                 } | ||||
|               }), | ||||
|               const ChatCallCurrentIndicator(), | ||||
|               Expanded( | ||||
|                 child: CenteredContainer( | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import 'package:get/get.dart'; | ||||
| import 'package:solian/controllers/chat_events_controller.dart'; | ||||
| import 'package:solian/models/channel.dart'; | ||||
| import 'package:solian/platform.dart'; | ||||
| import 'package:solian/providers/database/database.dart'; | ||||
| import 'package:solian/router.dart'; | ||||
| import 'package:solian/widgets/account/account_avatar.dart'; | ||||
|  | ||||
| @@ -38,8 +39,19 @@ class _ChannelListWidgetState extends State<ChannelListWidget> { | ||||
|   final List<Channel> _globalChannels = List.empty(growable: true); | ||||
|   final Map<String, List<Channel>> _inRealms = {}; | ||||
|  | ||||
|   Map<int, LocalMessageEventTableData>? _lastMessages; | ||||
|  | ||||
|   final ChatEventController _eventController = ChatEventController(); | ||||
|  | ||||
|   Future<void> _loadLastMessages() async { | ||||
|     final messages = await _eventController.src.getLastInAllChannels(); | ||||
|     setState(() { | ||||
|       _lastMessages = messages | ||||
|           .map((k, v) => MapEntry(k, v.firstOrNull)) | ||||
|           .cast<int, LocalMessageEventTableData>(); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   void _mapChannels() { | ||||
|     _inRealms.clear(); | ||||
|     _globalChannels.clear(); | ||||
| @@ -71,7 +83,9 @@ class _ChannelListWidgetState extends State<ChannelListWidget> { | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     _mapChannels(); | ||||
|     _eventController.initialize(); | ||||
|     _eventController.initialize().then((_) { | ||||
|       _loadLastMessages(); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   void _gotoChannel(Channel item) { | ||||
| @@ -98,30 +112,63 @@ class _ChannelListWidgetState extends State<ChannelListWidget> { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   Widget _buildDirectMessageDescription(Channel item, ChannelMember otherside) { | ||||
|   Widget _buildChannelDescription(Channel item, ChannelMember? otherside) { | ||||
|     if (PlatformInfo.isWeb) { | ||||
|       return Text('channelDirectDescription'.trParams( | ||||
|         {'username': '@${otherside.account.name}'}, | ||||
|       )); | ||||
|       return otherside != null | ||||
|           ? Text( | ||||
|               'channelDirectDescription'.trParams( | ||||
|                 {'username': '@${otherside.account.name}'}, | ||||
|               ), | ||||
|               maxLines: 1, | ||||
|               overflow: TextOverflow.ellipsis, | ||||
|             ) | ||||
|           : Text( | ||||
|               item.description, | ||||
|               maxLines: 1, | ||||
|               overflow: TextOverflow.ellipsis, | ||||
|             ); | ||||
|     } | ||||
|  | ||||
|     return FutureBuilder( | ||||
|       future: Future.delayed( | ||||
|         const Duration(milliseconds: 500), | ||||
|         () => _eventController.src.getLastInChannel(item), | ||||
|       ), | ||||
|       builder: (context, snapshot) { | ||||
|         if (!snapshot.hasData && snapshot.data == null) { | ||||
|           return Text('channelDirectDescription'.trParams( | ||||
|             {'username': '@${otherside.account.name}'}, | ||||
|           )); | ||||
|         } | ||||
|  | ||||
|         final data = snapshot.data!.data!; | ||||
|         return Text( | ||||
|           '${data.sender.account.nick}: ${data.body['text'] ?? 'Unsupported message to preview'}', | ||||
|           maxLines: 1, | ||||
|           overflow: TextOverflow.ellipsis, | ||||
|     return AnimatedSwitcher( | ||||
|       switchInCurve: Curves.easeIn, | ||||
|       switchOutCurve: Curves.easeOut, | ||||
|       transitionBuilder: (child, animation) { | ||||
|         return FadeTransition(opacity: animation, child: child); | ||||
|       }, | ||||
|       duration: const Duration(milliseconds: 300), | ||||
|       child: (_lastMessages == null || _lastMessages![item.id] == null) | ||||
|           ? Builder(builder: (context) { | ||||
|               return otherside != null | ||||
|                   ? Text( | ||||
|                       'channelDirectDescription'.trParams( | ||||
|                         {'username': '@${otherside.account.name}'}, | ||||
|                       ), | ||||
|                       maxLines: 1, | ||||
|                       overflow: TextOverflow.ellipsis, | ||||
|                     ) | ||||
|                   : Text( | ||||
|                       item.description, | ||||
|                       maxLines: 1, | ||||
|                       overflow: TextOverflow.ellipsis, | ||||
|                     ); | ||||
|             }) | ||||
|           : Builder( | ||||
|               builder: (context) { | ||||
|                 final data = _lastMessages![item.id]!.data!; | ||||
|                 return Text( | ||||
|                   '${data.sender.account.nick}: ${data.body['text'] ?? 'Unsupported message to preview'}', | ||||
|                   maxLines: 1, | ||||
|                   overflow: TextOverflow.ellipsis, | ||||
|                 ); | ||||
|               }, | ||||
|             ), | ||||
|       layoutBuilder: (currentChild, previousChildren) { | ||||
|         return Stack( | ||||
|           alignment: Alignment.centerLeft, | ||||
|           children: <Widget>[ | ||||
|             ...previousChildren, | ||||
|             if (currentChild != null) currentChild, | ||||
|           ], | ||||
|         ); | ||||
|       }, | ||||
|     ); | ||||
| @@ -157,9 +204,8 @@ class _ChannelListWidgetState extends State<ChannelListWidget> { | ||||
|         leading: avatar, | ||||
|         contentPadding: padding, | ||||
|         title: Text(otherside.account.nick), | ||||
|         subtitle: !widget.isDense | ||||
|             ? _buildDirectMessageDescription(item, otherside) | ||||
|             : null, | ||||
|         subtitle: | ||||
|             !widget.isDense ? _buildChannelDescription(item, otherside) : null, | ||||
|         onTap: () => _gotoChannel(item), | ||||
|       ); | ||||
|     } else { | ||||
| @@ -192,13 +238,7 @@ class _ChannelListWidgetState extends State<ChannelListWidget> { | ||||
|         leading: avatar, | ||||
|         contentPadding: padding, | ||||
|         title: Text(item.name), | ||||
|         subtitle: !widget.isDense | ||||
|             ? Text( | ||||
|                 item.description, | ||||
|                 maxLines: 1, | ||||
|                 overflow: TextOverflow.ellipsis, | ||||
|               ) | ||||
|             : null, | ||||
|         subtitle: !widget.isDense ? _buildChannelDescription(item, null) : null, | ||||
|         onTap: () => _gotoChannel(item), | ||||
|       ); | ||||
|     } | ||||
|   | ||||
| @@ -14,16 +14,16 @@ abstract class AppNavigation { | ||||
|       label: 'explore'.tr, | ||||
|       page: 'explore', | ||||
|     ), | ||||
|     AppNavigationDestination( | ||||
|       icon: const Icon(Icons.workspaces), | ||||
|       label: 'realms'.tr, | ||||
|       page: 'realms', | ||||
|     ), | ||||
|     AppNavigationDestination( | ||||
|       icon: const Icon(Icons.forum), | ||||
|       label: 'chat'.tr, | ||||
|       page: 'chat', | ||||
|     ), | ||||
|     AppNavigationDestination( | ||||
|       icon: const Icon(Icons.workspaces), | ||||
|       label: 'realms'.tr, | ||||
|       page: 'realms', | ||||
|     ), | ||||
|     AppNavigationDestination( | ||||
|       icon: const AppAccountWidget(), | ||||
|       label: 'account'.tr, | ||||
|   | ||||
| @@ -75,6 +75,9 @@ class RealmSwitcher extends StatelessWidget { | ||||
|             padding: EdgeInsets.symmetric(horizontal: 16), | ||||
|             height: 48, | ||||
|             width: 200, | ||||
|             decoration: BoxDecoration( | ||||
|               borderRadius: BorderRadius.all(Radius.circular(16)), | ||||
|             ), | ||||
|           ), | ||||
|           menuItemStyleData: const MenuItemStyleData( | ||||
|             height: 48, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user