aves/lib/widgets/viewer/info/info_page.dart
2021-06-25 21:38:56 +09:00

221 lines
7 KiB
Dart

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<AvesEntry?> entryNotifier;
final ValueNotifier<bool> isScrollingNotifier;
const InfoPage({
Key? key,
required this.collection,
required this.entryNotifier,
required this.isScrollingNotifier,
}) : super(key: key);
@override
State<StatefulWidget> createState() => _InfoPageState();
}
class _InfoPageState extends State<InfoPage> {
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<ScrollNotification>(
onNotification: _handleTopScroll,
child: NotificationListener<OpenTempEntryNotification>(
onNotification: (notification) {
_openTempEntry(notification.entry);
return true;
},
child: Selector<MediaQueryData, double>(
selector: (c, mq) => mq.size.width,
builder: (c, mqWidth, child) {
return ValueListenableBuilder<AvesEntry?>(
valueListenable: widget.entryNotifier,
builder: (context, entry, child) {
return entry != null
? _InfoPageContent(
collection: collection,
entry: entry,
isScrollingNotifier: widget.isScrollingNotifier,
scrollController: _scrollController,
split: mqWidth > 600,
goToViewer: _goToViewer,
)
: const SizedBox.shrink();
},
);
},
),
),
),
),
),
resizeToAvoidBottomInset: false,
),
);
}
bool _handleTopScroll(ScrollNotification notification) {
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.viewerVerticalPageScrollAnimation,
curve: Curves.easeInOut,
);
}
void _openTempEntry(AvesEntry tempEntry) {
Navigator.push(
context,
TransparentMaterialPageRoute(
settings: const RouteSettings(name: EntryViewerPage.routeName),
pageBuilder: (c, a, sa) => EntryViewerPage(
initialEntry: tempEntry,
),
),
);
}
}
class _InfoPageContent extends StatefulWidget {
final CollectionLens? collection;
final AvesEntry entry;
final ValueNotifier<bool> isScrollingNotifier;
final ScrollController scrollController;
final bool split;
final VoidCallback goToViewer;
const _InfoPageContent({
Key? key,
required this.collection,
required this.entry,
required this.isScrollingNotifier,
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<Map<String, MetadataDirectory>> _metadataNotifier = ValueNotifier({});
CollectionLens? get collection => widget.collection;
AvesEntry get entry => widget.entry;
@override
Widget build(BuildContext context) {
final basicSection = BasicSection(
entry: entry,
collection: collection,
onFilter: _goToCollection,
);
final locationAtTop = widget.split && entry.hasGps;
final locationSection = LocationSection(
collection: collection,
entry: entry,
showTitle: !locationAtTop,
isScrollingNotifier: widget.isScrollingNotifier,
onFilter: _goToCollection,
);
final basicAndLocationSliver = locationAtTop
? SliverToBoxAdapter(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(child: basicSection),
const SizedBox(width: 8),
Expanded(child: locationSection),
],
),
)
: SliverList(
delegate: SliverChildListDelegate.fixed(
[
basicSection,
locationSection,
],
),
);
final metadataSliver = MetadataSectionSliver(
entry: entry,
metadataNotifier: _metadataNotifier,
);
return CustomScrollView(
controller: widget.scrollController,
slivers: [
InfoAppBar(
entry: entry,
metadataNotifier: _metadataNotifier,
onBackPressed: widget.goToViewer,
),
SliverPadding(
padding: horizontalPadding + const EdgeInsets.only(top: 8),
sliver: basicAndLocationSliver,
),
SliverPadding(
padding: horizontalPadding + const EdgeInsets.only(bottom: 8),
sliver: metadataSliver,
),
const BottomPaddingSliver(),
],
);
}
void _goToCollection(CollectionFilter filter) {
if (collection == null) return;
FilterSelectedNotification(filter).dispatch(context);
}
}