import 'package:aves/model/entry.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/widgets/common/basic/insets.dart'; import 'package:aves/widgets/common/behaviour/routes.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; import 'package:aves/widgets/viewer/entry_viewer_page.dart'; import 'package:aves/widgets/viewer/info/basic_section.dart'; import 'package:aves/widgets/viewer/info/info_app_bar.dart'; import 'package:aves/widgets/viewer/info/location_section.dart'; import 'package:aves/widgets/viewer/info/metadata/metadata_section.dart'; import 'package:aves/widgets/viewer/info/notifications.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; class InfoPage extends StatefulWidget { final CollectionLens collection; final ValueNotifier entryNotifier; final ValueNotifier visibleNotifier; const InfoPage({ Key key, @required this.collection, @required this.entryNotifier, @required this.visibleNotifier, }) : super(key: key); @override State createState() => _InfoPageState(); } class _InfoPageState extends State { final ScrollController _scrollController = ScrollController(); bool _scrollStartFromTop = false; CollectionLens get collection => widget.collection; AvesEntry get entry => widget.entryNotifier.value; @override Widget build(BuildContext context) { return MediaQueryDataProvider( child: Scaffold( body: GestureAreaProtectorStack( child: SafeArea( bottom: false, child: NotificationListener( onNotification: _handleTopScroll, child: NotificationListener( onNotification: (notification) { _openTempEntry(notification.entry); return true; }, child: Selector( selector: (c, mq) => mq.size.width, builder: (c, mqWidth, child) { return ValueListenableBuilder( valueListenable: widget.entryNotifier, builder: (context, entry, child) { return entry != null ? _InfoPageContent( collection: collection, entry: entry, visibleNotifier: widget.visibleNotifier, scrollController: _scrollController, split: mqWidth > 400, goToViewer: _goToViewer, ) : SizedBox.shrink(); }, ); }, ), ), ), ), ), resizeToAvoidBottomInset: false, ), ); } bool _handleTopScroll(Notification notification) { if (notification is ScrollNotification) { if (notification is ScrollStartNotification) { final metrics = notification.metrics; _scrollStartFromTop = metrics.pixels == metrics.minScrollExtent; } if (_scrollStartFromTop) { if (notification is ScrollUpdateNotification) { _scrollStartFromTop = notification.scrollDelta < 0; } else if (notification is ScrollEndNotification) { _scrollStartFromTop = false; } else if (notification is OverscrollNotification) { if (notification.overscroll < 0) { _goToViewer(); _scrollStartFromTop = false; } } } } return false; } void _goToViewer() { BackUpNotification().dispatch(context); _scrollController.animateTo( 0, duration: Durations.viewerPageAnimation, curve: Curves.easeInOut, ); } void _openTempEntry(AvesEntry tempEntry) { Navigator.push( context, TransparentMaterialPageRoute( settings: RouteSettings(name: EntryViewerPage.routeName), pageBuilder: (c, a, sa) => EntryViewerPage( initialEntry: tempEntry, ), ), ); } } class _InfoPageContent extends StatefulWidget { final CollectionLens collection; final AvesEntry entry; final ValueNotifier visibleNotifier; final ScrollController scrollController; final bool split; final VoidCallback goToViewer; const _InfoPageContent({ Key key, @required this.collection, @required this.entry, @required this.visibleNotifier, @required this.scrollController, @required this.split, @required this.goToViewer, }) : super(key: key); @override _InfoPageContentState createState() => _InfoPageContentState(); } class _InfoPageContentState extends State<_InfoPageContent> { static const horizontalPadding = EdgeInsets.symmetric(horizontal: 8); final ValueNotifier> _metadataNotifier = ValueNotifier({}); CollectionLens get collection => widget.collection; AvesEntry get entry => widget.entry; @override Widget build(BuildContext context) { final locationAtTop = widget.split && entry.hasGps; final locationSection = LocationSection( collection: collection, entry: entry, showTitle: !locationAtTop, visibleNotifier: widget.visibleNotifier, onFilter: _goToCollection, ); final basicAndLocationSliver = locationAtTop ? SliverToBoxAdapter( child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(child: BasicSection(entry: entry, collection: collection, onFilter: _goToCollection)), SizedBox(width: 8), Expanded(child: locationSection), ], ), ) : SliverList( delegate: SliverChildListDelegate.fixed( [ BasicSection(entry: entry, collection: collection, onFilter: _goToCollection), locationSection, ], ), ); final metadataSliver = MetadataSectionSliver( entry: entry, metadataNotifier: _metadataNotifier, visibleNotifier: widget.visibleNotifier, ); return CustomScrollView( controller: widget.scrollController, slivers: [ InfoAppBar( entry: entry, metadataNotifier: _metadataNotifier, onBackPressed: widget.goToViewer, ), SliverPadding( padding: horizontalPadding + EdgeInsets.only(top: 8), sliver: basicAndLocationSliver, ), SliverPadding( padding: horizontalPadding + EdgeInsets.only(bottom: 8), sliver: metadataSliver, ), BottomPaddingSliver(), ], ); } void _goToCollection(CollectionFilter filter) { if (collection == null) return; FilterNotification(filter).dispatch(context); } }