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: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);
}
}

View File

@ -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(

View File

@ -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),
);
}

View File

@ -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,

View File

@ -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,