allow setting default outside video player

This commit is contained in:
Thibault Deckers 2022-11-27 20:05:40 +01:00
parent 25a8c1145c
commit f9c2156fed
10 changed files with 24 additions and 11 deletions

View file

@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
## <a id="unreleased"></a>[Unreleased] ## <a id="unreleased"></a>[Unreleased]
### Changed
- Viewer: allow setting default outside video player
## <a id="v1.7.7"></a>[v1.7.7] - 2022-11-27 ## <a id="v1.7.7"></a>[v1.7.7] - 2022-11-27
### Added ### Added

View file

@ -232,7 +232,8 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
val title = call.argument<String>("title") val title = call.argument<String>("title")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) } val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val mimeType = call.argument<String>("mimeType") val mimeType = call.argument<String>("mimeType")
if (uri == null) { val forceChooser = call.argument<Boolean>("forceChooser")
if (uri == null || forceChooser == null) {
result.error("open-args", "missing arguments", null) result.error("open-args", "missing arguments", null)
return return
} }
@ -240,7 +241,7 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
val intent = Intent(Intent.ACTION_VIEW) val intent = Intent(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.setDataAndType(getShareableUri(context, uri), mimeType) .setDataAndType(getShareableUri(context, uri), mimeType)
val started = safeStartActivityChooser(title, intent) val started = if (forceChooser) safeStartActivityChooser(title, intent) else safeStartActivity(intent)
result.success(started) result.success(started)
} }

View file

@ -34,6 +34,7 @@ enum EntryAction {
// external // external
edit, edit,
open, open,
openVideo,
openMap, openMap,
setAs, setAs,
// platform // platform
@ -191,6 +192,7 @@ extension ExtraEntryAction on EntryAction {
case EntryAction.edit: case EntryAction.edit:
return context.l10n.entryActionEdit; return context.l10n.entryActionEdit;
case EntryAction.open: case EntryAction.open:
case EntryAction.openVideo:
return context.l10n.entryActionOpen; return context.l10n.entryActionOpen;
case EntryAction.openMap: case EntryAction.openMap:
return context.l10n.entryActionOpenMap; return context.l10n.entryActionOpenMap;
@ -302,6 +304,7 @@ extension ExtraEntryAction on EntryAction {
case EntryAction.edit: case EntryAction.edit:
return AIcons.edit; return AIcons.edit;
case EntryAction.open: case EntryAction.open:
case EntryAction.openVideo:
return AIcons.openOutside; return AIcons.openOutside;
case EntryAction.openMap: case EntryAction.openMap:
return AIcons.map; return AIcons.map;

View file

@ -16,7 +16,7 @@ abstract class AndroidAppService {
Future<bool> edit(String uri, String mimeType); Future<bool> edit(String uri, String mimeType);
Future<bool> open(String uri, String mimeType); Future<bool> open(String uri, String mimeType, {required bool forceChooser});
Future<bool> openMap(LatLng latLng); Future<bool> openMap(LatLng latLng);
@ -101,11 +101,12 @@ class PlatformAndroidAppService implements AndroidAppService {
} }
@override @override
Future<bool> open(String uri, String mimeType) async { Future<bool> open(String uri, String mimeType, {required bool forceChooser}) async {
try { try {
final result = await _platform.invokeMethod('open', <String, dynamic>{ final result = await _platform.invokeMethod('open', <String, dynamic>{
'uri': uri, 'uri': uri,
'mimeType': mimeType, 'mimeType': mimeType,
'forceChooser': forceChooser,
}); });
if (result != null) return result as bool; if (result != null) return result as bool;
} on PlatformException catch (e, stack) { } on PlatformException catch (e, stack) {

View file

@ -58,12 +58,13 @@ class _AvesSelectionDialogState<T> extends State<AvesSelectionDialog<T>> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final title = widget.title; final title = widget.title;
final message = widget.message; final message = widget.message;
final verticalPadding = (title == null && message == null) ? AvesDialog.cornerRadius.y / 2 : .0;
final confirmationButtonLabel = widget.confirmationButtonLabel; final confirmationButtonLabel = widget.confirmationButtonLabel;
final needConfirmation = confirmationButtonLabel != null; final needConfirmation = confirmationButtonLabel != null;
return AvesDialog( return AvesDialog(
title: title, title: title,
scrollableContent: [ scrollableContent: [
if (title == null && message == null) SizedBox(height: AvesDialog.cornerRadius.y / 2), if (verticalPadding != 0) SizedBox(height: verticalPadding),
if (message != null) if (message != null)
Padding( Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
@ -82,6 +83,7 @@ class _AvesSelectionDialogState<T> extends State<AvesSelectionDialog<T>> {
setGroupValue: (v) => setState(() => _selectedValue = v), setGroupValue: (v) => setState(() => _selectedValue = v),
); );
}), }),
if (verticalPadding != 0) SizedBox(height: verticalPadding),
], ],
actions: [ actions: [
TextButton( TextButton(

View file

@ -93,6 +93,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
case EntryAction.videoTogglePlay: case EntryAction.videoTogglePlay:
case EntryAction.videoReplay10: case EntryAction.videoReplay10:
case EntryAction.videoSkip10: case EntryAction.videoSkip10:
case EntryAction.openVideo:
return targetEntry.isVideo; return targetEntry.isVideo;
case EntryAction.rotateScreen: case EntryAction.rotateScreen:
return settings.isRotationLocked; return settings.isRotationLocked;
@ -225,6 +226,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
case EntryAction.videoTogglePlay: case EntryAction.videoTogglePlay:
case EntryAction.videoReplay10: case EntryAction.videoReplay10:
case EntryAction.videoSkip10: case EntryAction.videoSkip10:
case EntryAction.openVideo:
final controller = context.read<VideoConductor>().getController(targetEntry); final controller = context.read<VideoConductor>().getController(targetEntry);
if (controller != null) { if (controller != null) {
VideoActionNotification(controller: controller, action: action).dispatch(context); VideoActionNotification(controller: controller, action: action).dispatch(context);
@ -236,7 +238,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
}); });
break; break;
case EntryAction.open: case EntryAction.open:
androidAppService.open(targetEntry.uri, targetEntry.mimeTypeAnySubtype).then((success) { androidAppService.open(targetEntry.uri, targetEntry.mimeTypeAnySubtype, forceChooser: true).then((success) {
if (!success) showNoMatchingAppDialog(context); if (!success) showNoMatchingAppDialog(context);
}); });
break; break;

View file

@ -54,7 +54,7 @@ class EmbeddedDataOpener extends StatelessWidget with FeedbackMixin {
final uri = fields['uri']!; final uri = fields['uri']!;
if (!MimeTypes.isImage(mimeType) && !MimeTypes.isVideo(mimeType)) { if (!MimeTypes.isImage(mimeType) && !MimeTypes.isVideo(mimeType)) {
// open with another app // open with another app
unawaited(androidAppService.open(uri, mimeType).then((success) { unawaited(androidAppService.open(uri, mimeType, forceChooser: true).then((success) {
if (!success) { if (!success) {
// fallback to sharing, so that the file can be saved somewhere // fallback to sharing, so that the file can be saved somewhere
androidAppService.shareSingle(uri, mimeType).then((success) { androidAppService.shareSingle(uri, mimeType).then((success) {

View file

@ -66,7 +66,7 @@ class VideoControlRow extends StatelessWidget {
final trashed = controller?.entry.trashed ?? false; final trashed = controller?.entry.trashed ?? false;
return Padding( return Padding(
padding: const EdgeInsetsDirectional.only(start: padding), padding: const EdgeInsetsDirectional.only(start: padding),
child: _buildIconButton(context, EntryAction.open, enabled: !trashed), child: _buildIconButton(context, EntryAction.openVideo, enabled: !trashed),
); );
case VideoControls.none: case VideoControls.none:
return const SizedBox(); return const SizedBox();

View file

@ -46,7 +46,7 @@ class _VideoControlOverlayState extends State<VideoControlOverlay> with SingleTi
final status = controller?.status ?? VideoStatus.idle; final status = controller?.status ?? VideoStatus.idle;
if (status == VideoStatus.error) { if (status == VideoStatus.error) {
const action = EntryAction.open; const action = EntryAction.openVideo;
return Align( return Align(
alignment: AlignmentDirectional.centerEnd, alignment: AlignmentDirectional.centerEnd,
child: OverlayButton( child: OverlayButton(

View file

@ -65,9 +65,9 @@ class VideoActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
case EntryAction.videoSkip10: case EntryAction.videoSkip10:
await controller.seekTo(controller.currentPosition + 10000); await controller.seekTo(controller.currentPosition + 10000);
break; break;
case EntryAction.open: case EntryAction.openVideo:
final entry = controller.entry; final entry = controller.entry;
await androidAppService.open(entry.uri, entry.mimeTypeAnySubtype).then((success) { await androidAppService.open(entry.uri, entry.mimeTypeAnySubtype, forceChooser: false).then((success) {
if (!success) showNoMatchingAppDialog(context); if (!success) showNoMatchingAppDialog(context);
}); });
break; break;