fix power level logic

This commit is contained in:
Henry Hiles 2026-05-18 12:57:30 -04:00
commit 22f9e61c7c
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs
9 changed files with 164 additions and 158 deletions

View file

@ -24,10 +24,9 @@ class PowerLevelController extends Notifier<bool> {
final event = room?.events.firstWhereOrNull(
(event) => event.rowId == room.state[EventType.powerLevels.type]?[""],
);
final content = event?.content ?? PowerLevelsContent();
final user = ref.watch(ClientStateController.provider)?.userId;
if (user == null || event?.content is! PowerLevelsContent) return false;
final content = event?.content as PowerLevelsContent;
if (user == null || content is! PowerLevelsContent) return false;
int powerLevelOf(String userId) =>
content.users[userId] ?? content.usersDefault;
@ -36,7 +35,8 @@ class PowerLevelController extends Notifier<bool> {
return switch (config) {
EventPowerLevelConfig(:final eventType) =>
userLevel > (content.events[eventType.type] ?? content.eventsDefault),
userLevel >= (content.events[eventType.type] ?? content.eventsDefault),
MembershipActionPowerLevelConfig(:final action, :final targetUser) =>
switch (action) {
MembershipAction.invite => userLevel >= content.invite,
@ -51,7 +51,8 @@ class PowerLevelController extends Notifier<bool> {
},
StatePowerLevelConfig(:final eventType) =>
userLevel > (content.events[eventType.type] ?? content.stateDefault),
userLevel >= (content.events[eventType.type] ?? content.stateDefault),
RedactionPowerLevelConfig(:final targetUser) =>
userLevel >=
(targetUser == user

View file

@ -7,6 +7,7 @@ part "membership.g.dart";
@freezed
abstract class MembershipContent extends Content with _$MembershipContent {
MembershipContent._();
factory MembershipContent({
@JsonKey(name: "displayname") required String? displayName,
@JsonKey(name: "membership") required MembershipStatus status,

View file

@ -1,7 +1,6 @@
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:freezed_annotation/freezed_annotation.dart";
import "package:nexus/models/content/content.dart";
part "power_levels.freezed.dart";
part "power_levels.g.dart";

View file

@ -54,7 +54,13 @@ class RelationPreview extends ConsumerWidget {
),
),
Expanded(
child: EventText(relatedEvent!, textOnly: true, maxLines: 1),
child: IgnorePointer(
child: EventText(
relatedEvent!,
textOnly: true,
maxLines: 1,
),
),
),
],
),

View file

@ -1,11 +1,16 @@
import "dart:convert";
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter/material.dart";
import "package:nexus/models/content/avatar.dart";
import "package:nexus/models/content/message.dart";
import "package:nexus/models/event.dart";
import "package:nexus/widgets/chat_page/lazy_loading/message_avatar.dart";
import "package:nexus/widgets/chat_page/lazy_loading/message_displayname.dart";
import "package:timeago/timeago.dart";
class EventText extends StatelessWidget {
final Event event;
final bool textOnly;
final bool isGrouped;
final int? maxLines;
final VoidCallback? onTapReply;
final IList<PopupMenuEntry> Function(Event event)? getEventOptions;
@ -13,6 +18,7 @@ class EventText extends StatelessWidget {
this.event, {
this.onTapReply,
this.textOnly = false,
this.isGrouped = false,
this.maxLines,
this.getEventOptions,
super.key,
@ -20,6 +26,81 @@ class EventText extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text(json.encode(event.toJson())); // NEXT TODO
final theme = Theme.of(context);
final timestamp = Tooltip(
message: event.timestamp.toString(),
child: Text(
format(event.timestamp),
style: theme.textTheme.labelSmall?.copyWith(color: Colors.grey),
),
);
return switch (event.content) {
MessageContent() => Row(
spacing: 8,
children: [
isGrouped ? SizedBox(width: 40) : MessageAvatar(event, height: 40),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!isGrouped)
Row(
spacing: 4,
children: [
MessageDisplayname(
event,
style: TextStyle(fontWeight: FontWeight.bold),
),
Expanded(child: timestamp),
],
),
Text("data"),
],
),
),
],
),
AvatarContent() => Row(
spacing: 4,
children: [
SizedBox(width: 4),
Icon(Icons.numbers),
MessageDisplayname(
event,
style: TextStyle(
color: theme.colorScheme.primary,
fontWeight: FontWeight.bold,
),
),
Text("changed the room avatar"),
],
),
_ => Text("AAAAA"),
};
}
}
// ExpandableImage(
// url,
// child: ClipRRect(
// borderRadius: BorderRadius.all(Radius.circular(8)),
// child: Image(
// image: CachedNetworkImage(
// url,
// ref.watch(CrossCacheController.provider),
// headers: ref.headers,
// ),
// width: width,
// height: height,
// loadingBuilder: (context, child, loadingProgress) =>
// blurHash == null ? Loading() : BlurHash(hash: blurHash!),
// errorBuilder: (context, error, stackTrace) => Center(
// child: Text(
// "Image Failed to Load",
// style: TextStyle(color: Theme.of(context).colorScheme.error),
// ),
// ),
// ),
// ),
// )

View file

@ -1,48 +0,0 @@
import "package:cross_cache/cross_cache.dart";
import "package:flutter/material.dart";
import "package:flutter_blurhash/flutter_blurhash.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/cross_cache_controller.dart";
import "package:nexus/helpers/extensions/get_headers.dart";
import "package:nexus/widgets/chat_page/expandable_image.dart";
import "package:nexus/widgets/loading.dart";
class ExpandableImageMessage extends ConsumerWidget {
final String url;
final double? width;
final double? height;
final String? blurHash;
const ExpandableImageMessage(
this.url, {
this.width,
this.height,
this.blurHash,
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) => ExpandableImage(
url,
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(8)),
child: Image(
image: CachedNetworkImage(
url,
ref.watch(CrossCacheController.provider),
headers: ref.headers,
),
width: width,
height: height,
loadingBuilder: (context, child, loadingProgress) =>
blurHash == null ? Loading() : BlurHash(hash: blurHash!),
errorBuilder: (context, error, stackTrace) => Center(
child: Text(
"Image Failed to Load",
style: TextStyle(color: Theme.of(context).colorScheme.error),
),
),
),
),
);
}

View file

@ -21,7 +21,7 @@ import "package:nexus/widgets/chat_page/emoji_picker_button.dart";
import "package:nexus/widgets/chat_page/event_text.dart";
import "package:nexus/widgets/chat_page/member_list.dart";
import "package:nexus/widgets/chat_page/room_appbar.dart";
import "package:nexus/widgets/chat_page/wrappers/message_wrapper.dart";
import "package:nexus/widgets/chat_page/wrappers/event_wrapper.dart";
import "package:nexus/widgets/error_dialog.dart";
import "package:nexus/widgets/form_text_input.dart";
import "package:nexus/main.dart";
@ -320,7 +320,9 @@ class RoomChat extends HookConsumerWidget {
SuperSliverList.builder(
listController: listController.value,
itemCount: value.length,
itemBuilder: (_, index) => MessageWrapper(
itemBuilder: (_, index) => Padding(
padding: EdgeInsets.symmetric(vertical: 8),
child: EventWrapper(
value[index],
EventText(
value[index],
@ -334,13 +336,14 @@ class RoomChat extends HookConsumerWidget {
curve: (_) => Curves.easeInOut,
),
getEventOptions: getEventOptions,
),
// TODO: Reimplement grouping
isGrouped: false,
),
// TODO: Reimplement flashing
isFlashing: false,
),
),
),
],
),
AsyncLoading() => Loading(),

View file

@ -0,0 +1,46 @@
import "package:flutter/material.dart";
import "package:nexus/models/event.dart";
import "package:nexus/widgets/chat_page/wrappers/reaction_row.dart";
class EventWrapper extends StatelessWidget {
final Event event;
final Widget child;
final bool isFlashing;
const EventWrapper(
this.event,
this.child, {
this.isFlashing = false,
super.key,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(12)),
child: AnimatedContainer(
padding: isFlashing ? EdgeInsets.all(8) : EdgeInsets.all(0),
color: isFlashing
? Theme.of(context).colorScheme.onSurface.withAlpha(50)
: Colors.transparent,
duration: Duration(milliseconds: 250),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 4,
children: [
child,
if (event.sendError != null && event.sendError != "not sent")
Text(
event.sendError!,
style: theme.textTheme.labelSmall?.copyWith(
color: theme.colorScheme.error,
),
),
ReactionRow(event),
],
),
),
);
}
}

View file

@ -1,83 +0,0 @@
import "package:flutter/material.dart";
import "package:nexus/models/event.dart";
import "package:nexus/widgets/chat_page/lazy_loading/message_avatar.dart";
import "package:nexus/widgets/chat_page/lazy_loading/message_displayname.dart";
import "package:nexus/widgets/chat_page/wrappers/reaction_row.dart";
import "package:timeago/timeago.dart";
class MessageWrapper extends StatelessWidget {
final Event event;
final Widget child;
final bool isGrouped;
final bool isFlashing;
const MessageWrapper(
this.event,
this.child, {
this.isGrouped = false,
this.isFlashing = false,
super.key,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(12)),
child: AnimatedContainer(
padding: isFlashing ? EdgeInsets.all(8) : EdgeInsets.all(0),
color: isFlashing
? Theme.of(context).colorScheme.onSurface.withAlpha(50)
: Colors.transparent,
duration: Duration(milliseconds: 250),
child: Row(
spacing: 8,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
isGrouped ? SizedBox(width: 40) : MessageAvatar(event, height: 40),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 4,
children: [
if (!isGrouped)
Row(
spacing: 4,
children: [
Flexible(
child: MessageDisplayname(
event,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
),
Tooltip(
message: event.timestamp.toString(),
child: Text(
format(event.timestamp),
style: theme.textTheme.labelSmall?.copyWith(
color: Colors.grey,
),
),
),
],
),
child,
if (event.sendError != null && event.sendError != "not sent")
Text(
event.sendError!,
style: theme.textTheme.labelSmall?.copyWith(
color: theme.colorScheme.error,
),
),
ReactionRow(event),
],
),
),
],
),
),
);
}
}