fluffychat merge
This commit is contained in:
commit
31073e2ddc
11 changed files with 508 additions and 467 deletions
2
.github/workflows/check_duplicates.yaml
vendored
2
.github/workflows/check_duplicates.yaml
vendored
|
|
@ -13,7 +13,7 @@
|
|||
# number: ${{ github.event.issue.number }}
|
||||
# GH_TOKEN: ${{ github.token }}
|
||||
# steps:
|
||||
# - uses: actions/checkout@v5
|
||||
# - uses: actions/checkout@v6
|
||||
# - name: Check duplicates
|
||||
# run: |
|
||||
# title=$(printf %q "${{ env.title }}")
|
||||
|
|
|
|||
10
.github/workflows/integrate.yaml
vendored
10
.github/workflows/integrate.yaml
vendored
|
|
@ -8,7 +8,7 @@ jobs:
|
|||
code_tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- run: ./scripts/generate-locale-config.sh
|
||||
- run: git diff --exit-code
|
||||
- run: cat .github/workflows/versions.env >> $GITHUB_ENV
|
||||
|
|
@ -33,7 +33,7 @@ jobs:
|
|||
build_debug_apk:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- run: cat .github/workflows/versions.env >> $GITHUB_ENV
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
|
|
@ -52,7 +52,7 @@ jobs:
|
|||
build_debug_web:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- run: cat .github/workflows/versions.env >> $GITHUB_ENV
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
|
|
@ -71,7 +71,7 @@ jobs:
|
|||
arch: [ x64, arm64 ]
|
||||
runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest'}}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- run: cat .github/workflows/versions.env >> $GITHUB_ENV
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get update && sudo apt-get install git wget curl libcurl4-openssl-dev clang cmake ninja-build pkg-config libgtk-3-dev libblkid-dev liblzma-dev libjsoncpp-dev cmake-data libsecret-1-dev libsecret-1-0 librhash0 libssl-dev libwebkit2gtk-4.1-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev -y
|
||||
|
|
@ -86,7 +86,7 @@ jobs:
|
|||
build_debug_ios:
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- run: cat .github/workflows/versions.env >> $GITHUB_ENV
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
|
|
|
|||
4
.github/workflows/main_deploy.yaml
vendored
4
.github/workflows/main_deploy.yaml
vendored
|
|
@ -15,7 +15,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
environment: staging
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- run: cat .github/workflows/versions.env >> $GITHUB_ENV
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
|
|
@ -80,7 +80,7 @@ jobs:
|
|||
SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT }}
|
||||
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- run: cat .github/workflows/versions.env >> $GITHUB_ENV
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
|
|
|
|||
4
.github/workflows/release.yaml
vendored
4
.github/workflows/release.yaml
vendored
|
|
@ -40,7 +40,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
environment: production
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- run: cat .github/workflows/versions.env >> $GITHUB_ENV
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
|
|
@ -90,7 +90,7 @@ jobs:
|
|||
env:
|
||||
WEB_APP_ENV: ${{ vars.WEB_APP_ENV }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- run: cat .github/workflows/versions.env >> $GITHUB_ENV
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ import 'package:fluffychat/pages/settings_chat/settings_chat.dart';
|
|||
import 'package:fluffychat/pages/settings_emotes/settings_emotes.dart';
|
||||
import 'package:fluffychat/pages/settings_homeserver/settings_homeserver.dart';
|
||||
import 'package:fluffychat/pages/settings_ignore_list/settings_ignore_list.dart';
|
||||
import 'package:fluffychat/pages/settings_multiple_emotes/settings_multiple_emotes.dart';
|
||||
import 'package:fluffychat/pages/settings_notifications/settings_notifications.dart';
|
||||
import 'package:fluffychat/pages/settings_password/settings_password.dart';
|
||||
import 'package:fluffychat/pages/settings_security/settings_security.dart';
|
||||
|
|
@ -293,10 +292,7 @@ abstract class AppRoutes {
|
|||
// Pangea#
|
||||
: ChatList(
|
||||
activeChat: state.pathParameters['roomid'],
|
||||
// #Pangea
|
||||
// activeSpace: state.uri.queryParameters['spaceId'],
|
||||
activeSpace: state.pathParameters['spaceid'],
|
||||
// Pangea#
|
||||
activeSpace: state.uri.queryParameters['spaceId'],
|
||||
),
|
||||
),
|
||||
routes: [
|
||||
|
|
@ -354,18 +350,6 @@ abstract class AppRoutes {
|
|||
redirect: loggedOutRedirect,
|
||||
),
|
||||
// #Pangea
|
||||
// ShellRoute(
|
||||
// pageBuilder: (context, state, child) => defaultPageBuilder(
|
||||
// context,
|
||||
// state,
|
||||
// FluffyThemes.isColumnMode(context)
|
||||
// ? TwoColumnLayout(
|
||||
// mainView: PangeaSideView(path: state.fullPath),
|
||||
// sideView: child,
|
||||
// )
|
||||
// : child,
|
||||
// ),
|
||||
// routes: [
|
||||
GoRoute(
|
||||
path: 'course',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
|
|
@ -671,7 +655,9 @@ abstract class AppRoutes {
|
|||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const EmotesSettings(),
|
||||
EmotesSettings(
|
||||
roomId: state.pathParameters['roomid'],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
@ -990,6 +976,10 @@ abstract class AppRoutes {
|
|||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
// #Pangea
|
||||
// InvitationSelection(
|
||||
// roomId: state.pathParameters['roomid']!,
|
||||
// ),
|
||||
PangeaInvitationSelection(
|
||||
roomId: state.pathParameters['roomid']!,
|
||||
initialFilter: state.uri.queryParameters['filter'] != null
|
||||
|
|
@ -998,6 +988,7 @@ abstract class AppRoutes {
|
|||
)
|
||||
: null,
|
||||
),
|
||||
// Pangea#
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
|
|
@ -1049,42 +1040,20 @@ abstract class AppRoutes {
|
|||
// pageBuilder: (context, state) => defaultPageBuilder(
|
||||
// context,
|
||||
// state,
|
||||
// PangeaInvitationSelection(
|
||||
// InvitationSelection(
|
||||
// roomId: state.pathParameters['roomid']!,
|
||||
// initialFilter:
|
||||
// state.uri.queryParameters['filter'] != null
|
||||
// ? InvitationFilter.fromString(
|
||||
// state.uri.queryParameters['filter']!,
|
||||
// )
|
||||
// : null,
|
||||
// ),
|
||||
// ),
|
||||
// redirect: loggedOutRedirect,
|
||||
// ),
|
||||
// GoRoute(
|
||||
// path: 'multiple_emotes',
|
||||
// pageBuilder: (context, state) => defaultPageBuilder(
|
||||
// context,
|
||||
// state,
|
||||
// const MultipleEmotesSettings(),
|
||||
// ),
|
||||
// redirect: loggedOutRedirect,
|
||||
// ),
|
||||
// GoRoute(
|
||||
// path: 'emotes',
|
||||
// pageBuilder: (context, state) => defaultPageBuilder(
|
||||
// context,
|
||||
// state,
|
||||
// const EmotesSettings(),
|
||||
// ),
|
||||
// redirect: loggedOutRedirect,
|
||||
// ),
|
||||
// GoRoute(
|
||||
// path: 'emotes/:state_key',
|
||||
// pageBuilder: (context, state) => defaultPageBuilder(
|
||||
// context,
|
||||
// state,
|
||||
// const EmotesSettings(),
|
||||
// EmotesSettings(
|
||||
// roomId: state.pathParameters['roomid'],
|
||||
// ),
|
||||
// ),
|
||||
// redirect: loggedOutRedirect,
|
||||
// ),
|
||||
|
|
@ -1218,21 +1187,14 @@ abstract class AppRoutes {
|
|||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'multiple_emotes',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const MultipleEmotesSettings(),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'emotes',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const EmotesSettings(),
|
||||
EmotesSettings(
|
||||
roomId: state.pathParameters[roomKey]!,
|
||||
),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
|
|
@ -1241,7 +1203,9 @@ abstract class AppRoutes {
|
|||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const EmotesSettings(),
|
||||
EmotesSettings(
|
||||
roomId: state.pathParameters[roomKey]!,
|
||||
),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -3453,6 +3453,14 @@
|
|||
},
|
||||
"thread": "Thread",
|
||||
"backToMainChat": "Back to main chat",
|
||||
"saveChanges": "Save changes",
|
||||
"createSticker": "Create sticker or emoji",
|
||||
"useAsSticker": "Use as sticker",
|
||||
"useAsEmoji": "Use as emoji",
|
||||
"stickerPackNameAlreadyExists": "Sticker pack name already exists",
|
||||
"newStickerPack": "New sticker pack",
|
||||
"stickerPackName": "Sticker pack name",
|
||||
"attribution": "Attribution",
|
||||
"ignore": "Block",
|
||||
"ignoredUsers": "Blocked users",
|
||||
"writeAMessageLangCodes": "Type in {l1} or {l2}...",
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ import 'package:archive/archive.dart'
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:http/http.dart' hide Client;
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
|
|
@ -14,36 +12,60 @@ import 'package:fluffychat/utils/client_manager.dart';
|
|||
import 'package:fluffychat/utils/file_selector.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_file_extension.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_text_input_dialog.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import '../../widgets/matrix.dart';
|
||||
import 'import_archive_dialog.dart';
|
||||
import 'settings_emotes_view.dart';
|
||||
|
||||
class EmotesSettings extends StatefulWidget {
|
||||
const EmotesSettings({super.key});
|
||||
final String? roomId;
|
||||
const EmotesSettings({required this.roomId, super.key});
|
||||
|
||||
@override
|
||||
EmotesSettingsController createState() => EmotesSettingsController();
|
||||
}
|
||||
|
||||
class EmotesSettingsController extends State<EmotesSettings> {
|
||||
// #Pangea
|
||||
// String? get roomId => GoRouterState.of(context).pathParameters['roomid'];
|
||||
String? get roomId {
|
||||
final pathParameters = GoRouterState.of(context).pathParameters;
|
||||
return pathParameters['roomid'] ?? pathParameters['spaceid'];
|
||||
late final Room? room;
|
||||
|
||||
String? stateKey;
|
||||
|
||||
List<String>? get packKeys {
|
||||
final room = this.room;
|
||||
if (room == null) return null;
|
||||
final keys = room.states['im.ponies.room_emotes']?.keys.toList() ?? [];
|
||||
keys.sort();
|
||||
return keys;
|
||||
}
|
||||
// Pangea#
|
||||
|
||||
Room? get room =>
|
||||
roomId != null ? Matrix.of(context).client.getRoomById(roomId!) : null;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
room = widget.roomId != null
|
||||
? Matrix.of(context).client.getRoomById(widget.roomId!)
|
||||
: null;
|
||||
setStateKey(packKeys?.firstOrNull, reset: false);
|
||||
}
|
||||
|
||||
String? get stateKey => GoRouterState.of(context).pathParameters['state_key'];
|
||||
void setStateKey(String? key, {reset = true}) {
|
||||
stateKey = key;
|
||||
|
||||
final event = key == null
|
||||
? null
|
||||
: room?.getState(
|
||||
'im.ponies.room_emotes',
|
||||
key,
|
||||
);
|
||||
final eventPack = event?.content.tryGetMap<String, Object?>('pack');
|
||||
packDisplayNameController.text =
|
||||
eventPack?.tryGet<String>('display_name') ?? '';
|
||||
packAttributionController.text =
|
||||
eventPack?.tryGet<String>('attribution') ?? '';
|
||||
if (reset) resetAction();
|
||||
}
|
||||
|
||||
bool showSave = false;
|
||||
TextEditingController newImageCodeController = TextEditingController();
|
||||
ValueNotifier<ImagePackImageContent?> newImageController =
|
||||
ValueNotifier<ImagePackImageContent?>(null);
|
||||
|
||||
ImagePackContent _getPack() {
|
||||
final client = Matrix.of(context).client;
|
||||
|
|
@ -73,25 +95,25 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
|||
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(),
|
||||
),
|
||||
);
|
||||
final result = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => room != null
|
||||
? client.setRoomStateWithKey(
|
||||
room!.id,
|
||||
'im.ponies.room_emotes',
|
||||
stateKey ?? '',
|
||||
pack!.toJson(),
|
||||
)
|
||||
: client.setAccountData(
|
||||
client.userID!,
|
||||
'im.ponies.user_emotes',
|
||||
pack!.toJson(),
|
||||
),
|
||||
);
|
||||
if (!result.isError) {
|
||||
setState(() {
|
||||
showSave = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -127,17 +149,56 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
|||
setState(() {});
|
||||
}
|
||||
|
||||
final TextEditingController packDisplayNameController =
|
||||
TextEditingController();
|
||||
|
||||
final TextEditingController packAttributionController =
|
||||
TextEditingController();
|
||||
|
||||
void removeImageAction(String oldImageCode) => setState(() {
|
||||
pack!.images.remove(oldImageCode);
|
||||
showSave = true;
|
||||
});
|
||||
|
||||
void toggleUsage(String imageCode, ImagePackUsage usage) {
|
||||
setState(() {
|
||||
final usages =
|
||||
pack!.images[imageCode]!.usage ??= List.from(ImagePackUsage.values);
|
||||
if (!usages.remove(usage)) usages.add(usage);
|
||||
showSave = true;
|
||||
});
|
||||
}
|
||||
|
||||
void submitDisplaynameAction() {
|
||||
if (readonly) return;
|
||||
packDisplayNameController.text = packDisplayNameController.text.trim();
|
||||
final input = packDisplayNameController.text;
|
||||
|
||||
setState(() {
|
||||
pack!.pack.displayName = input;
|
||||
showSave = true;
|
||||
});
|
||||
}
|
||||
|
||||
void submitAttributionAction() {
|
||||
if (readonly) return;
|
||||
packAttributionController.text = packAttributionController.text.trim();
|
||||
final input = packAttributionController.text;
|
||||
|
||||
setState(() {
|
||||
pack!.pack.attribution = input;
|
||||
showSave = true;
|
||||
});
|
||||
}
|
||||
|
||||
void submitImageAction(
|
||||
String oldImageCode,
|
||||
String imageCode,
|
||||
ImagePackImageContent image,
|
||||
TextEditingController controller,
|
||||
) {
|
||||
controller.text = controller.text.trim().replaceAll(' ', '-');
|
||||
final imageCode = controller.text;
|
||||
if (imageCode == oldImageCode) return;
|
||||
if (pack!.images.keys.any((k) => k == imageCode && k != oldImageCode)) {
|
||||
controller.text = oldImageCode;
|
||||
showOkAlertDialog(
|
||||
|
|
@ -173,8 +234,60 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
|||
?.tryGetMap<String, Object?>(stateKey ?? '') !=
|
||||
null;
|
||||
|
||||
bool get readonly =>
|
||||
room == null ? false : !(room!.canSendEvent('im.ponies.room_emotes'));
|
||||
bool get readonly => room == null
|
||||
? false
|
||||
: room?.canChangeStateEvent('im.ponies.room_emotes') == false;
|
||||
|
||||
void resetAction() {
|
||||
setState(() {
|
||||
_pack = _getPack();
|
||||
showSave = false;
|
||||
});
|
||||
}
|
||||
|
||||
void createImagePack() async {
|
||||
final room = this.room;
|
||||
if (room == null) throw Exception('Cannot create image pack without room');
|
||||
|
||||
final input = await showTextInputDialog(
|
||||
context: context,
|
||||
title: L10n.of(context).newStickerPack,
|
||||
hintText: L10n.of(context).name,
|
||||
okLabel: L10n.of(context).create,
|
||||
);
|
||||
final name = input?.trim();
|
||||
if (name == null || name.isEmpty) return;
|
||||
if (!mounted) return;
|
||||
|
||||
final keyName = name.toLowerCase().replaceAll(' ', '_');
|
||||
|
||||
if (packKeys?.contains(name) ?? false) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(L10n.of(context).stickerPackNameAlreadyExists),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => room.client.setRoomStateWithKey(
|
||||
room.id,
|
||||
'im.ponies.room_emotes',
|
||||
keyName,
|
||||
{
|
||||
'images': {},
|
||||
'pack': {'display_name': name},
|
||||
},
|
||||
),
|
||||
);
|
||||
if (!mounted) return;
|
||||
setState(() {});
|
||||
await room.client.oneShotSync();
|
||||
if (!mounted) return;
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void saveAction() async {
|
||||
await save(context);
|
||||
|
|
@ -183,95 +296,63 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
|||
});
|
||||
}
|
||||
|
||||
void addImageAction() async {
|
||||
if (newImageCodeController.text.isEmpty ||
|
||||
newImageController.value == null) {
|
||||
await showOkAlertDialog(
|
||||
useRootNavigator: false,
|
||||
context: context,
|
||||
title: 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,
|
||||
title: L10n.of(context).emoteExists,
|
||||
okLabel: L10n.of(context).ok,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!RegExp(r'^[-\w]+$').hasMatch(imageCode)) {
|
||||
await showOkAlertDialog(
|
||||
useRootNavigator: false,
|
||||
context: context,
|
||||
title: 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;
|
||||
});
|
||||
}
|
||||
|
||||
void imagePickerAction(
|
||||
ValueNotifier<ImagePackImageContent?> controller,
|
||||
) async {
|
||||
final result = await selectFiles(
|
||||
void createStickers() async {
|
||||
final pickedFiles = await selectFiles(
|
||||
context,
|
||||
type: FileSelectorType.images,
|
||||
allowMultiple: true,
|
||||
);
|
||||
final pickedFile = result.firstOrNull;
|
||||
if (pickedFile == null) return;
|
||||
var file = MatrixImageFile(
|
||||
bytes: await pickedFile.readAsBytes(),
|
||||
name: pickedFile.name,
|
||||
);
|
||||
try {
|
||||
file = (await file.generateThumbnail(
|
||||
nativeImplementations: ClientManager.nativeImplementations,
|
||||
))!;
|
||||
} catch (e, s) {
|
||||
Logs().w('Unable to create thumbnail', e, s);
|
||||
}
|
||||
final uploadResp = await showFutureLoadingDialog(
|
||||
if (pickedFiles.isEmpty) return;
|
||||
if (!mounted) return;
|
||||
|
||||
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 = <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();
|
||||
}
|
||||
futureWithProgress: (setProgress) async {
|
||||
for (final (i, pickedFile) in pickedFiles.indexed) {
|
||||
setProgress(i / pickedFiles.length);
|
||||
var file = MatrixImageFile(
|
||||
bytes: await pickedFile.readAsBytes(),
|
||||
name: pickedFile.name,
|
||||
);
|
||||
file = await file.generateThumbnail(
|
||||
nativeImplementations: ClientManager.nativeImplementations,
|
||||
) ??
|
||||
file;
|
||||
final uri = await Matrix.of(context).client.uploadContent(
|
||||
file.bytes,
|
||||
filename: file.name,
|
||||
contentType: file.mimeType,
|
||||
);
|
||||
|
||||
setState(() {
|
||||
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();
|
||||
}
|
||||
}
|
||||
final imageCode = pickedFile.name.split('.').first;
|
||||
pack!.images[imageCode] =
|
||||
ImagePackImageContent.fromJson(<String, dynamic>{
|
||||
'url': uri.toString(),
|
||||
'info': info,
|
||||
});
|
||||
});
|
||||
}
|
||||
controller.value = ImagePackImageContent.fromJson(<String, dynamic>{
|
||||
'url': uploadResp.result.toString(),
|
||||
'info': info,
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
setState(() {
|
||||
showSave = true;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import 'package:fluffychat/l10n/l10n.dart';
|
|||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
|
||||
import 'package:fluffychat/widgets/mxc_image.dart';
|
||||
import 'package:fluffychat/widgets/mxc_image_viewer.dart';
|
||||
import '../../widgets/matrix.dart';
|
||||
import 'settings_emotes.dart';
|
||||
|
||||
|
|
@ -19,108 +20,186 @@ class EmotesSettingsView extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (controller.widget.roomId != null && controller.room == null) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(L10n.of(context).oopsSomethingWentWrong),
|
||||
),
|
||||
body: Center(
|
||||
child: Text(L10n.of(context).youAreNoLongerParticipatingInThisChat),
|
||||
),
|
||||
);
|
||||
}
|
||||
final theme = Theme.of(context);
|
||||
|
||||
final client = Matrix.of(context).client;
|
||||
final imageKeys = controller.pack!.images.keys.toList();
|
||||
final packKeys = controller.packKeys;
|
||||
if (packKeys != null && packKeys.isEmpty) {
|
||||
packKeys.add('');
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: const Center(child: BackButton()),
|
||||
title: Text(L10n.of(context).customEmojisAndStickers),
|
||||
automaticallyImplyLeading: !controller.showSave,
|
||||
title: controller.showSave
|
||||
? TextButton(
|
||||
onPressed: controller.resetAction,
|
||||
child: Text(L10n.of(context).cancel),
|
||||
)
|
||||
: Text(L10n.of(context).customEmojisAndStickers),
|
||||
actions: [
|
||||
PopupMenuButton<PopupMenuEmojiActions>(
|
||||
useRootNavigator: true,
|
||||
onSelected: (value) {
|
||||
switch (value) {
|
||||
case PopupMenuEmojiActions.export:
|
||||
controller.exportAsZip();
|
||||
break;
|
||||
case PopupMenuEmojiActions.import:
|
||||
controller.importEmojiZip();
|
||||
break;
|
||||
}
|
||||
},
|
||||
enabled: !controller.readonly,
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
value: PopupMenuEmojiActions.import,
|
||||
child: Text(L10n.of(context).importFromZipFile),
|
||||
if (controller.showSave)
|
||||
ElevatedButton(
|
||||
onPressed: () => controller.save(context),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: theme.colorScheme.primary,
|
||||
foregroundColor: theme.colorScheme.onPrimary,
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: PopupMenuEmojiActions.export,
|
||||
child: Text(L10n.of(context).exportEmotePack),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
floatingActionButton: controller.showSave
|
||||
? FloatingActionButton(
|
||||
onPressed: controller.saveAction,
|
||||
child: const Icon(Icons.save_outlined, color: Colors.white),
|
||||
child: Text(L10n.of(context).saveChanges),
|
||||
)
|
||||
: null,
|
||||
else
|
||||
PopupMenuButton<PopupMenuEmojiActions>(
|
||||
useRootNavigator: true,
|
||||
onSelected: (value) {
|
||||
switch (value) {
|
||||
case PopupMenuEmojiActions.export:
|
||||
controller.exportAsZip();
|
||||
break;
|
||||
case PopupMenuEmojiActions.import:
|
||||
controller.importEmojiZip();
|
||||
break;
|
||||
}
|
||||
},
|
||||
enabled: !controller.readonly,
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
value: PopupMenuEmojiActions.import,
|
||||
child: Text(L10n.of(context).importFromZipFile),
|
||||
),
|
||||
if (imageKeys.isNotEmpty)
|
||||
PopupMenuItem(
|
||||
value: PopupMenuEmojiActions.export,
|
||||
child: Text(L10n.of(context).exportEmotePack),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
bottom: packKeys == null
|
||||
? null
|
||||
: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(48),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: packKeys.length + 1,
|
||||
itemBuilder: (context, i) {
|
||||
if (i == 0) {
|
||||
if (controller.readonly) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4.0,
|
||||
),
|
||||
child: FilterChip(
|
||||
label: const Icon(
|
||||
Icons.add_outlined,
|
||||
size: 20,
|
||||
),
|
||||
onSelected: controller.showSave
|
||||
? null
|
||||
: (_) => controller.createImagePack(),
|
||||
),
|
||||
);
|
||||
}
|
||||
i--;
|
||||
final key = packKeys[i];
|
||||
final event = controller.room
|
||||
?.getState('im.ponies.room_emotes', packKeys[i]);
|
||||
|
||||
final eventPack =
|
||||
event?.content.tryGetMap<String, Object?>('pack');
|
||||
final packName =
|
||||
eventPack?.tryGet<String>('display_name') ??
|
||||
eventPack?.tryGet<String>('name') ??
|
||||
(key.isNotEmpty ? key : 'Default');
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4.0,
|
||||
),
|
||||
child: FilterChip(
|
||||
label: Text(packName),
|
||||
selected: controller.stateKey == key ||
|
||||
(controller.stateKey == null && key.isEmpty),
|
||||
onSelected: controller.showSave
|
||||
? null
|
||||
: (_) => controller.setStateKey(key),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
body: MaxWidthBody(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
if (!controller.readonly)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
),
|
||||
child: ListTile(
|
||||
leading: Container(
|
||||
width: 180.0,
|
||||
height: 38,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(10)),
|
||||
color: theme.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.colorScheme.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
suffixStyle: TextStyle(
|
||||
color: theme.colorScheme.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
border: InputBorder.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
title: _ImagePicker(
|
||||
controller: controller.newImageController,
|
||||
onPressed: controller.imagePickerAction,
|
||||
),
|
||||
trailing: InkWell(
|
||||
onTap: controller.addImageAction,
|
||||
child: const Icon(
|
||||
Icons.add_outlined,
|
||||
color: Colors.green,
|
||||
size: 32.0,
|
||||
),
|
||||
if (controller.room != null) ...[
|
||||
const SizedBox(height: 16),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: TextField(
|
||||
maxLength: 256,
|
||||
controller: controller.packDisplayNameController,
|
||||
readOnly: controller.readonly,
|
||||
onSubmitted: (_) => controller.submitDisplaynameAction(),
|
||||
decoration: InputDecoration(
|
||||
counter: const SizedBox.shrink(),
|
||||
hintText: controller.stateKey,
|
||||
labelText: L10n.of(context).stickerPackName,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (controller.room != null)
|
||||
const SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: TextField(
|
||||
maxLength: 256,
|
||||
controller: controller.packAttributionController,
|
||||
readOnly: controller.readonly,
|
||||
onSubmitted: (_) => controller.submitAttributionAction(),
|
||||
decoration: InputDecoration(
|
||||
counter: const SizedBox.shrink(),
|
||||
labelText: L10n.of(context).attribution,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (!controller.readonly) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: controller.createStickers,
|
||||
icon: const Icon(Icons.upload_outlined),
|
||||
label: Text(L10n.of(context).createSticker),
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
],
|
||||
if (controller.room != null && imageKeys.isNotEmpty)
|
||||
SwitchListTile.adaptive(
|
||||
title: Text(L10n.of(context).enableEmotesGlobally),
|
||||
value: controller.isGloballyActive(client),
|
||||
onChanged: controller.setIsGloballyActive,
|
||||
),
|
||||
if (!controller.readonly || controller.room != null)
|
||||
const Divider(),
|
||||
imageKeys.isEmpty
|
||||
? Center(
|
||||
child: Padding(
|
||||
|
|
@ -136,11 +215,8 @@ class EmotesSettingsView extends StatelessWidget {
|
|||
physics: const NeverScrollableScrollPhysics(),
|
||||
separatorBuilder: (BuildContext context, int i) =>
|
||||
const SizedBox.shrink(),
|
||||
itemCount: imageKeys.length + 1,
|
||||
itemCount: imageKeys.length,
|
||||
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();
|
||||
|
|
@ -148,80 +224,110 @@ class EmotesSettingsView extends StatelessWidget {
|
|||
final useShortCuts =
|
||||
(PlatformInfos.isWeb || PlatformInfos.isDesktop);
|
||||
return ListTile(
|
||||
leading: Container(
|
||||
width: 180.0,
|
||||
height: 38,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(10)),
|
||||
color: theme.secondaryHeaderColor,
|
||||
),
|
||||
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;
|
||||
title: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Shortcuts(
|
||||
shortcuts: !useShortCuts
|
||||
? {}
|
||||
: {
|
||||
LogicalKeySet(LogicalKeyboardKey.enter):
|
||||
SubmitLineIntent(),
|
||||
},
|
||||
child: Actions(
|
||||
actions: !useShortCuts
|
||||
? {}
|
||||
: {
|
||||
SubmitLineIntent: CallbackAction(
|
||||
onInvoke: (i) {
|
||||
controller.submitImageAction(
|
||||
imageCode,
|
||||
image,
|
||||
textEditingController,
|
||||
);
|
||||
return null;
|
||||
},
|
||||
),
|
||||
},
|
||||
child: TextField(
|
||||
readOnly: controller.readonly,
|
||||
controller: textEditingController,
|
||||
autocorrect: false,
|
||||
minLines: 1,
|
||||
maxLines: 1,
|
||||
maxLength: 128,
|
||||
decoration: InputDecoration(
|
||||
hintText: L10n.of(context).emoteShortcode,
|
||||
prefixText: ': ',
|
||||
suffixText: ':',
|
||||
counter: const SizedBox.shrink(),
|
||||
filled: false,
|
||||
enabledBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
),
|
||||
},
|
||||
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.colorScheme.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
onSubmitted: (s) =>
|
||||
controller.submitImageAction(
|
||||
imageCode,
|
||||
image,
|
||||
textEditingController,
|
||||
),
|
||||
),
|
||||
suffixStyle: TextStyle(
|
||||
color: theme.colorScheme.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
border: InputBorder.none,
|
||||
),
|
||||
onSubmitted: (s) =>
|
||||
controller.submitImageAction(
|
||||
imageCode,
|
||||
s,
|
||||
image,
|
||||
textEditingController,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!controller.readonly)
|
||||
PopupMenuButton<ImagePackUsage>(
|
||||
onSelected: (usage) => controller.toggleUsage(
|
||||
imageCode,
|
||||
usage,
|
||||
),
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
value: ImagePackUsage.sticker,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (image.usage?.contains(
|
||||
ImagePackUsage.sticker,
|
||||
) ??
|
||||
true)
|
||||
const Icon(Icons.check_outlined),
|
||||
const SizedBox(width: 12),
|
||||
Text(L10n.of(context).useAsSticker),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: ImagePackUsage.emoticon,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (image.usage?.contains(
|
||||
ImagePackUsage.emoticon,
|
||||
) ??
|
||||
true)
|
||||
const Icon(Icons.check_outlined),
|
||||
const SizedBox(width: 12),
|
||||
Text(L10n.of(context).useAsEmoji),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
icon: const Icon(Icons.edit_outlined),
|
||||
),
|
||||
],
|
||||
),
|
||||
title: _EmoteImage(image.url),
|
||||
leading: _EmoteImage(image.url),
|
||||
trailing: controller.readonly
|
||||
? null
|
||||
: InkWell(
|
||||
onTap: () =>
|
||||
: IconButton(
|
||||
tooltip: L10n.of(context).delete,
|
||||
onPressed: () =>
|
||||
controller.removeImageAction(imageCode),
|
||||
child: const Icon(
|
||||
Icons.delete_outlined,
|
||||
color: Colors.red,
|
||||
size: 32.0,
|
||||
),
|
||||
icon: const Icon(Icons.delete_outlined),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
@ -240,43 +346,26 @@ class _EmoteImage extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const size = 38.0;
|
||||
return SizedBox.square(
|
||||
dimension: size,
|
||||
const size = 44.0;
|
||||
final key = 'sticker_preview_$mxc';
|
||||
return InkWell(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
onTap: () => showDialog(
|
||||
context: context,
|
||||
builder: (_) => MxcImageViewer(mxc),
|
||||
),
|
||||
child: MxcImage(
|
||||
key: ValueKey(key),
|
||||
cacheKey: key,
|
||||
uri: mxc,
|
||||
fit: BoxFit.contain,
|
||||
width: size,
|
||||
height: size,
|
||||
isThumbnail: false,
|
||||
isThumbnail: true,
|
||||
animated: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ImagePicker extends StatefulWidget {
|
||||
final ValueNotifier<ImagePackImageContent?> controller;
|
||||
|
||||
final void Function(ValueNotifier<ImagePackImageContent?>) onPressed;
|
||||
|
||||
const _ImagePicker({required this.controller, required this.onPressed});
|
||||
|
||||
@override
|
||||
_ImagePickerState createState() => _ImagePickerState();
|
||||
}
|
||||
|
||||
class _ImagePickerState extends State<_ImagePicker> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.controller.value == null) {
|
||||
return ElevatedButton(
|
||||
onPressed: () => widget.onPressed(widget.controller),
|
||||
child: Text(L10n.of(context).pickImage),
|
||||
);
|
||||
} else {
|
||||
return _EmoteImage(widget.controller.value!.url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SubmitLineIntent extends Intent {}
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'settings_multiple_emotes_view.dart';
|
||||
|
||||
class MultipleEmotesSettings extends StatefulWidget {
|
||||
const MultipleEmotesSettings({super.key});
|
||||
|
||||
@override
|
||||
MultipleEmotesSettingsController createState() =>
|
||||
MultipleEmotesSettingsController();
|
||||
}
|
||||
|
||||
class MultipleEmotesSettingsController extends State<MultipleEmotesSettings> {
|
||||
// #Pangea
|
||||
// String? get roomId => GoRouterState.of(context).pathParameters['roomid'];
|
||||
String? get roomId {
|
||||
final pathParameters = GoRouterState.of(context).pathParameters;
|
||||
return pathParameters['roomid'] ?? pathParameters['spaceid'];
|
||||
}
|
||||
// Pangea#
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => MultipleEmotesSettingsView(this);
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pages/settings_multiple_emotes/settings_multiple_emotes.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class MultipleEmotesSettingsView extends StatelessWidget {
|
||||
final MultipleEmotesSettingsController controller;
|
||||
|
||||
const MultipleEmotesSettingsView(this.controller, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final room = Matrix.of(context).client.getRoomById(controller.roomId!)!;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: const Center(child: BackButton()),
|
||||
title: Text(L10n.of(context).emotePacks),
|
||||
),
|
||||
body: StreamBuilder(
|
||||
stream: room.client.onRoomState.stream
|
||||
.where((update) => update.roomId == room.id),
|
||||
builder: (context, snapshot) {
|
||||
final packStateEvents = room.states['im.ponies.room_emotes'];
|
||||
// we need to manually convert the map using Map.of, otherwise assigning null will throw a type error.
|
||||
final packs = packStateEvents != null
|
||||
? Map<String, StrippedStateEvent?>.of(packStateEvents)
|
||||
: <String, StrippedStateEvent?>{};
|
||||
if (!packs.containsKey('')) {
|
||||
packs[''] = null;
|
||||
}
|
||||
final keys = packs.keys.toList();
|
||||
keys.sort();
|
||||
return ListView.separated(
|
||||
separatorBuilder: (BuildContext context, int i) =>
|
||||
const SizedBox.shrink(),
|
||||
itemCount: keys.length,
|
||||
itemBuilder: (BuildContext context, int i) {
|
||||
final event = packs[keys[i]];
|
||||
final eventPack =
|
||||
event?.content.tryGetMap<String, Object?>('pack');
|
||||
final packName = eventPack?.tryGet<String>('displayname') ??
|
||||
eventPack?.tryGet<String>('name') ??
|
||||
(keys[i].isNotEmpty ? keys[i] : 'Default Pack');
|
||||
|
||||
return ListTile(
|
||||
title: Text(packName),
|
||||
onTap: () async {
|
||||
context.go(
|
||||
['', 'rooms', room.id, 'details', 'emotes', keys[i]]
|
||||
.join('/'),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -31,19 +31,8 @@ class ChatSettingsPopupMenuState extends State<ChatSettingsPopupMenu> {
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
void goToEmoteSettings() async {
|
||||
final room = widget.room;
|
||||
// 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 ((room.states['im.ponies.room_emotes'] ?? <String, Event>{})
|
||||
.keys
|
||||
.any((String s) => s.isNotEmpty)) {
|
||||
context.push('/rooms/${room.id}/details/multiple_emotes');
|
||||
} else {
|
||||
context.push('/rooms/${room.id}/details/emotes');
|
||||
}
|
||||
}
|
||||
void goToEmoteSettings() =>
|
||||
context.push('/rooms/${widget.room.id}/details/emotes');
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue