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