diff --git a/CHANGELOG.md b/CHANGELOG.md index 7acc585b..debf9e9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Improve: Backup screen clearer and easier to understand - Improve: Show username above messages in group chats - Fix: Background audio correctly pauses and resumes when viewing videos +- Fix: Multiple bug fixes ## 0.5.0 diff --git a/lib/app.dart b/lib/app.dart index 020fe05e..c383e546 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -10,6 +10,7 @@ import 'package:twonly/globals.dart'; import 'package:twonly/locator.dart'; import 'package:twonly/src/constants/keyvalue.keys.dart'; import 'package:twonly/src/constants/routes.keys.dart'; +import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/localization/generated/app_localizations.dart'; import 'package:twonly/src/model/json/onboarding_state.model.dart'; import 'package:twonly/src/providers/routing.provider.dart'; @@ -180,9 +181,17 @@ class _AppMainWidgetState extends State { } }); + void handleShareMedia(String path, MediaType type) { + HomeViewState.pendingSharedMedia = (path, type); + routerProvider.go(Routes.home); + HomeViewState.streamHomeViewPageIndex.add(0); + HomeViewState.streamSharedMedia.add((path, type)); + } + _intentStreamSub = initIntentStreams( context, handleShareLink, + handleShareMedia, ); } diff --git a/lib/src/services/api/mediafiles/upload.api.dart b/lib/src/services/api/mediafiles/upload.api.dart index e6b37675..e8ba7f39 100644 --- a/lib/src/services/api/mediafiles/upload.api.dart +++ b/lib/src/services/api/mediafiles/upload.api.dart @@ -227,11 +227,19 @@ Future finishStartedPreprocessing() async { mediaFile.mediaId, ); if (messages.isEmpty) { - Log.info( - 'Deleted orphaned media file ${mediaFile.mediaId} as no messages reference it.', - ); - MediaFileService(mediaFile).fullMediaRemoval(); - await twonlyDB.mediaFilesDao.deleteMediaFile(mediaFile.mediaId); + if (mediaFile.createdAt.isBefore( + clock.now().subtract(const Duration(hours: 1)), + )) { + Log.info( + 'Deleted orphaned media file ${mediaFile.mediaId} as no messages reference it.', + ); + MediaFileService(mediaFile).fullMediaRemoval(); + await twonlyDB.mediaFilesDao.deleteMediaFile(mediaFile.mediaId); + } else { + Log.info( + 'Media file ${mediaFile.mediaId} has no messages, but is too new to be deleted by finishStartedPreprocessing. Skipping.', + ); + } continue; } @@ -484,12 +492,20 @@ Future _startBackgroundMediaUploadInternal( mediaService.mediaFile.mediaId, ); if (messages.isEmpty) { - Log.warn( - 'Media files ${mediaService.mediaFile.mediaId} has no original, temp, or stored path. Removing it from DB as files are not existent.', - ); - await twonlyDB.mediaFilesDao.deleteMediaFile( - mediaService.mediaFile.mediaId, - ); + if (mediaService.mediaFile.createdAt.isBefore( + clock.now().subtract(const Duration(hours: 1)), + )) { + Log.warn( + 'Media files ${mediaService.mediaFile.mediaId} has no original, temp, or stored path. Removing it from DB as files are not existent.', + ); + await twonlyDB.mediaFilesDao.deleteMediaFile( + mediaService.mediaFile.mediaId, + ); + } else { + Log.warn( + 'Media files ${mediaService.mediaFile.mediaId} has no paths, but is too new to be deleted. Skipping deletion.', + ); + } } else { Log.warn( 'Media files ${mediaService.mediaFile.mediaId} has no original, temp, or stored path, but messages still reference it. Marking as uploaded to stop retries.', diff --git a/lib/src/services/intent/links.intent.dart b/lib/src/services/intent/links.intent.dart index 054397ec..71b7bb93 100644 --- a/lib/src/services/intent/links.intent.dart +++ b/lib/src/services/intent/links.intent.dart @@ -1,7 +1,5 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:io'; - import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -12,7 +10,6 @@ import 'package:twonly/locator.dart'; import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/database/tables/contacts.table.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; -import 'package:twonly/src/services/api/mediafiles/upload.api.dart'; import 'package:twonly/src/services/passwordless_recovery.service.dart' show PasswordlessRecoveryService; import 'package:twonly/src/services/signal/session.signal.dart'; @@ -20,7 +17,6 @@ import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/qr.utils.dart'; import 'package:twonly/src/visual/components/alert.dialog.dart'; -import 'package:twonly/src/visual/views/camera/share_image_editor.view.dart'; import 'package:twonly/src/visual/views/contact/add_contact_via_qr_link.view.dart'; import 'package:twonly/src/visual/views/contact/add_new_contact.view.dart'; @@ -149,53 +145,20 @@ Future _pubKeysDoNotMatch(BuildContext context, String username) async { ); } -Future handleIntentMediaFile( - BuildContext context, - String filePath, - MediaType type, -) async { - final file = File(filePath); - if (!file.existsSync()) { - Log.error('The shared intent file does not exits.'); - return; - } - - final newMediaService = await initializeMediaUpload( - type, - userService.currentUser.defaultShowTime, - ); - if (newMediaService == null) { - Log.error('Could not create new media file for intent shared file'); - return; - } - - file.copySync(newMediaService.originalPath.path); - if (!context.mounted) return; - - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ShareImageEditorView( - mediaFileService: newMediaService, - sharedFromGallery: true, - ), - ), - ); -} - StreamSubscription> initIntentStreams( BuildContext context, void Function(Uri) onUrlCallBack, + void Function(String, MediaType) onMediaCallBack, ) { FlutterSharingIntent.instance.getInitialSharing().then((f) { if (!context.mounted) return; - handleIntentSharedFile(context, f, onUrlCallBack); + handleIntentSharedFile(context, f, onUrlCallBack, onMediaCallBack); }); return FlutterSharingIntent.instance.getMediaStream().listen( (f) { if (!context.mounted) return; - handleIntentSharedFile(context, f, onUrlCallBack); + handleIntentSharedFile(context, f, onUrlCallBack, onMediaCallBack); }, // ignore: inference_failure_on_untyped_parameter onError: (err) { @@ -208,6 +171,7 @@ Future handleIntentSharedFile( BuildContext context, List files, void Function(Uri) onUrlCallBack, + void Function(String, MediaType) onMediaCallBack, ) async { for (final file in files) { if (file.value == null) { @@ -231,9 +195,9 @@ Future handleIntentSharedFile( if (file.value!.endsWith('.gif')) { type = MediaType.gif; } - await handleIntentMediaFile(context, file.value!, type); + onMediaCallBack(file.value!, type); case SharedMediaType.VIDEO: - await handleIntentMediaFile(context, file.value!, MediaType.video); + onMediaCallBack(file.value!, MediaType.video); // ignore: no_default_cases default: } diff --git a/lib/src/visual/views/home.view.dart b/lib/src/visual/views/home.view.dart index 7c80aead..b86a3c55 100644 --- a/lib/src/visual/views/home.view.dart +++ b/lib/src/visual/views/home.view.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; @@ -9,7 +10,9 @@ import 'package:permission_handler/permission_handler.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/locator.dart'; import 'package:twonly/src/constants/routes.keys.dart'; +import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/providers/routing.provider.dart'; +import 'package:twonly/src/services/api/mediafiles/upload.api.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/services/notifications/setup.notifications.dart'; import 'package:twonly/src/utils/log.dart'; @@ -47,10 +50,14 @@ class HomeViewState extends State with WidgetsBindingObserver { StreamSubscription? _onMessageOpenedAppSub; StreamSubscription? _homeViewPageIndexSub; StreamSubscription? _selectNotificationSub; + StreamSubscription<(String, MediaType)>? _sharedMediaSub; static Uri? pendingSharedLink; + static (String, MediaType)? pendingSharedMedia; static final streamHomeViewPageIndex = StreamController.broadcast(); static final streamSharedLink = StreamController.broadcast(); + static final streamSharedMedia = + StreamController<(String, MediaType)>.broadcast(); @override void initState() { @@ -59,6 +66,8 @@ class HomeViewState extends State with WidgetsBindingObserver { var initialPage = widget.initialPage; if (HomeViewState.pendingSharedLink != null) { initialPage = 1; + } else if (HomeViewState.pendingSharedMedia != null) { + initialPage = 0; } else if (initialPage == 1 && !userService.currentUser.startWithCameraOpen) { initialPage = 0; @@ -114,12 +123,48 @@ class HomeViewState extends State with WidgetsBindingObserver { }); }); + _sharedMediaSub = streamSharedMedia.stream.listen((media) async { + HomeViewState.pendingSharedMedia = null; + final type = media.$2; + final filePath = media.$1; + + final newMediaService = await initializeMediaUpload( + type, + userService.currentUser.defaultShowTime, + ); + if (newMediaService == null) { + Log.error('Could not create new media file for intent shared file'); + return; + } + + final file = File(filePath); + if (!file.existsSync()) { + Log.error('The shared intent file does not exist.'); + return; + } + file.copySync(newMediaService.originalPath.path); + if (!mounted) return; + + await context.navPush( + ShareImageEditorView( + mediaFileService: newMediaService, + sharedFromGallery: true, + ), + ); + }); + if (HomeViewState.pendingSharedLink != null) { final link = HomeViewState.pendingSharedLink!; HomeViewState.pendingSharedLink = null; _mainCameraController.setSharedLinkForPreview(link); } + if (HomeViewState.pendingSharedMedia != null) { + final media = HomeViewState.pendingSharedMedia!; + HomeViewState.pendingSharedMedia = null; + streamSharedMedia.add(media); + } + if (initialPage == 1) { Permission.camera.isGranted.then((hasPermission) { if (hasPermission && mounted) { @@ -204,6 +249,7 @@ class HomeViewState extends State with WidgetsBindingObserver { _mainCameraController.setState = null; _mainCameraController.closeCamera(); _sharedLinkSub?.cancel(); + _sharedMediaSub?.cancel(); super.dispose(); }