feat: Enhance emote experience
This commit is contained in:
parent
c5d84c22ca
commit
9de3a2c7a2
8 changed files with 251 additions and 69 deletions
|
|
@ -237,22 +237,10 @@ class InputBar extends StatelessWidget {
|
|||
}
|
||||
if (insertText.isNotEmpty && startText.isNotEmpty) {
|
||||
controller.text = startText + afterText;
|
||||
if (startText == insertText) {
|
||||
// stupid fix for now
|
||||
FocusScope.of(context).requestFocus(FocusNode());
|
||||
Future.delayed(Duration(milliseconds: 1)).then((res) {
|
||||
focusNode.requestFocus();
|
||||
controller.selection = TextSelection(
|
||||
baseOffset: startText.length,
|
||||
extentOffset: startText.length,
|
||||
);
|
||||
});
|
||||
} else {
|
||||
controller.selection = TextSelection(
|
||||
baseOffset: startText.length,
|
||||
extentOffset: startText.length,
|
||||
);
|
||||
}
|
||||
controller.selection = TextSelection(
|
||||
baseOffset: startText.length,
|
||||
extentOffset: startText.length,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -529,11 +529,21 @@
|
|||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"emotePacks": "Emote packs for room",
|
||||
"@emotePacks": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"emptyChat": "Empty chat",
|
||||
"@emptyChat": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"enableEmotesGlobally": "Enable emote pack globally",
|
||||
"@enableEmotesGlobally": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"enableEncryptionWarning": "You won't be able to disable the encryption anymore. Are you sure?",
|
||||
"@enableEncryptionWarning": {
|
||||
"type": "text",
|
||||
|
|
@ -1708,4 +1718,4 @@
|
|||
"type": "text",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import 'package:matrix_link_text/link_text.dart';
|
|||
import 'package:memoryfilepicker/memoryfilepicker.dart';
|
||||
|
||||
import './settings_emotes.dart';
|
||||
import './settings_multiple_emotes.dart';
|
||||
import '../utils/url_launcher.dart';
|
||||
|
||||
class ChatDetails extends StatefulWidget {
|
||||
|
|
@ -285,13 +286,31 @@ class _ChatDetailsState extends State<ChatDetails> {
|
|||
child: Icon(Icons.insert_emoticon),
|
||||
),
|
||||
title: Text(L10n.of(context).emoteSettings),
|
||||
onTap: () async =>
|
||||
onTap: () async {
|
||||
// okay, we need to test if there are any emote state events other than the default one
|
||||
// if so, we need to be directed to a selection screen for which pack we want to look at
|
||||
// otherwise, we just open the normal one.
|
||||
if ((widget.room.states
|
||||
.states['im.ponies.room_emotes'] ??
|
||||
<String, Event>{})
|
||||
.keys
|
||||
.any((String s) => s.isNotEmpty)) {
|
||||
await Navigator.of(context).push(
|
||||
AppRoute.defaultRoute(
|
||||
context,
|
||||
EmotesSettingsView(room: widget.room),
|
||||
),
|
||||
),
|
||||
AppRoute.defaultRoute(
|
||||
context,
|
||||
MultipleEmotesSettingsView(
|
||||
room: widget.room),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await Navigator.of(context).push(
|
||||
AppRoute.defaultRoute(
|
||||
context,
|
||||
EmotesSettingsView(room: widget.room),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
PopupMenuButton(
|
||||
child: ListTile(
|
||||
|
|
|
|||
|
|
@ -14,23 +14,25 @@ import 'chat_list.dart';
|
|||
|
||||
class EmotesSettingsView extends StatelessWidget {
|
||||
final Room room;
|
||||
final String stateKey;
|
||||
|
||||
EmotesSettingsView({this.room});
|
||||
EmotesSettingsView({this.room, this.stateKey});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AdaptivePageLayout(
|
||||
primaryPage: FocusPage.SECOND,
|
||||
firstScaffold: ChatList(),
|
||||
secondScaffold: EmotesSettings(room: room),
|
||||
secondScaffold: EmotesSettings(room: room, stateKey: stateKey),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EmotesSettings extends StatefulWidget {
|
||||
final Room room;
|
||||
final String stateKey;
|
||||
|
||||
EmotesSettings({this.room});
|
||||
EmotesSettings({this.room, this.stateKey});
|
||||
|
||||
@override
|
||||
_EmotesSettingsState createState() => _EmotesSettingsState();
|
||||
|
|
@ -59,21 +61,42 @@ class _EmotesSettingsState extends State<EmotesSettings> {
|
|||
// be sure to preserve any data not in "short"
|
||||
Map<String, dynamic> content;
|
||||
if (widget.room != null) {
|
||||
content = widget.room.getState('im.ponies.room_emotes')?.content ??
|
||||
content = widget.room
|
||||
.getState('im.ponies.room_emotes', widget.stateKey ?? '')
|
||||
?.content ??
|
||||
<String, dynamic>{};
|
||||
} else {
|
||||
content = client.accountData['im.ponies.user_emotes']?.content ??
|
||||
<String, dynamic>{};
|
||||
}
|
||||
debugPrint(content.toString());
|
||||
content['short'] = <String, String>{};
|
||||
for (final emote in emotes) {
|
||||
content['short'][emote.emote] = emote.mxc;
|
||||
if (!(content['emoticons'] is Map)) {
|
||||
content['emoticons'] = <String, dynamic>{};
|
||||
}
|
||||
// add / update changed emotes
|
||||
final allowedShortcodes = <String>{};
|
||||
for (final emote in emotes) {
|
||||
allowedShortcodes.add(emote.emote);
|
||||
if (!(content['emoticons'][emote.emote] is Map)) {
|
||||
content['emoticons'][emote.emote] = <String, dynamic>{};
|
||||
}
|
||||
content['emoticons'][emote.emote]['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 content['emoticons'].keys.toList()) {
|
||||
if (!allowedShortcodes.contains(shortcode)) {
|
||||
content['emoticons'].remove(shortcode);
|
||||
}
|
||||
}
|
||||
// remove the old "short" key
|
||||
content.remove('short');
|
||||
debugPrint(content.toString());
|
||||
if (widget.room != null) {
|
||||
await SimpleDialogs(context).tryRequestWithLoadingDialog(
|
||||
client.sendState(widget.room.id, 'im.ponies.room_emotes', content),
|
||||
client.sendState(widget.room.id, 'im.ponies.room_emotes', content,
|
||||
widget.stateKey ?? ''),
|
||||
);
|
||||
} else {
|
||||
await SimpleDialogs(context).tryRequestWithLoadingDialog(
|
||||
|
|
@ -82,6 +105,43 @@ class _EmotesSettingsState extends State<EmotesSettings> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _setIsGloballyActive(BuildContext context, bool active) async {
|
||||
if (widget.room == null) {
|
||||
return;
|
||||
}
|
||||
final client = Matrix.of(context).client;
|
||||
final content = client.accountData['im.ponies.emote_rooms']?.content ??
|
||||
<String, dynamic>{};
|
||||
if (active) {
|
||||
if (!(content['rooms'] is Map)) {
|
||||
content['rooms'] = <String, dynamic>{};
|
||||
}
|
||||
if (!(content['rooms'][widget.room.id] is Map)) {
|
||||
content['rooms'][widget.room.id] = <String, dynamic>{};
|
||||
}
|
||||
if (!(content['rooms'][widget.room.id][widget.stateKey ?? ''] is Map)) {
|
||||
content['rooms'][widget.room.id]
|
||||
[widget.stateKey ?? ''] = <String, dynamic>{};
|
||||
}
|
||||
} else if (content['rooms'] is Map &&
|
||||
content['rooms'][widget.room.id] is Map) {
|
||||
content['rooms'][widget.room.id].remove(widget.stateKey ?? '');
|
||||
}
|
||||
// and save
|
||||
await SimpleDialogs(context).tryRequestWithLoadingDialog(
|
||||
client.setAccountData(client.userID, 'im.ponies.emote_rooms', content),
|
||||
);
|
||||
}
|
||||
|
||||
bool isGloballyActive(Client client) =>
|
||||
widget.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']
|
||||
[widget.room.id] is Map &&
|
||||
client.accountData['im.ponies.emote_rooms'].content['rooms']
|
||||
[widget.room.id][widget.stateKey ?? ''] is Map;
|
||||
|
||||
bool get readonly => widget.room == null
|
||||
? false
|
||||
: !(widget.room.canSendEvent('im.ponies.room_emotes'));
|
||||
|
|
@ -93,16 +153,31 @@ class _EmotesSettingsState extends State<EmotesSettings> {
|
|||
emotes = <_EmoteEntry>[];
|
||||
Map<String, dynamic> emoteSource;
|
||||
if (widget.room != null) {
|
||||
emoteSource = widget.room.getState('im.ponies.room_emotes')?.content;
|
||||
emoteSource = widget.room
|
||||
.getState('im.ponies.room_emotes', widget.stateKey ?? '')
|
||||
?.content;
|
||||
} else {
|
||||
emoteSource = client.accountData['im.ponies.user_emotes']?.content;
|
||||
}
|
||||
if (emoteSource != null && emoteSource['short'] is Map) {
|
||||
emoteSource['short'].forEach((key, value) {
|
||||
if (key is String && value is String && value.startsWith('mxc://')) {
|
||||
emotes.add(_EmoteEntry(emote: key, mxc: value));
|
||||
}
|
||||
});
|
||||
if (emoteSource != null) {
|
||||
if (emoteSource['emoticons'] is Map) {
|
||||
emoteSource['emoticons'].forEach((key, value) {
|
||||
if (key is String &&
|
||||
value is Map &&
|
||||
value['url'] is String &&
|
||||
value['url'].startsWith('mxc://')) {
|
||||
emotes.add(_EmoteEntry(emote: key, mxc: value['url']));
|
||||
}
|
||||
});
|
||||
} else if (emoteSource['short'] is Map) {
|
||||
emoteSource['short'].forEach((key, value) {
|
||||
if (key is String &&
|
||||
value is String &&
|
||||
value.startsWith('mxc://')) {
|
||||
emotes.add(_EmoteEntry(emote: key, mxc: value));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return Scaffold(
|
||||
|
|
@ -166,7 +241,6 @@ class _EmotesSettingsState extends State<EmotesSettings> {
|
|||
size: 32.0,
|
||||
),
|
||||
onTap: () async {
|
||||
debugPrint('blah');
|
||||
if (newEmoteController.text == null ||
|
||||
newEmoteController.text.isEmpty ||
|
||||
newMxcController.text == null ||
|
||||
|
|
@ -204,7 +278,19 @@ class _EmotesSettingsState extends State<EmotesSettings> {
|
|||
vertical: 8.0,
|
||||
),
|
||||
),
|
||||
if (!readonly)
|
||||
if (widget.room != null)
|
||||
ListTile(
|
||||
title: Text(L10n.of(context).enableEmotesGlobally),
|
||||
trailing: Switch(
|
||||
value: isGloballyActive(client),
|
||||
activeColor: Theme.of(context).primaryColor,
|
||||
onChanged: (bool newValue) async {
|
||||
await _setIsGloballyActive(context, newValue);
|
||||
setState(() => null);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (!readonly || widget.room != null)
|
||||
Divider(
|
||||
height: 2,
|
||||
thickness: 2,
|
||||
|
|
|
|||
74
lib/views/settings_multiple_emotes.dart
Normal file
74
lib/views/settings_multiple_emotes.dart
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import '../components/adaptive_page_layout.dart';
|
||||
import '../utils/app_route.dart';
|
||||
import 'chat_list.dart';
|
||||
import 'settings_emotes.dart';
|
||||
|
||||
class MultipleEmotesSettingsView extends StatelessWidget {
|
||||
final Room room;
|
||||
|
||||
MultipleEmotesSettingsView({this.room});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AdaptivePageLayout(
|
||||
primaryPage: FocusPage.SECOND,
|
||||
firstScaffold: ChatList(),
|
||||
secondScaffold: MultipleEmotesSettings(room: room),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MultipleEmotesSettings extends StatelessWidget {
|
||||
final Room room;
|
||||
|
||||
MultipleEmotesSettings({this.room});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(L10n.of(context).emotePacks),
|
||||
),
|
||||
body: StreamBuilder(
|
||||
stream: room.onUpdate.stream,
|
||||
builder: (context, snapshot) {
|
||||
final packs =
|
||||
room.states.states['im.ponies.room_emotes'] ?? <String, Event>{};
|
||||
if (!packs.containsKey('')) {
|
||||
packs[''] = null;
|
||||
}
|
||||
final keys = packs.keys.toList();
|
||||
keys.sort();
|
||||
return ListView.separated(
|
||||
separatorBuilder: (BuildContext context, int i) => Container(),
|
||||
itemCount: keys.length,
|
||||
itemBuilder: (BuildContext context, int i) {
|
||||
final event = packs[keys[i]];
|
||||
var packName = keys[i].isNotEmpty ? keys[i] : 'Default Pack';
|
||||
if (event != null && event.content['pack'] is Map) {
|
||||
if (event.content['pack']['displayname'] is String) {
|
||||
packName = event.content['pack']['displayname'];
|
||||
} else if (event.content['pack']['name'] is String) {
|
||||
packName = event.content['pack']['name'];
|
||||
}
|
||||
}
|
||||
return ListTile(
|
||||
title: Text(packName),
|
||||
onTap: () async {
|
||||
await Navigator.of(context).push(
|
||||
AppRoute.defaultRoute(
|
||||
context,
|
||||
EmotesSettingsView(room: room, stateKey: keys[i]),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue