Compare commits
2 commits
b9ac38e7df
...
5362e0dcde
| Author | SHA1 | Date | |
|---|---|---|---|
|
5362e0dcde |
|||
|
7d9d03deb1 |
7 changed files with 196 additions and 18 deletions
|
|
@ -16,7 +16,7 @@ class RoomChatController extends AsyncNotifier<ChatController> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<ChatController> build() async {
|
Future<ChatController> build() async {
|
||||||
final response = await ref.watch(EventsController.provider(room).future);
|
final timeline = 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 {
|
||||||
|
|
@ -31,7 +31,7 @@ class RoomChatController extends AsyncNotifier<ChatController> {
|
||||||
|
|
||||||
await controller.removeMessage(message);
|
await controller.removeMessage(message);
|
||||||
} else {
|
} else {
|
||||||
final message = await event.toMessage(includeEdits: true);
|
final message = await event.toMessage(includeEdits: true, timeline);
|
||||||
if (event.relationshipType == RelationshipTypes.edit) {
|
if (event.relationshipType == RelationshipTypes.edit) {
|
||||||
final controller = await future;
|
final controller = await future;
|
||||||
final oldMessage = controller.messages.firstWhereOrNull(
|
final oldMessage = controller.messages.firstWhereOrNull(
|
||||||
|
|
@ -60,7 +60,7 @@ class RoomChatController extends AsyncNotifier<ChatController> {
|
||||||
);
|
);
|
||||||
|
|
||||||
return InMemoryChatController(
|
return InMemoryChatController(
|
||||||
messages: await response.events.toMessages(room),
|
messages: await timeline.events.toMessages(room, timeline),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,18 +87,18 @@ class RoomChatController extends AsyncNotifier<ChatController> {
|
||||||
Future<void> loadOlder() async {
|
Future<void> loadOlder() async {
|
||||||
final currentEvents = await future;
|
final currentEvents = await future;
|
||||||
await ref.watch(EventsController.provider(room).notifier).prev();
|
await ref.watch(EventsController.provider(room).notifier).prev();
|
||||||
final newEvents = await ref.watch(EventsController.provider(room).future);
|
final timeline = await ref.watch(EventsController.provider(room).future);
|
||||||
|
|
||||||
final controller = await future;
|
final controller = await future;
|
||||||
await controller.insertAllMessages(
|
await controller.insertAllMessages(
|
||||||
await newEvents.events
|
await timeline.events
|
||||||
.where(
|
.where(
|
||||||
(event) => !currentEvents.messages.any(
|
(event) => !currentEvents.messages.any(
|
||||||
(existingEvent) => existingEvent.id == event.eventId,
|
(existingEvent) => existingEvent.id == event.eventId,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList()
|
.toList()
|
||||||
.toMessages(room),
|
.toMessages(room, timeline),
|
||||||
index: 0,
|
index: 0,
|
||||||
);
|
);
|
||||||
ref.notifyListeners();
|
ref.notifyListeners();
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@ import "package:flutter_chat_core/flutter_chat_core.dart";
|
||||||
import "package:matrix/matrix.dart";
|
import "package:matrix/matrix.dart";
|
||||||
|
|
||||||
extension EventToMessage on Event {
|
extension EventToMessage on Event {
|
||||||
Future<Message?> toMessage({
|
Future<Message?> toMessage(
|
||||||
|
Timeline timeline, {
|
||||||
bool mustBeText = false,
|
bool mustBeText = false,
|
||||||
bool includeEdits = false,
|
bool includeEdits = false,
|
||||||
}) async {
|
}) async {
|
||||||
|
|
@ -25,7 +26,7 @@ extension EventToMessage on Event {
|
||||||
event.content["formatted_body"] ??
|
event.content["formatted_body"] ??
|
||||||
event.content["body"] ??
|
event.content["body"] ??
|
||||||
"",
|
"",
|
||||||
"reply": await replyEvent?.toMessage(mustBeText: true),
|
"reply": await replyEvent?.toMessage(mustBeText: true, timeline),
|
||||||
"body": newContent?["body"] ?? event.content["body"],
|
"body": newContent?["body"] ?? event.content["body"],
|
||||||
"eventType": event.type,
|
"eventType": event.type,
|
||||||
"avatarUrl": sender.avatarUrl.toString(),
|
"avatarUrl": sender.avatarUrl.toString(),
|
||||||
|
|
@ -65,12 +66,21 @@ extension EventToMessage on Event {
|
||||||
as TextMessage;
|
as TextMessage;
|
||||||
|
|
||||||
if (mustBeText) return asText;
|
if (mustBeText) return asText;
|
||||||
|
|
||||||
return switch (type) {
|
return switch (type) {
|
||||||
EventTypes.Encrypted => asText.copyWith(
|
EventTypes.Encrypted => asText.copyWith(
|
||||||
text: "Unable to decrypt message.",
|
text: "Unable to decrypt message.",
|
||||||
metadata: {...metadata, "formatted": "Unable to decrypt message."},
|
metadata: {...metadata, "formatted": "Unable to decrypt message."},
|
||||||
),
|
),
|
||||||
|
PollEventContent.startType => Message.custom(
|
||||||
|
metadata: {
|
||||||
|
...metadata,
|
||||||
|
"poll": event.parsedPollEventContent.pollStartContent,
|
||||||
|
"responses": event.getPollResponses(timeline),
|
||||||
|
},
|
||||||
|
id: eventId,
|
||||||
|
deliveredAt: originServerTs,
|
||||||
|
authorId: senderId,
|
||||||
|
),
|
||||||
(EventTypes.Sticker || EventTypes.Message) => switch (messageType) {
|
(EventTypes.Sticker || EventTypes.Message) => switch (messageType) {
|
||||||
(MessageTypes.Sticker || MessageTypes.Image) => Message.image(
|
(MessageTypes.Sticker || MessageTypes.Image) => Message.image(
|
||||||
metadata: metadata,
|
metadata: metadata,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@ import "package:matrix/matrix.dart";
|
||||||
import "package:nexus/helpers/extensions/event_to_message.dart";
|
import "package:nexus/helpers/extensions/event_to_message.dart";
|
||||||
|
|
||||||
extension ListToMessages on List<MatrixEvent> {
|
extension ListToMessages on List<MatrixEvent> {
|
||||||
Future<List<Message>> toMessages(Room room) async => (await Future.wait(
|
Future<List<Message>> toMessages(Room room, Timeline timeline) async =>
|
||||||
map((event) => Event.fromMatrixEvent(event, room).toMessage()),
|
(await Future.wait(
|
||||||
)).nonNulls.toList().reversed.toList();
|
map((event) => Event.fromMatrixEvent(event, room).toMessage(timeline)),
|
||||||
|
)).nonNulls.toList().reversed.toList();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -124,11 +124,11 @@ class LoginPage extends HookConsumerWidget {
|
||||||
iconUrl: "https://federated.nexus/images/icon.png",
|
iconUrl: "https://federated.nexus/images/icon.png",
|
||||||
),
|
),
|
||||||
Homeserver(
|
Homeserver(
|
||||||
name: "envs.net",
|
name: "Unredacted",
|
||||||
description:
|
description:
|
||||||
"envs.net is a minimalist, non-commercial shared linux system and will always be free to use.",
|
"Unredacted is a 501(c)(3) non-profit organization that builds Internet infrastructure and services to help people evade censorship and protect their right to privacy.",
|
||||||
url: Uri.https("envs.net"),
|
url: Uri.https("unredacted.org", "services/si/matrix"),
|
||||||
iconUrl: "https://envs.net/favicon.ico",
|
iconUrl: "https://unredacted.org/favicon.ico",
|
||||||
),
|
),
|
||||||
].map(
|
].map(
|
||||||
(homeserver) => Card(
|
(homeserver) => Card(
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
import "dart:async";
|
||||||
|
|
||||||
|
import "package:dynamic_polls/dynamic_polls.dart";
|
||||||
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";
|
||||||
|
|
@ -8,6 +11,7 @@ import "package:flyer_chat_image_message/flyer_chat_image_message.dart";
|
||||||
import "package:flyer_chat_system_message/flyer_chat_system_message.dart";
|
import "package:flyer_chat_system_message/flyer_chat_system_message.dart";
|
||||||
import "package:flyer_chat_text_message/flyer_chat_text_message.dart";
|
import "package:flyer_chat_text_message/flyer_chat_text_message.dart";
|
||||||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||||
|
import "package:matrix/matrix.dart";
|
||||||
import "package:nexus/controllers/selected_room_controller.dart";
|
import "package:nexus/controllers/selected_room_controller.dart";
|
||||||
import "package:nexus/controllers/room_chat_controller.dart";
|
import "package:nexus/controllers/room_chat_controller.dart";
|
||||||
import "package:nexus/helpers/extensions/better_when.dart";
|
import "package:nexus/helpers/extensions/better_when.dart";
|
||||||
|
|
@ -236,6 +240,103 @@ class RoomChat extends HookConsumerWidget {
|
||||||
replyToMessage.value = null,
|
replyToMessage.value = null,
|
||||||
room: room.roomData,
|
room: room.roomData,
|
||||||
),
|
),
|
||||||
|
customMessageBuilder:
|
||||||
|
(
|
||||||
|
context,
|
||||||
|
message,
|
||||||
|
index, {
|
||||||
|
required bool isSentByMe,
|
||||||
|
MessageGroupStatus? groupStatus,
|
||||||
|
}) {
|
||||||
|
final poll =
|
||||||
|
message.metadata?["poll"]
|
||||||
|
as PollStartContent;
|
||||||
|
final responses =
|
||||||
|
(message.metadata?["responses"]
|
||||||
|
as Map<
|
||||||
|
String,
|
||||||
|
Set<String>
|
||||||
|
>)
|
||||||
|
.values
|
||||||
|
.expand((set) => set)
|
||||||
|
.fold(<String, int>{}, (
|
||||||
|
acc,
|
||||||
|
value,
|
||||||
|
) {
|
||||||
|
acc[value] =
|
||||||
|
(acc[value] ?? 0) + 1;
|
||||||
|
return acc;
|
||||||
|
});
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
|
spacing: 4,
|
||||||
|
children: [
|
||||||
|
TopWidget(
|
||||||
|
message,
|
||||||
|
headers: room
|
||||||
|
.roomData
|
||||||
|
.client
|
||||||
|
.headers,
|
||||||
|
groupStatus: groupStatus,
|
||||||
|
),
|
||||||
|
DynamicPolls(
|
||||||
|
startDate: DateTime.now(),
|
||||||
|
endDate: DateTime.now(),
|
||||||
|
private:
|
||||||
|
poll.kind ==
|
||||||
|
PollKind.undisclosed,
|
||||||
|
allowReselection: true,
|
||||||
|
backgroundDecoration:
|
||||||
|
BoxDecoration(
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.all(
|
||||||
|
Radius.circular(16),
|
||||||
|
),
|
||||||
|
border: Border.all(
|
||||||
|
color: theme
|
||||||
|
.colorScheme
|
||||||
|
.primaryContainer,
|
||||||
|
width: 4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
allStyle: Styles(
|
||||||
|
titleStyle: TitleStyle(
|
||||||
|
style: theme
|
||||||
|
.textTheme
|
||||||
|
.headlineSmall,
|
||||||
|
),
|
||||||
|
optionStyle: OptionStyle(
|
||||||
|
fillColor: theme
|
||||||
|
.colorScheme
|
||||||
|
.primaryContainer,
|
||||||
|
selectedBorderColor: theme
|
||||||
|
.colorScheme
|
||||||
|
.primary,
|
||||||
|
borderColor: theme
|
||||||
|
.colorScheme
|
||||||
|
.primary,
|
||||||
|
unselectedBorderColor:
|
||||||
|
Colors.transparent,
|
||||||
|
textSelectColor: theme
|
||||||
|
.colorScheme
|
||||||
|
.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onOptionSelected:
|
||||||
|
(int index) {},
|
||||||
|
title: poll.question.mText,
|
||||||
|
options: poll.answers
|
||||||
|
.map(
|
||||||
|
(option) => option.mText,
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
textMessageBuilder:
|
textMessageBuilder:
|
||||||
(
|
(
|
||||||
context,
|
context,
|
||||||
|
|
|
||||||
66
pubspec.lock
66
pubspec.lock
|
|
@ -57,6 +57,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.7.0"
|
version: "2.7.0"
|
||||||
|
asn1lib:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: asn1lib
|
||||||
|
sha256: "9a8f69025044eb466b9b60ef3bc3ac99b4dc6c158ae9c56d25eeccf5bc56d024"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.6.5"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -161,6 +169,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.0"
|
||||||
|
charcode:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: charcode
|
||||||
|
sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
checked_yaml:
|
checked_yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -337,6 +353,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.1"
|
||||||
|
dynamic_polls:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: dynamic_polls
|
||||||
|
sha256: fba71ee6fb0ae8f3bebf7d07b3f2a79347d496956de88fb24d3daa32d47e0774
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.0.6"
|
||||||
dynamic_system_colors:
|
dynamic_system_colors:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -345,6 +369,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.0"
|
version: "1.8.0"
|
||||||
|
encrypt:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: encrypt
|
||||||
|
sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.0.3"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -664,6 +696,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.0"
|
version: "4.0.0"
|
||||||
|
get_x_storage:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: get_x_storage
|
||||||
|
sha256: c9c65de2baa228783f46a55137538dc599a3c9b1834130cfd3b417ec3b643813
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.0.8"
|
||||||
glob:
|
glob:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1064,6 +1104,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.8"
|
version: "2.1.8"
|
||||||
|
pointycastle:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pointycastle
|
||||||
|
sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.9.1"
|
||||||
pool:
|
pool:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1485,6 +1533,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.0"
|
||||||
|
universal_html:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: universal_html
|
||||||
|
sha256: c0bcae5c733c60f26c7dfc88b10b0fd27cbcc45cb7492311cdaa6067e21c9cd4
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.0"
|
||||||
|
universal_io:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: universal_io
|
||||||
|
sha256: f63cbc48103236abf48e345e07a03ce5757ea86285ed313a6a032596ed9301e2
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.1"
|
||||||
unorm_dart:
|
unorm_dart:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1720,5 +1784,5 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.3"
|
version: "2.2.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.10.0 <4.0.0"
|
dart: ">=3.10.4 <4.0.0"
|
||||||
flutter: ">=3.35.0"
|
flutter: ">=3.35.0"
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,13 @@ dependency_overrides:
|
||||||
path: dart
|
path: dart
|
||||||
analyzer: ^8.4.0
|
analyzer: ^8.4.0
|
||||||
source_gen: ^4.0.2
|
source_gen: ^4.0.2
|
||||||
|
flutter_hooks: ^0.21.2
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_hooks: ^0.21.2
|
|
||||||
flutter_riverpod: ^3.0.3
|
flutter_riverpod: ^3.0.3
|
||||||
hooks_riverpod: ^3.0.3
|
hooks_riverpod: ^3.0.3
|
||||||
intl: ^0.20.1
|
intl: ^0.20.1
|
||||||
|
|
@ -71,6 +71,8 @@ dependencies:
|
||||||
mention_tag_text_field: ^0.0.9
|
mention_tag_text_field: ^0.0.9
|
||||||
fluttertagger: ^2.3.1
|
fluttertagger: ^2.3.1
|
||||||
flutter_secure_storage: ^10.0.0
|
flutter_secure_storage: ^10.0.0
|
||||||
|
dynamic_polls: ^0.0.6
|
||||||
|
flutter_hooks: ^0.21.3+1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
build_runner: ^2.4.11
|
build_runner: ^2.4.11
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue