Compare commits

..

No commits in common. "3afb4befa5603ea3a7442d8130bbc037d614ffca" and "27dca24889108c2820f78836307436d162ff206a" have entirely different histories.

6 changed files with 69 additions and 207 deletions

View file

@ -1,64 +0,0 @@
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/members_by_status_controller.dart";
import "package:nexus/controllers/rooms_controller.dart";
import "package:nexus/models/configs/members_by_status_config.dart";
import "package:nexus/models/content/content.dart";
import "package:nexus/models/content/create.dart";
import "package:nexus/models/content/power_levels.dart";
import "package:nexus/models/event.dart";
class MembersGroupedController extends AsyncNotifier<IMap<int?, ISet<Event>>> {
final MembersByStatusConfig config;
MembersGroupedController(this.config);
@override
Future<IMap<int?, ISet<Event>>> build() async {
final room = ref.watch(
RoomsController.provider.select((value) => value[config.roomId]),
);
final createRowId = room?.state[EventType.create.type]?[""];
final createEvent = createRowId == null ? null : room?.events[createRowId];
final createEventContent = switch (createEvent?.content) {
CreateContent content => content,
_ => null,
};
final creators = createEventContent?.additionalCreatorIds.add(
createEvent!.sender,
);
final powerLevelsRowId = room?.state[EventType.powerLevels.type]?[""];
final powerLevelsEvent = powerLevelsRowId == null
? null
: room?.events[powerLevelsRowId];
final content = switch (powerLevelsEvent?.content) {
PowerLevelsContent content => content,
_ => PowerLevelsContent(),
};
final members = await ref.watch(
MembersByStatusController.provider(config).future,
);
return members.fold<IMap<int?, ISet<Event>>>(.new(), (result, event) {
final groupKey = creators?.contains(event.stateKey!) == true
? null
: content.users[event.stateKey!] ?? content.usersDefault;
return result.update(
groupKey,
(value) => value.add(event),
ifAbsent: () => .new({event}),
);
});
}
static final provider =
AsyncNotifierProvider.family<
MembersGroupedController,
IMap<int?, ISet<Event>>,
MembersByStatusConfig
>(MembersGroupedController.new);
}

View file

@ -24,8 +24,9 @@ class RoomChatController extends AsyncNotifier<IList<Event>> {
final room = ref.watch( final room = ref.watch(
RoomsController.provider.select((rooms) => rooms[roomId]), RoomsController.provider.select((rooms) => rooms[roomId]),
); );
if (room == null) return .new();
if (!room!.hasFetchedState) { if (!room.hasFetchedState) {
final state = await client.getRoomState(.new(roomId: roomId)); final state = await client.getRoomState(.new(roomId: roomId));
await ref.read(RoomsController.provider.notifier).addState(roomId, state); await ref.read(RoomsController.provider.notifier).addState(roomId, state);

View file

@ -8,6 +8,8 @@ part "create.g.dart";
abstract class CreateContent extends Content with _$CreateContent { abstract class CreateContent extends Content with _$CreateContent {
CreateContent._(); CreateContent._();
factory CreateContent({ factory CreateContent({
@JsonKey(name: "creator") String? creatorId,
@JsonKey(name: "additional_creators") @JsonKey(name: "additional_creators")
@Default(IList.empty()) @Default(IList.empty())
IList<String> additionalCreatorIds, IList<String> additionalCreatorIds,

View file

@ -1,16 +1,13 @@
import "package:fast_immutable_collections/fast_immutable_collections.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";
import "package:m3e_buttons/m3e_buttons.dart"; import "package:nexus/controllers/members_by_status_controller.dart";
import "package:material_segmented_list/material_segmented_list.dart";
import "package:nexus/controllers/members_grouped_controller.dart";
import "package:nexus/helpers/extensions/get_localpart.dart"; import "package:nexus/helpers/extensions/get_localpart.dart";
import "package:nexus/helpers/extensions/show_user_popover.dart";
import "package:nexus/helpers/extensions/string_to_color.dart"; import "package:nexus/helpers/extensions/string_to_color.dart";
import "package:nexus/models/content/membership.dart"; import "package:nexus/models/content/membership.dart";
import "package:nexus/models/membership_status.dart"; import "package:nexus/models/membership_status.dart";
import "package:nexus/widgets/avatar_or_hash.dart"; import "package:nexus/widgets/avatar_or_hash.dart";
import "package:nexus/widgets/divider_text.dart";
import "package:nexus/widgets/error_dialog.dart"; import "package:nexus/widgets/error_dialog.dart";
import "package:nexus/widgets/loading.dart"; import "package:nexus/widgets/loading.dart";
@ -21,19 +18,12 @@ class MemberList extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final status = useState(MembershipStatus.join); final status = useState(MembershipStatus.join);
final membersProvider = ref.watch(
final membersData = ref.watch( MembersByStatusController.provider(
MembersGroupedController.provider(
.new(roomId: roomId, status: status.value), .new(roomId: roomId, status: status.value),
), ),
); );
final options = {
"Joined": MembershipStatus.join,
"Invited": MembershipStatus.invite,
"Banned": MembershipStatus.ban,
};
return Drawer( return Drawer(
shape: Border(), shape: Border(),
child: Column( child: Column(
@ -52,107 +42,74 @@ class MemberList extends HookConsumerWidget {
), ),
], ],
), ),
M3EToggleButtonGroup( Wrap(
type: M3EButtonGroupType.connected, alignment: .center,
selectedIndex: options.values.toIList().indexOf(status.value), spacing: 8,
onSelectedIndexChanged: (index) => runSpacing: 8,
status.value = options.values.elementAt(index ?? 0), children: [
// overflow: M3EButtonGroupOverflow.menu, FilterChip(
actions: options label: Text("Joined"),
.mapTo( onSelected: (value) => status.value = .join,
(name, value) => M3EToggleButtonGroupAction( selected: status.value == .join,
checkedLabel: Text( ),
"$name${switch (membersData) { FilterChip(
AsyncData(:final value) || AsyncLoading(:final value?) => " (${value.values.expand((element) => element).length})", label: Text("Invited"),
_ => "", onSelected: (value) => status.value = .invite,
}}", selected: status.value == .invite,
), ),
label: Text(name), FilterChip(
), label: Text("Banned"),
) onSelected: (value) => status.value = .ban,
.toList(), selected: status.value == .ban,
),
],
), ),
switch (membersProvider) {
switch (membersData) {
AsyncError(:final error, :final stackTrace) => ErrorDialog( AsyncError(:final error, :final stackTrace) => ErrorDialog(
error, error,
stackTrace, stackTrace,
), ),
AsyncData(:final value) || AsyncLoading(:final value?) => AsyncData(:final value) || AsyncLoading(:final value?) => Expanded(
value.isEmpty child: ListView(
? Center( children: value
child: Padding( .map(
padding: .symmetric(vertical: 18), (member) => switch (member.content) {
child: Text( MembershipContent(
"No ${options.keys.toIList()[options.values.toIList().indexOf(status.value)]} Members", :final avatarUrl,
style: Theme.of(context).textTheme.headlineSmall, :final displayName,
), ) =>
), InkWell(
) onTapUp: (details) => context.showUserPopover(
: Expanded( member.content as MembershipContent,
child: ListView( member.stateKey!,
padding: .all(12), roomId: roomId,
children: [ globalPosition: details.globalPosition,
for (final MapEntry(key: powerLevel, value: members) ),
in value.toEntryIList( child: ListTile(
compare: (a, b) => (b?.key ?? double.infinity) leading: AvatarOrHash(
.compareTo(a?.key ?? double.infinity), avatarUrl,
)) ...[ displayName ?? member.sender.localpart,
Padding( ),
padding: EdgeInsets.symmetric(horizontal: 4), title: Text(
child: DividerText( displayName ?? member.stateKey!.localpart,
powerLevel == null overflow: .ellipsis,
? "Creators" style: .new(
: "Power Level $powerLevel", color: member.stateKey!.colorHash,
fontWeight: .bold,
),
),
subtitle: Text(
member.stateKey!,
overflow: .ellipsis,
), ),
), ),
SegmentedListSection( ),
children: members _ => SizedBox.shrink(),
.map( },
(member) => switch (member.content) { )
MembershipContent( .toList(),
:final avatarUrl, ),
:final displayName, ),
) =>
SegmentedListTile(
onTap: () {},
// context.showUserPopover(
// member.content as MembershipContent,
// member.stateKey!,
// roomId: roomId,
// globalPosition: details.globalPosition,
// ),
title: Text(
displayName ??
member.stateKey!.localpart,
overflow: .ellipsis,
style: .new(
color: member.stateKey!.colorHash,
fontWeight: .bold,
),
),
subtitle: Text(
member.stateKey!,
overflow: .ellipsis,
),
leading: AvatarOrHash(
avatarUrl,
displayName ??
member.sender.localpart,
),
),
_ => throw Exception(
"Member content was not MembershipContent",
),
},
)
.toList(),
),
SizedBox(height: 4),
],
],
),
),
AsyncLoading _ => Loading(), AsyncLoading _ => Loading(),
}, },
], ],

View file

@ -299,14 +299,6 @@ packages:
url: "https://github.com/Henry-Hiles/emoji_text_field" url: "https://github.com/Henry-Hiles/emoji_text_field"
source: git source: git
version: "1.0.0" version: "1.0.0"
equatable:
dependency: transitive
description:
name: equatable
sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b"
url: "https://pub.dev"
source: hosted
version: "2.0.8"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -752,14 +744,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.3.0"
m3e_buttons:
dependency: "direct main"
description:
name: m3e_buttons
sha256: "50cdf9ba30fb3ab529afafb0e837484549f8599f1f109ac07da50951febaace1"
url: "https://pub.dev"
source: hosted
version: "0.0.3"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@ -776,14 +760,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.13.0" version: "0.13.0"
material_segmented_list:
dependency: "direct main"
description:
name: material_segmented_list
sha256: "384bfd41a78e745397ceff1dd39700961e6a5419ad911d1797bcc13ea3824241"
url: "https://pub.dev"
source: hosted
version: "1.0.5"
measure_size: measure_size:
dependency: "direct main" dependency: "direct main"
description: description:
@ -872,14 +848,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.0" version: "2.0.0"
motor:
dependency: transitive
description:
name: motor
sha256: cbd49f21b00e568c2b1a55f134ed803614a107782f4fea7769693bca32940c58
url: "https://pub.dev"
source: hosted
version: "1.1.0"
native_toolchain_c: native_toolchain_c:
dependency: transitive dependency: transitive
description: description:

View file

@ -64,8 +64,6 @@ dependencies:
media_kit_video: 2.0.1 media_kit_video: 2.0.1
media_kit_libs_video: 1.0.7 media_kit_libs_video: 1.0.7
measure_size: ^5.0.2 measure_size: ^5.0.2
material_segmented_list: ^1.0.5
m3e_buttons: ^0.0.3
dev_dependencies: dev_dependencies:
build_runner: 2.15.0 build_runner: 2.15.0