#838 HDR color mode
This commit is contained in:
parent
445aa2cb06
commit
49fa3eec96
10 changed files with 160 additions and 81 deletions
|
@ -1,6 +1,7 @@
|
||||||
package deckers.thibault.aves.channel.calls.window
|
package deckers.thibault.aves.channel.calls.window
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.content.pm.ActivityInfo
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import deckers.thibault.aves.utils.getDisplayCompat
|
import deckers.thibault.aves.utils.getDisplayCompat
|
||||||
|
@ -75,4 +76,21 @@ class ActivityWindowHandler(private val activity: Activity) : WindowHandler(acti
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun supportsHdr(call: MethodCall, result: MethodChannel.Result) {
|
||||||
|
result.success(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && activity.getDisplayCompat()?.isHdr ?: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setHdrColorMode(call: MethodCall, result: MethodChannel.Result) {
|
||||||
|
val on = call.argument<Boolean>("on")
|
||||||
|
if (on == null) {
|
||||||
|
result.error("setHdrColorMode-args", "missing arguments", null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
activity.window.colorMode = if (on) ActivityInfo.COLOR_MODE_HDR else ActivityInfo.COLOR_MODE_DEFAULT
|
||||||
|
}
|
||||||
|
result.success(null)
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -28,4 +28,12 @@ class ServiceWindowHandler(service: Service) : WindowHandler(service) {
|
||||||
override fun getCutoutInsets(call: MethodCall, result: MethodChannel.Result) {
|
override fun getCutoutInsets(call: MethodCall, result: MethodChannel.Result) {
|
||||||
result.success(HashMap<String, Any>())
|
result.success(HashMap<String, Any>())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun supportsHdr(call: MethodCall, result: MethodChannel.Result) {
|
||||||
|
result.success(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setHdrColorMode(call: MethodCall, result: MethodChannel.Result) {
|
||||||
|
result.success(null)
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -18,6 +18,8 @@ abstract class WindowHandler(private val contextWrapper: ContextWrapper) : Metho
|
||||||
"requestOrientation" -> Coresult.safe(call, result, ::requestOrientation)
|
"requestOrientation" -> Coresult.safe(call, result, ::requestOrientation)
|
||||||
"isCutoutAware" -> Coresult.safe(call, result, ::isCutoutAware)
|
"isCutoutAware" -> Coresult.safe(call, result, ::isCutoutAware)
|
||||||
"getCutoutInsets" -> Coresult.safe(call, result, ::getCutoutInsets)
|
"getCutoutInsets" -> Coresult.safe(call, result, ::getCutoutInsets)
|
||||||
|
"supportsHdr" -> Coresult.safe(call, result, ::supportsHdr)
|
||||||
|
"setHdrColorMode" -> Coresult.safe(call, result, ::setHdrColorMode)
|
||||||
else -> result.notImplemented()
|
else -> result.notImplemented()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,6 +46,10 @@ abstract class WindowHandler(private val contextWrapper: ContextWrapper) : Metho
|
||||||
|
|
||||||
abstract fun getCutoutInsets(call: MethodCall, result: MethodChannel.Result)
|
abstract fun getCutoutInsets(call: MethodCall, result: MethodChannel.Result)
|
||||||
|
|
||||||
|
abstract fun supportsHdr(call: MethodCall, result: MethodChannel.Result)
|
||||||
|
|
||||||
|
abstract fun setHdrColorMode(call: MethodCall, result: MethodChannel.Result)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val LOG_TAG = LogUtils.createTag<WindowHandler>()
|
private val LOG_TAG = LogUtils.createTag<WindowHandler>()
|
||||||
const val CHANNEL = "deckers.thibault/aves/window"
|
const val CHANNEL = "deckers.thibault/aves/window"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:aves/widgets/viewer/controls/controller.dart';
|
import 'package:aves/widgets/viewer/controls/transitions.dart';
|
||||||
import 'package:aves_model/aves_model.dart';
|
import 'package:aves_model/aves_model.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
|
@ -17,11 +17,17 @@ abstract class WindowService {
|
||||||
Future<bool> isCutoutAware();
|
Future<bool> isCutoutAware();
|
||||||
|
|
||||||
Future<EdgeInsets> getCutoutInsets();
|
Future<EdgeInsets> getCutoutInsets();
|
||||||
|
|
||||||
|
Future<bool> supportsHdr();
|
||||||
|
|
||||||
|
Future<void> setHdrColorMode(bool on);
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlatformWindowService implements WindowService {
|
class PlatformWindowService implements WindowService {
|
||||||
static const _platform = MethodChannel('deckers.thibault/aves/window');
|
static const _platform = MethodChannel('deckers.thibault/aves/window');
|
||||||
|
|
||||||
|
bool? _isCutoutAware, _supportsHdr;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> isActivity() async {
|
Future<bool> isActivity() async {
|
||||||
try {
|
try {
|
||||||
|
@ -90,8 +96,6 @@ class PlatformWindowService implements WindowService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool? _isCutoutAware;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> isCutoutAware() async {
|
Future<bool> isCutoutAware() async {
|
||||||
if (_isCutoutAware != null) return SynchronousFuture(_isCutoutAware!);
|
if (_isCutoutAware != null) return SynchronousFuture(_isCutoutAware!);
|
||||||
|
@ -121,4 +125,27 @@ class PlatformWindowService implements WindowService {
|
||||||
}
|
}
|
||||||
return EdgeInsets.zero;
|
return EdgeInsets.zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> supportsHdr() async {
|
||||||
|
if (_supportsHdr != null) return SynchronousFuture(_supportsHdr!);
|
||||||
|
try {
|
||||||
|
final result = await _platform.invokeMethod('supportsHdr');
|
||||||
|
_supportsHdr = result as bool?;
|
||||||
|
} on PlatformException catch (e, stack) {
|
||||||
|
await reportService.recordError(e, stack);
|
||||||
|
}
|
||||||
|
return _supportsHdr ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> setHdrColorMode(bool on) async {
|
||||||
|
try {
|
||||||
|
await _platform.invokeMethod('setHdrColorMode', <String, dynamic>{
|
||||||
|
'on': on,
|
||||||
|
});
|
||||||
|
} on PlatformException catch (e, stack) {
|
||||||
|
await reportService.recordError(e, stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,6 +154,7 @@ class _BugReportState extends State<BugReport> with FeedbackMixin {
|
||||||
final androidInfo = await DeviceInfoPlugin().androidInfo;
|
final androidInfo = await DeviceInfoPlugin().androidInfo;
|
||||||
final storageVolumes = await storageService.getStorageVolumes();
|
final storageVolumes = await storageService.getStorageVolumes();
|
||||||
final storageGrants = await storageService.getGrantedDirectories();
|
final storageGrants = await storageService.getGrantedDirectories();
|
||||||
|
final supportsHdr = await windowService.supportsHdr();
|
||||||
return [
|
return [
|
||||||
'Package: ${device.packageName}',
|
'Package: ${device.packageName}',
|
||||||
'Installer: ${packageInfo.installerStore}',
|
'Installer: ${packageInfo.installerStore}',
|
||||||
|
@ -162,7 +163,7 @@ class _BugReportState extends State<BugReport> with FeedbackMixin {
|
||||||
'Android version: ${androidInfo.version.release}, API ${androidInfo.version.sdkInt}',
|
'Android version: ${androidInfo.version.release}, API ${androidInfo.version.sdkInt}',
|
||||||
'Android build: ${androidInfo.display}',
|
'Android build: ${androidInfo.display}',
|
||||||
'Device: ${androidInfo.manufacturer} ${androidInfo.model}',
|
'Device: ${androidInfo.manufacturer} ${androidInfo.model}',
|
||||||
'Geocoder: ${device.hasGeocoder ? 'ready' : 'not available'}',
|
'Support: dynamic colors=${device.isDynamicColorAvailable}, geocoder=${device.hasGeocoder}, HDR=$supportsHdr',
|
||||||
'Mobile services: ${mobileServices.isServiceAvailable ? 'ready' : 'not available'}',
|
'Mobile services: ${mobileServices.isServiceAvailable ? 'ready' : 'not available'}',
|
||||||
'System locales: ${WidgetsBinding.instance.platformDispatcher.locales.join(', ')}',
|
'System locales: ${WidgetsBinding.instance.platformDispatcher.locales.join(', ')}',
|
||||||
'Storage volumes: ${storageVolumes.map((v) => v.path).join(', ')}',
|
'Storage volumes: ${storageVolumes.map((v) => v.path).join(', ')}',
|
||||||
|
|
|
@ -96,10 +96,10 @@ class GridThemeData {
|
||||||
else if (entry.isAnimated)
|
else if (entry.isAnimated)
|
||||||
const AnimatedImageIcon()
|
const AnimatedImageIcon()
|
||||||
else ...[
|
else ...[
|
||||||
|
if (entry.isHdr && showHdr) const HdrIcon(),
|
||||||
if (entry.isRaw && showRaw) const RawIcon(),
|
if (entry.isRaw && showRaw) const RawIcon(),
|
||||||
if (entry.is360) const PanoramaIcon(),
|
if (entry.is360) const PanoramaIcon(),
|
||||||
],
|
],
|
||||||
if (entry.isHdr && showHdr) const HdrIcon(),
|
|
||||||
if (entry.isMotionPhoto && showMotionPhoto) const MotionPhotoIcon(),
|
if (entry.isMotionPhoto && showMotionPhoto) const MotionPhotoIcon(),
|
||||||
if (entry.isMultiPage && !entry.isMotionPhoto) MultiPageIcon(entry: entry),
|
if (entry.isMultiPage && !entry.isMotionPhoto) MultiPageIcon(entry: entry),
|
||||||
if (entry.isGeotiff) const GeoTiffIcon(),
|
if (entry.isGeotiff) const GeoTiffIcon(),
|
||||||
|
|
|
@ -2,6 +2,8 @@ import 'dart:async';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
|
import 'package:aves/model/entry/extensions/multipage.dart';
|
||||||
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/widgets/viewer/controls/cast.dart';
|
import 'package:aves/widgets/viewer/controls/cast.dart';
|
||||||
import 'package:aves/widgets/viewer/controls/events.dart';
|
import 'package:aves/widgets/viewer/controls/events.dart';
|
||||||
|
@ -57,6 +59,7 @@ class ViewerController with CastMixin {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
_initialScale = initialScale;
|
_initialScale = initialScale;
|
||||||
|
entryNotifier.addListener(_onEntryChanged);
|
||||||
_autopilotNotifier = ValueNotifier(autopilot);
|
_autopilotNotifier = ValueNotifier(autopilot);
|
||||||
_autopilotNotifier.addListener(_onAutopilotChanged);
|
_autopilotNotifier.addListener(_onAutopilotChanged);
|
||||||
_onAutopilotChanged();
|
_onAutopilotChanged();
|
||||||
|
@ -66,12 +69,21 @@ class ViewerController with CastMixin {
|
||||||
if (kFlutterMemoryAllocationsEnabled) {
|
if (kFlutterMemoryAllocationsEnabled) {
|
||||||
MemoryAllocations.instance.dispatchObjectDisposed(object: this);
|
MemoryAllocations.instance.dispatchObjectDisposed(object: this);
|
||||||
}
|
}
|
||||||
|
entryNotifier.removeListener(_onEntryChanged);
|
||||||
|
windowService.setHdrColorMode(false);
|
||||||
_autopilotNotifier.dispose();
|
_autopilotNotifier.dispose();
|
||||||
_clearAutopilotAnimations();
|
_clearAutopilotAnimations();
|
||||||
_stopPlayTimer();
|
_stopPlayTimer();
|
||||||
_streamController.close();
|
_streamController.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _onEntryChanged() async {
|
||||||
|
if (await windowService.supportsHdr()) {
|
||||||
|
final enabled = entryNotifier.value?.isHdr ?? false;
|
||||||
|
await windowService.setHdrColorMode(enabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _onAutopilotChanged() {
|
void _onAutopilotChanged() {
|
||||||
_clearAutopilotAnimations();
|
_clearAutopilotAnimations();
|
||||||
_stopPlayTimer();
|
_stopPlayTimer();
|
||||||
|
@ -115,79 +127,3 @@ class ViewerController with CastMixin {
|
||||||
Future.delayed(ADurations.viewerHorizontalPageAnimation).then((_) => _autopilotAnimationControllers[vsync]?.forward());
|
Future.delayed(ADurations.viewerHorizontalPageAnimation).then((_) => _autopilotAnimationControllers[vsync]?.forward());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PageTransitionEffects {
|
|
||||||
static TransitionBuilder fade(
|
|
||||||
PageController pageController,
|
|
||||||
int index, {
|
|
||||||
required bool zoomIn,
|
|
||||||
}) =>
|
|
||||||
(context, child) {
|
|
||||||
double opacity = 0;
|
|
||||||
double dx = 0;
|
|
||||||
double scale = 1;
|
|
||||||
if (pageController.hasClients && pageController.position.haveDimensions) {
|
|
||||||
final position = (pageController.page! - index).clamp(-1.0, 1.0);
|
|
||||||
final width = pageController.position.viewportDimension;
|
|
||||||
opacity = (1 - position.abs()).clamp(0, 1);
|
|
||||||
dx = position * width;
|
|
||||||
if (zoomIn) {
|
|
||||||
scale = 1 + position;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Opacity(
|
|
||||||
opacity: opacity,
|
|
||||||
child: Transform.translate(
|
|
||||||
offset: Offset(dx, 0),
|
|
||||||
child: Transform.scale(
|
|
||||||
scale: scale,
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
static TransitionBuilder slide(
|
|
||||||
PageController pageController,
|
|
||||||
int index, {
|
|
||||||
required bool parallax,
|
|
||||||
}) =>
|
|
||||||
(context, child) {
|
|
||||||
double dx = 0;
|
|
||||||
if (pageController.hasClients && pageController.position.haveDimensions) {
|
|
||||||
final position = (pageController.page! - index).clamp(-1.0, 1.0);
|
|
||||||
final width = pageController.position.viewportDimension;
|
|
||||||
if (parallax) {
|
|
||||||
dx = position * width / 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ClipRect(
|
|
||||||
child: Transform.translate(
|
|
||||||
offset: Offset(dx, 0),
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
static TransitionBuilder none(
|
|
||||||
PageController pageController,
|
|
||||||
int index,
|
|
||||||
) =>
|
|
||||||
(context, child) {
|
|
||||||
double opacity = 0;
|
|
||||||
double dx = 0;
|
|
||||||
if (pageController.hasClients && pageController.position.haveDimensions) {
|
|
||||||
final position = (pageController.page! - index).clamp(-1.0, 1.0);
|
|
||||||
final width = pageController.position.viewportDimension;
|
|
||||||
opacity = (1 - position.abs()).roundToDouble().clamp(0, 1);
|
|
||||||
dx = position * width;
|
|
||||||
}
|
|
||||||
return Opacity(
|
|
||||||
opacity: opacity,
|
|
||||||
child: Transform.translate(
|
|
||||||
offset: Offset(dx, 0),
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
77
lib/widgets/viewer/controls/transitions.dart
Normal file
77
lib/widgets/viewer/controls/transitions.dart
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
class PageTransitionEffects {
|
||||||
|
static TransitionBuilder fade(
|
||||||
|
PageController pageController,
|
||||||
|
int index, {
|
||||||
|
required bool zoomIn,
|
||||||
|
}) =>
|
||||||
|
(context, child) {
|
||||||
|
double opacity = 0;
|
||||||
|
double dx = 0;
|
||||||
|
double scale = 1;
|
||||||
|
if (pageController.hasClients && pageController.position.haveDimensions) {
|
||||||
|
final position = (pageController.page! - index).clamp(-1.0, 1.0);
|
||||||
|
final width = pageController.position.viewportDimension;
|
||||||
|
opacity = (1 - position.abs()).clamp(0, 1);
|
||||||
|
dx = position * width;
|
||||||
|
if (zoomIn) {
|
||||||
|
scale = 1 + position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Opacity(
|
||||||
|
opacity: opacity,
|
||||||
|
child: Transform.translate(
|
||||||
|
offset: Offset(dx, 0),
|
||||||
|
child: Transform.scale(
|
||||||
|
scale: scale,
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
static TransitionBuilder slide(
|
||||||
|
PageController pageController,
|
||||||
|
int index, {
|
||||||
|
required bool parallax,
|
||||||
|
}) =>
|
||||||
|
(context, child) {
|
||||||
|
double dx = 0;
|
||||||
|
if (pageController.hasClients && pageController.position.haveDimensions) {
|
||||||
|
final position = (pageController.page! - index).clamp(-1.0, 1.0);
|
||||||
|
final width = pageController.position.viewportDimension;
|
||||||
|
if (parallax) {
|
||||||
|
dx = position * width / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ClipRect(
|
||||||
|
child: Transform.translate(
|
||||||
|
offset: Offset(dx, 0),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
static TransitionBuilder none(
|
||||||
|
PageController pageController,
|
||||||
|
int index,
|
||||||
|
) =>
|
||||||
|
(context, child) {
|
||||||
|
double opacity = 0;
|
||||||
|
double dx = 0;
|
||||||
|
if (pageController.hasClients && pageController.position.haveDimensions) {
|
||||||
|
final position = (pageController.page! - index).clamp(-1.0, 1.0);
|
||||||
|
final width = pageController.position.viewportDimension;
|
||||||
|
opacity = (1 - position.abs()).roundToDouble().clamp(0, 1);
|
||||||
|
dx = position * width;
|
||||||
|
}
|
||||||
|
return Opacity(
|
||||||
|
opacity: opacity,
|
||||||
|
child: Transform.translate(
|
||||||
|
offset: Offset(dx, 0),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
|
@ -21,4 +21,10 @@ class FakeWindowService extends Fake implements WindowService {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<EdgeInsets> getCutoutInsets() => SynchronousFuture(EdgeInsets.zero);
|
Future<EdgeInsets> getCutoutInsets() => SynchronousFuture(EdgeInsets.zero);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> supportsHdr() => SynchronousFuture(false);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> setHdrColorMode(bool on) => SynchronousFuture(null);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue