Initial commit
This commit is contained in:
49
lib/utils/matrix_sdk_extensions/device_extension.dart
Normal file
49
lib/utils/matrix_sdk_extensions/device_extension.dart
Normal file
@@ -0,0 +1,49 @@
|
||||
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
|
||||
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
IconData _getIconFromName(String displayname) {
|
||||
final name = displayname.toLowerCase();
|
||||
if ({'android'}.any(name.contains)) {
|
||||
return Icons.phone_android_outlined;
|
||||
}
|
||||
if ({'ios', 'ipad', 'iphone', 'ipod'}.any(name.contains)) {
|
||||
return Icons.phone_iphone_outlined;
|
||||
}
|
||||
if ({
|
||||
'web',
|
||||
'http://',
|
||||
'https://',
|
||||
'firefox',
|
||||
'chrome',
|
||||
'/_matrix',
|
||||
'safari',
|
||||
'opera',
|
||||
}.any(name.contains)) {
|
||||
return Icons.web_outlined;
|
||||
}
|
||||
if ({'desktop', 'windows', 'macos', 'linux', 'ubuntu'}.any(name.contains)) {
|
||||
return Icons.desktop_mac_outlined;
|
||||
}
|
||||
return Icons.device_unknown_outlined;
|
||||
}
|
||||
|
||||
extension DeviceExtension on Device {
|
||||
String get displayname =>
|
||||
(displayName?.isNotEmpty ?? false) ? displayName! : 'Unknown device';
|
||||
|
||||
IconData get icon => _getIconFromName(displayname);
|
||||
}
|
||||
|
||||
extension DeviceKeysExtension on DeviceKeys {
|
||||
String get displayname => (deviceDisplayName?.isNotEmpty ?? false)
|
||||
? deviceDisplayName!
|
||||
: 'Unknown device';
|
||||
|
||||
IconData get icon => _getIconFromName(displayname);
|
||||
}
|
||||
68
lib/utils/matrix_sdk_extensions/event_extension.dart
Normal file
68
lib/utils/matrix_sdk_extensions/event_extension.dart
Normal file
@@ -0,0 +1,68 @@
|
||||
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
|
||||
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import 'package:async/async.dart' as async;
|
||||
import 'package:tatchat/utils/size_string.dart';
|
||||
import 'package:tatchat/widgets/future_loading_dialog.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'matrix_file_extension.dart';
|
||||
|
||||
extension LocalizedBody on Event {
|
||||
Future<async.Result<MatrixFile?>> _getFile(BuildContext context) =>
|
||||
showFutureLoadingDialog(
|
||||
context: context,
|
||||
futureWithProgress: (onProgress) {
|
||||
final fileSize = infoMap['size'] is int
|
||||
? infoMap['size'] as int
|
||||
: null;
|
||||
return downloadAndDecryptAttachment(
|
||||
onDownloadProgress: fileSize == null
|
||||
? null
|
||||
: (bytes) => onProgress(bytes / fileSize),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Future<void> saveFile(BuildContext context) async {
|
||||
final matrixFile = await _getFile(context);
|
||||
if (!context.mounted) return;
|
||||
|
||||
matrixFile.result?.save(context);
|
||||
}
|
||||
|
||||
Future<void> shareFile(BuildContext context) async {
|
||||
final matrixFile = await _getFile(context);
|
||||
if (!context.mounted) return;
|
||||
|
||||
matrixFile.result?.share(context);
|
||||
}
|
||||
|
||||
bool get isAttachmentSmallEnough =>
|
||||
infoMap['size'] is int &&
|
||||
(infoMap['size'] as int) < room.client.database.maxFileSize;
|
||||
|
||||
bool get isThumbnailSmallEnough =>
|
||||
thumbnailInfoMap['size'] is int &&
|
||||
(thumbnailInfoMap['size'] as int) < room.client.database.maxFileSize;
|
||||
|
||||
bool get showThumbnail =>
|
||||
[
|
||||
MessageTypes.Image,
|
||||
MessageTypes.Sticker,
|
||||
MessageTypes.Video,
|
||||
].contains(messageType) &&
|
||||
(kIsWeb ||
|
||||
isAttachmentSmallEnough ||
|
||||
isThumbnailSmallEnough ||
|
||||
(content['url'] is String));
|
||||
|
||||
String? get sizeString => content
|
||||
.tryGetMap<String, Object?>('info')
|
||||
?.tryGet<int>('size')
|
||||
?.sizeString;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
|
||||
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import 'package:tatchat/config/setting_keys.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
extension VisibleInGuiExtension on List<Event> {
|
||||
List<Event> filterByVisibleInGui({
|
||||
String? exceptionEventId,
|
||||
String? threadId,
|
||||
}) => where((event) {
|
||||
if (threadId != null &&
|
||||
event.relationshipType != RelationshipTypes.reaction) {
|
||||
if ((event.relationshipType != RelationshipTypes.thread ||
|
||||
event.relationshipEventId != threadId) &&
|
||||
event.eventId != threadId) {
|
||||
return false;
|
||||
}
|
||||
} else if (event.relationshipType == RelationshipTypes.thread) {
|
||||
return false;
|
||||
}
|
||||
return event.isVisibleInGui || event.eventId == exceptionEventId;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
extension IsStateExtension on Event {
|
||||
bool get isVisibleInGui =>
|
||||
// always filter out edit and reaction relationships
|
||||
!{
|
||||
RelationshipTypes.edit,
|
||||
RelationshipTypes.reaction,
|
||||
}.contains(relationshipType) &&
|
||||
// always filter out m.key.* and other known but unimportant events
|
||||
!isKnownHiddenStates &&
|
||||
// event types to hide: redaction and reaction events
|
||||
// if a reaction has been redacted we also want it to be hidden in the timeline
|
||||
!{EventTypes.Reaction, EventTypes.Redaction}.contains(type) &&
|
||||
// if we enabled to hide all redacted events, don't show those
|
||||
(!AppSettings.hideRedactedEvents.value || !redacted) &&
|
||||
// if we enabled to hide all unknown events, don't show those
|
||||
(!AppSettings.hideUnknownEvents.value || isEventTypeKnown);
|
||||
|
||||
bool get isState => !{EventTypes.Message, EventTypes.Sticker}.contains(type);
|
||||
|
||||
bool get isCollapsedState => !{
|
||||
EventTypes.Message,
|
||||
EventTypes.Sticker,
|
||||
EventTypes.RoomCreate,
|
||||
EventTypes.RoomTombstone,
|
||||
}.contains(type);
|
||||
|
||||
bool get isKnownHiddenStates =>
|
||||
{PollEventContent.responseType}.contains(type) ||
|
||||
type.startsWith('m.key.verification.');
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
|
||||
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:tatchat/l10n/l10n.dart';
|
||||
import 'package:tatchat/utils/client_manager.dart';
|
||||
import 'package:tatchat/utils/platform_infos.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:path_provider_foundation/path_provider_foundation.dart';
|
||||
import 'package:sqflite_common/utils/utils.dart' as sqflite_utils;
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
import 'package:universal_html/html.dart' as html;
|
||||
|
||||
import 'cipher.dart';
|
||||
|
||||
Future<DatabaseApi> flutterMatrixSdkDatabaseBuilder(String clientName) async {
|
||||
try {
|
||||
return await _constructDatabase(clientName);
|
||||
} catch (e, s) {
|
||||
Logs().wtf('Unable to construct database!', e, s);
|
||||
|
||||
try {
|
||||
// Send error notification:
|
||||
final l10n = await lookupL10n(PlatformDispatcher.instance.locale);
|
||||
// We expect that the database cannot be open on iOS 2.8.0 due to that
|
||||
// the team ID has changed and te app can no longer access the database
|
||||
// key in the iOS keychain. This should be removed from 2.9.0 on.
|
||||
if (!PlatformInfos.isIOS) {
|
||||
ClientManager.sendInitNotification(l10n.initAppError, e.toString());
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logs().e('Unable to send error notification', e, s);
|
||||
}
|
||||
|
||||
// Delete database file:
|
||||
if (!kIsWeb) {
|
||||
final dbFile = File(await _getDatabasePath(clientName));
|
||||
if (await dbFile.exists()) await dbFile.delete();
|
||||
}
|
||||
|
||||
// Try again
|
||||
return await _constructDatabase(clientName);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Directory?> getFileStorageLocation() async {
|
||||
try {
|
||||
late final Directory temporaryDirectory;
|
||||
if (PlatformInfos.isIOS) {
|
||||
final containerPath = await PathProviderFoundation().getContainerPath(
|
||||
appGroupIdentifier: 'group.im.fluffychat.app',
|
||||
);
|
||||
temporaryDirectory = Directory(containerPath!);
|
||||
} else if (PlatformInfos.isLinux) {
|
||||
temporaryDirectory = await getApplicationCacheDirectory();
|
||||
} else {
|
||||
temporaryDirectory = await getTemporaryDirectory();
|
||||
}
|
||||
return await Directory(
|
||||
join(temporaryDirectory.path, 'fluffychat_download_cache'),
|
||||
).create(recursive: true);
|
||||
} on MissingPlatformDirectoryException catch (_) {
|
||||
Logs().w(
|
||||
'No temporary directory for file cache available on this platform.',
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<MatrixSdkDatabase> _constructDatabase(String clientName) async {
|
||||
if (kIsWeb) {
|
||||
html.window.navigator.storage?.persist();
|
||||
return await MatrixSdkDatabase.init(clientName);
|
||||
}
|
||||
|
||||
final cipher = await getDatabaseCipher();
|
||||
|
||||
final fileStorageLocation = await getFileStorageLocation();
|
||||
|
||||
final path = await _getDatabasePath(clientName);
|
||||
|
||||
// import the SQLite / SQLCipher shared objects / dynamic libraries
|
||||
final factory = createDatabaseFactoryFfi();
|
||||
|
||||
// required for [getDatabasesPath]
|
||||
databaseFactory = factory;
|
||||
|
||||
// migrate from potential previous SQLite database path to current one
|
||||
await _migrateLegacyLocation(path, clientName);
|
||||
|
||||
// in case we got a cipher, we use the encryption helper
|
||||
// to manage SQLite encryption
|
||||
final helper = cipher == null
|
||||
? null
|
||||
: SQfLiteEncryptionHelper(factory: factory, path: path, cipher: cipher);
|
||||
|
||||
// check whether the DB is already encrypted and otherwise do so
|
||||
await helper?.ensureDatabaseFileEncrypted();
|
||||
|
||||
final database = await factory.openDatabase(
|
||||
path,
|
||||
options: OpenDatabaseOptions(
|
||||
version: 1,
|
||||
// most important : apply encryption when opening the DB
|
||||
onConfigure: helper?.applyPragmaKey,
|
||||
),
|
||||
);
|
||||
|
||||
Logs().i('Database file size', await File(database.path).length());
|
||||
|
||||
final pageCount = sqflite_utils.firstIntValue(
|
||||
await database.rawQuery('PRAGMA page_count'),
|
||||
);
|
||||
final freePages = sqflite_utils.firstIntValue(
|
||||
await database.rawQuery('PRAGMA freelist_count'),
|
||||
);
|
||||
final pageSize = sqflite_utils.firstIntValue(
|
||||
await database.rawQuery('PRAGMA page_size'),
|
||||
);
|
||||
Logs().i(
|
||||
'DB pages: $pageCount total, $freePages free (~${(freePages ?? 0) * (pageSize ?? 0)} bytes wasted)',
|
||||
);
|
||||
|
||||
final tables = await database.rawQuery(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'",
|
||||
);
|
||||
for (final t in tables) {
|
||||
final name = t['name'] as String;
|
||||
final c = sqflite_utils.firstIntValue(
|
||||
await database.rawQuery('SELECT COUNT(*) FROM "$name"'),
|
||||
);
|
||||
Logs().i('Table $name: $c rows');
|
||||
}
|
||||
|
||||
await _ensureIncrementalAutoVacuum(database);
|
||||
|
||||
return await MatrixSdkDatabase.init(
|
||||
clientName,
|
||||
database: database,
|
||||
maxFileSize: 1000 * 1000 * 10,
|
||||
fileStorageLocation: fileStorageLocation?.uri,
|
||||
deleteFilesAfterDuration: const Duration(days: 30),
|
||||
);
|
||||
}
|
||||
|
||||
/// Without auto vacuum the database can never really shrink
|
||||
Future<void> _ensureIncrementalAutoVacuum(Database database) async {
|
||||
const incrementalAutoVacuum = 2;
|
||||
|
||||
final currentMode = sqflite_utils.firstIntValue(
|
||||
await database.rawQuery('PRAGMA auto_vacuum'),
|
||||
);
|
||||
|
||||
if (currentMode != incrementalAutoVacuum) {
|
||||
Logs().i('Switching database to incremental auto_vacuum...');
|
||||
await database.execute('PRAGMA auto_vacuum = $incrementalAutoVacuum');
|
||||
await database.execute('VACUUM');
|
||||
return;
|
||||
}
|
||||
|
||||
await database.execute('PRAGMA incremental_vacuum');
|
||||
}
|
||||
|
||||
Future<String> _getDatabaseDirectory() async {
|
||||
if (PlatformInfos.isIOS) {
|
||||
final containerPath = await PathProviderFoundation().getContainerPath(
|
||||
appGroupIdentifier: 'group.im.fluffychat.app',
|
||||
);
|
||||
if (containerPath == null) {
|
||||
Logs().w('No container path found for iOS app!');
|
||||
return (await getLibraryDirectory()).path;
|
||||
}
|
||||
return containerPath;
|
||||
}
|
||||
if (PlatformInfos.isMacOS) {
|
||||
return (await getLibraryDirectory()).path;
|
||||
}
|
||||
return (await getApplicationSupportDirectory()).path;
|
||||
}
|
||||
|
||||
Future<String> _getDatabasePath(String clientName) async {
|
||||
final databaseDirectory = await _getDatabaseDirectory();
|
||||
|
||||
return join(databaseDirectory, '$clientName.sqlite');
|
||||
}
|
||||
|
||||
Future<void> _migrateLegacyLocation(
|
||||
String sqlFilePath,
|
||||
String clientName,
|
||||
) async {
|
||||
final oldPath = PlatformInfos.isDesktop
|
||||
? (await getApplicationSupportDirectory()).path
|
||||
: PlatformInfos.isIOS
|
||||
? (await getLibraryDirectory()).path
|
||||
: await getDatabasesPath();
|
||||
|
||||
final oldFilePath = join(oldPath, '$clientName.sqlite');
|
||||
if (oldFilePath == sqlFilePath) return;
|
||||
|
||||
final maybeOldFile = File(oldFilePath);
|
||||
if (await maybeOldFile.exists()) {
|
||||
Logs().i(
|
||||
'Migrate legacy location for database from "$oldFilePath" to "$sqlFilePath"',
|
||||
);
|
||||
await maybeOldFile.copy(sqlFilePath);
|
||||
await maybeOldFile.delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
|
||||
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:tatchat/config/setting_keys.dart';
|
||||
import 'package:tatchat/l10n/l10n.dart';
|
||||
import 'package:tatchat/utils/client_manager.dart';
|
||||
import 'package:tatchat/utils/platform_infos.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
const _passwordStorageKey = 'database_password';
|
||||
|
||||
Future<String?> getDatabaseCipher() async {
|
||||
String? password;
|
||||
|
||||
const iosOptions = IOSOptions(groupId: 'group.im.fluffychat.app');
|
||||
|
||||
try {
|
||||
password = await FlutterSecureStorage(
|
||||
iOptions: iosOptions,
|
||||
).read(key: _passwordStorageKey, iOptions: iosOptions);
|
||||
if (password != null) return password;
|
||||
|
||||
if (PlatformInfos.isIOS) {
|
||||
final legacyPassword = await FlutterSecureStorage().read(
|
||||
key: _passwordStorageKey,
|
||||
);
|
||||
if (legacyPassword != null) {
|
||||
Logs().i('Migrate database key location on iOS...');
|
||||
await FlutterSecureStorage().delete(key: _passwordStorageKey);
|
||||
await FlutterSecureStorage(iOptions: iosOptions).write(
|
||||
key: _passwordStorageKey,
|
||||
value: legacyPassword,
|
||||
iOptions: iosOptions,
|
||||
);
|
||||
return legacyPassword;
|
||||
}
|
||||
}
|
||||
|
||||
final rng = Random.secure();
|
||||
final list = Uint8List(32);
|
||||
list.setAll(0, Iterable.generate(list.length, (i) => rng.nextInt(256)));
|
||||
final newPassword = base64UrlEncode(list);
|
||||
await FlutterSecureStorage(
|
||||
iOptions: iosOptions,
|
||||
).write(key: _passwordStorageKey, value: newPassword, iOptions: iosOptions);
|
||||
// workaround for if we just wrote to the key and it still doesn't exist
|
||||
password = await FlutterSecureStorage(
|
||||
iOptions: iosOptions,
|
||||
).read(key: _passwordStorageKey, iOptions: iosOptions);
|
||||
if (password == null) throw MissingPluginException();
|
||||
return password;
|
||||
} on MissingPluginException catch (e) {
|
||||
FlutterSecureStorage(
|
||||
iOptions: iosOptions,
|
||||
).delete(key: _passwordStorageKey, iOptions: iosOptions).catchError((_) {});
|
||||
Logs().w('Database encryption is not supported on this platform', e);
|
||||
_sendNoEncryptionWarning(e);
|
||||
} catch (e, s) {
|
||||
FlutterSecureStorage(
|
||||
iOptions: iosOptions,
|
||||
).delete(key: _passwordStorageKey, iOptions: iosOptions).catchError((_) {});
|
||||
Logs().w('Unable to init database encryption', e, s);
|
||||
_sendNoEncryptionWarning(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> _sendNoEncryptionWarning(Object exception) async {
|
||||
final isStored = AppSettings.noEncryptionWarningShown.value;
|
||||
|
||||
if (isStored == true) return;
|
||||
|
||||
final l10n = await lookupL10n(PlatformDispatcher.instance.locale);
|
||||
ClientManager.sendInitNotification(
|
||||
l10n.noDatabaseEncryption,
|
||||
exception.toString(),
|
||||
);
|
||||
|
||||
await AppSettings.noEncryptionWarningShown.setItem(true);
|
||||
}
|
||||
70
lib/utils/matrix_sdk_extensions/matrix_file_extension.dart
Normal file
70
lib/utils/matrix_sdk_extensions/matrix_file_extension.dart
Normal file
@@ -0,0 +1,70 @@
|
||||
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
|
||||
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:tatchat/l10n/l10n.dart';
|
||||
import 'package:tatchat/utils/size_string.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
extension MatrixFileExtension on MatrixFile {
|
||||
Future<void> save(BuildContext context) async {
|
||||
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||
final l10n = L10n.of(context);
|
||||
final downloadPath = await FilePicker.saveFile(
|
||||
dialogTitle: l10n.saveFile,
|
||||
fileName: name,
|
||||
type: filePickerFileType,
|
||||
bytes: bytes,
|
||||
);
|
||||
if (downloadPath == null) return;
|
||||
|
||||
scaffoldMessenger.showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(l10n.fileHasBeenSavedAt(downloadPath.toString())),
|
||||
showCloseIcon: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
FileType get filePickerFileType {
|
||||
if (this is MatrixImageFile) return FileType.image;
|
||||
if (this is MatrixAudioFile) return FileType.audio;
|
||||
if (this is MatrixVideoFile) return FileType.video;
|
||||
return FileType.any;
|
||||
}
|
||||
|
||||
Future<void> share(BuildContext context) async {
|
||||
// Workaround for iPad from
|
||||
// https://github.com/fluttercommunity/plus_plugins/tree/main/packages/share_plus/share_plus#ipad
|
||||
final box = context.findRenderObject() as RenderBox?;
|
||||
|
||||
await SharePlus.instance.share(
|
||||
ShareParams(
|
||||
files: [XFile.fromData(bytes, name: name, mimeType: mimeType)],
|
||||
sharePositionOrigin: box == null
|
||||
? null
|
||||
: box.localToGlobal(Offset.zero) & box.size,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
MatrixFile get detectFileType {
|
||||
if (msgType == MessageTypes.Image) {
|
||||
return MatrixImageFile(bytes: bytes, name: name);
|
||||
}
|
||||
if (msgType == MessageTypes.Video) {
|
||||
return MatrixVideoFile(bytes: bytes, name: name);
|
||||
}
|
||||
if (msgType == MessageTypes.Audio) {
|
||||
return MatrixAudioFile(bytes: bytes, name: name);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
String get sizeString => size.sizeString;
|
||||
}
|
||||
382
lib/utils/matrix_sdk_extensions/matrix_locals.dart
Normal file
382
lib/utils/matrix_sdk_extensions/matrix_locals.dart
Normal file
@@ -0,0 +1,382 @@
|
||||
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
|
||||
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import 'package:tatchat/l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
/// This is a temporary helper class until there is a proper solution to this with the new system
|
||||
class MatrixLocals extends MatrixLocalizations {
|
||||
final L10n l10n;
|
||||
|
||||
MatrixLocals(this.l10n);
|
||||
|
||||
@override
|
||||
String acceptedTheInvitation(String targetName) {
|
||||
return l10n.acceptedTheInvitation(targetName);
|
||||
}
|
||||
|
||||
@override
|
||||
String activatedEndToEndEncryption(String senderName) {
|
||||
return l10n.activatedEndToEndEncryption(senderName);
|
||||
}
|
||||
|
||||
@override
|
||||
String answeredTheCall(String senderName) {
|
||||
return l10n.answeredTheCall(senderName);
|
||||
}
|
||||
|
||||
@override
|
||||
String get anyoneCanJoin => l10n.anyoneCanJoin;
|
||||
|
||||
@override
|
||||
String bannedUser(String senderName, String targetName) {
|
||||
return l10n.bannedUser(senderName, targetName);
|
||||
}
|
||||
|
||||
@override
|
||||
String changedTheChatAvatar(String senderName) {
|
||||
return l10n.changedTheChatAvatar(senderName);
|
||||
}
|
||||
|
||||
@override
|
||||
String changedTheChatDescriptionTo(String senderName, _) {
|
||||
return l10n.changedTheChatDescription(senderName);
|
||||
}
|
||||
|
||||
@override
|
||||
String changedTheChatNameTo(String senderName, _) {
|
||||
return l10n.changedTheChatName(senderName);
|
||||
}
|
||||
|
||||
@override
|
||||
String changedTheChatPermissions(String senderName) {
|
||||
return l10n.changedTheChatPermissions(senderName);
|
||||
}
|
||||
|
||||
@override
|
||||
String changedTheDisplaynameTo(String targetName, String newDisplayname) {
|
||||
return l10n.changedTheDisplaynameTo(targetName, newDisplayname);
|
||||
}
|
||||
|
||||
@override
|
||||
String changedTheGuestAccessRules(String senderName) {
|
||||
return l10n.changedTheGuestAccessRules(senderName);
|
||||
}
|
||||
|
||||
@override
|
||||
String changedTheGuestAccessRulesTo(
|
||||
String senderName,
|
||||
String localizedString,
|
||||
) {
|
||||
return l10n.changedTheGuestAccessRulesTo(senderName, localizedString);
|
||||
}
|
||||
|
||||
@override
|
||||
String changedTheHistoryVisibility(String senderName) {
|
||||
return l10n.changedTheHistoryVisibility(senderName);
|
||||
}
|
||||
|
||||
@override
|
||||
String changedTheHistoryVisibilityTo(
|
||||
String senderName,
|
||||
String localizedString,
|
||||
) {
|
||||
return l10n.changedTheHistoryVisibilityTo(senderName, localizedString);
|
||||
}
|
||||
|
||||
@override
|
||||
String changedTheJoinRules(String senderName) {
|
||||
return l10n.changedTheJoinRules(senderName);
|
||||
}
|
||||
|
||||
@override
|
||||
String changedTheJoinRulesTo(String senderName, String localizedString) {
|
||||
return l10n.changedTheJoinRulesTo(senderName, localizedString);
|
||||
}
|
||||
|
||||
@override
|
||||
String changedTheProfileAvatar(String targetName) {
|
||||
return l10n.changedTheProfileAvatar(targetName);
|
||||
}
|
||||
|
||||
@override
|
||||
String changedTheRoomAliases(String senderName) {
|
||||
return l10n.changedTheRoomAliases(senderName);
|
||||
}
|
||||
|
||||
@override
|
||||
String changedTheRoomInvitationLink(String senderName) {
|
||||
return l10n.changedTheRoomInvitationLink(senderName);
|
||||
}
|
||||
|
||||
@override
|
||||
String get channelCorruptedDecryptError => l10n.channelCorruptedDecryptError;
|
||||
|
||||
@override
|
||||
String couldNotDecryptMessage(String errorText) {
|
||||
return l10n.couldNotDecryptMessage(errorText);
|
||||
}
|
||||
|
||||
@override
|
||||
String createdTheChat(String senderName) {
|
||||
return l10n.createdTheChat(senderName);
|
||||
}
|
||||
|
||||
@override
|
||||
String get emptyChat => l10n.emptyChat;
|
||||
|
||||
@override
|
||||
String get encryptionNotEnabled => l10n.encryptionNotEnabled;
|
||||
|
||||
@override
|
||||
String endedTheCall(String senderName) {
|
||||
return l10n.endedTheCall(senderName);
|
||||
}
|
||||
|
||||
@override
|
||||
String get fromJoining => l10n.fromJoining;
|
||||
|
||||
@override
|
||||
String get fromTheInvitation => l10n.fromTheInvitation;
|
||||
|
||||
@override
|
||||
String groupWith(String displayname) {
|
||||
return l10n.groupWith(displayname);
|
||||
}
|
||||
|
||||
@override
|
||||
String get guestsAreForbidden => l10n.guestsAreForbidden;
|
||||
|
||||
@override
|
||||
String get guestsCanJoin => l10n.guestsCanJoin;
|
||||
|
||||
@override
|
||||
String hasWithdrawnTheInvitationFor(String senderName, String targetName) {
|
||||
return l10n.hasWithdrawnTheInvitationFor(senderName, targetName);
|
||||
}
|
||||
|
||||
@override
|
||||
String invitedUser(String senderName, String targetName) {
|
||||
return l10n.invitedUser(senderName, targetName);
|
||||
}
|
||||
|
||||
@override
|
||||
String get invitedUsersOnly => l10n.invitedUsersOnly;
|
||||
|
||||
@override
|
||||
String joinedTheChat(String targetName) {
|
||||
return l10n.joinedTheChat(targetName);
|
||||
}
|
||||
|
||||
@override
|
||||
String kicked(String senderName, String targetName) {
|
||||
return l10n.kicked(senderName, targetName);
|
||||
}
|
||||
|
||||
@override
|
||||
String kickedAndBanned(String senderName, String targetName) {
|
||||
return l10n.kickedAndBanned(senderName, targetName);
|
||||
}
|
||||
|
||||
@override
|
||||
String get needPantalaimonWarning => l10n.oopsSomethingWentWrong;
|
||||
|
||||
@override
|
||||
String get noPermission => l10n.noKeyForThisMessage;
|
||||
|
||||
@override
|
||||
String redactedAnEvent(Event redactedEvent) {
|
||||
return l10n.redactedAnEvent(
|
||||
redactedEvent.redactedBecause?.senderFromMemoryOrFallback
|
||||
.calcDisplayname() ??
|
||||
l10n.user,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String rejectedTheInvitation(String targetName) {
|
||||
return l10n.rejectedTheInvitation(targetName);
|
||||
}
|
||||
|
||||
@override
|
||||
String removedBy(Event redactedEvent) {
|
||||
return l10n.redactedBy(
|
||||
redactedEvent.senderFromMemoryOrFallback.calcDisplayname(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String get roomHasBeenUpgraded => l10n.roomHasBeenUpgraded;
|
||||
|
||||
@override
|
||||
String sentAFile(String senderName) {
|
||||
return l10n.sentAFile(senderName);
|
||||
}
|
||||
|
||||
@override
|
||||
String sentAPicture(String senderName) {
|
||||
return l10n.sentAPicture(senderName);
|
||||
}
|
||||
|
||||
@override
|
||||
String sentASticker(String senderName) {
|
||||
return l10n.sentASticker(senderName);
|
||||
}
|
||||
|
||||
@override
|
||||
String sentAVideo(String senderName) {
|
||||
return l10n.sentAVideo(senderName);
|
||||
}
|
||||
|
||||
@override
|
||||
String sentAnAudio(String senderName) {
|
||||
return l10n.sentAnAudio(senderName);
|
||||
}
|
||||
|
||||
@override
|
||||
String sentCallInformations(String senderName) {
|
||||
return l10n.sentCallInformations(senderName);
|
||||
}
|
||||
|
||||
@override
|
||||
String sharedTheLocation(String senderName) {
|
||||
return l10n.sharedTheLocation(senderName);
|
||||
}
|
||||
|
||||
@override
|
||||
String startedACall(String senderName) {
|
||||
return l10n.startedACall(senderName);
|
||||
}
|
||||
|
||||
@override
|
||||
String unbannedUser(String senderName, String targetName) {
|
||||
return l10n.unbannedUser(senderName, targetName);
|
||||
}
|
||||
|
||||
@override
|
||||
String get unknownEncryptionAlgorithm => l10n.unknownEncryptionAlgorithm;
|
||||
|
||||
@override
|
||||
String unknownEvent(String typeKey) {
|
||||
return l10n.userSentUnknownEvent('User', typeKey);
|
||||
}
|
||||
|
||||
@override
|
||||
String userLeftTheChat(String targetName) {
|
||||
return l10n.userLeftTheChat(targetName);
|
||||
}
|
||||
|
||||
@override
|
||||
String get visibleForAllParticipants => l10n.visibleForAllParticipants;
|
||||
|
||||
@override
|
||||
String get visibleForEveryone => l10n.visibleForEveryone;
|
||||
|
||||
@override
|
||||
String get you => l10n.you;
|
||||
|
||||
@override
|
||||
String sentReaction(String senderName, String reactionKey) =>
|
||||
l10n.reactedWith(senderName, reactionKey);
|
||||
|
||||
@override
|
||||
// TODO: implement youAcceptedTheInvitation
|
||||
String get youAcceptedTheInvitation => l10n.youAcceptedTheInvitation;
|
||||
|
||||
@override
|
||||
String youBannedUser(String targetName) => l10n.youBannedUser(targetName);
|
||||
|
||||
@override
|
||||
String youHaveWithdrawnTheInvitationFor(String targetName) =>
|
||||
l10n.youHaveWithdrawnTheInvitationFor(targetName);
|
||||
|
||||
@override
|
||||
String youInvitedBy(String senderName) => l10n.youInvitedBy(senderName);
|
||||
|
||||
@override
|
||||
String youInvitedUser(String targetName) => l10n.youInvitedUser(targetName);
|
||||
|
||||
@override
|
||||
// TODO: implement youJoinedTheChat
|
||||
String get youJoinedTheChat => l10n.youJoinedTheChat;
|
||||
|
||||
@override
|
||||
String youKicked(String targetName) => l10n.youKicked(targetName);
|
||||
|
||||
@override
|
||||
String youKickedAndBanned(String targetName) =>
|
||||
l10n.youKickedAndBanned(targetName);
|
||||
|
||||
@override
|
||||
// TODO: implement youRejectedTheInvitation
|
||||
String get youRejectedTheInvitation => l10n.youRejectedTheInvitation;
|
||||
|
||||
@override
|
||||
String youUnbannedUser(String targetName) => l10n.youUnbannedUser(targetName);
|
||||
|
||||
@override
|
||||
String wasDirectChatDisplayName(String oldDisplayName) =>
|
||||
l10n.wasDirectChatDisplayName(oldDisplayName);
|
||||
|
||||
@override
|
||||
String get unknownUser => l10n.user;
|
||||
|
||||
@override
|
||||
String hasKnocked(String targetName) => l10n.hasKnocked(targetName);
|
||||
|
||||
@override
|
||||
String acceptedKeyVerification(String senderName) =>
|
||||
l10n.acceptedKeyVerification(senderName);
|
||||
|
||||
@override
|
||||
String canceledKeyVerification(String senderName) =>
|
||||
l10n.canceledKeyVerification(senderName);
|
||||
|
||||
@override
|
||||
String completedKeyVerification(String senderName) =>
|
||||
l10n.completedKeyVerification(senderName);
|
||||
|
||||
@override
|
||||
String isReadyForKeyVerification(String senderName) =>
|
||||
l10n.isReadyForKeyVerification(senderName);
|
||||
|
||||
@override
|
||||
String requestedKeyVerification(String senderName) =>
|
||||
l10n.requestedKeyVerification(senderName);
|
||||
|
||||
@override
|
||||
String startedKeyVerification(String senderName) =>
|
||||
l10n.startedKeyVerification(senderName);
|
||||
|
||||
@override
|
||||
String invitedBy(String senderName) => senderName;
|
||||
|
||||
@override
|
||||
String get cancelledSend => l10n.sendCanceled;
|
||||
|
||||
@override
|
||||
String voiceMessage(
|
||||
String senderName,
|
||||
Duration? duration,
|
||||
) => l10n.sentVoiceMessage(
|
||||
senderName,
|
||||
duration == null
|
||||
? ''
|
||||
: '${duration.inMinutes.toString().padLeft(2, '0')}:${(duration.inSeconds % 60).toString().padLeft(2, '0')}',
|
||||
);
|
||||
|
||||
@override
|
||||
String get refreshingLastEvent => l10n.loadingPleaseWait;
|
||||
|
||||
@override
|
||||
String startedAPoll(String senderName) => '$senderName started a poll';
|
||||
|
||||
@override
|
||||
String get pollHasBeenEnded => l10n.pollHasBeenEnded;
|
||||
|
||||
@override
|
||||
String incomingCallFrom(String senderName) =>
|
||||
l10n.incomingCallFrom(senderName);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
|
||||
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
extension OidcSessionJsonExtension on OidcLoginSession {
|
||||
static const String storeKey = 'oidc_session';
|
||||
static const String homeserverStoreKey = 'oidc_stored_homeserver';
|
||||
Map<String, Object?> toJson() => {
|
||||
'oidc_client_data': oidcClientData.toJson(),
|
||||
'authentication_uri': authenticationUri.toString(),
|
||||
'redirect_uri': redirectUri.toString(),
|
||||
'code_verifier': codeVerifier,
|
||||
'state': state,
|
||||
};
|
||||
|
||||
static OidcLoginSession fromJson(Map<String, Object?> json) =>
|
||||
OidcLoginSession(
|
||||
oidcClientData: OidcClientData.fromJson(
|
||||
json['oidc_client_data'] as Map<String, Object?>,
|
||||
),
|
||||
authenticationUri: Uri.parse(json['authentication_uri'] as String),
|
||||
redirectUri: Uri.parse(json['redirect_uri'] as String),
|
||||
codeVerifier: json['code_verifier'] as String,
|
||||
state: json['state'] as String,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user