fix avatar render issues, fix list update issues
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run

This commit is contained in:
otsmr 2025-11-18 22:56:42 +01:00
parent e00700d4e3
commit c3e92ac7d1
31 changed files with 129 additions and 124 deletions

View file

@ -0,0 +1 @@
5cde0a78d118f9e043e7443ceca0f306

Binary file not shown.

View file

@ -34,3 +34,6 @@ Map<String, VoidCallback> globalUserDataChangedCallBack = {};
bool globalIsAppInBackground = true; bool globalIsAppInBackground = true;
bool globalAllowErrorTrackingViaSentry = false; bool globalAllowErrorTrackingViaSentry = false;
late String globalApplicationCacheDirectory;
late String globalApplicationSupportDirectory;

View file

@ -1,14 +1,7 @@
// ignore_for_file: unused_import
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:camera/camera.dart'; 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/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:sentry_flutter/sentry_flutter.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/mediafiles/mediafile.service.dart';
import 'package:twonly/src/services/notifications/setup.notifications.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/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/log.dart';
import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/utils/storage.dart';
void main() async { void main() async {
SentryWidgetsFlutterBinding.ensureInitialized(); 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(); final user = await getUser();
if (user != null) { if (user != null) {
gUser = user; gUser = user;
@ -60,6 +45,10 @@ void main() async {
await initFCMService(); await initFCMService();
globalApplicationCacheDirectory = (await getApplicationCacheDirectory()).path;
globalApplicationSupportDirectory =
(await getApplicationSupportDirectory()).path;
initLogger(); initLogger();
final settingsController = SettingsChangeProvider(); final settingsController = SettingsChangeProvider();

View file

@ -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/messages.dart';
import 'package:twonly/src/services/api/utils.dart'; import 'package:twonly/src/services/api/utils.dart';
import 'package:twonly/src/services/notifications/pushkeys.notifications.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/log.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';

View file

@ -156,7 +156,7 @@ Future<void> handleMediaUpdate(
); );
case EncryptedContent_MediaUpdate_Type.STORED: case EncryptedContent_MediaUpdate_Type.STORED:
Log.info('Got media file stored ${mediaFile.mediaId}'); Log.info('Got media file stored ${mediaFile.mediaId}');
final mediaService = await MediaFileService.fromMedia(mediaFile); final mediaService = MediaFileService(mediaFile);
await mediaService.storeMediaFile(); await mediaService.storeMediaFile();
await twonlyDB.messagesDao.updateMessageId( await twonlyDB.messagesDao.updateMessageId(
message.messageId, message.messageId,

View file

@ -117,7 +117,7 @@ Future<void> handleDownloadStatusUpdate(TaskStatusUpdate update) async {
Mutex protectDownload = Mutex(); Mutex protectDownload = Mutex();
Future<void> startDownloadMedia(MediaFile media, bool force) async { Future<void> startDownloadMedia(MediaFile media, bool force) async {
final mediaService = await MediaFileService.fromMedia(media); final mediaService = MediaFileService(media);
if (mediaService.encryptedPath.existsSync()) { if (mediaService.encryptedPath.existsSync()) {
await handleEncryptedFile(media.mediaId); await handleEncryptedFile(media.mediaId);

View file

@ -126,7 +126,7 @@ Future<void> handleUploadStatusUpdate(TaskStatusUpdate update) async {
Log.error( Log.error(
'Background upload failed for $mediaId with status ${update.status} and ${update.responseStatusCode}. ', '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); await mediaService.setUploadState(UploadState.uploaded);
// In all other cases just try the upload again... // In all other cases just try the upload again...

View file

@ -28,7 +28,7 @@ Future<void> finishStartedPreprocessing() async {
continue; continue;
} }
try { try {
final service = await MediaFileService.fromMedia(mediaFile); final service = MediaFileService(mediaFile);
if (!service.originalPath.existsSync() && if (!service.originalPath.existsSync() &&
!service.uploadRequestPath.existsSync()) { !service.uploadRequestPath.existsSync()) {
if (service.storedPath.existsSync()) { if (service.storedPath.existsSync()) {
@ -78,7 +78,7 @@ Future<MediaFileService?> initializeMediaUpload(
), ),
); );
if (mediaFile == null) return null; if (mediaFile == null) return null;
return MediaFileService.fromMedia(mediaFile); return MediaFileService(mediaFile);
} }
Future<void> insertMediaFileInMessagesTable( Future<void> insertMediaFileInMessagesTable(

View file

@ -2,7 +2,6 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:path/path.dart'; import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart';
import 'package:twonly/src/database/twonly.db.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'; import 'package:twonly/src/utils/log.dart';
class MediaFileService { class MediaFileService {
MediaFileService(this.mediaFile, {required this.applicationSupportDirectory}); MediaFileService(this.mediaFile);
MediaFile mediaFile; MediaFile mediaFile;
final Directory applicationSupportDirectory;
static Future<MediaFileService> fromMedia(MediaFile media) async {
return MediaFileService(
media,
applicationSupportDirectory: await getApplicationSupportDirectory(),
);
}
static Future<MediaFileService?> fromMediaId(String mediaId) async { static Future<MediaFileService?> fromMediaId(String mediaId) async {
final mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById(mediaId); final mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById(mediaId);
if (mediaFile == null) return null; if (mediaFile == null) return null;
return MediaFileService( return MediaFileService(
mediaFile, mediaFile,
applicationSupportDirectory: await getApplicationSupportDirectory(),
); );
} }
static Future<void> purgeTempFolder() async { static Future<void> purgeTempFolder() async {
final tempDirectory = MediaFileService.buildDirectoryPath( final tempDirectory = MediaFileService.buildDirectoryPath(
'tmp', 'tmp',
await getApplicationSupportDirectory(), globalApplicationSupportDirectory,
); );
final files = tempDirectory.listSync(); final files = tempDirectory.listSync();
@ -241,11 +230,11 @@ class MediaFileService {
static Directory buildDirectoryPath( static Directory buildDirectoryPath(
String directory, String directory,
Directory applicationSupportDirectory, String applicationSupportDirectory,
) { ) {
final mediaBaseDir = Directory( final mediaBaseDir = Directory(
join( join(
applicationSupportDirectory.path, applicationSupportDirectory,
'mediafiles', 'mediafiles',
directory, directory,
), ),
@ -275,7 +264,7 @@ class MediaFileService {
} }
} }
final mediaBaseDir = final mediaBaseDir =
buildDirectoryPath(directory, applicationSupportDirectory); buildDirectoryPath(directory, globalApplicationSupportDirectory);
return File( return File(
join(mediaBaseDir.path, '${mediaFile.mediaId}$namePrefix.$extension'), join(mediaBaseDir.path, '${mediaFile.mediaId}$namePrefix.$extension'),
); );

View file

@ -1,13 +1,7 @@
// ignore_for_file: unreachable_from_main // ignore_for_file: unreachable_from_main
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'dart:ui' as ui;
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; 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<NotificationResponse> selectNotificationStream = final StreamController<NotificationResponse> selectNotificationStream =
StreamController<NotificationResponse>.broadcast(); StreamController<NotificationResponse>.broadcast();
@ -54,35 +48,3 @@ Future<void> setupPushNotification() async {
onDidReceiveBackgroundNotificationResponse: notificationTapBackground, onDidReceiveBackgroundNotificationResponse: notificationTapBackground,
); );
} }
Future<void> 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();
}
}

View file

@ -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<void> 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');
}

View file

@ -323,6 +323,7 @@ class UserList extends StatelessWidget {
itemBuilder: (BuildContext context, int i) { itemBuilder: (BuildContext context, int i) {
final group = groups[i]; final group = groups[i];
return ListTile( return ListTile(
key: ValueKey(group.groupId),
title: Row( title: Row(
children: [ children: [
Text(substringBy(group.groupName, 12)), Text(substringBy(group.groupName, 12)),

View file

@ -278,6 +278,7 @@ class ContactsListView extends StatelessWidget {
itemBuilder: (context, index) { itemBuilder: (context, index) {
final contact = contacts[index]; final contact = contacts[index];
return ListTile( return ListTile(
key: ValueKey(contact.userId),
title: Text(substringBy(contact.username, 25)), title: Text(substringBy(contact.username, 25)),
leading: AvatarIcon(contactId: contact.userId), leading: AvatarIcon(contactId: contact.userId),
trailing: Row( trailing: Row(

View file

@ -100,6 +100,7 @@ class _AllReactionsViewState extends State<AllReactionsView> {
child: ListView( child: ListView(
children: reactionsUsers.map((entry) { children: reactionsUsers.map((entry) {
return GestureDetector( return GestureDetector(
key: ValueKey(entry),
onTap: (entry.$2 != null) onTap: (entry.$2 != null)
? null ? null
: () { : () {

View file

@ -71,9 +71,9 @@ class _ChatListEntryState extends State<ChatListEntry> {
if (widget.message.mediaId != null) { if (widget.message.mediaId != null) {
final mediaFileStream = final mediaFileStream =
twonlyDB.mediaFilesDao.watchMedia(widget.message.mediaId!); twonlyDB.mediaFilesDao.watchMedia(widget.message.mediaId!);
mediaFileSub = mediaFileStream.listen((mediaFiles) async { mediaFileSub = mediaFileStream.listen((mediaFiles) {
if (mediaFiles != null) { if (mediaFiles != null) {
mediaService = await MediaFileService.fromMedia(mediaFiles); mediaService = MediaFileService(mediaFiles);
if (mounted) setState(() {}); if (mounted) setState(() {});
} }
}); });

View file

@ -123,6 +123,7 @@ class _MessageInfoViewState extends State<MessageInfoView> {
columns.add( columns.add(
Padding( Padding(
key: ValueKey(groupMember.$1.contactId),
padding: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.symmetric(vertical: 8),
child: Row( child: Row(
children: [ children: [

View file

@ -219,7 +219,7 @@ class _StartNewChatView extends State<StartNewChatView> {
if (i < filteredContacts.length) { if (i < filteredContacts.length) {
return UserContextMenu( return UserContextMenu(
key: Key(filteredContacts[i].userId.toString()), key: ValueKey(filteredContacts[i].userId),
contact: filteredContacts[i], contact: filteredContacts[i],
child: ListTile( child: ListTile(
title: Row( title: Row(
@ -259,7 +259,7 @@ class _StartNewChatView extends State<StartNewChatView> {
if (i < filteredGroups.length) { if (i < filteredGroups.length) {
return GroupContextMenu( return GroupContextMenu(
key: Key(filteredGroups[i].groupId), key: ValueKey(filteredGroups[i].groupId),
group: filteredGroups[i], group: filteredGroups[i],
child: ListTile( child: ListTile(
title: Text( title: Text(

View file

@ -3,7 +3,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/database/twonly.db.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:twonly/src/utils/misc.dart';
import 'package:vector_graphics/vector_graphics.dart';
class AvatarIcon extends StatefulWidget { class AvatarIcon extends StatefulWidget {
const AvatarIcon({ const AvatarIcon({
@ -25,8 +27,9 @@ class AvatarIcon extends StatefulWidget {
} }
class _AvatarIconState extends State<AvatarIcon> { class _AvatarIconState extends State<AvatarIcon> {
List<String> _avatarSVGs = []; List<Contact> _avatarContacts = [];
String? _globalUserDataCallBackId; String? _globalUserDataCallBackId;
String? _avatarSvg;
StreamSubscription<List<Contact>>? groupStream; StreamSubscription<List<Contact>>? groupStream;
StreamSubscription<List<Contact>>? contactsStream; StreamSubscription<List<Contact>>? contactsStream;
@ -49,20 +52,43 @@ class _AvatarIconState extends State<AvatarIcon> {
super.dispose(); super.dispose();
} }
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<void> initAsync() async { Future<void> initAsync() async {
if (widget.group != null) { if (widget.group != null) {
groupStream = twonlyDB.groupsDao groupStream = twonlyDB.groupsDao
.watchGroupContact(widget.group!.groupId) .watchGroupContact(widget.group!.groupId)
.listen((contacts) { .listen((contacts) {
_avatarSVGs = []; _avatarContacts = [];
if (contacts.length == 1) { if (contacts.length == 1) {
if (contacts.first.avatarSvgCompressed != null) { if (contacts.first.avatarSvgCompressed != null) {
_avatarSVGs.add(getAvatarSvg(contacts.first.avatarSvgCompressed!)); _avatarContacts.add(contacts.first);
} }
} else { } else {
for (final contact in contacts) { for (final contact in contacts) {
if (contact.avatarSvgCompressed != null) { if (contact.avatarSvgCompressed != null) {
_avatarSVGs.add(getAvatarSvg(contact.avatarSvgCompressed!)); _avatarContacts.add(contact);
} }
} }
} }
@ -73,21 +99,21 @@ class _AvatarIconState extends State<AvatarIcon> {
globalUserDataChangedCallBack[_globalUserDataCallBackId!] = () { globalUserDataChangedCallBack[_globalUserDataCallBackId!] = () {
setState(() { setState(() {
if (gUser.avatarSvg != null) { if (gUser.avatarSvg != null) {
_avatarSVGs = [gUser.avatarSvg!]; _avatarSvg = gUser.avatarSvg;
} else { } else {
_avatarSVGs = []; _avatarContacts = [];
} }
}); });
}; };
if (gUser.avatarSvg != null) { if (gUser.avatarSvg != null) {
_avatarSVGs = [gUser.avatarSvg!]; _avatarSvg = gUser.avatarSvg;
} }
} else if (widget.contactId != null) { } else if (widget.contactId != null) {
contactStream = twonlyDB.contactsDao contactStream = twonlyDB.contactsDao
.watchContact(widget.contactId!) .watchContact(widget.contactId!)
.listen((contact) { .listen((contact) {
if (contact != null && contact.avatarSvgCompressed != null) { if (contact != null && contact.avatarSvgCompressed != null) {
_avatarSVGs = [getAvatarSvg(contact.avatarSvgCompressed!)]; _avatarContacts = [contact];
setState(() {}); setState(() {});
} }
}); });
@ -99,27 +125,20 @@ class _AvatarIconState extends State<AvatarIcon> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final proSize = (widget.fontSize == null) ? 40 : (widget.fontSize! * 2); 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( avatars = SvgPicture.string(
_avatarSVGs.first, _avatarSvg!,
errorBuilder: (a, b, c) => avatars, errorBuilder: errorBuilder,
); );
} else if (_avatarSVGs.length >= 2) { } else if (_avatarContacts.length == 1) {
final a = SvgPicture.string( avatars = getAvatarForContact(_avatarContacts.first);
_avatarSVGs.first, } else if (_avatarContacts.length >= 2) {
errorBuilder: (a, b, c) => avatars, final a = getAvatarForContact(_avatarContacts.first);
); final b = getAvatarForContact(_avatarContacts[1]);
final b = SvgPicture.string( if (_avatarContacts.length >= 3) {
_avatarSVGs[1], final c = getAvatarForContact(_avatarContacts[2]);
errorBuilder: (a, b, c) => avatars,
);
if (_avatarSVGs.length >= 3) {
final c = SvgPicture.string(
_avatarSVGs[2],
errorBuilder: (a, b, c) => avatars,
);
avatars = Stack( avatars = Stack(
children: [ children: [
Transform.translate( Transform.translate(
@ -153,6 +172,10 @@ class _AvatarIconState extends State<AvatarIcon> {
], ],
); );
} }
} else {
avatars = const SvgPicture(
AssetBytesLoader('assets/images/default_avatar.svg.vec'),
);
} }
return Container( return Container(

View file

@ -100,6 +100,7 @@ class _ContactViewState extends State<ContactView> {
} }
final contact = snapshot.data!; final contact = snapshot.data!;
return ListView( return ListView(
key: ValueKey(contact.userId),
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),

View file

@ -228,6 +228,7 @@ class _GroupViewState extends State<GroupView> {
), ),
...members.map((member) { ...members.map((member) {
return GroupMemberContextMenu( return GroupMemberContextMenu(
key: ValueKey(member.$1.userId),
group: group, group: group,
contact: member.$1, contact: member.$1,
member: member.$2, member: member.$2,

View file

@ -101,6 +101,7 @@ class _GroupCreateSelectGroupNameViewState
itemBuilder: (BuildContext context, int i) { itemBuilder: (BuildContext context, int i) {
final user = widget.selectedUsers[i]; final user = widget.selectedUsers[i];
return UserContextMenu( return UserContextMenu(
key: ValueKey(user.userId),
contact: user, contact: user,
child: ListTile( child: ListTile(
title: Row( title: Row(

View file

@ -188,8 +188,7 @@ class _StartNewChatView extends State<GroupCreateSelectMembersView> {
} }
final user = contacts[i]; final user = contacts[i];
return UserContextMenu( return UserContextMenu(
// when this is not set, then the avatar is not updated in the list when searching :/ key: ValueKey(user.userId),
key: Key(user.userId.toString()),
contact: user, contact: user,
child: ListTile( child: ListTile(
title: Row( title: Row(

View file

@ -151,13 +151,12 @@ class HomeViewState extends State<HomeView> {
final draftMedia = await twonlyDB.mediaFilesDao.getDraftMediaFile(); final draftMedia = await twonlyDB.mediaFilesDao.getDraftMediaFile();
if (draftMedia != null) { if (draftMedia != null) {
final service = await MediaFileService.fromMedia(draftMedia);
if (!mounted) return; if (!mounted) return;
await Navigator.push( await Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => ShareImageEditorView( builder: (context) => ShareImageEditorView(
mediaFileService: service, mediaFileService: MediaFileService(draftMedia),
sharedFromGallery: true, sharedFromGallery: true,
), ),
), ),

View file

@ -1,7 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:path_provider/path_provider.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart';
import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/database/twonly.db.dart';
@ -47,13 +46,8 @@ class MemoriesViewState extends State<MemoriesView> {
months = []; months = [];
var lastMonth = ''; var lastMonth = '';
galleryItems = []; galleryItems = [];
final applicationSupportDirectory =
await getApplicationSupportDirectory();
for (final mediaFile in mediaFiles) { for (final mediaFile in mediaFiles) {
final mediaService = MediaFileService( final mediaService = MediaFileService(mediaFile);
mediaFile,
applicationSupportDirectory: applicationSupportDirectory,
);
if (!mediaService.imagePreviewAvailable) continue; if (!mediaService.imagePreviewAvailable) continue;
if (mediaService.mediaFile.type == MediaType.video) { if (mediaService.mediaFile.type == MediaType.video) {
if (!mediaService.thumbnailPath.existsSync()) { if (!mediaService.thumbnailPath.existsSync()) {

View file

@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart'; 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/services/mediafiles/mediafile.service.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
@ -44,12 +45,12 @@ class _ExportMediaViewState extends State<ExportMediaView> {
bool _zipSaved = false; bool _zipSaved = false;
bool _isStoring = false; bool _isStoring = false;
Future<Directory> _mediaFolder() async { Directory _mediaFolder() {
final dir = MediaFileService.buildDirectoryPath( final dir = MediaFileService.buildDirectoryPath(
'stored', 'stored',
await getApplicationSupportDirectory(), globalApplicationSupportDirectory,
); );
if (!dir.existsSync()) await dir.create(recursive: true); if (!dir.existsSync()) dir.createSync(recursive: true);
return dir; return dir;
} }
@ -62,7 +63,7 @@ class _ExportMediaViewState extends State<ExportMediaView> {
}); });
try { try {
final folder = await _mediaFolder(); final folder = _mediaFolder();
final allFiles = final allFiles =
folder.listSync(recursive: true).whereType<File>().toList(); folder.listSync(recursive: true).whereType<File>().toList();

View file

@ -118,7 +118,7 @@ class _ImportMediaViewState extends State<ImportMediaView> {
stored: const Value(true), stored: const Value(true),
), ),
); );
final mediaService = await MediaFileService.fromMedia(mediaFile!); final mediaService = MediaFileService(mediaFile!);
await mediaService.storedPath.writeAsBytes(file.content); await mediaService.storedPath.writeAsBytes(file.content);
processed++; processed++;

View file

@ -105,6 +105,7 @@ class UserList extends StatelessWidget {
itemBuilder: (BuildContext context, int i) { itemBuilder: (BuildContext context, int i) {
final user = users[i]; final user = users[i];
return UserContextMenu( return UserContextMenu(
key: ValueKey(user.userId),
contact: user, contact: user,
child: ListTile( child: ListTile(
title: Row( title: Row(

View file

@ -130,7 +130,7 @@ class _DatabaseMigrationViewState extends State<DatabaseMigrationView> {
stored: const Value(true), stored: const Value(true),
), ),
); );
final mediaService = await MediaFileService.fromMedia(mediaFile!); final mediaService = MediaFileService(mediaFile!);
File(file.path).copySync(mediaService.storedPath.path); File(file.path).copySync(mediaService.storedPath.path);
setState(() { setState(() {
_storedMediaFiles += 1; _storedMediaFiles += 1;

View file

@ -1822,7 +1822,7 @@ packages:
source: hosted source: hosted
version: "4.5.2" version: "4.5.2"
vector_graphics: vector_graphics:
dependency: transitive dependency: "direct main"
description: description:
name: vector_graphics name: vector_graphics
sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6

View file

@ -75,6 +75,7 @@ dependencies:
share_plus: ^12.0.0 share_plus: ^12.0.0
tutorial_coach_mark: ^1.3.0 tutorial_coach_mark: ^1.3.0
url_launcher: ^6.3.1 url_launcher: ^6.3.1
vector_graphics: ^1.1.19
video_player: ^2.9.5 video_player: ^2.9.5
web_socket_channel: ^3.0.1 web_socket_channel: ^3.0.1