mirror of
https://gitlab.com/famedly/fluffychat.git
synced 2024-11-27 14:59:29 +01:00
Merge branch 'main' of gitlab.com:famedly/fluffychat
This commit is contained in:
commit
8d752164a7
@ -2,16 +2,15 @@ import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:file_picker_cross/file_picker_cross.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:vrouter/vrouter.dart';
|
||||
|
||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'views/settings_emotes_view.dart';
|
||||
import '../widgets/matrix.dart';
|
||||
import '../utils/resize_image.dart';
|
||||
|
||||
class EmotesSettings extends StatefulWidget {
|
||||
EmotesSettings({Key key}) : super(key: key);
|
||||
@ -20,24 +19,18 @@ class EmotesSettings extends StatefulWidget {
|
||||
EmotesSettingsController createState() => EmotesSettingsController();
|
||||
}
|
||||
|
||||
class EmoteEntry {
|
||||
String emote;
|
||||
String mxc;
|
||||
EmoteEntry({this.emote, this.mxc});
|
||||
}
|
||||
|
||||
class EmotesSettingsController extends State<EmotesSettings> {
|
||||
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'];
|
||||
|
||||
List<EmoteEntry> emotes;
|
||||
bool showSave = false;
|
||||
TextEditingController newEmoteController = TextEditingController();
|
||||
TextEditingController newMxcController = TextEditingController();
|
||||
TextEditingController newImageCodeController = TextEditingController();
|
||||
ValueNotifier<ImagePackImageContent> newImageController =
|
||||
ValueNotifier<ImagePackImageContent>(null);
|
||||
|
||||
ImagePackContent _getPack(BuildContext context) {
|
||||
ImagePackContent _getPack() {
|
||||
final client = Matrix.of(context).client;
|
||||
final event = (room != null
|
||||
? room.getState('im.ponies.room_emotes', stateKey ?? '')
|
||||
@ -50,33 +43,20 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
||||
return BasicEvent.fromJson(event.toJson()).parsedImagePackContent;
|
||||
}
|
||||
|
||||
ImagePackContent _pack;
|
||||
ImagePackContent get pack {
|
||||
if (_pack != null) {
|
||||
return _pack;
|
||||
}
|
||||
_pack = _getPack();
|
||||
return _pack;
|
||||
}
|
||||
|
||||
Future<void> _save(BuildContext context) async {
|
||||
if (readonly) {
|
||||
return;
|
||||
}
|
||||
final client = Matrix.of(context).client;
|
||||
final pack = _getPack(context);
|
||||
// add / update changed emotes
|
||||
final allowedShortcodes = <String>{};
|
||||
for (final emote in emotes) {
|
||||
allowedShortcodes.add(emote.emote);
|
||||
if (pack.images.containsKey(emote.emote)) {
|
||||
pack.images[emote.emote].url = Uri.parse(emote.mxc);
|
||||
} else {
|
||||
pack.images[emote.emote] =
|
||||
ImagePackImageContent.fromJson(<String, dynamic>{
|
||||
'url': emote.mxc,
|
||||
});
|
||||
}
|
||||
}
|
||||
// remove emotes no more needed
|
||||
// we make the iterator .toList() here so that we don't get into trouble modifying the very
|
||||
// thing we are iterating over
|
||||
for (final shortcode in pack.images.keys.toList()) {
|
||||
if (!allowedShortcodes.contains(shortcode)) {
|
||||
pack.images.remove(shortcode);
|
||||
}
|
||||
}
|
||||
if (room != null) {
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
@ -121,19 +101,19 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
||||
setState(() => null);
|
||||
}
|
||||
|
||||
void removeEmoteAction(EmoteEntry emote) => setState(() {
|
||||
emotes.removeWhere((e) => e.emote == emote.emote);
|
||||
void removeImageAction(String oldImageCode) => setState(() {
|
||||
pack.images.remove(oldImageCode);
|
||||
showSave = true;
|
||||
});
|
||||
|
||||
void submitEmoteAction(
|
||||
String emoteCode,
|
||||
EmoteEntry emote,
|
||||
void submitImageAction(
|
||||
String oldImageCode,
|
||||
String imageCode,
|
||||
ImagePackImageContent image,
|
||||
TextEditingController controller,
|
||||
) {
|
||||
if (emotes.indexWhere((e) => e.emote == emoteCode && e.mxc != emote.mxc) !=
|
||||
-1) {
|
||||
controller.text = emote.emote;
|
||||
if (pack.images.keys.any((k) => k == imageCode && k != oldImageCode)) {
|
||||
controller.text = oldImageCode;
|
||||
showOkAlertDialog(
|
||||
useRootNavigator: false,
|
||||
context: context,
|
||||
@ -142,8 +122,8 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!RegExp(r'^[-\w]+$').hasMatch(emoteCode)) {
|
||||
controller.text = emote.emote;
|
||||
if (!RegExp(r'^[-\w]+$').hasMatch(imageCode)) {
|
||||
controller.text = oldImageCode;
|
||||
showOkAlertDialog(
|
||||
useRootNavigator: false,
|
||||
context: context,
|
||||
@ -153,7 +133,8 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
emote.emote = emoteCode;
|
||||
pack.images[imageCode] = image;
|
||||
pack.images.remove(oldImageCode);
|
||||
showSave = true;
|
||||
});
|
||||
}
|
||||
@ -177,11 +158,10 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
||||
});
|
||||
}
|
||||
|
||||
void addEmoteAction() async {
|
||||
if (newEmoteController.text == null ||
|
||||
newEmoteController.text.isEmpty ||
|
||||
newMxcController.text == null ||
|
||||
newMxcController.text.isEmpty) {
|
||||
void addImageAction() async {
|
||||
if (newImageCodeController.text == null ||
|
||||
newImageCodeController.text.isEmpty ||
|
||||
newImageController.value == null) {
|
||||
await showOkAlertDialog(
|
||||
useRootNavigator: false,
|
||||
context: context,
|
||||
@ -190,9 +170,8 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
||||
);
|
||||
return;
|
||||
}
|
||||
final emoteCode = '${newEmoteController.text}';
|
||||
final mxc = newMxcController.text;
|
||||
if (emotes.indexWhere((e) => e.emote == emoteCode && e.mxc != mxc) != -1) {
|
||||
final imageCode = newImageCodeController.text;
|
||||
if (pack.images.containsKey(imageCode)) {
|
||||
await showOkAlertDialog(
|
||||
useRootNavigator: false,
|
||||
context: context,
|
||||
@ -201,7 +180,7 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!RegExp(r'^[-\w]+$').hasMatch(emoteCode)) {
|
||||
if (!RegExp(r'^[-\w]+$').hasMatch(imageCode)) {
|
||||
await showOkAlertDialog(
|
||||
useRootNavigator: false,
|
||||
context: context,
|
||||
@ -210,41 +189,28 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
||||
);
|
||||
return;
|
||||
}
|
||||
emotes.add(EmoteEntry(emote: emoteCode, mxc: mxc));
|
||||
pack.images[imageCode] = newImageController.value;
|
||||
await _save(context);
|
||||
setState(() {
|
||||
newEmoteController.text = '';
|
||||
newMxcController.text = '';
|
||||
newImageCodeController.text = '';
|
||||
newImageController.value = null;
|
||||
showSave = false;
|
||||
});
|
||||
}
|
||||
|
||||
void emoteImagePickerAction(TextEditingController controller) async {
|
||||
if (kIsWeb) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(L10n.of(context).notSupportedInWeb)));
|
||||
return;
|
||||
}
|
||||
MatrixFile file;
|
||||
if (PlatformInfos.isMobile) {
|
||||
final result = await ImagePicker().getImage(
|
||||
source: ImageSource.gallery,
|
||||
imageQuality: 50,
|
||||
maxWidth: 1600,
|
||||
maxHeight: 1600);
|
||||
if (result == null) return;
|
||||
file = MatrixFile(
|
||||
bytes: await result.readAsBytes(),
|
||||
name: result.path,
|
||||
);
|
||||
} else {
|
||||
final result =
|
||||
await FilePickerCross.importFromStorage(type: FileTypeCross.image);
|
||||
if (result == null) return;
|
||||
file = MatrixFile(
|
||||
bytes: result.toUint8List(),
|
||||
name: result.fileName,
|
||||
);
|
||||
void imagePickerAction(
|
||||
ValueNotifier<ImagePackImageContent> 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: 1600);
|
||||
} catch (_) {
|
||||
// do nothing
|
||||
}
|
||||
final uploadResp = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
@ -253,21 +219,30 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
||||
);
|
||||
if (uploadResp.error == null) {
|
||||
setState(() {
|
||||
controller.text = uploadResp.result;
|
||||
final info = <String, dynamic>{
|
||||
...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(<String, dynamic>{
|
||||
'url': uploadResp.result,
|
||||
'info': info,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (emotes == null) {
|
||||
emotes = <EmoteEntry>[];
|
||||
final pack = _getPack(context);
|
||||
for (final entry in pack.images.entries) {
|
||||
emotes
|
||||
.add(EmoteEntry(emote: entry.key, mxc: entry.value.url.toString()));
|
||||
}
|
||||
}
|
||||
return EmotesSettingsView(this);
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ class EmotesSettingsView extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final client = Matrix.of(context).client;
|
||||
final imageKeys = controller.pack.images.keys.toList();
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: BackButton(),
|
||||
@ -29,200 +30,195 @@ class EmotesSettingsView extends StatelessWidget {
|
||||
)
|
||||
: null,
|
||||
body: MaxWidthBody(
|
||||
child: StreamBuilder(
|
||||
stream: controller.room?.onUpdate?.stream,
|
||||
builder: (context, snapshot) {
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
if (!controller.readonly)
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
if (!controller.readonly)
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
),
|
||||
child: ListTile(
|
||||
leading: Container(
|
||||
width: 180.0,
|
||||
height: 38,
|
||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||
color: Theme.of(context).secondaryHeaderColor,
|
||||
),
|
||||
child: TextField(
|
||||
controller: controller.newImageCodeController,
|
||||
autocorrect: false,
|
||||
minLines: 1,
|
||||
maxLines: 1,
|
||||
decoration: InputDecoration(
|
||||
hintText: L10n.of(context).emoteShortcode,
|
||||
prefixText: ': ',
|
||||
suffixText: ':',
|
||||
prefixStyle: TextStyle(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
suffixStyle: TextStyle(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
border: InputBorder.none,
|
||||
),
|
||||
child: ListTile(
|
||||
leading: Container(
|
||||
width: 180.0,
|
||||
height: 38,
|
||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||
color: Theme.of(context).secondaryHeaderColor,
|
||||
),
|
||||
child: TextField(
|
||||
controller: controller.newEmoteController,
|
||||
autocorrect: false,
|
||||
minLines: 1,
|
||||
maxLines: 1,
|
||||
decoration: InputDecoration(
|
||||
hintText: L10n.of(context).emoteShortcode,
|
||||
prefixText: ': ',
|
||||
suffixText: ':',
|
||||
prefixStyle: TextStyle(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
suffixStyle: TextStyle(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
border: InputBorder.none,
|
||||
),
|
||||
),
|
||||
title: _ImagePicker(
|
||||
controller: controller.newImageController,
|
||||
onPressed: controller.imagePickerAction,
|
||||
),
|
||||
trailing: InkWell(
|
||||
onTap: controller.addImageAction,
|
||||
child: Icon(
|
||||
Icons.add_outlined,
|
||||
color: Colors.green,
|
||||
size: 32.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (controller.room != null)
|
||||
ListTile(
|
||||
title: Text(L10n.of(context).enableEmotesGlobally),
|
||||
trailing: Switch(
|
||||
value: controller.isGloballyActive(client),
|
||||
onChanged: controller.setIsGloballyActive,
|
||||
),
|
||||
),
|
||||
if (!controller.readonly || controller.room != null)
|
||||
Divider(
|
||||
height: 2,
|
||||
thickness: 2,
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
Expanded(
|
||||
child: imageKeys.isEmpty
|
||||
? Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Text(
|
||||
L10n.of(context).noEmotesFound,
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
),
|
||||
)
|
||||
: ListView.separated(
|
||||
separatorBuilder: (BuildContext context, int i) =>
|
||||
Container(),
|
||||
itemCount: imageKeys.length + 1,
|
||||
itemBuilder: (BuildContext context, int i) {
|
||||
if (i >= imageKeys.length) {
|
||||
return Container(height: 70);
|
||||
}
|
||||
final imageCode = imageKeys[i];
|
||||
final image = controller.pack.images[imageCode];
|
||||
final textEditingController = TextEditingController();
|
||||
textEditingController.text = imageCode;
|
||||
final useShortCuts =
|
||||
(PlatformInfos.isWeb || PlatformInfos.isDesktop);
|
||||
return ListTile(
|
||||
leading: Container(
|
||||
width: 180.0,
|
||||
height: 38,
|
||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(10)),
|
||||
color: Theme.of(context).secondaryHeaderColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
title: _EmoteImagePicker(
|
||||
controller: controller.newMxcController,
|
||||
onPressed: controller.emoteImagePickerAction,
|
||||
),
|
||||
trailing: InkWell(
|
||||
onTap: controller.addEmoteAction,
|
||||
child: Icon(
|
||||
Icons.add_outlined,
|
||||
color: Colors.green,
|
||||
size: 32.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (controller.room != null)
|
||||
ListTile(
|
||||
title: Text(L10n.of(context).enableEmotesGlobally),
|
||||
trailing: Switch(
|
||||
value: controller.isGloballyActive(client),
|
||||
onChanged: controller.setIsGloballyActive,
|
||||
),
|
||||
),
|
||||
if (!controller.readonly || controller.room != null)
|
||||
Divider(
|
||||
height: 2,
|
||||
thickness: 2,
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
Expanded(
|
||||
child: controller.emotes.isEmpty
|
||||
? Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Text(
|
||||
L10n.of(context).noEmotesFound,
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
),
|
||||
)
|
||||
: ListView.separated(
|
||||
separatorBuilder: (BuildContext context, int i) =>
|
||||
Container(),
|
||||
itemCount: controller.emotes.length + 1,
|
||||
itemBuilder: (BuildContext context, int i) {
|
||||
if (i >= controller.emotes.length) {
|
||||
return Container(height: 70);
|
||||
}
|
||||
final emote = controller.emotes[i];
|
||||
final textEditingController =
|
||||
TextEditingController();
|
||||
textEditingController.text = emote.emote;
|
||||
final useShortCuts = (PlatformInfos.isWeb ||
|
||||
PlatformInfos.isDesktop);
|
||||
return ListTile(
|
||||
leading: Container(
|
||||
width: 180.0,
|
||||
height: 38,
|
||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(10)),
|
||||
color:
|
||||
Theme.of(context).secondaryHeaderColor,
|
||||
),
|
||||
child: Shortcuts(
|
||||
shortcuts: !useShortCuts
|
||||
? {}
|
||||
: {
|
||||
LogicalKeySet(
|
||||
LogicalKeyboardKey.enter):
|
||||
SubmitLineIntent(),
|
||||
},
|
||||
child: Actions(
|
||||
actions: !useShortCuts
|
||||
? {}
|
||||
: {
|
||||
SubmitLineIntent:
|
||||
CallbackAction(onInvoke: (i) {
|
||||
controller.submitEmoteAction(
|
||||
textEditingController.text,
|
||||
emote,
|
||||
textEditingController,
|
||||
);
|
||||
return null;
|
||||
}),
|
||||
},
|
||||
child: TextField(
|
||||
readOnly: controller.readonly,
|
||||
controller: textEditingController,
|
||||
autocorrect: false,
|
||||
minLines: 1,
|
||||
maxLines: 1,
|
||||
decoration: InputDecoration(
|
||||
hintText:
|
||||
L10n.of(context).emoteShortcode,
|
||||
prefixText: ': ',
|
||||
suffixText: ':',
|
||||
prefixStyle: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
suffixStyle: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
border: InputBorder.none,
|
||||
),
|
||||
onSubmitted: (s) =>
|
||||
controller.submitEmoteAction(
|
||||
s,
|
||||
emote,
|
||||
textEditingController,
|
||||
),
|
||||
),
|
||||
child: Shortcuts(
|
||||
shortcuts: !useShortCuts
|
||||
? {}
|
||||
: {
|
||||
LogicalKeySet(LogicalKeyboardKey.enter):
|
||||
SubmitLineIntent(),
|
||||
},
|
||||
child: Actions(
|
||||
actions: !useShortCuts
|
||||
? {}
|
||||
: {
|
||||
SubmitLineIntent:
|
||||
CallbackAction(onInvoke: (i) {
|
||||
controller.submitImageAction(
|
||||
imageCode,
|
||||
textEditingController.text,
|
||||
image,
|
||||
textEditingController,
|
||||
);
|
||||
return null;
|
||||
}),
|
||||
},
|
||||
child: TextField(
|
||||
readOnly: controller.readonly,
|
||||
controller: textEditingController,
|
||||
autocorrect: false,
|
||||
minLines: 1,
|
||||
maxLines: 1,
|
||||
decoration: InputDecoration(
|
||||
hintText: L10n.of(context).emoteShortcode,
|
||||
prefixText: ': ',
|
||||
suffixText: ':',
|
||||
prefixStyle: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
suffixStyle: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
border: InputBorder.none,
|
||||
),
|
||||
onSubmitted: (s) =>
|
||||
controller.submitImageAction(
|
||||
imageCode,
|
||||
s,
|
||||
image,
|
||||
textEditingController,
|
||||
),
|
||||
),
|
||||
title: _EmoteImage(emote.mxc),
|
||||
trailing: controller.readonly
|
||||
? null
|
||||
: InkWell(
|
||||
onTap: () =>
|
||||
controller.removeEmoteAction(emote),
|
||||
child: Icon(
|
||||
Icons.delete_forever_outlined,
|
||||
color: Colors.red,
|
||||
size: 32.0,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
title: _EmoteImage(image.url),
|
||||
trailing: controller.readonly
|
||||
? null
|
||||
: InkWell(
|
||||
onTap: () =>
|
||||
controller.removeImageAction(imageCode),
|
||||
child: Icon(
|
||||
Icons.delete_forever_outlined,
|
||||
color: Colors.red,
|
||||
size: 32.0,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _EmoteImage extends StatelessWidget {
|
||||
final String mxc;
|
||||
final Uri mxc;
|
||||
_EmoteImage(this.mxc);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final size = 38.0;
|
||||
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||
final url = Uri.parse(mxc)?.getThumbnail(
|
||||
final url = mxc?.getThumbnail(
|
||||
Matrix.of(context).client,
|
||||
width: size * devicePixelRatio,
|
||||
height: size * devicePixelRatio,
|
||||
@ -237,27 +233,27 @@ class _EmoteImage extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _EmoteImagePicker extends StatefulWidget {
|
||||
final TextEditingController controller;
|
||||
class _ImagePicker extends StatefulWidget {
|
||||
final ValueNotifier<ImagePackImageContent> controller;
|
||||
|
||||
final void Function(TextEditingController) onPressed;
|
||||
final void Function(ValueNotifier<ImagePackImageContent>) onPressed;
|
||||
|
||||
_EmoteImagePicker({@required this.controller, @required this.onPressed});
|
||||
_ImagePicker({@required this.controller, @required this.onPressed});
|
||||
|
||||
@override
|
||||
_EmoteImagePickerState createState() => _EmoteImagePickerState();
|
||||
_ImagePickerState createState() => _ImagePickerState();
|
||||
}
|
||||
|
||||
class _EmoteImagePickerState extends State<_EmoteImagePicker> {
|
||||
class _ImagePickerState extends State<_ImagePicker> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.controller.text == null || widget.controller.text.isEmpty) {
|
||||
if (widget.controller.value == null) {
|
||||
return ElevatedButton(
|
||||
onPressed: () => widget.onPressed(widget.controller),
|
||||
child: Text(L10n.of(context).pickImage),
|
||||
);
|
||||
} else {
|
||||
return _EmoteImage(widget.controller.text);
|
||||
return _EmoteImage(widget.controller.value.url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,16 @@ Future<MatrixImageFile> resizeImage(MatrixImageFile file,
|
||||
// freeze up the UI a bit
|
||||
|
||||
// we can't do width / height fetching in a separate isolate, as that may use the UI stuff
|
||||
await native.init();
|
||||
|
||||
// somehow doing native.init twice fixes it for linux desktop?
|
||||
// TODO: once native imaging is on sound null safety the errors are consistent and
|
||||
// then we can properly handle this instead
|
||||
// https://gitlab.com/famedly/company/frontend/libraries/native_imaging/-/issues/5
|
||||
try {
|
||||
await native.init();
|
||||
} catch (_) {
|
||||
await native.init();
|
||||
}
|
||||
|
||||
_IsolateArgs args;
|
||||
try {
|
||||
@ -47,9 +56,14 @@ Future<MatrixImageFile> resizeImage(MatrixImageFile file,
|
||||
mimeType: 'image/jpeg',
|
||||
width: res.width,
|
||||
height: res.height,
|
||||
blurhash: res.blurhash,
|
||||
);
|
||||
// only return the thumbnail if the size actually decreased
|
||||
return thumbnail.size >= file.size ? file : thumbnail;
|
||||
return thumbnail.size >= file.size ||
|
||||
thumbnail.width >= file.width ||
|
||||
thumbnail.height >= file.height
|
||||
? file
|
||||
: thumbnail;
|
||||
}
|
||||
|
||||
class _IsolateArgs {
|
||||
@ -70,7 +84,12 @@ class _IsolateResponse {
|
||||
}
|
||||
|
||||
Future<_IsolateResponse> _isolateFunction(_IsolateArgs args) async {
|
||||
await native.init();
|
||||
// Hack for desktop, see above why
|
||||
try {
|
||||
await native.init();
|
||||
} catch (_) {
|
||||
await native.init();
|
||||
}
|
||||
var nativeImg = native.Image();
|
||||
|
||||
try {
|
||||
|
Loading…
Reference in New Issue
Block a user