fullscreen: refresh overlay & reset info metadata on metadata change

This commit is contained in:
Thibault Deckers 2020-10-29 13:57:28 +09:00
parent 7aa50e7880
commit bb23e7a939
6 changed files with 158 additions and 100 deletions

View file

@ -196,14 +196,20 @@ class ImageEntry {
} }
} }
bool get portrait => rotationDegrees % 180 == 90; bool get isPortrait => rotationDegrees % 180 == 90;
String get resolutionText {
final w = width ?? '?';
final h = height ?? '?';
return isPortrait ? '$h × $w' : '$w × $h';
}
double get displayAspectRatio { double get displayAspectRatio {
if (width == 0 || height == 0) return 1; if (width == 0 || height == 0) return 1;
return portrait ? height / width : width / height; return isPortrait ? height / width : width / height;
} }
Size get displaySize => portrait ? Size(height.toDouble(), width.toDouble()) : Size(width.toDouble(), height.toDouble()); Size get displaySize => isPortrait ? Size(height.toDouble(), width.toDouble()) : Size(width.toDouble(), height.toDouble());
int get megaPixels => width != null && height != null ? (width * height / 1000000).round() : null; int get megaPixels => width != null && height != null ? (width * height / 1000000).round() : null;
@ -371,7 +377,10 @@ class ImageEntry {
final contentId = newFields['contentId']; final contentId = newFields['contentId'];
if (contentId is int) this.contentId = contentId; if (contentId is int) this.contentId = contentId;
final sourceTitle = newFields['title']; final sourceTitle = newFields['title'];
if (sourceTitle is String) this.sourceTitle = sourceTitle; if (sourceTitle is String) {
this.sourceTitle = sourceTitle;
_bestTitle = null;
}
final dateModifiedSecs = newFields['dateModifiedSecs']; final dateModifiedSecs = newFields['dateModifiedSecs'];
if (dateModifiedSecs is int) this.dateModifiedSecs = dateModifiedSecs; if (dateModifiedSecs is int) this.dateModifiedSecs = dateModifiedSecs;
final rotationDegrees = newFields['rotationDegrees']; final rotationDegrees = newFields['rotationDegrees'];
@ -381,6 +390,8 @@ class ImageEntry {
await metadataDb.saveEntries({this}); await metadataDb.saveEntries({this});
await metadataDb.saveMetadata({catalogMetadata}); await metadataDb.saveMetadata({catalogMetadata});
metadataChangeNotifier.notifyListeners();
} }
Future<bool> rename(String newName) async { Future<bool> rename(String newName) async {
@ -390,8 +401,6 @@ class ImageEntry {
if (newFields.isEmpty) return false; if (newFields.isEmpty) return false;
await _applyNewFields(newFields); await _applyNewFields(newFields);
_bestTitle = null;
metadataChangeNotifier.notifyListeners();
return true; return true;
} }

View file

@ -67,7 +67,7 @@ class _ThumbnailRasterImageState extends State<ThumbnailRasterImage> {
} }
void _unregisterWidget(ThumbnailRasterImage widget) { void _unregisterWidget(ThumbnailRasterImage widget) {
widget.entry.imageChangeNotifier?.removeListener(_onImageChanged); widget.entry.imageChangeNotifier.removeListener(_onImageChanged);
_pauseProvider(); _pauseProvider();
} }

View file

@ -78,7 +78,7 @@ class FullscreenDebugPage extends StatelessWidget {
'sourceRotationDegrees': '${entry.sourceRotationDegrees}', 'sourceRotationDegrees': '${entry.sourceRotationDegrees}',
'rotationDegrees': '${entry.rotationDegrees}', 'rotationDegrees': '${entry.rotationDegrees}',
'isFlipped': '${entry.isFlipped}', 'isFlipped': '${entry.isFlipped}',
'portrait': '${entry.portrait}', 'portrait': '${entry.isPortrait}',
'displayAspectRatio': '${entry.displayAspectRatio}', 'displayAspectRatio': '${entry.displayAspectRatio}',
'displaySize': '${entry.displaySize}', 'displaySize': '${entry.displaySize}',
}), }),

View file

@ -31,7 +31,7 @@ class BasicSection extends StatelessWidget {
final date = entry.bestDate; final date = entry.bestDate;
final dateText = date != null ? '${DateFormat.yMMMd().format(date)}${DateFormat.Hm().format(date)}' : Constants.unknown; final dateText = date != null ? '${DateFormat.yMMMd().format(date)}${DateFormat.Hm().format(date)}' : Constants.unknown;
final showMegaPixels = entry.isPhoto && entry.megaPixels != null && entry.megaPixels > 0; final showMegaPixels = entry.isPhoto && entry.megaPixels != null && entry.megaPixels > 0;
final resolutionText = '${entry.width ?? '?'} × ${entry.height ?? '?'}${showMegaPixels ? ' (${entry.megaPixels} MP)' : ''}'; final resolutionText = '${entry.resolutionText}${showMegaPixels ? ' (${entry.megaPixels} MP)' : ''}';
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,

View file

@ -36,48 +36,13 @@ class InfoPage extends StatefulWidget {
class InfoPageState extends State<InfoPage> { class InfoPageState extends State<InfoPage> {
final ScrollController _scrollController = ScrollController(); final ScrollController _scrollController = ScrollController();
bool _scrollStartFromTop = false; bool _scrollStartFromTop = false;
List<MetadataDirectory> _metadata = [];
String _loadedMetadataUri;
CollectionLens get collection => widget.collection; CollectionLens get collection => widget.collection;
ImageEntry get entry => widget.entryNotifier.value; ImageEntry get entry => widget.entryNotifier.value;
bool get isVisible => widget.visibleNotifier.value;
@override
void initState() {
super.initState();
_registerWidget(widget);
_getMetadata();
}
@override
void didUpdateWidget(InfoPage oldWidget) {
super.didUpdateWidget(oldWidget);
_unregisterWidget(oldWidget);
_registerWidget(widget);
_getMetadata();
}
@override
void dispose() {
_unregisterWidget(widget);
super.dispose();
}
void _registerWidget(InfoPage widget) {
widget.visibleNotifier.addListener(_getMetadata);
}
void _unregisterWidget(InfoPage widget) {
widget.visibleNotifier.removeListener(_getMetadata);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
const horizontalPadding = EdgeInsets.symmetric(horizontal: 8);
final appBar = SliverAppBar( final appBar = SliverAppBar(
leading: IconButton( leading: IconButton(
key: Key('back-button'), key: Key('back-button'),
@ -99,64 +64,20 @@ class InfoPageState extends State<InfoPage> {
builder: (c, mq, child) { builder: (c, mq, child) {
final mqWidth = mq.item1; final mqWidth = mq.item1;
final mqViewInsetsBottom = mq.item2; final mqViewInsetsBottom = mq.item2;
final split = mqWidth > 400;
return ValueListenableBuilder<ImageEntry>( return ValueListenableBuilder<ImageEntry>(
valueListenable: widget.entryNotifier, valueListenable: widget.entryNotifier,
builder: (context, entry, child) { builder: (context, entry, child) {
if (entry == null) return SizedBox.shrink(); return entry != null
? _InfoPageContent(
final locationAtTop = split && entry.hasGps; collection: collection,
final locationSection = LocationSection( entry: entry,
collection: collection, visibleNotifier: widget.visibleNotifier,
entry: entry, scrollController: _scrollController,
showTitle: !locationAtTop, appBar: appBar,
visibleNotifier: widget.visibleNotifier, split: mqWidth > 400,
onFilter: _goToCollection, mqViewInsetsBottom: mqViewInsetsBottom,
);
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( : SizedBox.shrink();
delegate: SliverChildListDelegate.fixed(
[
BasicSection(entry: entry, collection: collection, onFilter: _goToCollection),
locationSection,
],
),
);
final metadataSliver = MetadataSectionSliver(
entry: entry,
metadata: _metadata,
);
return AnimationLimiter(
// we update the limiter key after fetching the metadata of a new entry,
// in order to restart the staggered animation of the metadata section
key: Key(_loadedMetadataUri),
child: CustomScrollView(
controller: _scrollController,
slivers: [
appBar,
SliverPadding(
padding: horizontalPadding + EdgeInsets.only(top: 8),
sliver: basicAndLocationSliver,
),
SliverPadding(
padding: horizontalPadding + EdgeInsets.only(bottom: 8 + mqViewInsetsBottom),
sliver: metadataSliver,
),
],
),
);
}, },
); );
}, },
@ -198,12 +119,141 @@ class InfoPageState extends State<InfoPage> {
curve: Curves.easeInOut, curve: Curves.easeInOut,
); );
} }
}
class _InfoPageContent extends StatefulWidget {
final CollectionLens collection;
final ImageEntry entry;
final ValueNotifier<bool> visibleNotifier;
final ScrollController scrollController;
final SliverAppBar appBar;
final bool split;
final double mqViewInsetsBottom;
const _InfoPageContent({
Key key,
@required this.collection,
@required this.entry,
@required this.visibleNotifier,
@required this.scrollController,
@required this.appBar,
@required this.split,
@required this.mqViewInsetsBottom,
}) : super(key: key);
@override
_InfoPageContentState createState() => _InfoPageContentState();
}
class _InfoPageContentState extends State<_InfoPageContent> {
List<MetadataDirectory> _metadata = [];
String _loadedMetadataUri;
static const horizontalPadding = EdgeInsets.symmetric(horizontal: 8);
CollectionLens get collection => widget.collection;
ImageEntry get entry => widget.entry;
bool get isVisible => widget.visibleNotifier.value;
@override
void initState() {
super.initState();
_registerWidget(widget);
_getMetadata();
}
@override
void didUpdateWidget(_InfoPageContent oldWidget) {
super.didUpdateWidget(oldWidget);
_unregisterWidget(oldWidget);
_registerWidget(widget);
_getMetadata();
}
@override
void dispose() {
_unregisterWidget(widget);
super.dispose();
}
void _registerWidget(_InfoPageContent widget) {
widget.visibleNotifier.addListener(_getMetadata);
widget.entry.metadataChangeNotifier.addListener(_onMetadataChanged);
}
void _unregisterWidget(_InfoPageContent widget) {
widget.visibleNotifier.removeListener(_getMetadata);
widget.entry.metadataChangeNotifier.removeListener(_onMetadataChanged);
}
@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,
metadata: _metadata,
);
return AnimationLimiter(
// we update the limiter key after fetching the metadata of a new entry,
// in order to restart the staggered animation of the metadata section
key: Key(_loadedMetadataUri),
child: CustomScrollView(
controller: widget.scrollController,
slivers: [
widget.appBar,
SliverPadding(
padding: horizontalPadding + EdgeInsets.only(top: 8),
sliver: basicAndLocationSliver,
),
SliverPadding(
padding: horizontalPadding + EdgeInsets.only(bottom: 8 + widget.mqViewInsetsBottom),
sliver: metadataSliver,
),
],
),
);
}
void _goToCollection(CollectionFilter filter) { void _goToCollection(CollectionFilter filter) {
if (collection == null) return; if (collection == null) return;
FilterNotification(filter).dispatch(context); FilterNotification(filter).dispatch(context);
} }
void _onMetadataChanged() {
_metadata = [];
_loadedMetadataUri = null;
_getMetadata();
}
// fetch and hold metadata in the page widget and not in the section sliver, // fetch and hold metadata in the page widget and not in the section sliver,
// so that we can refresh and limit the staggered animation of the metadata section // so that we can refresh and limit the staggered animation of the metadata section
Future<void> _getMetadata() async { Future<void> _getMetadata() async {

View file

@ -229,13 +229,12 @@ class _DateRow extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final date = entry.bestDate; final date = entry.bestDate;
final dateText = date != null ? '${DateFormat.yMMMd().format(date)}${DateFormat.Hm().format(date)}' : Constants.unknown; final dateText = date != null ? '${DateFormat.yMMMd().format(date)}${DateFormat.Hm().format(date)}' : Constants.unknown;
final resolution = '${entry.width ?? '?'} × ${entry.height ?? '?'}';
return Row( return Row(
children: [ children: [
DecoratedIcon(AIcons.date, shadows: [Constants.embossShadow], size: _iconSize), DecoratedIcon(AIcons.date, shadows: [Constants.embossShadow], size: _iconSize),
SizedBox(width: _iconPadding), SizedBox(width: _iconPadding),
Expanded(flex: 3, child: Text(dateText, strutStyle: Constants.overflowStrutStyle)), Expanded(flex: 3, child: Text(dateText, strutStyle: Constants.overflowStrutStyle)),
if (!entry.isSvg) Expanded(flex: 2, child: Text(resolution, strutStyle: Constants.overflowStrutStyle)), if (!entry.isSvg) Expanded(flex: 2, child: Text(entry.resolutionText, strutStyle: Constants.overflowStrutStyle)),
], ],
); );
} }