diff --git a/CHANGELOG.md b/CHANGELOG.md index 137ccfa41..bbdec1925 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file. ### Added - Viewer: overlay details expand/collapse on tap +- TV: improved support for Info ### Changed diff --git a/lib/widgets/about/policy_page.dart b/lib/widgets/about/policy_page.dart index 26eb8d7b7..e5c953087 100644 --- a/lib/widgets/about/policy_page.dart +++ b/lib/widgets/about/policy_page.dart @@ -1,6 +1,7 @@ import 'package:aves/model/settings/settings.dart'; import 'package:aves/widgets/common/basic/markdown_container.dart'; import 'package:aves/widgets/common/basic/scaffold.dart'; +import 'package:aves/widgets/common/behaviour/intents.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -38,11 +39,11 @@ class _PolicyPageState extends State { child: FocusableActionDetector( autofocus: true, shortcuts: const { - SingleActivator(LogicalKeyboardKey.arrowUp): _ScrollIntent.up(), - SingleActivator(LogicalKeyboardKey.arrowDown): _ScrollIntent.down(), + SingleActivator(LogicalKeyboardKey.arrowUp): VerticalScrollIntent.up(), + SingleActivator(LogicalKeyboardKey.arrowDown): VerticalScrollIntent.down(), }, actions: { - _ScrollIntent: CallbackAction<_ScrollIntent>(onInvoke: _onScrollIntent), + VerticalScrollIntent: VerticalScrollIntentAction(scrollController: _scrollController), }, child: Center( child: FutureBuilder( @@ -65,38 +66,4 @@ class _PolicyPageState extends State { ), ); } - - void _onScrollIntent(_ScrollIntent intent) { - late int factor; - switch (intent.type) { - case _ScrollDirection.up: - factor = -1; - break; - case _ScrollDirection.down: - factor = 1; - break; - } - _scrollController.animateTo( - _scrollController.offset + factor * 150, - duration: const Duration(milliseconds: 500), - curve: Curves.easeOutCubic, - ); - } -} - -class _ScrollIntent extends Intent { - const _ScrollIntent({ - required this.type, - }); - - const _ScrollIntent.up() : type = _ScrollDirection.up; - - const _ScrollIntent.down() : type = _ScrollDirection.down; - - final _ScrollDirection type; -} - -enum _ScrollDirection { - up, - down, } diff --git a/lib/widgets/collection/collection_grid.dart b/lib/widgets/collection/collection_grid.dart index 30801629d..c31d6a27f 100644 --- a/lib/widgets/collection/collection_grid.dart +++ b/lib/widgets/collection/collection_grid.dart @@ -441,7 +441,7 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> with Widge @override Widget build(BuildContext context) { final scrollView = _buildScrollView(widget.appBar, widget.collection); - return _buildDraggableScrollView(scrollView, widget.collection); + return settings.useTvLayout ? scrollView : _buildDraggableScrollView(scrollView, widget.collection); } Widget _buildDraggableScrollView(ScrollView scrollView, CollectionLens collection) { diff --git a/lib/widgets/common/behaviour/intents.dart b/lib/widgets/common/behaviour/intents.dart new file mode 100644 index 000000000..a01585737 --- /dev/null +++ b/lib/widgets/common/behaviour/intents.dart @@ -0,0 +1,44 @@ +import 'package:flutter/widgets.dart'; + +class VerticalScrollIntent extends Intent { + const VerticalScrollIntent({ + required this.type, + }); + + const VerticalScrollIntent.up() : type = VerticalScrollDirection.up; + + const VerticalScrollIntent.down() : type = VerticalScrollDirection.down; + + final VerticalScrollDirection type; +} + +enum VerticalScrollDirection { + up, + down, +} + +class VerticalScrollIntentAction extends CallbackAction { + VerticalScrollIntentAction({ + required ScrollController scrollController, + }) : super(onInvoke: (intent) => _onScrollIntent(intent, scrollController)); + + static void _onScrollIntent( + VerticalScrollIntent intent, + ScrollController scrollController, + ) { + late int factor; + switch (intent.type) { + case VerticalScrollDirection.up: + factor = -1; + break; + case VerticalScrollDirection.down: + factor = 1; + break; + } + scrollController.animateTo( + scrollController.offset + factor * 150, + duration: const Duration(milliseconds: 500), + curve: Curves.easeOutCubic, + ); + } +} diff --git a/lib/widgets/filter_grids/common/filter_grid_page.dart b/lib/widgets/filter_grids/common/filter_grid_page.dart index 19551c323..af0455967 100644 --- a/lib/widgets/filter_grids/common/filter_grid_page.dart +++ b/lib/widgets/filter_grids/common/filter_grid_page.dart @@ -620,7 +620,7 @@ class _FilterScrollView extends StatelessWidget { @override Widget build(BuildContext context) { final scrollView = _buildScrollView(context); - return _buildDraggableScrollView(scrollView); + return settings.useTvLayout ? scrollView : _buildDraggableScrollView(scrollView); } Widget _buildDraggableScrollView(ScrollView scrollView) { diff --git a/lib/widgets/settings/settings_page.dart b/lib/widgets/settings/settings_page.dart index 7f32fcd0c..166ee11ed 100644 --- a/lib/widgets/settings/settings_page.dart +++ b/lib/widgets/settings/settings_page.dart @@ -97,49 +97,8 @@ class _SettingsPageState extends State with FeedbackMixin { primary: false, ), ), - Expanded( - child: ValueListenableBuilder( - valueListenable: _tvSelectedIndexNotifier, - builder: (context, selectedIndex, child) { - final rail = NavigationRail( - backgroundColor: Theme.of(context).scaffoldBackgroundColor, - extended: true, - destinations: sections - .map((section) => NavigationRailDestination( - icon: section.icon(context), - label: Text(section.title(context)), - )) - .toList(), - selectedIndex: selectedIndex, - onDestinationSelected: (index) => _tvSelectedIndexNotifier.value = index, - minExtendedWidth: TvRail.minExtendedWidth, - ); - return LayoutBuilder( - builder: (context, constraints) { - return Row( - children: [ - SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints(minHeight: constraints.maxHeight), - child: IntrinsicHeight(child: rail), - ), - ), - Expanded( - child: MediaQuery.removePadding( - context: context, - removeLeft: !context.isRtl, - removeRight: context.isRtl, - child: _SettingsSectionBody( - loader: Future.value(sections[selectedIndex].tiles(context)), - ), - ), - ), - ], - ); - }, - ); - }, - ), + const Expanded( + child: _TvRail(), ), ], ), @@ -359,3 +318,68 @@ class _SettingsSectionBody extends StatelessWidget { ); } } + +class _TvRail extends StatefulWidget { + const _TvRail(); + + @override + State<_TvRail> createState() => _TvRailState(); +} + +class _TvRailState extends State<_TvRail> { + final ValueNotifier _indexNotifier = ValueNotifier(0); + + @override + void dispose() { + _indexNotifier.dispose(); + super.dispose(); + } + + static final List sections = _SettingsPageState.sections; + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: _indexNotifier, + builder: (context, selectedIndex, child) { + final rail = NavigationRail( + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + extended: true, + destinations: sections + .map((section) => NavigationRailDestination( + icon: section.icon(context), + label: Text(section.title(context)), + )) + .toList(), + selectedIndex: selectedIndex, + onDestinationSelected: (index) => _indexNotifier.value = index, + minExtendedWidth: TvRail.minExtendedWidth, + ); + return LayoutBuilder( + builder: (context, constraints) { + return Row( + children: [ + SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints(minHeight: constraints.maxHeight), + child: IntrinsicHeight(child: rail), + ), + ), + Expanded( + child: MediaQuery.removePadding( + context: context, + removeLeft: !context.isRtl, + removeRight: context.isRtl, + child: _SettingsSectionBody( + loader: Future.value(sections[selectedIndex].tiles(context)), + ), + ), + ), + ], + ); + }, + ); + }, + ); + } +} diff --git a/lib/widgets/viewer/info/basic_section.dart b/lib/widgets/viewer/info/basic_section.dart index c0c465a8b..f35351432 100644 --- a/lib/widgets/viewer/info/basic_section.dart +++ b/lib/widgets/viewer/info/basic_section.dart @@ -27,10 +27,11 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -class BasicSection extends StatelessWidget { +class BasicSection extends StatefulWidget { final AvesEntry entry; final CollectionLens? collection; final EntryInfoActionDelegate actionDelegate; + final ValueNotifier isScrollingNotifier; final ValueNotifier isEditingMetadataNotifier; final FilterCallback onFilter; @@ -39,12 +40,54 @@ class BasicSection extends StatelessWidget { required this.entry, this.collection, required this.actionDelegate, + required this.isScrollingNotifier, required this.isEditingMetadataNotifier, required this.onFilter, }); + @override + State createState() => _BasicSectionState(); +} + +class _BasicSectionState extends State { + final FocusNode _chipFocusNode = FocusNode(); + + CollectionLens? get collection => widget.collection; + + EntryInfoActionDelegate get actionDelegate => widget.actionDelegate; + + @override + void initState() { + super.initState(); + _registerWidget(widget); + _onScrollingChanged(); + } + + @override + void didUpdateWidget(covariant BasicSection oldWidget) { + super.didUpdateWidget(oldWidget); + _unregisterWidget(oldWidget); + _registerWidget(widget); + } + + @override + void dispose() { + _unregisterWidget(widget); + _chipFocusNode.dispose(); + super.dispose(); + } + + void _registerWidget(BasicSection widget) { + widget.isScrollingNotifier.addListener(_onScrollingChanged); + } + + void _unregisterWidget(BasicSection widget) { + widget.isScrollingNotifier.removeListener(_onScrollingChanged); + } + @override Widget build(BuildContext context) { + final entry = widget.entry; return AnimatedBuilder( animation: entry.metadataChangeNotifier, builder: (context, child) { @@ -52,7 +95,12 @@ class BasicSection extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ _BasicInfo(entry: entry), - _buildChips(context), + Focus( + focusNode: _chipFocusNode, + skipTraversal: true, + canRequestFocus: false, + child: _buildChips(context), + ), _buildEditButtons(context), ], ); @@ -60,6 +108,7 @@ class BasicSection extends StatelessWidget { } Widget _buildChips(BuildContext context) { + final entry = widget.entry; final tags = entry.tags.toList()..sort(compareAsciiUpperCaseNatural); final album = entry.directory; final filters = { @@ -91,7 +140,7 @@ class BasicSection extends StatelessWidget { children: effectiveFilters .map((filter) => AvesFilterChip( filter: filter, - onTap: onFilter, + onTap: widget.onFilter, )) .toList(), ), @@ -101,6 +150,7 @@ class BasicSection extends StatelessWidget { } Widget _buildEditButtons(BuildContext context) { + final entry = widget.entry; final children = [ EntryAction.editRating, EntryAction.editTags, @@ -124,8 +174,9 @@ class BasicSection extends StatelessWidget { } Widget _buildEditMetadataButton(BuildContext context, EntryAction action) { + final entry = widget.entry; return ValueListenableBuilder( - valueListenable: isEditingMetadataNotifier, + valueListenable: widget.isEditingMetadataNotifier, builder: (context, editingAction, child) { final isEditing = editingAction != null; final onPressed = isEditing ? null : () => actionDelegate.onActionSelected(context, entry, collection, action); @@ -181,6 +232,14 @@ class BasicSection extends StatelessWidget { }, ); } + + void _onScrollingChanged() { + if (!widget.isScrollingNotifier.value) { + // using `autofocus` while scrolling seems to fail for widget built offscreen + // so we give focus to this page when the screen is no longer scrolling + _chipFocusNode.children.firstOrNull?.requestFocus(); + } + } } class _BasicInfo extends StatefulWidget { diff --git a/lib/widgets/viewer/info/info_page.dart b/lib/widgets/viewer/info/info_page.dart index 0552f7f1e..48519c873 100644 --- a/lib/widgets/viewer/info/info_page.dart +++ b/lib/widgets/viewer/info/info_page.dart @@ -40,50 +40,17 @@ class InfoPage extends StatefulWidget { } class _InfoPageState extends State { - final FocusNode _focusNode = FocusNode(); final ScrollController _scrollController = ScrollController(); bool _scrollStartFromTop = false; static const splitScreenWidthThreshold = 600; - @override - void initState() { - super.initState(); - _registerWidget(widget); - _onScrollingChanged(); - } - - @override - void didUpdateWidget(covariant InfoPage oldWidget) { - super.didUpdateWidget(oldWidget); - _unregisterWidget(oldWidget); - _registerWidget(widget); - } - @override void dispose() { - _unregisterWidget(widget); - _focusNode.dispose(); _scrollController.dispose(); super.dispose(); } - void _registerWidget(InfoPage widget) { - widget.isScrollingNotifier.addListener(_onScrollingChanged); - } - - void _unregisterWidget(InfoPage widget) { - widget.isScrollingNotifier.removeListener(_onScrollingChanged); - } - - void _onScrollingChanged() { - if (!widget.isScrollingNotifier.value) { - // using `autofocus` while scrolling seems to fail for widget built offscreen - // so we give focus to this page when the screen is no longer scrolling - _focusNode.requestFocus(); - } - } - @override Widget build(BuildContext context) { return AvesScaffold( @@ -104,16 +71,13 @@ class _InfoPageState extends State { final targetEntry = pageEntry ?? mainEntry; return EmbeddedDataOpener( entry: targetEntry, - child: Focus( - focusNode: _focusNode, - child: _InfoPageContent( - collection: widget.collection, - entry: targetEntry, - isScrollingNotifier: widget.isScrollingNotifier, - scrollController: _scrollController, - split: mqWidth > splitScreenWidthThreshold, - goToViewer: _goToViewer, - ), + child: _InfoPageContent( + collection: widget.collection, + entry: targetEntry, + isScrollingNotifier: widget.isScrollingNotifier, + scrollController: _scrollController, + split: mqWidth > splitScreenWidthThreshold, + goToViewer: _goToViewer, ), ); } @@ -240,6 +204,7 @@ class _InfoPageContentState extends State<_InfoPageContent> { entry: entry, collection: collection, actionDelegate: _actionDelegate, + isScrollingNotifier: widget.isScrollingNotifier, isEditingMetadataNotifier: _isEditingMetadataNotifier, onFilter: _onFilter, ); diff --git a/lib/widgets/viewer/info/metadata/metadata_dir_tile.dart b/lib/widgets/viewer/info/metadata/metadata_dir_tile.dart index 6b4cc987e..def7e8670 100644 --- a/lib/widgets/viewer/info/metadata/metadata_dir_tile.dart +++ b/lib/widgets/viewer/info/metadata/metadata_dir_tile.dart @@ -40,16 +40,60 @@ class MetadataDirTile extends StatelessWidget { var tags = dir.tags; if (tags.isEmpty) return const SizedBox(); + return AvesExpansionTile( + title: title, + highlightColor: getTitleColor(context, dir), + expandedNotifier: expandedDirectoryNotifier, + initiallyExpanded: initiallyExpanded, + children: [ + MetadataDirTileBody( + entry: entry, + dir: dir, + showThumbnails: showThumbnails, + ), + ], + ); + } + + static Color getTitleColor(BuildContext context, MetadataDirectory dir) { final dirName = dir.name; if (dirName == MetadataDirectory.xmpDirectory) { - return XmpDirTile( - entry: entry, - title: title, - allTags: dir.allTags, - tags: tags, - expandedNotifier: expandedDirectoryNotifier, - initiallyExpanded: initiallyExpanded, - ); + return context.select((v) => v.xmp); + } else { + final colors = context.watch(); + return dir.color ?? colors.fromBrandColor(BrandColors.get(dirName)) ?? colors.fromString(dirName); + } + } +} + +class MetadataDirTileBody extends StatelessWidget { + final AvesEntry entry; + final MetadataDirectory dir; + final bool showThumbnails; + + const MetadataDirTileBody({ + super.key, + required this.entry, + required this.dir, + this.showThumbnails = true, + }); + + @override + Widget build(BuildContext context) { + var tags = dir.tags; + + late final List children; + final dirName = dir.name; + if (dirName == MetadataDirectory.xmpDirectory) { + children = [ + Padding( + padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8), + child: XmpDirTileBody( + allTags: dir.allTags, + tags: tags, + ), + ), + ]; } else { Map? linkHandlers; switch (dirName) { @@ -67,25 +111,23 @@ class MetadataDirTile extends StatelessWidget { break; } - final colors = context.watch(); - return AvesExpansionTile( - title: title, - highlightColor: dir.color ?? colors.fromBrandColor(BrandColors.get(dirName)) ?? colors.fromString(dirName), - expandedNotifier: expandedDirectoryNotifier, - initiallyExpanded: initiallyExpanded, - children: [ - if (showThumbnails && dirName == MetadataDirectory.exifThumbnailDirectory) MetadataThumbnails(entry: entry), - Padding( - padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8), - child: InfoRowGroup( - info: tags, - maxValueLength: Constants.infoGroupMaxValueLength, - spanBuilders: linkHandlers, - ), + children = [ + if (showThumbnails && dirName == MetadataDirectory.exifThumbnailDirectory) MetadataThumbnails(entry: entry), + Padding( + padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8), + child: InfoRowGroup( + info: tags, + maxValueLength: Constants.infoGroupMaxValueLength, + spanBuilders: linkHandlers, ), - ], - ); + ), + ]; } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: children, + ); } static Map getSvgLinkHandlers(SplayTreeMap tags) { diff --git a/lib/widgets/viewer/info/metadata/metadata_section.dart b/lib/widgets/viewer/info/metadata/metadata_section.dart index 6977f8eea..80e76a7ce 100644 --- a/lib/widgets/viewer/info/metadata/metadata_section.dart +++ b/lib/widgets/viewer/info/metadata/metadata_section.dart @@ -2,11 +2,14 @@ import 'dart:async'; import 'package:aves/model/entry.dart'; import 'package:aves/model/entry_info.dart'; +import 'package:aves/model/settings/settings.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; +import 'package:aves/widgets/common/identity/buttons/outlined_button.dart'; import 'package:aves/widgets/viewer/info/common.dart'; import 'package:aves/widgets/viewer/info/metadata/metadata_dir.dart'; import 'package:aves/widgets/viewer/info/metadata/metadata_dir_tile.dart'; +import 'package:aves/widgets/viewer/info/metadata/tv_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; @@ -92,15 +95,32 @@ class _MetadataSectionSliverState extends State { child: child, ), ), - children: [ - const SectionRow(icon: AIcons.info), - ...metadata.entries.map((kv) => MetadataDirTile( - entry: entry, - title: kv.key, - dir: kv.value, - expandedDirectoryNotifier: _expandedDirectoryNotifier, - )), - ], + children: settings.useTvLayout + ? [ + AvesOutlinedButton( + label: MaterialLocalizations.of(context).moreButtonTooltip, + onPressed: () { + Navigator.maybeOf(context)?.push( + MaterialPageRoute( + settings: const RouteSettings(name: TvMetadataPage.routeName), + builder: (context) => TvMetadataPage( + entry: entry, + metadata: metadata, + ), + ), + ); + }, + ), + ] + : [ + const SectionRow(icon: AIcons.info), + ...metadata.entries.map((kv) => MetadataDirTile( + entry: entry, + title: kv.key, + dir: kv.value, + expandedDirectoryNotifier: _expandedDirectoryNotifier, + )), + ], ), ); } diff --git a/lib/widgets/viewer/info/metadata/tv_page.dart b/lib/widgets/viewer/info/metadata/tv_page.dart new file mode 100644 index 000000000..38273c361 --- /dev/null +++ b/lib/widgets/viewer/info/metadata/tv_page.dart @@ -0,0 +1,123 @@ +import 'package:aves/model/entry.dart'; +import 'package:aves/theme/icons.dart'; +import 'package:aves/widgets/common/basic/scaffold.dart'; +import 'package:aves/widgets/common/behaviour/intents.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/navigation/tv_rail.dart'; +import 'package:aves/widgets/viewer/info/metadata/metadata_dir.dart'; +import 'package:aves/widgets/viewer/info/metadata/metadata_dir_tile.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class TvMetadataPage extends StatefulWidget { + static const routeName = '/info/metadata'; + + final AvesEntry entry; + final Map metadata; + + const TvMetadataPage({ + super.key, + required this.entry, + required this.metadata, + }); + + @override + State createState() => _TvMetadataPageState(); +} + +class _TvMetadataPageState extends State { + final ValueNotifier _railIndexNotifier = ValueNotifier(0); + final FocusNode _railFocusNode = FocusNode(); + final ScrollController _detailsScrollController = ScrollController(); + + Map get metadata => widget.metadata; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + _railFocusNode.children.firstOrNull?.requestFocus(); + }); + } + + @override + void dispose() { + _railIndexNotifier.dispose(); + _railFocusNode.dispose(); + _detailsScrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AvesScaffold( + appBar: AppBar( + automaticallyImplyLeading: false, + title: Text(context.l10n.viewerInfoPageTitle), + ), + body: ValueListenableBuilder( + valueListenable: _railIndexNotifier, + builder: (context, selectedIndex, child) { + final titles = metadata.keys.toList(); + final selectedDir = metadata[titles[selectedIndex]]; + if (selectedDir == null) return const SizedBox(); + + final rail = NavigationRail( + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + extended: true, + destinations: titles.mapIndexed((i, title) { + final dir = metadata[titles[i]]!; + final color = MetadataDirTile.getTitleColor(context, dir); + return NavigationRailDestination( + icon: Icon(AIcons.disc, color: color), + label: Text(title), + ); + }).toList(), + selectedIndex: selectedIndex, + onDestinationSelected: (index) => _railIndexNotifier.value = index, + minExtendedWidth: TvRail.minExtendedWidth, + ); + + return SafeArea( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(width: 16), + Focus( + focusNode: _railFocusNode, + skipTraversal: true, + canRequestFocus: false, + child: SingleChildScrollView( + child: IntrinsicHeight( + child: rail, + ), + ), + ), + Expanded( + child: FocusableActionDetector( + shortcuts: const { + SingleActivator(LogicalKeyboardKey.arrowUp): VerticalScrollIntent.up(), + SingleActivator(LogicalKeyboardKey.arrowDown): VerticalScrollIntent.down(), + }, + actions: { + VerticalScrollIntent: VerticalScrollIntentAction(scrollController: _detailsScrollController), + }, + child: SingleChildScrollView( + controller: _detailsScrollController, + padding: const EdgeInsets.all(16), + child: MetadataDirTileBody( + entry: widget.entry, + dir: selectedDir, + ), + ), + ), + ), + ], + ), + ); + }, + ), + ); + } +} diff --git a/lib/widgets/viewer/info/metadata/xmp_tile.dart b/lib/widgets/viewer/info/metadata/xmp_tile.dart index ff71933e6..fd3ced944 100644 --- a/lib/widgets/viewer/info/metadata/xmp_tile.dart +++ b/lib/widgets/viewer/info/metadata/xmp_tile.dart @@ -1,41 +1,27 @@ import 'dart:collection'; import 'dart:convert'; -import 'package:aves/model/entry.dart'; -import 'package:aves/theme/colors.dart'; import 'package:aves/utils/xmp_utils.dart'; -import 'package:aves/widgets/common/identity/aves_expansion_tile.dart'; import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -class XmpDirTile extends StatefulWidget { - final AvesEntry entry; - final String title; +class XmpDirTileBody extends StatefulWidget { final SplayTreeMap allTags, tags; - final ValueNotifier? expandedNotifier; - final bool initiallyExpanded; - const XmpDirTile({ + const XmpDirTileBody({ super.key, - required this.entry, - required this.title, required this.allTags, required this.tags, - required this.expandedNotifier, - required this.initiallyExpanded, }); @override - State createState() => _XmpDirTileState(); + State createState() => _XmpDirTileBodyState(); } -class _XmpDirTileState extends State { +class _XmpDirTileBodyState extends State { late final Map _schemaRegistryPrefixes, _tags; - AvesEntry get entry => widget.entry; - static const schemaRegistryPrefixesKey = 'schemaRegistryPrefixes'; @override @@ -60,21 +46,10 @@ class _XmpDirTileState extends State { return XmpNamespace.create(_schemaRegistryPrefixes, nsPrefix, rawProps); }).toList() ..sort((a, b) => compareAsciiUpperCase(a.displayTitle, b.displayTitle)); - return AvesExpansionTile( - // title may contain parent to distinguish multiple XMP directories - title: widget.title, - highlightColor: context.select((v) => v.xmp), - expandedNotifier: widget.expandedNotifier, - initiallyExpanded: widget.initiallyExpanded, - children: [ - Padding( - padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: sections.expand((section) => section.buildNamespaceSection(context)).toList(), - ), - ), - ], + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: sections.expand((section) => section.buildNamespaceSection(context)).toList(), ); } }