fullscreen: fixed route transition

This commit is contained in:
Thibault Deckers 2019-12-26 22:06:31 +09:00
parent 68766d0e17
commit aafcc1da63
24 changed files with 186 additions and 175 deletions

View file

@ -4,8 +4,8 @@ import 'package:aves/widgets/album/all_collection_drawer.dart';
import 'package:aves/widgets/album/all_collection_page.dart';
import 'package:aves/widgets/common/fake_app_bar.dart';
import 'package:aves/widgets/common/icons.dart';
import 'package:aves/widgets/common/media_query_data_provider.dart';
import 'package:aves/widgets/common/media_store_collection_provider.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:aves/widgets/common/providers/media_store_collection_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:pedantic/pedantic.dart';
@ -64,7 +64,7 @@ class _HomePageState extends State<HomePage> {
// TODO reduce permission check time
// TODO TLAD ask android.permission.ACCESS_MEDIA_LOCATION (unredacted EXIF with scoped storage)
final permissions = await PermissionHandler().requestPermissions([
PermissionGroup.storage
PermissionGroup.storage,
]); // 350ms
if (permissions[PermissionGroup.storage] != PermissionStatus.granted) {
unawaited(SystemNavigator.pop());

View file

@ -60,7 +60,7 @@ class ImageCollection with ChangeNotifier {
break;
case SortFactor.size:
sections = Map.fromEntries([
MapEntry('All', _rawEntries)
MapEntry('All', _rawEntries),
]);
break;
}

View file

@ -181,7 +181,7 @@ class ImageEntry {
return {
addressDetails.countryName,
addressDetails.adminArea,
addressDetails.locality
addressDetails.locality,
}.where((part) => part != null && part.isNotEmpty).join(', ');
}

View file

@ -13,7 +13,7 @@ String _decimal2sexagesimal(final double dec) {
final List<String> tmp = NumberFormat('0.0#####').format(_round(value, decimals: 10)).split('.');
return <int>[
int.parse(tmp[0]).abs(),
int.parse(tmp[1])
int.parse(tmp[1]),
];
}
@ -39,6 +39,6 @@ List<String> toDMS(Tuple2<double, double> latLng) {
final lng = latLng.item2;
return [
'${_decimal2sexagesimal(lat)} ${lat < 0 ? 'S' : 'N'}',
'${_decimal2sexagesimal(lng)} ${lng < 0 ? 'W' : 'E'}'
'${_decimal2sexagesimal(lng)} ${lng < 0 ? 'W' : 'E'}',
];
}

View file

@ -60,22 +60,22 @@ class AllCollectionDrawer extends StatelessWidget {
Row(children: [
Icon(Icons.photo_library),
const SizedBox(width: 4),
Text('${collection.imageCount}')
Text('${collection.imageCount}'),
]),
Row(children: [
Icon(Icons.video_library),
const SizedBox(width: 4),
Text('${collection.videoCount}')
Text('${collection.videoCount}'),
]),
Row(children: [
Icon(Icons.photo_album),
const SizedBox(width: 4),
Text('${collection.albumCount}')
Text('${collection.albumCount}'),
]),
Row(children: [
Icon(Icons.label),
const SizedBox(width: 4),
Text('${collection.tagCount}')
Text('${collection.tagCount}'),
]),
],
),

View file

@ -0,0 +1,121 @@
import 'package:aves/model/image_collection.dart';
import 'package:aves/model/image_entry.dart';
import 'package:aves/widgets/album/sections.dart';
import 'package:aves/widgets/album/thumbnail.dart';
import 'package:aves/widgets/album/transparent_material_page_route.dart';
import 'package:aves/widgets/common/icons.dart';
import 'package:aves/widgets/fullscreen/fullscreen_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_sticky_header/flutter_sticky_header.dart';
class SectionSliver extends StatelessWidget {
final ImageCollection collection;
final Map<dynamic, List<ImageEntry>> sections;
final dynamic sectionKey;
final double screenWidth;
const SectionSliver({
Key key,
@required this.collection,
@required this.sections,
@required this.sectionKey,
@required this.screenWidth,
}) : super(key: key);
@override
Widget build(BuildContext context) {
const columnCount = 4;
return SliverStickyHeader(
header: SectionHeader(
collection: collection,
sections: sections,
sectionKey: sectionKey,
),
sliver: SliverGrid(
delegate: SliverChildBuilderDelegate(
// TODO TLAD find out why thumbnails are rebuilt (with `initState`) when:
// - config change (show/hide status bar)
// - navigating away/back
(sliverContext, index) {
final sectionEntries = sections[sectionKey];
if (index >= sectionEntries.length) return null;
final entry = sectionEntries[index];
return GestureDetector(
onTap: () => _showFullscreen(sliverContext, entry),
child: Thumbnail(
entry: entry,
extent: screenWidth / columnCount,
),
);
},
childCount: sections[sectionKey].length,
addAutomaticKeepAlives: false,
addRepaintBoundaries: true,
),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: columnCount,
),
),
overlapsContent: false,
);
}
void _showFullscreen(BuildContext context, ImageEntry entry) {
Navigator.push(
context,
TransparentMaterialPageRoute(
pageBuilder: (context, _, __) => FullscreenPage(
collection: collection,
initialUri: entry.uri,
),
),
);
}
}
class SectionHeader extends StatelessWidget {
final ImageCollection collection;
final Map<dynamic, List<ImageEntry>> sections;
final dynamic sectionKey;
const SectionHeader({
Key key,
@required this.collection,
@required this.sections,
@required this.sectionKey,
}) : super(key: key);
@override
Widget build(BuildContext context) {
Widget header = const SizedBox.shrink();
if (collection.sortFactor == SortFactor.date) {
switch (collection.groupFactor) {
case GroupFactor.album:
Widget albumIcon = IconUtils.getAlbumIcon(context, sectionKey as String);
if (albumIcon != null) {
albumIcon = Material(
type: MaterialType.circle,
elevation: 3,
color: Colors.transparent,
shadowColor: Colors.black,
child: albumIcon,
);
}
header = TitleSectionHeader(
leading: albumIcon,
title: collection.getUniqueAlbumName(sectionKey as String, sections.keys.cast<String>()),
);
break;
case GroupFactor.month:
header = MonthSectionHeader(date: sectionKey as DateTime);
break;
case GroupFactor.day:
header = DaySectionHeader(date: sectionKey as DateTime);
break;
}
}
return IgnorePointer(
child: header,
);
}
}

View file

@ -1,7 +1,7 @@
import 'package:aves/model/image_collection.dart';
import 'package:aves/model/image_entry.dart';
import 'package:aves/widgets/album/thumbnail_collection.dart';
import 'package:aves/widgets/common/media_query_data_provider.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:flutter/material.dart';
class FilteredCollectionPage extends StatelessWidget {

View file

@ -1,7 +1,7 @@
import 'package:aves/model/image_collection.dart';
import 'package:aves/model/image_entry.dart';
import 'package:aves/widgets/album/thumbnail_collection.dart';
import 'package:aves/widgets/common/media_query_data_provider.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:flutter/material.dart';
class ImageSearchDelegate extends SearchDelegate<ImageEntry> {

View file

@ -1,5 +1,5 @@
import 'package:aves/utils/time_utils.dart';
import 'package:aves/widgets/common/outlined_text.dart';
import 'package:aves/widgets/common/fx/outlined_text.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';

View file

@ -1,12 +1,8 @@
import 'package:aves/model/image_collection.dart';
import 'package:aves/model/image_entry.dart';
import 'package:aves/widgets/album/sections.dart';
import 'package:aves/widgets/album/thumbnail.dart';
import 'package:aves/widgets/common/icons.dart';
import 'package:aves/widgets/fullscreen/fullscreen_page.dart';
import 'package:aves/widgets/album/collection_section.dart';
import 'package:draggable_scrollbar/draggable_scrollbar.dart';
import 'package:flutter/material.dart';
import 'package:flutter_sticky_header/flutter_sticky_header.dart';
import 'package:provider/provider.dart';
class ThumbnailCollection extends AnimatedWidget {
@ -103,126 +99,3 @@ class ThumbnailCollectionContent extends StatelessWidget {
);
}
}
class SectionSliver extends StatelessWidget {
final ImageCollection collection;
final Map<dynamic, List<ImageEntry>> sections;
final dynamic sectionKey;
final double screenWidth;
const SectionSliver({
Key key,
@required this.collection,
@required this.sections,
@required this.sectionKey,
@required this.screenWidth,
}) : super(key: key);
@override
Widget build(BuildContext context) {
const columnCount = 4;
return SliverStickyHeader(
header: SectionHeader(
collection: collection,
sections: sections,
sectionKey: sectionKey,
),
sliver: SliverGrid(
delegate: SliverChildBuilderDelegate(
// TODO TLAD find out why thumbnails are rebuilt (with `initState`) when:
// - config change (show/hide status bar)
// - navigating away/back
(sliverContext, index) {
final sectionEntries = sections[sectionKey];
if (index >= sectionEntries.length) return null;
final entry = sectionEntries[index];
return GestureDetector(
onTap: () => _showFullscreen(sliverContext, entry),
child: Thumbnail(
entry: entry,
extent: screenWidth / columnCount,
),
);
},
childCount: sections[sectionKey].length,
addAutomaticKeepAlives: false,
addRepaintBoundaries: true,
),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: columnCount,
),
),
overlapsContent: false,
);
}
void _showFullscreen(BuildContext context, ImageEntry entry) {
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (context) => FullscreenPage(
// collection: collection,
// initialUri: entry.uri,
// ),
// ),
// );
// TODO TLAD consider the following to have transparency while popping fullscreen by drag down
Navigator.push(
context,
PageRouteBuilder(
opaque: false,
pageBuilder: (BuildContext context, _, __) => FullscreenPage(
collection: collection,
initialUri: entry.uri,
),
),
);
}
}
class SectionHeader extends StatelessWidget {
final ImageCollection collection;
final Map<dynamic, List<ImageEntry>> sections;
final dynamic sectionKey;
const SectionHeader({
Key key,
@required this.collection,
@required this.sections,
@required this.sectionKey,
}) : super(key: key);
@override
Widget build(BuildContext context) {
Widget header = const SizedBox.shrink();
if (collection.sortFactor == SortFactor.date) {
switch (collection.groupFactor) {
case GroupFactor.album:
Widget albumIcon = IconUtils.getAlbumIcon(context, sectionKey as String);
if (albumIcon != null) {
albumIcon = Material(
type: MaterialType.circle,
elevation: 3,
color: Colors.transparent,
shadowColor: Colors.black,
child: albumIcon,
);
}
header = TitleSectionHeader(
leading: albumIcon,
title: collection.getUniqueAlbumName(sectionKey as String, sections.keys.cast<String>()),
);
break;
case GroupFactor.month:
header = MonthSectionHeader(date: sectionKey as DateTime);
break;
case GroupFactor.day:
header = DaySectionHeader(date: sectionKey as DateTime);
break;
}
}
return IgnorePointer(
child: header,
);
}
}

View file

@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
class TransparentMaterialPageRoute<T> extends PageRouteBuilder<T> {
TransparentMaterialPageRoute({
@required RoutePageBuilder pageBuilder,
}) : super(pageBuilder: pageBuilder);
@override
bool get opaque => false;
@override
Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
final PageTransitionsTheme theme = Theme.of(context).pageTransitionsTheme;
return theme.buildTransitions<T>(this, context, animation, secondaryAnimation, child);
}
}

View file

@ -39,7 +39,7 @@ class ImagePreviewState extends State<ImagePreview> with AfterInitMixin {
super.initState();
_entryChangeNotifier = Listenable.merge([
entry.imageChangeNotifier,
entry.metadataChangeNotifier
entry.metadataChangeNotifier,
]);
_entryChangeNotifier.addListener(_onEntryChange);
}

View file

@ -3,7 +3,7 @@ import 'package:aves/model/image_metadata.dart';
import 'package:aves/model/metadata_db.dart';
import 'package:aves/model/settings.dart';
import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/widgets/common/media_query_data_provider.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';

View file

@ -3,7 +3,7 @@ import 'dart:math';
import 'package:aves/model/image_collection.dart';
import 'package:aves/model/image_entry.dart';
import 'package:aves/widgets/common/media_query_data_provider.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:aves/widgets/fullscreen/fullscreen_action_delegate.dart';
import 'package:aves/widgets/fullscreen/image_page.dart';
import 'package:aves/widgets/fullscreen/info/info_page.dart';
@ -298,7 +298,7 @@ class FullscreenVerticalPageView extends StatefulWidget {
class _FullscreenVerticalPageViewState extends State<FullscreenVerticalPageView> {
bool _isInitialScale = true;
Color _background = Colors.black;
ValueNotifier<Color> _backgroundColorNotifier = ValueNotifier(Colors.black);
@override
void initState() {
@ -328,24 +328,25 @@ class _FullscreenVerticalPageViewState extends State<FullscreenVerticalPageView>
}
void _onVerticalPageControllerChange() {
setState(() => _background = _background.withOpacity(min(1.0, widget.verticalPager.page)));
_backgroundColorNotifier.value = _backgroundColorNotifier.value.withOpacity(min(1.0, widget.verticalPager.page));
}
@override
Widget build(BuildContext context) {
return PageView(
scrollDirection: Axis.vertical,
controller: widget.verticalPager,
physics: _isInitialScale ? const PageScrollPhysics() : const NeverScrollableScrollPhysics(),
onPageChanged: widget.onVerticalPageChanged,
children: [
Container(
color: _background,
child: const SizedBox(),
),
Container(
color: _background,
child: ImagePage(
return ValueListenableBuilder(
valueListenable: _backgroundColorNotifier,
builder: (context, backgroundColor, child) => Container(
color: backgroundColor,
child: child,
),
child: PageView(
scrollDirection: Axis.vertical,
controller: widget.verticalPager,
physics: _isInitialScale ? const PageScrollPhysics() : const NeverScrollableScrollPhysics(),
onPageChanged: widget.onVerticalPageChanged,
children: [
const SizedBox(),
ImagePage(
collection: widget.collection,
pageController: widget.horizontalPager,
onTap: widget.onImageTap,
@ -353,15 +354,15 @@ class _FullscreenVerticalPageViewState extends State<FullscreenVerticalPageView>
onScaleChanged: (state) => setState(() => _isInitialScale = state == PhotoViewScaleState.initial),
videoControllers: widget.videoControllers,
),
),
NotificationListener(
onNotification: (notification) {
if (notification is BackUpNotification) widget.onImagePageRequested();
return false;
},
child: InfoPage(collection: widget.collection, entry: widget.entry),
),
],
NotificationListener(
onNotification: (notification) {
if (notification is BackUpNotification) widget.onImagePageRequested();
return false;
},
child: InfoPage(collection: widget.collection, entry: widget.entry),
),
],
),
);
}
}

View file

@ -37,7 +37,7 @@ class BasicSection extends StatelessWidget {
if (rotation != null) InfoRow('Rotation', '$rotation°');
return [
InfoRow('Duration', entry.durationText),
if (rotation != null) InfoRow('Rotation', '$rotation°')
if (rotation != null) InfoRow('Rotation', '$rotation°'),
];
}
}

View file

@ -1,6 +1,6 @@
import 'package:aves/model/image_collection.dart';
import 'package:aves/model/image_entry.dart';
import 'package:aves/widgets/common/media_query_data_provider.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:aves/widgets/fullscreen/info/basic_section.dart';
import 'package:aves/widgets/fullscreen/info/location_section.dart';
import 'package:aves/widgets/fullscreen/info/metadata_section.dart';

View file

@ -17,7 +17,7 @@ class LocationSection extends AnimatedWidget {
key: key,
listenable: Listenable.merge([
entry.metadataChangeNotifier,
entry.addressChangeNotifier
entry.addressChangeNotifier,
]));
@override

View file

@ -6,7 +6,7 @@ import 'package:aves/model/image_metadata.dart';
import 'package:aves/model/metadata_service.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/utils/geo_utils.dart';
import 'package:aves/widgets/common/blurred.dart';
import 'package:aves/widgets/common/fx/blurred.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';

View file

@ -1,4 +1,4 @@
import 'package:aves/widgets/common/blurred.dart';
import 'package:aves/widgets/common/fx/blurred.dart';
import 'package:flutter/material.dart';
class OverlayButton extends StatelessWidget {

View file

@ -1,7 +1,7 @@
import 'package:aves/model/image_entry.dart';
import 'package:aves/utils/android_app_service.dart';
import 'package:aves/utils/time_utils.dart';
import 'package:aves/widgets/common/blurred.dart';
import 'package:aves/widgets/common/fx/blurred.dart';
import 'package:aves/widgets/fullscreen/overlay/common.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';