diff --git a/assets/images/build/.last_build_id b/assets/images/build/.last_build_id new file mode 100644 index 0000000..9963a26 --- /dev/null +++ b/assets/images/build/.last_build_id @@ -0,0 +1 @@ +5cde0a78d118f9e043e7443ceca0f306 \ No newline at end of file diff --git a/assets/images/default_avatar.svg.vec b/assets/images/default_avatar.svg.vec new file mode 100644 index 0000000..bb4aa7b Binary files /dev/null and b/assets/images/default_avatar.svg.vec differ diff --git a/lib/globals.dart b/lib/globals.dart index 279e447..274507b 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -34,3 +34,6 @@ Map globalUserDataChangedCallBack = {}; bool globalIsAppInBackground = true; bool globalAllowErrorTrackingViaSentry = false; + +late String globalApplicationCacheDirectory; +late String globalApplicationSupportDirectory; diff --git a/lib/main.dart b/lib/main.dart index 1fbe97e..4bd72cd 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,14 +1,7 @@ -// ignore_for_file: unused_import - import 'dart:async'; -import 'dart:io'; - import 'package:camera/camera.dart'; -import 'package:device_info_plus/device_info_plus.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:provider/provider.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; @@ -25,21 +18,13 @@ import 'package:twonly/src/services/fcm.service.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/services/notifications/setup.notifications.dart'; import 'package:twonly/src/services/twonly_safe/create_backup.twonly_safe.dart'; +import 'package:twonly/src/utils/avatars.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/storage.dart'; void main() async { SentryWidgetsFlutterBinding.ensureInitialized(); - // try { - // File(join((await getApplicationSupportDirectory()).path, 'twonly.sqlite')) - // .deleteSync(); - // } catch (e) {} - // await updateUserdata((u) { - // u.appVersion = 0; - // return u; - // }); - final user = await getUser(); if (user != null) { gUser = user; @@ -60,6 +45,10 @@ void main() async { await initFCMService(); + globalApplicationCacheDirectory = (await getApplicationCacheDirectory()).path; + globalApplicationSupportDirectory = + (await getApplicationSupportDirectory()).path; + initLogger(); final settingsController = SettingsChangeProvider(); diff --git a/lib/src/services/api/client2client/contact.c2c.dart b/lib/src/services/api/client2client/contact.c2c.dart index 5a63668..cc699ec 100644 --- a/lib/src/services/api/client2client/contact.c2c.dart +++ b/lib/src/services/api/client2client/contact.c2c.dart @@ -9,7 +9,7 @@ import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; import 'package:twonly/src/services/api/messages.dart'; import 'package:twonly/src/services/api/utils.dart'; import 'package:twonly/src/services/notifications/pushkeys.notifications.dart'; -import 'package:twonly/src/services/notifications/setup.notifications.dart'; +import 'package:twonly/src/utils/avatars.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; diff --git a/lib/src/services/api/client2client/media.c2c.dart b/lib/src/services/api/client2client/media.c2c.dart index f58c3cc..31e2bed 100644 --- a/lib/src/services/api/client2client/media.c2c.dart +++ b/lib/src/services/api/client2client/media.c2c.dart @@ -156,7 +156,7 @@ Future handleMediaUpdate( ); case EncryptedContent_MediaUpdate_Type.STORED: Log.info('Got media file stored ${mediaFile.mediaId}'); - final mediaService = await MediaFileService.fromMedia(mediaFile); + final mediaService = MediaFileService(mediaFile); await mediaService.storeMediaFile(); await twonlyDB.messagesDao.updateMessageId( message.messageId, diff --git a/lib/src/services/api/mediafiles/download.service.dart b/lib/src/services/api/mediafiles/download.service.dart index b5ff2ad..f5108a1 100644 --- a/lib/src/services/api/mediafiles/download.service.dart +++ b/lib/src/services/api/mediafiles/download.service.dart @@ -45,6 +45,9 @@ Map> defaultAutoDownloadOptions = { }; Future isAllowedToDownload(MediaType type) async { + if (type == MediaType.audio) { + return true; // always download audio files + } final connectivityResult = await Connectivity().checkConnectivity(); final options = gUser.autoDownloadOptions ?? defaultAutoDownloadOptions; @@ -117,7 +120,7 @@ Future handleDownloadStatusUpdate(TaskStatusUpdate update) async { Mutex protectDownload = Mutex(); Future startDownloadMedia(MediaFile media, bool force) async { - final mediaService = await MediaFileService.fromMedia(media); + final mediaService = MediaFileService(media); if (mediaService.encryptedPath.existsSync()) { await handleEncryptedFile(media.mediaId); diff --git a/lib/src/services/api/mediafiles/media_background.service.dart b/lib/src/services/api/mediafiles/media_background.service.dart index 6e513d2..dc6c0b9 100644 --- a/lib/src/services/api/mediafiles/media_background.service.dart +++ b/lib/src/services/api/mediafiles/media_background.service.dart @@ -126,7 +126,7 @@ Future handleUploadStatusUpdate(TaskStatusUpdate update) async { Log.error( 'Background upload failed for $mediaId with status ${update.status} and ${update.responseStatusCode}. ', ); - final mediaService = await MediaFileService.fromMedia(media); + final mediaService = MediaFileService(media); await mediaService.setUploadState(UploadState.uploaded); // In all other cases just try the upload again... diff --git a/lib/src/services/api/mediafiles/upload.service.dart b/lib/src/services/api/mediafiles/upload.service.dart index b3a87c6..a6b5672 100644 --- a/lib/src/services/api/mediafiles/upload.service.dart +++ b/lib/src/services/api/mediafiles/upload.service.dart @@ -28,7 +28,7 @@ Future finishStartedPreprocessing() async { continue; } try { - final service = await MediaFileService.fromMedia(mediaFile); + final service = MediaFileService(mediaFile); if (!service.originalPath.existsSync() && !service.uploadRequestPath.existsSync()) { if (service.storedPath.existsSync()) { @@ -78,7 +78,7 @@ Future initializeMediaUpload( ), ); if (mediaFile == null) return null; - return MediaFileService.fromMedia(mediaFile); + return MediaFileService(mediaFile); } Future insertMediaFileInMessagesTable( diff --git a/lib/src/services/mediafiles/mediafile.service.dart b/lib/src/services/mediafiles/mediafile.service.dart index 6ef6b62..0425efa 100644 --- a/lib/src/services/mediafiles/mediafile.service.dart +++ b/lib/src/services/mediafiles/mediafile.service.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'dart:io'; import 'package:drift/drift.dart'; import 'package:path/path.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; @@ -11,31 +10,21 @@ import 'package:twonly/src/services/mediafiles/thumbnail.service.dart'; import 'package:twonly/src/utils/log.dart'; class MediaFileService { - MediaFileService(this.mediaFile, {required this.applicationSupportDirectory}); + MediaFileService(this.mediaFile); MediaFile mediaFile; - final Directory applicationSupportDirectory; - - static Future fromMedia(MediaFile media) async { - return MediaFileService( - media, - applicationSupportDirectory: await getApplicationSupportDirectory(), - ); - } - static Future fromMediaId(String mediaId) async { final mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById(mediaId); if (mediaFile == null) return null; return MediaFileService( mediaFile, - applicationSupportDirectory: await getApplicationSupportDirectory(), ); } static Future purgeTempFolder() async { final tempDirectory = MediaFileService.buildDirectoryPath( 'tmp', - await getApplicationSupportDirectory(), + globalApplicationSupportDirectory, ); final files = tempDirectory.listSync(); @@ -241,11 +230,11 @@ class MediaFileService { static Directory buildDirectoryPath( String directory, - Directory applicationSupportDirectory, + String applicationSupportDirectory, ) { final mediaBaseDir = Directory( join( - applicationSupportDirectory.path, + applicationSupportDirectory, 'mediafiles', directory, ), @@ -275,7 +264,7 @@ class MediaFileService { } } final mediaBaseDir = - buildDirectoryPath(directory, applicationSupportDirectory); + buildDirectoryPath(directory, globalApplicationSupportDirectory); return File( join(mediaBaseDir.path, '${mediaFile.mediaId}$namePrefix.$extension'), ); diff --git a/lib/src/services/notifications/setup.notifications.dart b/lib/src/services/notifications/setup.notifications.dart index 1181354..7cf9e45 100644 --- a/lib/src/services/notifications/setup.notifications.dart +++ b/lib/src/services/notifications/setup.notifications.dart @@ -1,13 +1,7 @@ // ignore_for_file: unreachable_from_main import 'dart:async'; -import 'dart:io'; -import 'dart:ui' as ui; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:twonly/globals.dart'; -import 'package:twonly/src/utils/misc.dart'; final StreamController selectNotificationStream = StreamController.broadcast(); @@ -54,35 +48,3 @@ Future setupPushNotification() async { onDidReceiveBackgroundNotificationResponse: notificationTapBackground, ); } - -Future createPushAvatars() async { - if (!Platform.isAndroid) { - return; // avatars currently only shown in Android... - } - final contacts = await twonlyDB.contactsDao.getAllNotBlockedContacts(); - - for (final contact in contacts) { - if (contact.avatarSvgCompressed == null) continue; - - final avatarSvg = getAvatarSvg(contact.avatarSvgCompressed!); - - final pictureInfo = await vg.loadPicture(SvgStringLoader(avatarSvg), null); - - final image = await pictureInfo.picture.toImage(300, 300); - - final byteData = await image.toByteData(format: ui.ImageByteFormat.png); - final pngBytes = byteData!.buffer.asUint8List(); - - // Get the directory to save the image - final directory = await getApplicationCacheDirectory(); - final avatarsDirectory = Directory('${directory.path}/avatars'); - - // Create the avatars directory if it does not exist - if (!avatarsDirectory.existsSync()) { - await avatarsDirectory.create(recursive: true); - } - final filePath = '${avatarsDirectory.path}/${contact.userId}.png'; - await File(filePath).writeAsBytes(pngBytes); - pictureInfo.picture.dispose(); - } -} diff --git a/lib/src/utils/avatars.dart b/lib/src/utils/avatars.dart new file mode 100644 index 0000000..918ff4e --- /dev/null +++ b/lib/src/utils/avatars.dart @@ -0,0 +1,36 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:ui' as ui; +import 'package:flutter_svg/svg.dart'; +import 'package:twonly/globals.dart'; +import 'package:twonly/src/utils/misc.dart'; + +Future createPushAvatars() async { + final contacts = await twonlyDB.contactsDao.getAllNotBlockedContacts(); + + for (final contact in contacts) { + if (contact.avatarSvgCompressed == null) continue; + + final avatarSvg = getAvatarSvg(contact.avatarSvgCompressed!); + + final pictureInfo = await vg.loadPicture(SvgStringLoader(avatarSvg), null); + + final image = await pictureInfo.picture.toImage(300, 300); + + final byteData = await image.toByteData(format: ui.ImageByteFormat.png); + final pngBytes = byteData!.buffer.asUint8List(); + + await avatarPNGFile(contact.userId).writeAsBytes(pngBytes); + pictureInfo.picture.dispose(); + } +} + +File avatarPNGFile(int contactId) { + final avatarsDirectory = + Directory('$globalApplicationCacheDirectory/avatars'); + + if (!avatarsDirectory.existsSync()) { + avatarsDirectory.createSync(recursive: true); + } + return File('${avatarsDirectory.path}/$contactId.png'); +} diff --git a/lib/src/views/camera/camera_preview_components/camera_preview.dart b/lib/src/views/camera/camera_preview_components/camera_preview.dart index 8cfb61d..6bae7e3 100644 --- a/lib/src/views/camera/camera_preview_components/camera_preview.dart +++ b/lib/src/views/camera/camera_preview_components/camera_preview.dart @@ -20,7 +20,7 @@ class HomeViewCameraPreview extends StatelessWidget { } return Positioned.fill( child: MediaViewSizing( - requiredHeight: 90, + requiredHeight: 80, bottomNavigation: Container(), child: Screenshot( controller: screenshotController, @@ -60,7 +60,7 @@ class SendToCameraPreview extends StatelessWidget { } return Positioned.fill( child: MediaViewSizing( - requiredHeight: 90, + requiredHeight: 80, bottomNavigation: Container(), child: Screenshot( controller: screenshotController, diff --git a/lib/src/views/camera/camera_preview_controller_view.dart b/lib/src/views/camera/camera_preview_controller_view.dart index 49fee04..007f1df 100644 --- a/lib/src/views/camera/camera_preview_controller_view.dart +++ b/lib/src/views/camera/camera_preview_controller_view.dart @@ -598,7 +598,7 @@ class _CameraPreviewViewState extends State { return Container(); } return MediaViewSizing( - requiredHeight: 90, + requiredHeight: 80, bottomNavigation: Container(), child: GestureDetector( onPanStart: (details) async { diff --git a/lib/src/views/camera/share_image_components/best_friends_selector.dart b/lib/src/views/camera/share_image_components/best_friends_selector.dart index 7271518..2f96d48 100644 --- a/lib/src/views/camera/share_image_components/best_friends_selector.dart +++ b/lib/src/views/camera/share_image_components/best_friends_selector.dart @@ -74,6 +74,7 @@ class BestFriendsSelector extends StatelessWidget { children: [ Expanded( child: UserCheckbox( + key: ValueKey(groups[firstUserIndex]), isChecked: selectedGroupIds .contains(groups[firstUserIndex].groupId), group: groups[firstUserIndex], @@ -83,6 +84,7 @@ class BestFriendsSelector extends StatelessWidget { if (secondUserIndex < groups.length) Expanded( child: UserCheckbox( + key: ValueKey(groups[secondUserIndex]), isChecked: selectedGroupIds .contains(groups[secondUserIndex].groupId), group: groups[secondUserIndex], diff --git a/lib/src/views/camera/share_image_editor_view.dart b/lib/src/views/camera/share_image_editor_view.dart index 515e8ce..8ef97c5 100644 --- a/lib/src/views/camera/share_image_editor_view.dart +++ b/lib/src/views/camera/share_image_editor_view.dart @@ -524,7 +524,7 @@ class _ShareImageEditorView extends State { setState(() {}); }, child: MediaViewSizing( - requiredHeight: 90, + requiredHeight: 80, bottomNavigation: ColoredBox( color: Theme.of(context).colorScheme.surface, child: Row( diff --git a/lib/src/views/camera/share_image_view.dart b/lib/src/views/camera/share_image_view.dart index 38fa15f..44ef8c9 100644 --- a/lib/src/views/camera/share_image_view.dart +++ b/lib/src/views/camera/share_image_view.dart @@ -323,6 +323,7 @@ class UserList extends StatelessWidget { itemBuilder: (BuildContext context, int i) { final group = groups[i]; return ListTile( + key: ValueKey(group.groupId), title: Row( children: [ Text(substringBy(group.groupName, 12)), diff --git a/lib/src/views/chats/add_new_user.view.dart b/lib/src/views/chats/add_new_user.view.dart index 2b5acf8..da17408 100644 --- a/lib/src/views/chats/add_new_user.view.dart +++ b/lib/src/views/chats/add_new_user.view.dart @@ -278,6 +278,7 @@ class ContactsListView extends StatelessWidget { itemBuilder: (context, index) { final contact = contacts[index]; return ListTile( + key: ValueKey(contact.userId), title: Text(substringBy(contact.username, 25)), leading: AvatarIcon(contactId: contact.userId), trailing: Row( diff --git a/lib/src/views/chats/chat_messages_components/bottom_sheets/all_reactions.bottom_sheet.dart b/lib/src/views/chats/chat_messages_components/bottom_sheets/all_reactions.bottom_sheet.dart index 011c294..3c45815 100644 --- a/lib/src/views/chats/chat_messages_components/bottom_sheets/all_reactions.bottom_sheet.dart +++ b/lib/src/views/chats/chat_messages_components/bottom_sheets/all_reactions.bottom_sheet.dart @@ -100,6 +100,7 @@ class _AllReactionsViewState extends State { child: ListView( children: reactionsUsers.map((entry) { return GestureDetector( + key: ValueKey(entry), onTap: (entry.$2 != null) ? null : () { diff --git a/lib/src/views/chats/chat_messages_components/chat_list_entry.dart b/lib/src/views/chats/chat_messages_components/chat_list_entry.dart index 496d97f..5f146e9 100644 --- a/lib/src/views/chats/chat_messages_components/chat_list_entry.dart +++ b/lib/src/views/chats/chat_messages_components/chat_list_entry.dart @@ -71,9 +71,9 @@ class _ChatListEntryState extends State { if (widget.message.mediaId != null) { final mediaFileStream = twonlyDB.mediaFilesDao.watchMedia(widget.message.mediaId!); - mediaFileSub = mediaFileStream.listen((mediaFiles) async { + mediaFileSub = mediaFileStream.listen((mediaFiles) { if (mediaFiles != null) { - mediaService = await MediaFileService.fromMedia(mediaFiles); + mediaService = MediaFileService(mediaFiles); if (mounted) setState(() {}); } }); diff --git a/lib/src/views/chats/media_viewer.view.dart b/lib/src/views/chats/media_viewer.view.dart index e3da12f..846b32d 100644 --- a/lib/src/views/chats/media_viewer.view.dart +++ b/lib/src/views/chats/media_viewer.view.dart @@ -513,7 +513,7 @@ class _MediaViewerViewState extends State { }, child: MediaViewSizing( bottomNavigation: bottomNavigation(), - requiredHeight: 90, + requiredHeight: 80, child: Stack( children: [ if (videoController != null) diff --git a/lib/src/views/chats/message_info.view.dart b/lib/src/views/chats/message_info.view.dart index bcf15c8..bfcdb03 100644 --- a/lib/src/views/chats/message_info.view.dart +++ b/lib/src/views/chats/message_info.view.dart @@ -123,6 +123,7 @@ class _MessageInfoViewState extends State { columns.add( Padding( + key: ValueKey(groupMember.$1.contactId), padding: const EdgeInsets.symmetric(vertical: 8), child: Row( children: [ diff --git a/lib/src/views/chats/start_new_chat.view.dart b/lib/src/views/chats/start_new_chat.view.dart index a014939..5689112 100644 --- a/lib/src/views/chats/start_new_chat.view.dart +++ b/lib/src/views/chats/start_new_chat.view.dart @@ -219,7 +219,7 @@ class _StartNewChatView extends State { if (i < filteredContacts.length) { return UserContextMenu( - key: Key(filteredContacts[i].userId.toString()), + key: ValueKey(filteredContacts[i].userId), contact: filteredContacts[i], child: ListTile( title: Row( @@ -259,7 +259,7 @@ class _StartNewChatView extends State { if (i < filteredGroups.length) { return GroupContextMenu( - key: Key(filteredGroups[i].groupId), + key: ValueKey(filteredGroups[i].groupId), group: filteredGroups[i], child: ListTile( title: Text( diff --git a/lib/src/views/components/avatar_icon.component.dart b/lib/src/views/components/avatar_icon.component.dart index 0eca6dd..5b269a7 100644 --- a/lib/src/views/components/avatar_icon.component.dart +++ b/lib/src/views/components/avatar_icon.component.dart @@ -3,7 +3,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/src/database/twonly.db.dart'; +import 'package:twonly/src/utils/avatars.dart'; import 'package:twonly/src/utils/misc.dart'; +import 'package:vector_graphics/vector_graphics.dart'; class AvatarIcon extends StatefulWidget { const AvatarIcon({ @@ -25,8 +27,9 @@ class AvatarIcon extends StatefulWidget { } class _AvatarIconState extends State { - List _avatarSVGs = []; + List _avatarContacts = []; String? _globalUserDataCallBackId; + String? _avatarSvg; StreamSubscription>? groupStream; StreamSubscription>? contactsStream; @@ -49,20 +52,44 @@ class _AvatarIconState extends State { super.dispose(); } + // ignore: strict_top_level_inference + Widget errorBuilder(_, __, ___) { + return const SvgPicture( + AssetBytesLoader('assets/images/default_avatar.svg.vec'), + ); + } + + Widget getAvatarForContact(Contact contact) { + final avatarFile = avatarPNGFile(contact.userId); + if (avatarFile.existsSync()) { + return Image.file( + avatarFile, + errorBuilder: errorBuilder, + ); + } + if (contact.avatarSvgCompressed != null) { + return SvgPicture.string( + getAvatarSvg(contact.avatarSvgCompressed!), + errorBuilder: errorBuilder, + ); + } + return errorBuilder(null, null, null); + } + Future initAsync() async { if (widget.group != null) { groupStream = twonlyDB.groupsDao .watchGroupContact(widget.group!.groupId) .listen((contacts) { - _avatarSVGs = []; + _avatarContacts = []; if (contacts.length == 1) { if (contacts.first.avatarSvgCompressed != null) { - _avatarSVGs.add(getAvatarSvg(contacts.first.avatarSvgCompressed!)); + _avatarContacts.add(contacts.first); } } else { for (final contact in contacts) { if (contact.avatarSvgCompressed != null) { - _avatarSVGs.add(getAvatarSvg(contact.avatarSvgCompressed!)); + _avatarContacts.add(contact); } } } @@ -73,21 +100,21 @@ class _AvatarIconState extends State { globalUserDataChangedCallBack[_globalUserDataCallBackId!] = () { setState(() { if (gUser.avatarSvg != null) { - _avatarSVGs = [gUser.avatarSvg!]; + _avatarSvg = gUser.avatarSvg; } else { - _avatarSVGs = []; + _avatarContacts = []; } }); }; if (gUser.avatarSvg != null) { - _avatarSVGs = [gUser.avatarSvg!]; + _avatarSvg = gUser.avatarSvg; } } else if (widget.contactId != null) { contactStream = twonlyDB.contactsDao .watchContact(widget.contactId!) .listen((contact) { if (contact != null && contact.avatarSvgCompressed != null) { - _avatarSVGs = [getAvatarSvg(contact.avatarSvgCompressed!)]; + _avatarContacts = [contact]; setState(() {}); } }); @@ -99,27 +126,20 @@ class _AvatarIconState extends State { Widget build(BuildContext context) { final proSize = (widget.fontSize == null) ? 40 : (widget.fontSize! * 2); - Widget avatars = SvgPicture.asset('assets/images/default_avatar.svg'); + Widget avatars = Container(); - if (_avatarSVGs.length == 1) { + if (_avatarSvg != null) { avatars = SvgPicture.string( - _avatarSVGs.first, - errorBuilder: (a, b, c) => avatars, + _avatarSvg!, + errorBuilder: errorBuilder, ); - } else if (_avatarSVGs.length >= 2) { - final a = SvgPicture.string( - _avatarSVGs.first, - errorBuilder: (a, b, c) => avatars, - ); - final b = SvgPicture.string( - _avatarSVGs[1], - errorBuilder: (a, b, c) => avatars, - ); - if (_avatarSVGs.length >= 3) { - final c = SvgPicture.string( - _avatarSVGs[2], - errorBuilder: (a, b, c) => avatars, - ); + } else if (_avatarContacts.length == 1) { + avatars = getAvatarForContact(_avatarContacts.first); + } else if (_avatarContacts.length >= 2) { + final a = getAvatarForContact(_avatarContacts.first); + final b = getAvatarForContact(_avatarContacts[1]); + if (_avatarContacts.length >= 3) { + final c = getAvatarForContact(_avatarContacts[2]); avatars = Stack( children: [ Transform.translate( @@ -153,6 +173,10 @@ class _AvatarIconState extends State { ], ); } + } else { + avatars = const SvgPicture( + AssetBytesLoader('assets/images/default_avatar.svg.vec'), + ); } return Container( diff --git a/lib/src/views/contact/contact.view.dart b/lib/src/views/contact/contact.view.dart index d911a1b..aba75e8 100644 --- a/lib/src/views/contact/contact.view.dart +++ b/lib/src/views/contact/contact.view.dart @@ -100,6 +100,7 @@ class _ContactViewState extends State { } final contact = snapshot.data!; return ListView( + key: ValueKey(contact.userId), children: [ Padding( padding: const EdgeInsets.all(10), diff --git a/lib/src/views/groups/group.view.dart b/lib/src/views/groups/group.view.dart index 4d495ac..918068b 100644 --- a/lib/src/views/groups/group.view.dart +++ b/lib/src/views/groups/group.view.dart @@ -228,6 +228,7 @@ class _GroupViewState extends State { ), ...members.map((member) { return GroupMemberContextMenu( + key: ValueKey(member.$1.userId), group: group, contact: member.$1, member: member.$2, diff --git a/lib/src/views/groups/group_create_select_group_name.view.dart b/lib/src/views/groups/group_create_select_group_name.view.dart index 3af27e5..ca7bf9f 100644 --- a/lib/src/views/groups/group_create_select_group_name.view.dart +++ b/lib/src/views/groups/group_create_select_group_name.view.dart @@ -101,6 +101,7 @@ class _GroupCreateSelectGroupNameViewState itemBuilder: (BuildContext context, int i) { final user = widget.selectedUsers[i]; return UserContextMenu( + key: ValueKey(user.userId), contact: user, child: ListTile( title: Row( diff --git a/lib/src/views/groups/group_create_select_members.view.dart b/lib/src/views/groups/group_create_select_members.view.dart index 0b2ca38..dedf974 100644 --- a/lib/src/views/groups/group_create_select_members.view.dart +++ b/lib/src/views/groups/group_create_select_members.view.dart @@ -188,8 +188,7 @@ class _StartNewChatView extends State { } final user = contacts[i]; return UserContextMenu( - // when this is not set, then the avatar is not updated in the list when searching :/ - key: Key(user.userId.toString()), + key: ValueKey(user.userId), contact: user, child: ListTile( title: Row( diff --git a/lib/src/views/home.view.dart b/lib/src/views/home.view.dart index 53ae88b..05b3320 100644 --- a/lib/src/views/home.view.dart +++ b/lib/src/views/home.view.dart @@ -151,13 +151,12 @@ class HomeViewState extends State { final draftMedia = await twonlyDB.mediaFilesDao.getDraftMediaFile(); if (draftMedia != null) { - final service = await MediaFileService.fromMedia(draftMedia); if (!mounted) return; await Navigator.push( context, MaterialPageRoute( builder: (context) => ShareImageEditorView( - mediaFileService: service, + mediaFileService: MediaFileService(draftMedia), sharedFromGallery: true, ), ), diff --git a/lib/src/views/memories/memories.view.dart b/lib/src/views/memories/memories.view.dart index e44f48d..524cd5d 100644 --- a/lib/src/views/memories/memories.view.dart +++ b/lib/src/views/memories/memories.view.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; @@ -47,13 +46,8 @@ class MemoriesViewState extends State { months = []; var lastMonth = ''; galleryItems = []; - final applicationSupportDirectory = - await getApplicationSupportDirectory(); for (final mediaFile in mediaFiles) { - final mediaService = MediaFileService( - mediaFile, - applicationSupportDirectory: applicationSupportDirectory, - ); + final mediaService = MediaFileService(mediaFile); if (!mediaService.imagePreviewAvailable) continue; if (mediaService.mediaFile.type == MediaType.video) { if (!mediaService.thumbnailPath.existsSync()) { diff --git a/lib/src/views/settings/data_and_storage.view.dart b/lib/src/views/settings/data_and_storage.view.dart index 21b0848..52cd299 100644 --- a/lib/src/views/settings/data_and_storage.view.dart +++ b/lib/src/views/settings/data_and_storage.view.dart @@ -107,7 +107,9 @@ class _DataAndStorageViewState extends State { ListTile( title: Text(context.lang.settingsStorageDataAutoDownMobile), subtitle: Text( - autoDownloadOptions[ConnectivityResult.mobile.name]!.join(', '), + autoDownloadOptions[ConnectivityResult.mobile.name]! + .where((e) => e != 'audio') + .join(', '), style: const TextStyle(color: Colors.grey), ), onTap: () async { @@ -117,7 +119,9 @@ class _DataAndStorageViewState extends State { ListTile( title: Text(context.lang.settingsStorageDataAutoDownWifi), subtitle: Text( - autoDownloadOptions[ConnectivityResult.wifi.name]!.join(', '), + autoDownloadOptions[ConnectivityResult.wifi.name]! + .where((e) => e != 'audio') + .join(', '), style: const TextStyle(color: Colors.grey), ), onTap: () async { @@ -178,14 +182,6 @@ class _AutoDownloadOptionsDialogState extends State { await _updateAutoDownloadSetting(DownloadMediaTypes.video, value); }, ), - CheckboxListTile( - title: const Text('Audio'), - value: autoDownloadOptions[widget.connectionMode.name]! - .contains(DownloadMediaTypes.audio.name), - onChanged: (bool? value) async { - await _updateAutoDownloadSetting(DownloadMediaTypes.audio, value); - }, - ), ], ), actions: [ diff --git a/lib/src/views/settings/data_and_storage/export_media.view.dart b/lib/src/views/settings/data_and_storage/export_media.view.dart index 9fd087c..5abace4 100644 --- a/lib/src/views/settings/data_and_storage/export_media.view.dart +++ b/lib/src/views/settings/data_and_storage/export_media.view.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; +import 'package:twonly/globals.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/utils/log.dart'; @@ -44,12 +45,12 @@ class _ExportMediaViewState extends State { bool _zipSaved = false; bool _isStoring = false; - Future _mediaFolder() async { + Directory _mediaFolder() { final dir = MediaFileService.buildDirectoryPath( 'stored', - await getApplicationSupportDirectory(), + globalApplicationSupportDirectory, ); - if (!dir.existsSync()) await dir.create(recursive: true); + if (!dir.existsSync()) dir.createSync(recursive: true); return dir; } @@ -62,7 +63,7 @@ class _ExportMediaViewState extends State { }); try { - final folder = await _mediaFolder(); + final folder = _mediaFolder(); final allFiles = folder.listSync(recursive: true).whereType().toList(); diff --git a/lib/src/views/settings/data_and_storage/import_media.view.dart b/lib/src/views/settings/data_and_storage/import_media.view.dart index d21999e..e7b771b 100644 --- a/lib/src/views/settings/data_and_storage/import_media.view.dart +++ b/lib/src/views/settings/data_and_storage/import_media.view.dart @@ -118,7 +118,7 @@ class _ImportMediaViewState extends State { stored: const Value(true), ), ); - final mediaService = await MediaFileService.fromMedia(mediaFile!); + final mediaService = MediaFileService(mediaFile!); await mediaService.storedPath.writeAsBytes(file.content); processed++; diff --git a/lib/src/views/settings/privacy_view_block.users.dart b/lib/src/views/settings/privacy_view_block.users.dart index d80f581..e0038c3 100644 --- a/lib/src/views/settings/privacy_view_block.users.dart +++ b/lib/src/views/settings/privacy_view_block.users.dart @@ -105,6 +105,7 @@ class UserList extends StatelessWidget { itemBuilder: (BuildContext context, int i) { final user = users[i]; return UserContextMenu( + key: ValueKey(user.userId), contact: user, child: ListTile( title: Row( diff --git a/lib/src/views/updates/62_database_migration.view.dart b/lib/src/views/updates/62_database_migration.view.dart index fb3bb01..aac2625 100644 --- a/lib/src/views/updates/62_database_migration.view.dart +++ b/lib/src/views/updates/62_database_migration.view.dart @@ -130,7 +130,7 @@ class _DatabaseMigrationViewState extends State { stored: const Value(true), ), ); - final mediaService = await MediaFileService.fromMedia(mediaFile!); + final mediaService = MediaFileService(mediaFile!); File(file.path).copySync(mediaService.storedPath.path); setState(() { _storedMediaFiles += 1; diff --git a/pubspec.lock b/pubspec.lock index 48f6fde..e381a20 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1822,7 +1822,7 @@ packages: source: hosted version: "4.5.2" vector_graphics: - dependency: transitive + dependency: "direct main" description: name: vector_graphics sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 diff --git a/pubspec.yaml b/pubspec.yaml index 0cf26b5..ab0fe5c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: "twonly, a privacy-friendly way to connect with friends through sec publish_to: 'none' -version: 0.0.70+70 +version: 0.0.71+71 environment: sdk: ^3.6.0 @@ -75,6 +75,7 @@ dependencies: share_plus: ^12.0.0 tutorial_coach_mark: ^1.3.0 url_launcher: ^6.3.1 + vector_graphics: ^1.1.19 video_player: ^2.9.5 web_socket_channel: ^3.0.1