panorama: fixed cropped area, added sensor control on overlay
This commit is contained in:
parent
3b3d3b581e
commit
80d7de43ed
7 changed files with 266 additions and 28 deletions
|
@ -70,6 +70,7 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
|
||||||
"getCatalogMetadata" -> GlobalScope.launch(Dispatchers.IO) { getCatalogMetadata(call, Coresult(result)) }
|
"getCatalogMetadata" -> GlobalScope.launch(Dispatchers.IO) { getCatalogMetadata(call, Coresult(result)) }
|
||||||
"getOverlayMetadata" -> GlobalScope.launch(Dispatchers.IO) { getOverlayMetadata(call, Coresult(result)) }
|
"getOverlayMetadata" -> GlobalScope.launch(Dispatchers.IO) { getOverlayMetadata(call, Coresult(result)) }
|
||||||
"getMultiPageInfo" -> GlobalScope.launch(Dispatchers.IO) { getMultiPageInfo(call, Coresult(result)) }
|
"getMultiPageInfo" -> GlobalScope.launch(Dispatchers.IO) { getMultiPageInfo(call, Coresult(result)) }
|
||||||
|
"getPanoramaInfo" -> GlobalScope.launch(Dispatchers.IO) { getPanoramaInfo(call, Coresult(result)) }
|
||||||
"getEmbeddedPictures" -> GlobalScope.launch(Dispatchers.IO) { getEmbeddedPictures(call, Coresult(result)) }
|
"getEmbeddedPictures" -> GlobalScope.launch(Dispatchers.IO) { getEmbeddedPictures(call, Coresult(result)) }
|
||||||
"getExifThumbnails" -> GlobalScope.launch(Dispatchers.IO) { getExifThumbnails(call, Coresult(result)) }
|
"getExifThumbnails" -> GlobalScope.launch(Dispatchers.IO) { getExifThumbnails(call, Coresult(result)) }
|
||||||
"extractXmpDataProp" -> GlobalScope.launch(Dispatchers.IO) { extractXmpDataProp(call, Coresult(result)) }
|
"extractXmpDataProp" -> GlobalScope.launch(Dispatchers.IO) { extractXmpDataProp(call, Coresult(result)) }
|
||||||
|
@ -539,6 +540,46 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
|
||||||
result.success(pages)
|
result.success(pages)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getPanoramaInfo(call: MethodCall, result: MethodChannel.Result) {
|
||||||
|
val mimeType = call.argument<String>("mimeType")
|
||||||
|
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
|
||||||
|
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
|
||||||
|
if (mimeType == null || uri == null) {
|
||||||
|
result.error("getPanoramaInfo-args", "failed because of missing arguments", null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSupportedByMetadataExtractor(mimeType)) {
|
||||||
|
try {
|
||||||
|
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
|
||||||
|
val metadata = ImageMetadataReader.readMetadata(input)
|
||||||
|
val xmpDirs = metadata.getDirectoriesOfType(XmpDirectory::class.java)
|
||||||
|
try {
|
||||||
|
fun getProp(propName: String): Int? = xmpDirs.map { it.xmpMeta.getPropertyInteger(XMP.GPANO_SCHEMA_NS, propName) }.firstOrNull { it != null }
|
||||||
|
val fields: FieldMap = hashMapOf(
|
||||||
|
"croppedAreaLeft" to getProp(XMP.GPANO_CROPPED_AREA_LEFT_PROP_NAME),
|
||||||
|
"croppedAreaTop" to getProp(XMP.GPANO_CROPPED_AREA_TOP_PROP_NAME),
|
||||||
|
"croppedAreaWidth" to getProp(XMP.GPANO_CROPPED_AREA_WIDTH_PROP_NAME),
|
||||||
|
"croppedAreaHeight" to getProp(XMP.GPANO_CROPPED_AREA_HEIGHT_PROP_NAME),
|
||||||
|
"fullPanoWidth" to getProp(XMP.GPANO_FULL_PANO_WIDTH_PROP_NAME),
|
||||||
|
"fullPanoHeight" to getProp(XMP.GPANO_FULL_PANO_HEIGHT_PROP_NAME),
|
||||||
|
)
|
||||||
|
result.success(fields)
|
||||||
|
return
|
||||||
|
} catch (e: XMPException) {
|
||||||
|
result.error("getPanoramaInfo-args", "failed to read XMP for uri=$uri", e.message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(LOG_TAG, "failed to read XMP", e)
|
||||||
|
} catch (e: NoClassDefFoundError) {
|
||||||
|
Log.w(LOG_TAG, "failed to read XMP", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.error("getPanoramaInfo-empty", "failed to read XMP from uri=$uri", null)
|
||||||
|
}
|
||||||
|
|
||||||
private fun getEmbeddedPictures(call: MethodCall, result: MethodChannel.Result) {
|
private fun getEmbeddedPictures(call: MethodCall, result: MethodChannel.Result) {
|
||||||
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
|
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
|
||||||
if (uri == null) {
|
if (uri == null) {
|
||||||
|
|
|
@ -42,15 +42,15 @@ object XMP {
|
||||||
// panorama
|
// panorama
|
||||||
// cf https://developers.google.com/streetview/spherical-metadata
|
// cf https://developers.google.com/streetview/spherical-metadata
|
||||||
|
|
||||||
private const val GPANO_SCHEMA_NS = "http://ns.google.com/photos/1.0/panorama/"
|
const val GPANO_SCHEMA_NS = "http://ns.google.com/photos/1.0/panorama/"
|
||||||
private const val PMTM_SCHEMA_NS = "http://www.hdrsoft.com/photomatix_settings01"
|
private const val PMTM_SCHEMA_NS = "http://www.hdrsoft.com/photomatix_settings01"
|
||||||
|
|
||||||
private const val GPANO_CROPPED_AREA_HEIGHT_PROP_NAME = "GPano:CroppedAreaImageHeightPixels"
|
const val GPANO_CROPPED_AREA_HEIGHT_PROP_NAME = "GPano:CroppedAreaImageHeightPixels"
|
||||||
private const val GPANO_CROPPED_AREA_WIDTH_PROP_NAME = "GPano:CroppedAreaImageWidthPixels"
|
const val GPANO_CROPPED_AREA_WIDTH_PROP_NAME = "GPano:CroppedAreaImageWidthPixels"
|
||||||
private const val GPANO_CROPPED_AREA_LEFT_PROP_NAME = "GPano:CroppedAreaLeftPixels"
|
const val GPANO_CROPPED_AREA_LEFT_PROP_NAME = "GPano:CroppedAreaLeftPixels"
|
||||||
private const val GPANO_CROPPED_AREA_TOP_PROP_NAME = "GPano:CroppedAreaTopPixels"
|
const val GPANO_CROPPED_AREA_TOP_PROP_NAME = "GPano:CroppedAreaTopPixels"
|
||||||
private const val GPANO_FULL_PANO_HEIGHT_PIXELS_PROP_NAME = "GPano:FullPanoHeightPixels"
|
const val GPANO_FULL_PANO_HEIGHT_PROP_NAME = "GPano:FullPanoHeightPixels"
|
||||||
private const val GPANO_FULL_PANO_WIDTH_PIXELS_PROP_NAME = "GPano:FullPanoWidthPixels"
|
const val GPANO_FULL_PANO_WIDTH_PROP_NAME = "GPano:FullPanoWidthPixels"
|
||||||
private const val GPANO_PROJECTION_TYPE_PROP_NAME = "GPano:ProjectionType"
|
private const val GPANO_PROJECTION_TYPE_PROP_NAME = "GPano:ProjectionType"
|
||||||
|
|
||||||
private const val PMTM_IS_PANO360 = "pmtm:IsPano360"
|
private const val PMTM_IS_PANO360 = "pmtm:IsPano360"
|
||||||
|
@ -60,8 +60,8 @@ object XMP {
|
||||||
GPANO_CROPPED_AREA_WIDTH_PROP_NAME,
|
GPANO_CROPPED_AREA_WIDTH_PROP_NAME,
|
||||||
GPANO_CROPPED_AREA_LEFT_PROP_NAME,
|
GPANO_CROPPED_AREA_LEFT_PROP_NAME,
|
||||||
GPANO_CROPPED_AREA_TOP_PROP_NAME,
|
GPANO_CROPPED_AREA_TOP_PROP_NAME,
|
||||||
GPANO_FULL_PANO_HEIGHT_PIXELS_PROP_NAME,
|
GPANO_FULL_PANO_HEIGHT_PROP_NAME,
|
||||||
GPANO_FULL_PANO_WIDTH_PIXELS_PROP_NAME,
|
GPANO_FULL_PANO_WIDTH_PROP_NAME,
|
||||||
GPANO_PROJECTION_TYPE_PROP_NAME,
|
GPANO_PROJECTION_TYPE_PROP_NAME,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
40
lib/model/panorama.dart
Normal file
40
lib/model/panorama.dart
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
class PanoramaInfo {
|
||||||
|
final Rect croppedAreaRect;
|
||||||
|
final Size fullPanoSize;
|
||||||
|
|
||||||
|
PanoramaInfo({
|
||||||
|
this.croppedAreaRect,
|
||||||
|
this.fullPanoSize,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory PanoramaInfo.fromMap(Map map) {
|
||||||
|
final cLeft = map['croppedAreaLeft'] as int;
|
||||||
|
final cTop = map['croppedAreaTop'] as int;
|
||||||
|
final cWidth = map['croppedAreaWidth'] as int;
|
||||||
|
final cHeight = map['croppedAreaHeight'] as int;
|
||||||
|
Rect croppedAreaRect;
|
||||||
|
if (cLeft != null && cTop != null && cWidth != null && cHeight != null) {
|
||||||
|
croppedAreaRect = Rect.fromLTWH(cLeft.toDouble(), cTop.toDouble(), cWidth.toDouble(), cHeight.toDouble());
|
||||||
|
}
|
||||||
|
|
||||||
|
final fWidth = map['fullPanoWidth'] as int;
|
||||||
|
final fHeight = map['fullPanoHeight'] as int;
|
||||||
|
Size fullPanoSize;
|
||||||
|
if (fWidth != null && fHeight != null) {
|
||||||
|
fullPanoSize = Size(fWidth.toDouble(), fHeight.toDouble());
|
||||||
|
}
|
||||||
|
|
||||||
|
return PanoramaInfo(
|
||||||
|
croppedAreaRect: croppedAreaRect,
|
||||||
|
fullPanoSize: fullPanoSize,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get hasCroppedArea => croppedAreaRect != null && fullPanoSize != null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => '$runtimeType#${shortHash(this)}{croppedAreaRect=$croppedAreaRect, fullPanoSize=$fullPanoSize}';
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ import 'dart:typed_data';
|
||||||
import 'package:aves/model/image_entry.dart';
|
import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/model/image_metadata.dart';
|
import 'package:aves/model/image_metadata.dart';
|
||||||
import 'package:aves/model/multipage.dart';
|
import 'package:aves/model/multipage.dart';
|
||||||
|
import 'package:aves/model/panorama.dart';
|
||||||
import 'package:aves/services/service_policy.dart';
|
import 'package:aves/services/service_policy.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
@ -94,6 +95,23 @@ class MetadataService {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<PanoramaInfo> getPanoramaInfo(ImageEntry entry) async {
|
||||||
|
try {
|
||||||
|
// return map with values for:
|
||||||
|
// 'croppedAreaLeft' (int), 'croppedAreaTop' (int), 'croppedAreaWidth' (int), 'croppedAreaHeight' (int),
|
||||||
|
// 'fullPanoWidth' (int), 'fullPanoHeight' (int)
|
||||||
|
final result = await platform.invokeMethod('getPanoramaInfo', <String, dynamic>{
|
||||||
|
'mimeType': entry.mimeType,
|
||||||
|
'uri': entry.uri,
|
||||||
|
'sizeBytes': entry.sizeBytes,
|
||||||
|
}) as Map;
|
||||||
|
return PanoramaInfo.fromMap(result);
|
||||||
|
} on PlatformException catch (e) {
|
||||||
|
debugPrint('PanoramaInfo failed with code=${e.code}, exception=${e.message}, details=${e.details}');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
static Future<List<Uint8List>> getEmbeddedPictures(String uri) async {
|
static Future<List<Uint8List>> getEmbeddedPictures(String uri) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('getEmbeddedPictures', <String, dynamic>{
|
final result = await platform.invokeMethod('getEmbeddedPictures', <String, dynamic>{
|
||||||
|
|
|
@ -18,6 +18,8 @@ class AIcons {
|
||||||
static const IconData raw = Icons.camera_outlined;
|
static const IconData raw = Icons.camera_outlined;
|
||||||
static const IconData shooting = Icons.camera_outlined;
|
static const IconData shooting = Icons.camera_outlined;
|
||||||
static const IconData removableStorage = Icons.sd_storage_outlined;
|
static const IconData removableStorage = Icons.sd_storage_outlined;
|
||||||
|
static const IconData sensorControl = Icons.explore_outlined;
|
||||||
|
static const IconData sensorControlOff = Icons.explore_off_outlined;
|
||||||
static const IconData settings = Icons.settings_outlined;
|
static const IconData settings = Icons.settings_outlined;
|
||||||
static const IconData text = Icons.format_quote_outlined;
|
static const IconData text = Icons.format_quote_outlined;
|
||||||
static const IconData tag = Icons.local_offer_outlined;
|
static const IconData tag = Icons.local_offer_outlined;
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import 'package:aves/model/image_entry.dart';
|
import 'package:aves/model/image_entry.dart';
|
||||||
|
import 'package:aves/services/metadata_service.dart';
|
||||||
import 'package:aves/widgets/viewer/overlay/common.dart';
|
import 'package:aves/widgets/viewer/overlay/common.dart';
|
||||||
import 'package:aves/widgets/viewer/panorama_page.dart';
|
import 'package:aves/widgets/viewer/panorama_page.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pedantic/pedantic.dart';
|
||||||
|
|
||||||
class PanoramaOverlay extends StatelessWidget {
|
class PanoramaOverlay extends StatelessWidget {
|
||||||
final ImageEntry entry;
|
final ImageEntry entry;
|
||||||
|
@ -21,14 +23,18 @@ class PanoramaOverlay extends StatelessWidget {
|
||||||
OverlayTextButton(
|
OverlayTextButton(
|
||||||
scale: scale,
|
scale: scale,
|
||||||
text: 'Open Panorama',
|
text: 'Open Panorama',
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
Navigator.push(
|
final info = await MetadataService.getPanoramaInfo(entry);
|
||||||
|
unawaited(Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
settings: RouteSettings(name: PanoramaPage.routeName),
|
settings: RouteSettings(name: PanoramaPage.routeName),
|
||||||
builder: (context) => PanoramaPage(entry: entry),
|
builder: (context) => PanoramaPage(
|
||||||
|
entry: entry,
|
||||||
|
info: info,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
));
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,38 +1,169 @@
|
||||||
import 'package:aves/image_providers/uri_image_provider.dart';
|
import 'package:aves/image_providers/uri_image_provider.dart';
|
||||||
import 'package:aves/model/image_entry.dart';
|
import 'package:aves/model/image_entry.dart';
|
||||||
|
import 'package:aves/model/panorama.dart';
|
||||||
|
import 'package:aves/theme/icons.dart';
|
||||||
|
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||||
|
import 'package:aves/widgets/viewer/overlay/common.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:panorama/panorama.dart';
|
import 'package:panorama/panorama.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class PanoramaPage extends StatelessWidget {
|
class PanoramaPage extends StatefulWidget {
|
||||||
static const routeName = '/viewer/panorama';
|
static const routeName = '/viewer/panorama';
|
||||||
|
|
||||||
final ImageEntry entry;
|
final ImageEntry entry;
|
||||||
|
|
||||||
final int page;
|
final int page;
|
||||||
|
final PanoramaInfo info;
|
||||||
|
|
||||||
const PanoramaPage({
|
const PanoramaPage({
|
||||||
@required this.entry,
|
@required this.entry,
|
||||||
this.page = 0,
|
this.page = 0,
|
||||||
|
@required this.info,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_PanoramaPageState createState() => _PanoramaPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PanoramaPageState extends State<PanoramaPage> {
|
||||||
|
final ValueNotifier<bool> _overlayVisible = ValueNotifier(true);
|
||||||
|
final ValueNotifier<SensorControl> _sensorControl = ValueNotifier(SensorControl.None);
|
||||||
|
|
||||||
|
ImageEntry get entry => widget.entry;
|
||||||
|
|
||||||
|
PanoramaInfo get info => widget.info;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_overlayVisible.addListener(_onOverlayVisibleChange);
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) => _initOverlay());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_overlayVisible.removeListener(_onOverlayVisibleChange);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return WillPopScope(
|
||||||
body: Panorama(
|
onWillPop: () {
|
||||||
child: Image(
|
_onLeave();
|
||||||
image: UriImage(
|
return SynchronousFuture(true);
|
||||||
uri: entry.uri,
|
},
|
||||||
mimeType: entry.mimeType,
|
child: MediaQueryDataProvider(
|
||||||
page: page,
|
child: Scaffold(
|
||||||
rotationDegrees: entry.rotationDegrees,
|
body: Stack(
|
||||||
isFlipped: entry.isFlipped,
|
children: [
|
||||||
expectedContentLength: entry.sizeBytes,
|
ValueListenableBuilder<SensorControl>(
|
||||||
|
valueListenable: _sensorControl,
|
||||||
|
builder: (context, sensorControl, child) {
|
||||||
|
return Panorama(
|
||||||
|
sensorControl: sensorControl,
|
||||||
|
croppedArea: info.hasCroppedArea ? info.croppedAreaRect : Rect.fromLTWH(0.0, 0.0, 1.0, 1.0),
|
||||||
|
croppedFullWidth: info.hasCroppedArea ? info.fullPanoSize.width : 1.0,
|
||||||
|
croppedFullHeight: info.hasCroppedArea ? info.fullPanoSize.height : 1.0,
|
||||||
|
onTap: (longitude, latitude, tilt) => _overlayVisible.value = !_overlayVisible.value,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Image(
|
||||||
|
image: UriImage(
|
||||||
|
uri: entry.uri,
|
||||||
|
mimeType: entry.mimeType,
|
||||||
|
page: widget.page,
|
||||||
|
rotationDegrees: entry.rotationDegrees,
|
||||||
|
isFlipped: entry.isFlipped,
|
||||||
|
expectedContentLength: entry.sizeBytes,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: 0,
|
||||||
|
right: 0,
|
||||||
|
child: TooltipTheme(
|
||||||
|
data: TooltipTheme.of(context).copyWith(
|
||||||
|
preferBelow: false,
|
||||||
|
),
|
||||||
|
child: ValueListenableBuilder<bool>(
|
||||||
|
valueListenable: _overlayVisible,
|
||||||
|
builder: (context, overlayVisible, child) {
|
||||||
|
return Visibility(
|
||||||
|
visible: overlayVisible,
|
||||||
|
child: Selector<MediaQueryData, EdgeInsets>(
|
||||||
|
selector: (c, mq) => mq.padding + mq.viewInsets,
|
||||||
|
builder: (c, mqViewInsets, child) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.all(8) + EdgeInsets.only(right: mqViewInsets.right, bottom: mqViewInsets.bottom),
|
||||||
|
child: OverlayButton(
|
||||||
|
scale: kAlwaysCompleteAnimation,
|
||||||
|
child: ValueListenableBuilder<SensorControl>(
|
||||||
|
valueListenable: _sensorControl,
|
||||||
|
builder: (context, sensorControl, child) {
|
||||||
|
return IconButton(
|
||||||
|
icon: Icon(sensorControl == SensorControl.None ? AIcons.sensorControl : AIcons.sensorControlOff),
|
||||||
|
onPressed: _toggleSensor,
|
||||||
|
tooltip: sensorControl == SensorControl.None ? 'Enable sensor control' : 'Disable sensor control',
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
|
resizeToAvoidBottomInset: false,
|
||||||
),
|
),
|
||||||
// TODO TLAD toggle sensor control
|
|
||||||
sensorControl: SensorControl.None,
|
|
||||||
),
|
),
|
||||||
resizeToAvoidBottomInset: false,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _toggleSensor() {
|
||||||
|
switch (_sensorControl.value) {
|
||||||
|
case SensorControl.None:
|
||||||
|
_sensorControl.value = SensorControl.AbsoluteOrientation;
|
||||||
|
break;
|
||||||
|
case SensorControl.AbsoluteOrientation:
|
||||||
|
case SensorControl.Orientation:
|
||||||
|
_sensorControl.value = SensorControl.None;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onLeave() {
|
||||||
|
_showSystemUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
// system UI
|
||||||
|
|
||||||
|
static void _showSystemUI() => SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values);
|
||||||
|
|
||||||
|
static void _hideSystemUI() => SystemChrome.setEnabledSystemUIOverlays([]);
|
||||||
|
|
||||||
|
// overlay
|
||||||
|
|
||||||
|
Future<void> _initOverlay() async {
|
||||||
|
// wait for MaterialPageRoute.transitionDuration
|
||||||
|
// to show overlay after page animation is complete
|
||||||
|
await Future.delayed(ModalRoute.of(context).transitionDuration * timeDilation);
|
||||||
|
await _onOverlayVisibleChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onOverlayVisibleChange() async {
|
||||||
|
if (_overlayVisible.value) {
|
||||||
|
_showSystemUI();
|
||||||
|
} else {
|
||||||
|
_hideSystemUI();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue