fix issue with media shares via intent

This commit is contained in:
otsmr 2026-08-23 00:46:57 +02:00
parent 280519d63a
commit 0c16188877
5 changed files with 89 additions and 53 deletions

View file

@ -6,6 +6,7 @@
- Improve: Backup screen clearer and easier to understand - Improve: Backup screen clearer and easier to understand
- Improve: Show username above messages in group chats - Improve: Show username above messages in group chats
- Fix: Background audio correctly pauses and resumes when viewing videos - Fix: Background audio correctly pauses and resumes when viewing videos
- Fix: Multiple bug fixes
## 0.5.0 ## 0.5.0

View file

@ -10,6 +10,7 @@ import 'package:twonly/globals.dart';
import 'package:twonly/locator.dart'; import 'package:twonly/locator.dart';
import 'package:twonly/src/constants/keyvalue.keys.dart'; import 'package:twonly/src/constants/keyvalue.keys.dart';
import 'package:twonly/src/constants/routes.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/localization/generated/app_localizations.dart';
import 'package:twonly/src/model/json/onboarding_state.model.dart'; import 'package:twonly/src/model/json/onboarding_state.model.dart';
import 'package:twonly/src/providers/routing.provider.dart'; import 'package:twonly/src/providers/routing.provider.dart';
@ -180,9 +181,17 @@ class _AppMainWidgetState extends State<AppMainWidget> {
} }
}); });
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( _intentStreamSub = initIntentStreams(
context, context,
handleShareLink, handleShareLink,
handleShareMedia,
); );
} }

View file

@ -227,11 +227,19 @@ Future<void> finishStartedPreprocessing() async {
mediaFile.mediaId, mediaFile.mediaId,
); );
if (messages.isEmpty) { if (messages.isEmpty) {
Log.info( if (mediaFile.createdAt.isBefore(
'Deleted orphaned media file ${mediaFile.mediaId} as no messages reference it.', clock.now().subtract(const Duration(hours: 1)),
); )) {
MediaFileService(mediaFile).fullMediaRemoval(); Log.info(
await twonlyDB.mediaFilesDao.deleteMediaFile(mediaFile.mediaId); '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; continue;
} }
@ -484,12 +492,20 @@ Future<void> _startBackgroundMediaUploadInternal(
mediaService.mediaFile.mediaId, mediaService.mediaFile.mediaId,
); );
if (messages.isEmpty) { if (messages.isEmpty) {
Log.warn( if (mediaService.mediaFile.createdAt.isBefore(
'Media files ${mediaService.mediaFile.mediaId} has no original, temp, or stored path. Removing it from DB as files are not existent.', clock.now().subtract(const Duration(hours: 1)),
); )) {
await twonlyDB.mediaFilesDao.deleteMediaFile( Log.warn(
mediaService.mediaFile.mediaId, '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 { } else {
Log.warn( 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.', 'Media files ${mediaService.mediaFile.mediaId} has no original, temp, or stored path, but messages still reference it. Marking as uploaded to stop retries.',

View file

@ -1,7 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.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/constants/routes.keys.dart';
import 'package:twonly/src/database/tables/contacts.table.dart'; import 'package:twonly/src/database/tables/contacts.table.dart';
import 'package:twonly/src/database/tables/mediafiles.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' import 'package:twonly/src/services/passwordless_recovery.service.dart'
show PasswordlessRecoveryService; show PasswordlessRecoveryService;
import 'package:twonly/src/services/signal/session.signal.dart'; 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/misc.dart';
import 'package:twonly/src/utils/qr.utils.dart'; import 'package:twonly/src/utils/qr.utils.dart';
import 'package:twonly/src/visual/components/alert.dialog.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_contact_via_qr_link.view.dart';
import 'package:twonly/src/visual/views/contact/add_new_contact.view.dart'; import 'package:twonly/src/visual/views/contact/add_new_contact.view.dart';
@ -149,53 +145,20 @@ Future<void> _pubKeysDoNotMatch(BuildContext context, String username) async {
); );
} }
Future<void> 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<List<SharedFile>> initIntentStreams( StreamSubscription<List<SharedFile>> initIntentStreams(
BuildContext context, BuildContext context,
void Function(Uri) onUrlCallBack, void Function(Uri) onUrlCallBack,
void Function(String, MediaType) onMediaCallBack,
) { ) {
FlutterSharingIntent.instance.getInitialSharing().then((f) { FlutterSharingIntent.instance.getInitialSharing().then((f) {
if (!context.mounted) return; if (!context.mounted) return;
handleIntentSharedFile(context, f, onUrlCallBack); handleIntentSharedFile(context, f, onUrlCallBack, onMediaCallBack);
}); });
return FlutterSharingIntent.instance.getMediaStream().listen( return FlutterSharingIntent.instance.getMediaStream().listen(
(f) { (f) {
if (!context.mounted) return; if (!context.mounted) return;
handleIntentSharedFile(context, f, onUrlCallBack); handleIntentSharedFile(context, f, onUrlCallBack, onMediaCallBack);
}, },
// ignore: inference_failure_on_untyped_parameter // ignore: inference_failure_on_untyped_parameter
onError: (err) { onError: (err) {
@ -208,6 +171,7 @@ Future<void> handleIntentSharedFile(
BuildContext context, BuildContext context,
List<SharedFile> files, List<SharedFile> files,
void Function(Uri) onUrlCallBack, void Function(Uri) onUrlCallBack,
void Function(String, MediaType) onMediaCallBack,
) async { ) async {
for (final file in files) { for (final file in files) {
if (file.value == null) { if (file.value == null) {
@ -231,9 +195,9 @@ Future<void> handleIntentSharedFile(
if (file.value!.endsWith('.gif')) { if (file.value!.endsWith('.gif')) {
type = MediaType.gif; type = MediaType.gif;
} }
await handleIntentMediaFile(context, file.value!, type); onMediaCallBack(file.value!, type);
case SharedMediaType.VIDEO: case SharedMediaType.VIDEO:
await handleIntentMediaFile(context, file.value!, MediaType.video); onMediaCallBack(file.value!, MediaType.video);
// ignore: no_default_cases // ignore: no_default_cases
default: default:
} }

View file

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.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/globals.dart';
import 'package:twonly/locator.dart'; import 'package:twonly/locator.dart';
import 'package:twonly/src/constants/routes.keys.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/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/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/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
@ -47,10 +50,14 @@ class HomeViewState extends State<HomeView> with WidgetsBindingObserver {
StreamSubscription<RemoteMessage>? _onMessageOpenedAppSub; StreamSubscription<RemoteMessage>? _onMessageOpenedAppSub;
StreamSubscription<int>? _homeViewPageIndexSub; StreamSubscription<int>? _homeViewPageIndexSub;
StreamSubscription<NotificationResponse>? _selectNotificationSub; StreamSubscription<NotificationResponse>? _selectNotificationSub;
StreamSubscription<(String, MediaType)>? _sharedMediaSub;
static Uri? pendingSharedLink; static Uri? pendingSharedLink;
static (String, MediaType)? pendingSharedMedia;
static final streamHomeViewPageIndex = StreamController<int>.broadcast(); static final streamHomeViewPageIndex = StreamController<int>.broadcast();
static final streamSharedLink = StreamController<Uri>.broadcast(); static final streamSharedLink = StreamController<Uri>.broadcast();
static final streamSharedMedia =
StreamController<(String, MediaType)>.broadcast();
@override @override
void initState() { void initState() {
@ -59,6 +66,8 @@ class HomeViewState extends State<HomeView> with WidgetsBindingObserver {
var initialPage = widget.initialPage; var initialPage = widget.initialPage;
if (HomeViewState.pendingSharedLink != null) { if (HomeViewState.pendingSharedLink != null) {
initialPage = 1; initialPage = 1;
} else if (HomeViewState.pendingSharedMedia != null) {
initialPage = 0;
} else if (initialPage == 1 && } else if (initialPage == 1 &&
!userService.currentUser.startWithCameraOpen) { !userService.currentUser.startWithCameraOpen) {
initialPage = 0; initialPage = 0;
@ -114,12 +123,48 @@ class HomeViewState extends State<HomeView> 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) { if (HomeViewState.pendingSharedLink != null) {
final link = HomeViewState.pendingSharedLink!; final link = HomeViewState.pendingSharedLink!;
HomeViewState.pendingSharedLink = null; HomeViewState.pendingSharedLink = null;
_mainCameraController.setSharedLinkForPreview(link); _mainCameraController.setSharedLinkForPreview(link);
} }
if (HomeViewState.pendingSharedMedia != null) {
final media = HomeViewState.pendingSharedMedia!;
HomeViewState.pendingSharedMedia = null;
streamSharedMedia.add(media);
}
if (initialPage == 1) { if (initialPage == 1) {
Permission.camera.isGranted.then((hasPermission) { Permission.camera.isGranted.then((hasPermission) {
if (hasPermission && mounted) { if (hasPermission && mounted) {
@ -204,6 +249,7 @@ class HomeViewState extends State<HomeView> with WidgetsBindingObserver {
_mainCameraController.setState = null; _mainCameraController.setState = null;
_mainCameraController.closeCamera(); _mainCameraController.closeCamera();
_sharedLinkSub?.cancel(); _sharedLinkSub?.cancel();
_sharedMediaSub?.cancel();
super.dispose(); super.dispose();
} }