forked from Henry-Hiles/nexus
Leave room support, persist last room, fixes
This commit is contained in:
parent
f7c6c3bb6a
commit
7dfd47a404
17 changed files with 312 additions and 136 deletions
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
|
@ -1,3 +1,3 @@
|
||||||
{
|
{
|
||||||
"cSpell.words": ["Appbar", "Displayname"]
|
"cSpell.words": ["Appbar", "Displayname", "prefs"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import "package:nexus/models/session_backup.dart";
|
||||||
|
|
||||||
class ClientController extends AsyncNotifier<Client> {
|
class ClientController extends AsyncNotifier<Client> {
|
||||||
static const sessionBackupKey = "sessionBackup";
|
static const sessionBackupKey = "sessionBackup";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Client> build() async {
|
Future<Client> build() async {
|
||||||
if (!voz.isInitialized()) await voz_fl.init();
|
if (!voz.isInitialized()) await voz_fl.init();
|
||||||
|
|
@ -43,10 +44,6 @@ class ClientController extends AsyncNotifier<Client> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ref.onDispose(
|
|
||||||
client.onRoomState.stream.listen((_) => ref.notifyListeners()).cancel,
|
|
||||||
);
|
|
||||||
|
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
|
||||||
import "package:nexus/controllers/spaces_controller.dart";
|
|
||||||
import "package:nexus/helpers/extensions/get_full_room.dart";
|
|
||||||
import "package:nexus/models/full_room.dart";
|
|
||||||
|
|
||||||
class CurrentRoomController extends AsyncNotifier<FullRoom?> {
|
|
||||||
@override
|
|
||||||
Future<FullRoom?> build() async {
|
|
||||||
final spaces = await ref.watch(SpacesController.provider.future);
|
|
||||||
|
|
||||||
if (spaces.isEmpty || spaces[0].children.isEmpty) return null;
|
|
||||||
return spaces[0].children[0].roomData.fullRoom;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> set(FullRoom room) async {
|
|
||||||
await future;
|
|
||||||
state = AsyncValue.data(room);
|
|
||||||
}
|
|
||||||
|
|
||||||
static final provider =
|
|
||||||
AsyncNotifierProvider<CurrentRoomController, FullRoom?>(
|
|
||||||
CurrentRoomController.new,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
30
lib/controllers/key_controller.dart
Normal file
30
lib/controllers/key_controller.dart
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
|
import "package:nexus/controllers/shared_prefs_controller.dart";
|
||||||
|
|
||||||
|
class KeyController extends Notifier<String?> {
|
||||||
|
final String key;
|
||||||
|
KeyController(this.key);
|
||||||
|
|
||||||
|
static const String spaceKey = "space";
|
||||||
|
static const String roomKey = "room";
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? build() =>
|
||||||
|
ref.watch(SharedPrefsController.provider).requireValue.getString(key);
|
||||||
|
|
||||||
|
Future<void> set(String? id) async {
|
||||||
|
final prefs = ref.watch(SharedPrefsController.provider).requireValue;
|
||||||
|
state = id;
|
||||||
|
|
||||||
|
if (id == null) {
|
||||||
|
prefs.remove(key);
|
||||||
|
} else {
|
||||||
|
prefs.setString(key, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final provider =
|
||||||
|
NotifierProvider.family<KeyController, String?, String>(
|
||||||
|
KeyController.new,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import "package:flutter_chat_core/flutter_chat_core.dart";
|
import "package:flutter_chat_core/flutter_chat_core.dart";
|
||||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
import "package:nexus/controllers/current_room_controller.dart";
|
import "package:nexus/controllers/selected_room_controller.dart";
|
||||||
import "package:nexus/helpers/extensions/event_to_message.dart";
|
import "package:nexus/helpers/extensions/event_to_message.dart";
|
||||||
|
|
||||||
class MessageController extends AsyncNotifier<TextMessage?> {
|
class MessageController extends AsyncNotifier<TextMessage?> {
|
||||||
|
|
@ -9,11 +9,11 @@ class MessageController extends AsyncNotifier<TextMessage?> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<TextMessage?> build() async {
|
Future<TextMessage?> build() async {
|
||||||
final room = await ref.watch(CurrentRoomController.provider.future);
|
final room = await ref.watch(SelectedRoomController.provider.future);
|
||||||
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 =
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,33 @@
|
||||||
|
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/selected_space_controller.dart";
|
||||||
|
import "package:nexus/models/full_room.dart";
|
||||||
|
|
||||||
class SelectedRoomController extends Notifier<int> {
|
class SelectedRoomController extends AsyncNotifier<FullRoom?> {
|
||||||
@override
|
@override
|
||||||
int build() => 0;
|
bool updateShouldNotify(
|
||||||
|
AsyncValue<FullRoom?> previous,
|
||||||
|
AsyncValue<FullRoom?> next,
|
||||||
|
) =>
|
||||||
|
previous.value?.avatar != next.value?.avatar ||
|
||||||
|
previous.value?.title != next.value?.title;
|
||||||
|
|
||||||
void set(int value) => state = value;
|
@override
|
||||||
|
Future<FullRoom?> build() async {
|
||||||
|
final space = await ref.watch(SelectedSpaceController.provider.future);
|
||||||
|
final selectedRoomId = ref.watch(
|
||||||
|
KeyController.provider(KeyController.roomKey),
|
||||||
|
);
|
||||||
|
|
||||||
static final provider = NotifierProvider<SelectedRoomController, int>(
|
return space.children.firstWhereOrNull(
|
||||||
SelectedRoomController.new,
|
(room) => room.roomData.id == selectedRoomId,
|
||||||
);
|
) ??
|
||||||
|
space.children.firstOrNull;
|
||||||
|
}
|
||||||
|
|
||||||
|
static final provider =
|
||||||
|
AsyncNotifierProvider<SelectedRoomController, FullRoom?>(
|
||||||
|
SelectedRoomController.new,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,22 @@
|
||||||
|
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/spaces_controller.dart";
|
||||||
|
import "package:nexus/models/space.dart";
|
||||||
|
|
||||||
class SelectedSpaceController extends Notifier<int> {
|
class SelectedSpaceController extends AsyncNotifier<Space> {
|
||||||
@override
|
@override
|
||||||
int build() => 0;
|
Future<Space> build() async {
|
||||||
|
final spaces = await ref.watch(SpacesController.provider.future);
|
||||||
|
final selectedSpaceId = ref.watch(
|
||||||
|
KeyController.provider(KeyController.spaceKey),
|
||||||
|
);
|
||||||
|
|
||||||
void set(int value) => state = value;
|
return spaces.firstWhereOrNull((space) => space.id == selectedSpaceId) ??
|
||||||
|
spaces.first;
|
||||||
|
}
|
||||||
|
|
||||||
static final provider = NotifierProvider<SelectedSpaceController, int>(
|
static final provider = AsyncNotifierProvider<SelectedSpaceController, Space>(
|
||||||
SelectedSpaceController.new,
|
SelectedSpaceController.new,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
lib/controllers/shared_prefs_controller.dart
Normal file
12
lib/controllers/shared_prefs_controller.dart
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
|
import "package:shared_preferences/shared_preferences.dart";
|
||||||
|
|
||||||
|
class SharedPrefsController extends AsyncNotifier<SharedPreferences> {
|
||||||
|
@override
|
||||||
|
Future<SharedPreferences> build() => SharedPreferences.getInstance();
|
||||||
|
|
||||||
|
static final provider =
|
||||||
|
AsyncNotifierProvider<SharedPrefsController, SharedPreferences>(
|
||||||
|
SharedPrefsController.new,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -11,6 +11,10 @@ class SpacesController extends AsyncNotifier<IList<Space>> {
|
||||||
Future<IList<Space>> build() async {
|
Future<IList<Space>> build() async {
|
||||||
final client = await ref.watch(ClientController.provider.future);
|
final client = await ref.watch(ClientController.provider.future);
|
||||||
|
|
||||||
|
ref.onDispose(
|
||||||
|
client.onSync.stream.listen((_) => ref.invalidateSelf()).cancel,
|
||||||
|
);
|
||||||
|
|
||||||
final topLevel = await Future.wait(
|
final topLevel = await Future.wait(
|
||||||
client.rooms
|
client.rooms
|
||||||
.where((room) => !room.isDirectChat)
|
.where((room) => !room.isDirectChat)
|
||||||
|
|
@ -33,12 +37,14 @@ class SpacesController extends AsyncNotifier<IList<Space>> {
|
||||||
Space(
|
Space(
|
||||||
client: client,
|
client: client,
|
||||||
title: "Home",
|
title: "Home",
|
||||||
|
id: "home",
|
||||||
children: topLevelRooms,
|
children: topLevelRooms,
|
||||||
icon: Icon(Icons.home),
|
icon: Icon(Icons.home),
|
||||||
),
|
),
|
||||||
Space(
|
Space(
|
||||||
client: client,
|
client: client,
|
||||||
title: "Direct Messages",
|
title: "Direct Messages",
|
||||||
|
id: "dms",
|
||||||
children: await Future.wait(
|
children: await Future.wait(
|
||||||
client.rooms
|
client.rooms
|
||||||
.where((room) => room.isDirectChat)
|
.where((room) => room.isDirectChat)
|
||||||
|
|
@ -52,6 +58,7 @@ class SpacesController extends AsyncNotifier<IList<Space>> {
|
||||||
client: client,
|
client: client,
|
||||||
title: space.title,
|
title: space.title,
|
||||||
avatar: space.avatar,
|
avatar: space.avatar,
|
||||||
|
id: space.roomData.id,
|
||||||
roomData: space.roomData,
|
roomData: space.roomData,
|
||||||
children: await Future.wait(
|
children: await Future.wait(
|
||||||
space.roomData.spaceChildren
|
space.roomData.spaceChildren
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import "package:flutter/foundation.dart";
|
||||||
import "package:flutter_chat_core/flutter_chat_core.dart";
|
import "package:flutter_chat_core/flutter_chat_core.dart";
|
||||||
import "package:matrix/matrix.dart";
|
import "package:matrix/matrix.dart";
|
||||||
|
|
||||||
|
|
@ -28,7 +29,7 @@ extension EventToMessage on Event {
|
||||||
? this.eventId
|
? this.eventId
|
||||||
: relationshipEventId ?? this.eventId;
|
: relationshipEventId ?? this.eventId;
|
||||||
|
|
||||||
if (redacted) return null;
|
if (redacted && !mustBeText) return null;
|
||||||
|
|
||||||
final asText =
|
final asText =
|
||||||
Message.text(
|
Message.text(
|
||||||
|
|
@ -88,13 +89,15 @@ extension EventToMessage on Event {
|
||||||
"${senderFromMemoryOrFallback.calcDisplayname()} joined the room.",
|
"${senderFromMemoryOrFallback.calcDisplayname()} joined the room.",
|
||||||
),
|
),
|
||||||
EventTypes.Redaction => null,
|
EventTypes.Redaction => null,
|
||||||
EventTypes.Reaction => null,
|
_ =>
|
||||||
_ => Message.unsupported(
|
kDebugMode
|
||||||
metadata: metadata,
|
? Message.unsupported(
|
||||||
id: eventId,
|
metadata: metadata,
|
||||||
authorId: senderId,
|
id: eventId,
|
||||||
replyToMessageId: replyId,
|
authorId: senderId,
|
||||||
),
|
replyToMessageId: replyId,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,35 @@
|
||||||
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/client_controller.dart";
|
||||||
|
import "package:nexus/controllers/shared_prefs_controller.dart";
|
||||||
import "package:nexus/helpers/extensions/better_when.dart";
|
import "package:nexus/helpers/extensions/better_when.dart";
|
||||||
import "package:nexus/helpers/extensions/scheme_to_theme.dart";
|
import "package:nexus/helpers/extensions/scheme_to_theme.dart";
|
||||||
import "package:nexus/pages/chat_page.dart";
|
import "package:nexus/pages/chat_page.dart";
|
||||||
import "package:nexus/pages/login_page.dart";
|
import "package:nexus/pages/login_page.dart";
|
||||||
|
import "package:nexus/widgets/error_dialog.dart";
|
||||||
import "package:window_manager/window_manager.dart";
|
import "package:window_manager/window_manager.dart";
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:dynamic_system_colors/dynamic_system_colors.dart";
|
import "package:dynamic_system_colors/dynamic_system_colors.dart";
|
||||||
import "package:window_size/window_size.dart";
|
import "package:window_size/window_size.dart";
|
||||||
|
|
||||||
|
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
|
void showError(Object error, [StackTrace? stackTrace]) {
|
||||||
|
if (error.toString().contains("DioException")) return;
|
||||||
|
if (error.toString().contains("UTF-16")) return;
|
||||||
|
|
||||||
|
debugPrintStack(stackTrace: stackTrace, label: error.toString());
|
||||||
|
if (navigatorKey.currentContext != null) {
|
||||||
|
Future.delayed(
|
||||||
|
Duration.zero,
|
||||||
|
() => showDialog(
|
||||||
|
context: navigatorKey.currentContext!,
|
||||||
|
builder: (_) => ErrorDialog(error, stackTrace),
|
||||||
|
barrierDismissible: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
|
@ -17,6 +38,9 @@ void main() async {
|
||||||
WindowOptions(titleBarStyle: TitleBarStyle.hidden),
|
WindowOptions(titleBarStyle: TitleBarStyle.hidden),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
FlutterError.onError = (FlutterErrorDetails details) =>
|
||||||
|
showError(details.exception.toString(), details.stack);
|
||||||
|
|
||||||
setWindowMinSize(const Size.square(500));
|
setWindowMinSize(const Size.square(500));
|
||||||
|
|
||||||
runApp(ProviderScope(child: const App()));
|
runApp(ProviderScope(child: const App()));
|
||||||
|
|
@ -28,6 +52,7 @@ class App extends ConsumerWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) => DynamicColorBuilder(
|
Widget build(BuildContext context, WidgetRef ref) => DynamicColorBuilder(
|
||||||
builder: (lightDynamic, darkDynamic) => MaterialApp(
|
builder: (lightDynamic, darkDynamic) => MaterialApp(
|
||||||
|
navigatorKey: navigatorKey,
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
// Use indigo to work around bugs in theme generation
|
// Use indigo to work around bugs in theme generation
|
||||||
theme: (lightDynamic ?? ColorScheme.fromSeed(seedColor: Colors.indigo))
|
theme: (lightDynamic ?? ColorScheme.fromSeed(seedColor: Colors.indigo))
|
||||||
|
|
@ -40,10 +65,14 @@ class App extends ConsumerWidget {
|
||||||
))
|
))
|
||||||
.theme,
|
.theme,
|
||||||
home: ref
|
home: ref
|
||||||
.watch(ClientController.provider)
|
.watch(SharedPrefsController.provider)
|
||||||
.betterWhen(
|
.betterWhen(
|
||||||
data: (client) =>
|
data: (_) => ref
|
||||||
client.accessToken == null ? LoginPage() : ChatPage(),
|
.watch(ClientController.provider)
|
||||||
|
.betterWhen(
|
||||||
|
data: (client) =>
|
||||||
|
client.accessToken == null ? LoginPage() : ChatPage(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ part "space.freezed.dart";
|
||||||
abstract class Space with _$Space {
|
abstract class Space with _$Space {
|
||||||
const factory Space({
|
const factory Space({
|
||||||
required String title,
|
required String title,
|
||||||
|
required String id,
|
||||||
required List<FullRoom> children,
|
required List<FullRoom> children,
|
||||||
required Client client,
|
required Client client,
|
||||||
Room? roomData,
|
Room? roomData,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import "package:flutter/foundation.dart";
|
|
||||||
import "package:flutter/material.dart";
|
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";
|
||||||
|
|
@ -9,7 +8,7 @@ import "package:flyer_chat_image_message/flyer_chat_image_message.dart";
|
||||||
import "package:flyer_chat_system_message/flyer_chat_system_message.dart";
|
import "package:flyer_chat_system_message/flyer_chat_system_message.dart";
|
||||||
import "package:flyer_chat_text_message/flyer_chat_text_message.dart";
|
import "package:flyer_chat_text_message/flyer_chat_text_message.dart";
|
||||||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||||
import "package:nexus/controllers/current_room_controller.dart";
|
import "package:nexus/controllers/selected_room_controller.dart";
|
||||||
import "package:nexus/controllers/room_chat_controller.dart";
|
import "package:nexus/controllers/room_chat_controller.dart";
|
||||||
import "package:nexus/helpers/extensions/better_when.dart";
|
import "package:nexus/helpers/extensions/better_when.dart";
|
||||||
import "package:nexus/helpers/extensions/get_headers.dart";
|
import "package:nexus/helpers/extensions/get_headers.dart";
|
||||||
|
|
@ -38,7 +37,7 @@ class RoomChat extends HookConsumerWidget {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
return ref
|
return ref
|
||||||
.watch(CurrentRoomController.provider)
|
.watch(SelectedRoomController.provider)
|
||||||
.betterWhen(
|
.betterWhen(
|
||||||
data: (room) {
|
data: (room) {
|
||||||
if (room == null) {
|
if (room == null) {
|
||||||
|
|
@ -291,13 +290,12 @@ class RoomChat extends HookConsumerWidget {
|
||||||
index, {
|
index, {
|
||||||
required bool isSentByMe,
|
required bool isSentByMe,
|
||||||
MessageGroupStatus? groupStatus,
|
MessageGroupStatus? groupStatus,
|
||||||
}) => kDebugMode
|
}) => Text(
|
||||||
? Text(
|
"${message.authorId} sent ${message.metadata?["eventType"]}",
|
||||||
"${message.authorId} sent ${message.metadata?["eventType"]}",
|
style: theme.textTheme.labelSmall?.copyWith(
|
||||||
style: theme.textTheme.labelSmall
|
color: Colors.grey,
|
||||||
?.copyWith(color: Colors.grey),
|
),
|
||||||
)
|
),
|
||||||
: SizedBox.shrink(),
|
|
||||||
),
|
),
|
||||||
onMessageSend: (message) {
|
onMessageSend: (message) {
|
||||||
notifier.send(
|
notifier.send(
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,32 @@ class RoomMenu extends StatelessWidget {
|
||||||
child: ListTile(leading: Icon(Icons.link), title: Text("Copy Link")),
|
child: ListTile(leading: Icon(Icons.link), title: Text("Copy Link")),
|
||||||
),
|
),
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
onTap: () =>
|
onTap: () => showDialog(
|
||||||
showDialog(context: context, builder: (context) => AlertDialog()),
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: Text("Leave Room"),
|
||||||
|
content: Text(
|
||||||
|
"Are you sure you want to leave \"${room.getLocalizedDisplayname()}\"?",
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: Navigator.of(context).pop,
|
||||||
|
child: Text("Cancel"),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
final snackbar = ScaffoldMessenger.of(
|
||||||
|
context,
|
||||||
|
).showSnackBar(SnackBar(content: Text("Leaving room...")));
|
||||||
|
await room.leave();
|
||||||
|
snackbar.close();
|
||||||
|
},
|
||||||
|
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)),
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
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:nexus/controllers/current_room_controller.dart";
|
import "package:nexus/controllers/key_controller.dart";
|
||||||
import "package:nexus/controllers/selected_room_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/better_when.dart";
|
||||||
|
|
@ -16,11 +15,15 @@ class Sidebar extends HookConsumerWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final selectedSpaceProvider = SelectedSpaceController.provider;
|
final selectedSpaceProvider = KeyController.provider(
|
||||||
|
KeyController.spaceKey,
|
||||||
|
);
|
||||||
final selectedSpace = ref.watch(selectedSpaceProvider);
|
final selectedSpace = ref.watch(selectedSpaceProvider);
|
||||||
final selectedSpaceNotifier = ref.watch(selectedSpaceProvider.notifier);
|
final selectedSpaceNotifier = ref.watch(selectedSpaceProvider.notifier);
|
||||||
|
|
||||||
final selectedRoomController = SelectedRoomController.provider;
|
final selectedRoomController = KeyController.provider(
|
||||||
|
KeyController.roomKey,
|
||||||
|
);
|
||||||
final selectedRoom = ref.watch(selectedRoomController);
|
final selectedRoom = ref.watch(selectedRoomController);
|
||||||
final selectedRoomNotifier = ref.watch(selectedRoomController.notifier);
|
final selectedRoomNotifier = ref.watch(selectedRoomController.notifier);
|
||||||
|
|
||||||
|
|
@ -36,73 +39,87 @@ class Sidebar extends HookConsumerWidget {
|
||||||
debugPrintStack(label: error.toString(), stackTrace: stack);
|
debugPrintStack(label: error.toString(), stackTrace: stack);
|
||||||
throw error;
|
throw error;
|
||||||
},
|
},
|
||||||
data: (spaces) => NavigationRail(
|
data: (spaces) {
|
||||||
scrollable: true,
|
final indexOfSelected = spaces.indexWhere(
|
||||||
onDestinationSelected: (value) {
|
(space) => space.id == selectedSpace,
|
||||||
selectedRoomNotifier.set(0);
|
);
|
||||||
selectedSpaceNotifier.set(value);
|
final selectedIndex = indexOfSelected == -1
|
||||||
ref
|
? null
|
||||||
.watch(CurrentRoomController.provider.notifier)
|
: indexOfSelected;
|
||||||
.set(spaces[value].children[0]);
|
|
||||||
},
|
return NavigationRail(
|
||||||
destinations: spaces
|
scrollable: true,
|
||||||
.map(
|
onDestinationSelected: (value) {
|
||||||
(space) => NavigationRailDestination(
|
selectedSpaceNotifier.set(spaces[value].roomData?.id);
|
||||||
icon: AvatarOrHash(
|
selectedRoomNotifier.set(
|
||||||
space.avatar,
|
spaces[value].children.firstOrNull?.roomData.id,
|
||||||
fallback: space.icon,
|
);
|
||||||
space.title,
|
},
|
||||||
headers: space.client.headers,
|
destinations: spaces
|
||||||
hasBadge:
|
.map(
|
||||||
space.children.firstWhereOrNull(
|
(space) => NavigationRailDestination(
|
||||||
(room) => room.roomData.hasNewMessages,
|
icon: AvatarOrHash(
|
||||||
) !=
|
space.avatar,
|
||||||
null,
|
fallback: space.icon,
|
||||||
|
space.title,
|
||||||
|
headers: space.client.headers,
|
||||||
|
hasBadge:
|
||||||
|
space.children.firstWhereOrNull(
|
||||||
|
(room) => room.roomData.hasNewMessages,
|
||||||
|
) !=
|
||||||
|
null,
|
||||||
|
),
|
||||||
|
label: Text(space.title),
|
||||||
|
padding: EdgeInsets.only(top: 4),
|
||||||
),
|
),
|
||||||
label: Text(space.title),
|
)
|
||||||
padding: EdgeInsets.only(top: 4),
|
.toList(),
|
||||||
),
|
selectedIndex: selectedIndex,
|
||||||
)
|
trailingAtBottom: true,
|
||||||
.toList(),
|
trailing: Padding(
|
||||||
selectedIndex: selectedSpace,
|
padding: EdgeInsets.symmetric(vertical: 16),
|
||||||
trailingAtBottom: true,
|
child: Column(
|
||||||
trailing: Padding(
|
spacing: 8,
|
||||||
padding: EdgeInsets.symmetric(vertical: 16),
|
children: [
|
||||||
child: Column(
|
IconButton(
|
||||||
spacing: 8,
|
onPressed: () => Navigator.of(context).push(
|
||||||
children: [
|
// TODO: join or create room/space
|
||||||
IconButton(
|
MaterialPageRoute(builder: (_) => SettingsPage()),
|
||||||
onPressed: () => Navigator.of(context).push(
|
),
|
||||||
// TODO: join or create room/space
|
icon: Icon(Icons.add),
|
||||||
MaterialPageRoute(builder: (_) => SettingsPage()),
|
|
||||||
),
|
),
|
||||||
icon: Icon(Icons.add),
|
IconButton(
|
||||||
),
|
onPressed: () => Navigator.of(context).push(
|
||||||
IconButton(
|
// TODO: explore public rooms/spaces
|
||||||
onPressed: () => Navigator.of(context).push(
|
MaterialPageRoute(builder: (_) => SettingsPage()),
|
||||||
// TODO: explore public rooms/spaces
|
),
|
||||||
MaterialPageRoute(builder: (_) => SettingsPage()),
|
icon: Icon(Icons.explore),
|
||||||
),
|
),
|
||||||
icon: Icon(Icons.explore),
|
IconButton(
|
||||||
),
|
onPressed: () => Navigator.of(context).push(
|
||||||
IconButton(
|
// TODO: explore public rooms/spaces
|
||||||
onPressed: () => Navigator.of(context).push(
|
MaterialPageRoute(builder: (_) => SettingsPage()),
|
||||||
// TODO: explore public rooms/spaces
|
),
|
||||||
MaterialPageRoute(builder: (_) => SettingsPage()),
|
icon: Icon(Icons.settings),
|
||||||
),
|
),
|
||||||
icon: Icon(Icons.settings),
|
],
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ref
|
child: ref
|
||||||
.watch(SpacesController.provider)
|
.watch(SelectedSpaceController.provider)
|
||||||
.betterWhen(
|
.betterWhen(
|
||||||
data: (spaces) {
|
data: (space) {
|
||||||
final space = spaces[selectedSpace];
|
final indexOfSelected = space.children.indexWhere(
|
||||||
|
(room) => room.roomData.id == selectedRoom,
|
||||||
|
);
|
||||||
|
final selectedIndex = indexOfSelected == -1
|
||||||
|
? null
|
||||||
|
: indexOfSelected;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
|
@ -125,9 +142,7 @@ class Sidebar extends HookConsumerWidget {
|
||||||
scrollable: true,
|
scrollable: true,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
extended: true,
|
extended: true,
|
||||||
selectedIndex: space.children.isEmpty
|
selectedIndex: selectedIndex,
|
||||||
? null
|
|
||||||
: selectedRoom,
|
|
||||||
destinations: space.children
|
destinations: space.children
|
||||||
.map(
|
.map(
|
||||||
(room) => NavigationRailDestination(
|
(room) => NavigationRailDestination(
|
||||||
|
|
@ -144,12 +159,8 @@ class Sidebar extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
onDestinationSelected: (value) {
|
onDestinationSelected: (value) => selectedRoomNotifier
|
||||||
selectedRoomNotifier.set(value);
|
.set(space.children[value].roomData.id),
|
||||||
ref
|
|
||||||
.watch(CurrentRoomController.provider.notifier)
|
|
||||||
.set(space.children[value]);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
56
pubspec.lock
56
pubspec.lock
|
|
@ -1200,6 +1200,62 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.2"
|
version: "2.4.2"
|
||||||
|
shared_preferences:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: shared_preferences
|
||||||
|
sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.5.3"
|
||||||
|
shared_preferences_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_android
|
||||||
|
sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.18"
|
||||||
|
shared_preferences_foundation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_foundation
|
||||||
|
sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.5.6"
|
||||||
|
shared_preferences_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_linux
|
||||||
|
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.1"
|
||||||
|
shared_preferences_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_platform_interface
|
||||||
|
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.1"
|
||||||
|
shared_preferences_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_web
|
||||||
|
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.3"
|
||||||
|
shared_preferences_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_windows
|
||||||
|
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.1"
|
||||||
shelf:
|
shelf:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@ dependencies:
|
||||||
json_annotation: ^4.9.0
|
json_annotation: ^4.9.0
|
||||||
vodozemac: ^0.4.0
|
vodozemac: ^0.4.0
|
||||||
clipboard: ^2.0.2
|
clipboard: ^2.0.2
|
||||||
|
shared_preferences: ^2.5.3
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
build_runner: ^2.4.11
|
build_runner: ^2.4.11
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue