#81 viewer: padding below overlay controls to avoid system gesture area, fixed video progress bar height to match interaction standards

This commit is contained in:
Thibault Deckers 2021-10-05 19:11:37 +09:00
parent db6b47b351
commit 35929fa7ab
3 changed files with 91 additions and 63 deletions

View file

@ -11,6 +11,7 @@ import 'package:aves/theme/format.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/utils/constants.dart'; import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/common/extensions/build_context.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/common/fx/blurred.dart';
import 'package:aves/widgets/viewer/multipage/controller.dart'; import 'package:aves/widgets/viewer/multipage/controller.dart';
import 'package:aves/widgets/viewer/overlay/bottom/multipage.dart'; import 'package:aves/widgets/viewer/overlay/bottom/multipage.dart';
@ -92,14 +93,20 @@ class _ViewerBottomOverlayState extends State<ViewerBottomOverlay> {
final viewPadding = widget.viewPadding ?? mqViewPadding; final viewPadding = widget.viewPadding ?? mqViewPadding;
final availableWidth = mqWidth - viewPadding.horizontal; final availableWidth = mqWidth - viewPadding.horizontal;
return Container( return Selector<MediaQueryData, double>(
color: hasEdgeContent ? overlayBackgroundColor(blurred: blurred) : Colors.transparent, selector: (context, mq) => max(mq.effectiveBottomPadding, mq.systemGestureInsets.bottom),
padding: EdgeInsets.only( builder: (context, mqPaddingBottom, child) {
left: max(viewInsets.left, viewPadding.left), return Container(
top: 0, color: hasEdgeContent ? overlayBackgroundColor(blurred: blurred) : Colors.transparent,
right: max(viewInsets.right, viewPadding.right), padding: EdgeInsets.only(
bottom: max(viewInsets.bottom, viewPadding.bottom), left: max(viewInsets.left, viewPadding.left),
), top: 0,
right: max(viewInsets.right, viewPadding.right),
bottom: mqPaddingBottom,
),
child: child,
);
},
child: FutureBuilder<OverlayMetadata?>( child: FutureBuilder<OverlayMetadata?>(
future: _detailLoader, future: _detailLoader,
builder: (context, snapshot) { builder: (context, snapshot) {

View file

@ -119,6 +119,7 @@ class _VideoControlOverlayState extends State<VideoControlOverlay> with SingleTi
Widget _buildProgressBar() { Widget _buildProgressBar() {
const progressBarBorderRadius = 123.0; const progressBarBorderRadius = 123.0;
final blurred = settings.enableOverlayBlurEffect; final blurred = settings.enableOverlayBlurEffect;
const textStyle = TextStyle(shadows: Constants.embossShadows);
return SizeTransition( return SizeTransition(
sizeFactor: scale, sizeFactor: scale,
child: BlurredRRect( child: BlurredRRect(
@ -138,50 +139,62 @@ class _VideoControlOverlayState extends State<VideoControlOverlay> with SingleTi
onHorizontalDragEnd: (details) { onHorizontalDragEnd: (details) {
if (_playingOnDragStart) controller!.play(); if (_playingOnDragStart) controller!.play();
}, },
child: Container( child: ConstrainedBox(
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16) + const EdgeInsets.only(bottom: 16), constraints: const BoxConstraints(
decoration: BoxDecoration( minHeight: kMinInteractiveDimension,
color: overlayBackgroundColor(blurred: blurred),
border: AvesBorder.border,
borderRadius: const BorderRadius.all(Radius.circular(progressBarBorderRadius)),
), ),
child: Column( child: Container(
key: _progressBarKey, alignment: Alignment.center,
children: [ padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16),
Row( decoration: BoxDecoration(
children: [ color: overlayBackgroundColor(blurred: blurred),
StreamBuilder<int>( border: AvesBorder.border,
borderRadius: const BorderRadius.all(Radius.circular(progressBarBorderRadius)),
),
child: Column(
key: _progressBarKey,
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
StreamBuilder<int>(
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<int>(
stream: positionStream, stream: positionStream,
builder: (context, snapshot) { builder: (context, snapshot) {
// do not use stream snapshot because it is obsolete when switching between videos // do not use stream snapshot because it is obsolete when switching between videos
final position = controller?.currentPosition.floor() ?? 0; var progress = controller?.progress ?? 0.0;
return Text( if (!progress.isFinite) progress = 0.0;
formatFriendlyDuration(Duration(milliseconds: position)), return LinearProgressIndicator(
style: const TextStyle(shadows: Constants.embossShadows), value: progress,
backgroundColor: Colors.grey.shade700,
); );
}), }),
const Spacer(), ),
Text( const Text(
entry.durationText, // fake text below to match the height of the text above and center the whole thing
style: const TextStyle(shadows: Constants.embossShadows), '',
), style: textStyle,
], ),
), ],
ClipRRect( ),
borderRadius: const BorderRadius.all(Radius.circular(4)),
child: StreamBuilder<int>(
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,
);
}),
),
],
), ),
), ),
), ),

View file

@ -1,9 +1,12 @@
import 'dart:math';
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/model/entry_images.dart'; import 'package:aves/model/entry_images.dart';
import 'package:aves/model/panorama.dart'; import 'package:aves/model/panorama.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/basic/insets.dart'; import 'package:aves/widgets/common/basic/insets.dart';
import 'package:aves/widgets/common/extensions/build_context.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/common/providers/media_query_data_provider.dart';
import 'package:aves/widgets/viewer/overlay/common.dart'; import 'package:aves/widgets/viewer/overlay/common.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -70,7 +73,7 @@ class _PanoramaPageState extends State<PanoramaPage> {
croppedFullWidth: info.hasCroppedArea ? info.fullPanoSize!.width : 1.0, croppedFullWidth: info.hasCroppedArea ? info.fullPanoSize!.width : 1.0,
croppedFullHeight: info.hasCroppedArea ? info.fullPanoSize!.height : 1.0, croppedFullHeight: info.hasCroppedArea ? info.fullPanoSize!.height : 1.0,
onTap: (longitude, latitude, tilt) => _overlayVisible.value = !_overlayVisible.value, onTap: (longitude, latitude, tilt) => _overlayVisible.value = !_overlayVisible.value,
child: child as Image?, child: child as Image,
); );
}, },
child: Image( child: Image(
@ -78,8 +81,8 @@ class _PanoramaPageState extends State<PanoramaPage> {
), ),
), ),
Positioned( Positioned(
bottom: 0,
right: 0, right: 0,
bottom: 0,
child: TooltipTheme( child: TooltipTheme(
data: TooltipTheme.of(context).copyWith( data: TooltipTheme.of(context).copyWith(
preferBelow: false, preferBelow: false,
@ -89,24 +92,29 @@ class _PanoramaPageState extends State<PanoramaPage> {
builder: (context, overlayVisible, child) { builder: (context, overlayVisible, child) {
return Visibility( return Visibility(
visible: overlayVisible, visible: overlayVisible,
child: Selector<MediaQueryData, EdgeInsets>( child: Selector<MediaQueryData, double>(
selector: (context, mq) => mq.viewPadding + mq.viewInsets, selector: (context, mq) => max(mq.effectiveBottomPadding, mq.systemGestureInsets.bottom),
builder: (context, mqPadding, child) { builder: (context, mqPaddingBottom, child) {
return Padding( return SafeArea(
padding: const EdgeInsets.all(8) + EdgeInsets.only(right: mqPadding.right, bottom: mqPadding.bottom), bottom: false,
child: OverlayButton( child: Padding(
child: ValueListenableBuilder<SensorControl>( padding: const EdgeInsets.all(8) + EdgeInsets.only(bottom: mqPaddingBottom),
valueListenable: _sensorControl, child: child,
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: OverlayButton(
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 ? context.l10n.panoramaEnableSensorControl : context.l10n.panoramaDisableSensorControl,
);
},
),
),
), ),
); );
}, },