From 35929fa7ab0c1ea0978f36f9ff47c79c4a883326 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Tue, 5 Oct 2021 19:11:37 +0900 Subject: [PATCH] #81 viewer: padding below overlay controls to avoid system gesture area, fixed video progress bar height to match interaction standards --- lib/widgets/viewer/overlay/bottom/common.dart | 23 +++-- lib/widgets/viewer/overlay/bottom/video.dart | 89 +++++++++++-------- lib/widgets/viewer/panorama_page.dart | 42 +++++---- 3 files changed, 91 insertions(+), 63 deletions(-) diff --git a/lib/widgets/viewer/overlay/bottom/common.dart b/lib/widgets/viewer/overlay/bottom/common.dart index 921cd7e95..e0ba96522 100644 --- a/lib/widgets/viewer/overlay/bottom/common.dart +++ b/lib/widgets/viewer/overlay/bottom/common.dart @@ -11,6 +11,7 @@ import 'package:aves/theme/format.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/utils/constants.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/common/extensions/media_query.dart'; import 'package:aves/widgets/common/fx/blurred.dart'; import 'package:aves/widgets/viewer/multipage/controller.dart'; import 'package:aves/widgets/viewer/overlay/bottom/multipage.dart'; @@ -92,14 +93,20 @@ class _ViewerBottomOverlayState extends State { final viewPadding = widget.viewPadding ?? mqViewPadding; final availableWidth = mqWidth - viewPadding.horizontal; - return Container( - color: hasEdgeContent ? overlayBackgroundColor(blurred: blurred) : Colors.transparent, - padding: EdgeInsets.only( - left: max(viewInsets.left, viewPadding.left), - top: 0, - right: max(viewInsets.right, viewPadding.right), - bottom: max(viewInsets.bottom, viewPadding.bottom), - ), + return Selector( + selector: (context, mq) => max(mq.effectiveBottomPadding, mq.systemGestureInsets.bottom), + builder: (context, mqPaddingBottom, child) { + return Container( + color: hasEdgeContent ? overlayBackgroundColor(blurred: blurred) : Colors.transparent, + padding: EdgeInsets.only( + left: max(viewInsets.left, viewPadding.left), + top: 0, + right: max(viewInsets.right, viewPadding.right), + bottom: mqPaddingBottom, + ), + child: child, + ); + }, child: FutureBuilder( future: _detailLoader, builder: (context, snapshot) { diff --git a/lib/widgets/viewer/overlay/bottom/video.dart b/lib/widgets/viewer/overlay/bottom/video.dart index 5c606a422..6474e1d6c 100644 --- a/lib/widgets/viewer/overlay/bottom/video.dart +++ b/lib/widgets/viewer/overlay/bottom/video.dart @@ -119,6 +119,7 @@ class _VideoControlOverlayState extends State with SingleTi Widget _buildProgressBar() { const progressBarBorderRadius = 123.0; final blurred = settings.enableOverlayBlurEffect; + const textStyle = TextStyle(shadows: Constants.embossShadows); return SizeTransition( sizeFactor: scale, child: BlurredRRect( @@ -138,50 +139,62 @@ class _VideoControlOverlayState extends State with SingleTi onHorizontalDragEnd: (details) { if (_playingOnDragStart) controller!.play(); }, - child: Container( - padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16) + const EdgeInsets.only(bottom: 16), - decoration: BoxDecoration( - color: overlayBackgroundColor(blurred: blurred), - border: AvesBorder.border, - borderRadius: const BorderRadius.all(Radius.circular(progressBarBorderRadius)), + child: ConstrainedBox( + constraints: const BoxConstraints( + minHeight: kMinInteractiveDimension, ), - child: Column( - key: _progressBarKey, - children: [ - Row( - children: [ - StreamBuilder( + child: Container( + alignment: Alignment.center, + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16), + decoration: BoxDecoration( + color: overlayBackgroundColor(blurred: blurred), + border: AvesBorder.border, + borderRadius: const BorderRadius.all(Radius.circular(progressBarBorderRadius)), + ), + child: Column( + key: _progressBarKey, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + StreamBuilder( + stream: positionStream, + builder: (context, snapshot) { + // do not use stream snapshot because it is obsolete when switching between videos + final position = controller?.currentPosition.floor() ?? 0; + return Text( + formatFriendlyDuration(Duration(milliseconds: position)), + style: textStyle, + ); + }), + const Spacer(), + Text( + entry.durationText, + style: textStyle, + ), + ], + ), + ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(4)), + child: StreamBuilder( stream: positionStream, builder: (context, snapshot) { // do not use stream snapshot because it is obsolete when switching between videos - final position = controller?.currentPosition.floor() ?? 0; - return Text( - formatFriendlyDuration(Duration(milliseconds: position)), - style: const TextStyle(shadows: Constants.embossShadows), + var progress = controller?.progress ?? 0.0; + if (!progress.isFinite) progress = 0.0; + return LinearProgressIndicator( + value: progress, + backgroundColor: Colors.grey.shade700, ); }), - const Spacer(), - Text( - entry.durationText, - style: const TextStyle(shadows: Constants.embossShadows), - ), - ], - ), - ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(4)), - child: StreamBuilder( - stream: positionStream, - builder: (context, snapshot) { - // do not use stream snapshot because it is obsolete when switching between videos - var progress = controller?.progress ?? 0.0; - if (!progress.isFinite) progress = 0.0; - return LinearProgressIndicator( - value: progress, - backgroundColor: Colors.grey.shade700, - ); - }), - ), - ], + ), + const Text( + // fake text below to match the height of the text above and center the whole thing + '', + style: textStyle, + ), + ], + ), ), ), ), diff --git a/lib/widgets/viewer/panorama_page.dart b/lib/widgets/viewer/panorama_page.dart index b2e0e4653..9e13e9965 100644 --- a/lib/widgets/viewer/panorama_page.dart +++ b/lib/widgets/viewer/panorama_page.dart @@ -1,9 +1,12 @@ +import 'dart:math'; + import 'package:aves/model/entry.dart'; import 'package:aves/model/entry_images.dart'; import 'package:aves/model/panorama.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/basic/insets.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/common/extensions/media_query.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'; @@ -70,7 +73,7 @@ class _PanoramaPageState extends State { 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 as Image?, + child: child as Image, ); }, child: Image( @@ -78,8 +81,8 @@ class _PanoramaPageState extends State { ), ), Positioned( - bottom: 0, right: 0, + bottom: 0, child: TooltipTheme( data: TooltipTheme.of(context).copyWith( preferBelow: false, @@ -89,24 +92,29 @@ class _PanoramaPageState extends State { builder: (context, overlayVisible, child) { return Visibility( visible: overlayVisible, - child: Selector( - selector: (context, mq) => mq.viewPadding + mq.viewInsets, - builder: (context, mqPadding, child) { - return Padding( - padding: const EdgeInsets.all(8) + EdgeInsets.only(right: mqPadding.right, bottom: mqPadding.bottom), - child: OverlayButton( - child: ValueListenableBuilder( - valueListenable: _sensorControl, - builder: (context, sensorControl, child) { - return IconButton( - icon: Icon(sensorControl == SensorControl.None ? AIcons.sensorControl : AIcons.sensorControlOff), - onPressed: _toggleSensor, - tooltip: sensorControl == SensorControl.None ? context.l10n.panoramaEnableSensorControl : context.l10n.panoramaDisableSensorControl, - ); - }), + child: Selector( + selector: (context, mq) => max(mq.effectiveBottomPadding, mq.systemGestureInsets.bottom), + builder: (context, mqPaddingBottom, child) { + return SafeArea( + bottom: false, + child: Padding( + padding: const EdgeInsets.all(8) + EdgeInsets.only(bottom: mqPaddingBottom), + child: child, ), ); }, + child: OverlayButton( + child: ValueListenableBuilder( + valueListenable: _sensorControl, + builder: (context, sensorControl, child) { + return IconButton( + icon: Icon(sensorControl == SensorControl.None ? AIcons.sensorControl : AIcons.sensorControlOff), + onPressed: _toggleSensor, + tooltip: sensorControl == SensorControl.None ? context.l10n.panoramaEnableSensorControl : context.l10n.panoramaDisableSensorControl, + ); + }, + ), + ), ), ); },