⚡ Better last message preview
This commit is contained in:
parent
f353c05cb5
commit
147879e4d8
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
|
@ -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),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user