forked from Henry-Hiles/nexus
progress
This commit is contained in:
parent
541933a939
commit
63a9d2d169
15 changed files with 388 additions and 299 deletions
|
|
@ -18,10 +18,8 @@ class EventsController extends AsyncNotifier<GetRoomEventsResponse> {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<GetRoomEventsResponse> prev() async {
|
Future<GetRoomEventsResponse> prev() async =>
|
||||||
final resp = await build(from: ref.read(FromController.provider(room)));
|
build(from: ref.read(FromController.provider(room)));
|
||||||
return resp;
|
|
||||||
}
|
|
||||||
|
|
||||||
static final provider = AsyncNotifierProvider.autoDispose
|
static final provider = AsyncNotifierProvider.autoDispose
|
||||||
.family<EventsController, GetRoomEventsResponse, Room>(
|
.family<EventsController, GetRoomEventsResponse, Room>(
|
||||||
|
|
|
||||||
|
|
@ -9,17 +9,15 @@ class MessageController extends AsyncNotifier<TextMessage?> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<TextMessage?> build() async {
|
Future<TextMessage?> build() async {
|
||||||
final room = await ref.watch(
|
final room = await ref.watch(SelectedRoomController.provider.future);
|
||||||
SelectedRoomController.provider.selectAsync((a) => a),
|
|
||||||
);
|
|
||||||
if (room == null) return null;
|
if (room == null) return null;
|
||||||
|
|
||||||
final event = await room.roomData.getEventById(id);
|
final event = await room.roomData.getEventById(id);
|
||||||
return (await event?.toMessage(mustBeText: true)) as TextMessage?;
|
return (await event?.toMessage(mustBeText: true)) as TextMessage?;
|
||||||
}
|
}
|
||||||
|
|
||||||
static final provider =
|
static final provider = AsyncNotifierProvider.family
|
||||||
AsyncNotifierProvider.family<MessageController, TextMessage?, String>(
|
.autoDispose<MessageController, TextMessage?, String>(
|
||||||
MessageController.new,
|
MessageController.new,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +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/avatar_controller.dart";
|
||||||
import "package:nexus/controllers/events_controller.dart";
|
import "package:nexus/controllers/events_controller.dart";
|
||||||
import "package:nexus/helpers/extensions/event_to_message.dart";
|
import "package:nexus/helpers/extensions/event_to_message.dart";
|
||||||
import "package:nexus/helpers/extensions/list_to_messages.dart";
|
import "package:nexus/helpers/extensions/list_to_messages.dart";
|
||||||
|
|
@ -13,7 +14,9 @@ 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 response = await ref.watch(
|
||||||
|
EventsController.provider(room).selectAsync((a) => a),
|
||||||
|
);
|
||||||
|
|
||||||
ref.onDispose(
|
ref.onDispose(
|
||||||
room.client.onTimelineEvent.stream.listen((event) async {
|
room.client.onTimelineEvent.stream.listen((event) async {
|
||||||
|
|
@ -75,10 +78,10 @@ class RoomChatController extends AsyncNotifier<ChatController> {
|
||||||
.watch(EventsController.provider(room).notifier)
|
.watch(EventsController.provider(room).notifier)
|
||||||
.prev();
|
.prev();
|
||||||
|
|
||||||
await controller.insertAllMessages(
|
final messages = await response.chunk.toMessages(room);
|
||||||
await response.chunk.toMessages(room),
|
|
||||||
index: 0,
|
await controller.insertAllMessages(messages, index: 0);
|
||||||
);
|
ref.notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> markRead() async {
|
Future<void> markRead() async {
|
||||||
|
|
@ -92,30 +95,22 @@ class RoomChatController extends AsyncNotifier<ChatController> {
|
||||||
Future<void> updateMessage(Message message, Message newMessage) async =>
|
Future<void> updateMessage(Message message, Message newMessage) async =>
|
||||||
(await future).updateMessage(message, newMessage);
|
(await future).updateMessage(message, newMessage);
|
||||||
|
|
||||||
Future<void> send(Message message, {Message? replyTo}) async {
|
Future<void> send(String message, {Message? replyTo}) async =>
|
||||||
final controller = await future;
|
room.sendTextEvent(
|
||||||
controller.insertMessage(message);
|
message,
|
||||||
|
|
||||||
if (message is TextMessage) {
|
|
||||||
await room.sendTextEvent(
|
|
||||||
message.text,
|
|
||||||
inReplyTo: replyTo == null ? null : await room.getEventById(replyTo.id),
|
inReplyTo: replyTo == null ? null : await room.getEventById(replyTo.id),
|
||||||
);
|
);
|
||||||
}
|
|
||||||
// TODO: Handle other types of message
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<chat.User> resolveUser(String id) async {
|
Future<chat.User> resolveUser(String id) async {
|
||||||
final user = await room.client.getUserProfile(id);
|
final user = await room.client.getUserProfile(id);
|
||||||
return chat.User(
|
return chat.User(
|
||||||
id: id,
|
id: id,
|
||||||
name: user.displayname,
|
name: user.displayname,
|
||||||
imageSource: (await user.avatarUrl?.getThumbnailUri(
|
imageSource: user.avatarUrl == null
|
||||||
// TODO: Fix use of account avatar not room avatar
|
? null
|
||||||
room.client,
|
: (await ref.watch(
|
||||||
width: 24,
|
AvatarController.provider(user.avatarUrl!.toString()).future,
|
||||||
height: 24,
|
)).toString(),
|
||||||
))?.toString(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@ import "package:nexus/models/space.dart";
|
||||||
class SelectedSpaceController extends AsyncNotifier<Space> {
|
class SelectedSpaceController extends AsyncNotifier<Space> {
|
||||||
@override
|
@override
|
||||||
Future<Space> build() async {
|
Future<Space> build() async {
|
||||||
final spaces = await ref.watch(SpacesController.provider.future);
|
final spaces = await ref.watch(
|
||||||
|
SpacesController.provider.selectAsync((data) => data),
|
||||||
|
);
|
||||||
final selectedSpaceId = ref.watch(
|
final selectedSpaceId = ref.watch(
|
||||||
KeyController.provider(KeyController.spaceKey),
|
KeyController.provider(KeyController.spaceKey),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ class SpacesController extends AsyncNotifier<IList<Space>> {
|
||||||
title: "Home",
|
title: "Home",
|
||||||
id: "home",
|
id: "home",
|
||||||
children: topLevelRooms,
|
children: topLevelRooms,
|
||||||
icon: Icon(Icons.home),
|
icon: Icons.home,
|
||||||
),
|
),
|
||||||
Space(
|
Space(
|
||||||
client: client,
|
client: client,
|
||||||
|
|
@ -54,7 +54,7 @@ class SpacesController extends AsyncNotifier<IList<Space>> {
|
||||||
.map((room) => room.fullRoom),
|
.map((room) => room.fullRoom),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
icon: Icon(Icons.person),
|
icon: Icons.person,
|
||||||
),
|
),
|
||||||
...(await Future.wait(
|
...(await Future.wait(
|
||||||
topLevelSpaces.map(
|
topLevelSpaces.map(
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,7 @@ class ThumbnailController extends AsyncNotifier<String?> {
|
||||||
@override
|
@override
|
||||||
Future<String?> build({String? from}) async {
|
Future<String?> build({String? from}) async {
|
||||||
final client = await ref.watch(ClientController.provider.future);
|
final client = await ref.watch(ClientController.provider.future);
|
||||||
final uri = await Uri.tryParse(data.uri)?.getDownloadUri(
|
final uri = await Uri.tryParse(data.uri)?.getDownloadUri(client);
|
||||||
client,
|
|
||||||
); // TODO: Should use thumb when c10y fixes animated thumbs
|
|
||||||
|
|
||||||
return uri.toString();
|
return uri.toString();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ extension EventToMessage on Event {
|
||||||
final editedAt = relationshipType == RelationshipTypes.edit
|
final editedAt = relationshipType == RelationshipTypes.edit
|
||||||
? originServerTs
|
? originServerTs
|
||||||
: null;
|
: null;
|
||||||
final body = newContent?["body"] as String? ?? this.body;
|
final body = (newContent?["body"] ?? content["body"]).toString();
|
||||||
final eventId = editedAt == null
|
final eventId = editedAt == null
|
||||||
? this.eventId
|
? this.eventId
|
||||||
: relationshipEventId ?? this.eventId;
|
: relationshipEventId ?? this.eventId;
|
||||||
|
|
|
||||||
|
|
@ -20,17 +20,15 @@ final class Logger extends ProviderObserver {
|
||||||
ProviderObserverContext context,
|
ProviderObserverContext context,
|
||||||
Object? previousValue,
|
Object? previousValue,
|
||||||
Object? newValue,
|
Object? newValue,
|
||||||
) {
|
) => debugPrint("""
|
||||||
print('''{
|
Time: ${DateTime.now().toIso8601String()}
|
||||||
"provider": "${context.provider}",
|
Provider: ${context.provider}
|
||||||
"changed": ${previousValue != newValue}
|
Previous Value: ${previousValue is AsyncData ? previousValue.value : previousValue}
|
||||||
"type": ${previousValue.runtimeType}
|
New Value: ${newValue is AsyncData ? newValue.value : newValue}
|
||||||
}''');
|
}""");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void showError(Object error, [StackTrace? stackTrace]) {
|
void showError(Object error, [StackTrace? stackTrace]) {
|
||||||
if (error.toString().contains("ParentDataWidget")) return;
|
|
||||||
if (error.toString().contains("DioException")) return;
|
if (error.toString().contains("DioException")) return;
|
||||||
if (error.toString().contains("UTF-16")) return;
|
if (error.toString().contains("UTF-16")) return;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,6 @@ abstract class Space with _$Space {
|
||||||
required Client client,
|
required Client client,
|
||||||
Room? roomData,
|
Room? roomData,
|
||||||
Uri? avatar,
|
Uri? avatar,
|
||||||
Icon? icon,
|
IconData? icon,
|
||||||
}) = _Space;
|
}) = _Space;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,11 @@ class LoginPage extends HookConsumerWidget {
|
||||||
(homeserver) => Card(
|
(homeserver) => Card(
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
title: Text(homeserver.name),
|
title: Text(homeserver.name),
|
||||||
leading: Image.network(homeserver.iconUrl, height: 32),
|
leading: Image.network(
|
||||||
|
homeserver.iconUrl,
|
||||||
|
errorBuilder: (_, _, _) => SizedBox.shrink(),
|
||||||
|
height: 32,
|
||||||
|
),
|
||||||
subtitle: Text(homeserver.description),
|
subtitle: Text(homeserver.description),
|
||||||
onTap: isLoading.value
|
onTap: isLoading.value
|
||||||
? null
|
? null
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
|
import "dart:io";
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter/services.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_hooks/flutter_hooks.dart";
|
import "package:flutter_hooks/flutter_hooks.dart";
|
||||||
import "package:fluttertagger/fluttertagger.dart";
|
import "package:fluttertagger/fluttertagger.dart";
|
||||||
|
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||||
import "package:matrix/matrix.dart";
|
import "package:matrix/matrix.dart";
|
||||||
import "package:nexus/helpers/extensions/get_headers.dart";
|
import "package:nexus/controllers/room_chat_controller.dart";
|
||||||
import "package:nexus/widgets/form_text_input.dart";
|
|
||||||
|
|
||||||
class ChatBox extends HookWidget {
|
class ChatBox extends HookConsumerWidget {
|
||||||
final Message? replyToMessage;
|
final Message? replyToMessage;
|
||||||
final VoidCallback onDismiss;
|
final VoidCallback onDismiss;
|
||||||
final Room room;
|
final Room room;
|
||||||
|
|
@ -19,83 +20,142 @@ class ChatBox extends HookWidget {
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final controller = useRef(FlutterTaggerController());
|
final controller = useRef(FlutterTaggerController());
|
||||||
final trigger = useState<String?>(null);
|
|
||||||
|
Future<void> send() => ref
|
||||||
|
.watch(RoomChatController.provider(room).notifier)
|
||||||
|
.send(controller.value.text);
|
||||||
|
|
||||||
|
final node = useFocusNode(
|
||||||
|
onKeyEvent: (_, event) {
|
||||||
|
if (event is KeyDownEvent &&
|
||||||
|
event.logicalKey == LogicalKeyboardKey.enter &&
|
||||||
|
!(Platform.isAndroid || Platform.isIOS) ^
|
||||||
|
HardwareKeyboard.instance.isShiftPressed) {
|
||||||
|
send();
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
}
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
final style = TextStyle(
|
final style = TextStyle(
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
);
|
);
|
||||||
|
|
||||||
return Column(
|
return Positioned(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
bottom: 0,
|
||||||
children: [
|
left: 0,
|
||||||
FlutterTagger(
|
right: 0,
|
||||||
overlay: SizedBox(),
|
child: Padding(
|
||||||
controller: controller.value,
|
padding: EdgeInsetsGeometry.all(12),
|
||||||
onSearch: (query, triggerCharacter) {
|
child: ClipRRect(
|
||||||
triggerCharacter == "#";
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
if (controller.value.tags.isEmpty)
|
child: Container(
|
||||||
controller.value.addTag(id: "id", name: "name");
|
color: theme.colorScheme.surfaceContainerHighest,
|
||||||
},
|
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||||
triggerCharacterAndStyles: {"@": style, "#": style},
|
child: // TODO: This doesn't work?
|
||||||
builder: (context, key) => TextFormField(controller: controller.value, key: key,autofocus: true,onFieldSubmitted: (_) {
|
room.canSendDefaultMessages
|
||||||
|
? Row(
|
||||||
},)
|
spacing: 8,
|
||||||
// Composer(
|
children: [
|
||||||
// textEditingController: controller.value,
|
PopupMenuButton(
|
||||||
// key: key,
|
itemBuilder: (context) => [],
|
||||||
// sigmaY: 0,
|
icon: Icon(Icons.add),
|
||||||
// sendIconColor: theme.colorScheme.primary,
|
),
|
||||||
// sendOnEnter: true,
|
Expanded(
|
||||||
// topWidget: replyToMessage == null
|
child: FlutterTagger(
|
||||||
// ? null
|
overlay: SizedBox(),
|
||||||
// : ColoredBox(
|
controller: controller.value,
|
||||||
// color: theme.colorScheme.surfaceContainer,
|
onSearch: (query, triggerCharacter) {
|
||||||
// child: Padding(
|
triggerCharacter == "#";
|
||||||
// padding: EdgeInsets.symmetric(
|
if (controller.value.tags.isEmpty) {
|
||||||
// horizontal: 16,
|
controller.value.addTag(
|
||||||
// vertical: 4,
|
id: "id",
|
||||||
// ),
|
name: "name",
|
||||||
// child: Row(
|
); // TODO: RM
|
||||||
// spacing: 8,
|
}
|
||||||
// children: [
|
},
|
||||||
// Avatar(
|
triggerCharacterAndStyles: {
|
||||||
// userId: replyToMessage!.authorId,
|
"@": style,
|
||||||
// headers: room.client.headers,
|
"#": style,
|
||||||
// size: 16,
|
":": style,
|
||||||
// ),
|
},
|
||||||
// Text(
|
builder: (context, key) => TextFormField(
|
||||||
// replyToMessage!.metadata?["displayName"] ??
|
maxLines: 12,
|
||||||
// replyToMessage!.authorId,
|
minLines: 1,
|
||||||
// style: theme.textTheme.labelMedium?.copyWith(
|
decoration: InputDecoration(
|
||||||
// fontWeight: FontWeight.bold,
|
hintText: "Your message here...",
|
||||||
// ),
|
border: InputBorder.none,
|
||||||
// ),
|
),
|
||||||
// Expanded(
|
controller: controller.value,
|
||||||
// child: (replyToMessage is TextMessage)
|
key: key,
|
||||||
// ? Text(
|
autofocus: true,
|
||||||
// (replyToMessage as TextMessage).text,
|
focusNode: node,
|
||||||
// overflow: TextOverflow.ellipsis,
|
),
|
||||||
// style: theme.textTheme.labelMedium,
|
),
|
||||||
// maxLines: 1,
|
),
|
||||||
// )
|
IconButton(onPressed: send, icon: Icon(Icons.send)),
|
||||||
// : SizedBox(),
|
],
|
||||||
// ),
|
)
|
||||||
// IconButton(
|
: Text("You don't have permission to send messages here..."),
|
||||||
// onPressed: onDismiss,
|
// Composer(
|
||||||
// icon: Icon(Icons.close),
|
// textEditingController: controller.value,
|
||||||
// iconSize: 20,
|
// key: key,
|
||||||
// ),
|
// sigmaY: 0,
|
||||||
// ],
|
// sendIconColor: theme.colorScheme.primary,
|
||||||
// ),
|
// sendOnEnter: true,
|
||||||
// ),
|
// topWidget: replyToMessage == null
|
||||||
// ),
|
// ? null
|
||||||
// autofocus: true,
|
// : ColoredBox(
|
||||||
// ),
|
// color: theme.colorScheme.surfaceContainer,
|
||||||
|
// child: Padding(
|
||||||
|
// padding: EdgeInsets.symmetric(
|
||||||
|
// horizontal: 16,
|
||||||
|
// vertical: 4,
|
||||||
|
// ),
|
||||||
|
// child: Row(
|
||||||
|
// spacing: 8,
|
||||||
|
// children: [
|
||||||
|
// Avatar(
|
||||||
|
// userId: replyToMessage!.authorId,
|
||||||
|
// headers: room.client.headers,
|
||||||
|
// size: 16,
|
||||||
|
// ),
|
||||||
|
// Text(
|
||||||
|
// replyToMessage!.metadata?["displayName"] ??
|
||||||
|
// replyToMessage!.authorId,
|
||||||
|
// style: theme.textTheme.labelMedium?.copyWith(
|
||||||
|
// fontWeight: FontWeight.bold,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// Expanded(
|
||||||
|
// child: (replyToMessage is TextMessage)
|
||||||
|
// ? Text(
|
||||||
|
// (replyToMessage as TextMessage).text,
|
||||||
|
// overflow: TextOverflow.ellipsis,
|
||||||
|
// style: theme.textTheme.labelMedium,
|
||||||
|
// maxLines: 1,
|
||||||
|
// )
|
||||||
|
// : SizedBox(),
|
||||||
|
// ),
|
||||||
|
// IconButton(
|
||||||
|
// onPressed: onDismiss,
|
||||||
|
// icon: Icon(Icons.close),
|
||||||
|
// iconSize: 20,
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// autofocus: true,
|
||||||
|
// ),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_hooks/flutter_hooks.dart";
|
import "package:flutter_hooks/flutter_hooks.dart";
|
||||||
|
import "package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart";
|
||||||
|
|
||||||
class SpoilerText extends HookWidget {
|
class SpoilerText extends HookWidget {
|
||||||
final String text;
|
final String text;
|
||||||
|
|
@ -10,18 +11,20 @@ class SpoilerText extends HookWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final revealed = useState(false);
|
final revealed = useState(false);
|
||||||
|
|
||||||
return InkWell(
|
return InlineCustomWidget(
|
||||||
onTap: () => revealed.value = !revealed.value,
|
child: InkWell(
|
||||||
child: AnimatedContainer(
|
onTap: () => revealed.value = !revealed.value,
|
||||||
duration: const Duration(milliseconds: 100),
|
child: AnimatedContainer(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
|
duration: const Duration(milliseconds: 100),
|
||||||
decoration: BoxDecoration(
|
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
|
||||||
color: revealed.value ? Colors.transparent : Colors.blueGrey,
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(4),
|
color: revealed.value ? Colors.transparent : Colors.blueGrey,
|
||||||
),
|
borderRadius: BorderRadius.circular(4),
|
||||||
child: Text(
|
),
|
||||||
text,
|
child: Text(
|
||||||
style: TextStyle(color: revealed.value ? null : Colors.transparent),
|
text,
|
||||||
|
style: TextStyle(color: revealed.value ? null : Colors.transparent),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -135,177 +135,197 @@ class RoomChat extends HookConsumerWidget {
|
||||||
body: Row(
|
body: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ref
|
child: Column(
|
||||||
.watch(controllerProvider)
|
children: [
|
||||||
.betterWhen(
|
Expanded(
|
||||||
data: (controller) => Chat(
|
child: ref
|
||||||
currentUserId: room.roomData.client.userID!,
|
.watch(controllerProvider)
|
||||||
theme: ChatTheme.fromThemeData(theme).copyWith(
|
.betterWhen(
|
||||||
colors: ChatColors.fromThemeData(theme).copyWith(
|
data: (controller) => Chat(
|
||||||
primary: theme.colorScheme.primaryContainer,
|
currentUserId: room.roomData.client.userID!,
|
||||||
onPrimary: theme.colorScheme.onPrimaryContainer,
|
theme: ChatTheme.fromThemeData(theme)
|
||||||
),
|
.copyWith(
|
||||||
),
|
colors: ChatColors.fromThemeData(theme)
|
||||||
onMessageSecondaryTap:
|
.copyWith(
|
||||||
(
|
primary: theme
|
||||||
context,
|
.colorScheme
|
||||||
message, {
|
.primaryContainer,
|
||||||
required details,
|
onPrimary: theme
|
||||||
required index,
|
.colorScheme
|
||||||
}) => context.showContextMenu(
|
.onPrimaryContainer,
|
||||||
globalPosition: details.globalPosition,
|
|
||||||
children: getMessageOptions(message),
|
|
||||||
),
|
|
||||||
onMessageLongPress:
|
|
||||||
(
|
|
||||||
context,
|
|
||||||
message, {
|
|
||||||
required details,
|
|
||||||
required index,
|
|
||||||
}) => context.showContextMenu(
|
|
||||||
globalPosition: details.globalPosition,
|
|
||||||
children: getMessageOptions(message),
|
|
||||||
),
|
|
||||||
builders: Builders(
|
|
||||||
loadMoreBuilder: (_) => Loading(),
|
|
||||||
chatAnimatedListBuilder: (_, itemBuilder) =>
|
|
||||||
ChatAnimatedList(
|
|
||||||
itemBuilder: itemBuilder,
|
|
||||||
onEndReached: notifier.loadOlder,
|
|
||||||
onStartReached: notifier.markRead,
|
|
||||||
),
|
|
||||||
composerBuilder: (_) => ChatBox(
|
|
||||||
replyToMessage: replyToMessage.value,
|
|
||||||
onDismiss: () => replyToMessage.value = null,
|
|
||||||
room: room.roomData,
|
|
||||||
),
|
|
||||||
textMessageBuilder:
|
|
||||||
(
|
|
||||||
context,
|
|
||||||
message,
|
|
||||||
index, {
|
|
||||||
required bool isSentByMe,
|
|
||||||
MessageGroupStatus? groupStatus,
|
|
||||||
}) => FlyerChatTextMessage(
|
|
||||||
customWidget: Html(
|
|
||||||
message.metadata?["formatted"]
|
|
||||||
.replaceAllMapped(
|
|
||||||
RegExp(
|
|
||||||
regexLink,
|
|
||||||
caseSensitive: false,
|
|
||||||
),
|
|
||||||
(m) =>
|
|
||||||
"<a href=\"${m.group(0)!}\">${m.group(0)!}</a>",
|
|
||||||
) +
|
|
||||||
((message.editedAt != null)
|
|
||||||
? "<sub edited>(edited)</sub>"
|
|
||||||
: ""),
|
|
||||||
client: room.roomData.client,
|
|
||||||
),
|
|
||||||
topWidget: TopWidget(
|
|
||||||
message,
|
|
||||||
headers: room.roomData.client.headers,
|
|
||||||
groupStatus: groupStatus,
|
|
||||||
),
|
|
||||||
message: message,
|
|
||||||
showTime: true,
|
|
||||||
index: index,
|
|
||||||
),
|
|
||||||
linkPreviewBuilder: (_, message, isSentByMe) =>
|
|
||||||
LinkPreview(
|
|
||||||
text: message.text,
|
|
||||||
backgroundColor: isSentByMe
|
|
||||||
? theme.colorScheme.inversePrimary
|
|
||||||
: theme.colorScheme.surfaceContainerLow,
|
|
||||||
insidePadding: EdgeInsets.symmetric(
|
|
||||||
vertical: 8,
|
|
||||||
horizontal: 16,
|
|
||||||
),
|
|
||||||
linkPreviewData: message.linkPreviewData,
|
|
||||||
onLinkPreviewDataFetched:
|
|
||||||
(linkPreviewData) =>
|
|
||||||
notifier.updateMessage(
|
|
||||||
message,
|
|
||||||
message.copyWith(
|
|
||||||
linkPreviewData:
|
|
||||||
linkPreviewData,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
|
||||||
imageMessageBuilder:
|
|
||||||
(
|
|
||||||
_,
|
|
||||||
message,
|
|
||||||
index, {
|
|
||||||
required bool isSentByMe,
|
|
||||||
MessageGroupStatus? groupStatus,
|
|
||||||
}) => FlyerChatImageMessage(
|
|
||||||
topWidget: TopWidget(
|
|
||||||
message,
|
|
||||||
headers: room.roomData.client.headers,
|
|
||||||
groupStatus: groupStatus,
|
|
||||||
alwaysShow: true,
|
|
||||||
),
|
|
||||||
message: message,
|
|
||||||
index: index,
|
|
||||||
headers: room.roomData.client.headers,
|
|
||||||
),
|
|
||||||
fileMessageBuilder:
|
|
||||||
(
|
|
||||||
_,
|
|
||||||
message,
|
|
||||||
index, {
|
|
||||||
required bool isSentByMe,
|
|
||||||
MessageGroupStatus? groupStatus,
|
|
||||||
}) => InkWell(
|
|
||||||
onTap: () => showAboutDialog(
|
|
||||||
context: context,
|
|
||||||
), // TODO: Download
|
|
||||||
child: FlyerChatFileMessage(
|
|
||||||
topWidget: TopWidget(
|
|
||||||
message,
|
|
||||||
headers: room.roomData.client.headers,
|
|
||||||
groupStatus: groupStatus,
|
|
||||||
),
|
),
|
||||||
message: message,
|
onMessageSecondaryTap:
|
||||||
index: index,
|
(
|
||||||
|
context,
|
||||||
|
message, {
|
||||||
|
required details,
|
||||||
|
required index,
|
||||||
|
}) => context.showContextMenu(
|
||||||
|
globalPosition: details.globalPosition,
|
||||||
|
children: getMessageOptions(message),
|
||||||
|
),
|
||||||
|
onMessageLongPress:
|
||||||
|
(
|
||||||
|
context,
|
||||||
|
message, {
|
||||||
|
required details,
|
||||||
|
required index,
|
||||||
|
}) => context.showContextMenu(
|
||||||
|
globalPosition: details.globalPosition,
|
||||||
|
children: getMessageOptions(message),
|
||||||
|
),
|
||||||
|
builders: Builders(
|
||||||
|
loadMoreBuilder: (_) => Loading(),
|
||||||
|
chatAnimatedListBuilder: (_, itemBuilder) =>
|
||||||
|
ChatAnimatedList(
|
||||||
|
itemBuilder: itemBuilder,
|
||||||
|
onEndReached: notifier.loadOlder,
|
||||||
|
onStartReached: notifier.markRead,
|
||||||
|
bottomPadding: 72,
|
||||||
|
),
|
||||||
|
composerBuilder: (_) => ChatBox(
|
||||||
|
replyToMessage: replyToMessage.value,
|
||||||
|
onDismiss: () =>
|
||||||
|
replyToMessage.value = null,
|
||||||
|
room: room.roomData,
|
||||||
),
|
),
|
||||||
|
textMessageBuilder:
|
||||||
|
(
|
||||||
|
context,
|
||||||
|
message,
|
||||||
|
index, {
|
||||||
|
required bool isSentByMe,
|
||||||
|
MessageGroupStatus? groupStatus,
|
||||||
|
}) => FlyerChatTextMessage(
|
||||||
|
customWidget: Html(
|
||||||
|
message.metadata?["formatted"]
|
||||||
|
.replaceAllMapped(
|
||||||
|
RegExp(
|
||||||
|
regexLink,
|
||||||
|
caseSensitive: false,
|
||||||
|
),
|
||||||
|
(m) =>
|
||||||
|
"<a href=\"${m.group(0)!}\">${m.group(0)!}</a>",
|
||||||
|
) +
|
||||||
|
((message.editedAt != null)
|
||||||
|
? "<sub edited>(edited)</sub>"
|
||||||
|
: ""),
|
||||||
|
client: room.roomData.client,
|
||||||
|
),
|
||||||
|
topWidget: TopWidget(
|
||||||
|
message,
|
||||||
|
headers:
|
||||||
|
room.roomData.client.headers,
|
||||||
|
groupStatus: groupStatus,
|
||||||
|
),
|
||||||
|
message: message,
|
||||||
|
showTime: true,
|
||||||
|
index: index,
|
||||||
|
),
|
||||||
|
linkPreviewBuilder:
|
||||||
|
(_, message, isSentByMe) => LinkPreview(
|
||||||
|
text: message.text,
|
||||||
|
backgroundColor: isSentByMe
|
||||||
|
? theme.colorScheme.inversePrimary
|
||||||
|
: theme
|
||||||
|
.colorScheme
|
||||||
|
.surfaceContainerLow,
|
||||||
|
insidePadding: EdgeInsets.symmetric(
|
||||||
|
vertical: 8,
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
linkPreviewData:
|
||||||
|
message.linkPreviewData,
|
||||||
|
onLinkPreviewDataFetched:
|
||||||
|
(linkPreviewData) =>
|
||||||
|
notifier.updateMessage(
|
||||||
|
message,
|
||||||
|
message.copyWith(
|
||||||
|
linkPreviewData:
|
||||||
|
linkPreviewData,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
imageMessageBuilder:
|
||||||
|
(
|
||||||
|
_,
|
||||||
|
message,
|
||||||
|
index, {
|
||||||
|
required bool isSentByMe,
|
||||||
|
MessageGroupStatus? groupStatus,
|
||||||
|
}) => FlyerChatImageMessage(
|
||||||
|
topWidget: TopWidget(
|
||||||
|
message,
|
||||||
|
headers:
|
||||||
|
room.roomData.client.headers,
|
||||||
|
groupStatus: groupStatus,
|
||||||
|
alwaysShow: true,
|
||||||
|
),
|
||||||
|
message: message,
|
||||||
|
index: index,
|
||||||
|
headers: room.roomData.client.headers,
|
||||||
|
),
|
||||||
|
fileMessageBuilder:
|
||||||
|
(
|
||||||
|
_,
|
||||||
|
message,
|
||||||
|
index, {
|
||||||
|
required bool isSentByMe,
|
||||||
|
MessageGroupStatus? groupStatus,
|
||||||
|
}) => InkWell(
|
||||||
|
onTap: () => showAboutDialog(
|
||||||
|
context: context,
|
||||||
|
), // TODO: Download
|
||||||
|
child: FlyerChatFileMessage(
|
||||||
|
topWidget: TopWidget(
|
||||||
|
message,
|
||||||
|
headers:
|
||||||
|
room.roomData.client.headers,
|
||||||
|
groupStatus: groupStatus,
|
||||||
|
),
|
||||||
|
message: message,
|
||||||
|
index: index,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
systemMessageBuilder:
|
||||||
|
(
|
||||||
|
_,
|
||||||
|
message,
|
||||||
|
index, {
|
||||||
|
required bool isSentByMe,
|
||||||
|
MessageGroupStatus? groupStatus,
|
||||||
|
}) => FlyerChatSystemMessage(
|
||||||
|
message: message,
|
||||||
|
index: index,
|
||||||
|
),
|
||||||
|
unsupportedMessageBuilder:
|
||||||
|
(
|
||||||
|
_,
|
||||||
|
message,
|
||||||
|
index, {
|
||||||
|
required bool isSentByMe,
|
||||||
|
MessageGroupStatus? groupStatus,
|
||||||
|
}) => Text(
|
||||||
|
"${message.authorId} sent ${message.metadata?["eventType"]}",
|
||||||
|
style: theme.textTheme.labelSmall
|
||||||
|
?.copyWith(color: Colors.grey),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
systemMessageBuilder:
|
resolveUser: notifier.resolveUser,
|
||||||
(
|
chatController: controller,
|
||||||
_,
|
),
|
||||||
message,
|
),
|
||||||
index, {
|
|
||||||
required bool isSentByMe,
|
|
||||||
MessageGroupStatus? groupStatus,
|
|
||||||
}) => FlyerChatSystemMessage(
|
|
||||||
message: message,
|
|
||||||
index: index,
|
|
||||||
),
|
|
||||||
unsupportedMessageBuilder:
|
|
||||||
(
|
|
||||||
_,
|
|
||||||
message,
|
|
||||||
index, {
|
|
||||||
required bool isSentByMe,
|
|
||||||
MessageGroupStatus? groupStatus,
|
|
||||||
}) => Text(
|
|
||||||
"${message.authorId} sent ${message.metadata?["eventType"]}",
|
|
||||||
style: theme.textTheme.labelSmall?.copyWith(
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
resolveUser: notifier.resolveUser,
|
|
||||||
chatController: controller,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
if (memberListOpened.value == true && showMembersByDefault)
|
if (memberListOpened.value == true && showMembersByDefault)
|
||||||
MemberList(room.roomData),
|
MemberList(room.roomData),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
endDrawer: showMembersByDefault
|
endDrawer: showMembersByDefault
|
||||||
? null
|
? null
|
||||||
: MemberList(room.roomData),
|
: MemberList(room.roomData),
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,9 @@ class Sidebar extends HookConsumerWidget {
|
||||||
(space) => NavigationRailDestination(
|
(space) => NavigationRailDestination(
|
||||||
icon: AvatarOrHash(
|
icon: AvatarOrHash(
|
||||||
space.avatar,
|
space.avatar,
|
||||||
fallback: space.icon,
|
fallback: space.icon == null
|
||||||
|
? null
|
||||||
|
: Icon(space.icon),
|
||||||
space.title,
|
space.title,
|
||||||
headers: space.client.headers,
|
headers: space.client.headers,
|
||||||
hasBadge:
|
hasBadge:
|
||||||
|
|
@ -126,7 +128,9 @@ class Sidebar extends HookConsumerWidget {
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: AvatarOrHash(
|
leading: AvatarOrHash(
|
||||||
space.avatar,
|
space.avatar,
|
||||||
fallback: space.icon,
|
fallback: space.icon == null
|
||||||
|
? null
|
||||||
|
: Icon(space.icon),
|
||||||
space.title,
|
space.title,
|
||||||
headers: space.client.headers,
|
headers: space.client.headers,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,10 @@ 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";
|
||||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
|
import "package:nexus/controllers/avatar_controller.dart";
|
||||||
import "package:nexus/controllers/message_controller.dart";
|
import "package:nexus/controllers/message_controller.dart";
|
||||||
import "package:nexus/helpers/extensions/better_when.dart";
|
import "package:nexus/helpers/extensions/better_when.dart";
|
||||||
|
import "package:nexus/widgets/avatar_or_hash.dart";
|
||||||
import "package:nexus/widgets/chat_page/quoted.dart";
|
import "package:nexus/widgets/chat_page/quoted.dart";
|
||||||
|
|
||||||
class TopWidget extends ConsumerWidget {
|
class TopWidget extends ConsumerWidget {
|
||||||
|
|
@ -62,11 +64,18 @@ class TopWidget extends ConsumerWidget {
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
Avatar(
|
ref
|
||||||
userId: replyMessage.authorId,
|
.watch(
|
||||||
headers: headers,
|
AvatarController.provider(replyMessage.authorId),
|
||||||
size: 16,
|
)
|
||||||
),
|
.betterWhen(
|
||||||
|
data: (avatar) => AvatarOrHash(
|
||||||
|
avatar,
|
||||||
|
replyMessage.metadata?["displayName"] ??
|
||||||
|
replyMessage.authorId,
|
||||||
|
headers: headers,
|
||||||
|
),
|
||||||
|
),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
replyMessage.metadata?["displayName"] ??
|
replyMessage.metadata?["displayName"] ??
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue