accessibility: fixed navigation with TalkBack

This commit is contained in:
Thibault Deckers 2023-03-01 22:04:22 +01:00
parent 7797c03170
commit 64e153ef5c
23 changed files with 241 additions and 198 deletions

View file

@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
## <a id="unreleased"></a>[Unreleased]
### Fixed
- Accessibility: navigation with TalkBack
## <a id="v1.8.2"></a>[v1.8.2] - 2023-02-28
### Added

View file

@ -23,7 +23,9 @@ import 'package:aves/widgets/common/action_controls/togglers/favourite.dart';
import 'package:aves/widgets/common/action_controls/togglers/title_search.dart';
import 'package:aves/widgets/common/app_bar/app_bar_subtitle.dart';
import 'package:aves/widgets/common/app_bar/app_bar_title.dart';
import 'package:aves/widgets/common/basic/menu.dart';
import 'package:aves/widgets/common/basic/popup/container.dart';
import 'package:aves/widgets/common/basic/popup/expansion_panel.dart';
import 'package:aves/widgets/common/basic/popup/menu_row.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/aves_app_bar.dart';
import 'package:aves/widgets/common/identity/buttons/captioned_button.dart';
@ -405,19 +407,15 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
},
),
if (isSelecting && !settings.isReadOnly && appMode == AppMode.main && !isTrash)
PopupMenuItem<EntrySetAction>(
PopupMenuExpansionPanel<EntrySetAction>(
enabled: hasSelection,
padding: EdgeInsets.zero,
child: PopupMenuItemExpansionPanel<EntrySetAction>(
enabled: hasSelection,
value: 'edit',
icon: AIcons.edit,
title: context.l10n.collectionActionEdit,
items: [
_buildRotateAndFlipMenuItems(context, canApply: canApply),
...EntrySetActions.edit.where((v) => isVisible(v) && !selectionQuickActions.contains(v)).map((action) => _toMenuItem(action, enabled: canApply(action), selection: selection)),
],
),
value: 'edit',
icon: AIcons.edit,
title: context.l10n.collectionActionEdit,
items: [
_buildRotateAndFlipMenuItems(context, canApply: canApply),
...EntrySetActions.edit.where((v) => isVisible(v) && !selectionQuickActions.contains(v)).map((action) => _toMenuItem(action, enabled: canApply(action), selection: selection)),
],
),
];
@ -529,7 +527,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
);
}
PopupMenuItem<EntrySetAction> _buildRotateAndFlipMenuItems(
PopupMenuEntry<EntrySetAction> _buildRotateAndFlipMenuItems(
BuildContext context, {
required bool Function(EntrySetAction action) canApply,
}) {
@ -558,22 +556,17 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
),
);
return PopupMenuItem(
child: TooltipTheme(
data: TooltipTheme.of(context).copyWith(
preferBelow: false,
),
child: Row(
children: [
buildDivider(),
buildItem(EntrySetAction.rotateCCW),
buildDivider(),
buildItem(EntrySetAction.rotateCW),
buildDivider(),
buildItem(EntrySetAction.flip),
buildDivider(),
],
),
return PopupMenuItemContainer(
child: Row(
children: [
buildDivider(),
buildItem(EntrySetAction.rotateCCW),
buildDivider(),
buildItem(EntrySetAction.rotateCW),
buildDivider(),
buildItem(EntrySetAction.flip),
buildDivider(),
],
),
);
}

View file

@ -56,6 +56,19 @@ abstract class ChooserQuickButtonState<T extends ChooserQuickButton<U>, U> exten
Widget build(BuildContext context) {
final _hasChooser = hasChooser;
Widget child = IconButton(
icon: icon,
onPressed: widget.onPressed,
focusNode: widget.focusNode,
tooltip: _hasChooser ? null : tooltip,
);
if (_hasChooser) {
child = Semantics(
tooltip: tooltip,
child: child,
);
}
return GestureDetector(
behavior: HitTestBehavior.opaque,
onLongPressStart: _hasChooser ? _showChooser : null,
@ -70,12 +83,7 @@ abstract class ChooserQuickButtonState<T extends ChooserQuickButton<U>, U> exten
}
: null,
onLongPressCancel: _clearChooserOverlayEntry,
child: IconButton(
icon: icon,
onPressed: widget.onPressed,
focusNode: widget.focusNode,
tooltip: _hasChooser ? null : tooltip,
),
child: child,
);
}

View file

@ -2,7 +2,7 @@ import 'dart:async';
import 'package:aves/model/actions/share_actions.dart';
import 'package:aves/widgets/common/action_controls/quick_choosers/common/menu.dart';
import 'package:aves/widgets/common/basic/menu.dart';
import 'package:aves/widgets/common/basic/popup/menu_row.dart';
import 'package:flutter/material.dart';
class ShareQuickChooser extends StatelessWidget {

View file

@ -2,7 +2,7 @@ import 'package:aves/model/entry.dart';
import 'package:aves/model/favourites.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/basic/menu.dart';
import 'package:aves/widgets/common/basic/popup/menu_row.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/fx/sweeper.dart';
import 'package:aves/widgets/common/identity/buttons/captioned_button.dart';

View file

@ -1,7 +1,7 @@
import 'dart:async';
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/basic/menu.dart';
import 'package:aves/widgets/common/basic/popup/menu_row.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/buttons/captioned_button.dart';
import 'package:aves/widgets/viewer/video/controller.dart';

View file

@ -2,7 +2,7 @@ import 'dart:async';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/basic/menu.dart';
import 'package:aves/widgets/common/basic/popup/menu_row.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/buttons/captioned_button.dart';
import 'package:aves/widgets/viewer/video/controller.dart';

View file

@ -1,6 +1,6 @@
import 'package:aves/model/query.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/basic/menu.dart';
import 'package:aves/widgets/common/basic/popup/menu_row.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/buttons/captioned_button.dart';
import 'package:flutter/material.dart';

View file

@ -212,37 +212,40 @@ class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProv
),
);
}),
RepaintBoundary(
child: GestureDetector(
onLongPressStart: (details) {
_longPressLastGlobalPosition = details.globalPosition;
_onVerticalDragStart();
},
onLongPressMoveUpdate: (details) {
final dy = (details.globalPosition - _longPressLastGlobalPosition).dy;
_longPressLastGlobalPosition = details.globalPosition;
_onVerticalDragUpdate(dy);
},
onLongPressEnd: (_) => _onVerticalDragEnd(),
onVerticalDragStart: (_) => _onVerticalDragStart(),
onVerticalDragUpdate: (details) => _onVerticalDragUpdate(details.delta.dy),
onVerticalDragEnd: (_) => _onVerticalDragEnd(),
child: ValueListenableBuilder<double>(
valueListenable: _thumbOffsetNotifier,
builder: (context, thumbOffset, child) => Container(
alignment: AlignmentDirectional.topEnd,
padding: EdgeInsets.only(top: thumbOffset) + widget.padding,
child: widget.scrollThumbBuilder(
widget.backgroundColor,
_thumbAnimation,
_labelAnimation,
widget.scrollThumbSize.height,
labelText: _isDragInProcess
? ValueListenableBuilder<double>(
valueListenable: _viewOffsetNotifier,
builder: (context, viewOffset, child) => widget.labelTextBuilder(viewOffset + thumbOffset),
)
: null,
// exclude semantics, otherwise this layer will block access to content layers below when using TalkBack
ExcludeSemantics(
child: RepaintBoundary(
child: GestureDetector(
onLongPressStart: (details) {
_longPressLastGlobalPosition = details.globalPosition;
_onVerticalDragStart();
},
onLongPressMoveUpdate: (details) {
final dy = (details.globalPosition - _longPressLastGlobalPosition).dy;
_longPressLastGlobalPosition = details.globalPosition;
_onVerticalDragUpdate(dy);
},
onLongPressEnd: (_) => _onVerticalDragEnd(),
onVerticalDragStart: (_) => _onVerticalDragStart(),
onVerticalDragUpdate: (details) => _onVerticalDragUpdate(details.delta.dy),
onVerticalDragEnd: (_) => _onVerticalDragEnd(),
child: ValueListenableBuilder<double>(
valueListenable: _thumbOffsetNotifier,
builder: (context, thumbOffset, child) => Container(
alignment: AlignmentDirectional.topEnd,
padding: EdgeInsets.only(top: thumbOffset) + widget.padding,
child: widget.scrollThumbBuilder(
widget.backgroundColor,
_thumbAnimation,
_labelAnimation,
widget.scrollThumbSize.height,
labelText: _isDragInProcess
? ValueListenableBuilder<double>(
valueListenable: _viewOffsetNotifier,
builder: (context, viewOffset, child) => widget.labelTextBuilder(viewOffset + thumbOffset),
)
: null,
),
),
),
),

View file

@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
class PopupMenuItemContainer<T> extends PopupMenuEntry<T> {
final Widget child;
const PopupMenuItemContainer({
super.key,
this.height = kMinInteractiveDimension,
required this.child,
});
@override
final double height;
@override
bool represents(void value) => false;
@override
State<PopupMenuItemContainer> createState() => _TransitionPopupMenuItemState();
}
class _TransitionPopupMenuItemState extends State<PopupMenuItemContainer> {
@override
Widget build(BuildContext context) {
return TooltipTheme(
data: TooltipTheme.of(context).copyWith(
preferBelow: false,
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: widget.child,
),
);
}
}

View file

@ -1,62 +1,9 @@
import 'package:aves/theme/durations.dart';
import 'package:aves/widgets/common/basic/popup/menu_row.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class MenuRow extends StatelessWidget {
final String text;
final Widget? icon;
const MenuRow({
super.key,
required this.text,
this.icon,
});
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
if (icon != null)
Padding(
padding: const EdgeInsetsDirectional.only(end: 12),
child: IconTheme.merge(
data: IconThemeData(
color: ListTileTheme.of(context).iconColor,
),
child: icon!,
),
),
Flexible(
child: Text(text),
),
],
);
}
}
// scale icons according to text scale
class MenuIconTheme extends StatelessWidget {
final Widget child;
const MenuIconTheme({
super.key,
required this.child,
});
@override
Widget build(BuildContext context) {
final iconTheme = IconTheme.of(context);
return IconTheme(
data: iconTheme.copyWith(
size: iconTheme.size! * MediaQuery.textScaleFactorOf(context),
),
child: child,
);
}
}
class PopupMenuItemExpansionPanel<T> extends StatefulWidget {
class PopupMenuExpansionPanel<T> extends PopupMenuEntry<T> {
final bool enabled;
final String value;
final ValueNotifier<String?> expandedNotifier;
@ -64,9 +11,10 @@ class PopupMenuItemExpansionPanel<T> extends StatefulWidget {
final String title;
final List<PopupMenuEntry<T>> items;
PopupMenuItemExpansionPanel({
PopupMenuExpansionPanel({
super.key,
this.enabled = true,
this.height = kMinInteractiveDimension,
required this.value,
ValueNotifier<String?>? expandedNotifier,
required this.icon,
@ -75,10 +23,16 @@ class PopupMenuItemExpansionPanel<T> extends StatefulWidget {
}) : expandedNotifier = expandedNotifier ?? ValueNotifier(null);
@override
State<PopupMenuItemExpansionPanel<T>> createState() => _PopupMenuItemExpansionPanelState<T>();
final double height;
@override
bool represents(void value) => false;
@override
State<PopupMenuExpansionPanel<T>> createState() => _PopupMenuExpansionPanelState<T>();
}
class _PopupMenuItemExpansionPanelState<T> extends State<PopupMenuItemExpansionPanel<T>> {
class _PopupMenuExpansionPanelState<T> extends State<PopupMenuExpansionPanel<T>> {
// ref `_kMenuHorizontalPadding` used in `PopupMenuItem`
static const double _horizontalPadding = 16;
@ -103,16 +57,23 @@ class _PopupMenuItemExpansionPanelState<T> extends State<PopupMenuItemExpansionP
elevation: 0,
children: [
ExpansionPanel(
headerBuilder: (context, isExpanded) => DefaultTextStyle(
style: style,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: _horizontalPadding),
child: MenuRow(
text: widget.title,
icon: Icon(widget.icon),
headerBuilder: (context, isExpanded) {
return DefaultTextStyle(
style: style,
child: IconTheme.merge(
data: IconThemeData(
color: widget.enabled ? null : Theme.of(context).disabledColor,
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: _horizontalPadding),
child: MenuRow(
text: widget.title,
icon: Icon(widget.icon),
),
),
),
),
),
);
},
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [

View file

@ -0,0 +1,55 @@
import 'package:flutter/material.dart';
class MenuRow extends StatelessWidget {
final String text;
final Widget? icon;
const MenuRow({
super.key,
required this.text,
this.icon,
});
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
if (icon != null)
Padding(
padding: const EdgeInsetsDirectional.only(end: 12),
child: IconTheme.merge(
data: IconThemeData(
color: ListTileTheme.of(context).iconColor,
),
child: icon!,
),
),
Flexible(
child: Text(text),
),
],
);
}
}
// scale icons according to text scale
class MenuIconTheme extends StatelessWidget {
final Widget child;
const MenuIconTheme({
super.key,
required this.child,
});
@override
Widget build(BuildContext context) {
final iconTheme = IconTheme.of(context);
return IconTheme(
data: iconTheme.copyWith(
size: iconTheme.size! * MediaQuery.textScaleFactorOf(context),
),
child: child,
);
}
}

View file

@ -14,7 +14,7 @@ import 'package:aves/theme/colors.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/collection/filter_bar.dart';
import 'package:aves/widgets/common/basic/menu.dart';
import 'package:aves/widgets/common/basic/popup/menu_row.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:aves/widgets/filter_grids/common/action_delegates/chip.dart';

View file

@ -8,7 +8,7 @@ import 'package:aves/model/source/collection_source.dart';
import 'package:aves/services/analysis_service.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/widgets/common/basic/menu.dart';
import 'package:aves/widgets/common/basic/popup/menu_row.dart';
import 'package:aves/widgets/common/basic/scaffold.dart';
import 'package:aves/widgets/common/behaviour/pop/scope.dart';
import 'package:aves/widgets/common/behaviour/pop/tv_navigation.dart';

View file

@ -7,7 +7,7 @@ import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/collection/collection_grid.dart';
import 'package:aves/widgets/common/basic/menu.dart';
import 'package:aves/widgets/common/basic/popup/menu_row.dart';
import 'package:aves/widgets/common/basic/scaffold.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/grid/theme.dart';

View file

@ -13,7 +13,7 @@ import 'package:aves/model/vaults/details.dart';
import 'package:aves/model/vaults/vaults.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/basic/menu.dart';
import 'package:aves/widgets/common/basic/popup/menu_row.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
import 'package:aves/widgets/common/identity/buttons/captioned_button.dart';

View file

@ -11,7 +11,7 @@ import 'package:aves/theme/durations.dart';
import 'package:aves/widgets/common/action_controls/togglers/title_search.dart';
import 'package:aves/widgets/common/app_bar/app_bar_subtitle.dart';
import 'package:aves/widgets/common/app_bar/app_bar_title.dart';
import 'package:aves/widgets/common/basic/menu.dart';
import 'package:aves/widgets/common/basic/popup/menu_row.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/aves_app_bar.dart';
import 'package:aves/widgets/common/identity/buttons/captioned_button.dart';

View file

@ -18,7 +18,7 @@ import 'package:aves/utils/debouncer.dart';
import 'package:aves/widgets/collection/collection_page.dart';
import 'package:aves/widgets/collection/entry_set_action_delegate.dart';
import 'package:aves/widgets/common/basic/insets.dart';
import 'package:aves/widgets/common/basic/menu.dart';
import 'package:aves/widgets/common/basic/popup/menu_row.dart';
import 'package:aves/widgets/common/basic/scaffold.dart';
import 'package:aves/widgets/common/behaviour/routes.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';

View file

@ -6,7 +6,7 @@ import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/common/basic/menu.dart';
import 'package:aves/widgets/common/basic/popup/menu_row.dart';
import 'package:aves/widgets/common/basic/scaffold.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/buttons/outlined_button.dart';

View file

@ -12,7 +12,7 @@ import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/action_mixins/feedback.dart';
import 'package:aves/widgets/common/app_bar/app_bar_title.dart';
import 'package:aves/widgets/common/basic/insets.dart';
import 'package:aves/widgets/common/basic/menu.dart';
import 'package:aves/widgets/common/basic/popup/menu_row.dart';
import 'package:aves/widgets/common/basic/scaffold.dart';
import 'package:aves/widgets/common/behaviour/pop/scope.dart';
import 'package:aves/widgets/common/behaviour/pop/tv_navigation.dart';

View file

@ -6,7 +6,7 @@ import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/app_bar/app_bar_title.dart';
import 'package:aves/widgets/common/app_bar/sliver_app_bar_title.dart';
import 'package:aves/widgets/common/basic/menu.dart';
import 'package:aves/widgets/common/basic/popup/menu_row.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/viewer/action/entry_info_action_delegate.dart';
import 'package:aves/widgets/viewer/info/info_search.dart';

View file

@ -11,8 +11,10 @@ import 'package:aves/widgets/common/action_controls/quick_choosers/tag_button.da
import 'package:aves/widgets/common/action_controls/togglers/favourite.dart';
import 'package:aves/widgets/common/action_controls/togglers/mute.dart';
import 'package:aves/widgets/common/action_controls/togglers/play.dart';
import 'package:aves/widgets/common/basic/menu.dart';
import 'package:aves/widgets/common/basic/popup_menu_button.dart';
import 'package:aves/widgets/common/basic/popup/container.dart';
import 'package:aves/widgets/common/basic/popup/expansion_panel.dart';
import 'package:aves/widgets/common/basic/popup/menu_button.dart';
import 'package:aves/widgets/common/basic/popup/menu_row.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/buttons/captioned_button.dart';
import 'package:aves/widgets/common/identity/buttons/overlay_button.dart';
@ -231,32 +233,26 @@ class ViewerButtonRowContent extends StatelessWidget {
if (pageEntry.canRotate || pageEntry.canFlip) _buildRotateAndFlipMenuItems(context),
...topLevelActions.map((action) => _buildPopupMenuItem(context, action, videoController)),
if (exportActions.isNotEmpty)
PopupMenuItem<EntryAction>(
padding: EdgeInsets.zero,
child: PopupMenuItemExpansionPanel<EntryAction>(
value: 'export',
expandedNotifier: _popupExpandedNotifier,
icon: AIcons.export,
title: context.l10n.entryActionExport,
items: [
...exportInternalActions.map((action) => _buildPopupMenuItem(context, action, videoController)).toList(),
if (exportInternalActions.isNotEmpty && exportExternalActions.isNotEmpty) const PopupMenuDivider(height: 0),
...exportExternalActions.map((action) => _buildPopupMenuItem(context, action, videoController)).toList(),
],
),
PopupMenuExpansionPanel<EntryAction>(
value: 'export',
expandedNotifier: _popupExpandedNotifier,
icon: AIcons.export,
title: context.l10n.entryActionExport,
items: [
...exportInternalActions.map((action) => _buildPopupMenuItem(context, action, videoController)).toList(),
if (exportInternalActions.isNotEmpty && exportExternalActions.isNotEmpty) const PopupMenuDivider(height: 0),
...exportExternalActions.map((action) => _buildPopupMenuItem(context, action, videoController)).toList(),
],
),
if (videoActions.isNotEmpty)
PopupMenuItem<EntryAction>(
padding: EdgeInsets.zero,
child: PopupMenuItemExpansionPanel<EntryAction>(
value: 'video',
expandedNotifier: _popupExpandedNotifier,
icon: AIcons.video,
title: context.l10n.settingsVideoSectionTitle,
items: [
...videoActions.map((action) => _buildPopupMenuItem(context, action, videoController)).toList(),
],
),
PopupMenuExpansionPanel<EntryAction>(
value: 'video',
expandedNotifier: _popupExpandedNotifier,
icon: AIcons.video,
title: context.l10n.settingsVideoSectionTitle,
items: [
...videoActions.map((action) => _buildPopupMenuItem(context, action, videoController)).toList(),
],
),
if (!kReleaseMode) ...[
const PopupMenuDivider(),
@ -357,7 +353,7 @@ class ViewerButtonRowContent extends StatelessWidget {
);
}
PopupMenuItem<EntryAction> _buildRotateAndFlipMenuItems(BuildContext context) {
PopupMenuEntry<EntryAction> _buildRotateAndFlipMenuItems(BuildContext context) {
Widget buildDivider() => const SizedBox(
height: 16,
child: VerticalDivider(
@ -383,29 +379,17 @@ class ViewerButtonRowContent extends StatelessWidget {
),
);
return PopupMenuItem(
padding: EdgeInsets.zero,
child: IconTheme.merge(
data: IconThemeData(
color: ListTileTheme.of(context).iconColor,
),
child: ColoredBox(
color: PopupMenuTheme.of(context).color!,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
buildDivider(),
buildItem(EntryAction.rotateCCW),
buildDivider(),
buildItem(EntryAction.rotateCW),
buildDivider(),
buildItem(EntryAction.flip),
buildDivider(),
],
),
),
),
return PopupMenuItemContainer(
child: Row(
children: [
buildDivider(),
buildItem(EntryAction.rotateCCW),
buildDivider(),
buildItem(EntryAction.rotateCW),
buildDivider(),
buildItem(EntryAction.flip),
buildDivider(),
],
),
);
}