Better last message preview

This commit is contained in:
LittleSheep 2024-10-05 15:11:48 +08:00
parent f353c05cb5
commit 147879e4d8
5 changed files with 115 additions and 45 deletions

View File

@ -1,3 +1,6 @@
import 'dart:convert';
import 'package:collection/collection.dart';
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:get/get.dart' hide Value; import 'package:get/get.dart' hide Value;
import 'package:solian/exceptions/request.dart'; import 'package:solian/exceptions/request.dart';
@ -182,4 +185,26 @@ class MessagesFetchingProvider extends GetxController {
..orderBy([(t) => OrderingTerm.desc(t.id)])) ..orderBy([(t) => OrderingTerm.desc(t.id)]))
.getSingleOrNull(); .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);
}
} }

View File

@ -44,7 +44,16 @@ class _ChatScreenState extends State<ChatScreen> {
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,
child: Scaffold( child: Scaffold(
appBar: AppBar( 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), title: AppBarTitle('chat'.tr),
centerTitle: true, centerTitle: true,
toolbarHeight: AppTheme.toolbarHeight(context), toolbarHeight: AppTheme.toolbarHeight(context),
@ -110,13 +119,6 @@ class _ChatScreenState extends State<ChatScreen> {
return Column( return Column(
children: [ children: [
Obx(() {
if (_channels.isLoading.isFalse) {
return const SizedBox.shrink();
} else {
return const LinearProgressIndicator();
}
}),
const ChatCallCurrentIndicator(), const ChatCallCurrentIndicator(),
Expanded( Expanded(
child: CenteredContainer( child: CenteredContainer(

View File

@ -7,6 +7,7 @@ import 'package:get/get.dart';
import 'package:solian/controllers/chat_events_controller.dart'; import 'package:solian/controllers/chat_events_controller.dart';
import 'package:solian/models/channel.dart'; import 'package:solian/models/channel.dart';
import 'package:solian/platform.dart'; import 'package:solian/platform.dart';
import 'package:solian/providers/database/database.dart';
import 'package:solian/router.dart'; import 'package:solian/router.dart';
import 'package:solian/widgets/account/account_avatar.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 List<Channel> _globalChannels = List.empty(growable: true);
final Map<String, List<Channel>> _inRealms = {}; final Map<String, List<Channel>> _inRealms = {};
Map<int, LocalMessageEventTableData>? _lastMessages;
final ChatEventController _eventController = ChatEventController(); 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() { void _mapChannels() {
_inRealms.clear(); _inRealms.clear();
_globalChannels.clear(); _globalChannels.clear();
@ -71,7 +83,9 @@ class _ChannelListWidgetState extends State<ChannelListWidget> {
void initState() { void initState() {
super.initState(); super.initState();
_mapChannels(); _mapChannels();
_eventController.initialize(); _eventController.initialize().then((_) {
_loadLastMessages();
});
} }
void _gotoChannel(Channel item) { 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) { if (PlatformInfo.isWeb) {
return Text('channelDirectDescription'.trParams( return otherside != null
{'username': '@${otherside.account.name}'}, ? Text(
)); 'channelDirectDescription'.trParams(
{'username': '@${otherside.account.name}'},
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
)
: Text(
item.description,
maxLines: 1,
overflow: TextOverflow.ellipsis,
);
} }
return FutureBuilder( return AnimatedSwitcher(
future: Future.delayed( switchInCurve: Curves.easeIn,
const Duration(milliseconds: 500), switchOutCurve: Curves.easeOut,
() => _eventController.src.getLastInChannel(item), transitionBuilder: (child, animation) {
), return FadeTransition(opacity: animation, child: child);
builder: (context, snapshot) { },
if (!snapshot.hasData && snapshot.data == null) { duration: const Duration(milliseconds: 300),
return Text('channelDirectDescription'.trParams( child: (_lastMessages == null || _lastMessages![item.id] == null)
{'username': '@${otherside.account.name}'}, ? Builder(builder: (context) {
)); return otherside != null
} ? Text(
'channelDirectDescription'.trParams(
final data = snapshot.data!.data!; {'username': '@${otherside.account.name}'},
return Text( ),
'${data.sender.account.nick}: ${data.body['text'] ?? 'Unsupported message to preview'}', maxLines: 1,
maxLines: 1, overflow: TextOverflow.ellipsis,
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, leading: avatar,
contentPadding: padding, contentPadding: padding,
title: Text(otherside.account.nick), title: Text(otherside.account.nick),
subtitle: !widget.isDense subtitle:
? _buildDirectMessageDescription(item, otherside) !widget.isDense ? _buildChannelDescription(item, otherside) : null,
: null,
onTap: () => _gotoChannel(item), onTap: () => _gotoChannel(item),
); );
} else { } else {
@ -192,13 +238,7 @@ class _ChannelListWidgetState extends State<ChannelListWidget> {
leading: avatar, leading: avatar,
contentPadding: padding, contentPadding: padding,
title: Text(item.name), title: Text(item.name),
subtitle: !widget.isDense subtitle: !widget.isDense ? _buildChannelDescription(item, null) : null,
? Text(
item.description,
maxLines: 1,
overflow: TextOverflow.ellipsis,
)
: null,
onTap: () => _gotoChannel(item), onTap: () => _gotoChannel(item),
); );
} }

View File

@ -14,16 +14,16 @@ abstract class AppNavigation {
label: 'explore'.tr, label: 'explore'.tr,
page: 'explore', page: 'explore',
), ),
AppNavigationDestination(
icon: const Icon(Icons.workspaces),
label: 'realms'.tr,
page: 'realms',
),
AppNavigationDestination( AppNavigationDestination(
icon: const Icon(Icons.forum), icon: const Icon(Icons.forum),
label: 'chat'.tr, label: 'chat'.tr,
page: 'chat', page: 'chat',
), ),
AppNavigationDestination(
icon: const Icon(Icons.workspaces),
label: 'realms'.tr,
page: 'realms',
),
AppNavigationDestination( AppNavigationDestination(
icon: const AppAccountWidget(), icon: const AppAccountWidget(),
label: 'account'.tr, label: 'account'.tr,

View File

@ -75,6 +75,9 @@ class RealmSwitcher extends StatelessWidget {
padding: EdgeInsets.symmetric(horizontal: 16), padding: EdgeInsets.symmetric(horizontal: 16),
height: 48, height: 48,
width: 200, width: 200,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(16)),
),
), ),
menuItemStyleData: const MenuItemStyleData( menuItemStyleData: const MenuItemStyleData(
height: 48, height: 48,