tweak color picking algorithm for power levels, and introduce setting for per PL or per user colors

This commit is contained in:
Henry Hiles 2026-10-23 10:23:49 -04:00
commit e5dd53ed6c
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs
4 changed files with 47 additions and 6 deletions

View file

@ -19,6 +19,7 @@ import "package:nexus/controllers/unified_push_distributors.dart";
import "package:nexus/helpers/extensions/distributor_name.dart"; import "package:nexus/helpers/extensions/distributor_name.dart";
import "package:nexus/models/account_data.dart"; import "package:nexus/models/account_data.dart";
import "package:nexus/models/client_state.dart"; import "package:nexus/models/client_state.dart";
import "package:nexus/models/name_color.dart";
import "package:nexus/models/room_order.dart"; import "package:nexus/models/room_order.dart";
import "package:nexus/models/settings.dart"; import "package:nexus/models/settings.dart";
import "package:nexus/models/settings_category.dart"; import "package:nexus/models/settings_category.dart";
@ -83,6 +84,26 @@ class SettingsSectionsController
.onError(showError), .onError(showError),
), ),
), ),
.new(
title: "Name Colors",
description: "Color names by power level, so moderators and admins stand out, or by user, so everyone has their own.",
icon: Icons.format_color_text,
builder: (title, description, icon) => DialogListTile<NameColor>(
icon: Icon(icon),
title: title,
subtitle: Text(description),
value: settings.nameColor,
options: NameColor.values,
getName: (option) => switch (option) {
.powerLevel => "By power level",
.user => "By user",
},
onChanged: (value) => ref
.read(SettingsController.provider.notifier)
.set(settings.copyWith(nameColor: value))
.onError(showError),
),
),
.new( .new(
title: "Use Dynamic Theme", title: "Use Dynamic Theme",
icon: Icons.palette, icon: Icons.palette,

View file

@ -1,13 +1,29 @@
import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:material_ui/material_ui.dart"; import "package:material_ui/material_ui.dart";
import "package:nexus/controllers/room_power_levels.dart"; import "package:nexus/controllers/room_power_levels.dart";
import "package:nexus/controllers/settings.dart";
import "package:nexus/helpers/extensions/string_to_color.dart"; import "package:nexus/helpers/extensions/string_to_color.dart";
extension DisplaynameColor on WidgetRef { extension DisplaynameColor on WidgetRef {
/// The color of [userId]'s name in [roomId], hashed from their power level, /// The color of [userId]'s name in [roomId], from their power level
/// so everyone with the same level shares one. /// (so everyone with the same level shares one) or their user ID, depending
Color displaynameColor(String roomId, String userId) => watch( /// on the user's settings.
Color displaynameColor(String roomId, String userId) => switch (watch(
SettingsController.provider.select((settings) => settings.value?.nameColor),
)) {
.user => userId.colorHash,
.powerLevel || null => powerLevelColor(
watch(
RoomPowerLevelsController.provider(roomId) RoomPowerLevelsController.provider(roomId)
.select((levels) => levels.levelOf(userId)), .select((levels) => levels.levelOf(userId)),
).toString().colorHash; ),
),
};
static Color powerLevelColor(double level) => HSLColor.fromAHSL(
1,
level.isInfinite ? 30 : (210 + level / 50 * 137.50776) % 360,
0.7,
0.5,
).toColor();
} }

View file

@ -0,0 +1,2 @@
/// What users' name colors are hashed from.
enum NameColor { powerLevel, user }

View file

@ -1,5 +1,6 @@
import "package:material_ui/material_ui.dart"; import "package:material_ui/material_ui.dart";
import "package:freezed_annotation/freezed_annotation.dart"; import "package:freezed_annotation/freezed_annotation.dart";
import "package:nexus/models/name_color.dart";
import "package:nexus/models/room_order.dart"; import "package:nexus/models/room_order.dart";
import "package:nexus/models/show_and_send.dart"; import "package:nexus/models/show_and_send.dart";
@ -17,6 +18,7 @@ class const Settings({
final bool swipeToReply = false, final bool swipeToReply = false,
final bool hideMembershipEvents = false, final bool hideMembershipEvents = false,
final RoomOrder roomOrder = .unreadFirst, final RoomOrder roomOrder = .unreadFirst,
final NameColor nameColor = .powerLevel,
final ShowAndSend typingIndicators = .showAndSend, final ShowAndSend typingIndicators = .showAndSend,
/// When not sending, read receipts are still sent privately, so that the /// When not sending, read receipts are still sent privately, so that the