parent
df592b3f38
commit
ed84410039
13 changed files with 540 additions and 66 deletions
111
lib/helpers/extensions/create_room.dart
Normal file
111
lib/helpers/extensions/create_room.dart
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
import "dart:async";
|
||||
|
||||
import "package:collection/collection.dart";
|
||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||
import "package:material_ui/material_ui.dart";
|
||||
import "package:nexus/controllers/client.dart";
|
||||
import "package:nexus/controllers/rooms.dart";
|
||||
import "package:nexus/controllers/sidebar_page.dart";
|
||||
import "package:nexus/controllers/spaces.dart";
|
||||
import "package:nexus/helpers/extensions/focus_room.dart";
|
||||
import "package:nexus/helpers/extensions/get_all_child_rooms.dart";
|
||||
import "package:nexus/helpers/extensions/with_progress_snackbar.dart";
|
||||
import "package:nexus/models/requests/create_room.dart";
|
||||
|
||||
extension CreateRoom on BuildContext {
|
||||
/// Creates a room, showing progress and errors in snackbars, and opens it
|
||||
/// once it has synced. [request] may be a future, so any lookups needed to
|
||||
/// build it happen while the progress snackbar is shown.
|
||||
///
|
||||
/// Returns the created room's ID, or null if creating it failed.
|
||||
Future<String?> createRoom(
|
||||
CreateRoomRequest request, {
|
||||
required String name,
|
||||
}) async {
|
||||
final container = ProviderScope.containerOf(this);
|
||||
final messenger = ScaffoldMessenger.of(this);
|
||||
|
||||
final result = await withProgressSnackbar("Creating $name...", () async {
|
||||
final roomId = await container
|
||||
.read(ClientController.provider.notifier)
|
||||
.createRoom(request);
|
||||
|
||||
// Wait for the room to show up in the room list (under DMs, if it's
|
||||
// one) before opening it.
|
||||
final synced = Completer<bool>();
|
||||
final subscription = container.listen(
|
||||
SpacesController.provider.select(
|
||||
(async) =>
|
||||
async.value?.any(
|
||||
(space) =>
|
||||
space.id == roomId ||
|
||||
((!request.isDirect || space.id == "dms") &&
|
||||
space.allChildRooms.any(
|
||||
(room) => room.metadata?.id == roomId,
|
||||
)),
|
||||
) ??
|
||||
false,
|
||||
),
|
||||
(_, found) {
|
||||
if (found && !synced.isCompleted) synced.complete(true);
|
||||
},
|
||||
fireImmediately: true,
|
||||
);
|
||||
|
||||
return (
|
||||
roomId,
|
||||
await synced.future
|
||||
.timeout(const .new(seconds: 15), onTimeout: () => false)
|
||||
.whenComplete(subscription.close),
|
||||
);
|
||||
});
|
||||
if (result == null) return null;
|
||||
|
||||
final (roomId, synced) = result;
|
||||
if (synced && await container.focusRoom(roomId)) {
|
||||
container.read(SidebarPageController.provider.notifier).set(.rooms);
|
||||
} else {
|
||||
messenger.showSnackBar(
|
||||
.new(
|
||||
content: Text("Created $name, but it hasn't finished syncing yet."),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return roomId;
|
||||
}
|
||||
|
||||
/// Opens the existing DM with [userId], or creates one if there isn't one.
|
||||
Future<void> startDirectMessage(
|
||||
String userId, {
|
||||
required String name,
|
||||
bool encrypted = true,
|
||||
String? inviteReason,
|
||||
}) async {
|
||||
final container = ProviderScope.containerOf(this);
|
||||
final existing = container
|
||||
.read(RoomsController.provider)
|
||||
.values
|
||||
.firstWhereOrNull((room) => room.metadata?.dmUserId == userId)
|
||||
?.metadata
|
||||
?.id;
|
||||
|
||||
if (existing != null) {
|
||||
if (await container.focusRoom(existing)) {
|
||||
container.read(SidebarPageController.provider.notifier).set(.rooms);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
await createRoom(
|
||||
.new(
|
||||
isDirect: true,
|
||||
preset: .trustedPrivateChat,
|
||||
invite: .new([userId]),
|
||||
inviteReason: inviteReason,
|
||||
initialState: encrypted ? .new([.encryption]) : const .empty(),
|
||||
),
|
||||
name: "a DM with $name",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,8 @@
|
|||
extension GetLocalpart on String {
|
||||
String get localpart => length > 1 ? substring(1).split(":").first : "?";
|
||||
|
||||
String? get serverName {
|
||||
final index = indexOf(":");
|
||||
return index == -1 ? null : substring(index + 1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import "package:material_ui/material_ui.dart";
|
|||
import "package:nexus/controllers/client.dart";
|
||||
import "package:nexus/controllers/sidebar_page.dart";
|
||||
import "package:nexus/helpers/extensions/focus_room.dart";
|
||||
import "package:nexus/helpers/extensions/with_progress_snackbar.dart";
|
||||
import "package:nexus/models/requests/join_room.dart";
|
||||
|
||||
extension JoinRoom on BuildContext {
|
||||
|
|
@ -16,56 +17,27 @@ extension JoinRoom on BuildContext {
|
|||
}) async {
|
||||
final container = ProviderScope.containerOf(this);
|
||||
final messenger = ScaffoldMessenger.of(this);
|
||||
final colorScheme = Theme.of(this).colorScheme;
|
||||
|
||||
final joining = messenger.showSnackBar(
|
||||
.new(content: Text("Joining $name..."), duration: const .new(days: 1)),
|
||||
final roomId = await withProgressSnackbar(
|
||||
"Joining $name...",
|
||||
() =>
|
||||
container.read(ClientController.provider.notifier).joinRoom(request),
|
||||
);
|
||||
if (roomId == null) return null;
|
||||
|
||||
messenger.showSnackBar(
|
||||
.new(
|
||||
content: Text("Joined $name."),
|
||||
action: .new(
|
||||
label: "Open",
|
||||
onPressed: () async {
|
||||
await container.focusRoom(roomId);
|
||||
container.read(SidebarPageController.provider.notifier).set(.rooms);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
final roomId = await container
|
||||
.read(ClientController.provider.notifier)
|
||||
.joinRoom(request);
|
||||
|
||||
joining.close();
|
||||
messenger.showSnackBar(
|
||||
.new(
|
||||
content: Text("Joined $name."),
|
||||
action: .new(
|
||||
label: "Open",
|
||||
onPressed: () async {
|
||||
if (await container.focusRoom(roomId)) {
|
||||
container
|
||||
.read(SidebarPageController.provider.notifier)
|
||||
.set(.rooms);
|
||||
} else {
|
||||
messenger.showSnackBar(
|
||||
.new(
|
||||
content: Text(
|
||||
"$name hasn't finished syncing yet, try again in a moment.",
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return roomId;
|
||||
} catch (error) {
|
||||
joining.close();
|
||||
messenger.showSnackBar(
|
||||
.new(
|
||||
backgroundColor: colorScheme.errorContainer,
|
||||
content: Text(
|
||||
error.toString(),
|
||||
style: .new(color: colorScheme.onErrorContainer),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
return roomId;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import "package:nexus/helpers/extensions/is_desktop.dart";
|
|||
extension ShowAdaptiveSheet on BuildContext {
|
||||
/// Shows the widget from [builder] in a dialog on desktop, or a bottom sheet
|
||||
/// on mobile. Use the context passed to [builder] to close it.
|
||||
Future<void> showAdaptiveSheet(WidgetBuilder builder) => isDesktop
|
||||
? showDialog(
|
||||
Future<T?> showAdaptiveSheet<T>(WidgetBuilder builder) => isDesktop
|
||||
? showDialog<T>(
|
||||
context: this,
|
||||
builder: (context) => Dialog(
|
||||
clipBehavior: .antiAlias,
|
||||
|
|
@ -15,7 +15,7 @@ extension ShowAdaptiveSheet on BuildContext {
|
|||
),
|
||||
),
|
||||
)
|
||||
: showModalBottomSheet(
|
||||
: showModalBottomSheet<T>(
|
||||
context: this,
|
||||
isScrollControlled: true,
|
||||
constraints: .new(maxHeight: MediaQuery.sizeOf(this).height - 80),
|
||||
|
|
|
|||
37
lib/helpers/extensions/with_progress_snackbar.dart
Normal file
37
lib/helpers/extensions/with_progress_snackbar.dart
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import "package:material_ui/material_ui.dart";
|
||||
|
||||
extension WithProgressSnackbar on BuildContext {
|
||||
/// Shows [progress] in a snackbar while [action] runs, and shows any error
|
||||
/// it throws in an error snackbar.
|
||||
///
|
||||
/// Returns the result of [action], or null if it failed.
|
||||
Future<T?> withProgressSnackbar<T>(
|
||||
String progress,
|
||||
Future<T> Function() action,
|
||||
) async {
|
||||
final messenger = ScaffoldMessenger.of(this);
|
||||
final colorScheme = Theme.of(this).colorScheme;
|
||||
|
||||
final snackbar = messenger.showSnackBar(
|
||||
.new(content: Text(progress), duration: const .new(days: 1)),
|
||||
);
|
||||
|
||||
try {
|
||||
return await action();
|
||||
} catch (error) {
|
||||
messenger.showSnackBar(
|
||||
.new(
|
||||
backgroundColor: colorScheme.errorContainer,
|
||||
content: Text(
|
||||
error.toString(),
|
||||
style: .new(color: colorScheme.onErrorContainer),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return null;
|
||||
} finally {
|
||||
snackbar.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue