overlay: bottom overlay scrolling along vertical axis
This commit is contained in:
parent
9f5566d55d
commit
2176c654f1
1 changed files with 80 additions and 53 deletions
|
@ -3,6 +3,7 @@ import 'dart:math';
|
||||||
|
|
||||||
import 'package:aves/model/collection_lens.dart';
|
import 'package:aves/model/collection_lens.dart';
|
||||||
import 'package:aves/model/image_entry.dart';
|
import 'package:aves/model/image_entry.dart';
|
||||||
|
import 'package:aves/utils/change_notifier.dart';
|
||||||
import 'package:aves/utils/constants.dart';
|
import 'package:aves/utils/constants.dart';
|
||||||
import 'package:aves/widgets/common/image_providers/thumbnail_provider.dart';
|
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_image_provider.dart';
|
||||||
|
@ -40,6 +41,7 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
||||||
int _currentHorizontalPage;
|
int _currentHorizontalPage;
|
||||||
ValueNotifier<int> _currentVerticalPage;
|
ValueNotifier<int> _currentVerticalPage;
|
||||||
PageController _horizontalPager, _verticalPager;
|
PageController _horizontalPager, _verticalPager;
|
||||||
|
final AChangeNotifier _verticalScrollNotifier = AChangeNotifier();
|
||||||
final ValueNotifier<bool> _overlayVisible = ValueNotifier(true);
|
final ValueNotifier<bool> _overlayVisible = ValueNotifier(true);
|
||||||
AnimationController _overlayAnimationController;
|
AnimationController _overlayAnimationController;
|
||||||
Animation<double> _topOverlayScale, _bottomOverlayScale;
|
Animation<double> _topOverlayScale, _bottomOverlayScale;
|
||||||
|
@ -69,7 +71,7 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
||||||
_currentHorizontalPage = max(0, entries.indexOf(_entry));
|
_currentHorizontalPage = max(0, entries.indexOf(_entry));
|
||||||
_currentVerticalPage = ValueNotifier(imagePage);
|
_currentVerticalPage = ValueNotifier(imagePage);
|
||||||
_horizontalPager = PageController(initialPage: _currentHorizontalPage);
|
_horizontalPager = PageController(initialPage: _currentHorizontalPage);
|
||||||
_verticalPager = PageController(initialPage: _currentVerticalPage.value);
|
_verticalPager = PageController(initialPage: _currentVerticalPage.value)..addListener(_onVerticalPageControllerChange);
|
||||||
_overlayAnimationController = AnimationController(
|
_overlayAnimationController = AnimationController(
|
||||||
duration: const Duration(milliseconds: 400),
|
duration: const Duration(milliseconds: 400),
|
||||||
vsync: this,
|
vsync: this,
|
||||||
|
@ -110,6 +112,7 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
||||||
_overlayAnimationController.dispose();
|
_overlayAnimationController.dispose();
|
||||||
_overlayVisible.removeListener(_onOverlayVisibleChange);
|
_overlayVisible.removeListener(_onOverlayVisibleChange);
|
||||||
_videoControllers.forEach((kv) => kv.item2.dispose());
|
_videoControllers.forEach((kv) => kv.item2.dispose());
|
||||||
|
_verticalPager.removeListener(_onVerticalPageControllerChange);
|
||||||
_unregisterWidget(widget);
|
_unregisterWidget(widget);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
@ -124,6 +127,76 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final topOverlay = ValueListenableBuilder<int>(
|
||||||
|
valueListenable: _currentVerticalPage,
|
||||||
|
builder: (context, page, child) {
|
||||||
|
final showOverlay = _entry != null && page == imagePage;
|
||||||
|
return showOverlay
|
||||||
|
? FullscreenTopOverlay(
|
||||||
|
entries: entries,
|
||||||
|
index: _currentHorizontalPage,
|
||||||
|
scale: _topOverlayScale,
|
||||||
|
canToggleFavourite: hasCollection,
|
||||||
|
viewInsets: _frozenViewInsets,
|
||||||
|
viewPadding: _frozenViewPadding,
|
||||||
|
onActionSelected: (action) => _actionDelegate.onActionSelected(context, _entry, action),
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
final videoController = _entry != null && _entry.isVideo ? _videoControllers.firstWhere((kv) => kv.item1 == _entry.uri, orElse: () => null)?.item2 : null;
|
||||||
|
Widget bottomOverlay = Column(
|
||||||
|
children: [
|
||||||
|
if (videoController != null)
|
||||||
|
VideoControlOverlay(
|
||||||
|
entry: _entry,
|
||||||
|
controller: videoController,
|
||||||
|
scale: _bottomOverlayScale,
|
||||||
|
viewInsets: _frozenViewInsets,
|
||||||
|
viewPadding: _frozenViewPadding,
|
||||||
|
),
|
||||||
|
SlideTransition(
|
||||||
|
position: _bottomOverlayOffset,
|
||||||
|
child: FullscreenBottomOverlay(
|
||||||
|
entries: entries,
|
||||||
|
index: _currentHorizontalPage,
|
||||||
|
showPosition: hasCollection,
|
||||||
|
viewInsets: _frozenViewInsets,
|
||||||
|
viewPadding: _frozenViewPadding,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
bottomOverlay = ValueListenableBuilder<double>(
|
||||||
|
valueListenable: _overlayAnimationController,
|
||||||
|
builder: (context, animation, child) {
|
||||||
|
return Visibility(
|
||||||
|
visible: _entry != null && _overlayAnimationController.status != AnimationStatus.dismissed,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: bottomOverlay,
|
||||||
|
);
|
||||||
|
|
||||||
|
bottomOverlay = Selector<MediaQueryData, double>(
|
||||||
|
selector: (c, mq) => mq.size.height,
|
||||||
|
builder: (c, mqHeight, child) {
|
||||||
|
// when orientation change, the `PageController` offset is not updated right away
|
||||||
|
// and it does not trigger its listeners when it does, so we force a refresh in the next frame
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) => _onVerticalPageControllerChange());
|
||||||
|
return AnimatedBuilder(
|
||||||
|
animation: _verticalScrollNotifier,
|
||||||
|
builder: (context, child) => Positioned(
|
||||||
|
bottom: (_verticalPager.offset ?? 0) - mqHeight,
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: bottomOverlay,
|
||||||
|
);
|
||||||
|
|
||||||
return WillPopScope(
|
return WillPopScope(
|
||||||
onWillPop: () {
|
onWillPop: () {
|
||||||
if (_currentVerticalPage.value == infoPage) {
|
if (_currentVerticalPage.value == infoPage) {
|
||||||
|
@ -147,63 +220,17 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
||||||
onImageTap: () => _overlayVisible.value = !_overlayVisible.value,
|
onImageTap: () => _overlayVisible.value = !_overlayVisible.value,
|
||||||
onImagePageRequested: () => _goToVerticalPage(imagePage),
|
onImagePageRequested: () => _goToVerticalPage(imagePage),
|
||||||
),
|
),
|
||||||
ValueListenableBuilder<int>(
|
topOverlay,
|
||||||
valueListenable: _currentVerticalPage,
|
bottomOverlay,
|
||||||
builder: (context, page, child) {
|
|
||||||
final showOverlay = _entry != null && page == imagePage;
|
|
||||||
return showOverlay
|
|
||||||
? FullscreenTopOverlay(
|
|
||||||
entries: entries,
|
|
||||||
index: _currentHorizontalPage,
|
|
||||||
scale: _topOverlayScale,
|
|
||||||
canToggleFavourite: hasCollection,
|
|
||||||
viewInsets: _frozenViewInsets,
|
|
||||||
viewPadding: _frozenViewPadding,
|
|
||||||
onActionSelected: (action) => _actionDelegate.onActionSelected(context, _entry, action),
|
|
||||||
)
|
|
||||||
: const SizedBox.shrink();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ValueListenableBuilder<int>(
|
|
||||||
valueListenable: _currentVerticalPage,
|
|
||||||
builder: (context, page, child) {
|
|
||||||
final showOverlay = _entry != null && page == imagePage;
|
|
||||||
final videoController = showOverlay && _entry.isVideo ? _videoControllers.firstWhere((kv) => kv.item1 == _entry.uri, orElse: () => null)?.item2 : null;
|
|
||||||
return Positioned(
|
|
||||||
bottom: 0,
|
|
||||||
child: Opacity(
|
|
||||||
opacity: showOverlay ? 1 : 0,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
if (videoController != null)
|
|
||||||
VideoControlOverlay(
|
|
||||||
entry: _entry,
|
|
||||||
controller: videoController,
|
|
||||||
scale: _bottomOverlayScale,
|
|
||||||
viewInsets: _frozenViewInsets,
|
|
||||||
viewPadding: _frozenViewPadding,
|
|
||||||
),
|
|
||||||
SlideTransition(
|
|
||||||
position: _bottomOverlayOffset,
|
|
||||||
child: FullscreenBottomOverlay(
|
|
||||||
entries: entries,
|
|
||||||
index: _currentHorizontalPage,
|
|
||||||
showPosition: hasCollection,
|
|
||||||
viewInsets: _frozenViewInsets,
|
|
||||||
viewPadding: _frozenViewPadding,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onVerticalPageControllerChange() {
|
||||||
|
_verticalScrollNotifier.notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _goToVerticalPage(int page) {
|
Future<void> _goToVerticalPage(int page) {
|
||||||
return _verticalPager.animateToPage(
|
return _verticalPager.animateToPage(
|
||||||
page,
|
page,
|
||||||
|
|
Loading…
Reference in a new issue