forked from Henry-Hiles/nexus
working sidebar
This commit is contained in:
parent
c084bc4caf
commit
85d96b80bc
13 changed files with 491 additions and 436 deletions
|
|
@ -5,6 +5,7 @@ import "package:ffi/ffi.dart";
|
||||||
import "package:flutter/foundation.dart";
|
import "package:flutter/foundation.dart";
|
||||||
import "package:nexus/controllers/client_state_controller.dart";
|
import "package:nexus/controllers/client_state_controller.dart";
|
||||||
import "package:nexus/controllers/rooms_controller.dart";
|
import "package:nexus/controllers/rooms_controller.dart";
|
||||||
|
import "package:nexus/controllers/space_edges_controller.dart";
|
||||||
import "package:nexus/controllers/sync_status_controller.dart";
|
import "package:nexus/controllers/sync_status_controller.dart";
|
||||||
import "package:nexus/controllers/top_level_spaces_controller.dart";
|
import "package:nexus/controllers/top_level_spaces_controller.dart";
|
||||||
import "package:nexus/helpers/extensions/gomuks_buffer.dart";
|
import "package:nexus/helpers/extensions/gomuks_buffer.dart";
|
||||||
|
|
@ -52,9 +53,16 @@ class ClientController extends AsyncNotifier<int> {
|
||||||
ref
|
ref
|
||||||
.watch(roomProvider.notifier)
|
.watch(roomProvider.notifier)
|
||||||
.update(syncData.rooms, syncData.leftRooms);
|
.update(syncData.rooms, syncData.leftRooms);
|
||||||
|
if (syncData.topLevelSpaces != null) {
|
||||||
ref
|
ref
|
||||||
.watch(TopLevelSpacesController.provider.notifier)
|
.watch(TopLevelSpacesController.provider.notifier)
|
||||||
.set(syncData.topLevelSpaces);
|
.set(syncData.topLevelSpaces!);
|
||||||
|
}
|
||||||
|
if (syncData.spaceEdges != null) {
|
||||||
|
ref
|
||||||
|
.watch(SpaceEdgesController.provider.notifier)
|
||||||
|
.set(syncData.spaceEdges!);
|
||||||
|
}
|
||||||
|
|
||||||
// ref
|
// ref
|
||||||
// .watch(SyncStatusController.provider.notifier)
|
// .watch(SyncStatusController.provider.notifier)
|
||||||
|
|
|
||||||
|
|
@ -2,24 +2,23 @@ import "package:collection/collection.dart";
|
||||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
import "package:nexus/controllers/key_controller.dart";
|
import "package:nexus/controllers/key_controller.dart";
|
||||||
import "package:nexus/controllers/selected_space_controller.dart";
|
import "package:nexus/controllers/selected_space_controller.dart";
|
||||||
import "package:nexus/models/full_room.dart";
|
import "package:nexus/models/room.dart";
|
||||||
|
|
||||||
class SelectedRoomController extends AsyncNotifier<FullRoom?> {
|
class SelectedRoomController extends Notifier<Room?> {
|
||||||
@override
|
@override
|
||||||
Future<FullRoom?> build() async {
|
Room? build() {
|
||||||
final space = await ref.watch(SelectedSpaceController.provider.future);
|
final space = ref.watch(SelectedSpaceController.provider);
|
||||||
final selectedRoomId = ref.watch(
|
final selectedRoomId = ref.watch(
|
||||||
KeyController.provider(KeyController.roomKey),
|
KeyController.provider(KeyController.roomKey),
|
||||||
);
|
);
|
||||||
|
|
||||||
return space.children.firstWhereOrNull(
|
return space.children.firstWhereOrNull(
|
||||||
(room) => room.roomData.id == selectedRoomId,
|
(room) => room.metadata?.id == selectedRoomId,
|
||||||
) ??
|
) ??
|
||||||
space.children.firstOrNull;
|
space.children.firstOrNull;
|
||||||
}
|
}
|
||||||
|
|
||||||
static final provider =
|
static final provider = NotifierProvider<SelectedRoomController, Room?>(
|
||||||
AsyncNotifierProvider<SelectedRoomController, FullRoom?>(
|
|
||||||
SelectedRoomController.new,
|
SelectedRoomController.new,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,10 @@ import "package:nexus/controllers/key_controller.dart";
|
||||||
import "package:nexus/controllers/spaces_controller.dart";
|
import "package:nexus/controllers/spaces_controller.dart";
|
||||||
import "package:nexus/models/space.dart";
|
import "package:nexus/models/space.dart";
|
||||||
|
|
||||||
class SelectedSpaceController extends AsyncNotifier<Space> {
|
class SelectedSpaceController extends Notifier<Space> {
|
||||||
@override
|
@override
|
||||||
Future<Space> build() async {
|
Space build() {
|
||||||
final spaces = await ref.watch(
|
final spaces = ref.watch(SpacesController.provider);
|
||||||
SpacesController.provider.selectAsync((data) => data),
|
|
||||||
);
|
|
||||||
final selectedSpaceId = ref.watch(
|
final selectedSpaceId = ref.watch(
|
||||||
KeyController.provider(KeyController.spaceKey),
|
KeyController.provider(KeyController.spaceKey),
|
||||||
);
|
);
|
||||||
|
|
@ -18,7 +16,7 @@ class SelectedSpaceController extends AsyncNotifier<Space> {
|
||||||
spaces.first;
|
spaces.first;
|
||||||
}
|
}
|
||||||
|
|
||||||
static final provider = AsyncNotifierProvider<SelectedSpaceController, Space>(
|
static final provider = NotifierProvider<SelectedSpaceController, Space>(
|
||||||
SelectedSpaceController.new,
|
SelectedSpaceController.new,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
15
lib/controllers/space_edges_controller.dart
Normal file
15
lib/controllers/space_edges_controller.dart
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
|
import "package:nexus/models/space_edge.dart";
|
||||||
|
|
||||||
|
class SpaceEdgesController extends Notifier<IMap<String, IList<SpaceEdge>>> {
|
||||||
|
@override
|
||||||
|
IMap<String, IList<SpaceEdge>> build() => const IMap.empty();
|
||||||
|
|
||||||
|
void set(IMap<String, IList<SpaceEdge>> newEdges) => state = newEdges;
|
||||||
|
|
||||||
|
static final provider =
|
||||||
|
NotifierProvider<SpaceEdgesController, IMap<String, IList<SpaceEdge>>>(
|
||||||
|
SpaceEdgesController.new,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,55 +1,94 @@
|
||||||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
import "package:nexus/controllers/client_controller.dart";
|
|
||||||
import "package:nexus/controllers/rooms_controller.dart";
|
import "package:nexus/controllers/rooms_controller.dart";
|
||||||
import "package:nexus/controllers/top_level_spaces_controller.dart";
|
import "package:nexus/controllers/top_level_spaces_controller.dart";
|
||||||
|
import "package:nexus/controllers/space_edges_controller.dart";
|
||||||
import "package:nexus/models/space.dart";
|
import "package:nexus/models/space.dart";
|
||||||
|
import "package:nexus/models/room.dart";
|
||||||
|
import "package:nexus/models/space_edge.dart";
|
||||||
|
|
||||||
class SpacesController extends AsyncNotifier<IList<Space>> {
|
class SpacesController extends Notifier<IList<Space>> {
|
||||||
@override
|
@override
|
||||||
Future<IList<Space>> build() async {
|
IList<Space> build() {
|
||||||
final topLevelSpaceIds = ref.watch(TopLevelSpacesController.provider);
|
|
||||||
final rooms = ref.watch(RoomsController.provider);
|
final rooms = ref.watch(RoomsController.provider);
|
||||||
|
final topLevelSpaceIds = ref.watch(TopLevelSpacesController.provider);
|
||||||
|
final spaceEdges = ref.watch(SpaceEdgesController.provider);
|
||||||
|
|
||||||
final topLevelSpaces = topLevelSpaceIds
|
ISet<String> collectChildIds(String spaceId) {
|
||||||
.map((id) => rooms[id])
|
ISet<String> result = ISet<String>();
|
||||||
.nonNulls
|
void walk(String currentId) {
|
||||||
.toIList();
|
final children = spaceEdges[currentId] ?? IList<SpaceEdge>();
|
||||||
|
for (final edge in children) {
|
||||||
|
final childId = edge.childId;
|
||||||
|
if (!result.contains(childId)) {
|
||||||
|
result = result.add(childId);
|
||||||
|
walk(childId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
walk(spaceId);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
final spaceIdToChildren = IMap.fromEntries(
|
||||||
|
topLevelSpaceIds.map((spaceId) {
|
||||||
|
final children = collectChildIds(
|
||||||
|
spaceId,
|
||||||
|
).map((id) => rooms[id]).nonNulls.toIList();
|
||||||
|
return MapEntry(spaceId, children);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
final allNestedRoomIds = spaceIdToChildren.values
|
||||||
|
.expand((l) => l)
|
||||||
|
.map((r) => rooms.entries.firstWhere((e) => e.value == r).key)
|
||||||
|
.toISet();
|
||||||
|
|
||||||
final dmRooms = rooms.values
|
final dmRooms = rooms.values
|
||||||
.where((room) => room.metadata?.dmUserId != null)
|
.where((room) => room.metadata?.dmUserId != null)
|
||||||
.toIList();
|
.toIList();
|
||||||
|
|
||||||
final topLevelRooms = rooms.values
|
final homeRooms = rooms.entries
|
||||||
.where((room) => room.metadata?.dmUserId == null)
|
|
||||||
.where(
|
.where(
|
||||||
(room) => spaceRooms.every(
|
(e) =>
|
||||||
(space) =>
|
e.value.metadata?.dmUserId == null &&
|
||||||
space.spaceChildren.every((child) => child.roomId != room.id),
|
!allNestedRoomIds.contains(e.key) &&
|
||||||
),
|
!topLevelSpaceIds.contains(e.key),
|
||||||
)
|
)
|
||||||
|
.map((e) => e.value)
|
||||||
.toIList();
|
.toIList();
|
||||||
|
|
||||||
// 4️⃣ Combine all into a single IList
|
final topLevelSpacesList = topLevelSpaceIds
|
||||||
return IList([
|
.map((id) {
|
||||||
Space(
|
final room = rooms[id];
|
||||||
id: "home",
|
if (room == null) return null;
|
||||||
title: "Home",
|
|
||||||
children: topLevelRooms,
|
final children = spaceIdToChildren[id] ?? IList<Room>();
|
||||||
icon: Icons.home,
|
return Space(
|
||||||
),
|
id: id,
|
||||||
|
title: room.metadata?.name ?? "Unnamed Room",
|
||||||
|
room: room,
|
||||||
|
children: children,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.nonNulls
|
||||||
|
.toIList();
|
||||||
|
|
||||||
|
return <Space>[
|
||||||
|
Space(id: "home", title: "Home", icon: Icons.home, children: homeRooms),
|
||||||
Space(
|
Space(
|
||||||
id: "dms",
|
id: "dms",
|
||||||
title: "Direct Messages",
|
title: "Direct Messages",
|
||||||
|
icon: Icons.people,
|
||||||
children: dmRooms,
|
children: dmRooms,
|
||||||
icon: Icons.person,
|
|
||||||
),
|
),
|
||||||
...topLevelSpaces,
|
...topLevelSpacesList,
|
||||||
]);
|
].toIList();
|
||||||
}
|
}
|
||||||
|
|
||||||
static final provider = AsyncNotifierProvider<SpacesController, IList<Space>>(
|
static final provider = NotifierProvider<SpacesController, IList<Space>>(
|
||||||
SpacesController.new,
|
SpacesController.new,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,87 +1,87 @@
|
||||||
import "package:collection/collection.dart";
|
import "package:collection/collection.dart";
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||||
import "package:matrix/matrix.dart";
|
import "package:nexus/controllers/client_controller.dart";
|
||||||
import "package:nexus/controllers/key_controller.dart";
|
import "package:nexus/controllers/key_controller.dart";
|
||||||
import "package:nexus/controllers/spaces_controller.dart";
|
import "package:nexus/controllers/spaces_controller.dart";
|
||||||
|
|
||||||
extension JoinRoomWithSnackbars on Client {
|
extension JoinRoomWithSnackbars on ClientController {
|
||||||
Future<void> joinRoomWithSnackBars(
|
Future<void> joinRoomWithSnackBars(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
String roomAlias,
|
String roomAlias,
|
||||||
WidgetRef ref,
|
WidgetRef ref,
|
||||||
) async {
|
) async {
|
||||||
final parsed = roomAlias.parseIdentifierIntoParts();
|
// final parsed = roomAlias.parseIdentifierIntoParts();
|
||||||
final alias = parsed?.primaryIdentifier ?? roomAlias;
|
// final alias = parsed?.primaryIdentifier ?? roomAlias;
|
||||||
|
|
||||||
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
// final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||||
|
|
||||||
final snackbar = scaffoldMessenger.showSnackBar(
|
// final snackbar = scaffoldMessenger.showSnackBar(
|
||||||
SnackBar(
|
// SnackBar(
|
||||||
content: Text("Joining room $alias."),
|
// content: Text("Joining room $alias."),
|
||||||
duration: Duration(days: 999),
|
// duration: Duration(days: 999),
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
|
|
||||||
try {
|
// try {
|
||||||
final id = await joinRoom(alias, via: parsed?.via.toList());
|
// final id = await joinRoom(alias, via: parsed?.via.toList());
|
||||||
|
|
||||||
snackbar.close();
|
// snackbar.close();
|
||||||
|
|
||||||
scaffoldMessenger.showSnackBar(
|
// scaffoldMessenger.showSnackBar(
|
||||||
SnackBar(
|
// SnackBar(
|
||||||
content: Text("Room $alias successfully joined."),
|
// content: Text("Room $alias successfully joined."),
|
||||||
action: SnackBarAction(
|
// action: SnackBarAction(
|
||||||
label: "Open",
|
// label: "Open",
|
||||||
onPressed: () async {
|
// onPressed: () async {
|
||||||
final spaces = await ref.refresh(
|
// final spaces = await ref.refresh(
|
||||||
SpacesController.provider.future,
|
// SpacesController.provider.future,
|
||||||
);
|
// );
|
||||||
final space = spaces.firstWhereOrNull((space) => space.id == id);
|
// final space = spaces.firstWhereOrNull((space) => space.id == id);
|
||||||
|
|
||||||
await ref
|
// await ref
|
||||||
.watch(
|
// .watch(
|
||||||
KeyController.provider(KeyController.spaceKey).notifier,
|
// KeyController.provider(KeyController.spaceKey).notifier,
|
||||||
)
|
// )
|
||||||
.set(
|
// .set(
|
||||||
space?.id ??
|
// space?.id ??
|
||||||
spaces
|
// spaces
|
||||||
.firstWhere(
|
// .firstWhere(
|
||||||
(space) =>
|
// (space) =>
|
||||||
space.children.firstWhereOrNull(
|
// space.children.firstWhereOrNull(
|
||||||
(child) => child.roomData.id == id,
|
// (child) => child.roomData.id == id,
|
||||||
) !=
|
// ) !=
|
||||||
null,
|
// null,
|
||||||
)
|
// )
|
||||||
.id,
|
// .id,
|
||||||
);
|
// );
|
||||||
|
|
||||||
if (space == null) {
|
// if (space == null) {
|
||||||
await ref
|
// await ref
|
||||||
.watch(
|
// .watch(
|
||||||
KeyController.provider(KeyController.roomKey).notifier,
|
// KeyController.provider(KeyController.roomKey).notifier,
|
||||||
)
|
// )
|
||||||
.set(id);
|
// .set(id);
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
snackbar.close();
|
// snackbar.close();
|
||||||
if (context.mounted) {
|
// if (context.mounted) {
|
||||||
scaffoldMessenger.showSnackBar(
|
// scaffoldMessenger.showSnackBar(
|
||||||
SnackBar(
|
// SnackBar(
|
||||||
backgroundColor: Theme.of(context).colorScheme.errorContainer,
|
// backgroundColor: Theme.of(context).colorScheme.errorContainer,
|
||||||
content: Text(
|
// content: Text(
|
||||||
error.toString(),
|
// error.toString(),
|
||||||
style: TextStyle(
|
// style: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.onErrorContainer,
|
// color: Theme.of(context).colorScheme.onErrorContainer,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:flutter/widgets.dart";
|
import "package:flutter/widgets.dart";
|
||||||
import "package:freezed_annotation/freezed_annotation.dart";
|
import "package:freezed_annotation/freezed_annotation.dart";
|
||||||
import "package:nexus/models/room.dart";
|
import "package:nexus/models/room.dart";
|
||||||
|
|
@ -10,6 +11,6 @@ abstract class Space with _$Space {
|
||||||
required String title,
|
required String title,
|
||||||
IconData? icon,
|
IconData? icon,
|
||||||
Room? room,
|
Room? room,
|
||||||
required List<Room> children,
|
required IList<Room> children,
|
||||||
}) = _Space;
|
}) = _Space;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
14
lib/models/space_edge.dart
Normal file
14
lib/models/space_edge.dart
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import "package:freezed_annotation/freezed_annotation.dart";
|
||||||
|
part "space_edge.freezed.dart";
|
||||||
|
part "space_edge.g.dart";
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class SpaceEdge with _$SpaceEdge {
|
||||||
|
const factory SpaceEdge({
|
||||||
|
required String childId,
|
||||||
|
@Default(false) bool suggested,
|
||||||
|
}) = _SpaceEdge;
|
||||||
|
|
||||||
|
factory SpaceEdge.fromJson(Map<String, Object?> json) =>
|
||||||
|
_$SpaceEdgeFromJson(json);
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:freezed_annotation/freezed_annotation.dart";
|
import "package:freezed_annotation/freezed_annotation.dart";
|
||||||
import "package:nexus/models/room.dart";
|
import "package:nexus/models/room.dart";
|
||||||
|
import "package:nexus/models/space_edge.dart";
|
||||||
part "sync_data.freezed.dart";
|
part "sync_data.freezed.dart";
|
||||||
part "sync_data.g.dart";
|
part "sync_data.g.dart";
|
||||||
|
|
||||||
|
|
@ -12,8 +13,8 @@ abstract class SyncData with _$SyncData {
|
||||||
@Default(IMap.empty()) IMap<String, Room> rooms,
|
@Default(IMap.empty()) IMap<String, Room> rooms,
|
||||||
@Default(ISet.empty()) ISet<String> leftRooms,
|
@Default(ISet.empty()) ISet<String> leftRooms,
|
||||||
// required IList<InvitedRoom> invitedRooms,
|
// required IList<InvitedRoom> invitedRooms,
|
||||||
// required IList<SpaceEdge> spaceEdges,
|
IMap<String, IList<SpaceEdge>>? spaceEdges,
|
||||||
@Default(IList.empty()) IList<String> topLevelSpaces,
|
IList<String>? topLevelSpaces,
|
||||||
}) = _SyncData;
|
}) = _SyncData;
|
||||||
|
|
||||||
factory SyncData.fromJson(Map<String, Object?> json) =>
|
factory SyncData.fromJson(Map<String, Object?> json) =>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
|
import "package:nexus/widgets/chat_page/sidebar.dart";
|
||||||
// import "package:nexus/widgets/chat_page/room_chat.dart";
|
// import "package:nexus/widgets/chat_page/room_chat.dart";
|
||||||
// import "package:nexus/widgets/chat_page/sidebar.dart";
|
|
||||||
|
|
||||||
class ChatPage extends StatelessWidget {
|
class ChatPage extends StatelessWidget {
|
||||||
const ChatPage({super.key});
|
const ChatPage({super.key});
|
||||||
|
|
@ -10,23 +10,23 @@ class ChatPage extends StatelessWidget {
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
final isDesktop = constraints.maxWidth > 650;
|
final isDesktop = constraints.maxWidth > 650;
|
||||||
final showMembersByDefault = constraints.maxWidth > 1000;
|
final showMembersByDefault = constraints.maxWidth > 1000;
|
||||||
return Placeholder();
|
|
||||||
// return Scaffold(
|
return Scaffold(
|
||||||
// body: Builder(
|
body: Builder(
|
||||||
// builder: (context) => Row(
|
builder: (context) => Row(
|
||||||
// children: [
|
children: [
|
||||||
// if (isDesktop) Sidebar(),
|
if (isDesktop) Sidebar(),
|
||||||
// Expanded(
|
// Expanded(
|
||||||
// child: RoomChat(
|
// child: RoomChat(
|
||||||
// isDesktop: isDesktop,
|
// isDesktop: isDesktop,
|
||||||
// showMembersByDefault: showMembersByDefault,
|
// showMembersByDefault: showMembersByDefault,
|
||||||
// ),
|
// ),
|
||||||
// ),
|
// ),
|
||||||
// ],
|
],
|
||||||
// ),
|
),
|
||||||
// ),
|
),
|
||||||
// drawer: isDesktop ? null : Sidebar(),
|
drawer: isDesktop ? null : Sidebar(),
|
||||||
// );
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import "dart:io";
|
import "dart:io";
|
||||||
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:window_manager/window_manager.dart";
|
import "package:window_manager/window_manager.dart";
|
||||||
|
|
||||||
|
|
@ -7,7 +8,7 @@ class Appbar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
final Widget? title;
|
final Widget? title;
|
||||||
final Color? backgroundColor;
|
final Color? backgroundColor;
|
||||||
final double? scrolledUnderElevation;
|
final double? scrolledUnderElevation;
|
||||||
final List<Widget> actions;
|
final IList<Widget> actions;
|
||||||
|
|
||||||
const Appbar({
|
const Appbar({
|
||||||
super.key,
|
super.key,
|
||||||
|
|
@ -15,7 +16,7 @@ class Appbar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
this.backgroundColor,
|
this.backgroundColor,
|
||||||
this.scrolledUnderElevation,
|
this.scrolledUnderElevation,
|
||||||
this.leading,
|
this.leading,
|
||||||
this.actions = const [],
|
this.actions = const IList.empty(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
||||||
|
|
@ -1,122 +1,124 @@
|
||||||
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter/services.dart";
|
import "package:flutter/services.dart";
|
||||||
import "package:flutter_hooks/flutter_hooks.dart";
|
import "package:flutter_hooks/flutter_hooks.dart";
|
||||||
import "package:matrix/matrix.dart";
|
import "package:nexus/models/room.dart";
|
||||||
import "package:nexus/helpers/extensions/room_to_children.dart";
|
|
||||||
import "package:nexus/widgets/form_text_input.dart";
|
import "package:nexus/widgets/form_text_input.dart";
|
||||||
|
|
||||||
class RoomMenu extends StatelessWidget {
|
class RoomMenu extends StatelessWidget {
|
||||||
final Room room;
|
final Room room;
|
||||||
const RoomMenu(this.room, {super.key});
|
final IList<Room> children;
|
||||||
|
const RoomMenu(this.room, {this.children = const IList.empty(), super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final danger = Theme.of(context).colorScheme.error;
|
final danger = Theme.of(context).colorScheme.error;
|
||||||
|
|
||||||
void markRead(String roomId) async {
|
void markRead(String roomId) async {
|
||||||
for (final child in await room.getAllChildren()) {
|
// TODO: Set parent read
|
||||||
await child.roomData.setReadMarker(
|
for (final child in children) {
|
||||||
child.roomData.lastEvent?.eventId,
|
// await child.setReadMarker( TODO: Set children read
|
||||||
mRead: child.roomData.lastEvent?.eventId,
|
// child.roomData.lastEvent?.eventId,
|
||||||
);
|
// mRead: child.roomData.lastEvent?.eventId,
|
||||||
|
// );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return PopupMenuButton(
|
return PopupMenuButton(
|
||||||
itemBuilder: (_) => [
|
itemBuilder: (_) => [
|
||||||
PopupMenuItem(
|
// PopupMenuItem(
|
||||||
onTap: () async {
|
// onTap: () async {
|
||||||
final link = await room.matrixToInviteLink();
|
// final link = await room.matrixToInviteLink();
|
||||||
await Clipboard.setData(ClipboardData(text: link.toString()));
|
// await Clipboard.setData(ClipboardData(text: link.toString()));
|
||||||
},
|
// },
|
||||||
child: ListTile(leading: Icon(Icons.link), title: Text("Copy Link")),
|
// child: ListTile(leading: Icon(Icons.link), title: Text("Copy Link")),
|
||||||
),
|
// ),
|
||||||
PopupMenuItem(
|
// PopupMenuItem(
|
||||||
onTap: () => markRead(room.id),
|
// onTap: () => markRead(room.id),
|
||||||
child: ListTile(
|
// child: ListTile(
|
||||||
leading: Icon(Icons.check),
|
// leading: Icon(Icons.check),
|
||||||
title: Text("Mark as Read"),
|
// title: Text("Mark as Read"),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
PopupMenuItem(
|
// PopupMenuItem(
|
||||||
onTap: () => showDialog(
|
// onTap: () => showDialog(
|
||||||
context: context,
|
// context: context,
|
||||||
builder: (context) => AlertDialog(
|
// builder: (context) => AlertDialog(
|
||||||
title: Text("Leave Room"),
|
// title: Text("Leave Room"),
|
||||||
content: Text(
|
// content: Text(
|
||||||
"Are you sure you want to leave \"${room.getLocalizedDisplayname()}\"?",
|
// "Are you sure you want to leave \"${room.getLocalizedDisplayname()}\"?",
|
||||||
),
|
// ),
|
||||||
actions: [
|
// actions: [
|
||||||
TextButton(
|
// TextButton(
|
||||||
onPressed: Navigator.of(context).pop,
|
// onPressed: Navigator.of(context).pop,
|
||||||
child: Text("Cancel"),
|
// child: Text("Cancel"),
|
||||||
),
|
// ),
|
||||||
TextButton(
|
// TextButton(
|
||||||
onPressed: () async {
|
// onPressed: () async {
|
||||||
Navigator.of(context).pop();
|
// Navigator.of(context).pop();
|
||||||
final snackbar = ScaffoldMessenger.of(
|
// final snackbar = ScaffoldMessenger.of(
|
||||||
context,
|
// context,
|
||||||
).showSnackBar(SnackBar(content: Text("Leaving room...")));
|
// ).showSnackBar(SnackBar(content: Text("Leaving room...")));
|
||||||
await room.leave();
|
// await room.leave();
|
||||||
snackbar.close();
|
// snackbar.close();
|
||||||
},
|
// },
|
||||||
child: Text("Leave"),
|
// child: Text("Leave"),
|
||||||
),
|
// ),
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
child: ListTile(
|
// child: ListTile(
|
||||||
leading: Icon(Icons.logout, color: danger),
|
// leading: Icon(Icons.logout, color: danger),
|
||||||
title: Text("Leave", style: TextStyle(color: danger)),
|
// title: Text("Leave", style: TextStyle(color: danger)),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
PopupMenuItem(
|
// PopupMenuItem(
|
||||||
onTap: () => showDialog(
|
// onTap: () => showDialog(
|
||||||
context: context,
|
// context: context,
|
||||||
builder: (context) => HookBuilder(
|
// builder: (context) => HookBuilder(
|
||||||
builder: (_) {
|
// builder: (_) {
|
||||||
final reasonController = useTextEditingController();
|
// final reasonController = useTextEditingController();
|
||||||
return AlertDialog(
|
// return AlertDialog(
|
||||||
title: Text("Report"),
|
// title: Text("Report"),
|
||||||
content: Column(
|
// content: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
// mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
// children: [
|
||||||
Text(
|
// Text(
|
||||||
"Report this room to your server administrators, who can take action like banning this room.",
|
// "Report this room to your server administrators, who can take action like banning this room.",
|
||||||
),
|
// ),
|
||||||
|
|
||||||
SizedBox(height: 12),
|
// SizedBox(height: 12),
|
||||||
FormTextInput(
|
// FormTextInput(
|
||||||
required: false,
|
// required: false,
|
||||||
capitalize: true,
|
// capitalize: true,
|
||||||
controller: reasonController,
|
// controller: reasonController,
|
||||||
title: "Reason for report (optional)",
|
// title: "Reason for report (optional)",
|
||||||
),
|
// ),
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
actions: [
|
// actions: [
|
||||||
TextButton(
|
// TextButton(
|
||||||
onPressed: Navigator.of(context).pop,
|
// onPressed: Navigator.of(context).pop,
|
||||||
child: Text("Cancel"),
|
// child: Text("Cancel"),
|
||||||
),
|
// ),
|
||||||
TextButton(
|
// TextButton(
|
||||||
onPressed: () {
|
// onPressed: () {
|
||||||
room.client.reportRoom(room.id, reasonController.text);
|
// room.client.reportRoom(room.id, reasonController.text);
|
||||||
Navigator.of(context).pop();
|
// Navigator.of(context).pop();
|
||||||
},
|
// },
|
||||||
child: Text("Report"),
|
// child: Text("Report"),
|
||||||
),
|
// ),
|
||||||
],
|
// ],
|
||||||
);
|
// );
|
||||||
},
|
// },
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
child: ListTile(
|
// child: ListTile(
|
||||||
leading: Icon(Icons.report, color: danger),
|
// leading: Icon(Icons.report, color: danger),
|
||||||
title: Text("Report", style: TextStyle(color: danger)),
|
// title: Text("Report", style: TextStyle(color: danger)),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import "package:collection/collection.dart";
|
|
||||||
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:hooks_riverpod/hooks_riverpod.dart";
|
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||||
|
|
@ -6,8 +5,6 @@ import "package:nexus/controllers/client_controller.dart";
|
||||||
import "package:nexus/controllers/key_controller.dart";
|
import "package:nexus/controllers/key_controller.dart";
|
||||||
import "package:nexus/controllers/selected_space_controller.dart";
|
import "package:nexus/controllers/selected_space_controller.dart";
|
||||||
import "package:nexus/controllers/spaces_controller.dart";
|
import "package:nexus/controllers/spaces_controller.dart";
|
||||||
import "package:nexus/helpers/extensions/better_when.dart";
|
|
||||||
import "package:nexus/helpers/extensions/get_headers.dart";
|
|
||||||
import "package:nexus/helpers/extensions/join_room_with_snackbars.dart";
|
import "package:nexus/helpers/extensions/join_room_with_snackbars.dart";
|
||||||
import "package:nexus/pages/settings_page.dart";
|
import "package:nexus/pages/settings_page.dart";
|
||||||
import "package:nexus/widgets/avatar_or_hash.dart";
|
import "package:nexus/widgets/avatar_or_hash.dart";
|
||||||
|
|
@ -22,58 +19,57 @@ class Sidebar extends HookConsumerWidget {
|
||||||
final selectedSpaceProvider = KeyController.provider(
|
final selectedSpaceProvider = KeyController.provider(
|
||||||
KeyController.spaceKey,
|
KeyController.spaceKey,
|
||||||
);
|
);
|
||||||
final selectedSpace = ref.watch(selectedSpaceProvider);
|
final selectedSpaceId = ref.watch(selectedSpaceProvider);
|
||||||
final selectedSpaceNotifier = ref.watch(selectedSpaceProvider.notifier);
|
final selectedSpaceIdNotifier = ref.watch(selectedSpaceProvider.notifier);
|
||||||
|
|
||||||
final selectedRoomController = KeyController.provider(
|
final selectedRoomController = KeyController.provider(
|
||||||
KeyController.roomKey,
|
KeyController.roomKey,
|
||||||
);
|
);
|
||||||
final selectedRoom = ref.watch(selectedRoomController);
|
final selectedRoomId = ref.watch(selectedRoomController);
|
||||||
final selectedRoomNotifier = ref.watch(selectedRoomController.notifier);
|
final selectedRoomIdNotifier = ref.watch(selectedRoomController.notifier);
|
||||||
|
|
||||||
|
final spaces = ref.watch(SpacesController.provider);
|
||||||
|
final indexOfSelected = spaces.indexWhere(
|
||||||
|
(space) => space.id == selectedSpaceId,
|
||||||
|
);
|
||||||
|
final selectedIndex = indexOfSelected == -1 ? 0 : indexOfSelected;
|
||||||
|
|
||||||
|
final selectedSpace = ref.watch(SelectedSpaceController.provider);
|
||||||
|
|
||||||
|
final indexOfSelectedRoom = selectedSpace.children.indexWhere(
|
||||||
|
(room) => room.metadata?.id == selectedRoomId,
|
||||||
|
);
|
||||||
|
final selectedRoomIndex = indexOfSelected == -1
|
||||||
|
? selectedSpace.children.isEmpty
|
||||||
|
? null
|
||||||
|
: 0
|
||||||
|
: indexOfSelectedRoom;
|
||||||
|
|
||||||
return Drawer(
|
return Drawer(
|
||||||
shape: Border(),
|
shape: Border(),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
ref
|
NavigationRail(
|
||||||
.watch(SpacesController.provider)
|
|
||||||
.when(
|
|
||||||
loading: SizedBox.shrink,
|
|
||||||
error: (error, stack) {
|
|
||||||
debugPrintStack(label: error.toString(), stackTrace: stack);
|
|
||||||
throw error;
|
|
||||||
},
|
|
||||||
data: (spaces) {
|
|
||||||
final indexOfSelected = spaces.indexWhere(
|
|
||||||
(space) => space.id == selectedSpace,
|
|
||||||
);
|
|
||||||
final selectedIndex = indexOfSelected == -1
|
|
||||||
? 0
|
|
||||||
: indexOfSelected;
|
|
||||||
|
|
||||||
return NavigationRail(
|
|
||||||
scrollable: true,
|
scrollable: true,
|
||||||
onDestinationSelected: (value) {
|
onDestinationSelected: (value) {
|
||||||
selectedSpaceNotifier.set(spaces[value].id);
|
selectedSpaceIdNotifier.set(spaces[value].id);
|
||||||
selectedRoomNotifier.set(
|
selectedRoomIdNotifier.set(
|
||||||
spaces[value].children.firstOrNull?.roomData.id,
|
spaces[value].children.firstOrNull?.metadata?.id,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
destinations: spaces
|
destinations: spaces
|
||||||
.map(
|
.map(
|
||||||
(space) => NavigationRailDestination(
|
(space) => NavigationRailDestination(
|
||||||
icon: AvatarOrHash(
|
icon: AvatarOrHash(
|
||||||
space.avatar,
|
null, // TODO: Url
|
||||||
fallback: space.icon == null
|
fallback: space.icon == null ? null : Icon(space.icon),
|
||||||
? null
|
|
||||||
: Icon(space.icon),
|
|
||||||
space.title,
|
space.title,
|
||||||
headers: space.client.headers,
|
headers: {}, // TODO
|
||||||
hasBadge:
|
hasBadge: false,
|
||||||
space.children.firstWhereOrNull(
|
// space.children.firstWhereOrNull( TODO
|
||||||
(room) => room.roomData.hasNewMessages,
|
// (room) => room.roomData.hasNewMessages,
|
||||||
) !=
|
// ) !=
|
||||||
null,
|
// null,
|
||||||
),
|
),
|
||||||
label: Text(space.title),
|
label: Text(space.title),
|
||||||
padding: EdgeInsets.only(top: 4),
|
padding: EdgeInsets.only(top: 4),
|
||||||
|
|
@ -94,14 +90,12 @@ class Sidebar extends HookConsumerWidget {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (alertContext) => HookBuilder(
|
builder: (alertContext) => HookBuilder(
|
||||||
builder: (_) {
|
builder: (_) {
|
||||||
final roomAlias =
|
final roomAlias = useTextEditingController();
|
||||||
useTextEditingController();
|
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text("Join a Room"),
|
title: Text("Join a Room"),
|
||||||
content: Column(
|
content: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment:
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Enter the room alias, ID, or a Matrix.to link.",
|
"Enter the room alias, ID, or a Matrix.to link.",
|
||||||
|
|
@ -117,19 +111,15 @@ class Sidebar extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: Navigator.of(
|
onPressed: Navigator.of(context).pop,
|
||||||
context,
|
|
||||||
).pop,
|
|
||||||
child: Text("Cancel"),
|
child: Text("Cancel"),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
Navigator.of(alertContext).pop();
|
Navigator.of(alertContext).pop();
|
||||||
|
|
||||||
final client = await ref.watch(
|
final client = ref.watch(
|
||||||
ClientController
|
ClientController.provider.notifier,
|
||||||
.provider
|
|
||||||
.future,
|
|
||||||
);
|
);
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
client.joinRoomWithSnackBars(
|
client.joinRoomWithSnackBars(
|
||||||
|
|
@ -147,9 +137,7 @@ class Sidebar extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
title: Text(
|
title: Text("Join an existing room (or space)"),
|
||||||
"Join an existing room (or space)",
|
|
||||||
),
|
|
||||||
leading: Icon(Icons.numbers),
|
leading: Icon(Icons.numbers),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -166,83 +154,72 @@ class Sidebar extends HookConsumerWidget {
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => showDialog(
|
onPressed: () => showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) =>
|
builder: (context) => AlertDialog(title: Text("To-do")),
|
||||||
AlertDialog(title: Text("To-do")),
|
|
||||||
),
|
),
|
||||||
icon: Icon(Icons.explore),
|
icon: Icon(Icons.explore),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => Navigator.of(context).push(
|
onPressed: () => Navigator.of(
|
||||||
MaterialPageRoute(builder: (_) => SettingsPage()),
|
context,
|
||||||
),
|
).push(MaterialPageRoute(builder: (_) => SettingsPage())),
|
||||||
icon: Icon(Icons.settings),
|
icon: Icon(Icons.settings),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ref
|
child: Scaffold(
|
||||||
.watch(SelectedSpaceController.provider)
|
|
||||||
.betterWhen(
|
|
||||||
data: (space) {
|
|
||||||
final indexOfSelected = space.children.indexWhere(
|
|
||||||
(room) => room.roomData.id == selectedRoom,
|
|
||||||
);
|
|
||||||
final selectedIndex = indexOfSelected == -1
|
|
||||||
? space.children.isEmpty
|
|
||||||
? null
|
|
||||||
: 0
|
|
||||||
: indexOfSelected;
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: AvatarOrHash(
|
leading: AvatarOrHash(
|
||||||
space.avatar,
|
null,
|
||||||
fallback: space.icon == null
|
// space.avatar, TODO
|
||||||
|
fallback: selectedSpace.icon == null
|
||||||
? null
|
? null
|
||||||
: Icon(space.icon),
|
: Icon(selectedSpace.icon),
|
||||||
space.title,
|
|
||||||
headers: space.client.headers,
|
selectedSpace.title, // TODO RM
|
||||||
|
headers: {},
|
||||||
|
// space.client.headers, TODO
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
space.title,
|
selectedSpace.room?.metadata?.avatar.toString() ??
|
||||||
|
selectedSpace.title,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
actions: [
|
actions: [
|
||||||
if (space.roomData != null) RoomMenu(space.roomData!),
|
if (selectedSpace.room != null) RoomMenu(selectedSpace.room!),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: NavigationRail(
|
body: NavigationRail(
|
||||||
scrollable: true,
|
scrollable: true,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
extended: true,
|
extended: true,
|
||||||
selectedIndex: selectedIndex,
|
selectedIndex: selectedRoomIndex,
|
||||||
destinations: space.children
|
destinations: selectedSpace.children
|
||||||
.map(
|
.map(
|
||||||
(room) => NavigationRailDestination(
|
(room) => NavigationRailDestination(
|
||||||
label: Text(room.title),
|
label: Text(room.metadata?.name ?? "Unnamed Room"),
|
||||||
icon: AvatarOrHash(
|
icon: AvatarOrHash(
|
||||||
hasBadge: room.roomData.hasNewMessages,
|
// hasBadge: room.roomData.hasNewMessages, TODO
|
||||||
room.avatar,
|
null,
|
||||||
room.title,
|
// room.avatar, TODO
|
||||||
fallback: selectedSpace == "dms"
|
room.metadata?.name ?? "Unnamed Room",
|
||||||
|
fallback: selectedSpaceId == "dms"
|
||||||
? null
|
? null
|
||||||
: Icon(Icons.numbers),
|
: Icon(Icons.numbers),
|
||||||
headers: space.client.headers,
|
headers: {},
|
||||||
|
// space.client.headers,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
onDestinationSelected: (value) => selectedRoomNotifier
|
onDestinationSelected: (value) => selectedRoomIdNotifier.set(
|
||||||
.set(space.children[value].roomData.id),
|
selectedSpace.children[value].metadata?.id,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue