load old messages
This commit is contained in:
parent
0bb3961cd2
commit
de561e0b95
10 changed files with 130 additions and 73 deletions
30
lib/controllers/events_controller.dart
Normal file
30
lib/controllers/events_controller.dart
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
|
import "package:matrix/matrix.dart";
|
||||||
|
import "package:nexus/controllers/from_controller.dart";
|
||||||
|
|
||||||
|
class EventsController extends AsyncNotifier<GetRoomEventsResponse> {
|
||||||
|
EventsController(this.room);
|
||||||
|
final Room room;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<GetRoomEventsResponse> build({String? from}) async {
|
||||||
|
final response = await room.client.getRoomEvents(
|
||||||
|
room.id,
|
||||||
|
Direction.b,
|
||||||
|
from: from,
|
||||||
|
limit: 32,
|
||||||
|
);
|
||||||
|
ref.watch(FromController.provider(room).notifier).set(response.end);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<GetRoomEventsResponse> prev() async {
|
||||||
|
final resp = await build(from: ref.read(FromController.provider(room)));
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
static final provider = AsyncNotifierProvider.autoDispose
|
||||||
|
.family<EventsController, GetRoomEventsResponse, Room>(
|
||||||
|
EventsController.new,
|
||||||
|
);
|
||||||
|
}
|
||||||
15
lib/controllers/from_controller.dart
Normal file
15
lib/controllers/from_controller.dart
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
|
import "package:matrix/matrix.dart";
|
||||||
|
|
||||||
|
class FromController extends Notifier<String?> {
|
||||||
|
FromController(_);
|
||||||
|
@override
|
||||||
|
String? build() => null;
|
||||||
|
|
||||||
|
void set(String? value) => state = value;
|
||||||
|
|
||||||
|
static final provider =
|
||||||
|
NotifierProvider.family<FromController, String?, Room>(
|
||||||
|
FromController.new,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,20 +1,22 @@
|
||||||
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
import "package:matrix/matrix.dart";
|
import "package:matrix/matrix.dart";
|
||||||
|
|
||||||
class MembersController extends AsyncNotifier<List<MatrixEvent>> {
|
class MembersController extends AsyncNotifier<IList<MatrixEvent>> {
|
||||||
final Room room;
|
final Room room;
|
||||||
MembersController(this.room);
|
MembersController(this.room);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<MatrixEvent>> build() async =>
|
Future<IList<MatrixEvent>> build() async => IList(
|
||||||
(await room.client.getMembersByRoom(
|
(await room.client.getMembersByRoom(
|
||||||
room.id,
|
room.id,
|
||||||
notMembership: Membership.leave,
|
notMembership: Membership.leave,
|
||||||
)) ??
|
)) ??
|
||||||
[];
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
static final provider =
|
static final provider =
|
||||||
AsyncNotifierProvider.family<MembersController, List<MatrixEvent>, Room>(
|
AsyncNotifierProvider.family<MembersController, IList<MatrixEvent>, Room>(
|
||||||
MembersController.new,
|
MembersController.new,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import "package:flutter_chat_core/flutter_chat_core.dart";
|
||||||
import "package:flutter_chat_core/flutter_chat_core.dart" as chat;
|
import "package:flutter_chat_core/flutter_chat_core.dart" as chat;
|
||||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
import "package:matrix/matrix.dart";
|
import "package:matrix/matrix.dart";
|
||||||
import "package:nexus/controllers/timeline_controller.dart";
|
import "package:nexus/controllers/events_controller.dart";
|
||||||
import "package:nexus/helpers/extension_helper.dart";
|
import "package:nexus/helpers/extension_helper.dart";
|
||||||
|
|
||||||
class RoomChatController extends AsyncNotifier<ChatController> {
|
class RoomChatController extends AsyncNotifier<ChatController> {
|
||||||
|
|
@ -12,7 +12,7 @@ class RoomChatController extends AsyncNotifier<ChatController> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<ChatController> build() async {
|
Future<ChatController> build() async {
|
||||||
final timeline = await ref.watch(TimelineController.provider(room).future);
|
final response = await ref.watch(EventsController.provider(room).future);
|
||||||
|
|
||||||
ref.onDispose(
|
ref.onDispose(
|
||||||
room.client.onTimelineEvent.stream.listen((event) async {
|
room.client.onTimelineEvent.stream.listen((event) async {
|
||||||
|
|
@ -26,8 +26,10 @@ class RoomChatController extends AsyncNotifier<ChatController> {
|
||||||
|
|
||||||
return InMemoryChatController(
|
return InMemoryChatController(
|
||||||
messages: (await Future.wait(
|
messages: (await Future.wait(
|
||||||
timeline.events.map((event) => event.toMessage()),
|
response.chunk.map(
|
||||||
)).toList().reversed.nonNulls.toList(),
|
(event) => Event.fromMatrixEvent(event, room).toMessage(),
|
||||||
|
),
|
||||||
|
)).nonNulls.toList(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,13 +48,22 @@ class RoomChatController extends AsyncNotifier<ChatController> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadOlder() async {
|
Future<void> loadOlder() async {
|
||||||
await ref.watch(TimelineController.provider(room).notifier).prev();
|
final controller = await future;
|
||||||
|
final response = await ref
|
||||||
|
.watch(EventsController.provider(room).notifier)
|
||||||
|
.prev();
|
||||||
|
await controller.insertAllMessages(
|
||||||
|
(await Future.wait(
|
||||||
|
response.chunk.map(
|
||||||
|
(event) => Event.fromMatrixEvent(event, room).toMessage(),
|
||||||
|
),
|
||||||
|
)).nonNulls.toList().reversed.toList(),
|
||||||
|
index: 0,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateMessage(Message message, Message newMessage) async {
|
Future<void> updateMessage(Message message, Message newMessage) async =>
|
||||||
final controller = await future;
|
(await future).updateMessage(message, newMessage);
|
||||||
return controller.updateMessage(message, newMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> send(String message, {Message? replyTo}) async =>
|
Future<void> send(String message, {Message? replyTo}) async =>
|
||||||
await room.sendTextEvent(
|
await room.sendTextEvent(
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
import "package:collection/collection.dart";
|
import "package:collection/collection.dart";
|
||||||
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
import "package:nexus/controllers/client_controller.dart";
|
import "package:nexus/controllers/client_controller.dart";
|
||||||
import "package:nexus/helpers/extension_helper.dart";
|
import "package:nexus/helpers/extension_helper.dart";
|
||||||
import "package:nexus/models/space.dart";
|
import "package:nexus/models/space.dart";
|
||||||
|
|
||||||
class SpacesController extends AsyncNotifier<List<Space>> {
|
class SpacesController extends AsyncNotifier<IList<Space>> {
|
||||||
@override
|
@override
|
||||||
Future<List<Space>> build() async {
|
Future<IList<Space>> build() async {
|
||||||
final client = await ref.watch(ClientController.provider.future);
|
final client = await ref.watch(ClientController.provider.future);
|
||||||
|
|
||||||
final topLevel = await Future.wait(
|
final topLevel = await Future.wait(
|
||||||
|
|
@ -28,7 +29,7 @@ class SpacesController extends AsyncNotifier<List<Space>> {
|
||||||
final topLevelSpaces = topLevel.where((r) => r.roomData.isSpace).toList();
|
final topLevelSpaces = topLevel.where((r) => r.roomData.isSpace).toList();
|
||||||
final topLevelRooms = topLevel.where((r) => !r.roomData.isSpace).toList();
|
final topLevelRooms = topLevel.where((r) => !r.roomData.isSpace).toList();
|
||||||
|
|
||||||
return [
|
return IList([
|
||||||
Space(
|
Space(
|
||||||
client: client,
|
client: client,
|
||||||
title: "Home",
|
title: "Home",
|
||||||
|
|
@ -66,10 +67,10 @@ class SpacesController extends AsyncNotifier<List<Space>> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
];
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static final provider = AsyncNotifierProvider<SpacesController, List<Space>>(
|
static final provider = AsyncNotifierProvider<SpacesController, IList<Space>>(
|
||||||
SpacesController.new,
|
SpacesController.new,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
|
||||||
import "package:matrix/matrix.dart";
|
|
||||||
|
|
||||||
class TimelineController extends AsyncNotifier<Timeline> {
|
|
||||||
TimelineController(this.room);
|
|
||||||
final Room room;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Timeline> build() => room.getTimeline();
|
|
||||||
|
|
||||||
Future<void> prev() async {
|
|
||||||
final timeline = await future;
|
|
||||||
await timeline.requestHistory();
|
|
||||||
state = AsyncValue.data(timeline);
|
|
||||||
}
|
|
||||||
|
|
||||||
static final provider =
|
|
||||||
AsyncNotifierProvider.family<TimelineController, Timeline, Room>(
|
|
||||||
TimelineController.new,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -39,7 +39,9 @@ extension ToMessage on Event {
|
||||||
final metadata = {
|
final metadata = {
|
||||||
"formatted": formattedText.isEmpty ? body : formattedText,
|
"formatted": formattedText.isEmpty ? body : formattedText,
|
||||||
"eventType": type,
|
"eventType": type,
|
||||||
"displayName": senderFromMemoryOrFallback.displayName,
|
"displayName":
|
||||||
|
senderFromMemoryOrFallback.displayName ??
|
||||||
|
senderFromMemoryOrFallback.id,
|
||||||
"txnId": transactionId,
|
"txnId": transactionId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ class App extends StatelessWidget {
|
||||||
builder: (lightDynamic, darkDynamic) => LayoutBuilder(
|
builder: (lightDynamic, darkDynamic) => LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
final isDesktop = constraints.maxWidth > 650;
|
final isDesktop = constraints.maxWidth > 650;
|
||||||
|
final showMembersByDefault = constraints.maxWidth > 1000;
|
||||||
|
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
|
|
@ -47,7 +48,12 @@ class App extends StatelessWidget {
|
||||||
builder: (context) => Row(
|
builder: (context) => Row(
|
||||||
children: [
|
children: [
|
||||||
if (isDesktop) Sidebar(),
|
if (isDesktop) Sidebar(),
|
||||||
Expanded(child: RoomChat(isDesktop: isDesktop)),
|
Expanded(
|
||||||
|
child: RoomChat(
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
showMembersByDefault: showMembersByDefault,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,12 @@ import "package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart
|
||||||
|
|
||||||
class RoomChat extends HookConsumerWidget {
|
class RoomChat extends HookConsumerWidget {
|
||||||
final bool isDesktop;
|
final bool isDesktop;
|
||||||
const RoomChat({required this.isDesktop, super.key});
|
final bool showMembersByDefault;
|
||||||
|
const RoomChat({
|
||||||
|
required this.isDesktop,
|
||||||
|
required this.showMembersByDefault,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
void showContextMenu({
|
void showContextMenu({
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
|
|
@ -48,8 +53,7 @@ class RoomChat extends HookConsumerWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final replyToMessage = useState<Message?>(null);
|
final replyToMessage = useState<Message?>(null);
|
||||||
final memberListOpened = useState<bool>(isDesktop);
|
final memberListOpened = useState<bool>(showMembersByDefault);
|
||||||
final urlRegex = RegExp(r"https?://[^\s\]\(\)]+");
|
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
return ref
|
return ref
|
||||||
.watch(CurrentRoomController.provider)
|
.watch(CurrentRoomController.provider)
|
||||||
|
|
@ -105,14 +109,13 @@ class RoomChat extends HookConsumerWidget {
|
||||||
onTap: () => replyToMessage.value = message,
|
onTap: () => replyToMessage.value = message,
|
||||||
),
|
),
|
||||||
builders: Builders(
|
builders: Builders(
|
||||||
chatAnimatedListBuilder: (context, itemBuilder) {
|
chatAnimatedListBuilder: (_, itemBuilder) =>
|
||||||
return ChatAnimatedList(
|
ChatAnimatedList(
|
||||||
itemBuilder: itemBuilder,
|
itemBuilder: itemBuilder,
|
||||||
onEndReached: ref
|
onEndReached: ref
|
||||||
.watch(controllerProvider.notifier)
|
.watch(controllerProvider.notifier)
|
||||||
.loadOlder,
|
.loadOlder,
|
||||||
);
|
),
|
||||||
},
|
|
||||||
composerBuilder: (_) => ChatBox(
|
composerBuilder: (_) => ChatBox(
|
||||||
replyToMessage: replyToMessage.value,
|
replyToMessage: replyToMessage.value,
|
||||||
onDismiss: () => replyToMessage.value = null,
|
onDismiss: () => replyToMessage.value = null,
|
||||||
|
|
@ -133,14 +136,20 @@ class RoomChat extends HookConsumerWidget {
|
||||||
return SizedBox.shrink();
|
return SizedBox.shrink();
|
||||||
}
|
}
|
||||||
if (element.localName == "code") {
|
if (element.localName == "code") {
|
||||||
return SizedBox(
|
if (element.parent?.localName ==
|
||||||
width: 400,
|
"pre") {
|
||||||
child: CodeField(
|
return SizedBox(
|
||||||
name: element.className
|
width: 400,
|
||||||
.replaceAll("language-", ""),
|
child: CodeField(
|
||||||
codes: element.text,
|
name: element.className
|
||||||
),
|
.replaceAll(
|
||||||
);
|
"language-",
|
||||||
|
"",
|
||||||
|
),
|
||||||
|
codes: element.text,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (element.localName == "img") {
|
if (element.localName == "img") {
|
||||||
final src = Uri.tryParse(
|
final src = Uri.tryParse(
|
||||||
|
|
@ -204,11 +213,7 @@ class RoomChat extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
linkPreviewBuilder: (_, message, isSentByMe) =>
|
linkPreviewBuilder: (_, message, isSentByMe) =>
|
||||||
LinkPreview(
|
LinkPreview(
|
||||||
text:
|
text: message.text,
|
||||||
urlRegex
|
|
||||||
.firstMatch(message.text)
|
|
||||||
?.group(0) ??
|
|
||||||
"",
|
|
||||||
backgroundColor: isSentByMe
|
backgroundColor: isSentByMe
|
||||||
? theme.colorScheme.inversePrimary
|
? theme.colorScheme.inversePrimary
|
||||||
: theme.colorScheme.surfaceContainerLow,
|
: theme.colorScheme.surfaceContainerLow,
|
||||||
|
|
@ -306,11 +311,13 @@ class RoomChat extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
if (memberListOpened.value == true && isDesktop)
|
if (memberListOpened.value == true && showMembersByDefault)
|
||||||
MemberList(room.roomData),
|
MemberList(room.roomData),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
endDrawer: isDesktop ? null : MemberList(room.roomData),
|
endDrawer: showMembersByDefault
|
||||||
|
? null
|
||||||
|
: MemberList(room.roomData),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import "dart:math";
|
import "dart:math";
|
||||||
|
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_chat_core/flutter_chat_core.dart";
|
import "package:flutter_chat_core/flutter_chat_core.dart";
|
||||||
import "package:flutter_chat_ui/flutter_chat_ui.dart";
|
import "package:flutter_chat_ui/flutter_chat_ui.dart";
|
||||||
|
|
@ -29,13 +28,18 @@ class TopWidget extends ConsumerWidget {
|
||||||
loading: SizedBox.shrink,
|
loading: SizedBox.shrink,
|
||||||
data: (replyMessage) {
|
data: (replyMessage) {
|
||||||
if (replyMessage == null) return SizedBox.shrink();
|
if (replyMessage == null) return SizedBox.shrink();
|
||||||
|
|
||||||
|
// Black magic to limit reply preview length
|
||||||
final replyText = message is TextMessage
|
final replyText = message is TextMessage
|
||||||
? replyMessage.text.substring(
|
? replyMessage.text.substring(
|
||||||
0,
|
0,
|
||||||
min(
|
min(
|
||||||
max(
|
max(
|
||||||
min(
|
min(
|
||||||
(message as TextMessage).text.length - 20,
|
max(
|
||||||
|
(message as TextMessage).text.length - 20,
|
||||||
|
message.metadata?["displayName"].length,
|
||||||
|
),
|
||||||
replyMessage.text.length,
|
replyMessage.text.length,
|
||||||
),
|
),
|
||||||
5,
|
5,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue