Skip to content

Commit 3c59b56

Browse files
javiergarciaveraclaudemetamaskbot
authored
perf(accessibility): fix iOS accessibility in Predict components and … (#29122)
…Homepage section - PredictMarketRowItem, PredictMarketCard, PredictPositionRow, PredictActivity: Pressable → TouchableOpacity - PredictMarketDetails (and sub-components): fix view nesting for iOS XCUITest element lookup - PredictDetailsChart, TimeframeSelector: align accessibility props - PredictionsSection (Homepage): remove redundant Box wrappers, –1 native node <!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until this PR meets the canonical Definition of Ready For Review in `docs/readme/ready-for-review.md`. In short: the template must be materially complete (not just section titles present), all status checks must be currently passing, and the only expected follow-up commits must be reviewer-driven. --> ## **Description** <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** <!-- Every checklist item must be consciously assessed before marking this PR as "Ready for review". A checked box means you deliberately considered that responsibility, not that you literally performed every action listed. Unchecked boxes are ambiguous: they are not an implicit "N/A" and they are not a silent "skip". See `docs/readme/ready-for-review.md` for the full checklist semantics. --> - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I've included tests if applicable - [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. #### Performance checks (if applicable) - [ ] I've tested on Android - Ideally on a mid-range device; emulator is acceptable - [ ] I've tested with a power user scenario - Use these [power-user SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93) to import wallets with many accounts and tokens - [ ] I've instrumented key operations with Sentry traces for production performance metrics - See [`trace()`](/app/util/trace.ts) for usage and [`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274) for an example For performance guidelines and tooling, see the [Performance Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers). ## **Pre-merge reviewer checklist** <!-- Reviewer checklist items follow the same semantics as the author checklist: an unchecked box is ambiguous, a checked box means the reviewer consciously assessed that responsibility. See `docs/readme/ready-for-review.md`. --> - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Primarily refactors layout/styling and view nesting to improve iOS accessibility/XCUITest element lookup, with minor navigation and rendering-guard tweaks; functional risk is low but UI regressions are possible. > > **Overview** > Improves Predict and Homepage Predictions *iOS accessibility/test reliability* by simplifying view hierarchies and reducing unnecessary wrapper nodes (e.g., switching several layouts to direct `TouchableOpacity` styling, removing extra `Box`/`View` nesting, and returning `null` instead of empty containers). > > Adjusts Predict Market Details header/actions composition (padding moved into components; actions now self-wrap with bottom border/padding) and fixes a wallet back-navigation target. Adds a new Predict E2E selector prefix (`TRENDING_MARKET_CARD`) and extends `PredictionsSection` tests to ensure the header and unrealized PnL row aren’t duplicated/shown when the trending carousel renders above positions. > > Bumps build/version numbers in `bitrise.yml` and the iOS Xcode project (`CURRENT_PROJECT_VERSION` to `4823`). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 6aa6fca. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: metamaskbot <metamaskbot@users.noreply.github.com>
1 parent b701393 commit 3c59b56

21 files changed

Lines changed: 529 additions & 569 deletions

File tree

app/components/UI/Predict/Predict.testIds.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export const PredictMarketListSelectorsIDs = {
3131
CRYPTO_TAB: 'predict-market-list-crypto-tab',
3232
POLITICS_TAB: 'predict-market-list-politics-tab',
3333
BACK_BUTTON: 'back-button',
34+
TRENDING_MARKET_CARD: 'predict-market-list-trending-card-',
3435
// Empty state
3536
EMPTY_STATE: 'predict-market-list-empty-state',
3637
} as const;

app/components/UI/Predict/components/PredictActivity/PredictActivity.tsx

Lines changed: 31 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@ import { TouchableOpacity } from 'react-native';
33
import { useTailwind } from '@metamask/design-system-twrnc-preset';
44
import {
55
Box,
6-
BoxAlignItems,
7-
BoxFlexDirection,
8-
BoxJustifyContent,
96
Icon,
107
IconName,
118
Text,
@@ -76,46 +73,40 @@ const PredictActivity: React.FC<PredictActivityProps> = ({ item }) => {
7673
};
7774

7875
return (
79-
<TouchableOpacity onPress={handlePress}>
80-
<Box
81-
flexDirection={BoxFlexDirection.Row}
82-
alignItems={BoxAlignItems.Start}
83-
justifyContent={BoxJustifyContent.Between}
84-
twClassName="w-full p-2"
85-
>
86-
<Box twClassName="pt-1">
87-
<Box twClassName="h-10 w-10 items-center justify-center rounded-full bg-muted mr-3 overflow-hidden">
88-
{item.icon ? (
89-
<Image
90-
source={{ uri: item.icon }}
91-
style={tw.style('w-full h-full')}
92-
accessibilityLabel="activity icon"
93-
/>
94-
) : (
95-
<Icon name={IconName.Activity} />
96-
)}
97-
</Box>
98-
</Box>
76+
<TouchableOpacity
77+
onPress={handlePress}
78+
style={tw.style('flex-row items-start justify-between w-full p-2')}
79+
>
80+
<Box twClassName="h-10 w-10 items-center justify-center rounded-full bg-muted mr-3 overflow-hidden mt-1">
81+
{item.icon ? (
82+
<Image
83+
source={{ uri: item.icon }}
84+
style={tw.style('w-full h-full')}
85+
accessibilityLabel="activity icon"
86+
/>
87+
) : (
88+
<Icon name={IconName.Activity} />
89+
)}
90+
</Box>
9991

100-
<Box twClassName="flex-1">
101-
<Text variant={TextVariant.BodyMd} numberOfLines={1}>
102-
{activityTitleByType[item.type]}
103-
</Text>
104-
<Text variant={TextVariant.BodySm} twClassName="text-alternative">
105-
{item.marketTitle}
106-
</Text>
107-
</Box>
92+
<Box twClassName="flex-1">
93+
<Text variant={TextVariant.BodyMd} numberOfLines={1}>
94+
{activityTitleByType[item.type]}
95+
</Text>
96+
<Text variant={TextVariant.BodySm} twClassName="text-alternative">
97+
{item.marketTitle}
98+
</Text>
99+
</Box>
108100

109-
<Box twClassName="items-end ml-3">
110-
<Text variant={TextVariant.BodyMd} twClassName="text-alternative">
111-
{signedAmount}
101+
<Box twClassName="items-end ml-3">
102+
<Text variant={TextVariant.BodyMd} twClassName="text-alternative">
103+
{signedAmount}
104+
</Text>
105+
{item.percentChange !== undefined ? (
106+
<Text variant={TextVariant.BodySm} twClassName={percentColor}>
107+
{formatPercentage(item.percentChange)}
112108
</Text>
113-
{item.percentChange !== undefined ? (
114-
<Text variant={TextVariant.BodySm} twClassName={percentColor}>
115-
{formatPercentage(item.percentChange)}
116-
</Text>
117-
) : null}
118-
</Box>
109+
) : null}
119110
</Box>
120111
</TouchableOpacity>
121112
);

app/components/UI/Predict/components/PredictDetailsChart/PredictDetailsChart.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -457,11 +457,6 @@ const PredictDetailsChart: React.FC<PredictDetailsChartProps> = ({
457457
)}
458458
</Box>
459459
</Box>
460-
<Box
461-
flexDirection={BoxFlexDirection.Row}
462-
justifyContent={BoxJustifyContent.Between}
463-
twClassName="px-4 pt-4 pb-0 min-h-[31px]"
464-
/>
465460
</Box>
466461
);
467462
}
@@ -586,14 +581,14 @@ const PredictDetailsChart: React.FC<PredictDetailsChartProps> = ({
586581
};
587582

588583
return (
589-
<Box>
584+
<>
590585
{renderGraph()}
591586
<TimeframeSelector
592587
timeframes={timeframes}
593588
selectedTimeframe={selectedTimeframe}
594589
onTimeframeChange={onTimeframeChange}
595590
/>
596-
</Box>
591+
</>
597592
);
598593
};
599594

app/components/UI/Predict/components/PredictDetailsChart/components/TimeframeSelector.tsx

Lines changed: 24 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
Box,
66
BoxFlexDirection,
77
BoxAlignItems,
8-
BoxJustifyContent,
98
} from '@metamask/design-system-react-native';
109
import Text, {
1110
TextColor,
@@ -29,39 +28,33 @@ const TimeframeSelector: React.FC<TimeframeSelectorProps> = ({
2928
<Box
3029
flexDirection={BoxFlexDirection.Row}
3130
alignItems={BoxAlignItems.Center}
32-
justifyContent={BoxJustifyContent.Between}
31+
twClassName="gap-1"
3332
>
34-
<Box
35-
flexDirection={BoxFlexDirection.Row}
36-
alignItems={BoxAlignItems.Center}
37-
twClassName="gap-1"
38-
>
39-
{timeframes.map((timeframe) => (
40-
<Pressable
41-
key={timeframe}
42-
onPress={() => onTimeframeChange(timeframe)}
43-
style={({ pressed }) =>
44-
tw.style(
45-
'flex-1 py-2 rounded-lg',
46-
selectedTimeframe === timeframe ? 'bg-muted' : 'bg-default',
47-
pressed && 'bg-pressed',
48-
)
33+
{timeframes.map((timeframe) => (
34+
<Pressable
35+
key={timeframe}
36+
onPress={() => onTimeframeChange(timeframe)}
37+
style={({ pressed }) =>
38+
tw.style(
39+
'flex-1 py-2 rounded-lg',
40+
selectedTimeframe === timeframe ? 'bg-muted' : 'bg-default',
41+
pressed && 'bg-pressed',
42+
)
43+
}
44+
>
45+
<Text
46+
variant={TextVariant.BodySM}
47+
color={
48+
selectedTimeframe === timeframe
49+
? TextColor.Default
50+
: TextColor.Alternative
4951
}
52+
style={tw.style('text-center')}
5053
>
51-
<Text
52-
variant={TextVariant.BodySM}
53-
color={
54-
selectedTimeframe === timeframe
55-
? TextColor.Default
56-
: TextColor.Alternative
57-
}
58-
style={tw.style('text-center')}
59-
>
60-
{timeframe.toUpperCase()}
61-
</Text>
62-
</Pressable>
63-
))}
64-
</Box>
54+
{timeframe.toUpperCase()}
55+
</Text>
56+
</Pressable>
57+
))}
6558
</Box>
6659
);
6760
};

app/components/UI/Predict/components/PredictMarketOutcome/PredictMarketOutcome.styles.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,6 @@ const styleSheet = (params: { theme: Theme }) => {
1515
padding: 16,
1616
marginBottom: 16,
1717
},
18-
marketHeader: {
19-
flexDirection: 'row',
20-
justifyContent: 'space-between',
21-
alignItems: 'center',
22-
width: '100%',
23-
},
2418
marketFooter: {
2519
flexDirection: 'row',
2620
justifyContent: 'flex-end',

app/components/UI/Predict/components/PredictMarketOutcome/PredictMarketOutcome.tsx

Lines changed: 62 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -111,81 +111,74 @@ const PredictMarketOutcome: React.FC<PredictMarketOutcomeProps> = ({
111111

112112
return (
113113
<View style={styles.marketContainer}>
114-
<View style={styles.marketHeader}>
115-
<Box
116-
flexDirection={BoxFlexDirection.Row}
117-
alignItems={BoxAlignItems.Center}
118-
twClassName="flex-1 gap-3"
119-
>
120-
<Box twClassName="w-10 h-10 rounded-lg bg-muted overflow-hidden self-start">
121-
{getImageUrl() ? (
122-
<Image
123-
source={{ uri: getImageUrl() }}
124-
style={tw.style('w-full h-full')}
125-
resizeMode="cover"
126-
/>
127-
) : (
128-
<Box twClassName="w-full h-full bg-muted" />
129-
)}
130-
</Box>
131-
<Box twClassName="flex-1 -mt-1">
114+
<Box
115+
flexDirection={BoxFlexDirection.Row}
116+
alignItems={BoxAlignItems.Center}
117+
twClassName="w-full gap-3"
118+
>
119+
<Box twClassName="w-10 h-10 rounded-lg bg-muted overflow-hidden self-start">
120+
{getImageUrl() ? (
121+
<Image
122+
source={{ uri: getImageUrl() }}
123+
style={tw.style('w-full h-full')}
124+
resizeMode="cover"
125+
/>
126+
) : (
127+
<Box twClassName="w-full h-full bg-muted" />
128+
)}
129+
</Box>
130+
<Box twClassName="flex-1 -mt-1">
131+
<Text
132+
variant={TextVariant.BodyMd}
133+
color={TextColor.TextDefault}
134+
style={tw.style('font-medium')}
135+
>
136+
{getTitle()}
137+
</Text>
138+
<Text variant={TextVariant.BodySm} color={TextColor.TextAlternative}>
139+
${getVolumeDisplay()} {strings('predict.volume_abbreviated')}
140+
</Text>
141+
</Box>
142+
{isClosed && outcomeToken ? (
143+
<Box
144+
flexDirection={BoxFlexDirection.Row}
145+
alignItems={BoxAlignItems.Center}
146+
twClassName="gap-1"
147+
>
132148
<Text
133149
variant={TextVariant.BodyMd}
134-
color={TextColor.TextDefault}
135-
style={tw.style('font-medium')}
136-
>
137-
{getTitle()}
138-
</Text>
139-
<Text
140-
variant={TextVariant.BodySm}
141-
color={TextColor.TextAlternative}
150+
twClassName="font-medium"
151+
color={
152+
outcomeToken.price === 1
153+
? TextColor.TextDefault
154+
: TextColor.TextAlternative
155+
}
142156
>
143-
${getVolumeDisplay()} {strings('predict.volume_abbreviated')}
157+
{outcomeToken.price === 1
158+
? strings('predict.outcome_winner')
159+
: strings('predict.outcome_loser')}
144160
</Text>
145-
</Box>
146-
<Box>
147-
{isClosed && outcomeToken ? (
148-
<Box
149-
flexDirection={BoxFlexDirection.Row}
150-
alignItems={BoxAlignItems.Center}
151-
twClassName="gap-1"
152-
>
153-
<Text
154-
variant={TextVariant.BodyMd}
155-
twClassName="font-medium"
156-
color={
157-
outcomeToken.price === 1
158-
? TextColor.TextDefault
159-
: TextColor.TextAlternative
160-
}
161-
>
162-
{outcomeToken.price === 1
163-
? strings('predict.outcome_winner')
164-
: strings('predict.outcome_loser')}
165-
</Text>
166-
{outcomeToken.price === 1 && (
167-
<Icon
168-
name={IconName.Confirmation}
169-
size={IconSize.Md}
170-
color={
171-
outcomeToken.price === 1
172-
? TextColor.SuccessDefault
173-
: TextColor.TextMuted
174-
}
175-
/>
176-
)}
177-
</Box>
178-
) : (
179-
<Text
180-
style={tw.style('text-[20px] font-medium')}
181-
color={TextColor.TextDefault}
182-
>
183-
{getYesPercentage()}
184-
</Text>
161+
{outcomeToken.price === 1 && (
162+
<Icon
163+
name={IconName.Confirmation}
164+
size={IconSize.Md}
165+
color={
166+
outcomeToken.price === 1
167+
? TextColor.SuccessDefault
168+
: TextColor.TextMuted
169+
}
170+
/>
185171
)}
186172
</Box>
187-
</Box>
188-
</View>
173+
) : (
174+
<Text
175+
style={tw.style('text-[20px] font-medium')}
176+
color={TextColor.TextDefault}
177+
>
178+
{getYesPercentage()}
179+
</Text>
180+
)}
181+
</Box>
189182
{!isClosed && (
190183
<View style={styles.buttonContainer}>
191184
<Button

0 commit comments

Comments
 (0)