Skip to content

Commit 2752b3c

Browse files
authored
🚑️ Fix semantics with paths list (#249)
1 parent 91ddee6 commit 2752b3c

File tree

2 files changed

+102
-82
lines changed

2 files changed

+102
-82
lines changed

lib/src/delegates/asset_picker_builder_delegate.dart

Lines changed: 94 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import '../widget/gaps.dart';
3131
import '../widget/platform_progress_indicator.dart';
3232
import '../widget/scale_text.dart';
3333

34+
const String _ordinalNamePermissionOverlay = 'permissionOverlay';
35+
3436
typedef IndicatorBuilder = Widget Function(
3537
BuildContext context,
3638
bool isAssetsEmpty,
@@ -623,7 +625,10 @@ abstract class AssetPickerBuilderDelegate<Asset, Path> {
623625
}
624626
return Positioned.fill(
625627
child: Semantics(
626-
sortKey: const OrdinalSortKey(0),
628+
sortKey: const OrdinalSortKey(
629+
0,
630+
name: _ordinalNamePermissionOverlay,
631+
),
627632
child: Container(
628633
padding: context.mediaQuery.padding,
629634
color: context.themeData.canvasColor,
@@ -876,6 +881,32 @@ class DefaultAssetPickerBuilderDelegate
876881
}
877882
}
878883

884+
@override
885+
AssetPickerAppBar appBar(BuildContext context) {
886+
return AssetPickerAppBar(
887+
backgroundColor: theme.appBarTheme.backgroundColor,
888+
centerTitle: isAppleOS,
889+
title: Semantics(
890+
onTapHint: textDelegate.sActionSwitchPathLabel,
891+
child: pathEntitySelector(context),
892+
),
893+
leading: backButton(context),
894+
// Condition for displaying the confirm button:
895+
// - On Android, show if preview is enabled or if multi asset mode.
896+
// If no preview and single asset mode, do not show confirm button,
897+
// because any click on an asset selects it.
898+
// - On iOS, show if no preview and multi asset mode. This is because for iOS
899+
// the [bottomActionBar] has the confirm button, but if no preview,
900+
// [bottomActionBar] is not displayed.
901+
actions: (!isAppleOS || !isPreviewEnabled) &&
902+
(isPreviewEnabled || !isSingleAssetMode)
903+
? <Widget>[confirmButton(context)]
904+
: null,
905+
actionsPadding: const EdgeInsetsDirectional.only(end: 14),
906+
blurRadius: isAppleOS ? appleOSBlurRadius : 0,
907+
);
908+
}
909+
879910
@override
880911
Widget androidLayout(BuildContext context) {
881912
return AssetPickerAppBarWrapper(
@@ -911,32 +942,6 @@ class DefaultAssetPickerBuilderDelegate
911942
);
912943
}
913944

914-
@override
915-
AssetPickerAppBar appBar(BuildContext context) {
916-
return AssetPickerAppBar(
917-
backgroundColor: theme.appBarTheme.backgroundColor,
918-
centerTitle: isAppleOS,
919-
title: Semantics(
920-
onTapHint: textDelegate.sActionSwitchPathLabel,
921-
child: pathEntitySelector(context),
922-
),
923-
leading: backButton(context),
924-
// Condition for displaying the confirm button:
925-
// - On Android, show if preview is enabled or if multi asset mode.
926-
// If no preview and single asset mode, do not show confirm button,
927-
// because any click on an asset selects it.
928-
// - On iOS, show if no preview and multi asset mode. This is because for iOS
929-
// the [bottomActionBar] has the confirm button, but if no preview,
930-
// [bottomActionBar] is not displayed.
931-
actions: (!isAppleOS || !isPreviewEnabled) &&
932-
(isPreviewEnabled || !isSingleAssetMode)
933-
? <Widget>[confirmButton(context)]
934-
: null,
935-
actionsPadding: const EdgeInsetsDirectional.only(end: 14),
936-
blurRadius: isAppleOS ? appleOSBlurRadius : 0,
937-
);
938-
}
939-
940945
@override
941946
Widget appleOSLayout(BuildContext context) {
942947
Widget _gridLayout(BuildContext context) {
@@ -990,7 +995,7 @@ class DefaultAssetPickerBuilderDelegate
990995
},
991996
),
992997
),
993-
Semantics(sortKey: const OrdinalSortKey(0), child: appBar(context)),
998+
appBar(context),
994999
],
9951000
);
9961001
}
@@ -1003,7 +1008,7 @@ class DefaultAssetPickerBuilderDelegate
10031008
}
10041009
return Semantics(
10051010
excludeSemantics: true,
1006-
sortKey: const OrdinalSortKey(1),
1011+
sortKey: const OrdinalSortKey(1, name: _ordinalNamePermissionOverlay),
10071012
child: child,
10081013
);
10091014
},
@@ -1263,59 +1268,70 @@ class DefaultAssetPickerBuilderDelegate
12631268
AssetEntity asset,
12641269
Widget child,
12651270
) {
1266-
return Consumer<DefaultAssetPickerProvider>(
1267-
child: child,
1268-
builder: (_, DefaultAssetPickerProvider p, Widget? child) {
1269-
final bool isBanned =
1270-
(!p.selectedAssets.contains(asset) && p.selectedMaximumAssets) ||
1271+
return ValueListenableBuilder<bool>(
1272+
valueListenable: isSwitchingPath,
1273+
builder: (_, bool isSwitchingPath, Widget? child) {
1274+
return Consumer<DefaultAssetPickerProvider>(
1275+
builder: (_, DefaultAssetPickerProvider p, __) {
1276+
final bool isBanned = (!p.selectedAssets.contains(asset) &&
1277+
p.selectedMaximumAssets) ||
12711278
(isWeChatMoment &&
12721279
asset.type == AssetType.video &&
12731280
p.selectedAssets.isNotEmpty);
1274-
final bool isSelected = p.selectedDescriptions.contains(
1275-
asset.toString(),
1276-
);
1277-
final int selectedIndex = p.selectedAssets.indexOf(asset) + 1;
1278-
String hint = '';
1279-
if (asset.type == AssetType.audio || asset.type == AssetType.video) {
1280-
hint += '${textDelegate.sNameDurationLabel}: ';
1281-
hint += textDelegate.durationIndicatorBuilder(asset.videoDuration);
1282-
}
1283-
if (asset.title?.isNotEmpty == true) {
1284-
hint += ', ${asset.title}';
1285-
}
1286-
return Semantics(
1287-
button: false,
1288-
enabled: !isBanned,
1289-
excludeSemantics: true,
1290-
focusable: !isSwitchingPath.value,
1291-
label: '${textDelegate.semanticTypeLabel(asset.type)}'
1292-
'${semanticIndex(index)}, '
1293-
'${asset.createDateTime.toString().replaceAll('.000', '')}',
1294-
hidden: isSwitchingPath.value,
1295-
hint: hint,
1296-
image: asset.type == AssetType.image || asset.type == AssetType.video,
1297-
onTap: () => selectAsset(context, asset, isSelected),
1298-
onTapHint: textDelegate.sActionSelectHint,
1299-
onLongPress: isPreviewEnabled
1300-
? () => _pushAssetToViewer(context, index, asset)
1301-
: null,
1302-
onLongPressHint: textDelegate.sActionPreviewHint,
1303-
selected: isSelected,
1304-
sortKey: OrdinalSortKey(
1305-
semanticIndex(index).toDouble(),
1306-
name: 'GridItem',
1307-
),
1308-
value: selectedIndex > 0 ? '$selectedIndex' : null,
1309-
child: GestureDetector(
1310-
// Regression https://github.com/flutter/flutter/issues/35112.
1311-
onLongPress:
1312-
isPreviewEnabled && context.mediaQuery.accessibleNavigation
1313-
? () => _pushAssetToViewer(context, index, asset)
1314-
: null,
1315-
child: IndexedSemantics(index: semanticIndex(index), child: child),
1316-
),
1281+
final bool isSelected = p.selectedDescriptions.contains(
1282+
asset.toString(),
1283+
);
1284+
final int selectedIndex = p.selectedAssets.indexOf(asset) + 1;
1285+
String hint = '';
1286+
if (asset.type == AssetType.audio ||
1287+
asset.type == AssetType.video) {
1288+
hint += '${textDelegate.sNameDurationLabel}: ';
1289+
hint +=
1290+
textDelegate.durationIndicatorBuilder(asset.videoDuration);
1291+
}
1292+
if (asset.title?.isNotEmpty == true) {
1293+
hint += ', ${asset.title}';
1294+
}
1295+
return Semantics(
1296+
button: false,
1297+
enabled: !isBanned,
1298+
excludeSemantics: true,
1299+
focusable: !isSwitchingPath,
1300+
label: '${textDelegate.semanticTypeLabel(asset.type)}'
1301+
'${semanticIndex(index)}, '
1302+
'${asset.createDateTime.toString().replaceAll('.000', '')}',
1303+
hidden: isSwitchingPath,
1304+
hint: hint,
1305+
image: asset.type == AssetType.image ||
1306+
asset.type == AssetType.video,
1307+
onTap: () => selectAsset(context, asset, isSelected),
1308+
onTapHint: textDelegate.sActionSelectHint,
1309+
onLongPress: isPreviewEnabled
1310+
? () => _pushAssetToViewer(context, index, asset)
1311+
: null,
1312+
onLongPressHint: textDelegate.sActionPreviewHint,
1313+
selected: isSelected,
1314+
sortKey: OrdinalSortKey(
1315+
semanticIndex(index).toDouble(),
1316+
name: 'GridItem',
1317+
),
1318+
value: selectedIndex > 0 ? '$selectedIndex' : null,
1319+
child: GestureDetector(
1320+
// Regression https://github.com/flutter/flutter/issues/35112.
1321+
onLongPress:
1322+
isPreviewEnabled && context.mediaQuery.accessibleNavigation
1323+
? () => _pushAssetToViewer(context, index, asset)
1324+
: null,
1325+
child: IndexedSemantics(
1326+
index: semanticIndex(index),
1327+
child: child,
1328+
),
1329+
),
1330+
);
1331+
},
13171332
);
13181333
},
1334+
child: child,
13191335
);
13201336
}
13211337

@@ -1553,9 +1569,7 @@ class DefaultAssetPickerBuilderDelegate
15531569
child: ValueListenableBuilder<bool>(
15541570
valueListenable: isSwitchingPath,
15551571
builder: (_, bool isSwitchingPath, Widget? child) => Semantics(
1556-
focusable: isSwitchingPath,
1557-
sortKey: const OrdinalSortKey(1),
1558-
hidden: !isSwitchingPath,
1572+
hidden: isSwitchingPath ? null : true,
15591573
child: AnimatedAlign(
15601574
duration: switchingPathDuration,
15611575
curve: switchingPathCurve,

lib/src/widget/asset_picker_app_bar.dart

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'dart:ui' as ui;
66

77
import 'package:flutter/material.dart';
8+
import 'package:flutter/semantics.dart';
89
import 'package:flutter/services.dart';
910

1011
/// A custom app bar.
@@ -26,6 +27,7 @@ class AssetPickerAppBar extends StatelessWidget implements PreferredSizeWidget {
2627
this.height,
2728
this.blurRadius = 0,
2829
this.iconTheme,
30+
this.semanticsBuilder,
2931
}) : super(key: key);
3032

3133
/// Title widget. Typically a [Text] widget.
@@ -82,6 +84,8 @@ class AssetPickerAppBar extends StatelessWidget implements PreferredSizeWidget {
8284

8385
final IconThemeData? iconTheme;
8486

87+
final Semantics Function(Widget appBar)? semanticsBuilder;
88+
8589
bool canPop(BuildContext context) =>
8690
Navigator.of(context).canPop() && automaticallyImplyLeading;
8791

@@ -180,8 +184,8 @@ class AssetPickerAppBar extends StatelessWidget implements PreferredSizeWidget {
180184
),
181185
);
182186

183-
// Wrap with a [Material] to ensure the child rendered correctly.
184-
return Material(
187+
final Widget _result = Material(
188+
// Wrap to ensure the child rendered correctly
185189
color: Color.lerp(
186190
backgroundColor ?? Theme.of(context).colorScheme.surface,
187191
Colors.transparent,
@@ -190,6 +194,8 @@ class AssetPickerAppBar extends StatelessWidget implements PreferredSizeWidget {
190194
elevation: elevation,
191195
child: child,
192196
);
197+
return semanticsBuilder?.call(_result) ??
198+
Semantics(sortKey: const OrdinalSortKey(0), child: _result);
193199
}
194200
}
195201

0 commit comments

Comments
 (0)