#503 viewer: overlay details expand/collapse on tap

This commit is contained in:
Thibault Deckers 2023-01-26 23:04:56 +01:00
parent b379cfaa21
commit e2229e8967
7 changed files with 128 additions and 52 deletions

View file

@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
## <a id="unreleased"></a>[Unreleased]
### Added
- Viewer: overlay details expand/collapse on tap
### Fixed
- SD card access grant on Android Lollipop

View file

@ -64,6 +64,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
final AChangeNotifier _verticalScrollNotifier = AChangeNotifier();
bool _overlayInitialized = false;
final ValueNotifier<bool> _overlayVisible = ValueNotifier(true);
final ValueNotifier<bool> _overlayExpandedNotifier = ValueNotifier(false);
late AnimationController _overlayAnimationController;
late Animation<double> _overlayButtonScale, _overlayVideoControlScale, _overlayOpacity;
late Animation<Offset> _overlayTopOffset;
@ -161,8 +162,10 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
cleanEntryControllers(entryNotifier.value);
_videoActionDelegate.dispose();
_overlayAnimationController.dispose();
_overlayVisible.removeListener(_onOverlayVisibleChanged);
_verticalPager.removeListener(_onVerticalPageControllerChanged);
_overlayVisible.dispose();
_overlayExpandedNotifier.dispose();
_verticalPager.dispose();
_heroInfoNotifier.dispose();
WidgetsBinding.instance.removeObserver(this);
_unregisterWidget(widget);
super.dispose();
@ -295,9 +298,10 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
child: ViewerTopOverlay(
entries: entries,
index: _currentEntryIndex,
hasCollection: hasCollection,
mainEntry: mainEntry,
scale: _overlayButtonScale,
hasCollection: hasCollection,
expandedNotifier: _overlayExpandedNotifier,
availableSize: availableSize,
viewInsets: _frozenViewInsets,
viewPadding: _frozenViewPadding,

View file

@ -14,12 +14,24 @@ class OverlayDescriptionRow extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
children: [
DecoratedIcon(AIcons.description, size: ViewerDetailOverlayContent.iconSize, shadows: ViewerDetailOverlayContent.shadows(context)),
const SizedBox(width: ViewerDetailOverlayContent.iconPadding),
Expanded(child: Text(description, strutStyle: Constants.overflowStrutStyle)),
],
return Text.rich(
TextSpan(
children: [
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Padding(
padding: const EdgeInsetsDirectional.only(end: ViewerDetailOverlayContent.iconPadding),
child: DecoratedIcon(
AIcons.description,
size: ViewerDetailOverlayContent.iconSize,
shadows: ViewerDetailOverlayContent.shadows(context),
),
),
),
TextSpan(text: description),
],
),
strutStyle: Constants.overflowStrutStyle,
);
}
}

View file

@ -9,6 +9,7 @@ import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/viewer/multipage/controller.dart';
import 'package:aves/widgets/viewer/overlay/details/date.dart';
import 'package:aves/widgets/viewer/overlay/details/description.dart';
import 'package:aves/widgets/viewer/overlay/details/expander.dart';
import 'package:aves/widgets/viewer/overlay/details/location.dart';
import 'package:aves/widgets/viewer/overlay/details/position_title.dart';
import 'package:aves/widgets/viewer/overlay/details/rating_tags.dart';
@ -23,6 +24,7 @@ class ViewerDetailOverlay extends StatefulWidget {
final int index;
final bool hasCollection;
final MultiPageController? multiPageController;
final ValueNotifier<bool> expandedNotifier;
final Size availableSize;
const ViewerDetailOverlay({
@ -31,6 +33,7 @@ class ViewerDetailOverlay extends StatefulWidget {
required this.index,
required this.hasCollection,
required this.multiPageController,
required this.expandedNotifier,
required this.availableSize,
});
@ -102,6 +105,7 @@ class _ViewerDetailOverlayState extends State<ViewerDetailOverlay> {
position: widget.hasCollection ? '${widget.index + 1}/${entries.length}' : null,
availableWidth: widget.availableSize.width,
multiPageController: multiPageController,
expandedNotifier: widget.expandedNotifier,
);
return multiPageController != null
@ -123,6 +127,7 @@ class ViewerDetailOverlayContent extends StatelessWidget {
final String? position;
final double availableWidth;
final MultiPageController? multiPageController;
final ValueNotifier<bool> expandedNotifier;
static const double _interRowPadding = 2.0;
static const double _subRowMinWidth = 300.0;
@ -140,6 +145,7 @@ class ViewerDetailOverlayContent extends StatelessWidget {
required this.position,
required this.availableWidth,
required this.multiPageController,
required this.expandedNotifier,
});
@override
@ -152,7 +158,11 @@ class ViewerDetailOverlayContent extends StatelessWidget {
return AnimatedBuilder(
animation: pageEntry.metadataChangeNotifier,
builder: (context, child) {
final positionTitle = OverlayPositionTitleRow(entry: pageEntry, collectionPosition: position, multiPageController: multiPageController);
final positionTitle = OverlayPositionTitleRow(
entry: pageEntry,
collectionPosition: position,
multiPageController: multiPageController,
);
return DefaultTextStyle(
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
shadows: shadows(context),
@ -172,7 +182,10 @@ class ViewerDetailOverlayContent extends StatelessWidget {
final rows = <Widget>[];
if (positionTitle.isNotEmpty) {
rows.add(positionTitle);
rows.add(OverlayRowExpander(
expandedNotifier: expandedNotifier,
child: positionTitle,
));
rows.add(const SizedBox(height: _interRowPadding));
}
if (twoColumns) {
@ -225,13 +238,19 @@ class ViewerDetailOverlayContent extends StatelessWidget {
Widget _buildRatingTagsFullRow(BuildContext context) => _buildFullRowSwitcher(
context: context,
visible: pageEntry.rating != 0 || pageEntry.tags.isNotEmpty,
builder: (context) => OverlayRatingTagsRow(entry: pageEntry),
builder: (context) => OverlayRowExpander(
expandedNotifier: expandedNotifier,
child: OverlayRatingTagsRow(entry: pageEntry),
),
);
Widget _buildDescriptionFullRow(BuildContext context) => _buildFullRowSwitcher(
context: context,
visible: description != null,
builder: (context) => OverlayDescriptionRow(description: description!),
builder: (context) => OverlayRowExpander(
expandedNotifier: expandedNotifier,
child: OverlayDescriptionRow(description: description!),
),
);
Widget _buildShootingFullRow(BuildContext context, double subRowWidth) => _buildFullRowSwitcher(

View file

@ -0,0 +1,33 @@
import 'package:flutter/material.dart';
class OverlayRowExpander extends StatelessWidget {
final ValueNotifier<bool> expandedNotifier;
final Widget child;
const OverlayRowExpander({
super.key,
required this.expandedNotifier,
required this.child,
});
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<bool>(
valueListenable: expandedNotifier,
builder: (context, expanded, child) {
final parent = DefaultTextStyle.of(context);
return DefaultTextStyle(
key: key,
style: parent.style,
textAlign: parent.textAlign,
softWrap: expanded,
overflow: parent.overflow,
maxLines: expanded ? null : 42,
textWidthBasis: parent.textWidthBasis,
child: child!,
);
},
child: child,
);
}
}

View file

@ -1,14 +1,11 @@
import 'package:aves/model/entry.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/common/basic/text/animated_diff.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/viewer/overlay/details/details.dart';
import 'package:collection/collection.dart';
import 'package:decorated_icon/decorated_icon.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class OverlayRatingTagsRow extends AnimatedWidget {
final AvesEntry entry;
@ -37,27 +34,28 @@ class OverlayRatingTagsRow extends AnimatedWidget {
final tags = entry.tags.toList()..sort(compareAsciiUpperCaseNatural);
final hasTags = tags.isNotEmpty;
final animationDuration = context.select<DurationsData, Duration>((v) => v.textDiffAnimation);
return Row(
children: [
AnimatedDiffText(
ratingString,
strutStyle: Constants.overflowStrutStyle,
duration: animationDuration,
),
if (hasTags) ...[
if (ratingString.isNotEmpty) const Text(Constants.separator),
DecoratedIcon(AIcons.tag, size: ViewerDetailOverlayContent.iconSize, shadows: ViewerDetailOverlayContent.shadows(context)),
const SizedBox(width: ViewerDetailOverlayContent.iconPadding),
Expanded(
child: AnimatedDiffText(
tags.join(Constants.separator),
strutStyle: Constants.overflowStrutStyle,
duration: animationDuration,
return Text.rich(
TextSpan(
children: [
TextSpan(text: ratingString),
if (hasTags) ...[
if (ratingString.isNotEmpty) const TextSpan(text: Constants.separator),
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Padding(
padding: const EdgeInsetsDirectional.only(end: ViewerDetailOverlayContent.iconPadding),
child: DecoratedIcon(
AIcons.tag,
size: ViewerDetailOverlayContent.iconSize,
shadows: ViewerDetailOverlayContent.shadows(context),
),
),
),
),
TextSpan(text: tags.join(Constants.separator)),
]
],
],
),
strutStyle: Constants.overflowStrutStyle,
);
}
}

View file

@ -16,6 +16,7 @@ class ViewerTopOverlay extends StatelessWidget {
final AvesEntry mainEntry;
final Animation<double> scale;
final bool hasCollection;
final ValueNotifier<bool> expandedNotifier;
final Size availableSize;
final EdgeInsets? viewInsets, viewPadding;
@ -26,6 +27,7 @@ class ViewerTopOverlay extends StatelessWidget {
required this.mainEntry,
required this.scale,
required this.hasCollection,
required this.expandedNotifier,
required this.availableSize,
required this.viewInsets,
required this.viewPadding,
@ -51,23 +53,27 @@ class ViewerTopOverlay extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (showInfo)
BlurredRect(
enabled: blurred,
child: Container(
color: Themes.overlayBackgroundColor(brightness: Theme.of(context).brightness, blurred: blurred),
child: SafeArea(
bottom: false,
minimum: EdgeInsets.only(
left: viewInsetsPadding.left,
top: viewInsetsPadding.top,
right: viewInsetsPadding.right,
),
child: ViewerDetailOverlay(
index: index,
entries: entries,
hasCollection: hasCollection,
multiPageController: multiPageController,
availableSize: availableSize,
GestureDetector(
onTap: () => expandedNotifier.value = !expandedNotifier.value,
child: BlurredRect(
enabled: blurred,
child: Container(
color: Themes.overlayBackgroundColor(brightness: Theme.of(context).brightness, blurred: blurred),
child: SafeArea(
bottom: false,
minimum: EdgeInsets.only(
left: viewInsetsPadding.left,
top: viewInsetsPadding.top,
right: viewInsetsPadding.right,
),
child: ViewerDetailOverlay(
index: index,
entries: entries,
hasCollection: hasCollection,
multiPageController: multiPageController,
expandedNotifier: expandedNotifier,
availableSize: availableSize,
),
),
),
),