viewer: minimap in overlay
This commit is contained in:
parent
c742b72f70
commit
9b9dc1db40
7 changed files with 243 additions and 37 deletions
|
@ -45,6 +45,7 @@ class Settings extends ChangeNotifier {
|
|||
static const pinnedFiltersKey = 'pinned_filters';
|
||||
|
||||
// viewer
|
||||
static const showOverlayMinimapKey = 'show_overlay_minimap';
|
||||
static const showOverlayShootingDetailsKey = 'show_overlay_shooting_details';
|
||||
|
||||
// info
|
||||
|
@ -159,6 +160,10 @@ class Settings extends ChangeNotifier {
|
|||
|
||||
// viewer
|
||||
|
||||
bool get showOverlayMinimap => getBoolOrDefault(showOverlayMinimapKey, false);
|
||||
|
||||
set showOverlayMinimap(bool newValue) => setAndNotify(showOverlayMinimapKey, newValue);
|
||||
|
||||
bool get showOverlayShootingDetails => getBoolOrDefault(showOverlayShootingDetailsKey, true);
|
||||
|
||||
set showOverlayShootingDetails(bool newValue) => setAndNotify(showOverlayShootingDetailsKey, newValue);
|
||||
|
|
|
@ -10,6 +10,7 @@ import 'package:aves/utils/durations.dart';
|
|||
import 'package:aves/widgets/collection/collection_page.dart';
|
||||
import 'package:aves/widgets/common/action_delegates/entry_action_delegate.dart';
|
||||
import 'package:aves/widgets/fullscreen/image_page.dart';
|
||||
import 'package:aves/widgets/fullscreen/image_view.dart';
|
||||
import 'package:aves/widgets/fullscreen/info/info_page.dart';
|
||||
import 'package:aves/widgets/fullscreen/info/notifications.dart';
|
||||
import 'package:aves/widgets/fullscreen/overlay/bottom.dart';
|
||||
|
@ -52,6 +53,7 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
|||
EdgeInsets _frozenViewInsets, _frozenViewPadding;
|
||||
EntryActionDelegate _actionDelegate;
|
||||
final List<Tuple2<String, IjkMediaController>> _videoControllers = [];
|
||||
final List<Tuple2<String, ValueNotifier<ViewState>>> _viewStateNotifiers = [];
|
||||
|
||||
CollectionLens get collection => widget.collection;
|
||||
|
||||
|
@ -97,7 +99,7 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
|||
collection: collection,
|
||||
showInfo: () => _goToVerticalPage(infoPage),
|
||||
);
|
||||
_initVideoController();
|
||||
_initViewStateControllers();
|
||||
_registerWidget(widget);
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _initOverlay());
|
||||
|
@ -169,6 +171,7 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
|||
onHorizontalPageChanged: _onHorizontalPageChanged,
|
||||
onImageTap: () => _overlayVisible.value = !_overlayVisible.value,
|
||||
onImagePageRequested: () => _goToVerticalPage(imagePage),
|
||||
viewStateNotifiers: _viewStateNotifiers,
|
||||
),
|
||||
_buildTopOverlay(),
|
||||
_buildBottomOverlay(),
|
||||
|
@ -183,6 +186,7 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
|||
valueListenable: _entryNotifier,
|
||||
builder: (context, entry, child) {
|
||||
if (entry == null) return SizedBox.shrink();
|
||||
final viewStateNotifier = _viewStateNotifiers.firstWhere((kv) => kv.item1 == entry.uri, orElse: () => null)?.item2;
|
||||
return FullscreenTopOverlay(
|
||||
entry: entry,
|
||||
scale: _topOverlayScale,
|
||||
|
@ -190,6 +194,7 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
|||
viewInsets: _frozenViewInsets,
|
||||
viewPadding: _frozenViewPadding,
|
||||
onActionSelected: (action) => _actionDelegate.onActionSelected(context, entry, action),
|
||||
viewStateNotifier: viewStateNotifier,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -324,7 +329,7 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
|||
if (_entryNotifier.value == newEntry) return;
|
||||
_entryNotifier.value = newEntry;
|
||||
_pauseVideoControllers();
|
||||
_initVideoController();
|
||||
_initViewStateControllers();
|
||||
}
|
||||
|
||||
void _onLeave() {
|
||||
|
@ -381,33 +386,51 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
|||
}
|
||||
}
|
||||
|
||||
// video controller
|
||||
// state controllers/monitors
|
||||
|
||||
void _pauseVideoControllers() => _videoControllers.forEach((e) => e.item2.pause());
|
||||
|
||||
Future<void> _initVideoController() async {
|
||||
void _initViewStateControllers() {
|
||||
final entry = _entryNotifier.value;
|
||||
if (entry == null || !entry.isVideo) return;
|
||||
if (entry == null) return;
|
||||
|
||||
final uri = entry.uri;
|
||||
var controllerEntry = _videoControllers.firstWhere((kv) => kv.item1 == uri, orElse: () => null);
|
||||
if (controllerEntry != null) {
|
||||
_videoControllers.remove(controllerEntry);
|
||||
} else {
|
||||
// do not set data source of IjkMediaController here
|
||||
controllerEntry = Tuple2(uri, IjkMediaController());
|
||||
}
|
||||
_videoControllers.insert(0, controllerEntry);
|
||||
while (_videoControllers.length > 3) {
|
||||
_videoControllers.removeLast().item2.dispose();
|
||||
_initViewSpecificController<ValueNotifier<ViewState>>(
|
||||
uri,
|
||||
_viewStateNotifiers,
|
||||
() => ValueNotifier<ViewState>(ViewState.zero),
|
||||
(_) => _.dispose(),
|
||||
);
|
||||
if (entry.isVideo) {
|
||||
_initViewSpecificController<IjkMediaController>(
|
||||
uri,
|
||||
_videoControllers,
|
||||
() => IjkMediaController(),
|
||||
(_) => _.dispose(),
|
||||
);
|
||||
}
|
||||
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void _initViewSpecificController<T>(String uri, List<Tuple2<String, T>> controllers, T Function() builder, void Function(T controller) disposer) {
|
||||
var controller = controllers.firstWhere((kv) => kv.item1 == uri, orElse: () => null);
|
||||
if (controller != null) {
|
||||
controllers.remove(controller);
|
||||
} else {
|
||||
controller = Tuple2(uri, builder());
|
||||
}
|
||||
controllers.insert(0, controller);
|
||||
while (controllers.length > 3) {
|
||||
disposer?.call(controllers.removeLast().item2);
|
||||
}
|
||||
}
|
||||
|
||||
void _pauseVideoControllers() => _videoControllers.forEach((e) => e.item2.pause());
|
||||
}
|
||||
|
||||
class FullscreenVerticalPageView extends StatefulWidget {
|
||||
final CollectionLens collection;
|
||||
final ValueNotifier<ImageEntry> entryNotifier;
|
||||
final List<Tuple2<String, ValueNotifier<ViewState>>> viewStateNotifiers;
|
||||
final List<Tuple2<String, IjkMediaController>> videoControllers;
|
||||
final PageController horizontalPager, verticalPager;
|
||||
final void Function(int page) onVerticalPageChanged, onHorizontalPageChanged;
|
||||
|
@ -416,6 +439,7 @@ class FullscreenVerticalPageView extends StatefulWidget {
|
|||
const FullscreenVerticalPageView({
|
||||
@required this.collection,
|
||||
@required this.entryNotifier,
|
||||
@required this.viewStateNotifiers,
|
||||
@required this.videoControllers,
|
||||
@required this.verticalPager,
|
||||
@required this.horizontalPager,
|
||||
|
@ -482,11 +506,13 @@ class _FullscreenVerticalPageViewState extends State<FullscreenVerticalPageView>
|
|||
pageController: widget.horizontalPager,
|
||||
onTap: widget.onImageTap,
|
||||
onPageChanged: widget.onHorizontalPageChanged,
|
||||
viewStateNotifiers: widget.viewStateNotifiers,
|
||||
videoControllers: widget.videoControllers,
|
||||
)
|
||||
: SingleImagePage(
|
||||
entry: entry,
|
||||
onTap: widget.onImageTap,
|
||||
viewStateNotifiers: widget.viewStateNotifiers,
|
||||
videoControllers: widget.videoControllers,
|
||||
),
|
||||
NotificationListener(
|
||||
|
|
|
@ -10,16 +10,16 @@ class MultiImagePage extends StatefulWidget {
|
|||
final CollectionLens collection;
|
||||
final PageController pageController;
|
||||
final ValueChanged<int> onPageChanged;
|
||||
final ValueChanged<PhotoViewScaleState> onScaleChanged;
|
||||
final VoidCallback onTap;
|
||||
final List<Tuple2<String, ValueNotifier<ViewState>>> viewStateNotifiers;
|
||||
final List<Tuple2<String, IjkMediaController>> videoControllers;
|
||||
|
||||
const MultiImagePage({
|
||||
this.collection,
|
||||
this.pageController,
|
||||
this.onPageChanged,
|
||||
this.onScaleChanged,
|
||||
this.onTap,
|
||||
this.viewStateNotifiers,
|
||||
this.videoControllers,
|
||||
});
|
||||
|
||||
|
@ -49,8 +49,8 @@ class MultiImagePageState extends State<MultiImagePage> with AutomaticKeepAliveC
|
|||
key: Key('imageview'),
|
||||
entry: entry,
|
||||
heroTag: widget.collection.heroTag(entry),
|
||||
onScaleChanged: widget.onScaleChanged,
|
||||
onTap: widget.onTap,
|
||||
viewStateNotifiers: widget.viewStateNotifiers,
|
||||
videoControllers: widget.videoControllers,
|
||||
),
|
||||
);
|
||||
|
@ -66,14 +66,14 @@ class MultiImagePageState extends State<MultiImagePage> with AutomaticKeepAliveC
|
|||
|
||||
class SingleImagePage extends StatefulWidget {
|
||||
final ImageEntry entry;
|
||||
final ValueChanged<PhotoViewScaleState> onScaleChanged;
|
||||
final VoidCallback onTap;
|
||||
final List<Tuple2<String, ValueNotifier<ViewState>>> viewStateNotifiers;
|
||||
final List<Tuple2<String, IjkMediaController>> videoControllers;
|
||||
|
||||
const SingleImagePage({
|
||||
this.entry,
|
||||
this.onScaleChanged,
|
||||
this.onTap,
|
||||
this.viewStateNotifiers,
|
||||
this.videoControllers,
|
||||
});
|
||||
|
||||
|
@ -90,8 +90,8 @@ class SingleImagePageState extends State<SingleImagePage> with AutomaticKeepAliv
|
|||
axis: [Axis.vertical],
|
||||
child: ImageView(
|
||||
entry: widget.entry,
|
||||
onScaleChanged: widget.onScaleChanged,
|
||||
onTap: widget.onTap,
|
||||
viewStateNotifiers: widget.viewStateNotifiers,
|
||||
videoControllers: widget.videoControllers,
|
||||
),
|
||||
);
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/widgets/collection/empty.dart';
|
||||
|
@ -6,28 +8,56 @@ import 'package:aves/widgets/common/image_providers/thumbnail_provider.dart';
|
|||
import 'package:aves/widgets/common/image_providers/uri_image_provider.dart';
|
||||
import 'package:aves/widgets/common/image_providers/uri_picture_provider.dart';
|
||||
import 'package:aves/widgets/fullscreen/video_view.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_ijkplayer/flutter_ijkplayer.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class ImageView extends StatelessWidget {
|
||||
class ImageView extends StatefulWidget {
|
||||
final ImageEntry entry;
|
||||
final Object heroTag;
|
||||
final ValueChanged<PhotoViewScaleState> onScaleChanged;
|
||||
final VoidCallback onTap;
|
||||
final List<Tuple2<String, ValueNotifier<ViewState>>> viewStateNotifiers;
|
||||
final List<Tuple2<String, IjkMediaController>> videoControllers;
|
||||
|
||||
const ImageView({
|
||||
Key key,
|
||||
this.entry,
|
||||
@required this.entry,
|
||||
this.heroTag,
|
||||
this.onScaleChanged,
|
||||
this.onTap,
|
||||
this.videoControllers,
|
||||
@required this.onTap,
|
||||
@required this.viewStateNotifiers,
|
||||
@required this.videoControllers,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_ImageViewState createState() => _ImageViewState();
|
||||
}
|
||||
|
||||
class _ImageViewState extends State<ImageView> {
|
||||
final PhotoViewController _photoViewController = PhotoViewController();
|
||||
StreamSubscription<PhotoViewControllerValue> _subscription;
|
||||
|
||||
ImageEntry get entry => widget.entry;
|
||||
|
||||
VoidCallback get onTap => widget.onTap;
|
||||
|
||||
ValueNotifier<ViewState> get viewStateNotifier => widget.viewStateNotifiers.firstWhere((kv) => kv.item1 == entry.uri, orElse: () => null)?.item2;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_subscription = _photoViewController.outputStateStream.listen(_onViewChanged);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_subscription.cancel();
|
||||
_subscription = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const backgroundDecoration = BoxDecoration(color: Colors.transparent);
|
||||
|
@ -35,7 +65,7 @@ class ImageView extends StatelessWidget {
|
|||
// no hero for videos, as a typical video first frame is different from its thumbnail
|
||||
|
||||
if (entry.isVideo) {
|
||||
final videoController = videoControllers.firstWhere((kv) => kv.item1 == entry.uri, orElse: () => null)?.item2;
|
||||
final videoController = widget.videoControllers.firstWhere((kv) => kv.item1 == entry.uri, orElse: () => null)?.item2;
|
||||
return PhotoView.customChild(
|
||||
child: videoController != null
|
||||
? AvesVideo(
|
||||
|
@ -44,7 +74,7 @@ class ImageView extends StatelessWidget {
|
|||
)
|
||||
: SizedBox(),
|
||||
backgroundDecoration: backgroundDecoration,
|
||||
scaleStateChangedCallback: onScaleChanged,
|
||||
controller: _photoViewController,
|
||||
minScale: PhotoViewComputedScale.contained,
|
||||
initialScale: PhotoViewComputedScale.contained,
|
||||
onTapUp: (tapContext, details, value) => onTap?.call(),
|
||||
|
@ -88,7 +118,7 @@ class ImageView extends StatelessWidget {
|
|||
colorFilter: colorFilter,
|
||||
),
|
||||
backgroundDecoration: backgroundDecoration,
|
||||
scaleStateChangedCallback: onScaleChanged,
|
||||
controller: _photoViewController,
|
||||
minScale: PhotoViewComputedScale.contained,
|
||||
initialScale: PhotoViewComputedScale.contained,
|
||||
onTapUp: (tapContext, details, value) => onTap?.call(),
|
||||
|
@ -113,7 +143,7 @@ class ImageView extends StatelessWidget {
|
|||
),
|
||||
loadFailedChild: _buildError(),
|
||||
backgroundDecoration: backgroundDecoration,
|
||||
scaleStateChangedCallback: onScaleChanged,
|
||||
controller: _photoViewController,
|
||||
minScale: PhotoViewComputedScale.contained,
|
||||
initialScale: PhotoViewComputedScale.contained,
|
||||
onTapUp: (tapContext, details, value) => onTap?.call(),
|
||||
|
@ -123,9 +153,9 @@ class ImageView extends StatelessWidget {
|
|||
child = _buildError();
|
||||
}
|
||||
|
||||
return heroTag != null
|
||||
return widget.heroTag != null
|
||||
? Hero(
|
||||
tag: heroTag,
|
||||
tag: widget.heroTag,
|
||||
transitionOnUserGestures: true,
|
||||
child: child,
|
||||
)
|
||||
|
@ -145,4 +175,22 @@ class ImageView extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
);
|
||||
|
||||
void _onViewChanged(PhotoViewControllerValue v) {
|
||||
viewStateNotifier?.value = ViewState(v.position, v.scale);
|
||||
}
|
||||
}
|
||||
|
||||
class ViewState {
|
||||
final Offset position;
|
||||
final double scale;
|
||||
|
||||
static const ViewState zero = ViewState(Offset(0.0, 0.0), 0);
|
||||
|
||||
const ViewState(this.position, this.scale);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '$runtimeType#${shortHash(this)}{position=$position, scale=$scale}';
|
||||
}
|
||||
}
|
||||
|
|
101
lib/widgets/fullscreen/overlay/minimap.dart
Normal file
101
lib/widgets/fullscreen/overlay/minimap.dart
Normal file
|
@ -0,0 +1,101 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/widgets/fullscreen/image_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class Minimap extends StatelessWidget {
|
||||
final ImageEntry entry;
|
||||
final ValueNotifier<ViewState> viewStateNotifier;
|
||||
final Size size;
|
||||
|
||||
static const defaultSize = Size(96, 96);
|
||||
|
||||
const Minimap({
|
||||
@required this.entry,
|
||||
@required this.viewStateNotifier,
|
||||
this.size = defaultSize,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<MediaQueryData, Size>(
|
||||
selector: (context, mq) => mq.size,
|
||||
builder: (context, mqSize, child) {
|
||||
return AnimatedBuilder(
|
||||
animation: viewStateNotifier,
|
||||
builder: (context, child) {
|
||||
return CustomPaint(
|
||||
painter: MinimapPainter(
|
||||
entrySize: entry.displaySize,
|
||||
viewportSize: mqSize,
|
||||
viewCenterOffset: viewStateNotifier.value.position,
|
||||
viewScale: viewStateNotifier.value.scale,
|
||||
minimapBorderColor: Colors.white30,
|
||||
),
|
||||
size: size,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class MinimapPainter extends CustomPainter {
|
||||
final Size entrySize, viewportSize;
|
||||
final Offset viewCenterOffset;
|
||||
final double viewScale;
|
||||
final Color minimapBorderColor, viewportBorderColor;
|
||||
|
||||
const MinimapPainter({
|
||||
@required this.entrySize,
|
||||
@required this.viewportSize,
|
||||
@required this.viewCenterOffset,
|
||||
@required this.viewScale,
|
||||
this.minimapBorderColor = Colors.white,
|
||||
this.viewportBorderColor = Colors.white,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final viewSize = entrySize * viewScale;
|
||||
if (viewSize.isEmpty) return;
|
||||
|
||||
final canvasScale = size.longestSide / viewSize.longestSide;
|
||||
final scaledEntrySize = viewSize * canvasScale;
|
||||
final scaledViewportSize = viewportSize * canvasScale;
|
||||
// hide minimap when image is in full view
|
||||
if (scaledViewportSize >= scaledEntrySize) return;
|
||||
|
||||
final entryRect = Rect.fromCenter(
|
||||
center: size.center(Offset.zero),
|
||||
width: scaledEntrySize.width,
|
||||
height: scaledEntrySize.height,
|
||||
);
|
||||
final viewportRect = Rect.fromCenter(
|
||||
center: size.center(Offset.zero) - viewCenterOffset * canvasScale,
|
||||
width: min(scaledEntrySize.width, scaledViewportSize.width),
|
||||
height: min(scaledEntrySize.height, scaledViewportSize.height),
|
||||
);
|
||||
|
||||
canvas.translate((entryRect.width - size.width) / 2, (entryRect.height - size.height) / 2);
|
||||
|
||||
final fill = Paint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = Color(0x33000000);
|
||||
final minimapStroke = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..color = minimapBorderColor;
|
||||
final viewportStroke = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..color = viewportBorderColor;
|
||||
|
||||
canvas.drawRect(viewportRect, fill);
|
||||
canvas.drawRect(entryRect, fill);
|
||||
canvas.drawRect(entryRect, minimapStroke);
|
||||
canvas.drawRect(viewportRect, viewportStroke);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(CustomPainter oldDelegate) => true;
|
||||
}
|
|
@ -2,11 +2,14 @@ import 'dart:math';
|
|||
|
||||
import 'package:aves/model/favourite_repo.dart';
|
||||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/widgets/common/entry_actions.dart';
|
||||
import 'package:aves/widgets/common/fx/sweeper.dart';
|
||||
import 'package:aves/widgets/common/icons.dart';
|
||||
import 'package:aves/widgets/common/menu_row.dart';
|
||||
import 'package:aves/widgets/fullscreen/image_view.dart';
|
||||
import 'package:aves/widgets/fullscreen/overlay/common.dart';
|
||||
import 'package:aves/widgets/fullscreen/overlay/minimap.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
@ -18,6 +21,7 @@ class FullscreenTopOverlay extends StatelessWidget {
|
|||
final EdgeInsets viewInsets, viewPadding;
|
||||
final Function(EntryAction value) onActionSelected;
|
||||
final bool canToggleFavourite;
|
||||
final ValueNotifier<ViewState> viewStateNotifier;
|
||||
|
||||
static const double padding = 8;
|
||||
|
||||
|
@ -33,6 +37,7 @@ class FullscreenTopOverlay extends StatelessWidget {
|
|||
@required this.viewInsets,
|
||||
@required this.viewPadding,
|
||||
@required this.onActionSelected,
|
||||
this.viewStateNotifier,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -58,8 +63,7 @@ class FullscreenTopOverlay extends StatelessWidget {
|
|||
].where(_canDo).take(quickActionCount).toList();
|
||||
final inAppActions = EntryActions.inApp.where((action) => !quickActions.contains(action)).where(_canDo).toList();
|
||||
final externalAppActions = EntryActions.externalApp.where(_canDo).toList();
|
||||
|
||||
return _TopOverlayRow(
|
||||
final buttonRow = _TopOverlayRow(
|
||||
quickActions: quickActions,
|
||||
inAppActions: inAppActions,
|
||||
externalAppActions: externalAppActions,
|
||||
|
@ -67,6 +71,23 @@ class FullscreenTopOverlay extends StatelessWidget {
|
|||
entry: entry,
|
||||
onActionSelected: onActionSelected,
|
||||
);
|
||||
|
||||
return settings.showOverlayMinimap && viewStateNotifier != null
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
buttonRow,
|
||||
SizedBox(height: 8),
|
||||
FadeTransition(
|
||||
opacity: scale,
|
||||
child: Minimap(
|
||||
entry: entry,
|
||||
viewStateNotifier: viewStateNotifier,
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
: buttonRow;
|
||||
},
|
||||
),
|
||||
),
|
||||
|
|
|
@ -177,6 +177,11 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
title: 'Viewer',
|
||||
expandedNotifier: _expandedNotifier,
|
||||
children: [
|
||||
SwitchListTile(
|
||||
value: settings.showOverlayMinimap,
|
||||
onChanged: (v) => settings.showOverlayMinimap = v,
|
||||
title: Text('Show minimap'),
|
||||
),
|
||||
SwitchListTile(
|
||||
value: settings.showOverlayShootingDetails,
|
||||
onChanged: (v) => settings.showOverlayShootingDetails = v,
|
||||
|
|
Loading…
Reference in a new issue