diff --git a/commet/lib/client/components/emoticon/emoji_pack.dart b/commet/lib/client/components/emoticon/emoji_pack.dart index 1ff2caa2b..8ac0fb264 100644 --- a/commet/lib/client/components/emoticon/emoji_pack.dart +++ b/commet/lib/client/components/emoticon/emoji_pack.dart @@ -11,7 +11,7 @@ abstract class EmoticonPack { String get ownerId; String get ownerDisplayName; - Stream get onEmoticonAdded; + bool get isGloballyAvailable; List get emotes; @@ -24,31 +24,37 @@ abstract class EmoticonPack { ImageProvider? get image; IconData? get icon; - bool get isStickerPack; - bool get isEmojiPack; - bool get isGloballyAvailable; + EmoticonUsage get usage; Future deleteEmoticon(Emoticon emoticon); - Future renameEmoticon(Emoticon emoticon, String name); - - Future markEmoticonAsSticker(Emoticon emoticon, bool isSticker); + Future setPackUsage(EmoticonUsage usage); - Future markEmoticonAsEmoji(Emoticon emoticon, bool isEmoji); + Future updatePack( + {EmoticonUsage? usage, String? name, Uint8List? imageData}); - Future markAsEmoji(bool isEmojiPack); + Emoticon? getByShortcode(String shortcode); - Future markAsSticker(bool isStickerPack); + bool get isStickerPack; - Emoticon? getByShortcode(String shortcode); + bool get isEmojiPack; - Future addEmoticon( - {required String slug, - String? shortcode, - required Uint8List data, - String? mimeType, - bool? isEmoji, - bool? isSticker}); + Future updateEmoticon({ + String? slug, + String? shortcode, + Uint8List? data, + String? mimeType, + EmoticonUsage? usage, + required Emoticon previous, + }); + + Future addEmoticon({ + required String slug, + String? shortcode, + required Uint8List data, + String? mimeType, + EmoticonUsage usage, + }); Future markAsGlobal(bool isGlobal); diff --git a/commet/lib/client/components/emoticon/emoticon.dart b/commet/lib/client/components/emoticon/emoticon.dart index 773342456..d9935ab2f 100644 --- a/commet/lib/client/components/emoticon/emoticon.dart +++ b/commet/lib/client/components/emoticon/emoticon.dart @@ -1,15 +1,24 @@ import 'dart:core'; import 'package:flutter/material.dart'; +enum EmoticonUsage { + sticker, + emoji, + all, + inherit, +} + abstract class Emoticon { ImageProvider? get image; String get slug; String? get shortcode; String get key; - bool get isMarkedEmoji; - bool get isMarkedSticker; + EmoticonUsage get usage; + + bool get isSticker => + usage == EmoticonUsage.sticker || usage == EmoticonUsage.all; - bool get isSticker; - bool get isEmoji; + bool get isEmoji => + usage == EmoticonUsage.emoji || usage == EmoticonUsage.all; } diff --git a/commet/lib/client/components/emoticon/emoticon_component.dart b/commet/lib/client/components/emoticon/emoticon_component.dart index df75a9761..dc6f5dfb6 100644 --- a/commet/lib/client/components/emoticon/emoticon_component.dart +++ b/commet/lib/client/components/emoticon/emoticon_component.dart @@ -14,7 +14,7 @@ abstract class EmoticonComponent implements Component { List globalPacks(); List get ownedPacks; bool get canCreatePack; - Stream get onOwnedPackAdded; + Stream get onStateChanged; Future createEmoticonPack(String name, Uint8List? avatarData); Future importEmoticonPack(String name, int avatarIndex, diff --git a/commet/lib/client/matrix/components/emoticon/matrix_emoticon.dart b/commet/lib/client/matrix/components/emoticon/matrix_emoticon.dart index 79141ed9f..ffdd321e3 100644 --- a/commet/lib/client/matrix/components/emoticon/matrix_emoticon.dart +++ b/commet/lib/client/matrix/components/emoticon/matrix_emoticon.dart @@ -18,34 +18,16 @@ class MatrixEmoticon implements Emoticon { Uri emojiUrl; - late bool _isEmojiPack; - late bool _isStickerPack; - late bool _isMarkedEmoji; - late bool _isMarkedSticker; - @override - bool get isEmoji => _isEmojiPack || _isMarkedEmoji; + EmoticonUsage usage; - @override - bool get isSticker => _isStickerPack || _isMarkedSticker; - - @override - bool get isMarkedEmoji => _isMarkedEmoji; - - @override - bool get isMarkedSticker => _isMarkedSticker; + EmoticonUsage packUsage; MatrixEmoticon(this.emojiUrl, matrix.Client client, - {required String shortcode, - bool isEmojiPack = true, - bool isStickerPack = true, - bool isMarkedSticker = false, - bool isMarkedEmoji = false}) { + {required this.packUsage, + required String shortcode, + required this.usage}) { _shortcode = shortcode; - _isEmojiPack = isEmojiPack; - _isStickerPack = isStickerPack; - _isMarkedEmoji = isMarkedEmoji; - _isMarkedSticker = isMarkedSticker; _image = MatrixMxcImage(emojiUrl, client, doThumbnail: false); } @@ -53,22 +35,6 @@ class MatrixEmoticon implements Emoticon { _shortcode = shortcode; } - void markAsEmoji(bool value) { - _isMarkedEmoji = value; - } - - void markAsSticker(bool value) { - _isMarkedSticker = value; - } - - void markPackAsEmoji(bool value) { - _isEmojiPack = value; - } - - void markPackAsSticker(bool value) { - _isStickerPack = value; - } - void setImage(MatrixMxcImage image) { _image = image; } @@ -91,4 +57,18 @@ class MatrixEmoticon implements Emoticon { int get hashCode { return key.hashCode; } + + @override + bool get isSticker => + usage == EmoticonUsage.sticker || + usage == EmoticonUsage.all || + (usage == EmoticonUsage.inherit && + [EmoticonUsage.sticker, EmoticonUsage.all].contains(packUsage)); + + @override + bool get isEmoji => + usage == EmoticonUsage.emoji || + usage == EmoticonUsage.all || + (usage == EmoticonUsage.inherit && + [EmoticonUsage.emoji, EmoticonUsage.all].contains(packUsage)); } diff --git a/commet/lib/client/matrix/components/emoticon/matrix_emoticon_component.dart b/commet/lib/client/matrix/components/emoticon/matrix_emoticon_component.dart index 1dd89887d..b19d13a9d 100644 --- a/commet/lib/client/matrix/components/emoticon/matrix_emoticon_component.dart +++ b/commet/lib/client/matrix/components/emoticon/matrix_emoticon_component.dart @@ -1,13 +1,14 @@ +import 'dart:async'; import 'dart:typed_data'; import 'package:commet/client/components/emoticon/emoji_pack.dart'; +import 'package:commet/client/components/emoticon/emoticon.dart'; import 'package:commet/client/components/emoticon/emoticon_component.dart'; import 'package:commet/client/matrix/components/emoticon/matrix_emoticon_pack.dart'; import 'package:commet/client/matrix/components/emoticon/matrix_emoticon_state_manager.dart'; import 'package:commet/client/matrix/components/emoticon/matrix_import_emoticon_pack_task.dart'; import 'package:commet/client/matrix/matrix_client.dart'; import 'package:commet/main.dart'; -import 'package:commet/utils/notifying_list.dart'; import 'package:flutter/material.dart'; /// Manages custom emoticon packs from the matrix user account state @@ -15,6 +16,8 @@ class MatrixEmoticonComponent extends EmoticonComponent { static const roomEmotesStateKey = "im.ponies.room_emotes"; static const globalEmoteRoomsStateKey = "im.ponies.emote_rooms"; + final StreamController _onStateChanged = StreamController.broadcast(); + @override bool get canCreatePack => ownedPacks.isEmpty; @@ -27,23 +30,37 @@ class MatrixEmoticonComponent extends EmoticonComponent { MatrixEmoticonStateManager state; - final NotifyingList _packs = - NotifyingList.empty(growable: true); - MatrixEmoticonComponent(this.client, this.state) { - loadFromState(state.getAllStates()); + refreshOwnedPacks(); - state.onStateChanged.listen((event) { - var s = state.getAllStates(); - loadFromState(s); + state.onStateChanged.listen((_) { + refreshOwnedPacks(); + _onStateChanged.add(null); }); } + void refreshOwnedPacks() { + final state = this.state.getAllStates(); + + _ownedPacks = state.entries.where((e) { + final val = e.value; + if (val is Map) { + return val.isNotEmpty; + } else { + return false; + } + }).map((e) { + return MatrixEmoticonPack(this, e.key, e.value); + }).toList(); + } + @override - Stream get onOwnedPackAdded => _packs.onAdd; + Stream get onStateChanged => _onStateChanged.stream; + + List _ownedPacks = List.empty(); @override - List get ownedPacks => _packs; + List get ownedPacks => _ownedPacks; String getDefaultDisplayName() { return "Personal"; @@ -61,29 +78,6 @@ class MatrixEmoticonComponent extends EmoticonComponent { return true; } - void loadFromState(Map newState) { - _packs.removeWhere( - (element) => newState.containsKey(element.identifier) == false); - - for (var key in newState.keys) { - var s = newState[key]; - if (s is! Map) continue; - if (s.isEmpty) { - _packs.removeWhere((element) => element.identifier == key); - continue; - } - - var existing = - _packs.where((element) => element.identifier == key).firstOrNull; - if (existing is MatrixEmoticonPack) { - existing.updateFromState(s); - } else { - var pack = MatrixEmoticonPack(this, key, s); - _packs.add(pack); - } - } - } - @override Future createEmoticonPack(String name, Uint8List? avatarData) async { Uri? avatar; @@ -150,7 +144,6 @@ class MatrixEmoticonComponent extends EmoticonComponent { @override Future deleteEmoticonPack(EmoticonPack pack) { - _packs.remove(pack); var matrixPack = pack as MatrixEmoticonPack; return state.setState(matrixPack.stateKey, {}); } @@ -219,62 +212,84 @@ class MatrixEmoticonComponent extends EmoticonComponent { return state.setState(packKey, content); } - Future renameEmoticon( - String packKey, String emoteName, String newName) async { + Future setPackUsages(String packKey, List? usages) async { var content = state.getState(packKey); - if (content.containsKey('images')) { - var images = content['images'] as Map; - var image = images[emoteName] as Map?; - images.remove(emoteName); + var pack = content['pack'] as Map?; - if (image != null) { - image['display_name'] = newName; - images[newName] = image; - } + if (pack == null) return; - content['images'] = images; - } + pack['usage'] = usages?.isEmpty == true ? null : usages; + content['pack'] = pack; return state.setState(packKey, content); } - Future setEmoticonUsages( - String packKey, String emoteName, List? usages) async { + Future updatePack(String packKey, + {EmoticonUsage? usage, String? name, Uint8List? imageData}) async { var content = state.getState(packKey); - if (content.containsKey('images')) { - var images = content['images'] as Map; - - if (images.containsKey(emoteName)) { - var emote = images[emoteName] as Map; - - emote.remove('usage'); - - if (usages != null && usages.isNotEmpty) { - emote['usage'] = usages; - } + if (usage != null) { + content['pack']['usage'] = switch (usage) { + EmoticonUsage.sticker => ["sticker"], + EmoticonUsage.emoji => ["emoticon"], + EmoticonUsage.all => ["emoticon", "sticker"], + EmoticonUsage.inherit => null, + }; + } - images[emoteName] = emote; - } + if (name != null) { + content['pack']['display_name'] = name; + } - content['images'] = images; + if (imageData != null) { + Uri url = await client.getMatrixClient().uploadContent(imageData); + content['pack']['avatar_url'] = url.toString(); } return state.setState(packKey, content); } - Future setPackUsages(String packKey, List? usages) async { + Future updateEmoticon( + String packKey, + String emoteName, { + Uint8List? data, + String? mimeType, + EmoticonUsage? usage, + required Emoticon previous, + }) async { var content = state.getState(packKey); - var pack = content['pack'] as Map?; + var emoteState = content['images'][previous.shortcode!]; - if (pack == null) return; + if (usage != null) { + emoteState['usage'] = switch (usage) { + EmoticonUsage.sticker => ["sticker"], + EmoticonUsage.emoji => ["emoticon"], + EmoticonUsage.all => ["emoticon", "sticker"], + EmoticonUsage.inherit => null, + }; + } - pack['usage'] = usages?.isEmpty == true ? null : usages; - content['pack'] = pack; + if (data != null) { + Uri url = await client.getMatrixClient().uploadContent(data); + emoteState['url'] = url.toString(); + } - return state.setState(packKey, content); + var pack = {"pack": content['pack'], "images": {}}; + + var keys = content['images'].keys.toList(); + + // construct a new map this way, to keep ordering :O + for (var key in keys) { + if (key == previous.shortcode) { + pack['images'][emoteName] = emoteState; + } else { + pack['images'][key] = content['images'][key]; + } + } + + await state.setState(packKey, pack); } Future>? createEmoticon( diff --git a/commet/lib/client/matrix/components/emoticon/matrix_emoticon_pack.dart b/commet/lib/client/matrix/components/emoticon/matrix_emoticon_pack.dart index 5ceb0d145..9973c7920 100644 --- a/commet/lib/client/matrix/components/emoticon/matrix_emoticon_pack.dart +++ b/commet/lib/client/matrix/components/emoticon/matrix_emoticon_pack.dart @@ -8,7 +8,6 @@ import 'package:commet/client/matrix/components/emoticon/matrix_room_emoticon_co import 'package:commet/client/matrix/components/emoticon/matrix_space_emoticon_component.dart'; import 'package:commet/client/matrix/extensions/matrix_client_extensions.dart'; import 'package:commet/client/matrix/matrix_mxc_image_provider.dart'; -import 'package:commet/utils/notifying_list.dart'; import 'package:flutter/widgets.dart'; import 'package:fuzzy/fuzzy.dart'; import 'package:matrix/matrix.dart'; @@ -18,116 +17,101 @@ class MatrixEmoticonPack implements EmoticonPack { String stateKey; @override - final NotifyingList emotes = - NotifyingList.empty(growable: true); + List get emotes { + final images = state.tryGetMap>("images"); - late Map shortcodeToEmoticon; + if (images == null) { + return List.empty(); + } - @override - late String displayName; + return images.entries.map((e) { + final shortCode = e.key; + final url = e.value.tryGet("url"); + final usages = e.value.tryGetList("usage"); - @override - ImageProvider? image; + if (url == null) { + throw UnimplementedError; + } + + final usage = usagesArrayToUsage(usages); + + return MatrixEmoticon(Uri.parse(url), component.client.getMatrixClient(), + packUsage: this.usage, shortcode: shortCode, usage: usage); + }).toList(); + } + + late Map shortcodeToEmoticon; + + late Map state; @override - IconData? icon; - - MatrixEmoticonPack( - this.component, - this.stateKey, - Map initialState, - ) { - updateFromState(initialState); + String get displayName { + return state + .tryGetMap("pack") + ?.tryGet("display_name") ?? + "Unnamed Pack"; } - void updateFromState(Map initialState) { - var info = initialState['pack']; - displayName = info?['display_name'] ?? component.getDefaultDisplayName(); - shortcodeToEmoticon = {}; - if (info?['avatar_url'] != null) { - try { - var uri = Uri.parse(info!['avatar_url']!); - image = MatrixMxcImage(uri, component.client.getMatrixClient()); - } catch (_) {} - } + @override + ImageProvider? get image { + final pack = state.tryGetMap("pack"); - image ??= component.getDefaultImage(); - icon = component.getDefaultIcon(); + final url = pack?.tryGet("avatar_url"); - var images = initialState['images'] as Map?; - if (images == null) return; + if (url != null) { + return MatrixMxcImage(Uri.parse(url), component.client.getMatrixClient()); + } - bool isStickerPackCache = isStickerPack; - bool isEmojiPackCache = isEmojiPack; + return null; + } - emotes.removeWhere((element) => images.containsKey(element.key) == false); - shortcodeToEmoticon.removeWhere((key, value) => - emotes.any((element) => element.shortcode == key) == false); + @override + IconData? get icon { + return component.getDefaultIcon(); + } - for (var image in images.keys) { - var url = images[image]['url']; - if (url == null) { - continue; - } - var uri = Uri.parse(url); + MatrixEmoticonPack(this.component, this.stateKey, this.state); - var usages = images[image]['usage'] as List?; + EmoticonUsage usagesArrayToUsage(List? usages) { + if ((usages?.contains("emoticon") == true) && + (usages?.contains("sticker") == false)) { + return EmoticonUsage.emoji; + } - bool markedSticker = false; - bool markedEmoji = false; - if (usages != null) { - markedSticker = usages.contains("sticker"); - markedEmoji = usages.contains("emoticon"); - } + if ((usages?.contains("sticker") == true) && + (usages?.contains("emoticon") == false)) { + return EmoticonUsage.sticker; + } - var existing = - emotes.where((element) => element.key == image).firstOrNull; - - if (existing != null) { - existing.markAsSticker(markedSticker); - existing.markAsEmoji(markedEmoji); - existing.markPackAsEmoji(isEmojiPackCache); - existing.markPackAsSticker(isStickerPackCache); - - if (uri != existing.emojiUrl) { - existing.emojiUrl = uri; - existing.setImage( - MatrixMxcImage(uri, component.client.getMatrixClient())); - } - } else { - var emote = MatrixEmoticon(uri, component.client.getMatrixClient(), - shortcode: image, - isEmojiPack: isEmojiPackCache, - isStickerPack: isStickerPackCache, - isMarkedEmoji: markedEmoji, - isMarkedSticker: markedSticker); - emotes.add(emote); - - if (emote.shortcode != null) { - shortcodeToEmoticon[emote.shortcode!] = emote; - } - } + if ((usages?.contains("sticker") == true) && + (usages?.contains("emoticon") == true)) { + return EmoticonUsage.all; } + + return EmoticonUsage.inherit; } @override - Future addEmoticon( - {required String slug, - String? shortcode, - required Uint8List data, - String? mimeType, - bool? isEmoji, - bool? isSticker}) async { + Future addEmoticon({ + required String slug, + String? shortcode, + required Uint8List data, + String? mimeType, + EmoticonUsage? usage, + }) async { await component.createEmoticon(identifier, shortcode!, data); } - List? _getUsage() { - var info = - component.state.getState(identifier)['pack'] as Map?; - if (info == null) return null; - - var usage = info.tryGet("usage") as List?; - return usage; + @override + Future updateEmoticon( + {String? slug, + String? shortcode, + Uint8List? data, + String? mimeType, + EmoticonUsage? usage, + required Emoticon previous}) async { + await component.updateEmoticon(identifier, shortcode!, + data: data, usage: usage, previous: previous); } @override @@ -136,11 +120,6 @@ class MatrixEmoticonPack implements EmoticonPack { @override Future deleteEmoticon(Emoticon emoticon) async { await component.deleteEmoticon(identifier, emoticon.shortcode!); - emotes.remove(emoticon); - - if (emoticon.shortcode != null) { - shortcodeToEmoticon.remove(emoticon.shortcode); - } } @override @@ -154,15 +133,6 @@ class MatrixEmoticonPack implements EmoticonPack { @override String get identifier => stateKey; - @override - bool get isEmojiPack => _getUsage()?.contains("emoticon") ?? true; - - @override - bool get isGloballyAvailable => component.isGloballyAvailable(identifier); - - @override - bool get isStickerPack => _getUsage()?.contains("sticker") ?? true; - @override Future markAsGlobal(bool isGlobal) async { late Room room; @@ -181,48 +151,28 @@ class MatrixEmoticonPack implements EmoticonPack { } } - @override - Future markAsEmoji(bool isEmojiPack) async { - await component.setPackUsages(identifier, - [if (isEmojiPack) 'emoticon', if (isStickerPack) 'sticker']); + List? _emoticonUsageToArray(EmoticonUsage usage) { + final usages = switch (usage) { + EmoticonUsage.sticker => ["sticker"], + EmoticonUsage.emoji => ["emoticon"], + EmoticonUsage.all => ["sticker", "emoticon"], + EmoticonUsage.inherit => null, + }; - for (var emote in emotes) { - emote.markPackAsEmoji(isEmojiPack); - } + return usages; } @override - Future markAsSticker(bool isStickerPack) async { - await component.setPackUsages(identifier, - [if (isEmojiPack) 'emoticon', if (isStickerPack) 'sticker']); - - for (var emote in emotes) { - emote.markPackAsSticker(isStickerPack); - } + Future setPackUsage(EmoticonUsage usage) { + final usages = _emoticonUsageToArray(usage); + return component.setPackUsages(identifier, usages); } @override - Future markEmoticonAsEmoji(Emoticon emoticon, bool isEmoji) async { - await component.setEmoticonUsages(identifier, emoticon.shortcode!, - [if (isEmoji) 'emoticon', if (emoticon.isMarkedSticker) 'sticker']); - - (emoticon as MatrixEmoticon).markAsEmoji(isEmoji); - } - - @override - Future markEmoticonAsSticker(Emoticon emoticon, bool isSticker) async { - await component.setEmoticonUsages(identifier, emoticon.shortcode!, - [if (emoticon.isMarkedEmoji) 'emoticon', if (isSticker) 'sticker']); - - (emoticon as MatrixEmoticon).markAsSticker(isSticker); - } - - @override - Stream get onEmoticonAdded => emotes.onAdd; - - @override - Future renameEmoticon(Emoticon emoticon, String name) { - return component.renameEmoticon(identifier, emoticon.shortcode!, name); + Future updatePack( + {EmoticonUsage? usage, String? name, Uint8List? imageData}) { + return component.updatePack(identifier, + usage: usage, name: name, imageData: imageData); } @override @@ -255,4 +205,64 @@ class MatrixEmoticonPack implements EmoticonPack { @override String get ownerDisplayName => component.ownerDisplayName; + + @override + bool operator ==(Object other) { + if (other is! MatrixEmoticonPack) return false; + if (other.component != component) return false; + + return (other.stateKey == stateKey && + other.component.state.id == component.state.id); + } + + @override + int get hashCode => stateKey.hashCode; + + @override + EmoticonUsage get usage { + final pack = state.tryGetMap("pack"); + var usages = pack?.tryGetList("usage"); + var usage = usagesArrayToUsage(usages); + if (usage == EmoticonUsage.inherit) { + return EmoticonUsage.all; + } else { + return usage; + } + } + + @override + bool get isEmojiPack => + [EmoticonUsage.emoji, EmoticonUsage.all].contains(usage); + + @override + bool get isStickerPack => + [EmoticonUsage.sticker, EmoticonUsage.all].contains(usage); + + @override + bool get isGloballyAvailable { + late Room room; + if (component is MatrixRoomEmoticonComponent) { + room = (component as MatrixRoomEmoticonComponent).room.matrixRoom; + } else if (component is MatrixSpaceEmoticonComponent) { + room = (component as MatrixSpaceEmoticonComponent).space.matrixRoom; + } else { + return false; + } + + final data = room + .client.accountData[MatrixEmoticonComponent.globalEmoteRoomsStateKey]; + if (data == null) { + return false; + } + + final rooms = data.content.tryGetMap("rooms"); + + if (rooms == null) { + return false; + } + + final roomData = rooms.tryGetMap(room.id); + + return roomData?.containsKey(identifier) ?? false; + } } diff --git a/commet/lib/client/matrix/components/emoticon/matrix_emoticon_state_manager.dart b/commet/lib/client/matrix/components/emoticon/matrix_emoticon_state_manager.dart index 8df1f1410..bf5de5ffa 100644 --- a/commet/lib/client/matrix/components/emoticon/matrix_emoticon_state_manager.dart +++ b/commet/lib/client/matrix/components/emoticon/matrix_emoticon_state_manager.dart @@ -12,6 +12,8 @@ abstract class MatrixEmoticonStateManager { Future setState(String packKey, Map content); Stream get onStateChanged; + + String get id; } class MatrixEmoticonPersonalStateManager implements MatrixEmoticonStateManager { @@ -33,9 +35,14 @@ class MatrixEmoticonPersonalStateManager implements MatrixEmoticonStateManager { onStateChangedController.add(null); }); - mx.onSync.stream - .where((e) => e.accountData != null) - .listen((_) => onStateChangedController.add(null)); + mx.onSync.stream.where((e) => e.accountData != null).listen((update) { + if (update.accountData?.any((e) => + e.type == "im.ponies.user_emotes" || + e.type == "im.ponies.emote_rooms") == + true) { + onStateChangedController.add(null); + } + }); } @override @@ -62,6 +69,9 @@ class MatrixEmoticonPersonalStateManager implements MatrixEmoticonStateManager { ?.content ?? {}; } + + @override + String get id => client.identifier; } class MatrixEmoticonRoomStateManager implements MatrixEmoticonStateManager { @@ -119,4 +129,7 @@ class MatrixEmoticonRoomStateManager implements MatrixEmoticonStateManager { @override Stream get onStateChanged => onStateChangedController.stream; + + @override + String get id => room.id; } diff --git a/commet/lib/client/matrix/components/emoticon/matrix_room_emoticon_component.dart b/commet/lib/client/matrix/components/emoticon/matrix_room_emoticon_component.dart index 165842ff3..298fe2f4b 100644 --- a/commet/lib/client/matrix/components/emoticon/matrix_room_emoticon_component.dart +++ b/commet/lib/client/matrix/components/emoticon/matrix_room_emoticon_component.dart @@ -131,7 +131,7 @@ class MatrixRoomEmoticonComponent extends MatrixEmoticonComponent .where((element) => element.containsRoom(room.identifier))) { var component = space.getComponent(); if (component != null) { - result.addAll(component.ownedPacks); + result.addAll(component.ownedPacks.where((e) => !result.contains(e))); } } @@ -145,7 +145,8 @@ class MatrixRoomEmoticonComponent extends MatrixEmoticonComponent } if (globalComponent != null) { - result.addAll(globalComponent.ownedPacks); + result + .addAll(globalComponent.ownedPacks.where((e) => !result.contains(e))); } if (includeUnicode) result.addAll(UnicodeEmojis.packs!); diff --git a/commet/lib/client/matrix/timeline_events/matrix_timeline_event_mixin_reactions.dart b/commet/lib/client/matrix/timeline_events/matrix_timeline_event_mixin_reactions.dart index 887b1a109..74a03f83a 100644 --- a/commet/lib/client/matrix/timeline_events/matrix_timeline_event_mixin_reactions.dart +++ b/commet/lib/client/matrix/timeline_events/matrix_timeline_event_mixin_reactions.dart @@ -70,7 +70,9 @@ mixin MatrixTimelineEventReactions on MatrixTimelineEvent if (key.startsWith("mxc://")) { return MatrixEmoticon(Uri.parse(key), timeline.room.client, - shortcode: event.content.tryGet("shortcode") ?? ""); + shortcode: event.content.tryGet("shortcode") ?? "", + packUsage: EmoticonUsage.all, + usage: EmoticonUsage.all); } return UnicodeEmoticon(key, shortcode: content['shortcode'] as String?); diff --git a/commet/lib/ui/atoms/rich_text/matrix_html_parser.dart b/commet/lib/ui/atoms/rich_text/matrix_html_parser.dart index 9a6dd948a..a0479d1d0 100644 --- a/commet/lib/ui/atoms/rich_text/matrix_html_parser.dart +++ b/commet/lib/ui/atoms/rich_text/matrix_html_parser.dart @@ -1,3 +1,4 @@ +import 'package:commet/client/components/emoticon/emoticon.dart'; import 'package:commet/client/matrix/components/emoticon/matrix_emoticon.dart'; import 'package:commet/client/matrix/matrix_mxc_image_provider.dart'; import 'package:commet/ui/atoms/code_block.dart'; @@ -218,7 +219,10 @@ class MatrixEmoticonHtmlExtension extends HtmlExtension { return WidgetSpan( child: EmojiWidget( - MatrixEmoticon(uri, client, shortcode: context.attributes["alt"] ?? ""), + MatrixEmoticon(uri, client, + shortcode: context.attributes["alt"] ?? "", + packUsage: EmoticonUsage.all, + usage: EmoticonUsage.emoji), height: emojiSize, )); } diff --git a/commet/lib/ui/molecules/account_selector.dart b/commet/lib/ui/molecules/account_selector.dart index e1b0fa2dc..e3ff21a97 100644 --- a/commet/lib/ui/molecules/account_selector.dart +++ b/commet/lib/ui/molecules/account_selector.dart @@ -4,18 +4,35 @@ import 'package:flutter/widgets.dart'; import 'package:tiamat/tiamat.dart' as tiamat; -class AccountSelector extends StatelessWidget { +class AccountSelector extends StatefulWidget { const AccountSelector(this.clients, {super.key, this.onClientSelected}); final List clients; final Function(Client client)? onClientSelected; + @override + State createState() => _AccountSelectorState(); +} + +class _AccountSelectorState extends State { + late Client selectedClient; + + @override + void initState() { + selectedClient = widget.clients.first; + super.initState(); + } + @override Widget build(BuildContext context) { return tiamat.DropdownSelector( - items: clients, + items: widget.clients, + value: selectedClient, itemHeight: 65, onItemSelected: (item) { - onClientSelected?.call(item); + setState(() { + selectedClient = item; + }); + widget.onClientSelected?.call(item); }, itemBuilder: (item) { return Padding( diff --git a/commet/lib/ui/pages/add_space_or_room/add_space_or_room_view.dart b/commet/lib/ui/pages/add_space_or_room/add_space_or_room_view.dart index 6eacd0a89..28195ffe2 100644 --- a/commet/lib/ui/pages/add_space_or_room/add_space_or_room_view.dart +++ b/commet/lib/ui/pages/add_space_or_room/add_space_or_room_view.dart @@ -275,6 +275,7 @@ class _AddSpaceOrRoomViewState extends State { child: tiamat.DropdownSelector( items: const [RoomVisibility.private, RoomVisibility.public], itemHeight: 90, + value: visibility, onItemSelected: (item) { setState(() { visibility = item; diff --git a/commet/lib/ui/pages/matrix/room_address_settings/matrix_room_address_settings_view.dart b/commet/lib/ui/pages/matrix/room_address_settings/matrix_room_address_settings_view.dart index dc496c7d0..deb9486c4 100644 --- a/commet/lib/ui/pages/matrix/room_address_settings/matrix_room_address_settings_view.dart +++ b/commet/lib/ui/pages/matrix/room_address_settings/matrix_room_address_settings_view.dart @@ -44,19 +44,11 @@ class MatrixRoomAddressSettingsView extends StatefulWidget { class _MatrixRoomAddressSettingsViewState extends State { - int? mainAliasIndex; String? errorMessage; StreamSubscription? subscription; - GlobalKey stateKey = GlobalKey(); @override void initState() { - if (widget.mainAlias != null) { - mainAliasIndex = widget.knownAliases.indexOf(widget.mainAlias!); - if (mainAliasIndex == -1) { - mainAliasIndex = null; - } - } subscription = widget.mainAliasChangedStream.listen(onMainAliasChanged); super.initState(); @@ -69,9 +61,7 @@ class _MatrixRoomAddressSettingsViewState } void onMainAliasChanged(String? value) { - stateKey.currentState?.setState(() { - stateKey.currentState?.value = value; - }); + setState(() {}); } @override @@ -219,21 +209,20 @@ class _MatrixRoomAddressSettingsViewState crossAxisAlignment: CrossAxisAlignment.start, children: [ tiamat.DropdownSelector( - key: stateKey, items: widget.knownAliases, - defaultIndex: mainAliasIndex, + value: widget.mainAlias, itemHeight: 60, hint: tiamat.Text.labelLow(widget.canChangeMainAlias ? "Select a main room address" : "This room does not have a set main alias"), - onItemSelected: (item) => widget.setMainAlias(item), + onItemSelected: (item) => widget.setMainAlias(item!), itemBuilder: (item) { return Row( children: [ Flexible( child: Padding( padding: const EdgeInsets.all(8.0), - child: tiamat.Text.label(item), + child: tiamat.Text.label(item!), ), ), if (item == widget.mainAlias) const TinyPill("Main"), diff --git a/commet/lib/ui/pages/settings/categories/account/account_emoji/account_emoji_tab.dart b/commet/lib/ui/pages/settings/categories/account/account_emoji/account_emoji_tab.dart index a8415d591..d9f17f1bf 100644 --- a/commet/lib/ui/pages/settings/categories/account/account_emoji/account_emoji_tab.dart +++ b/commet/lib/ui/pages/settings/categories/account/account_emoji/account_emoji_tab.dart @@ -62,19 +62,14 @@ class _AccountEmojiTabState extends State { // I dont love using a key here, is there a better way to do this? i dont know key: ValueKey("account_emoji_editor_key_${selectedClient!.identifier}"), children: [ - RoomEmojiPackSettingsView(component!.ownedPacks, - createNewPack: createPack, - defaultExpanded: true, - canCreatePack: component!.canCreatePack, - deleteEmoticon: deleteEmoticon, - deletePack: deletePack, - renameEmoticon: renameEmoticon, - onPackCreated: component!.onOwnedPackAdded), + RoomEmojiPackSettingsView( + component: component!, + editable: true, + ), const SizedBox( height: 5, ), - if (component!.globalPacks().isNotEmpty) - AccountEmojiView(component!.globalPacks(), component!.ownedPacks), + if (component!.globalPacks().isNotEmpty) AccountEmojiView(component!), ], ); } @@ -83,11 +78,6 @@ class _AccountEmojiTabState extends State { return component!.createEmoticonPack(name, avatarData); } - Future renameEmoticon( - EmoticonPack pack, Emoticon emoticon, String name) { - return pack.renameEmoticon(emoticon, name); - } - Future deleteEmoticon(EmoticonPack pack, Emoticon emoticon) { return pack.deleteEmoticon(emoticon); } diff --git a/commet/lib/ui/pages/settings/categories/account/account_emoji/account_emoji_view.dart b/commet/lib/ui/pages/settings/categories/account/account_emoji/account_emoji_view.dart index fe4d49479..f02c8c710 100644 --- a/commet/lib/ui/pages/settings/categories/account/account_emoji/account_emoji_view.dart +++ b/commet/lib/ui/pages/settings/categories/account/account_emoji/account_emoji_view.dart @@ -1,27 +1,44 @@ +import 'dart:async'; + import 'package:commet/client/components/emoticon/emoji_pack.dart'; +import 'package:commet/client/components/emoticon/emoticon_component.dart'; import 'package:commet/main.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:tiamat/tiamat.dart' as tiamat; class AccountEmojiView extends StatefulWidget { - const AccountEmojiView(this.globalPacks, this.personalPacks, {super.key}); - final List globalPacks; - final List personalPacks; + const AccountEmojiView(this.component, {super.key}); + final EmoticonComponent component; @override State createState() => _AccountEmojiViewState(); } class _AccountEmojiViewState extends State { + late List globalPacks; + StreamSubscription? sub; + + @override + void initState() { + sub = widget.component.onStateChanged.listen((_) => updateState()); + updateState(); + super.initState(); + } + + void updateState() { + setState(() { + globalPacks = widget.component.globalPacks(); + }); + } + @override Widget build(BuildContext context) { return Column( children: [ tiamat.Panel( - header: "Global Packs", + header: "Favorite Packs", mode: tiamat.TileType.surfaceContainerLow, - child: Column( - children: widget.globalPacks.map((e) => packSummary(e)).toList()), + child: + Column(children: globalPacks.map((e) => packSummary(e)).toList()), ), ], ); @@ -33,25 +50,42 @@ class _AccountEmojiViewState extends State { child: SizedBox( height: 40, child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - if (pack.image != null) - Image( - image: pack.image!, - filterQuality: FilterQuality.medium, - ), - Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 0, 0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - tiamat.Text.labelEmphasised(pack.displayName), - tiamat.Text.labelLow(preferences.developerMode - ? "${pack.ownerDisplayName} - (${pack.ownerId})" - : pack.ownerDisplayName), - ], - ), + Row( + children: [ + if (pack.image != null) + SizedBox( + width: 40, + height: 40, + child: Image( + image: pack.image!, + filterQuality: FilterQuality.medium, + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 0, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + tiamat.Text.labelEmphasised(pack.displayName), + tiamat.Text.labelLow(preferences.developerMode + ? "${pack.ownerDisplayName} - (${pack.ownerId})" + : pack.ownerDisplayName), + ], + ), + ), + ], ), + SizedBox( + width: 40, + height: 40, + child: tiamat.IconButton( + icon: Icons.heart_broken, + onPressed: () => pack.markAsGlobal(false), + ), + ) ], ), ), diff --git a/commet/lib/ui/pages/settings/categories/room/emoji_packs/room_emoji_pack_settings_page.dart b/commet/lib/ui/pages/settings/categories/room/emoji_packs/room_emoji_pack_settings_page.dart index cc645bdef..48fbc50c8 100644 --- a/commet/lib/ui/pages/settings/categories/room/emoji_packs/room_emoji_pack_settings_page.dart +++ b/commet/lib/ui/pages/settings/categories/room/emoji_packs/room_emoji_pack_settings_page.dart @@ -1,10 +1,6 @@ -import 'dart:typed_data'; - import 'package:commet/client/client.dart'; import 'package:commet/client/components/emoticon/emoticon_component.dart'; import 'package:commet/ui/pages/settings/categories/room/emoji_packs/room_emoji_pack_settings_view.dart'; -import 'package:commet/client/components/emoticon/emoji_pack.dart'; -import 'package:commet/client/components/emoticon/emoticon.dart'; import 'package:flutter/widgets.dart'; class RoomEmojiPackSettingsPage extends StatefulWidget { @@ -28,37 +24,8 @@ class _RoomEmojiPackSettingsPageState extends State { @override Widget build(BuildContext context) { return RoomEmojiPackSettingsView( - component.ownedPacks, - createNewPack: createNewPack, - onPackCreated: component.onOwnedPackAdded, - deletePack: deletePack, - deleteEmoticon: deleteEmoticon, - canCreatePack: component.canCreatePack, - renameEmoticon: renameEmoticon, + component: component, editable: widget.room.permissions.canEditRoomEmoticons, - importPack: importPack, ); } - - Future createNewPack(String name, Uint8List? avatarData) async { - await component.createEmoticonPack(name, avatarData); - } - - Future deletePack(EmoticonPack pack) async { - await component.deleteEmoticonPack(pack); - } - - Future deleteEmoticon(EmoticonPack pack, Emoticon emoticon) async { - await pack.deleteEmoticon(emoticon); - } - - Future renameEmoticon( - EmoticonPack pack, Emoticon emoticon, String name) async { - await pack.renameEmoticon(emoticon, name); - } - - Future importPack(String name, int avatarIndex, List names, - List imageDatas) async { - component.importEmoticonPack(name, avatarIndex, names, imageDatas); - } } diff --git a/commet/lib/ui/pages/settings/categories/room/emoji_packs/room_emoji_pack_settings_view.dart b/commet/lib/ui/pages/settings/categories/room/emoji_packs/room_emoji_pack_settings_view.dart index 9923516ed..2071efdbd 100644 --- a/commet/lib/ui/pages/settings/categories/room/emoji_packs/room_emoji_pack_settings_view.dart +++ b/commet/lib/ui/pages/settings/categories/room/emoji_packs/room_emoji_pack_settings_view.dart @@ -1,65 +1,33 @@ import 'dart:async'; import 'dart:typed_data'; -import 'package:commet/ui/atoms/emoji_widget.dart'; -import 'package:commet/ui/molecules/editable_label.dart'; +import 'package:commet/client/components/emoticon/emoji_pack.dart'; +import 'package:commet/client/components/emoticon/emoticon.dart'; +import 'package:commet/client/components/emoticon/emoticon_component.dart'; import 'package:commet/ui/molecules/image_picker.dart'; import 'package:commet/ui/navigation/adaptive_dialog.dart'; import 'package:commet/ui/pages/settings/categories/room/emoji_packs/bulk_import_view.dart'; -import 'package:commet/utils/common_animation.dart'; -import 'package:commet/client/components/emoticon/emoticon.dart'; -import 'package:commet/client/components/emoticon/emoji_pack.dart'; import 'package:commet/utils/common_strings.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; -import 'package:tiamat/atoms/circle_button.dart'; import 'package:tiamat/tiamat.dart' as tiamat; import 'package:path/path.dart' as path; class RoomEmojiPackSettingsView extends StatefulWidget { - final List packs; - final Stream? onPackCreated; - final Future Function(String name, Uint8List? avatarData)? - createNewPack; - final Future Function(EmoticonPack pack)? deletePack; - final Future Function(EmoticonPack pack, Emoticon emoticon)? - deleteEmoticon; - - final Future Function( - EmoticonPack pack, Emoticon emoticon, String name)? renameEmoticon; - - final Function(String name, int avatarIndex, List names, - List imageDatas)? importPack; - + const RoomEmojiPackSettingsView( + {required this.component, this.editable = true, super.key}); + final EmoticonComponent component; final bool editable; - final bool canCreatePack; - final bool defaultExpanded; - final bool showBulkImport; - const RoomEmojiPackSettingsView(this.packs, - {this.createNewPack, - super.key, - this.onPackCreated, - this.deletePack, - this.editable = true, - this.canCreatePack = true, - this.defaultExpanded = false, - this.showBulkImport = true, - this.importPack, - this.renameEmoticon, - this.deleteEmoticon}); - @override State createState() => _RoomEmojiPackSettingsViewState(); } class _RoomEmojiPackSettingsViewState extends State { - int itemCount = 0; - final GlobalKey _listKey = GlobalKey(); - StreamSubscription? onItemAddedSubscription; - String get promptCreateEmoticonPack => Intl.message("Create pack", - name: "promptCreateEmoticonPack", - desc: "Prompt to create a new emoticon pack, for emoji or stickers"); + late List packs; + + StreamSubscription? sub; + bool canCreatePack = false; String get promptImportPack => Intl.message("Import pack", name: "promptImportPack", @@ -67,121 +35,24 @@ class _RoomEmojiPackSettingsViewState extends State { @override void initState() { - itemCount = widget.packs.length; - onItemAddedSubscription = widget.onPackCreated?.listen(onPackAdded); super.initState(); - } - @override - void dispose() { - onItemAddedSubscription?.cancel(); - super.dispose(); + sub = widget.component.onStateChanged.listen((_) => updateState()); + + updateState(); } - void onPackAdded(int index) { + void updateState() { setState(() { - _listKey.currentState?.insertItem(index); + packs = widget.component.ownedPacks; + canCreatePack = widget.component.canCreatePack; }); } @override - Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - AnimatedList( - initialItemCount: itemCount, - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - key: _listKey, - itemBuilder: (context, index, animation) { - return SizeTransition( - sizeFactor: CommonAnimations.easeOut(animation), - child: EmojiPackEditor( - widget.packs[index], - deletePack: () => deletePack(index), - deleteEmoticon: (emoticon) => deleteEmoticon(index, emoticon), - editable: widget.editable, - initiallyExpanded: widget.defaultExpanded, - renameEmoticon: (emoticon, name) => - renameEmoticon(index, emoticon, name), - ), - ); - }, - ), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - if (widget.editable && - widget.canCreatePack && - widget.showBulkImport) - Padding( - padding: const EdgeInsets.all(4.0), - child: Align( - alignment: Alignment.centerRight, - child: CircleButton( - radius: 20, - icon: Icons.auto_awesome_motion, - onPressed: promptBulkImport, - ), - ), - ), - if (widget.editable && widget.canCreatePack) - Padding( - padding: const EdgeInsets.all(4.0), - child: Align( - alignment: Alignment.centerRight, - child: CircleButton( - radius: 20, - icon: Icons.add, - onPressed: promptNewPack, - ), - ), - ), - ], - ) - ], - ); - } - - void deletePack(int index) { - var pack = widget.packs[index]; - widget.deletePack?.call(pack).then((_) { - setState(() { - itemCount--; - _listKey.currentState?.removeItem( - index, - (context, animation) => SizeTransition( - sizeFactor: CommonAnimations.easeOut(animation), - child: EmojiPackEditor( - pack, - ), - )); - }); - }); - } - - Future deleteEmoticon(int index, Emoticon emoticon) async { - var pack = widget.packs[index]; - await widget.deleteEmoticon?.call(pack, emoticon); - } - - Future renameEmoticon(int index, Emoticon emoticon, String name) async { - var pack = widget.packs[index]; - await widget.renameEmoticon?.call(pack, emoticon, name); - } - - void promptNewPack() async { - await AdaptiveDialog.show( - context, - title: promptCreateEmoticonPack, - builder: (context) { - return EmoticonCreator( - pack: true, - create: widget.createNewPack, - ); - }, - ); + void dispose() { + sub?.cancel; + super.dispose(); } void promptBulkImport() async { @@ -191,405 +62,272 @@ class _RoomEmojiPackSettingsViewState extends State { builder: (context) { return EmoticonBulkImportDialog( importPack: (name, avatarIndex, names, imageDatas) { - widget.importPack?.call(name, avatarIndex, names, imageDatas); + widget.component + .importEmoticonPack(name, avatarIndex, names, imageDatas); Navigator.pop(context); }, ); }, ); } -} - -class EmojiPackEditor extends StatefulWidget { - const EmojiPackEditor(this.pack, - {super.key, - this.deletePack, - this.deleteEmoticon, - this.renameEmoticon, - this.initiallyExpanded = false, - this.showDeleteButton = true, - this.editable = false}); - final EmoticonPack pack; - final Function()? deletePack; - final bool editable; - final bool initiallyExpanded; - final bool showDeleteButton; - final Future Function(Emoticon)? deleteEmoticon; - final Future Function(Emoticon, String)? renameEmoticon; - - @override - State createState() => _EmojiPackEditorState(); -} - -class _EmojiPackEditorState extends State { - final GlobalKey _listKey = GlobalKey(); - StreamSubscription? onCreate; - late int _itemCount; - late bool isPackEmoji; - late bool isPackSticker; - late bool isGlobalPack; - - String promptConfirmDeleteEmoticonPack(packName) => Intl.message( - "Are you sure you want to delete the **$packName** pack?", - args: [packName], - name: "promptConfirmDeleteEmoticonPack", - desc: - "Prompt to confirm deletion of an emoticon pack, supports markdown to emphasise the pack name"); - - String get createEmoticonDialogTitle => Intl.message("Create Emote", - name: "createEmoticonDialogTitle", - desc: - "Title of a dialog that pops up when choosing to create a new emoticon"); - - @override - void initState() { - widget.pack.onEmoticonAdded.listen(onEmojiInsert); - _itemCount = widget.pack.emotes.length; - isPackEmoji = widget.pack.isEmojiPack; - isPackSticker = widget.pack.isStickerPack; - isGlobalPack = widget.pack.isGloballyAvailable; - super.initState(); - } - - void onEmojiInsert(int index) { - setState(() { - _itemCount++; - _listKey.currentState?.insertItem(index); - }); - } - - void setIsEmojiPack(bool isEmoji) { - //if (!(isEmoji || isPackSticker)) return; - - setState(() { - isPackEmoji = isEmoji; - }); - - widget.pack.markAsEmoji(isEmoji); - } - - void setIsStickerPack(bool isSticker) { - //if (!(isSticker || isPackEmoji)) return; - - setState(() { - isPackSticker = isSticker; - }); - - widget.pack.markAsSticker(isSticker); - } - - void setIsGlobal(bool isGlobal) { - setState(() { - isGlobalPack = isGlobal; - }); - - widget.pack.markAsGlobal(isGlobal); - } - - void deleteEmoji(int index) { - var emoji = widget.pack.emotes[index]; - widget.deleteEmoticon?.call(emoji).then((value) { - setState(() { - _itemCount--; - _listKey.currentState?.removeItem( - index, - (context, animation) => SizeTransition( - sizeFactor: CommonAnimations.easeOut(animation), - child: Padding( - padding: const EdgeInsets.fromLTRB(8, 2, 0, 2), - child: EmojiEditor( - emoji, - editable: widget.editable, - ), - ), - )); - }); - }); - } - - void renameEmoji(int index, String name) { - var emoji = widget.pack.emotes[index]; - widget.renameEmoticon?.call(emoji, name); - } @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(2.0), - child: ClipRRect( - borderRadius: BorderRadius.circular(10), - child: ExpansionTile( - initiallyExpanded: widget.initiallyExpanded, - backgroundColor: Theme.of(context).colorScheme.surfaceContainerLow, - collapsedBackgroundColor: - Theme.of(context).colorScheme.surfaceContainerLow, - title: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - if (widget.pack.image != null || widget.pack.icon != null) - SizedBox( - width: 30, - height: 30, - child: widget.pack.image != null - ? Image( - image: widget.pack.image!, - filterQuality: FilterQuality.medium, - ) - : Icon( - widget.pack.icon!, - )), - const SizedBox( - width: 10, - ), - tiamat.Text.labelEmphasised(widget.pack.displayName), - ], - ), - Row( - children: [ - SizedBox( - width: 40, - height: 40, - child: tiamat.IconToggle( - icon: Icons.public, - size: 20, - state: isGlobalPack, - onPressed: (newState) => setIsGlobal(newState), - ), - ), - if (widget.editable) - SizedBox( - width: 40, - height: 40, - child: tiamat.IconToggle( - icon: Icons.sticky_note_2_rounded, - size: 20, - state: isPackSticker, - onPressed: (newState) => setIsStickerPack(newState), + return Column( + children: [ + Column( + children: packs + .map( + (e) => Padding( + padding: const EdgeInsets.fromLTRB(0, 2, 0, 2), + child: ExpansionTile( + collapsedBackgroundColor: + Theme.of(context).colorScheme.surfaceContainer, + backgroundColor: + Theme.of(context).colorScheme.surfaceContainer, + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + if (e.image != null) + Padding( + padding: const EdgeInsets.fromLTRB(0, 0, 8, 0), + child: SizedBox( + width: 40, + height: 40, + child: Image(image: e.image!)), + ), + tiamat.Text.label(e.displayName), + ], ), - ), - if (widget.editable) - const SizedBox( - width: 4, - ), - if (widget.editable) - SizedBox( - width: 40, - height: 40, - child: tiamat.IconToggle( - size: 20, - icon: Icons.emoji_emotions, - state: isPackEmoji, - onPressed: (newState) => setIsEmojiPack(newState), + Row( + children: [ + Row( + children: [ + if (widget.editable) + tiamat.IconButton( + size: 20, + icon: Icons.edit, + onPressed: () => AdaptiveDialog.show( + context, + builder: (context) => EmoticonCreator( + pack: e, + createPack: true, + onCreate: (name, usage, + newImageData) async { + await e.updatePack( + name: name, + usage: usage, + imageData: newImageData, + ); + return true; + }, + onDelete: () { + return widget.component + .deleteEmoticonPack(e); + }, + ))), + if (e.isEmojiPack) + Icon( + color: Theme.of(context) + .colorScheme + .secondaryContainer, + size: 20, + Icons.emoji_emotions), + if (e.isStickerPack) + Icon( + color: Theme.of(context) + .colorScheme + .secondaryContainer, + Icons.sticky_note_2_rounded), + tiamat.IconToggle( + icon: Icons.favorite, + size: 17, + state: e.isGloballyAvailable, + onPressed: (newState) async { + await e.markAsGlobal(newState); + updateState(); + }, + ), + ], + ), + ], + ) + ], + ), + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(4, 0, 4, 0), + child: EmoticonPackEditor( + pack: e, + editable: widget.editable, ), ) - ], + ], + ), + ), + ) + .toList(), + ), + if (canCreatePack) + Align( + alignment: Alignment.topRight, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + tiamat.CircleButton( + icon: Icons.auto_awesome_motion, + onPressed: promptBulkImport), + const SizedBox( + width: 10, ), + tiamat.CircleButton( + icon: Icons.add, + onPressed: () => AdaptiveDialog.show(context, + builder: (context) => EmoticonCreator( + createPack: true, + creatingNew: true, + onCreate: (name, usage, newImageData) async { + await widget.component + .createEmoticonPack(name, newImageData); + + return true; + }, + ))), ], ), - children: [ - AnimatedList( - shrinkWrap: true, - key: _listKey, - physics: const NeverScrollableScrollPhysics(), - initialItemCount: _itemCount, - itemBuilder: (context, index, animation) { - return SizeTransition( - sizeFactor: CommonAnimations.easeOut(animation), - child: Padding( - padding: const EdgeInsets.fromLTRB(8, 2, 9, 2), - child: index >= widget.pack.emotes.length - ? Container() - : EmojiEditor(widget.pack.emotes[index], - deleteEmoji: () => deleteEmoji(index), - editable: widget.editable, - setIsEmoji: (value) => widget.pack - .markEmoticonAsEmoji( - widget.pack.emotes[index], value), - setIsSticker: (value) => widget.pack - .markEmoticonAsSticker( - widget.pack.emotes[index], value), - renameEmoji: (name) => renameEmoji(index, name)), - ), - ); - }, - ), - if (widget.editable) - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (widget.showDeleteButton) - tiamat.Button.danger( - text: CommonStrings.promptDelete, - onTap: () async { - var result = await AdaptiveDialog.confirmation( - context, - dangerous: true, - prompt: promptConfirmDeleteEmoticonPack( - widget.pack.displayName)); - - if (result == true) widget.deletePack?.call(); - }), - //Just putting a widget here to make the circle button stay on the right - if (!widget.showDeleteButton) const SizedBox(), - CircleButton( - radius: 20, - icon: Icons.add, - onPressed: createEmoticon, - ), - ], - ), - ) - ]), - ), + ) + ], ); } - - void createEmoticon() async { - await AdaptiveDialog.show(context, - builder: (context) => EmoticonCreator( - emoji: true, - create: (name, data) async { - await widget.pack - .addEmoticon(slug: name, shortcode: name, data: data!); - }, - ), - title: createEmoticonDialogTitle); - } } -class EmojiEditor extends StatefulWidget { - final Emoticon emoji; - const EmojiEditor(this.emoji, - {super.key, - this.deleteEmoji, - this.editable = false, - this.renameEmoji, - this.setIsEmoji, - this.setIsSticker}); - final void Function()? deleteEmoji; - final void Function(String)? renameEmoji; +class EmoticonPackEditor extends StatelessWidget { + const EmoticonPackEditor( + {required this.pack, this.editable = false, super.key}); + final EmoticonPack pack; final bool editable; - final void Function(bool)? setIsEmoji; - final void Function(bool)? setIsSticker; - - @override - State createState() => _EmojiEditorState(); -} -class _EmojiEditorState extends State { - late bool isSticker; - late bool isEmoji; - - String promptConfirmDeleteEmoticon(emoticon) => Intl.message( - "Are you sure you want to delete **$emoticon**?", - args: [emoticon], - name: "promptConfirmDeleteEmoticon", + String get createEmoticonDialogTitle => Intl.message("Create Emote", + name: "createEmoticonDialogTitle", desc: - "Prompt to confirm deletion of an emoticon pack, supports markdown to emphasise the emote name"); - - String get promptRenameEmoticon => Intl.message("Rename emote", - name: "promptRenameEmoticon", - desc: "Tooltip for button to rename emoticon"); - - @override - void initState() { - isSticker = widget.emoji.isMarkedSticker; - isEmoji = widget.emoji.isMarkedEmoji; - super.initState(); - } - - void setSticker(bool newValue) { - setState(() { - isSticker = newValue; - }); - - widget.setIsSticker?.call(newValue); - } - - void setEmoji(bool newValue) { - setState(() { - isEmoji = newValue; - }); + "Title of a dialog that pops up when choosing to create a new emoticon"); - widget.setIsEmoji?.call(newValue); - } + String get editEmoticonDialogTitle => Intl.message("Edit Emote", + name: "editEmoticonDialogTitle", + desc: + "Title of a dialog that pops up when choosing to edit an existing emoticon"); @override Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + return Column( children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - if (widget.editable) - tiamat.IconButton( - icon: Icons.remove_circle_outline, - size: 20, - onPressed: () async { - var result = await AdaptiveDialog.confirmation(context, - prompt: - promptConfirmDeleteEmoticon(widget.emoji.shortcode!), - dangerous: true); - - if (result == true) { - widget.deleteEmoji?.call(); - } - }, - ), - SizedBox( - height: 50, - width: 50, - child: EmojiWidget(widget.emoji), - ), - const SizedBox( - width: 10, - ), - widget.editable - ? EditableLabel( - initialText: widget.emoji.shortcode!, - changeTooltip: promptRenameEmoticon, - onTextConfirmed: (newText) => - widget.renameEmoji?.call(newText!), - ) - : tiamat.Text.label(widget.emoji.shortcode!), - ], + Column( + children: pack.emotes + .map((e) => Padding( + padding: const EdgeInsets.fromLTRB(0, 2, 0, 2), + child: ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Material( + child: InkWell( + onTap: !editable + ? null + : () => AdaptiveDialog.show(context, + title: editEmoticonDialogTitle, + builder: (context) => EmoticonCreator( + pack: pack, + initialEmoticon: e, + onCreate: + (name, usage, newImageData) async { + await pack.updateEmoticon( + previous: e, + shortcode: name, + usage: usage, + data: newImageData, + ); + return true; + }, + onDelete: () async { + await pack.deleteEmoticon(e); + }, + ), + dismissible: true), + child: Padding( + padding: const EdgeInsets.all(4), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + SizedBox( + width: 40, + height: 40, + child: Image(image: e.image!)), + const SizedBox( + width: 10, + ), + tiamat.Text.label(e.shortcode!) + ], + ), + Row( + children: [ + if (e.isEmoji) + Icon( + color: Theme.of(context) + .colorScheme + .secondaryContainer, + size: 20, + Icons.emoji_emotions), + if (e.isSticker) + Icon( + color: Theme.of(context) + .colorScheme + .secondaryContainer, + Icons.sticky_note_2_rounded), + if (e.usage == EmoticonUsage.inherit) + Icon( + color: Theme.of(context) + .colorScheme + .secondaryContainer, + Icons.arrow_downward) + ], + ) + ], + ), + ), + ), + ), + ), + )) + .toList(), ), - if (widget.editable) - Row( - children: [ - SizedBox( - width: 40, - height: 40, - child: tiamat.IconToggle( - icon: Icons.sticky_note_2_rounded, - size: 20, - state: isSticker, - onPressed: setSticker, - ), - ), - const SizedBox( - width: 4, - ), - SizedBox( - width: 40, - height: 40, - child: tiamat.IconToggle( - size: 20, - icon: Icons.emoji_emotions, - state: isEmoji, - onPressed: setEmoji, - ), + if (editable) + Align( + alignment: Alignment.topRight, + child: Padding( + padding: const EdgeInsets.fromLTRB(8, 8, 0, 8), + child: tiamat.CircleButton( + icon: Icons.add, + onPressed: () => AdaptiveDialog.show(context, + title: createEmoticonDialogTitle, + builder: (context) => EmoticonCreator( + pack: pack, + creatingNew: true, + onCreate: (name, usage, newImageData) async { + await pack.addEmoticon( + slug: name, + shortcode: name, + data: newImageData!, + usage: usage); + + return true; + }, + ), + dismissible: true), ), - const SizedBox(width: 43) - ], - ), + ), + ) ], ); } @@ -597,19 +335,33 @@ class _EmojiEditorState extends State { class EmoticonCreator extends StatefulWidget { const EmoticonCreator( - {super.key, this.create, this.emoji, this.pack, this.sticker}); - final bool? pack; - final bool? sticker; - final bool? emoji; - final Future Function(String name, Uint8List? data)? create; + {this.initialEmoticon, + this.pack, + this.createPack = false, + this.onCreate, + this.onDelete, + this.creatingNew = false, + super.key}); + + final Emoticon? initialEmoticon; + final EmoticonPack? pack; + final bool createPack; + final bool creatingNew; + + final Future Function( + String name, EmoticonUsage usage, Uint8List? newImageData)? onCreate; + + final Future Function()? onDelete; @override State createState() => _EmoticonCreatorState(); } class _EmoticonCreatorState extends State { + late EmoticonUsage usage; + ImageProvider? image; + Uint8List? imageData; - ImageProvider? pickedImage; TextEditingController controller = TextEditingController(); bool loading = false; @@ -617,100 +369,185 @@ class _EmoticonCreatorState extends State { name: "promptEmoticonPackName", desc: "Prompt for the input of the name of an emoticon pack"); - String get promptEmojiName => Intl.message("Emoji name", - name: "promptEmojiName", + String get promptEmoteName => Intl.message("Emote name", + name: "promptEmoteName", desc: "Prompt for the input of the name of an emoji"); - String get promptStickerName => Intl.message("Sticker name", - name: "promptStickerName", desc: "Prompt for the input of a sticker"); - - String get promptConfirmCreateEmoticon => Intl.message("Create!", - name: "promptConfirmCreateEmoticon", + String get promptConfirmSaveEmoticon => Intl.message("Save!", + name: "promptConfirmSaveEmoticon", desc: "Prompt to confirm the creation of an Emoticon Pack, Emoji, or Sticker"); + @override + void initState() { + super.initState(); + + if (widget.createPack) { + if (widget.pack != null) { + usage = widget.pack!.usage; + controller.text = widget.pack!.displayName; + image = widget.pack!.image; + } else { + usage = EmoticonUsage.all; + } + } else { + if (widget.initialEmoticon != null) { + usage = widget.initialEmoticon!.usage; + } else { + usage = EmoticonUsage.inherit; + } + controller.text = widget.initialEmoticon?.shortcode ?? ""; + image = widget.initialEmoticon?.image; + } + } + @override Widget build(BuildContext context) { - if (loading) - return const Column( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox(width: 50, height: 50, child: CircularProgressIndicator()), - ], - ); - - return Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox( - width: 64, - height: 64, - child: ImagePicker( - size: 64, - icon: Icons.add_a_photo, - withData: true, - currentImage: pickedImage, - onImageRead: (bytes, mimeType, filepath) { - imageData = bytes; - var name = path.basename(filepath).split('.').first; - if (controller.text.isEmpty && !(widget.pack == true)) { - controller.text = name; - } - - pickedImage = Image.memory(bytes).image; - }, - ), - ), - Expanded( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: ConstrainedBox( - constraints: const BoxConstraints(minWidth: 300), - child: tiamat.TextInput( - placeholder: widget.pack == true - ? promptEmoticonPackName - : widget.emoji == true - ? promptEmojiName - : promptStickerName, - controller: controller, + return Stack( + alignment: Alignment.center, + children: [ + AnimatedOpacity( + opacity: loading ? 0.5 : 1, + duration: Durations.short2, + child: IgnorePointer( + ignoring: loading, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: 50, + height: 50, + child: ImagePicker( + size: 50, + icon: Icons.add_a_photo, + withData: true, + currentImage: image, + onImageRead: (bytes, mimeType, filepath) { + imageData = bytes; + var name = path.basename(filepath).split('.').first; + if (controller.text.isEmpty && !widget.createPack) { + controller.text = name; + } + + image = Image.memory(bytes).image; + }, + ), + ), + const SizedBox( + width: 4, + ), + Expanded( + child: ConstrainedBox( + constraints: const BoxConstraints(minWidth: 300), + child: tiamat.TextInput( + maxLines: 1, + placeholder: widget.createPack + ? promptEmoticonPackName + : promptEmoteName, + controller: controller, + ), + ), + ) + ], + ), + const SizedBox( + height: 4, + ), + SizedBox( + height: 40, + width: 40, + child: tiamat.DropdownSelector( + itemHeight: 40, + items: [ + EmoticonUsage.emoji, + EmoticonUsage.sticker, + EmoticonUsage.all, + if (!widget.createPack) EmoticonUsage.inherit, + ], + value: usage, + onItemSelected: (item) { + setState(() { + usage = item; + }); + }, + itemBuilder: (item) { + return Row( + children: [ + Icon(switch (item) { + EmoticonUsage.sticker => Icons.sticky_note_2, + EmoticonUsage.emoji => Icons.emoji_emotions, + EmoticonUsage.inherit => Icons.arrow_downward, + EmoticonUsage.all => Icons.star + }), + const SizedBox( + width: 8, + ), + tiamat.Text.label(switch (item) { + EmoticonUsage.sticker => "Sticker", + EmoticonUsage.emoji => "Emoji", + EmoticonUsage.inherit => "Follow Pack Settings", + EmoticonUsage.all => "Emoji & Sticker", + }) + ], + ); + }, ), ), - ), - ) - ], - ), - Padding( - padding: const EdgeInsets.fromLTRB(0, 8, 0, 8), - child: SizedBox( - height: 48, - child: tiamat.Button( - text: promptConfirmCreateEmoticon, - onTap: () { - if (controller.text.isNotEmpty) { - setState(() { - loading = true; - }); - - if (widget.create != null) { - widget.create! - .call(controller.text, imageData) - .then((value) { - Navigator.pop(context); - }); - } - } - }, + const SizedBox( + height: 4, + ), + SizedBox( + height: 48, + child: tiamat.Button( + text: promptConfirmSaveEmoticon, + onTap: () async { + if (controller.text.isNotEmpty) { + setState(() { + loading = true; + }); + + await widget.onCreate + ?.call(controller.text, usage, imageData); + + if (context.mounted) Navigator.of(context).pop(); + } + }, + ), + ), + const SizedBox( + height: 4, + ), + if (!widget.creatingNew) + tiamat.Button.danger( + text: CommonStrings.promptDelete, + onTap: () async { + final confirm = + await AdaptiveDialog.confirmation(context); + + if (confirm == true) { + setState(() { + loading = true; + }); + + await widget.onDelete?.call(); + + if (context.mounted) Navigator.of(context).pop(); + } + }, + ) + ], ), ), - ) - ], - ), + ), + ), + if (loading) const Center(child: CircularProgressIndicator()) + ], ); } } diff --git a/commet/lib/ui/pages/settings/categories/space/space_emoji_pack_settings.dart b/commet/lib/ui/pages/settings/categories/space/space_emoji_pack_settings.dart index 2a14e357d..77eacd021 100644 --- a/commet/lib/ui/pages/settings/categories/space/space_emoji_pack_settings.dart +++ b/commet/lib/ui/pages/settings/categories/space/space_emoji_pack_settings.dart @@ -1,10 +1,6 @@ -import 'dart:typed_data'; - import 'package:commet/client/client.dart'; import 'package:commet/client/components/emoticon/emoticon_component.dart'; import 'package:commet/ui/pages/settings/categories/room/emoji_packs/room_emoji_pack_settings_view.dart'; -import 'package:commet/client/components/emoticon/emoji_pack.dart'; -import 'package:commet/client/components/emoticon/emoticon.dart'; import 'package:flutter/widgets.dart'; class SpaceEmojiPackSettings extends StatefulWidget { @@ -27,40 +23,8 @@ class _SpaceEmojiPackSettingsState extends State { @override Widget build(BuildContext context) { return RoomEmojiPackSettingsView( - component.ownedPacks, - createNewPack: createNewPack, - onPackCreated: component.onOwnedPackAdded, - deletePack: deletePack, - deleteEmoticon: deleteEmoticon, - canCreatePack: component.canCreatePack, + component: component, editable: widget.space.permissions.canEditRoomEmoticons, - renameEmoticon: renameEmoticon, - importPack: importPack, - ); - } - - Future createNewPack(String name, Uint8List? avatarData) async { - await component.createEmoticonPack( - name, - avatarData, ); } - - Future deletePack(EmoticonPack pack) async { - await component.deleteEmoticonPack(pack); - } - - Future deleteEmoticon(EmoticonPack pack, Emoticon emoticon) async { - await pack.deleteEmoticon(emoticon); - } - - Future renameEmoticon( - EmoticonPack pack, Emoticon emoticon, String name) async { - await pack.renameEmoticon(emoticon, name); - } - - Future importPack(String name, int avatarIndex, List names, - List imageDatas) async { - component.importEmoticonPack(name, avatarIndex, names, imageDatas); - } } diff --git a/commet/lib/utils/emoji/unicode_emoji.dart b/commet/lib/utils/emoji/unicode_emoji.dart index 49aff1e6d..93fa3aee7 100644 --- a/commet/lib/utils/emoji/unicode_emoji.dart +++ b/commet/lib/utils/emoji/unicode_emoji.dart @@ -91,9 +91,6 @@ class UnicodeEmoticonPack implements EmoticonPack { @override String get identifier => throw UnimplementedError(); - @override - Stream get onEmoticonAdded => throw UnimplementedError(); - @override List get emotes => _emoji!; @@ -149,55 +146,16 @@ class UnicodeEmoticonPack implements EmoticonPack { } } - @override - Future addEmoticon( - {required String slug, - String? shortcode, - required Uint8List data, - String? mimeType, - bool? isEmoji, - bool? isSticker}) { - throw UnimplementedError(); - } - @override Future deleteEmoticon(Emoticon emoticon) { throw UnimplementedError(); } - @override - Future renameEmoticon(Emoticon emoticon, String name) { - throw UnimplementedError(); - } - - @override - Future markEmoticonAsEmoji(Object emoticon, bool isEmoji) { - throw UnimplementedError(); - } - - @override - Future markEmoticonAsSticker(Emoticon emoticon, bool isSticker) { - throw UnimplementedError(); - } - - @override - Future markAsEmoji(bool isEmojiPack) { - throw UnimplementedError(); - } - - @override - Future markAsSticker(bool isStickerPack) { - throw UnimplementedError(); - } - @override Future markAsGlobal(bool isGlobal) { throw UnimplementedError(); } - @override - bool get isGloballyAvailable => true; - @override List getShortcodes() { return emoji.map((e) => e.shortcode!).toList(); @@ -228,6 +186,42 @@ class UnicodeEmoticonPack implements EmoticonPack { @override String get ownerId => ""; + + @override + Future setPackUsage(EmoticonUsage usage) async {} + + @override + EmoticonUsage get usage => EmoticonUsage.emoji; + + @override + Future addEmoticon( + {required String slug, + String? shortcode, + required Uint8List data, + String? mimeType, + EmoticonUsage? usage}) { + throw UnimplementedError(); + } + + @override + Future updateEmoticon( + {String? slug, + String? shortcode, + Uint8List? data, + String? mimeType, + EmoticonUsage? usage, + required Emoticon previous}) { + throw UnimplementedError(); + } + + @override + Future updatePack( + {EmoticonUsage? usage, String? name, Uint8List? imageData}) { + throw UnimplementedError(); + } + + @override + bool get isGloballyAvailable => false; } class UnicodeEmoticon extends Emoticon { @@ -251,12 +245,6 @@ class UnicodeEmoticon extends Emoticon { @override bool get isSticker => false; - @override - bool get isMarkedEmoji => true; - - @override - bool get isMarkedSticker => false; - UnicodeEmoticon(String text, {String? shortcode}) { _shortcode = shortcode; slug = text; @@ -305,4 +293,7 @@ class UnicodeEmoticon extends Emoticon { int get hashCode { return slug.hashCode; } + + @override + EmoticonUsage get usage => EmoticonUsage.emoji; } diff --git a/tiamat/lib/atoms/dropdown_selector.dart b/tiamat/lib/atoms/dropdown_selector.dart index 880c73a2e..5ad71befe 100644 --- a/tiamat/lib/atoms/dropdown_selector.dart +++ b/tiamat/lib/atoms/dropdown_selector.dart @@ -1,145 +1,23 @@ import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:flutter/material.dart'; -import 'package:tiamat/atoms/text.dart'; -import 'package:tiamat/config/config.dart'; -import 'package:widgetbook_annotation/widgetbook_annotation.dart'; -import 'package:tiamat/tiamat.dart' as tiamat; - -@UseCase(name: 'String Selector', type: DropdownSelector) -Widget wbDropdownSelector(BuildContext context) { - return tiamat.Tile.low2( - child: Padding( - padding: EdgeInsets.all(10.0), - child: Center( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Padding( - padding: EdgeInsets.all(8.0), - child: DropdownSelector( - items: ["Alpha", "Bravo", "Charlie", "Delta"], - itemBuilder: (item) { - return Padding( - padding: const EdgeInsets.all(8.0), - child: tiamat.Text(item), - ); - }, - ), - ), - ], - ), - ), - ), - ); -} - -@UseCase(name: 'Multi Line Text', type: DropdownSelector) -Widget wbDropdownSelectorMultiLine(BuildContext context) { - return tiamat.Tile.low2( - child: Padding( - padding: EdgeInsets.all(10.0), - child: Center( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Padding( - padding: EdgeInsets.all(8.0), - child: DropdownSelector( - itemHeight: 80, - items: [loremIpsum, "Bravo", loremIpsum + " ", "Delta"], - itemBuilder: (item) { - return Padding( - padding: const EdgeInsets.all(8.0), - child: tiamat.Text(item), - ); - }, - ), - ), - ], - ), - ), - ), - ); -} - -@UseCase(name: 'Avatar Selector', type: DropdownSelector) -Widget wbDropdownAvatarSelector(BuildContext context) { - return tiamat.Tile( - child: Padding( - padding: EdgeInsets.all(10.0), - child: Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: EdgeInsets.all(8.0), - child: SizedBox( - child: DropdownSelector( - itemHeight: 70, - items: [ - AssetImage( - "assets/images/placeholder/generic/checker_purple.png"), - AssetImage( - "assets/images/placeholder/generic/checker_red.png"), - AssetImage( - "assets/images/placeholder/generic/checker_green.png"), - AssetImage( - "assets/images/placeholder/generic/checker_orange.png") - ], - itemBuilder: (item) { - return Row( - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: tiamat.Avatar.medium(image: item), - ), - tiamat.Text.labelEmphasised("Avatar with text") - ], - ); - }, - ), - ), - ), - ], - ), - ), - ), - ); -} - -class DropdownSelector extends StatefulWidget { +class DropdownSelector extends StatelessWidget { const DropdownSelector( {required this.items, required this.itemBuilder, this.itemHeight = 50, this.onItemSelected, - this.defaultIndex = 0, this.hint, + required this.value, super.key}); + final List items; final Widget Function(T item) itemBuilder; final void Function(T item)? onItemSelected; - final int? defaultIndex; final double itemHeight; final Widget? hint; - @override - State> createState() => DropdownSelectorState(); -} - -class DropdownSelectorState extends State> { - T? value; - - @override - void initState() { - if (widget.defaultIndex != null) { - value = widget.items[widget.defaultIndex!]; - } - super.initState(); - } + final T value; @override Widget build(BuildContext context) { @@ -156,29 +34,26 @@ class DropdownSelectorState extends State> { builder: (BuildContext context, BoxConstraints constraints) { return DropdownButtonHideUnderline( child: DropdownButton2( - menuItemStyleData: MenuItemStyleData(height: widget.itemHeight), + menuItemStyleData: MenuItemStyleData(height: itemHeight), value: value, - hint: widget.hint, + hint: hint, dropdownStyleData: DropdownStyleData( decoration: BoxDecoration( borderRadius: BorderRadius.only( bottomRight: Radius.circular(10), bottomLeft: Radius.circular(10)), color: Theme.of(context).colorScheme.surfaceContainerHigh)), - items: widget.items.map((value) { + items: items.map((value) { return DropdownMenuItem( alignment: Alignment.centerLeft, value: value, child: SizedBox( width: constraints.maxWidth - 60, - child: widget.itemBuilder(value)), + child: itemBuilder(value)), ); }).toList(), onChanged: (newValue) { - setState(() { - value = newValue!; - }); - widget.onItemSelected?.call(newValue!); + onItemSelected?.call(newValue!); }, )); }), diff --git a/tiamat/lib/atoms/icon_toggle.dart b/tiamat/lib/atoms/icon_toggle.dart index a5a5f81ae..93d555aad 100644 --- a/tiamat/lib/atoms/icon_toggle.dart +++ b/tiamat/lib/atoms/icon_toggle.dart @@ -76,8 +76,8 @@ class _IconToggleState extends State { icon: widget.icon, size: widget.size, iconColor: widget.state - ? m.Theme.of(context).colorScheme.onPrimary - : m.Theme.of(context).colorScheme.secondary, + ? m.Theme.of(context).colorScheme.primary + : m.Theme.of(context).colorScheme.onPrimary, onPressed: () => widget.onPressed?.call(!widget.state), backgroundColor: widget.backgroundColor); } diff --git a/tiamat/pubspec.lock b/tiamat/pubspec.lock index 3ebc2d3f2..8b756ad06 100644 --- a/tiamat/pubspec.lock +++ b/tiamat/pubspec.lock @@ -344,18 +344,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -392,18 +392,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: transitive description: @@ -549,10 +549,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" timing: dependency: transitive description: @@ -581,10 +581,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.5" watcher: dependency: transitive description: