optimise mxc_image
fixed mx-reply tags added russian translations to newly added strings
This commit is contained in:
parent
4dd242bc7d
commit
e44af26e8d
|
|
@ -3482,5 +3482,14 @@
|
|||
"@open": {},
|
||||
"@waitingForServer": {},
|
||||
"@appIntroduction": {},
|
||||
"@previous": {}
|
||||
"@previous": {},
|
||||
"backToMainChat": "Вернуться в главный чат",
|
||||
"saveChanges": "Сохранить",
|
||||
"createSticker": "Создать стикер или эмодзи",
|
||||
"newStickerPack": "Новый набор стикеров",
|
||||
"stickerPackNameAlreadyExists": "Набор стикеров с таким названием уже существует",
|
||||
"stickerPackName": "Название набора стикеров",
|
||||
"attribution": "Авторство",
|
||||
"useAsSticker": "Стикер",
|
||||
"useAsEmoji": "Эмодзи"
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@ abstract class FluffyThemes {
|
|||
);
|
||||
}
|
||||
|
||||
static const Duration animationDuration = Duration(milliseconds: 250);
|
||||
static const Duration animationDuration = Duration(milliseconds: 3000);
|
||||
static const Curve animationCurve = Curves.easeInOut;
|
||||
|
||||
static ThemeData buildTheme(
|
||||
|
|
|
|||
|
|
@ -2904,30 +2904,30 @@ class L10nRu extends L10n {
|
|||
'Пожалуйста, подождите когда администраторы примут Ваш запрос.';
|
||||
|
||||
@override
|
||||
String get backToMainChat => 'Back to main chat';
|
||||
String get backToMainChat => 'Вернуться в главный чат';
|
||||
|
||||
@override
|
||||
String get saveChanges => 'Save changes';
|
||||
String get saveChanges => 'Сохранить';
|
||||
|
||||
@override
|
||||
String get createSticker => 'Create sticker or emoji';
|
||||
String get createSticker => 'Создать стикер или эмодзи';
|
||||
|
||||
@override
|
||||
String get newStickerPack => 'New sticker pack';
|
||||
String get newStickerPack => 'Новый набор стикеров';
|
||||
|
||||
@override
|
||||
String get stickerPackNameAlreadyExists =>
|
||||
'A sticker pack with that name already exists';
|
||||
'Набор стикеров с таким названием уже существует';
|
||||
|
||||
@override
|
||||
String get stickerPackName => 'Sticker pack name';
|
||||
String get stickerPackName => 'Название набора стикеров';
|
||||
|
||||
@override
|
||||
String get attribution => 'Attribution';
|
||||
String get attribution => 'Авторство';
|
||||
|
||||
@override
|
||||
String get useAsSticker => 'Sticker';
|
||||
String get useAsSticker => 'Стикер';
|
||||
|
||||
@override
|
||||
String get useAsEmoji => 'Emoji';
|
||||
String get useAsEmoji => 'Эмодзи';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:isolate';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:extera_next/utils/notification_background_handler.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
|
|
@ -25,11 +26,12 @@ void main() async {
|
|||
|
||||
if (PlatformInfos.isAndroid) {
|
||||
final port = mainIsolateReceivePort = ReceivePort();
|
||||
IsolateNameServer.removePortNameMapping('main_isolate');
|
||||
IsolateNameServer.removePortNameMapping(AppConfig.mainIsolatePortName);
|
||||
IsolateNameServer.registerPortWithName(
|
||||
port.sendPort,
|
||||
'main_isolate',
|
||||
AppConfig.mainIsolatePortName,
|
||||
);
|
||||
await waitForPushIsolateDone();
|
||||
}
|
||||
|
||||
// Our background push shared isolate accesses flutter-internal things very early in the startup proccess
|
||||
|
|
|
|||
|
|
@ -83,6 +83,10 @@ class HtmlMessage extends StatelessWidget {
|
|||
'tg-forward',
|
||||
};
|
||||
|
||||
static const Set<String> blockedHtmlTags = {
|
||||
'mx-reply'
|
||||
};
|
||||
|
||||
/// We add line breaks before these tags:
|
||||
static const Set<String> blockHtmlTags = {
|
||||
'p',
|
||||
|
|
@ -139,6 +143,10 @@ class HtmlMessage extends StatelessWidget {
|
|||
// We must not render elements nested more than 100 elements deep:
|
||||
if (depth >= 100) return const TextSpan();
|
||||
|
||||
if (node is dom.Element && blockedHtmlTags.contains(node.localName)) {
|
||||
return const TextSpan();
|
||||
}
|
||||
|
||||
// This is a text node, so we render it as text:
|
||||
if (node is! dom.Element || !allowedHtmlTags.contains(node.localName)) {
|
||||
var text = node.text ?? '';
|
||||
|
|
|
|||
|
|
@ -96,13 +96,14 @@ class ChatDetailsController extends State<ChatDetails> {
|
|||
// 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');
|
||||
}
|
||||
context.push('/rooms/${room.id}/details/emotes');
|
||||
// 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 setAvatarAction() async {
|
||||
|
|
|
|||
|
|
@ -1,38 +1,41 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:isolate';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:extera_next/config/app_config.dart';
|
||||
import 'package:extera_next/generated/l10n/l10n.dart';
|
||||
import 'package:extera_next/utils/client_download_content_extension.dart';
|
||||
import 'package:extera_next/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import 'package:extera_next/utils/platform_infos.dart';
|
||||
import 'package:extera_next/utils/push_helper.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:flutter_vodozemac/flutter_vodozemac.dart' as vod;
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'package:extera_next/generated/l10n/l10n.dart';
|
||||
import 'package:extera_next/utils/client_download_content_extension.dart';
|
||||
import 'package:extera_next/utils/client_manager.dart';
|
||||
import 'package:extera_next/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import 'package:extera_next/utils/platform_infos.dart';
|
||||
import 'package:extera_next/utils/push_helper.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../config/app_config.dart';
|
||||
import '../config/setting_keys.dart';
|
||||
|
||||
bool _vodInitialized = false;
|
||||
|
||||
extension NotificationResponseJson on NotificationResponse {
|
||||
String toJsonString() => jsonEncode({
|
||||
'type': notificationResponseType.name,
|
||||
'id': id,
|
||||
'actionId': actionId,
|
||||
'input': input,
|
||||
'payload': payload,
|
||||
'data': data,
|
||||
});
|
||||
'type': notificationResponseType.name,
|
||||
'id': id,
|
||||
'actionId': actionId,
|
||||
'input': input,
|
||||
'payload': payload,
|
||||
'data': data,
|
||||
});
|
||||
|
||||
static NotificationResponse fromJsonString(String jsonString) {
|
||||
final json = jsonDecode(jsonString) as Map<String, Object?>;
|
||||
return NotificationResponse(
|
||||
notificationResponseType: NotificationResponseType.values
|
||||
.singleWhere((t) => t.name == json['type']),
|
||||
notificationResponseType: NotificationResponseType.values.singleWhere(
|
||||
(t) => t.name == json['type'],
|
||||
),
|
||||
id: json['id'] as int?,
|
||||
actionId: json['actionId'] as String?,
|
||||
input: json['input'] as String?,
|
||||
|
|
@ -42,16 +45,35 @@ extension NotificationResponseJson on NotificationResponse {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> waitForPushIsolateDone() async {
|
||||
if (IsolateNameServer.lookupPortByName(AppConfig.pushIsolatePortName) !=
|
||||
null) {
|
||||
Logs().i('Wait for Push Isolate to be done...');
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
}
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void notificationTapBackground(
|
||||
NotificationResponse notificationResponse,
|
||||
) async {
|
||||
final sendPort = IsolateNameServer.lookupPortByName(AppConfig.mainIsolatePortName);
|
||||
final sendPort = IsolateNameServer.lookupPortByName(
|
||||
AppConfig.mainIsolatePortName,
|
||||
);
|
||||
if (sendPort != null) {
|
||||
sendPort.send(notificationResponse.toJsonString());
|
||||
Logs().i('Notification tap sent to main isolate');
|
||||
Logs().i('Notification tap sent to main isolate!');
|
||||
return;
|
||||
}
|
||||
Logs().i(
|
||||
'Main isolate no up - Create temporary client for notification tap intend!',
|
||||
);
|
||||
|
||||
final pushIsolateReceivePort = ReceivePort();
|
||||
IsolateNameServer.registerPortWithName(
|
||||
pushIsolateReceivePort.sendPort,
|
||||
AppConfig.pushIsolatePortName,
|
||||
);
|
||||
|
||||
if (!_vodInitialized) {
|
||||
await vod.init();
|
||||
|
|
@ -61,14 +83,13 @@ void notificationTapBackground(
|
|||
final client = (await ClientManager.getClients(
|
||||
initialize: false,
|
||||
store: store,
|
||||
))
|
||||
.first;
|
||||
|
||||
)).first;
|
||||
await client.abortSync();
|
||||
await client.init(
|
||||
waitForFirstSync: false,
|
||||
waitUntilLoadCompletedLoaded: false,
|
||||
);
|
||||
|
||||
if (!client.isLogged()) {
|
||||
throw Exception('Notification tab in background but not logged in!');
|
||||
}
|
||||
|
|
@ -76,6 +97,8 @@ void notificationTapBackground(
|
|||
await notificationTap(notificationResponse, client: client);
|
||||
} finally {
|
||||
await client.dispose(closeDatabase: false);
|
||||
pushIsolateReceivePort.sendPort.send('DONE');
|
||||
IsolateNameServer.removePortNameMapping(AppConfig.pushIsolatePortName);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -83,15 +106,16 @@ void notificationTapBackground(
|
|||
Future<void> notificationTap(
|
||||
NotificationResponse notificationResponse, {
|
||||
GoRouter? router,
|
||||
L10n? l10n,
|
||||
required Client client,
|
||||
L10n? l10n,
|
||||
}) async {
|
||||
Logs().d(
|
||||
'Notification action handler started',
|
||||
notificationResponse.notificationResponseType.name,
|
||||
);
|
||||
final payload =
|
||||
NotificationPushPayload.fromString(notificationResponse.payload ?? '');
|
||||
final payload = NotificationPushPayload.fromString(
|
||||
notificationResponse.payload ?? '',
|
||||
);
|
||||
switch (notificationResponse.notificationResponseType) {
|
||||
case NotificationResponseType.selectedNotification:
|
||||
final roomId = payload.roomId;
|
||||
|
|
@ -121,7 +145,7 @@ Future<void> notificationTap(
|
|||
if (actionType == null) {
|
||||
throw Exception('Selected notification with action but no action ID');
|
||||
}
|
||||
final roomId = notificationResponse.payload;
|
||||
final roomId = payload.roomId;
|
||||
if (roomId == null) {
|
||||
throw Exception('Selected notification with action but no payload');
|
||||
}
|
||||
|
|
@ -137,10 +161,9 @@ Future<void> notificationTap(
|
|||
switch (actionType) {
|
||||
case FluffyChatNotificationActions.markAsRead:
|
||||
await room.setReadMarker(
|
||||
payload!.eventId,
|
||||
mRead: payload!.eventId,
|
||||
public:
|
||||
AppConfig.sendPublicReadReceipts, // TODO: Load preference here
|
||||
payload.eventId ?? room.lastEvent!.eventId,
|
||||
mRead: payload.eventId ?? room.lastEvent!.eventId,
|
||||
public: AppConfig.sendPublicReadReceipts,
|
||||
);
|
||||
case FluffyChatNotificationActions.reply:
|
||||
final input = notificationResponse.input;
|
||||
|
|
@ -149,25 +172,29 @@ Future<void> notificationTap(
|
|||
'Selected notification with reply action but without input',
|
||||
);
|
||||
}
|
||||
final eventId = await room.sendTextEvent(input, parseCommands: false);
|
||||
|
||||
final eventId = await room.sendTextEvent(
|
||||
input,
|
||||
parseCommands: false,
|
||||
displayPendingEvent: false,
|
||||
);
|
||||
|
||||
if (PlatformInfos.isAndroid) {
|
||||
final ownProfile = await room.client.fetchOwnProfile();
|
||||
|
||||
final avatar = ownProfile.avatarUrl;
|
||||
final avatarFile = avatar == null
|
||||
? null
|
||||
: await client
|
||||
.downloadMxcCached(
|
||||
avatar,
|
||||
thumbnailMethod: ThumbnailMethod.scale,
|
||||
width: notificationAvatarDimension,
|
||||
height: notificationAvatarDimension,
|
||||
animated: false,
|
||||
isThumbnail: true,
|
||||
rounded: true,
|
||||
)
|
||||
.timeout(const Duration(seconds: 3));
|
||||
.downloadMxcCached(
|
||||
avatar,
|
||||
thumbnailMethod: ThumbnailMethod.crop,
|
||||
width: notificationAvatarDimension,
|
||||
height: notificationAvatarDimension,
|
||||
animated: false,
|
||||
isThumbnail: true,
|
||||
rounded: true,
|
||||
)
|
||||
.timeout(const Duration(seconds: 3));
|
||||
final messagingStyleInformation =
|
||||
await AndroidFlutterLocalNotificationsPlugin()
|
||||
.getActiveNotificationMessagingStyle(room.id.hashCode);
|
||||
|
|
|
|||
|
|
@ -33,13 +33,7 @@ class ChatSettingsPopupMenuState extends State<ChatSettingsPopupMenu> {
|
|||
|
||||
void goToEmoteSettings() async {
|
||||
final room = widget.room;
|
||||
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');
|
||||
}
|
||||
context.push('/rooms/${room.id}/details/emotes');
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -141,36 +141,37 @@ class _MxcImageState extends State<MxcImage> {
|
|||
final data = _imageData;
|
||||
final hasData = data != null && data.isNotEmpty;
|
||||
|
||||
return AnimatedSwitcher(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
child: hasData
|
||||
? ClipRRect(
|
||||
borderRadius: widget.borderRadius,
|
||||
child: Image.memory(
|
||||
data,
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
fit: widget.fit,
|
||||
filterQuality: widget.isThumbnail
|
||||
? FilterQuality.low
|
||||
: FilterQuality.medium,
|
||||
errorBuilder: (context, e, s) {
|
||||
Logs().d('Unable to render mxc image', e, s);
|
||||
return SizedBox(
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
child: Material(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
child: Icon(
|
||||
Icons.broken_image_outlined,
|
||||
size: min(widget.height ?? 64, 64),
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
return hasData
|
||||
? ClipRRect(
|
||||
key: ValueKey(data), // Add key based on image data
|
||||
borderRadius: widget.borderRadius,
|
||||
child: Image.memory(
|
||||
data,
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
fit: widget.fit,
|
||||
filterQuality:
|
||||
widget.isThumbnail ? FilterQuality.low : FilterQuality.medium,
|
||||
errorBuilder: (context, e, s) {
|
||||
Logs().d('Unable to render mxc image', e, s);
|
||||
return SizedBox(
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
child: Material(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
child: Icon(
|
||||
Icons.broken_image_outlined,
|
||||
size: min(widget.height ?? 64, 64),
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
: placeholder(context));
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
: KeyedSubtree(
|
||||
key: const ValueKey('placeholder'), // Add key for placeholder
|
||||
child: placeholder(context),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue