parent
9e024fb6f2
commit
281bab0202
9 changed files with 230 additions and 204 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -41,4 +41,6 @@ key.properties
|
|||
password.txt
|
||||
|
||||
# Nix
|
||||
/result
|
||||
/result
|
||||
# Widget Preview related
|
||||
.widget_preview/
|
||||
|
|
|
|||
36
lib/helpers/extensions/focus_room.dart
Normal file
36
lib/helpers/extensions/focus_room.dart
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import "package:collection/collection.dart";
|
||||
import "package:flutter_riverpod/experimental/mutation.dart";
|
||||
import "package:nexus/controllers/key.dart";
|
||||
import "package:nexus/controllers/spaces.dart";
|
||||
import "package:nexus/models/space.dart";
|
||||
|
||||
extension FocusRoom on MutationTarget {
|
||||
Future<void> focusRoom(String id) async {
|
||||
final spaces = container.read(SpacesController.provider);
|
||||
|
||||
if (spaces.firstWhereOrNull((space) => space.id == id) case Space _?) {
|
||||
return await container
|
||||
.read(KeyController.provider(KeyController.spaceKey).notifier)
|
||||
.set(id);
|
||||
}
|
||||
|
||||
final parent = spaces.firstWhereOrNull(
|
||||
(space) =>
|
||||
space.children.any((room) => room.metadata?.id == id) ||
|
||||
space.subSpaces.any(
|
||||
(sub) =>
|
||||
sub.room.metadata?.id == id ||
|
||||
sub.children.any((room) => room.metadata?.id == id),
|
||||
),
|
||||
);
|
||||
if (parent == null) return;
|
||||
|
||||
await container
|
||||
.read(KeyController.provider(KeyController.spaceKey).notifier)
|
||||
.set(parent.id);
|
||||
|
||||
await container
|
||||
.read(KeyController.provider(KeyController.roomKey).notifier)
|
||||
.set(id);
|
||||
}
|
||||
}
|
||||
|
|
@ -7,8 +7,9 @@ part "lazy_load_summary.g.dart";
|
|||
@Freezed(toJson: false, fromJson: false)
|
||||
@JsonSerializable()
|
||||
class const LazyLoadSummary({
|
||||
required final IList<String>? heroes,
|
||||
required final int? joinedMemberCount,
|
||||
@JsonKey(name: "m.heroes") required final IList<String>? heroes,
|
||||
@JsonKey(name: "m.joined_member_count") required final int? joinedMemberCount,
|
||||
@JsonKey(name: "m.invited_member_count")
|
||||
required final int? invitedMemberCount,
|
||||
}) with _$LazyLoadSummary {
|
||||
Map<String, Object?> toJson() => _$LazyLoadSummaryToJson(this);
|
||||
|
|
|
|||
|
|
@ -9,9 +9,6 @@ part "room_metadata.g.dart";
|
|||
@JsonSerializable()
|
||||
class const RoomMetadata({
|
||||
@JsonKey(name: "room_id") required final String id,
|
||||
|
||||
// CreateEventContent creationContent,
|
||||
// TombstoneEventContent tombstoneEventContent,
|
||||
final String? name,
|
||||
final Uri? avatar,
|
||||
final String? dmUserId,
|
||||
|
|
|
|||
|
|
@ -23,9 +23,8 @@ final class const AvatarOrHash(
|
|||
child: Center(
|
||||
child: ClipRRect(
|
||||
borderRadius: .all(.circular((height - 8) / 2.5)),
|
||||
child: SizedBox(
|
||||
width: height,
|
||||
height: height,
|
||||
child: SizedBox.square(
|
||||
dimension: height,
|
||||
child: avatar == null
|
||||
? fallback ?? box
|
||||
: Image(
|
||||
|
|
|
|||
|
|
@ -1,16 +1,111 @@
|
|||
import "package:collection/collection.dart";
|
||||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||
import "package:material_ui/material_ui.dart";
|
||||
import "package:flutter_hooks/flutter_hooks.dart";
|
||||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||
import "package:nexus/controllers/client.dart";
|
||||
import "package:nexus/controllers/key.dart";
|
||||
import "package:nexus/controllers/spaces.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/widgets/room_summary_dialog.dart";
|
||||
import "package:nexus/main.dart";
|
||||
|
||||
class const JoinDialog(final WidgetRef ref, {super.key}) extends HookWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final roomAlias = useTextEditingController();
|
||||
|
||||
Future<void> onJoin() async {
|
||||
Navigator.of(context).pop();
|
||||
|
||||
final roomIdOrAlias = roomAlias.text.mention ?? roomAlias.text;
|
||||
final IList<String> via = .new(
|
||||
Uri.tryParse(roomAlias.text.replaceAll("/#", ""))
|
||||
?.queryParametersAll["via"] ??
|
||||
[],
|
||||
);
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => ref
|
||||
.watch(
|
||||
RoomSummaryController.provider(
|
||||
.new(roomIdOrAlias: roomIdOrAlias, via: via),
|
||||
),
|
||||
)
|
||||
.betterWhen(
|
||||
data: (summary) => RoomSummaryDialog(
|
||||
summary,
|
||||
extraActions: .new([
|
||||
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(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return AlertDialog(
|
||||
title: Text("Join a Room"),
|
||||
content: Column(
|
||||
|
|
@ -21,111 +116,14 @@ class const JoinDialog(final WidgetRef ref, {super.key}) extends HookWidget {
|
|||
SizedBox(height: 12),
|
||||
TextField(
|
||||
controller: roomAlias,
|
||||
decoration: .new(hintText: "#room:server"),
|
||||
decoration: .new(hintText: "#room:server.name"),
|
||||
onEditingComplete: onJoin,
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(onPressed: Navigator.of(context).pop, child: Text("Cancel")),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
|
||||
if (context.mounted) {
|
||||
final roomIdOrAlias = roomAlias.text.mention ?? roomAlias.text;
|
||||
|
||||
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||
|
||||
final snackbar = scaffoldMessenger.showSnackBar(
|
||||
.new(
|
||||
content: Text("Joining room $roomIdOrAlias."),
|
||||
duration: Duration(days: 999),
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
final id = await ref
|
||||
.watch(ClientController.provider.notifier)
|
||||
.joinRoom(
|
||||
.new(
|
||||
roomIdOrAlias: roomIdOrAlias,
|
||||
via: .new(
|
||||
Uri.tryParse(roomAlias.text.replaceAll("/#", ""))
|
||||
?.queryParametersAll["via"] ??
|
||||
[],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
snackbar.close();
|
||||
|
||||
scaffoldMessenger.showSnackBar(
|
||||
.new(
|
||||
content: Text("Room $roomIdOrAlias successfully joined."),
|
||||
action: .new(
|
||||
label: "Open",
|
||||
onPressed: () async {
|
||||
final spaces = ref.watch(SpacesController.provider);
|
||||
final space = spaces.firstWhereOrNull(
|
||||
(space) => space.id == id,
|
||||
);
|
||||
|
||||
await ref
|
||||
.watch(
|
||||
KeyController.provider(KeyController.spaceKey)
|
||||
.notifier,
|
||||
)
|
||||
.set(
|
||||
space?.id ??
|
||||
spaces
|
||||
.firstWhere(
|
||||
(space) =>
|
||||
space.children.any(
|
||||
(child) =>
|
||||
child.metadata?.id == id,
|
||||
) ||
|
||||
space.subSpaces.any(
|
||||
(child) =>
|
||||
child.room.metadata?.id == id,
|
||||
),
|
||||
)
|
||||
.id,
|
||||
);
|
||||
|
||||
if (space == null) {
|
||||
await ref
|
||||
.watch(
|
||||
KeyController.provider(KeyController.roomKey)
|
||||
.notifier,
|
||||
)
|
||||
.set(id);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
snackbar.close();
|
||||
if (context.mounted) {
|
||||
scaffoldMessenger.showSnackBar(
|
||||
.new(
|
||||
backgroundColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.errorContainer,
|
||||
content: Text(
|
||||
error.toString(),
|
||||
style: .new(
|
||||
color: Theme.of(context).colorScheme.onErrorContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Text("Join"),
|
||||
),
|
||||
TextButton(onPressed: onJoin, child: Text("Join")),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
import "dart:async";
|
||||
|
||||
import "package:collection/collection.dart";
|
||||
import "package:m3e_buttons/m3e_buttons.dart";
|
||||
import "package:material_ui/material_ui.dart";
|
||||
import "package:flutter_hooks/flutter_hooks.dart";
|
||||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||
import "package:nexus/controllers/contextual_event.dart";
|
||||
import "package:nexus/controllers/key.dart";
|
||||
import "package:nexus/controllers/notifications.dart";
|
||||
import "package:nexus/controllers/spaces.dart";
|
||||
import "package:nexus/helpers/extensions/focus_room.dart";
|
||||
import "package:nexus/models/event.dart";
|
||||
import "package:nexus/widgets/appbar.dart";
|
||||
import "package:nexus/widgets/error_dialog.dart";
|
||||
|
|
@ -131,41 +129,7 @@ class const NotificationsPage({
|
|||
child: HighlightWrapper(
|
||||
InkWell(
|
||||
onTap: () async {
|
||||
final spaces = ref.read(
|
||||
SpacesController.provider,
|
||||
);
|
||||
final space = spaces.firstWhereOrNull(
|
||||
(space) =>
|
||||
space.children.any(
|
||||
(room) =>
|
||||
room.metadata?.id ==
|
||||
event.roomId,
|
||||
) ||
|
||||
space.subSpaces.any(
|
||||
(subSpace) =>
|
||||
subSpace.children.any(
|
||||
(room) =>
|
||||
room.metadata?.id ==
|
||||
event.roomId,
|
||||
),
|
||||
),
|
||||
);
|
||||
if (space == null) return;
|
||||
|
||||
await ref
|
||||
.read(
|
||||
KeyController.provider(
|
||||
KeyController.spaceKey,
|
||||
).notifier,
|
||||
)
|
||||
.set(space.id);
|
||||
await ref
|
||||
.read(
|
||||
KeyController.provider(
|
||||
KeyController.roomKey,
|
||||
).notifier,
|
||||
)
|
||||
.set(event.roomId);
|
||||
await ref.focusRoom(event.roomId);
|
||||
ref
|
||||
.watch(
|
||||
ContextualEventController.provider(
|
||||
|
|
|
|||
|
|
@ -3,9 +3,8 @@ import "package:hooks_riverpod/hooks_riverpod.dart";
|
|||
import "package:nexus/controllers/rooms.dart";
|
||||
import "package:nexus/widgets/appbar.dart";
|
||||
import "package:nexus/widgets/avatar_or_hash.dart";
|
||||
import "package:nexus/widgets/expandable_image.dart";
|
||||
import "package:nexus/widgets/linkified_text.dart";
|
||||
import "package:nexus/widgets/room_menu.dart";
|
||||
import "package:nexus/widgets/room_summary_dialog.dart";
|
||||
|
||||
final class const RoomAppbar({
|
||||
required final String? roomId,
|
||||
|
|
@ -25,61 +24,19 @@ final class const RoomAppbar({
|
|||
: ref.watch(RoomsController.provider.select((value) => value[roomId!]));
|
||||
|
||||
return Appbar(
|
||||
onTap: room == null
|
||||
onTap: room?.metadata == null
|
||||
? null
|
||||
: () => showDialog(
|
||||
context: context,
|
||||
builder: (context) => Dialog(
|
||||
constraints: .loose(.fromWidth(400)),
|
||||
child: Padding(
|
||||
padding: .all(24),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: .min,
|
||||
crossAxisAlignment: .start,
|
||||
spacing: 8,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 12,
|
||||
mainAxisSize: .min,
|
||||
children: [
|
||||
if (room.metadata?.avatar != null)
|
||||
ExpandableImage(
|
||||
room.metadata?.avatar == null
|
||||
? null
|
||||
: .new(mxc: room.metadata!.avatar!),
|
||||
child: AvatarOrHash(
|
||||
room.metadata?.avatar,
|
||||
room.metadata?.name ?? "Unnamed Room",
|
||||
height: 64,
|
||||
fallback: Icon(Icons.numbers),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
room.metadata?.name ?? "Unnamed Room",
|
||||
overflow: .ellipsis,
|
||||
maxLines: 3,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineSmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (room.metadata?.topic?.isNotEmpty == true)
|
||||
LinkifiedText(
|
||||
room.metadata!.topic!,
|
||||
style: Theme.of(context).textTheme.bodyLarge
|
||||
?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
builder: (context) => RoomSummaryDialog(
|
||||
.new(
|
||||
roomId: room!.metadata!.id,
|
||||
joinedMembers:
|
||||
room.metadata!.lazyLoadSummary?.joinedMemberCount ?? -1,
|
||||
avatarUrl: room.metadata?.avatar,
|
||||
canonicalAlias: room.metadata?.canonicalAlias,
|
||||
name: room.metadata?.name,
|
||||
topic: room.metadata?.topic,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
72
lib/widgets/room_summary_dialog.dart
Normal file
72
lib/widgets/room_summary_dialog.dart
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||
import "package:material_ui/material_ui.dart";
|
||||
import "package:nexus/widgets/avatar_or_hash.dart";
|
||||
import "package:nexus/widgets/expandable_image.dart";
|
||||
import "package:nexus/widgets/linkified_text.dart";
|
||||
import "package:nexus/models/room_summary.dart";
|
||||
|
||||
class const RoomSummaryDialog(
|
||||
final RoomSummary summary, {
|
||||
final IList<Widget> extraActions = const IList.empty(),
|
||||
super.key,
|
||||
}) extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) => AlertDialog(
|
||||
constraints: .loose(.fromWidth(400)),
|
||||
scrollable: true,
|
||||
contentPadding: EdgeInsets.all(24).copyWith(top: 8),
|
||||
title: Row(
|
||||
spacing: 12,
|
||||
mainAxisSize: .min,
|
||||
children: [
|
||||
if (summary.avatarUrl != null)
|
||||
ExpandableImage(
|
||||
summary.avatarUrl == null ? null : .new(mxc: summary.avatarUrl!),
|
||||
child: AvatarOrHash(
|
||||
summary.avatarUrl,
|
||||
summary.name ?? "Unnamed Room",
|
||||
height: 64,
|
||||
fallback: Icon(Icons.numbers),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
summary.name ?? summary.canonicalAlias ?? summary.roomId,
|
||||
overflow: .ellipsis,
|
||||
maxLines: 3,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
content: Column(
|
||||
children: [
|
||||
ListTile(
|
||||
title: Text("${summary.joinedMembers} members"),
|
||||
leading: Icon(Icons.people),
|
||||
),
|
||||
ListTile(
|
||||
title: SelectableText(summary.canonicalAlias ?? summary.roomId),
|
||||
leading: Icon(Icons.numbers),
|
||||
),
|
||||
|
||||
if (summary.topic != null)
|
||||
ListTile(
|
||||
isThreeLine: true,
|
||||
subtitle: LinkifiedText(
|
||||
summary.topic!,
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
leading: Icon(Icons.info),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
actions: [
|
||||
TextButton(onPressed: Navigator.of(context).pop, child: Text("Cancel")),
|
||||
...extraActions,
|
||||
],
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue