parent
9bc2d9955a
commit
c8b9654219
8 changed files with 113 additions and 107 deletions
|
|
@ -81,10 +81,10 @@ A simple and user-friendly Matrix client made with Flutter and a Gomuks backend.
|
||||||
- [x] Users
|
- [x] Users
|
||||||
- [x] Clickable
|
- [x] Clickable
|
||||||
- [x] Rooms
|
- [x] Rooms
|
||||||
- [ ] Clickable
|
- [x] Clickable
|
||||||
- [x] Matrix URIs
|
- [x] Matrix URIs
|
||||||
- [x] Matrix.to links
|
- [x] Matrix.to links
|
||||||
- [x] Events
|
- [ ] Events
|
||||||
- [ ] Render more nicely
|
- [ ] Render more nicely
|
||||||
- [ ] Clickable
|
- [ ] Clickable
|
||||||
- [x] Custom emojis/stickers
|
- [x] Custom emojis/stickers
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,14 @@ import "package:nexus/controllers/spaces.dart";
|
||||||
import "package:nexus/models/space.dart";
|
import "package:nexus/models/space.dart";
|
||||||
|
|
||||||
extension FocusRoom on MutationTarget {
|
extension FocusRoom on MutationTarget {
|
||||||
Future<void> focusRoom(String id) async {
|
Future<bool> focusRoom(String id) async {
|
||||||
final spaces = container.read(SpacesController.provider);
|
final spaces = container.read(SpacesController.provider);
|
||||||
|
|
||||||
if (spaces.firstWhereOrNull((space) => space.id == id) case Space _?) {
|
if (spaces.firstWhereOrNull((space) => space.id == id) case Space _?) {
|
||||||
return await container
|
await container
|
||||||
.read(KeyController.provider(KeyController.spaceKey).notifier)
|
.read(KeyController.provider(KeyController.spaceKey).notifier)
|
||||||
.set(id);
|
.set(id);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
final parent = spaces.firstWhereOrNull(
|
final parent = spaces.firstWhereOrNull(
|
||||||
|
|
@ -23,7 +24,7 @@ extension FocusRoom on MutationTarget {
|
||||||
sub.children.any((room) => room.metadata?.id == id),
|
sub.children.any((room) => room.metadata?.id == id),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (parent == null) return;
|
if (parent == null) return false;
|
||||||
|
|
||||||
await container
|
await container
|
||||||
.read(KeyController.provider(KeyController.spaceKey).notifier)
|
.read(KeyController.provider(KeyController.spaceKey).notifier)
|
||||||
|
|
@ -32,5 +33,7 @@ extension FocusRoom on MutationTarget {
|
||||||
await container
|
await container
|
||||||
.read(KeyController.provider(KeyController.roomKey).notifier)
|
.read(KeyController.provider(KeyController.roomKey).notifier)
|
||||||
.set(id);
|
.set(id);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
|
|
||||||
extension LinkToMention on String {
|
extension LinkToMention on String {
|
||||||
/// Extracts a Matrix identifier from this string.
|
/// Extracts a Matrix identifier from this string.
|
||||||
///
|
///
|
||||||
|
|
@ -43,4 +45,7 @@ extension LinkToMention on String {
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IList<String> get via =>
|
||||||
|
.new(Uri.tryParse(replaceAll("/#", ""))?.queryParametersAll["via"] ?? []);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ part "room_summary.g.dart";
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class const RoomSummary({
|
class const RoomSummary({
|
||||||
required final String roomId,
|
required final String roomId,
|
||||||
@JsonKey(name: "num_joined_members") required final int joinedMembers,
|
@JsonKey(name: "num_joined_members") final int? joinedMembers,
|
||||||
final JoinRule? joinRule,
|
final JoinRule? joinRule,
|
||||||
final String? name,
|
final String? name,
|
||||||
final Uri? avatarUrl,
|
final Uri? avatarUrl,
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,12 @@ import "package:material_ui/material_ui.dart";
|
||||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
import "package:nexus/controllers/room_summary.dart";
|
import "package:nexus/controllers/room_summary.dart";
|
||||||
import "package:nexus/controllers/user.dart";
|
import "package:nexus/controllers/user.dart";
|
||||||
|
import "package:nexus/helpers/extensions/focus_room.dart";
|
||||||
import "package:nexus/helpers/extensions/link_to_mention.dart";
|
import "package:nexus/helpers/extensions/link_to_mention.dart";
|
||||||
import "package:nexus/helpers/extensions/show_user_popover.dart";
|
import "package:nexus/helpers/extensions/show_user_popover.dart";
|
||||||
import "package:nexus/models/content/membership.dart";
|
import "package:nexus/models/content/membership.dart";
|
||||||
import "package:nexus/models/room_summary.dart";
|
import "package:nexus/models/room_summary.dart";
|
||||||
|
import "package:nexus/widgets/room_summary_dialog.dart";
|
||||||
|
|
||||||
class const MentionChip(final String content, final String? roomId, {super.key})
|
class const MentionChip(final String content, final String? roomId, {super.key})
|
||||||
extends ConsumerWidget {
|
extends ConsumerWidget {
|
||||||
|
|
@ -25,7 +27,8 @@ class const MentionChip(final String content, final String? roomId, {super.key})
|
||||||
.watch(
|
.watch(
|
||||||
RoomSummaryController.provider(.new(roomIdOrAlias: mention!)),
|
RoomSummaryController.provider(.new(roomIdOrAlias: mention!)),
|
||||||
)
|
)
|
||||||
.whenOrNull(data: (data) => data),
|
.whenOrNull(data: (data) => data) ??
|
||||||
|
RoomSummary(roomId: mention),
|
||||||
|
|
||||||
_ => null,
|
_ => null,
|
||||||
};
|
};
|
||||||
|
|
@ -33,11 +36,28 @@ class const MentionChip(final String content, final String? roomId, {super.key})
|
||||||
return mention == null
|
return mention == null
|
||||||
? SizedBox.shrink()
|
? SizedBox.shrink()
|
||||||
: InkWell(
|
: InkWell(
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
if (data case MembershipContent membership) {
|
if (data case MembershipContent membership) {
|
||||||
context.showUserPopover(membership, mention, roomId: roomId);
|
context.showUserPopover(membership, mention, roomId: roomId);
|
||||||
} else if (data case RoomSummary summary) {
|
} else if (data case RoomSummary summary) {
|
||||||
// TODO: Handle summary
|
if (!await ref.focusRoom(summary.roomId) && context.mounted) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => Consumer(
|
||||||
|
builder: (context, ref, _) => RoomSummaryDialog(
|
||||||
|
ref
|
||||||
|
.watch(
|
||||||
|
RoomSummaryController.provider(
|
||||||
|
.new(roomIdOrAlias: mention),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.whenOrNull(data: (data) => data) ??
|
||||||
|
summary,
|
||||||
|
via: content.via,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,9 @@
|
||||||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
|
||||||
import "package:material_ui/material_ui.dart";
|
import "package:material_ui/material_ui.dart";
|
||||||
import "package:flutter_hooks/flutter_hooks.dart";
|
import "package:flutter_hooks/flutter_hooks.dart";
|
||||||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||||
import "package:nexus/controllers/client.dart";
|
|
||||||
import "package:nexus/controllers/room_summary.dart";
|
import "package:nexus/controllers/room_summary.dart";
|
||||||
import "package:nexus/helpers/extensions/better_when.dart";
|
|
||||||
import "package:nexus/helpers/extensions/focus_room.dart";
|
|
||||||
import "package:nexus/helpers/extensions/link_to_mention.dart";
|
import "package:nexus/helpers/extensions/link_to_mention.dart";
|
||||||
import "package:nexus/widgets/room_summary_dialog.dart";
|
import "package:nexus/widgets/room_summary_dialog.dart";
|
||||||
import "package:nexus/main.dart";
|
|
||||||
|
|
||||||
class const JoinDialog(final WidgetRef ref, {super.key}) extends HookWidget {
|
class const JoinDialog(final WidgetRef ref, {super.key}) extends HookWidget {
|
||||||
@override
|
@override
|
||||||
|
|
@ -19,90 +14,21 @@ class const JoinDialog(final WidgetRef ref, {super.key}) extends HookWidget {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
|
||||||
final roomIdOrAlias = roomAlias.text.mention ?? roomAlias.text;
|
final roomIdOrAlias = roomAlias.text.mention ?? roomAlias.text;
|
||||||
final IList<String> via = .new(
|
final via = roomAlias.text.via;
|
||||||
Uri.tryParse(roomAlias.text.replaceAll("/#", ""))
|
|
||||||
?.queryParametersAll["via"] ??
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => ref
|
builder: (context) => switch (ref.watch(
|
||||||
.watch(
|
|
||||||
RoomSummaryController.provider(
|
RoomSummaryController.provider(
|
||||||
.new(roomIdOrAlias: roomIdOrAlias, via: via),
|
.new(roomIdOrAlias: roomIdOrAlias, via: via),
|
||||||
),
|
),
|
||||||
)
|
)) {
|
||||||
.betterWhen(
|
AsyncData(:final value) => RoomSummaryDialog(value, via: via),
|
||||||
data: (summary) => RoomSummaryDialog(
|
AsyncError _ || AsyncLoading _ => RoomSummaryDialog(
|
||||||
summary,
|
.new(roomId: roomAlias.text),
|
||||||
extraActions: .new([
|
via: via,
|
||||||
TextButton(
|
|
||||||
onPressed: () async {
|
|
||||||
// Capture everything before the dialog closes.
|
|
||||||
final container = ProviderScope.containerOf(context);
|
|
||||||
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
|
||||||
final colors = Theme.of(context).colorScheme;
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
|
|
||||||
final snackbar = scaffoldMessenger.showSnackBar(
|
|
||||||
.new(
|
|
||||||
content: Text("Joining room $roomIdOrAlias."),
|
|
||||||
duration: Duration(days: 999),
|
|
||||||
),
|
),
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
final id = await container
|
|
||||||
.read(ClientController.provider.notifier)
|
|
||||||
.joinRoom(
|
|
||||||
.new(roomIdOrAlias: roomIdOrAlias, via: via),
|
|
||||||
);
|
|
||||||
|
|
||||||
snackbar.close();
|
|
||||||
|
|
||||||
scaffoldMessenger.showSnackBar(
|
|
||||||
.new(
|
|
||||||
content: Text(
|
|
||||||
"Room $roomIdOrAlias successfully joined.",
|
|
||||||
),
|
|
||||||
action: .new(
|
|
||||||
label: "Open",
|
|
||||||
onPressed: () =>
|
|
||||||
container.focusRoom(id).onError(showError),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
snackbar.close();
|
|
||||||
scaffoldMessenger.showSnackBar(
|
|
||||||
.new(
|
|
||||||
backgroundColor: colors.errorContainer,
|
|
||||||
content: Text(
|
|
||||||
error.toString(),
|
|
||||||
style: .new(color: colors.onErrorContainer),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
child: Text("Join"),
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
loading: () => Dialog(
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: .center,
|
|
||||||
mainAxisSize: .min,
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: .all(24),
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ final class const RoomAppbar({
|
||||||
.new(
|
.new(
|
||||||
roomId: room!.metadata!.id,
|
roomId: room!.metadata!.id,
|
||||||
joinedMembers:
|
joinedMembers:
|
||||||
room.metadata!.lazyLoadSummary?.joinedMemberCount ?? -1,
|
room.metadata!.lazyLoadSummary?.joinedMemberCount,
|
||||||
avatarUrl: room.metadata?.avatar,
|
avatarUrl: room.metadata?.avatar,
|
||||||
canonicalAlias: room.metadata?.canonicalAlias,
|
canonicalAlias: room.metadata?.canonicalAlias,
|
||||||
name: room.metadata?.name,
|
name: room.metadata?.name,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
import "package:material_ui/material_ui.dart";
|
import "package:material_ui/material_ui.dart";
|
||||||
|
import "package:nexus/controllers/client.dart";
|
||||||
|
import "package:nexus/helpers/extensions/focus_room.dart";
|
||||||
import "package:nexus/widgets/avatar_or_hash.dart";
|
import "package:nexus/widgets/avatar_or_hash.dart";
|
||||||
import "package:nexus/widgets/expandable_image.dart";
|
import "package:nexus/widgets/expandable_image.dart";
|
||||||
import "package:nexus/widgets/linkified_text.dart";
|
import "package:nexus/widgets/linkified_text.dart";
|
||||||
|
|
@ -8,11 +10,11 @@ import "package:nexus/models/room_summary.dart";
|
||||||
|
|
||||||
class const RoomSummaryDialog(
|
class const RoomSummaryDialog(
|
||||||
final RoomSummary summary, {
|
final RoomSummary summary, {
|
||||||
final IList<Widget> extraActions = const IList.empty(),
|
final IList<String>? via,
|
||||||
super.key,
|
super.key,
|
||||||
}) extends ConsumerWidget {
|
}) extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) => AlertDialog(
|
Widget build(BuildContext context) => AlertDialog(
|
||||||
constraints: .loose(.fromWidth(400)),
|
constraints: .loose(.fromWidth(400)),
|
||||||
scrollable: true,
|
scrollable: true,
|
||||||
contentPadding: EdgeInsets.all(24).copyWith(top: 8),
|
contentPadding: EdgeInsets.all(24).copyWith(top: 8),
|
||||||
|
|
@ -25,14 +27,14 @@ class const RoomSummaryDialog(
|
||||||
summary.avatarUrl == null ? null : .new(mxc: summary.avatarUrl!),
|
summary.avatarUrl == null ? null : .new(mxc: summary.avatarUrl!),
|
||||||
child: AvatarOrHash(
|
child: AvatarOrHash(
|
||||||
summary.avatarUrl,
|
summary.avatarUrl,
|
||||||
summary.name ?? "Unnamed Room",
|
"",
|
||||||
height: 64,
|
height: 64,
|
||||||
fallback: Icon(Icons.numbers),
|
fallback: Icon(Icons.numbers),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
summary.name ?? summary.canonicalAlias ?? summary.roomId,
|
summary.name ?? summary.canonicalAlias ?? "Unnamed Room",
|
||||||
overflow: .ellipsis,
|
overflow: .ellipsis,
|
||||||
maxLines: 3,
|
maxLines: 3,
|
||||||
),
|
),
|
||||||
|
|
@ -42,7 +44,9 @@ class const RoomSummaryDialog(
|
||||||
content: Column(
|
content: Column(
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text("${summary.joinedMembers} members"),
|
title: Text(
|
||||||
|
"${summary.joinedMembers ?? "Unknown number of"} members",
|
||||||
|
),
|
||||||
leading: Icon(Icons.people),
|
leading: Icon(Icons.people),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
|
|
@ -66,7 +70,55 @@ class const RoomSummaryDialog(
|
||||||
|
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(onPressed: Navigator.of(context).pop, child: Text("Cancel")),
|
TextButton(onPressed: Navigator.of(context).pop, child: Text("Cancel")),
|
||||||
...extraActions,
|
if (via != null)
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
// Capture everything before the dialog closes.
|
||||||
|
final container = ProviderScope.containerOf(context);
|
||||||
|
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||||
|
final colors = Theme.of(context).colorScheme;
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
|
||||||
|
final roomIdOrAlias = summary.canonicalAlias ?? summary.roomId;
|
||||||
|
|
||||||
|
final snackbar = scaffoldMessenger.showSnackBar(
|
||||||
|
.new(
|
||||||
|
content: Text("Joining room $roomIdOrAlias."),
|
||||||
|
duration: Duration(days: 999),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final id = await container
|
||||||
|
.read(ClientController.provider.notifier)
|
||||||
|
.joinRoom(.new(roomIdOrAlias: summary.roomId, via: via!));
|
||||||
|
|
||||||
|
snackbar.close();
|
||||||
|
|
||||||
|
scaffoldMessenger.showSnackBar(
|
||||||
|
.new(
|
||||||
|
content: Text("Room $roomIdOrAlias successfully joined."),
|
||||||
|
action: .new(
|
||||||
|
label: "Open",
|
||||||
|
onPressed: () => container.focusRoom(id),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
snackbar.close();
|
||||||
|
scaffoldMessenger.showSnackBar(
|
||||||
|
.new(
|
||||||
|
backgroundColor: colors.errorContainer,
|
||||||
|
content: Text(
|
||||||
|
error.toString(),
|
||||||
|
style: .new(color: colors.onErrorContainer),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text("Join"),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue