diff --git a/CHANGELOG.md b/CHANGELOG.md
index b3fce214a..ab919d4eb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
+### Added
+
+- Viewer: overlay details expand/collapse on tap
+
### Fixed
- SD card access grant on Android Lollipop
diff --git a/lib/widgets/viewer/entry_viewer_stack.dart b/lib/widgets/viewer/entry_viewer_stack.dart
index 2012a1778..3dfb0f3dc 100644
--- a/lib/widgets/viewer/entry_viewer_stack.dart
+++ b/lib/widgets/viewer/entry_viewer_stack.dart
@@ -64,6 +64,7 @@ class _EntryViewerStackState extends State with EntryViewContr
final AChangeNotifier _verticalScrollNotifier = AChangeNotifier();
bool _overlayInitialized = false;
final ValueNotifier _overlayVisible = ValueNotifier(true);
+ final ValueNotifier _overlayExpandedNotifier = ValueNotifier(false);
late AnimationController _overlayAnimationController;
late Animation _overlayButtonScale, _overlayVideoControlScale, _overlayOpacity;
late Animation _overlayTopOffset;
@@ -161,8 +162,10 @@ class _EntryViewerStackState extends State 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 with EntryViewContr
child: ViewerTopOverlay(
entries: entries,
index: _currentEntryIndex,
- hasCollection: hasCollection,
mainEntry: mainEntry,
scale: _overlayButtonScale,
+ hasCollection: hasCollection,
+ expandedNotifier: _overlayExpandedNotifier,
availableSize: availableSize,
viewInsets: _frozenViewInsets,
viewPadding: _frozenViewPadding,
diff --git a/lib/widgets/viewer/overlay/details/description.dart b/lib/widgets/viewer/overlay/details/description.dart
index 02a476fdf..fec7ab219 100644
--- a/lib/widgets/viewer/overlay/details/description.dart
+++ b/lib/widgets/viewer/overlay/details/description.dart
@@ -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,
);
}
}
diff --git a/lib/widgets/viewer/overlay/details/details.dart b/lib/widgets/viewer/overlay/details/details.dart
index 0e7be08e7..d24265ee7 100644
--- a/lib/widgets/viewer/overlay/details/details.dart
+++ b/lib/widgets/viewer/overlay/details/details.dart
@@ -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 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 {
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 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 = [];
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(
diff --git a/lib/widgets/viewer/overlay/details/expander.dart b/lib/widgets/viewer/overlay/details/expander.dart
new file mode 100644
index 000000000..592546145
--- /dev/null
+++ b/lib/widgets/viewer/overlay/details/expander.dart
@@ -0,0 +1,33 @@
+import 'package:flutter/material.dart';
+
+class OverlayRowExpander extends StatelessWidget {
+ final ValueNotifier expandedNotifier;
+ final Widget child;
+
+ const OverlayRowExpander({
+ super.key,
+ required this.expandedNotifier,
+ required this.child,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return ValueListenableBuilder(
+ 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,
+ );
+ }
+}
diff --git a/lib/widgets/viewer/overlay/details/rating_tags.dart b/lib/widgets/viewer/overlay/details/rating_tags.dart
index f665664fd..f99c222a1 100644
--- a/lib/widgets/viewer/overlay/details/rating_tags.dart
+++ b/lib/widgets/viewer/overlay/details/rating_tags.dart
@@ -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((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,
);
}
}
diff --git a/lib/widgets/viewer/overlay/top.dart b/lib/widgets/viewer/overlay/top.dart
index 3d3e8ddaa..7021e0149 100644
--- a/lib/widgets/viewer/overlay/top.dart
+++ b/lib/widgets/viewer/overlay/top.dart
@@ -16,6 +16,7 @@ class ViewerTopOverlay extends StatelessWidget {
final AvesEntry mainEntry;
final Animation scale;
final bool hasCollection;
+ final ValueNotifier 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,
+ ),
),
),
),