import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:file_picker_cross/file_picker_cross.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:matrix/matrix.dart'; import 'package:vrouter/vrouter.dart'; import '../utils/resize_image.dart'; import '../widgets/matrix.dart'; import 'views/settings_emotes_view.dart'; class EmotesSettings extends StatefulWidget { const EmotesSettings({Key key}) : super(key: key); @override EmotesSettingsController createState() => EmotesSettingsController(); } class EmotesSettingsController extends State { String get roomId => VRouter.of(context).pathParameters['roomid']; Room get room => roomId != null ? Matrix.of(context).client.getRoomById(roomId) : null; String get stateKey => VRouter.of(context).pathParameters['state_key']; bool showSave = false; TextEditingController newImageCodeController = TextEditingController(); ValueNotifier newImageController = ValueNotifier(null); ImagePackContent _getPack() { final client = Matrix.of(context).client; final event = (room != null ? room.getState('im.ponies.room_emotes', stateKey ?? '') : client.accountData['im.ponies.user_emotes']) ?? BasicEvent.fromJson({ 'type': 'm.dummy', 'content': {}, }); // make sure we work on a *copy* of the event return BasicEvent.fromJson(event.toJson()).parsedImagePackContent; } ImagePackContent _pack; ImagePackContent get pack { if (_pack != null) { return _pack; } _pack = _getPack(); return _pack; } Future _save(BuildContext context) async { if (readonly) { return; } final client = Matrix.of(context).client; if (room != null) { await showFutureLoadingDialog( context: context, future: () => client.setRoomStateWithKey( room.id, 'im.ponies.room_emotes', stateKey ?? '', pack.toJson()), ); } else { await showFutureLoadingDialog( context: context, future: () => client.setAccountData( client.userID, 'im.ponies.user_emotes', pack.toJson()), ); } } Future setIsGloballyActive(bool active) async { if (room == null) { return; } final client = Matrix.of(context).client; final content = client.accountData['im.ponies.emote_rooms']?.content ?? {}; if (active) { if (content['rooms'] is! Map) { content['rooms'] = {}; } if (content['rooms'][room.id] is! Map) { content['rooms'][room.id] = {}; } if (content['rooms'][room.id][stateKey ?? ''] is! Map) { content['rooms'][room.id][stateKey ?? ''] = {}; } } else if (content['rooms'] is Map && content['rooms'][room.id] is Map) { content['rooms'][room.id].remove(stateKey ?? ''); } // and save await showFutureLoadingDialog( context: context, future: () => client.setAccountData( client.userID, 'im.ponies.emote_rooms', content), ); setState(() => null); } void removeImageAction(String oldImageCode) => setState(() { pack.images.remove(oldImageCode); showSave = true; }); void submitImageAction( String oldImageCode, String imageCode, ImagePackImageContent image, TextEditingController controller, ) { if (pack.images.keys.any((k) => k == imageCode && k != oldImageCode)) { controller.text = oldImageCode; showOkAlertDialog( useRootNavigator: false, context: context, message: L10n.of(context).emoteExists, okLabel: L10n.of(context).ok, ); return; } if (!RegExp(r'^[-\w]+$').hasMatch(imageCode)) { controller.text = oldImageCode; showOkAlertDialog( useRootNavigator: false, context: context, message: L10n.of(context).emoteInvalid, okLabel: L10n.of(context).ok, ); return; } setState(() { pack.images[imageCode] = image; pack.images.remove(oldImageCode); showSave = true; }); } bool isGloballyActive(Client client) => room != null && client.accountData['im.ponies.emote_rooms']?.content is Map && client.accountData['im.ponies.emote_rooms'].content['rooms'] is Map && client.accountData['im.ponies.emote_rooms'].content['rooms'][room.id] is Map && client.accountData['im.ponies.emote_rooms'].content['rooms'][room.id] [stateKey ?? ''] is Map; bool get readonly => room == null ? false : !(room.canSendEvent('im.ponies.room_emotes')); void saveAction() async { await _save(context); setState(() { showSave = false; }); } void addImageAction() async { if (newImageCodeController.text == null || newImageCodeController.text.isEmpty || newImageController.value == null) { await showOkAlertDialog( useRootNavigator: false, context: context, message: L10n.of(context).emoteWarnNeedToPick, okLabel: L10n.of(context).ok, ); return; } final imageCode = newImageCodeController.text; if (pack.images.containsKey(imageCode)) { await showOkAlertDialog( useRootNavigator: false, context: context, message: L10n.of(context).emoteExists, okLabel: L10n.of(context).ok, ); return; } if (!RegExp(r'^[-\w]+$').hasMatch(imageCode)) { await showOkAlertDialog( useRootNavigator: false, context: context, message: L10n.of(context).emoteInvalid, okLabel: L10n.of(context).ok, ); return; } pack.images[imageCode] = newImageController.value; await _save(context); setState(() { newImageCodeController.text = ''; newImageController.value = null; showSave = false; }); } static const maxImageWidth = 1600; void imagePickerAction( ValueNotifier controller) async { final result = await FilePickerCross.importFromStorage(type: FileTypeCross.image); if (result == null) return; var file = MatrixImageFile( bytes: result.toUint8List(), name: result.fileName, ); try { file = await resizeImage(file, max: maxImageWidth); } catch (_) { // do nothing } final uploadResp = await showFutureLoadingDialog( context: context, future: () => Matrix.of(context).client.uploadContent(file.bytes, filename: file.name, contentType: file.mimeType), ); if (uploadResp.error == null) { setState(() { final info = { ...file.info, }; // normalize width / height to 256, required for stickers if (info['w'] is int && info['h'] is int) { final ratio = info['w'] / info['h']; if (info['w'] > info['h']) { info['w'] = 256; info['h'] = (256.0 / ratio).round(); } else { info['h'] = 256; info['w'] = (ratio * 256.0).round(); } } controller.value = ImagePackImageContent.fromJson({ 'url': uploadResp.result.toString(), 'info': info, }); }); } } @override Widget build(BuildContext context) { return EmotesSettingsView(this); } }