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: Show username above messages in group chats
- Fix: Background audio correctly pauses and resumes when viewing videos
- Fix: Multiple bug fixes
## 0.5.0

View file

@ -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<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(
context,
handleShareLink,
handleShareMedia,
);
}

View file

@ -227,11 +227,19 @@ Future<void> 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<void> _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.',

View file

@ -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<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(
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<void> handleIntentSharedFile(
BuildContext context,
List<SharedFile> 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<void> 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:
}

View file

@ -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<HomeView> with WidgetsBindingObserver {
StreamSubscription<RemoteMessage>? _onMessageOpenedAppSub;
StreamSubscription<int>? _homeViewPageIndexSub;
StreamSubscription<NotificationResponse>? _selectNotificationSub;
StreamSubscription<(String, MediaType)>? _sharedMediaSub;
static Uri? pendingSharedLink;
static (String, MediaType)? pendingSharedMedia;
static final streamHomeViewPageIndex = StreamController<int>.broadcast();
static final streamSharedLink = StreamController<Uri>.broadcast();
static final streamSharedMedia =
StreamController<(String, MediaType)>.broadcast();
@override
void initState() {
@ -59,6 +66,8 @@ class HomeViewState extends State<HomeView> 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<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) {
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<HomeView> with WidgetsBindingObserver {
_mainCameraController.setState = null;
_mainCameraController.closeCamera();
_sharedLinkSub?.cancel();
_sharedMediaSub?.cancel();
super.dispose();
}