parent
2041b81141
commit
4b329b4c6f
7 changed files with 1115 additions and 119 deletions
|
|
@ -1,8 +1,10 @@
|
|||
import "dart:async";
|
||||
import "dart:convert";
|
||||
|
||||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||
import "package:flutter/foundation.dart";
|
||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||
import "package:nexus/controllers/client.dart";
|
||||
import "package:nexus/models/account_data.dart";
|
||||
|
||||
class AccountDataController extends Notifier<AccountData> {
|
||||
|
|
@ -22,6 +24,44 @@ class AccountDataController extends Notifier<AccountData> {
|
|||
}
|
||||
});
|
||||
|
||||
Future<void> setEnabledImagePacks(ImagePackRooms packs) async {
|
||||
ISet<(String, String)> keysOf(ImagePackRooms rooms) => rooms.rooms.entries
|
||||
.expand((entry) => entry.value.keys.map((key) => (entry.key, key)))
|
||||
.toISet();
|
||||
final keys = keysOf(packs);
|
||||
bool arrived(AccountData data) {
|
||||
final current = keysOf(data.enabledImagePacks);
|
||||
return current.length == keys.length && current.containsAll(keys);
|
||||
}
|
||||
|
||||
final completer = Completer<void>();
|
||||
final removeListener = listenSelf((_, data) {
|
||||
if (arrived(data) && !completer.isCompleted) completer.complete();
|
||||
});
|
||||
|
||||
try {
|
||||
final client = ref.read(ClientController.provider.notifier);
|
||||
await Future.wait([
|
||||
if (state.imagePackRooms != null)
|
||||
client.setAccountData(
|
||||
.new(type: AccountData.imagePackRoomsKey, content: packs),
|
||||
),
|
||||
client.setAccountData(
|
||||
.new(type: AccountData.legacyImagePackRoomsKey, content: packs),
|
||||
),
|
||||
]);
|
||||
|
||||
if (!arrived(state)) {
|
||||
await completer.future.timeout(
|
||||
const .new(seconds: 10),
|
||||
onTimeout: () {},
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
removeListener();
|
||||
}
|
||||
}
|
||||
|
||||
static final provider = NotifierProvider<AccountDataController, AccountData>(
|
||||
AccountDataController.new,
|
||||
);
|
||||
|
|
|
|||
69
lib/controllers/enabled_image_packs.dart
Normal file
69
lib/controllers/enabled_image_packs.dart
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||
import "package:nexus/controllers/account_data.dart";
|
||||
import "package:nexus/controllers/client.dart";
|
||||
import "package:nexus/controllers/rooms.dart";
|
||||
import "package:nexus/models/content/content.dart";
|
||||
import "package:nexus/models/requests/get_specific_room_state.dart";
|
||||
|
||||
/// The image packs the user has enabled everywhere, as room IDs to their
|
||||
/// packs' state keys, once the packs' state has been loaded.
|
||||
class EnabledImagePacksController
|
||||
extends AsyncNotifier<IMap<String, ISet<String>>> {
|
||||
@override
|
||||
Future<IMap<String, ISet<String>>> build() async {
|
||||
final enabledPacks = ref.watch(
|
||||
AccountDataController.provider.select(
|
||||
(data) => data.enabledImagePacks.rooms.map(
|
||||
(roomId, packs) => .new(roomId, packs.keys.toISet()),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const packTypes = <EventType>[.imagePack, .legacyImagePack];
|
||||
// Rooms we aren't in can't be fetched, and fetched rooms have every pack.
|
||||
final missingKeys = ref.watch(
|
||||
RoomsController.provider.select(
|
||||
(rooms) => enabledPacks.entries
|
||||
.where((entry) => !(rooms[entry.key]?.hasFetchedState ?? true))
|
||||
.expand(
|
||||
(entry) => entry.value
|
||||
.where(
|
||||
(stateKey) => packTypes.every(
|
||||
(type) =>
|
||||
rooms[entry.key]?.state[type.type]?[stateKey] == null,
|
||||
),
|
||||
)
|
||||
.expand(
|
||||
(stateKey) => packTypes.map(
|
||||
(type) => RoomStateKey(
|
||||
roomId: entry.key,
|
||||
type: type.type,
|
||||
stateKey: stateKey,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toIList(),
|
||||
),
|
||||
);
|
||||
|
||||
if (missingKeys.isNotEmpty) {
|
||||
ref
|
||||
.read(RoomsController.provider.notifier)
|
||||
.addSpecificState(
|
||||
await ref
|
||||
.read(ClientController.provider.notifier)
|
||||
.getSpecificRoomState(.new(keys: missingKeys)),
|
||||
);
|
||||
}
|
||||
|
||||
return enabledPacks;
|
||||
}
|
||||
|
||||
static final provider =
|
||||
AsyncNotifierProvider.autoDispose<
|
||||
EnabledImagePacksController,
|
||||
IMap<String, ISet<String>>
|
||||
>(EnabledImagePacksController.new);
|
||||
}
|
||||
|
|
@ -1,11 +1,9 @@
|
|||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||
import "package:nexus/controllers/account_data.dart";
|
||||
import "package:nexus/controllers/client.dart";
|
||||
import "package:nexus/controllers/enabled_image_packs.dart";
|
||||
import "package:nexus/controllers/rooms.dart";
|
||||
import "package:nexus/controllers/space_edges.dart";
|
||||
import "package:nexus/models/content/content.dart";
|
||||
import "package:nexus/models/requests/get_specific_room_state.dart";
|
||||
|
||||
typedef ImagePackSource = ({String roomId, String? stateKey});
|
||||
|
||||
|
|
@ -13,14 +11,8 @@ class ImagePackSourcesController(final String roomId)
|
|||
extends AsyncNotifier<IList<ImagePackSource>> {
|
||||
@override
|
||||
Future<IList<ImagePackSource>> build() async {
|
||||
final enabledPacks = ref.watch(
|
||||
AccountDataController.provider.select(
|
||||
(data) =>
|
||||
(data.imagePackRooms ?? data.legacyImagePackRooms)?.rooms.map(
|
||||
(roomId, packs) => .new(roomId, packs.keys.toISet()),
|
||||
) ??
|
||||
const .empty(),
|
||||
),
|
||||
final enabledPacksFuture = ref.watch(
|
||||
EnabledImagePacksController.provider.future,
|
||||
);
|
||||
final spaceEdges = ref.watch(SpaceEdgesController.provider);
|
||||
final directParents = ref.watch(
|
||||
|
|
@ -63,36 +55,7 @@ class ImagePackSourcesController(final String roomId)
|
|||
),
|
||||
),
|
||||
);
|
||||
|
||||
const packTypes = <EventType>[.imagePack, .legacyImagePack];
|
||||
// Rooms we aren't in can't be fetched, and fetched rooms have every pack
|
||||
final missingKeys = enabledPacks.entries
|
||||
.where((entry) => !(rooms[entry.key]?.hasFetchedState ?? true))
|
||||
.expand(
|
||||
(entry) => entry.value
|
||||
.where(
|
||||
(stateKey) => packTypes.every(
|
||||
(type) =>
|
||||
rooms[entry.key]?.state[type.type]?[stateKey] == null,
|
||||
),
|
||||
)
|
||||
.expand(
|
||||
(stateKey) => packTypes.map(
|
||||
(type) => RoomStateKey(
|
||||
roomId: entry.key,
|
||||
type: type.type,
|
||||
stateKey: stateKey,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toIList();
|
||||
|
||||
if (missingKeys.isNotEmpty) {
|
||||
roomsNotifier.addSpecificState(
|
||||
await client.getSpecificRoomState(.new(keys: missingKeys)),
|
||||
);
|
||||
}
|
||||
final enabledPacks = await enabledPacksFuture;
|
||||
|
||||
return .new([
|
||||
(roomId: roomId, stateKey: null),
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
|||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||
import "package:nexus/controllers/image_pack_sources.dart";
|
||||
import "package:nexus/controllers/rooms.dart";
|
||||
import "package:nexus/models/content/content.dart";
|
||||
import "package:nexus/models/content/image_pack.dart";
|
||||
import "package:nexus/models/event.dart";
|
||||
import "package:nexus/helpers/extensions/room_image_packs.dart";
|
||||
import "package:nexus/models/image_pack.dart";
|
||||
|
||||
class ImagePacksController(final String roomId)
|
||||
|
|
@ -18,40 +16,13 @@ class ImagePacksController(final String roomId)
|
|||
final packs = ref.watch(
|
||||
RoomsController.provider.select(
|
||||
(rooms) => sources.expand((source) {
|
||||
final room = rooms[source.roomId];
|
||||
if (room == null) return const <ImagePack>[];
|
||||
|
||||
final newPacks = room.state[EventType.imagePack.type];
|
||||
final legacyPacks = room.state[EventType.legacyImagePack.type];
|
||||
final roomName = room.metadata?.name ?? source.roomId;
|
||||
final packs =
|
||||
rooms[source.roomId]?.imagePacks(source.roomId) ?? const .empty();
|
||||
|
||||
return switch (source.stateKey) {
|
||||
final stateKey? => ISet([stateKey]),
|
||||
null => ISet(
|
||||
newPacks?.keys ?? const <String>[],
|
||||
).addAll(legacyPacks?.keys ?? const []),
|
||||
}
|
||||
.map(
|
||||
(stateKey) =>
|
||||
switch (newPacks?[stateKey] ?? legacyPacks?[stateKey]) {
|
||||
final rowId? => switch (room.events[rowId]) {
|
||||
Event(
|
||||
redactedBy: null,
|
||||
content: final ImagePackContent content,
|
||||
) =>
|
||||
ImagePack.fromContent(
|
||||
content,
|
||||
id: "${source.roomId}/$stateKey",
|
||||
fallbackName: stateKey.isEmpty
|
||||
? roomName
|
||||
: "$roomName - $stateKey",
|
||||
),
|
||||
_ => null,
|
||||
},
|
||||
null => null,
|
||||
},
|
||||
)
|
||||
.nonNulls;
|
||||
final stateKey? => [?packs[stateKey]],
|
||||
null => packs.values,
|
||||
};
|
||||
}).toIList(),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
37
lib/controllers/room_image_packs.dart
Normal file
37
lib/controllers/room_image_packs.dart
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||
import "package:nexus/controllers/client.dart";
|
||||
import "package:nexus/controllers/rooms.dart";
|
||||
import "package:nexus/helpers/extensions/room_image_packs.dart";
|
||||
import "package:nexus/models/image_pack.dart";
|
||||
|
||||
/// The image packs in [roomId]'s own state by state key, loading its state if
|
||||
/// it hasn't been yet.
|
||||
class RoomImagePacksController(final String roomId)
|
||||
extends AsyncNotifier<IMap<String, ImagePack>> {
|
||||
@override
|
||||
Future<IMap<String, ImagePack>> build() async {
|
||||
if (ref.read(RoomsController.provider)[roomId] case final room?
|
||||
when !room.hasFetchedState) {
|
||||
ref
|
||||
.read(RoomsController.provider.notifier)
|
||||
.addState(
|
||||
roomId,
|
||||
await ref
|
||||
.read(ClientController.provider.notifier)
|
||||
.getRoomState(.new(roomId: roomId)),
|
||||
);
|
||||
}
|
||||
|
||||
return ref.watch(
|
||||
RoomsController.provider.select(
|
||||
(rooms) => rooms[roomId]?.imagePacks(roomId) ?? const .empty(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static final provider = AsyncNotifierProvider.autoDispose
|
||||
.family<RoomImagePacksController, IMap<String, ImagePack>, String>(
|
||||
RoomImagePacksController.new,
|
||||
);
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import "package:nexus/controllers/client_state.dart";
|
|||
import "package:nexus/controllers/local_aliases.dart";
|
||||
import "package:nexus/controllers/power_level.dart";
|
||||
import "package:nexus/controllers/power_levels_saving.dart";
|
||||
import "package:nexus/controllers/room_image_packs.dart";
|
||||
import "package:nexus/controllers/room_power_levels.dart";
|
||||
import "package:nexus/controllers/rooms.dart";
|
||||
import "package:nexus/controllers/space_edges.dart";
|
||||
|
|
@ -21,6 +22,7 @@ import "package:nexus/models/content/content.dart";
|
|||
import "package:nexus/models/content/create.dart";
|
||||
import "package:nexus/models/content/encryption.dart";
|
||||
import "package:nexus/models/content/history_visibility.dart";
|
||||
import "package:nexus/models/content/image_pack.dart";
|
||||
import "package:nexus/models/content/join_rules.dart";
|
||||
import "package:nexus/models/content/membership.dart";
|
||||
import "package:nexus/models/content/message.dart";
|
||||
|
|
@ -33,10 +35,12 @@ import "package:nexus/models/setting.dart";
|
|||
import "package:nexus/models/settings_category.dart";
|
||||
import "package:nexus/widgets/avatar_or_hash.dart";
|
||||
import "package:nexus/widgets/divider_text.dart";
|
||||
import "package:nexus/widgets/image_pack_avatar.dart";
|
||||
import "package:nexus/widgets/settings/add_users_power_level_sheet.dart";
|
||||
import "package:nexus/widgets/settings/aliases_sheet.dart";
|
||||
import "package:nexus/widgets/settings/allowed_rooms_sheet.dart";
|
||||
import "package:nexus/widgets/settings/dialog_list_tile.dart";
|
||||
import "package:nexus/widgets/settings/image_pack_sheet.dart";
|
||||
import "package:nexus/widgets/settings/local_aliases_sheet.dart";
|
||||
import "package:nexus/widgets/settings/power_level_entry_sheet.dart";
|
||||
import "package:nexus/widgets/settings/power_level_tile.dart";
|
||||
|
|
@ -91,6 +95,7 @@ class RoomSettingsSectionsController(final String roomId)
|
|||
final canSetJoinRules = canSet(.joinRules);
|
||||
final canSetHistoryVisibility = canSet(.historyVisibility);
|
||||
final canSetAliases = canSet(.canonicalAlias);
|
||||
final canCreatePacks = canSet(.imagePack);
|
||||
final canSetPowerLevels =
|
||||
canSet(.powerLevels) &&
|
||||
!ref.watch(PowerLevelsSavingController.provider(roomId));
|
||||
|
|
@ -161,15 +166,78 @@ class RoomSettingsSectionsController(final String roomId)
|
|||
final encrypted = stateContent(.encryption) is EncryptionContent;
|
||||
final shownEvents = PowerLevelEvent.shownIn(encrypted: encrypted);
|
||||
|
||||
Future<void> setState(EventType type, Content content) async {
|
||||
Future<void> setState(
|
||||
EventType type,
|
||||
Content content, {
|
||||
String stateKey = "",
|
||||
}) async {
|
||||
final eventId = await client.setState(
|
||||
.new(roomId: roomId, type: type.type, stateKey: "", content: content),
|
||||
.new(
|
||||
roomId: roomId,
|
||||
type: type.type,
|
||||
stateKey: stateKey,
|
||||
content: content,
|
||||
),
|
||||
);
|
||||
if (eventId case final eventId?) {
|
||||
await roomsController.awaitState(roomId, type.type, eventId);
|
||||
await roomsController.awaitState(
|
||||
roomId,
|
||||
type.type,
|
||||
eventId,
|
||||
stateKey: stateKey,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final imagePacks =
|
||||
ref.watch(RoomImagePacksController.provider(roomId)).value ??
|
||||
const .empty();
|
||||
|
||||
// Pack sheets can stay open while the state changes, so these read it when
|
||||
// called, rather than using the state from building
|
||||
IMap<String, IMap<String, int>> currentState() =>
|
||||
ref.read(RoomsController.provider)[roomId]?.state ?? const .empty();
|
||||
|
||||
// Whether the legacy type still has a pack at [stateKey], to clear it
|
||||
bool hasLegacyPack(String stateKey) =>
|
||||
switch (currentState()[EventType.legacyImagePack.type]?[stateKey]) {
|
||||
final rowId? => switch (contentOf(rowId)) {
|
||||
final ImagePackContent content => content.hasContent,
|
||||
_ => false,
|
||||
},
|
||||
null => false,
|
||||
};
|
||||
final canSetLegacyPacks = canSet(.legacyImagePack);
|
||||
|
||||
/// Saves the pack at [stateKey] as the stable type, migrating it by
|
||||
/// clearing its legacy event if it has one.
|
||||
Future<void> setPack(String stateKey, Content content) async {
|
||||
await setState(.imagePack, content, stateKey: stateKey);
|
||||
if (hasLegacyPack(stateKey)) {
|
||||
await setState(.legacyImagePack, const .new(), stateKey: stateKey);
|
||||
}
|
||||
}
|
||||
|
||||
/// A state key for a new pack named [name]. It's one no pack has ever
|
||||
/// had, even deleted ones, as users may still have those enabled.
|
||||
String newPackKey(String name) {
|
||||
final state = currentState();
|
||||
final usedKeys = [
|
||||
EventType.imagePack,
|
||||
EventType.legacyImagePack,
|
||||
].expand((type) => state[type.type]?.keys ?? []).toISet();
|
||||
final slug = name
|
||||
.toLowerCase()
|
||||
.replaceAll(RegExp(r"[^\p{L}\p{N}_-]+", unicode: true), "_")
|
||||
.replaceAll(RegExp(r"^_+|_+$"), "");
|
||||
final base = slug.isEmpty ? "pack" : slug;
|
||||
|
||||
return Iterable.generate(
|
||||
usedKeys.length + 1,
|
||||
(index) => index == 0 ? base : "${base}_${index + 1}",
|
||||
).firstWhere((key) => !usedKeys.contains(key));
|
||||
}
|
||||
|
||||
Future<void> setAliases(
|
||||
String? canonical,
|
||||
ISet<String> alternatives,
|
||||
|
|
@ -817,6 +885,90 @@ class RoomSettingsSectionsController(final String roomId)
|
|||
),
|
||||
]),
|
||||
),
|
||||
.new(
|
||||
title: "Image Packs",
|
||||
icon: Icons.emoji_emotions,
|
||||
settings: .new([
|
||||
...imagePacks.entries.map((entry) {
|
||||
final stateKey = entry.key;
|
||||
final pack = entry.value;
|
||||
final canEdit =
|
||||
canCreatePacks &&
|
||||
(canSetLegacyPacks || !hasLegacyPack(stateKey));
|
||||
|
||||
return Setting(
|
||||
title: pack.name,
|
||||
description: pack.imageCountLabel,
|
||||
icon: Icons.emoji_emotions,
|
||||
builder: (title, description, _) => HookBuilder(
|
||||
key: ValueKey(stateKey),
|
||||
builder: (context) {
|
||||
final saving = Saving.use();
|
||||
|
||||
return ListTile(
|
||||
enabled: !saving.active,
|
||||
leading: ImagePackAvatar(pack.icon),
|
||||
title: Text(title),
|
||||
subtitle: Text(description),
|
||||
onTap: () => context.showAdaptiveSheet(
|
||||
(_) => ImagePackSheet(
|
||||
title: canEdit ? "Edit Image Pack" : title,
|
||||
content: pack.content,
|
||||
onSave: canEdit
|
||||
? (content) => saving.run(
|
||||
() => setPack(
|
||||
stateKey,
|
||||
content,
|
||||
).onError(showError),
|
||||
)
|
||||
: null,
|
||||
onDeleted: canEdit
|
||||
? () => saving.run(
|
||||
() => setPack(
|
||||
stateKey,
|
||||
const .new(),
|
||||
).onError(showError),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
.new(
|
||||
title: "Create Pack",
|
||||
description: "Add a pack of emoticons and stickers to the room.",
|
||||
icon: Icons.add,
|
||||
builder: (title, description, icon) => HookBuilder(
|
||||
builder: (context) {
|
||||
final saving = Saving.use();
|
||||
|
||||
return ListTile(
|
||||
enabled: canCreatePacks && !saving.active,
|
||||
leading: Icon(icon),
|
||||
title: Text(title),
|
||||
subtitle: Text(description),
|
||||
onTap: () => context.showAdaptiveSheet(
|
||||
(_) => ImagePackSheet(
|
||||
title: "Create Pack",
|
||||
content: const .new(),
|
||||
onSave: (content) => saving.run(
|
||||
() => setState(
|
||||
.imagePack,
|
||||
content,
|
||||
stateKey: newPackKey(content.pack!.displayName!),
|
||||
).onError(showError),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
]),
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,23 +9,31 @@ import "package:m3e_buttons/m3e_buttons.dart";
|
|||
import "package:nexus/controllers/account_data.dart";
|
||||
import "package:nexus/controllers/client.dart";
|
||||
import "package:nexus/controllers/client_state.dart";
|
||||
import "package:nexus/controllers/enabled_image_packs.dart";
|
||||
import "package:nexus/controllers/init_complete.dart";
|
||||
import "package:nexus/controllers/notification.dart";
|
||||
import "package:nexus/controllers/rooms.dart";
|
||||
import "package:nexus/controllers/settings.dart";
|
||||
import "package:nexus/controllers/unified_push.dart";
|
||||
import "package:nexus/controllers/spec_versions.dart";
|
||||
import "package:nexus/controllers/unified_push_allowed.dart";
|
||||
import "package:nexus/controllers/unified_push_distributors.dart";
|
||||
import "package:nexus/helpers/extensions/distributor_name.dart";
|
||||
import "package:nexus/helpers/extensions/room_image_packs.dart";
|
||||
import "package:nexus/helpers/hooks/saving.dart";
|
||||
import "package:nexus/models/account_data.dart";
|
||||
import "package:nexus/models/client_state.dart";
|
||||
import "package:nexus/models/image_pack.dart";
|
||||
import "package:nexus/models/name_color.dart";
|
||||
import "package:nexus/models/room_order.dart";
|
||||
import "package:nexus/models/setting.dart";
|
||||
import "package:nexus/models/settings.dart";
|
||||
import "package:nexus/models/settings_category.dart";
|
||||
import "package:nexus/models/show_and_send.dart";
|
||||
import "package:nexus/main.dart";
|
||||
import "package:nexus/widgets/image_pack_avatar.dart";
|
||||
import "package:nexus/widgets/settings/dialog_list_tile.dart";
|
||||
import "package:nexus/widgets/settings/enable_image_packs_sheet.dart";
|
||||
import "package:nexus/widgets/user_bottom_sheet.dart";
|
||||
import "package:nexus/widgets/reset_encryption_sheet.dart";
|
||||
import "package:nexus/helpers/extensions/show_adaptive_sheet.dart";
|
||||
|
|
@ -36,6 +44,33 @@ class SettingsSectionsController
|
|||
Future<IMap<String, IList<SettingsCategory>>> build() async {
|
||||
final settings = await ref.watch(SettingsController.provider.future);
|
||||
|
||||
// Loads the enabled packs' state, so they can be shown
|
||||
ref.watch(EnabledImagePacksController.provider);
|
||||
final enabledPacks = ref.watch(
|
||||
AccountDataController.provider.select((data) => data.enabledImagePacks),
|
||||
);
|
||||
final enabledPackInfo = ref.watch(
|
||||
RoomsController.provider.select(
|
||||
(rooms) => enabledPacks.rooms.entries.expand((entry) {
|
||||
final room = rooms[entry.key];
|
||||
final packs = room?.imagePacks(entry.key) ?? const .empty();
|
||||
|
||||
return entry.value.keys.map(
|
||||
(stateKey) => (
|
||||
roomId: entry.key,
|
||||
stateKey: stateKey,
|
||||
roomName: room?.metadata?.name ?? entry.key,
|
||||
pack: packs[stateKey],
|
||||
),
|
||||
);
|
||||
}).toIList(),
|
||||
),
|
||||
);
|
||||
|
||||
Future<void> setEnabledPacks(ImagePackRooms packs) => ref
|
||||
.read(AccountDataController.provider.notifier)
|
||||
.setEnabledImagePacks(packs);
|
||||
|
||||
/// A tile for a [ShowAndSend] setting, saving the settings from [update].
|
||||
Widget showAndSendTile(
|
||||
String title,
|
||||
|
|
@ -300,6 +335,78 @@ class SettingsSectionsController
|
|||
),
|
||||
]),
|
||||
),
|
||||
.new(
|
||||
title: "Image Packs",
|
||||
icon: Icons.emoji_emotions,
|
||||
settings: .new([
|
||||
...enabledPackInfo.map(
|
||||
(info) => Setting(
|
||||
title:
|
||||
info.pack?.name ??
|
||||
ImagePack.fallbackName(info.roomName, info.stateKey),
|
||||
description: info.roomName,
|
||||
icon: Icons.emoji_emotions,
|
||||
builder: (title, description, _) => HookBuilder(
|
||||
key: ValueKey((info.roomId, info.stateKey)),
|
||||
builder: (context) {
|
||||
final saving = Saving.use();
|
||||
|
||||
return ListTile(
|
||||
enabled: !saving.active,
|
||||
leading: ImagePackAvatar(info.pack?.icon),
|
||||
title: Text(title),
|
||||
subtitle: Text(description),
|
||||
trailing: IconButton(
|
||||
tooltip: "Remove pack",
|
||||
icon: Icon(Icons.delete),
|
||||
onPressed: saving.active
|
||||
? null
|
||||
: () => saving.run(
|
||||
() => setEnabledPacks(
|
||||
ref
|
||||
.read(AccountDataController.provider)
|
||||
.enabledImagePacks
|
||||
.withPack(
|
||||
info.roomId,
|
||||
info.stateKey,
|
||||
enabled: false,
|
||||
),
|
||||
).onError(showError),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
.new(
|
||||
title: "Add Packs",
|
||||
description:
|
||||
"Use image packs from any of your rooms in every room.",
|
||||
icon: Icons.add,
|
||||
builder: (title, description, icon) => HookBuilder(
|
||||
builder: (context) {
|
||||
final saving = Saving.use();
|
||||
|
||||
return ListTile(
|
||||
enabled: !saving.active,
|
||||
leading: Icon(icon),
|
||||
title: Text(title),
|
||||
subtitle: Text(description),
|
||||
onTap: () => context.showAdaptiveSheet(
|
||||
(_) => EnableImagePacksSheet(
|
||||
enabled: enabledPacks,
|
||||
onSave: (packs) => saving.run(
|
||||
() => setEnabledPacks(packs).onError(showError),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
.new(
|
||||
title: "Notifications",
|
||||
icon: Icons.notifications,
|
||||
|
|
|
|||
Loading…
Reference in a new issue