#437 tv: read-only, ambient mode, webview, settings

This commit is contained in:
Thibault Deckers 2022-12-08 23:50:34 +01:00
parent 829ec201eb
commit e5e1a8f275
27 changed files with 221 additions and 124 deletions

View file

@ -11,6 +11,13 @@ This change eventually prevents building the app with Flutter v3.3.3.
package="deckers.thibault.aves"
android:installLocation="auto">
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<uses-feature
android:name="android.software.leanback"
android:required="false" />
<!--
Scoped storage on Android 10 is inconvenient because users need to confirm edition on each individual file.
So we request `WRITE_EXTERNAL_STORAGE` until Android 10 (API 29), and enable `requestLegacyExternalStorage`
@ -67,6 +74,7 @@ This change eventually prevents building the app with Flutter v3.3.3.
<application
android:allowBackup="true"
android:appCategory="image"
android:banner="@drawable/banner"
android:fullBackupOnly="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
@ -83,6 +91,8 @@ This change eventually prevents building the app with Flutter v3.3.3.
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>

View file

@ -20,11 +20,15 @@ class ActivityWindowHandler(private val activity: Activity) : WindowHandler(acti
val window = activity.window
val flag = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
val old = (window.attributes.flags and flag) != 0
if (old != on) {
if (on) {
window.addFlags(flag)
} else {
window.clearFlags(flag)
}
}
result.success(null)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 KiB

View file

@ -195,6 +195,7 @@
"nameConflictStrategySkip": "Skip",
"keepScreenOnNever": "Never",
"keepScreenOnVideoPlayback": "During video playback",
"keepScreenOnViewerOnly": "Viewer page only",
"keepScreenOnAlways": "Always",

View file

@ -1,4 +1,5 @@
import 'package:aves/services/common/services.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:package_info_plus/package_info_plus.dart';
final Device device = Device._private();
@ -6,7 +7,7 @@ final Device device = Device._private();
class Device {
late final String _userAgent;
late final bool _canGrantDirectoryAccess, _canPinShortcut, _canPrint, _canRenderFlagEmojis, _canRequestManageMedia, _canSetLockScreenWallpaper;
late final bool _hasGeocoder, _isDynamicColorAvailable, _showPinShortcutFeedback, _supportEdgeToEdgeUIMode;
late final bool _hasGeocoder, _isDynamicColorAvailable, _isTelevision, _showPinShortcutFeedback, _supportEdgeToEdgeUIMode;
String get userAgent => _userAgent;
@ -26,6 +27,10 @@ class Device {
bool get isDynamicColorAvailable => _isDynamicColorAvailable;
bool get isReadOnly => _isTelevision;
bool get isTelevision => _isTelevision;
bool get showPinShortcutFeedback => _showPinShortcutFeedback;
bool get supportEdgeToEdgeUIMode => _supportEdgeToEdgeUIMode;
@ -36,6 +41,9 @@ class Device {
final packageInfo = await PackageInfo.fromPlatform();
_userAgent = '${packageInfo.packageName}/${packageInfo.version}';
final androidInfo = await DeviceInfoPlugin().androidInfo;
_isTelevision = androidInfo.systemFeatures.contains('android.software.leanback');
final capabilities = await deviceService.getCapabilities();
_canGrantDirectoryAccess = capabilities['canGrantDirectoryAccess'] ?? false;
_canPinShortcut = capabilities['canPinShortcut'] ?? false;

View file

@ -3,6 +3,7 @@ import 'dart:io';
import 'dart:ui';
import 'package:aves/geo/countries.dart';
import 'package:aves/model/device.dart';
import 'package:aves/model/entry_cache.dart';
import 'package:aves/model/entry_dirs.dart';
import 'package:aves/model/favourites.dart';
@ -280,7 +281,7 @@ class AvesEntry {
bool get isMediaStoreMediaContent => isMediaStoreContent && {'/external/images/', '/external/video/'}.any(uri.contains);
bool get canEdit => path != null && !trashed && isMediaStoreContent;
bool get canEdit => !device.isReadOnly && path != null && !trashed && isMediaStoreContent;
bool get canEditDate => canEdit && (canEditExif || canEditXmp);

View file

@ -16,7 +16,7 @@ enum EntryBackground { black, white, checkered }
enum HomePageSetting { collection, albums }
enum KeepScreenOn { never, viewerOnly, always }
enum KeepScreenOn { never, videoPlayback, viewerOnly, always }
enum SlideshowVideoPlayback { skip, playMuted, playWithSound }

View file

@ -9,6 +9,8 @@ extension ExtraKeepScreenOn on KeepScreenOn {
switch (this) {
case KeepScreenOn.never:
return context.l10n.keepScreenOnNever;
case KeepScreenOn.videoPlayback:
return context.l10n.keepScreenOnVideoPlayback;
case KeepScreenOn.viewerOnly:
return context.l10n.keepScreenOnViewerOnly;
case KeepScreenOn.always:

View file

@ -5,7 +5,11 @@ import 'dart:math';
import 'package:aves/app_flavor.dart';
import 'package:aves/model/actions/entry_actions.dart';
import 'package:aves/model/actions/entry_set_actions.dart';
import 'package:aves/model/device.dart';
import 'package:aves/model/filters/favourite.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/filters/mime.dart';
import 'package:aves/model/filters/recent.dart';
import 'package:aves/model/settings/defaults.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/enums/map_style.dart';
@ -230,6 +234,25 @@ class Settings extends ChangeNotifier {
mapStyle = styles[Random().nextInt(styles.length)];
}
}
if (device.isTelevision) {
drawerTypeBookmarks = [
null,
MimeFilter.video,
FavouriteFilter.instance,
RecentlyAddedFilter.instance,
];
mustBackTwiceToExit = false;
keepScreenOn = KeepScreenOn.videoPlayback;
enableBottomNavigationBar = false;
viewerGestureSideTapNext = false;
viewerUseCutout = true;
viewerMaxBrightness = false;
videoControls = VideoControls.playSeek;
videoGestureDoubleTapTogglePlay = false;
videoGestureSideDoubleTapSeek = false;
enableBin = false;
}
}
// app

View file

@ -11,6 +11,7 @@ import 'package:aves/services/common/services.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/aves_app.dart';
import 'package:aves/widgets/common/action_mixins/feedback.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
@ -21,7 +22,6 @@ import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
class BugReport extends StatefulWidget {
const BugReport({super.key});
@ -34,7 +34,7 @@ class _BugReportState extends State<BugReport> with FeedbackMixin {
late Future<String> _infoLoader;
bool _showInstructions = false;
static final bugReportUri = Uri.parse('${Constants.avesGithub}/issues/new?labels=type%3Abug&template=bug_report.md');
static const bugReportUrl = '${Constants.avesGithub}/issues/new?labels=type%3Abug&template=bug_report.md';
@override
void initState() {
@ -184,9 +184,5 @@ class _BugReportState extends State<BugReport> with FeedbackMixin {
showFeedback(context, context.l10n.genericSuccessFeedback);
}
Future<void> _goToGithub() async {
if (await canLaunchUrl(bugReportUri)) {
await launchUrl(bugReportUri, mode: LaunchMode.externalApplication);
}
}
Future<void> _goToGithub() => AvesApp.launchUrl(bugReportUrl);
}

View file

@ -44,6 +44,7 @@ import 'package:material_color_utilities/material_color_utilities.dart';
import 'package:overlay_support/overlay_support.dart';
import 'package:provider/provider.dart';
import 'package:tuple/tuple.dart';
import 'package:url_launcher/url_launcher.dart' as ul;
class AvesApp extends StatefulWidget {
final AppFlavor flavor;
@ -103,6 +104,19 @@ class AvesApp extends StatefulWidget {
static Future<void> hideSystemUI() async {
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
}
static Future<void> launchUrl(String? urlString) async {
if (urlString != null) {
final url = Uri.parse(urlString);
if (await ul.canLaunchUrl(url)) {
try {
await ul.launchUrl(url, mode: device.isTelevision ? ul.LaunchMode.inAppWebView : ul.LaunchMode.externalApplication);
} catch (error, stack) {
debugPrint('failed to open url=$urlString with error=$error\n$stack');
}
}
}
}
}
class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
@ -207,13 +221,20 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
lightAccent = Color(tonalPalette?.get(60) ?? defaultAccent.value);
darkAccent = Color(tonalPalette?.get(70) ?? defaultAccent.value);
}
final lightTheme = Themes.lightTheme(lightAccent, initialized);
final darkTheme = themeBrightness == AvesThemeBrightness.black ? Themes.blackTheme(darkAccent, initialized) : Themes.darkTheme(darkAccent, initialized);
return FutureBuilder<bool>(
future: _shouldUseBoldFontLoader,
builder: (context, snapshot) {
// Flutter v3.4 already checks the system `Configuration.fontWeightAdjustment` to update `MediaQuery`
// but we need to also check the non-standard Samsung field `bf` representing the bold font toggle
final shouldUseBoldFont = snapshot.data ?? false;
return MaterialApp(
return Shortcuts(
shortcuts: <LogicalKeySet, Intent>{
// handle Android TV remote `select` button
LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(),
},
child: MaterialApp(
navigatorKey: AvesApp.navigatorKey,
home: home,
navigatorObservers: _navigatorObservers,
@ -234,14 +255,15 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
);
},
onGenerateTitle: (context) => context.l10n.appName,
theme: Themes.lightTheme(lightAccent, initialized),
darkTheme: themeBrightness == AvesThemeBrightness.black ? Themes.blackTheme(darkAccent, initialized) : Themes.darkTheme(darkAccent, initialized),
theme: lightTheme,
darkTheme: darkTheme,
themeMode: themeBrightness.appThemeMode,
locale: settingsLocale,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AvesApp.supportedLocales,
// TODO TLAD remove custom scroll behavior when this is fixed: https://github.com/flutter/flutter/issues/82906
scrollBehavior: StretchMaterialScrollBehavior(),
),
);
},
);

View file

@ -3,6 +3,7 @@ import 'dart:ui';
import 'package:aves/app_mode.dart';
import 'package:aves/model/actions/entry_set_actions.dart';
import 'package:aves/model/device.dart';
import 'package:aves/model/entry.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/filters/query.dart';
@ -307,7 +308,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
...(isSelecting ? selectionMenuActions : browsingMenuActions).where(isVisible).map(
(action) => _toMenuItem(action, enabled: canApply(action), selection: selection),
),
if (isSelecting && !isTrash && appMode == AppMode.main)
if (isSelecting && !device.isReadOnly && appMode == AppMode.main && !isTrash)
PopupMenuItem<EntrySetAction>(
enabled: canApplyEditActions,
padding: EdgeInsets.zero,

View file

@ -73,7 +73,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
case EntrySetAction.addShortcut:
return appMode == AppMode.main && !isSelecting && device.canPinShortcut && !isTrash;
case EntrySetAction.emptyBin:
return appMode == AppMode.main && isTrash;
return !device.isReadOnly && appMode == AppMode.main && isTrash;
// browsing or selecting
case EntrySetAction.map:
case EntrySetAction.slideshow:
@ -82,13 +82,14 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
case EntrySetAction.rescan:
return appMode == AppMode.main && !isTrash;
// selecting
case EntrySetAction.delete:
return appMode == AppMode.main && isSelecting;
case EntrySetAction.share:
case EntrySetAction.toggleFavourite:
return appMode == AppMode.main && isSelecting && !isTrash;
case EntrySetAction.delete:
return !device.isReadOnly && appMode == AppMode.main && isSelecting;
case EntrySetAction.copy:
case EntrySetAction.move:
case EntrySetAction.rename:
case EntrySetAction.toggleFavourite:
case EntrySetAction.rotateCCW:
case EntrySetAction.rotateCW:
case EntrySetAction.flip:
@ -98,9 +99,9 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
case EntrySetAction.editRating:
case EntrySetAction.editTags:
case EntrySetAction.removeMetadata:
return appMode == AppMode.main && isSelecting && !isTrash;
return !device.isReadOnly && appMode == AppMode.main && isSelecting && !isTrash;
case EntrySetAction.restore:
return appMode == AppMode.main && isSelecting && isTrash;
return !device.isReadOnly && appMode == AppMode.main && isSelecting && isTrash;
}
}

View file

@ -1,6 +1,6 @@
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/aves_app.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
class LinkChip extends StatelessWidget {
final Widget? leading;
@ -24,20 +24,11 @@ class LinkChip extends StatelessWidget {
@override
Widget build(BuildContext context) {
final _urlString = urlString;
return DefaultTextStyle.merge(
style: (textStyle ?? const TextStyle()).copyWith(color: color),
child: InkWell(
borderRadius: borderRadius,
onTap: onTap ??
() async {
if (_urlString != null) {
final url = Uri.parse(_urlString);
if (await canLaunchUrl(url)) {
await launchUrl(url, mode: LaunchMode.externalApplication);
}
}
},
onTap: onTap ?? () => AvesApp.launchUrl(urlString),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(

View file

@ -1,7 +1,7 @@
import 'package:aves/widgets/aves_app.dart';
import 'package:aves/widgets/common/fx/borders.dart';
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:url_launcher/url_launcher.dart';
class MarkdownContainer extends StatelessWidget {
final String data;
@ -43,14 +43,7 @@ class MarkdownContainer extends StatelessWidget {
child: Markdown(
data: data,
selectable: true,
onTapLink: (text, href, title) async {
if (href != null) {
final url = Uri.parse(href);
if (await canLaunchUrl(url)) {
await launchUrl(url, mode: LaunchMode.externalApplication);
}
}
},
onTapLink: (text, href, title) => AvesApp.launchUrl(href),
shrinkWrap: true,
),
),

View file

@ -1,9 +1,9 @@
import 'package:aves/widgets/aves_app.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/viewer/info/common.dart';
import 'package:aves_map/aves_map.dart';
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:url_launcher/url_launcher.dart';
class Attribution extends StatelessWidget {
final EntryMapStyle? style;
@ -37,14 +37,7 @@ class Attribution extends StatelessWidget {
a: TextStyle(color: theme.colorScheme.secondary),
p: theme.textTheme.bodySmall!.merge(const TextStyle(fontSize: InfoRowGroup.fontSize)),
),
onTapLink: (text, href, title) async {
if (href != null) {
final url = Uri.parse(href);
if (await canLaunchUrl(url)) {
await launchUrl(url, mode: LaunchMode.externalApplication);
}
}
},
onTapLink: (text, href, title) => AvesApp.launchUrl(href),
),
);
}

View file

@ -3,6 +3,7 @@ import 'dart:io';
import 'package:aves/app_mode.dart';
import 'package:aves/model/actions/chip_set_actions.dart';
import 'package:aves/model/actions/move_type.dart';
import 'package:aves/model/device.dart';
import 'package:aves/model/filters/album.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/highlight.dart';
@ -77,7 +78,7 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate<AlbumFilter> with
return appMode == AppMode.main && !isSelecting;
case ChipSetAction.delete:
case ChipSetAction.rename:
return appMode == AppMode.main && isSelecting;
return !device.isReadOnly && appMode == AppMode.main && isSelecting;
default:
return super.isVisible(
action,

View file

@ -33,7 +33,7 @@ class DisplaySection extends SettingsSection {
SettingsTileDisplayThemeColorMode(),
if (device.isDynamicColorAvailable) SettingsTileDisplayEnableDynamicColor(),
SettingsTileDisplayEnableBlurEffect(),
SettingsTileDisplayDisplayRefreshRateMode(),
if (!device.isTelevision) SettingsTileDisplayDisplayRefreshRateMode(),
];
}

View file

@ -1,5 +1,6 @@
import 'dart:async';
import 'package:aves/model/device.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/enums/home_page.dart';
import 'package:aves/model/settings/enums/screen_on.dart';
@ -31,11 +32,11 @@ class NavigationSection extends SettingsSection {
@override
FutureOr<List<SettingsTile>> tiles(BuildContext context) => [
SettingsTileNavigationHomePage(),
SettingsTileShowBottomNavigationBar(),
if (!device.isTelevision) SettingsTileShowBottomNavigationBar(),
SettingsTileNavigationDrawer(),
SettingsTileNavigationConfirmationDialog(),
SettingsTileNavigationKeepScreenOn(),
SettingsTileNavigationDoubleBackExit(),
if (!device.isTelevision) SettingsTileNavigationConfirmationDialog(),
if (!device.isTelevision) SettingsTileNavigationKeepScreenOn(),
if (!device.isTelevision) SettingsTileNavigationDoubleBackExit(),
];
}

View file

@ -34,11 +34,11 @@ class PrivacySection extends SettingsSection {
return [
SettingsTilePrivacyAllowInstalledAppAccess(),
if (canEnableErrorReporting) SettingsTilePrivacyAllowErrorReporting(),
if (device.canRequestManageMedia) SettingsTilePrivacyManageMedia(),
if (!device.isTelevision && device.canRequestManageMedia) SettingsTilePrivacyManageMedia(),
SettingsTilePrivacySaveSearchHistory(),
SettingsTilePrivacyEnableBin(),
if (!device.isTelevision) SettingsTilePrivacyEnableBin(),
SettingsTilePrivacyHiddenItems(),
if (device.canGrantDirectoryAccess) SettingsTilePrivacyStorageAccess(),
if (!device.isTelevision && device.canGrantDirectoryAccess) SettingsTilePrivacyStorageAccess(),
];
}
}

View file

@ -3,6 +3,7 @@ import 'dart:math';
import 'dart:typed_data';
import 'package:aves/model/actions/settings_actions.dart';
import 'package:aves/model/device.dart';
import 'package:aves/model/source/collection_source.dart';
import 'package:aves/ref/mime_types.dart';
import 'package:aves/services/common/services.dart';
@ -75,6 +76,7 @@ class _SettingsPageState extends State<SettingsPage> with FeedbackMixin {
onPressed: () => _goToSearch(context),
tooltip: MaterialLocalizations.of(context).searchFieldLabel,
),
if (!device.isTelevision)
MenuIconTheme(
child: PopupMenuButton<SettingsAction>(
itemBuilder: (context) {

View file

@ -1,5 +1,6 @@
import 'dart:async';
import 'package:aves/model/device.dart';
import 'package:aves/model/filters/mime.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/enums/video_auto_play_mode.dart';
@ -42,7 +43,7 @@ class VideoSection extends SettingsSection {
SettingsTileVideoEnableHardwareAcceleration(),
SettingsTileVideoEnableAutoPlay(),
SettingsTileVideoLoopMode(),
SettingsTileVideoControls(),
if (!device.isTelevision) SettingsTileVideoControls(),
SettingsTileVideoSubtitleTheme(),
];
}

View file

@ -1,5 +1,6 @@
import 'dart:async';
import 'package:aves/model/device.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/common/services.dart';
@ -36,9 +37,9 @@ class ViewerSection extends SettingsSection {
SettingsTileViewerQuickActions(),
SettingsTileViewerOverlay(),
SettingsTileViewerSlideshow(),
SettingsTileViewerGestureSideTapNext(),
if (canSetCutoutMode) SettingsTileViewerCutoutMode(),
SettingsTileViewerMaxBrightness(),
if (!device.isTelevision) SettingsTileViewerGestureSideTapNext(),
if (!device.isTelevision && canSetCutoutMode) SettingsTileViewerCutoutMode(),
if (!device.isTelevision) SettingsTileViewerMaxBrightness(),
SettingsTileViewerMotionPhotoAutoPlay(),
SettingsTileViewerImageBackground(),
];

View file

@ -72,22 +72,25 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
return collection != null;
case EntryAction.delete:
case EntryAction.rename:
case EntryAction.copy:
case EntryAction.move:
return targetEntry.canEdit;
case EntryAction.copy:
return !device.isReadOnly;
case EntryAction.rotateCCW:
case EntryAction.rotateCW:
return targetEntry.canRotate;
case EntryAction.flip:
return targetEntry.canFlip;
case EntryAction.convert:
return !device.isReadOnly && !targetEntry.isVideo;
case EntryAction.print:
return !targetEntry.isVideo && device.canPrint;
return device.canPrint && !targetEntry.isVideo;
case EntryAction.openMap:
return targetEntry.hasGps;
case EntryAction.viewSource:
return targetEntry.isSvg;
case EntryAction.videoCaptureFrame:
return !device.isReadOnly && targetEntry.isVideo;
case EntryAction.videoToggleMute:
case EntryAction.videoSelectStreams:
case EntryAction.videoSetSpeed:
@ -98,12 +101,13 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
case EntryAction.openVideo:
return targetEntry.isVideo;
case EntryAction.rotateScreen:
return settings.isRotationLocked;
return !device.isTelevision && settings.isRotationLocked;
case EntryAction.addShortcut:
return device.canPinShortcut;
case EntryAction.edit:
return !device.isReadOnly;
case EntryAction.info:
case EntryAction.copyToClipboard:
case EntryAction.edit:
case EntryAction.open:
case EntryAction.setAs:
case EntryAction.share:

View file

@ -3,6 +3,7 @@ import 'dart:convert';
import 'package:aves/model/actions/entry_actions.dart';
import 'package:aves/model/actions/events.dart';
import 'package:aves/model/device.dart';
import 'package:aves/model/entry.dart';
import 'package:aves/model/entry_info.dart';
import 'package:aves/model/entry_metadata_edition.dart';
@ -37,12 +38,13 @@ class EntryInfoActionDelegate with FeedbackMixin, PermissionAwareMixin, EntryEdi
case EntryAction.editTags:
case EntryAction.removeMetadata:
case EntryAction.exportMetadata:
return true;
return !device.isReadOnly;
// GeoTIFF
case EntryAction.showGeoTiffOnMap:
return targetEntry.isGeotiff;
// motion photo
case EntryAction.convertMotionPhotoToStillImage:
return !device.isReadOnly && targetEntry.isMotionPhoto;
case EntryAction.viewMotionPhotoVideo:
return targetEntry.isMotionPhoto;
default:

View file

@ -1,12 +1,16 @@
import 'dart:async';
import 'package:aves/model/entry.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/widgets/viewer/video/controller.dart';
import 'package:aves/widgets/viewer/video/fijkplayer.dart';
import 'package:collection/collection.dart';
class VideoConductor {
final List<AvesVideoController> _controllers = [];
final List<StreamSubscription> _subscriptions = [];
final bool persistPlayback;
static const _defaultMaxControllerCount = 3;
@ -15,7 +19,13 @@ class VideoConductor {
Future<void> dispose() async {
await Future.forEach<AvesVideoController>(_controllers, (controller) => controller.dispose());
_subscriptions
..forEach((sub) => sub.cancel())
..clear();
_controllers.clear();
if (settings.keepScreenOn == KeepScreenOn.videoPlayback) {
await windowService.keepScreenOn(false);
}
}
AvesVideoController getOrCreateController(AvesEntry entry, {int? maxControllerCount}) {
@ -24,6 +34,7 @@ class VideoConductor {
_controllers.remove(controller);
} else {
controller = IjkPlayerAvesVideoController(entry, persistPlayback: persistPlayback);
_subscriptions.add(controller.statusStream.listen(_onControllerStatusChanged));
}
_controllers.insert(0, controller);
while (_controllers.length > (maxControllerCount ?? _defaultMaxControllerCount)) {
@ -36,6 +47,12 @@ class VideoConductor {
return _controllers.firstWhereOrNull((c) => c.entry.uri == entry.uri && c.entry.pageId == entry.pageId);
}
Future<void> _onControllerStatusChanged(VideoStatus status) async {
if (settings.keepScreenOn == KeepScreenOn.videoPlayback) {
await windowService.keepScreenOn(status == VideoStatus.playing);
}
}
Future<void> _applyToAll(FutureOr Function(AvesVideoController controller) action) => Future.forEach<AvesVideoController>(_controllers, action);
Future<void> pauseAll() => _applyToAll((controller) => controller.pause());

View file

@ -138,6 +138,7 @@
"nameConflictStrategyReplace",
"nameConflictStrategySkip",
"keepScreenOnNever",
"keepScreenOnVideoPlayback",
"keepScreenOnViewerOnly",
"keepScreenOnAlways",
"accessibilityAnimationsRemove",
@ -599,7 +600,8 @@
"de": [
"entryActionShareImageOnly",
"entryActionShareVideoOnly",
"entryInfoActionRemoveLocation"
"entryInfoActionRemoveLocation",
"keepScreenOnVideoPlayback"
],
"el": [
@ -609,13 +611,15 @@
"filterAspectRatioLandscapeLabel",
"filterAspectRatioPortraitLabel",
"filterNoAddressLabel",
"keepScreenOnVideoPlayback",
"settingsViewerShowRatingTags"
],
"es": [
"entryActionShareImageOnly",
"entryActionShareVideoOnly",
"entryInfoActionRemoveLocation"
"entryInfoActionRemoveLocation",
"keepScreenOnVideoPlayback"
],
"fa": [
@ -757,6 +761,7 @@
"nameConflictStrategyReplace",
"nameConflictStrategySkip",
"keepScreenOnNever",
"keepScreenOnVideoPlayback",
"keepScreenOnViewerOnly",
"keepScreenOnAlways",
"accessibilityAnimationsRemove",
@ -1218,7 +1223,8 @@
"fr": [
"entryActionShareImageOnly",
"entryActionShareVideoOnly",
"entryInfoActionRemoveLocation"
"entryInfoActionRemoveLocation",
"keepScreenOnVideoPlayback"
],
"gl": [
@ -1229,6 +1235,7 @@
"filterAspectRatioLandscapeLabel",
"filterAspectRatioPortraitLabel",
"filterNoAddressLabel",
"keepScreenOnVideoPlayback",
"accessibilityAnimationsRemove",
"accessibilityAnimationsKeep",
"displayRefreshRatePreferHighest",
@ -1693,6 +1700,7 @@
"filterAspectRatioLandscapeLabel",
"filterAspectRatioPortraitLabel",
"filterNoAddressLabel",
"keepScreenOnVideoPlayback",
"subtitlePositionTop",
"subtitlePositionBottom",
"widgetDisplayedItemRandom",
@ -1710,6 +1718,7 @@
"filterAspectRatioLandscapeLabel",
"filterAspectRatioPortraitLabel",
"filterNoAddressLabel",
"keepScreenOnVideoPlayback",
"settingsViewerShowRatingTags"
],
@ -1722,6 +1731,7 @@
"filterAspectRatioLandscapeLabel",
"filterAspectRatioPortraitLabel",
"filterNoAddressLabel",
"keepScreenOnVideoPlayback",
"subtitlePositionTop",
"subtitlePositionBottom",
"widgetDisplayedItemRandom",
@ -1735,19 +1745,22 @@
"ko": [
"entryActionShareImageOnly",
"entryActionShareVideoOnly",
"entryInfoActionRemoveLocation"
"entryInfoActionRemoveLocation",
"keepScreenOnVideoPlayback"
],
"lt": [
"entryActionShareImageOnly",
"entryActionShareVideoOnly",
"entryInfoActionRemoveLocation"
"entryInfoActionRemoveLocation",
"keepScreenOnVideoPlayback"
],
"nb": [
"entryActionShareImageOnly",
"entryActionShareVideoOnly",
"entryInfoActionRemoveLocation"
"entryInfoActionRemoveLocation",
"keepScreenOnVideoPlayback"
],
"nl": [
@ -1758,6 +1771,7 @@
"filterAspectRatioLandscapeLabel",
"filterAspectRatioPortraitLabel",
"filterNoAddressLabel",
"keepScreenOnVideoPlayback",
"subtitlePositionTop",
"subtitlePositionBottom",
"widgetDisplayedItemRandom",
@ -1781,6 +1795,7 @@
"filterTypePanoramaLabel",
"mapStyleOsmHot",
"mapStyleStamenToner",
"keepScreenOnVideoPlayback",
"accessibilityAnimationsKeep",
"displayRefreshRatePreferHighest",
"displayRefreshRatePreferLowest",
@ -2261,6 +2276,7 @@
"nameConflictStrategyReplace",
"nameConflictStrategySkip",
"keepScreenOnNever",
"keepScreenOnVideoPlayback",
"keepScreenOnViewerOnly",
"keepScreenOnAlways",
"accessibilityAnimationsRemove",
@ -2727,6 +2743,7 @@
"filterAspectRatioLandscapeLabel",
"filterAspectRatioPortraitLabel",
"filterNoAddressLabel",
"keepScreenOnVideoPlayback",
"subtitlePositionTop",
"subtitlePositionBottom",
"widgetDisplayedItemRandom",
@ -2744,13 +2761,15 @@
"filterAspectRatioLandscapeLabel",
"filterAspectRatioPortraitLabel",
"filterNoAddressLabel",
"keepScreenOnVideoPlayback",
"settingsViewerShowRatingTags"
],
"ru": [
"entryActionShareImageOnly",
"entryActionShareVideoOnly",
"entryInfoActionRemoveLocation"
"entryInfoActionRemoveLocation",
"keepScreenOnVideoPlayback"
],
"th": [
@ -2769,6 +2788,7 @@
"filterAspectRatioPortraitLabel",
"filterNoAddressLabel",
"coordinateDms",
"keepScreenOnVideoPlayback",
"keepScreenOnViewerOnly",
"accessibilityAnimationsRemove",
"accessibilityAnimationsKeep",
@ -3133,7 +3153,8 @@
"tr": [
"entryActionShareImageOnly",
"entryActionShareVideoOnly",
"entryInfoActionRemoveLocation"
"entryInfoActionRemoveLocation",
"keepScreenOnVideoPlayback"
],
"zh": [
@ -3143,6 +3164,7 @@
"filterAspectRatioLandscapeLabel",
"filterAspectRatioPortraitLabel",
"filterNoAddressLabel",
"keepScreenOnVideoPlayback",
"settingsViewerShowRatingTags"
]
}