refactor: Structure files in more directories

This commit is contained in:
Christian Pauly 2021-05-22 09:24:39 +02:00
commit 988a691eeb
35 changed files with 55 additions and 55 deletions

View file

@ -0,0 +1,29 @@
import 'package:famedlysdk/famedlysdk.dart';
extension ClientPresenceExtension on Client {
List<Presence> get contactList {
final directChatsMxid = rooms
.where((r) => r.isDirectChat)
.map((r) => r.directChatMatrixID)
.toSet();
final contactList = directChatsMxid
.map(
(mxid) =>
presences[mxid] ??
Presence.fromJson(
{
'sender': mxid,
'type': 'm.presence',
'content': {'presence': 'offline'},
},
),
)
.toList();
contactList.sort((a, b) => a.senderId.compareTo(b.senderId));
contactList.sort((a, b) => (a.presence.lastActiveAgo?.toDouble() ??
double.infinity)
.compareTo((b.presence.lastActiveAgo?.toDouble() ?? double.infinity)));
return contactList;
}
}

View file

@ -0,0 +1,33 @@
import 'package:famedlysdk/famedlysdk.dart';
import 'package:flutter/material.dart';
extension DeviceExtension on Device {
String get displayname =>
(displayName?.isNotEmpty ?? false) ? displayName : 'Unknown device';
IconData get icon => displayname.toLowerCase().contains('android')
? Icons.phone_android_outlined
: displayname.toLowerCase().contains('ios')
? Icons.phone_iphone_outlined
: displayname.toLowerCase().contains('web')
? Icons.web_outlined
: displayname.toLowerCase().contains('desktop')
? Icons.desktop_mac_outlined
: Icons.device_unknown_outlined;
}
extension DeviceKeysExtension on DeviceKeys {
String get displayname => (deviceDisplayName?.isNotEmpty ?? false)
? deviceDisplayName
: 'Unknown device';
IconData get icon => displayname.toLowerCase().contains('android')
? Icons.phone_android_outlined
: displayname.toLowerCase().contains('ios')
? Icons.phone_iphone_outlined
: displayname.toLowerCase().contains('web')
? Icons.web_outlined
: displayname.toLowerCase().contains('desktop')
? Icons.desktop_mac_outlined
: Icons.device_unknown_outlined;
}

View file

@ -0,0 +1,107 @@
import 'package:famedlysdk/famedlysdk.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'matrix_file_extension.dart';
import '../../pages/image_viewer.dart';
extension LocalizedBody on Event {
void openFile(BuildContext context, {bool downloadOnly = false}) async {
if (!downloadOnly &&
[MessageTypes.Image, MessageTypes.Sticker].contains(messageType)) {
await Navigator.of(context, rootNavigator: false).push(
MaterialPageRoute(builder: (_) => ImageViewer(this)),
);
return;
}
final matrixFile = await showFutureLoadingDialog(
context: context,
future: () => downloadAndDecryptAttachmentCached(),
);
matrixFile.result?.open();
}
IconData get statusIcon {
switch (status) {
case -1:
return Icons.error_outline;
case 0:
return Icons.timer_outlined;
case 1:
return Icons.done_outlined;
case 2:
return Icons.done_all_outlined;
default:
return Icons.done_outlined;
}
}
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].contains(messageType) &&
(kIsWeb ||
isAttachmentSmallEnough ||
isThumbnailSmallEnough ||
(content['url'] is String));
String get sizeString {
if (content['info'] is Map<String, dynamic> &&
content['info'].containsKey('size')) {
num size = content['info']['size'];
if (size < 1000000) {
size = size / 1000;
size = (size * 10).round() / 10;
return '${size.toString()} KB';
} else if (size < 1000000000) {
size = size / 1000000;
size = (size * 10).round() / 10;
return '${size.toString()} MB';
} else {
size = size / 1000000000;
size = (size * 10).round() / 10;
return '${size.toString()} GB';
}
} else {
return null;
}
}
static final _downloadAndDecryptFutures = <String, Future<MatrixFile>>{};
Future<bool> isAttachmentCached({bool getThumbnail = false}) async {
final mxcUrl = attachmentOrThumbnailMxcUrl(getThumbnail: getThumbnail);
// check if we have it in-memory
if (_downloadAndDecryptFutures.containsKey(mxcUrl)) {
return true;
}
// check if it is stored
if (await isAttachmentInLocalStore(getThumbnail: getThumbnail)) {
return true;
}
// check if the url is cached
final url = Uri.parse(mxcUrl).getDownloadLink(room.client);
final file = await DefaultCacheManager().getFileFromCache(url.toString());
return file != null;
}
Future<MatrixFile> downloadAndDecryptAttachmentCached(
{bool getThumbnail = false}) async {
final mxcUrl = attachmentOrThumbnailMxcUrl(getThumbnail: getThumbnail);
_downloadAndDecryptFutures[mxcUrl] ??= downloadAndDecryptAttachment(
getThumbnail: getThumbnail,
downloadCallback: (Uri url) async {
final file = await DefaultCacheManager().getSingleFile(url.toString());
return await file.readAsBytes();
},
);
final res = await _downloadAndDecryptFutures[mxcUrl];
return res;
}
}

View file

@ -0,0 +1,53 @@
import 'package:famedlysdk/famedlysdk.dart';
import '../../config/app_config.dart';
extension FilteredTimelineExtension on Timeline {
List<Event> getFilteredEvents({Set<String> unfolded = const {}}) {
final filteredEvents = events
.where((e) =>
// always filter out edit and reaction relationships
!{RelationshipTypes.edit, RelationshipTypes.reaction}
.contains(e.relationshipType) &&
// always filter out m.key.* events
!e.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(e.type) &&
// if we enabled to hide all redacted events, don't show those
(!AppConfig.hideRedactedEvents || !e.redacted) &&
// if we enabled to hide all unknown events, don't show those
(!AppConfig.hideUnknownEvents || e.isEventTypeKnown) &&
// remove state events that we don't want to render
(e.isState || !AppConfig.hideAllStateEvents))
.toList();
// Fold state events
var counter = 0;
for (var i = filteredEvents.length - 1; i >= 0; i--) {
if (!filteredEvents[i].isState) continue;
if (i > 0 &&
filteredEvents[i - 1].isState &&
!unfolded.contains(filteredEvents[i - 1].eventId)) {
counter++;
filteredEvents[i].unsigned['im.fluffychat.collapsed_state_event'] =
true;
} else {
filteredEvents[i].unsigned['im.fluffychat.collapsed_state_event'] =
false;
filteredEvents[i]
.unsigned['im.fluffychat.collapsed_state_event_count'] = counter;
counter = 0;
}
}
return filteredEvents;
}
}
extension IsStateExtension on Event {
bool get isState => !{
EventTypes.Message,
EventTypes.Sticker,
EventTypes.Encrypted
}.contains(type);
}

View file

@ -0,0 +1,38 @@
import 'package:famedlysdk/famedlysdk.dart';
import 'package:famedlysdk/encryption.dart';
import 'package:matrix_api_lite/fake_matrix_api.dart';
import '../platform_infos.dart';
import '../famedlysdk_store.dart';
class FluffyClient extends Client {
static FluffyClient _instance;
/// The ID of the currently active room, if there is one. May be null or emtpy
String activeRoomId;
factory FluffyClient({testMode = false}) {
_instance ??= FluffyClient._internal(testMode: testMode);
return _instance;
}
FluffyClient._internal({testMode = false})
: super(
testMode ? 'FluffyChat Widget Tests' : PlatformInfos.clientName,
httpClient: testMode ? FakeMatrixApi() : null,
enableE2eeRecovery: true,
verificationMethods: {
KeyVerificationMethod.numbers,
if (PlatformInfos.isMobile || PlatformInfos.isLinux)
KeyVerificationMethod.emoji,
},
importantStateEvents: <String>{
'im.ponies.room_emotes', // we want emotes to work properly
},
databaseBuilder: testMode ? null : getDatabase,
supportedLoginTypes: {
AuthenticationTypes.password,
if (PlatformInfos.isMobile || PlatformInfos.isWeb)
AuthenticationTypes.sso
},
);
}

View file

@ -0,0 +1,74 @@
import 'dart:io';
import 'package:android_path_provider/android_path_provider.dart';
import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:flutter/foundation.dart';
import 'package:open_file/open_file.dart';
import 'package:path_provider/path_provider.dart';
import 'package:universal_html/html.dart' as html;
import 'package:mime_type/mime_type.dart';
import 'package:permission_handler/permission_handler.dart';
extension MatrixFileExtension on MatrixFile {
void open() async {
if (kIsWeb) {
final fileName = name.split('/').last;
final mimeType = mime(fileName);
final element = html.document.createElement('a');
element.setAttribute(
'href', html.Url.createObjectUrlFromBlob(html.Blob([bytes])));
element.setAttribute('target', '_blank');
element.setAttribute('rel', 'noopener');
element.setAttribute('download', fileName);
element.setAttribute('type', mimeType);
element.style.display = 'none';
html.document.body.append(element);
element.click();
element.remove();
} else {
if (PlatformInfos.isMobile &&
!(await Permission.storage.request()).isGranted) return;
final downloadsDir = PlatformInfos.isDesktop
? (await getDownloadsDirectory()).path
: Platform.isAndroid
? (await AndroidPathProvider.downloadsPath)
: (await getApplicationDocumentsDirectory()).path;
final file = File(downloadsDir + '/' + name.split('/').last);
file.writeAsBytesSync(bytes);
await OpenFile.open(file.path);
}
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 {
var size = this.size.toDouble();
if (size < 1000000) {
size = size / 1000;
size = (size * 10).round() / 10;
return '${size.toString()} KB';
} else if (size < 1000000000) {
size = size / 1000000;
size = (size * 10).round() / 10;
return '${size.toString()} MB';
} else {
size = size / 1000000000;
size = (size * 10).round() / 10;
return '${size.toString()} GB';
}
}
}

View file

@ -0,0 +1,264 @@
import 'package:famedlysdk/famedlysdk.dart';
import 'package:flutter_gen/gen_l10n/l10n.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.noPermission;
@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;
}

View file

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