refactor: Remove dart from folder name

This commit is contained in:
Krille 2022-12-30 17:54:01 +01:00
commit 5cf18aabbf
40 changed files with 37 additions and 37 deletions

View file

@ -0,0 +1,98 @@
import 'package:flutter/cupertino.dart';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:matrix/matrix.dart';
extension ClientStoriesExtension on Client {
static const String storiesRoomType = 'msc3588.stories.stories-room';
static const String storiesBlockListType = 'msc3588.stories.block-list';
static const int lifeTimeInHours = 24;
static const int maxPostsPerStory = 20;
List<User> get contacts => rooms
.where((room) => room.isDirectChat)
.map((room) =>
room.unsafeGetUserFromMemoryOrFallback(room.directChatMatrixID!))
.toList();
List<Room> get storiesRooms =>
rooms.where((room) => room.isStoryRoom).toList();
Future<List<User>> getUndecidedContactsForStories(Room? storiesRoom) async {
if (storiesRoom == null) return contacts;
final invitedContacts =
(await storiesRoom.requestParticipants()).map((user) => user.id);
final decidedContacts = storiesBlockList.toSet()..addAll(invitedContacts);
return contacts
.where((contact) => !decidedContacts.contains(contact.id))
.toList();
}
List<String> get storiesBlockList =>
accountData[storiesBlockListType]?.content.tryGetList<String>('users') ??
[];
Future<void> setStoriesBlockList(List<String> users) => setAccountData(
userID!,
storiesBlockListType,
{'users': users},
);
Future<Room> createStoriesRoom([List<String>? invite]) async {
final roomId = await createRoom(
creationContent: {"type": "msc3588.stories.stories-room"},
preset: CreateRoomPreset.privateChat,
powerLevelContentOverride: {"events_default": 100},
name: 'Stories from ${userID!.localpart}',
topic:
'This is a room for stories sharing, not unlike the similarly named features in other messaging networks. For best experience please use FluffyChat or minesTrix. Feature development can be followed on: https://github.com/matrix-org/matrix-doc/pull/3588',
initialState: [
StateEvent(
type: EventTypes.Encryption,
stateKey: '',
content: {
'algorithm': 'm.megolm.v1.aes-sha2',
},
),
StateEvent(
type: 'm.room.retention',
stateKey: '',
content: {
'min_lifetime': 86400000,
'max_lifetime': 86400000,
},
),
],
invite: invite,
);
if (getRoomById(roomId) == null) {
// Wait for room actually appears in sync
await onSync.stream
.firstWhere((sync) => sync.rooms?.join?.containsKey(roomId) ?? false);
}
return getRoomById(roomId)!;
}
Future<Room?> getStoriesRoom(BuildContext context) async {
final candidates = rooms.where((room) =>
room.getState(EventTypes.RoomCreate)?.content.tryGet<String>('type') ==
storiesRoomType &&
room.ownPowerLevel >= 100);
if (candidates.isEmpty) return null;
if (candidates.length == 1) return candidates.single;
return await showModalActionSheet<Room>(
context: context,
actions: candidates
.map(
(room) => SheetAction(label: room.displayname, key: room),
)
.toList());
}
}
extension StoryRoom on Room {
bool get isStoryRoom =>
getState(EventTypes.RoomCreate)?.content.tryGet<String>('type') ==
ClientStoriesExtension.storiesRoomType;
}

View file

@ -0,0 +1,45 @@
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
IconData _getIconFromName(String displayname) {
final name = displayname.toLowerCase();
if ({'android'}.any((s) => name.contains(s))) {
return Icons.phone_android_outlined;
}
if ({'ios', 'ipad', 'iphone', 'ipod'}.any((s) => name.contains(s))) {
return Icons.phone_iphone_outlined;
}
if ({
'web',
'http://',
'https://',
'firefox',
'chrome',
'/_matrix',
'safari',
'opera',
}.any((s) => name.contains(s))) {
return Icons.web_outlined;
}
if ({'desktop', 'windows', 'macos', 'linux', 'ubuntu'}
.any((s) => name.contains(s))) {
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);
}

View file

@ -0,0 +1,49 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/utils/size_string.dart';
import 'matrix_file_extension.dart';
extension LocalizedBody on Event {
Future<LoadingDialogResult<MatrixFile?>> _getFile(BuildContext context) =>
showFutureLoadingDialog(
context: context,
future: downloadAndDecryptAttachment,
);
void saveFile(BuildContext context) async {
final matrixFile = await _getFile(context);
matrixFile.result?.save(context);
}
void shareFile(BuildContext context) async {
final matrixFile = await _getFile(context);
matrixFile.result?.share(context);
}
bool get isAttachmentSmallEnough =>
infoMap['size'] is int &&
infoMap['size'] < room.client.database!.maxFileSize;
bool get isThumbnailSmallEnough =>
thumbnailInfoMap['size'] is int &&
thumbnailInfoMap['size'] < 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, dynamic>('info')
?.tryGet<int>('size')
?.sizeString;
}

View file

@ -0,0 +1,45 @@
import 'package:matrix/matrix.dart';
import '../../config/app_config.dart';
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.* events
!type.startsWith('m.key.verification.') &&
// 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
(!AppConfig.hideRedactedEvents || !redacted) &&
// if we enabled to hide all unknown events, don't show those
(!AppConfig.hideUnknownEvents || isEventTypeKnown) &&
// remove state events that we don't want to render
(isState || !AppConfig.hideAllStateEvents) &&
// hide unimportant state events
(!AppConfig.hideUnimportantStateEvents ||
!isState ||
importantStateEvents.contains(type)) &&
// hide simple join/leave member events in public rooms
(!AppConfig.hideUnimportantStateEvents ||
type != EventTypes.RoomMember ||
room.joinRules != JoinRules.public ||
content.tryGet<String>('membership') == 'ban' ||
stateKey != senderId);
static const Set<String> importantStateEvents = {
EventTypes.Encryption,
EventTypes.RoomCreate,
EventTypes.RoomMember,
EventTypes.RoomTombstone,
EventTypes.CallInvite,
};
bool get isState => !{
EventTypes.Message,
EventTypes.Sticker,
EventTypes.Encrypted
}.contains(type);
}

View file

@ -0,0 +1,146 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart' hide Key;
import 'package:flutter/services.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:hive/hive.dart';
import 'package:matrix/matrix.dart';
import 'package:path_provider/path_provider.dart';
class FlutterHiveCollectionsDatabase extends HiveCollectionsDatabase {
FlutterHiveCollectionsDatabase(
String name,
String path, {
HiveCipher? key,
}) : super(
name,
path,
key: key,
);
static const String _cipherStorageKey = 'hive_encryption_key';
static Future<FlutterHiveCollectionsDatabase> databaseBuilder(
Client client) async {
Logs().d('Open Hive...');
HiveAesCipher? hiverCipher;
try {
// Workaround for secure storage is calling Platform.operatingSystem on web
if (kIsWeb) throw MissingPluginException();
const secureStorage = FlutterSecureStorage();
final containsEncryptionKey =
await secureStorage.read(key: _cipherStorageKey) != null;
if (!containsEncryptionKey) {
// do not try to create a buggy secure storage for new Linux users
if (Platform.isLinux) throw MissingPluginException();
final key = Hive.generateSecureKey();
await secureStorage.write(
key: _cipherStorageKey,
value: base64UrlEncode(key),
);
}
// workaround for if we just wrote to the key and it still doesn't exist
final rawEncryptionKey = await secureStorage.read(key: _cipherStorageKey);
if (rawEncryptionKey == null) throw MissingPluginException();
hiverCipher = HiveAesCipher(base64Url.decode(rawEncryptionKey));
} on MissingPluginException catch (_) {
const FlutterSecureStorage()
.delete(key: _cipherStorageKey)
.catchError((_) {});
Logs().i('Hive encryption is not supported on this platform');
} catch (e, s) {
const FlutterSecureStorage()
.delete(key: _cipherStorageKey)
.catchError((_) {});
Logs().w('Unable to init Hive encryption', e, s);
}
final db = FlutterHiveCollectionsDatabase(
'hive_collections_${client.clientName.replaceAll(' ', '_').toLowerCase()}',
await _findDatabasePath(client),
key: hiverCipher,
);
try {
await db.open();
} catch (e, s) {
Logs().w('Unable to open Hive. Delete database and storage key...', e, s);
const FlutterSecureStorage().delete(key: _cipherStorageKey);
await db.clear().catchError((_) {});
await Hive.deleteFromDisk();
rethrow;
}
Logs().d('Hive is ready');
return db;
}
static Future<String> _findDatabasePath(Client client) async {
String path = client.clientName;
if (!kIsWeb) {
Directory directory;
try {
if (Platform.isLinux) {
directory = await getApplicationSupportDirectory();
} else {
directory = await getApplicationDocumentsDirectory();
}
} catch (_) {
try {
directory = await getLibraryDirectory();
} catch (_) {
directory = Directory.current;
}
}
// do not destroy your stable FluffyChat in debug mode
directory = Directory(directory.uri
.resolve(kDebugMode ? 'hive_debug' : 'hive')
.toFilePath());
directory.create(recursive: true);
path = directory.path;
}
return path;
}
@override
int get maxFileSize => supportsFileStoring ? 100 * 1024 * 1024 : 0;
@override
bool get supportsFileStoring => !kIsWeb;
Future<String> _getFileStoreDirectory() async {
try {
try {
return (await getApplicationSupportDirectory()).path;
} catch (_) {
return (await getApplicationDocumentsDirectory()).path;
}
} catch (_) {
return (await getDownloadsDirectory())!.path;
}
}
@override
Future<Uint8List?> getFile(Uri mxcUri) async {
if (!supportsFileStoring) return null;
final tempDirectory = await _getFileStoreDirectory();
final file =
File('$tempDirectory/${Uri.encodeComponent(mxcUri.toString())}');
if (await file.exists() == false) return null;
final bytes = await file.readAsBytes();
return bytes;
}
@override
Future storeFile(Uri mxcUri, Uint8List bytes, int time) async {
if (!supportsFileStoring) return null;
final tempDirectory = await _getFileStoreDirectory();
final file =
File('$tempDirectory/${Uri.encodeComponent(mxcUri.toString())}');
if (await file.exists()) return;
await file.writeAsBytes(bytes);
return;
}
}

View file

@ -0,0 +1,19 @@
import 'package:flutter_app_badger/flutter_app_badger.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/utils/platform_infos.dart';
extension IosBadgeClientExtension on Client {
void updateIosBadge() {
if (PlatformInfos.isIOS) {
// Workaround for iOS not clearing notifications with fcm_shared_isolate
if (!rooms.any((r) =>
r.membership == Membership.invite || (r.notificationCount > 0))) {
// ignore: unawaited_futures
FlutterLocalNotificationsPlugin().cancelAll();
FlutterAppBadger.removeBadge();
}
}
}
}

View file

@ -0,0 +1,56 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:file_picker_cross/file_picker_cross.dart';
import 'package:matrix/matrix.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/utils/size_string.dart';
extension MatrixFileExtension on MatrixFile {
void save(BuildContext context) async {
if (PlatformInfos.isIOS) {
return share(context);
}
final fileName = name.split('/').last;
final file = FilePickerCross(bytes);
await file.exportToStorage(fileName: fileName, share: false);
}
void share(BuildContext context) async {
final fileName = name.split('/').last;
final tmpDirectory = await getTemporaryDirectory();
final path = '${tmpDirectory.path}$fileName';
await File(path).writeAsBytes(bytes);
// 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 Share.shareFiles(
[path],
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;
}

View file

@ -0,0 +1,307 @@
import 'package:flutter_gen/gen_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, String content) {
return l10n.changedTheChatDescriptionTo(senderName, content);
}
@override
String changedTheChatNameTo(String senderName, String content) {
return l10n.changedTheChatNameTo(senderName, content);
}
@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.needPantalaimonWarning;
@override
String get noPermission => l10n.noKeyForThisMessage;
@override
String redactedAnEvent(String senderName) {
return l10n.redactedAnEvent(senderName);
}
@override
String rejectedTheInvitation(String targetName) {
return l10n.rejectedTheInvitation(targetName);
}
@override
String removedBy(String calcDisplayname) {
return l10n.removedBy(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.unknownEvent(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);
}

View file

@ -0,0 +1,40 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart';
import '../date_time_extension.dart';
extension PresenceExtension on CachedPresence {
String getLocalizedLastActiveAgo(BuildContext context) {
final lastActiveTimestamp = this.lastActiveTimestamp;
if (lastActiveTimestamp != null) {
return L10n.of(context)!
.lastActiveAgo(lastActiveTimestamp.localizedTimeShort(context));
}
return L10n.of(context)!.lastSeenLongTimeAgo;
}
String getLocalizedStatusMessage(BuildContext context) {
final statusMsg = this.statusMsg;
if (statusMsg != null && statusMsg.isNotEmpty) {
return statusMsg;
}
if (currentlyActive ?? false) {
return L10n.of(context)!.currentlyActive;
}
return getLocalizedLastActiveAgo(context);
}
Color get color {
switch (presence) {
case PresenceType.online:
return Colors.green;
case PresenceType.offline:
return Colors.grey;
case PresenceType.unavailable:
default:
return Colors.red;
}
}
}