#24 viewer: added option to show/hide info on overlay
This commit is contained in:
parent
80d7de43ed
commit
6ea9923a92
4 changed files with 113 additions and 75 deletions
|
@ -47,6 +47,7 @@ class Settings extends ChangeNotifier {
|
||||||
|
|
||||||
// viewer
|
// viewer
|
||||||
static const showOverlayMinimapKey = 'show_overlay_minimap';
|
static const showOverlayMinimapKey = 'show_overlay_minimap';
|
||||||
|
static const showOverlayInfoKey = 'show_overlay_info';
|
||||||
static const showOverlayShootingDetailsKey = 'show_overlay_shooting_details';
|
static const showOverlayShootingDetailsKey = 'show_overlay_shooting_details';
|
||||||
|
|
||||||
// info
|
// info
|
||||||
|
@ -166,6 +167,10 @@ class Settings extends ChangeNotifier {
|
||||||
|
|
||||||
set showOverlayMinimap(bool newValue) => setAndNotify(showOverlayMinimapKey, newValue);
|
set showOverlayMinimap(bool newValue) => setAndNotify(showOverlayMinimapKey, newValue);
|
||||||
|
|
||||||
|
bool get showOverlayInfo => getBoolOrDefault(showOverlayInfoKey, true);
|
||||||
|
|
||||||
|
set showOverlayInfo(bool newValue) => setAndNotify(showOverlayInfoKey, newValue);
|
||||||
|
|
||||||
bool get showOverlayShootingDetails => getBoolOrDefault(showOverlayShootingDetailsKey, true);
|
bool get showOverlayShootingDetails => getBoolOrDefault(showOverlayShootingDetailsKey, true);
|
||||||
|
|
||||||
set showOverlayShootingDetails(bool newValue) => setAndNotify(showOverlayShootingDetailsKey, newValue);
|
set showOverlayShootingDetails(bool newValue) => setAndNotify(showOverlayShootingDetailsKey, newValue);
|
||||||
|
|
|
@ -5,18 +5,25 @@ import 'package:flutter/material.dart';
|
||||||
final _filter = ImageFilter.blur(sigmaX: 4, sigmaY: 4);
|
final _filter = ImageFilter.blur(sigmaX: 4, sigmaY: 4);
|
||||||
|
|
||||||
class BlurredRect extends StatelessWidget {
|
class BlurredRect extends StatelessWidget {
|
||||||
|
final bool enabled;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
const BlurredRect({Key key, this.child}) : super(key: key);
|
const BlurredRect({
|
||||||
|
Key key,
|
||||||
|
this.enabled = true,
|
||||||
|
this.child,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ClipRect(
|
return enabled
|
||||||
child: BackdropFilter(
|
? ClipRect(
|
||||||
filter: _filter,
|
child: BackdropFilter(
|
||||||
child: child,
|
filter: _filter,
|
||||||
),
|
child: child,
|
||||||
);
|
),
|
||||||
|
)
|
||||||
|
: child;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,33 +25,42 @@ class _SettingsPageState extends State<SettingsPage> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
return MediaQueryDataProvider(
|
return MediaQueryDataProvider(
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('Settings'),
|
title: Text('Settings'),
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: Theme(
|
||||||
child: Consumer<Settings>(
|
data: theme.copyWith(
|
||||||
builder: (context, settings, child) => AnimationLimiter(
|
textTheme: theme.textTheme.copyWith(
|
||||||
child: ListView(
|
// dense style font for tile subtitles, without modifying title font
|
||||||
padding: EdgeInsets.all(8),
|
bodyText2: TextStyle(fontSize: 12),
|
||||||
children: AnimationConfiguration.toStaggeredList(
|
),
|
||||||
duration: Durations.staggeredAnimation,
|
),
|
||||||
delay: Durations.staggeredAnimationDelay,
|
child: SafeArea(
|
||||||
childAnimationBuilder: (child) => SlideAnimation(
|
child: Consumer<Settings>(
|
||||||
verticalOffset: 50.0,
|
builder: (context, settings, child) => AnimationLimiter(
|
||||||
child: FadeInAnimation(
|
child: ListView(
|
||||||
child: child,
|
padding: EdgeInsets.all(8),
|
||||||
|
children: AnimationConfiguration.toStaggeredList(
|
||||||
|
duration: Durations.staggeredAnimation,
|
||||||
|
delay: Durations.staggeredAnimationDelay,
|
||||||
|
childAnimationBuilder: (child) => SlideAnimation(
|
||||||
|
verticalOffset: 50.0,
|
||||||
|
child: FadeInAnimation(
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
children: [
|
||||||
|
_buildNavigationSection(context),
|
||||||
|
_buildDisplaySection(context),
|
||||||
|
_buildThumbnailsSection(context),
|
||||||
|
_buildViewerSection(context),
|
||||||
|
_buildSearchSection(context),
|
||||||
|
_buildPrivacySection(context),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
children: [
|
|
||||||
_buildNavigationSection(context),
|
|
||||||
_buildDisplaySection(context),
|
|
||||||
_buildThumbnailsSection(context),
|
|
||||||
_buildViewerSection(context),
|
|
||||||
_buildSearchSection(context),
|
|
||||||
_buildPrivacySection(context),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -188,9 +197,15 @@ class _SettingsPageState extends State<SettingsPage> {
|
||||||
onChanged: (v) => settings.showOverlayMinimap = v,
|
onChanged: (v) => settings.showOverlayMinimap = v,
|
||||||
title: Text('Show minimap'),
|
title: Text('Show minimap'),
|
||||||
),
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
value: settings.showOverlayInfo,
|
||||||
|
onChanged: (v) => settings.showOverlayInfo = v,
|
||||||
|
title: Text('Show information'),
|
||||||
|
subtitle: Text('Show title, date, location, etc.'),
|
||||||
|
),
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
value: settings.showOverlayShootingDetails,
|
value: settings.showOverlayShootingDetails,
|
||||||
onChanged: (v) => settings.showOverlayShootingDetails = v,
|
onChanged: settings.showOverlayInfo ? (v) => settings.showOverlayShootingDetails = v : null,
|
||||||
title: Text('Show shooting details'),
|
title: Text('Show shooting details'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -51,6 +51,8 @@ class _ViewerBottomOverlayState extends State<ViewerBottomOverlay> {
|
||||||
return index < entries.length ? entries[index] : null;
|
return index < entries.length ? entries[index] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MultiPageController get multiPageController => widget.multiPageController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
@ -71,7 +73,9 @@ class _ViewerBottomOverlayState extends State<ViewerBottomOverlay> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final hasEdgeContent = settings.showOverlayInfo || multiPageController != null;
|
||||||
return BlurredRect(
|
return BlurredRect(
|
||||||
|
enabled: hasEdgeContent,
|
||||||
child: Selector<MediaQueryData, Tuple3<double, EdgeInsets, EdgeInsets>>(
|
child: Selector<MediaQueryData, Tuple3<double, EdgeInsets, EdgeInsets>>(
|
||||||
selector: (c, mq) => Tuple3(mq.size.width, mq.viewInsets, mq.viewPadding),
|
selector: (c, mq) => Tuple3(mq.size.width, mq.viewInsets, mq.viewPadding),
|
||||||
builder: (c, mq, child) {
|
builder: (c, mq, child) {
|
||||||
|
@ -84,7 +88,7 @@ class _ViewerBottomOverlayState extends State<ViewerBottomOverlay> {
|
||||||
final availableWidth = mqWidth - viewPadding.horizontal;
|
final availableWidth = mqWidth - viewPadding.horizontal;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
color: kOverlayBackgroundColor,
|
color: hasEdgeContent ? kOverlayBackgroundColor: Colors.transparent,
|
||||||
padding: viewInsets + viewPadding.copyWith(top: 0),
|
padding: viewInsets + viewPadding.copyWith(top: 0),
|
||||||
child: FutureBuilder<OverlayMetadata>(
|
child: FutureBuilder<OverlayMetadata>(
|
||||||
future: _detailLoader,
|
future: _detailLoader,
|
||||||
|
@ -100,7 +104,7 @@ class _ViewerBottomOverlayState extends State<ViewerBottomOverlay> {
|
||||||
details: _lastDetails,
|
details: _lastDetails,
|
||||||
position: widget.showPosition ? '${widget.index + 1}/${widget.entries.length}' : null,
|
position: widget.showPosition ? '${widget.index + 1}/${widget.entries.length}' : null,
|
||||||
availableWidth: availableWidth,
|
availableWidth: availableWidth,
|
||||||
multiPageController: widget.multiPageController,
|
multiPageController: multiPageController,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -136,8 +140,6 @@ class _BottomOverlayContent extends AnimatedWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final infoMaxWidth = availableWidth - infoPadding.horizontal;
|
|
||||||
|
|
||||||
return DefaultTextStyle(
|
return DefaultTextStyle(
|
||||||
style: Theme.of(context).textTheme.bodyText2.copyWith(
|
style: Theme.of(context).textTheme.bodyText2.copyWith(
|
||||||
shadows: [Constants.embossShadow],
|
shadows: [Constants.embossShadow],
|
||||||
|
@ -150,48 +152,11 @@ class _BottomOverlayContent extends AnimatedWidget {
|
||||||
child: Selector<MediaQueryData, Orientation>(
|
child: Selector<MediaQueryData, Orientation>(
|
||||||
selector: (c, mq) => mq.orientation,
|
selector: (c, mq) => mq.orientation,
|
||||||
builder: (c, orientation, child) {
|
builder: (c, orientation, child) {
|
||||||
final twoColumns = orientation == Orientation.landscape && infoMaxWidth / 2 > _subRowMinWidth;
|
Widget infoColumn;
|
||||||
final subRowWidth = twoColumns ? min(_subRowMinWidth, infoMaxWidth / 2) : infoMaxWidth;
|
|
||||||
final positionTitle = _PositionTitleRow(entry: entry, collectionPosition: position, multiPageController: multiPageController);
|
|
||||||
final hasShootingDetails = details != null && !details.isEmpty && settings.showOverlayShootingDetails;
|
|
||||||
|
|
||||||
Widget infoColumn = Padding(
|
if (settings.showOverlayInfo) {
|
||||||
padding: infoPadding,
|
infoColumn = _buildInfoColumn(orientation);
|
||||||
child: Column(
|
}
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
if (positionTitle.isNotEmpty) positionTitle,
|
|
||||||
_buildSoloLocationRow(),
|
|
||||||
if (twoColumns)
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.only(top: _interRowPadding),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: subRowWidth,
|
|
||||||
child: _DateRow(
|
|
||||||
entry: entry,
|
|
||||||
multiPageController: multiPageController,
|
|
||||||
)),
|
|
||||||
_buildDuoShootingRow(subRowWidth, hasShootingDetails),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
else ...[
|
|
||||||
Container(
|
|
||||||
padding: EdgeInsets.only(top: _interRowPadding),
|
|
||||||
width: subRowWidth,
|
|
||||||
child: _DateRow(
|
|
||||||
entry: entry,
|
|
||||||
multiPageController: multiPageController,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
_buildSoloShootingRow(subRowWidth, hasShootingDetails),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (multiPageController != null) {
|
if (multiPageController != null) {
|
||||||
infoColumn = Column(
|
infoColumn = Column(
|
||||||
|
@ -203,18 +168,64 @@ class _BottomOverlayContent extends AnimatedWidget {
|
||||||
controller: multiPageController,
|
controller: multiPageController,
|
||||||
availableWidth: availableWidth,
|
availableWidth: availableWidth,
|
||||||
),
|
),
|
||||||
infoColumn,
|
if (infoColumn != null) infoColumn,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return infoColumn;
|
return infoColumn ?? SizedBox();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildInfoColumn(Orientation orientation) {
|
||||||
|
final infoMaxWidth = availableWidth - infoPadding.horizontal;
|
||||||
|
final twoColumns = orientation == Orientation.landscape && infoMaxWidth / 2 > _subRowMinWidth;
|
||||||
|
final subRowWidth = twoColumns ? min(_subRowMinWidth, infoMaxWidth / 2) : infoMaxWidth;
|
||||||
|
final positionTitle = _PositionTitleRow(entry: entry, collectionPosition: position, multiPageController: multiPageController);
|
||||||
|
final hasShootingDetails = details != null && !details.isEmpty && settings.showOverlayShootingDetails;
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: infoPadding,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (positionTitle.isNotEmpty) positionTitle,
|
||||||
|
_buildSoloLocationRow(),
|
||||||
|
if (twoColumns)
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(top: _interRowPadding),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: subRowWidth,
|
||||||
|
child: _DateRow(
|
||||||
|
entry: entry,
|
||||||
|
multiPageController: multiPageController,
|
||||||
|
)),
|
||||||
|
_buildDuoShootingRow(subRowWidth, hasShootingDetails),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else ...[
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.only(top: _interRowPadding),
|
||||||
|
width: subRowWidth,
|
||||||
|
child: _DateRow(
|
||||||
|
entry: entry,
|
||||||
|
multiPageController: multiPageController,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_buildSoloShootingRow(subRowWidth, hasShootingDetails),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildSoloLocationRow() => AnimatedSwitcher(
|
Widget _buildSoloLocationRow() => AnimatedSwitcher(
|
||||||
duration: Durations.viewerOverlayChangeAnimation,
|
duration: Durations.viewerOverlayChangeAnimation,
|
||||||
switchInCurve: Curves.easeInOutCubic,
|
switchInCurve: Curves.easeInOutCubic,
|
||||||
|
|
Loading…
Reference in a new issue