Skip to content

Commit 0435195

Browse files
authored
Merge branch 'main' into handle-deeplinks-in-boost-card
2 parents 0783fb4 + 43c399b commit 0435195

File tree

168 files changed

+17416
-2515
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

168 files changed

+17416
-2515
lines changed

.github/workflows/build-android-e2e.yml

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,14 @@ jobs:
6464

6565
- name: Cache Gradle dependencies
6666
uses: cirruslabs/cache@v4
67+
id: gradle-cache-restore
68+
env:
69+
GRADLE_CACHE_VERSION: 1
6770
with:
6871
path: |
69-
/home/runner/_work/.gradle/caches
70-
/home/runner/_work/.gradle/wrapper
71-
/home/admin/_work/.gradle/caches
72-
/home/admin/_work/.gradle/wrapper
73-
android/.gradle
74-
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
75-
restore-keys: |
76-
gradle-${{ runner.os }}-
72+
~/_work/.gradle/caches
73+
~/_work/.gradle/wrapper
74+
key: gradle-${{ env.GRADLE_CACHE_VERSION }}-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
7775

7876
- name: Setup project dependencies with retry
7977
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2
@@ -116,7 +114,7 @@ jobs:
116114
fi
117115
118116
- name: Check and restore cached APKs if Fingerprint is found
119-
id: cache-restore
117+
id: apk-cache-restore
120118
uses: cirruslabs/cache@v4
121119
with:
122120
path: |
@@ -129,7 +127,7 @@ jobs:
129127
android-apk-
130128
131129
- name: Build Android E2E APKs
132-
if: ${{ steps.cache-restore.outputs.cache-hit != 'true' }}
130+
if: ${{ steps.apk-cache-restore.outputs.cache-hit != 'true' || steps.gradle-cache-restore.outputs.cache-hit != 'true' }}
133131
run: |
134132
echo "🏗 Building Android E2E APKs..."
135133
export NODE_OPTIONS="--max-old-space-size=8192"
@@ -180,7 +178,7 @@ jobs:
180178
MM_INFURA_PROJECT_ID: ${{ secrets.MM_INFURA_PROJECT_ID }}
181179

182180
- name: Repack APK with JS updates using @expo/repack-app
183-
if: ${{ steps.cache-restore.outputs.cache-hit == 'true' }}
181+
if: ${{ steps.apk-cache-restore.outputs.cache-hit == 'true' && steps.gradle-cache-restore.outputs.cache-hit == 'true' }}
184182
run: |
185183
echo "📦 Repacking APK with updated JavaScript bundle using @expo/repack-app..."
186184
# Use the optimized repack script which uses @expo/repack-app
@@ -231,7 +229,7 @@ jobs:
231229

232230
# Cache build artifacts with the pre-build fingerprint
233231
- name: Cache build artifacts
234-
if: ${{ steps.cache-restore.outputs.cache-hit != 'true' }}
232+
if: ${{ steps.apk-cache-restore.outputs.cache-hit != 'true' || steps.gradle-cache-restore.outputs.cache-hit != 'true' }}
235233
uses: cirruslabs/cache@v4
236234
with:
237235
path: |

app/components/UI/Assets/components/Balance/AccountGroupBalance.test.tsx

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,38 @@ jest.mock('../../../../../selectors/assets/balances', () => ({
99
selectBalanceBySelectedAccountGroup: jest.fn(() => null),
1010
// This one is a factory: selectBalanceChangeBySelectedAccountGroup(period) -> (state) => value
1111
selectBalanceChangeBySelectedAccountGroup: jest.fn(() => () => null),
12+
// This selector is used to display the BalanceEmptyState
13+
selectAccountGroupBalanceForEmptyState: jest.fn(() => null),
14+
}));
15+
16+
// Mock homepage redesign feature flag for BalanceEmptyState
17+
jest.mock('../../../../../selectors/featureFlagController/homepage', () => ({
18+
selectHomepageRedesignV1Enabled: jest.fn(() => true),
19+
}));
20+
21+
// This selector is used to determine if the current network is a testnet for BalanceEmptyState display logic
22+
jest.mock('../../../../../selectors/networkController', () => ({
23+
...jest.requireActual('../../../../../selectors/networkController'),
24+
selectEvmChainId: jest.fn(() => '0x1'), // Ethereum mainnet (not a testnet)
25+
selectChainId: jest.fn(() => '0x1'), // BalanceEmptyState also needs this
26+
}));
27+
28+
// Mock navigation hooks used by BalanceEmptyState
29+
jest.mock('@react-navigation/native', () => ({
30+
...jest.requireActual('@react-navigation/native'),
31+
useNavigation: () => ({
32+
navigate: jest.fn(),
33+
goBack: jest.fn(),
34+
reset: jest.fn(),
35+
}),
36+
}));
37+
38+
// Mock metrics hook used by BalanceEmptyState
39+
jest.mock('../../../../../components/hooks/useMetrics', () => ({
40+
useMetrics: () => ({
41+
trackEvent: jest.fn(),
42+
createEventBuilder: jest.fn(() => ({ record: jest.fn() })),
43+
}),
1244
}));
1345

1446
const testState = {
@@ -52,4 +84,36 @@ describe('AccountGroupBalance', () => {
5284
const el = getByTestId(WalletViewSelectorsIDs.TOTAL_BALANCE_TEXT);
5385
expect(el).toBeTruthy();
5486
});
87+
88+
it('renders empty state when account group balance is zero', () => {
89+
const {
90+
selectAccountGroupBalanceForEmptyState,
91+
selectBalanceBySelectedAccountGroup,
92+
} = jest.requireMock('../../../../../selectors/assets/balances');
93+
94+
// Mock the regular balance selector to return data (prevents skeleton loader)
95+
(selectBalanceBySelectedAccountGroup as jest.Mock).mockImplementation(
96+
() => ({
97+
totalBalanceInUserCurrency: 100, // Some non-zero amount for current network
98+
userCurrency: 'usd',
99+
}),
100+
);
101+
102+
// Mock the empty state selector to return zero balance across all mainnet networks
103+
(selectAccountGroupBalanceForEmptyState as jest.Mock).mockImplementation(
104+
() => ({
105+
totalBalanceInUserCurrency: 0, // Zero across all mainnet networks
106+
userCurrency: 'usd',
107+
}),
108+
);
109+
110+
const { getByTestId } = renderWithProvider(<AccountGroupBalance />, {
111+
state: testState,
112+
});
113+
114+
const el = getByTestId(
115+
WalletViewSelectorsIDs.BALANCE_EMPTY_STATE_CONTAINER,
116+
);
117+
expect(el).toBeOnTheScreen();
118+
});
55119
});

app/components/UI/Assets/components/Balance/AccountGroupBalance.tsx

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ import { selectPrivacyMode } from '../../../../../selectors/preferencesControlle
77
import {
88
selectBalanceBySelectedAccountGroup,
99
selectBalanceChangeBySelectedAccountGroup,
10+
selectAccountGroupBalanceForEmptyState,
1011
} from '../../../../../selectors/assets/balances';
12+
import { selectHomepageRedesignV1Enabled } from '../../../../../selectors/featureFlagController/homepage';
13+
import { selectEvmChainId } from '../../../../../selectors/networkController';
14+
import { TEST_NETWORK_IDS } from '../../../../../constants/network';
1115
import SensitiveText, {
1216
SensitiveTextLength,
1317
} from '../../../../../component-library/components/Texts/SensitiveText';
@@ -16,16 +20,24 @@ import { WalletViewSelectorsIDs } from '../../../../../../e2e/selectors/wallet/W
1620
import { Skeleton } from '../../../../../component-library/components/Skeleton';
1721
import { useFormatters } from '../../../../hooks/useFormatters';
1822
import AccountGroupBalanceChange from '../../components/BalanceChange/AccountGroupBalanceChange';
23+
import BalanceEmptyState from '../../../BalanceEmptyState';
1924

2025
const AccountGroupBalance = () => {
2126
const { PreferencesController } = Engine.context;
2227
const styles = createStyles();
2328
const { formatCurrency } = useFormatters();
2429
const privacyMode = useSelector(selectPrivacyMode);
2530
const groupBalance = useSelector(selectBalanceBySelectedAccountGroup);
31+
const accountGroupBalance = useSelector(
32+
selectAccountGroupBalanceForEmptyState,
33+
);
2634
const balanceChange1d = useSelector(
2735
selectBalanceChangeBySelectedAccountGroup('1d'),
2836
);
37+
const isHomepageRedesignV1Enabled = useSelector(
38+
selectHomepageRedesignV1Enabled,
39+
);
40+
const selectedChainId = useSelector(selectEvmChainId);
2941

3042
const togglePrivacy = useCallback(
3143
(value: boolean) => {
@@ -38,10 +50,30 @@ const AccountGroupBalance = () => {
3850
const userCurrency = groupBalance?.userCurrency ?? '';
3951
const displayBalance = formatCurrency(totalBalance, userCurrency);
4052

53+
// Check if account group balance (across all mainnet networks) is zero for empty state
54+
const hasZeroAccountGroupBalance =
55+
accountGroupBalance && accountGroupBalance.totalBalanceInUserCurrency === 0;
56+
57+
// Check if current network is a testnet
58+
const isCurrentNetworkTestnet = TEST_NETWORK_IDS.includes(selectedChainId);
59+
60+
// Show empty state on accounts with an aggregated mainnet balance of zero
61+
const shouldShowEmptyState =
62+
hasZeroAccountGroupBalance &&
63+
isHomepageRedesignV1Enabled &&
64+
!isCurrentNetworkTestnet;
65+
4166
return (
4267
<View style={styles.accountGroupBalance}>
4368
<View>
44-
{groupBalance ? (
69+
{!groupBalance ? (
70+
<View style={styles.skeletonContainer}>
71+
<Skeleton width={100} height={40} />
72+
<Skeleton width={100} height={20} />
73+
</View>
74+
) : shouldShowEmptyState ? (
75+
<BalanceEmptyState testID="account-group-balance-empty-state" />
76+
) : (
4577
<TouchableOpacity
4678
onPress={() => togglePrivacy(!privacyMode)}
4779
testID="balance-container"
@@ -66,11 +98,6 @@ const AccountGroupBalance = () => {
6698
/>
6799
)}
68100
</TouchableOpacity>
69-
) : (
70-
<View style={styles.skeletonContainer}>
71-
<Skeleton width={100} height={40} />
72-
<Skeleton width={100} height={20} />
73-
</View>
74101
)}
75102
</View>
76103
</View>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { StyleSheet } from 'react-native';
2+
import type { Theme } from '../../../../../util/theme/models';
3+
4+
export const createStyles = (_theme: Theme) =>
5+
StyleSheet.create({
6+
contentContainer: {
7+
paddingHorizontal: 16,
8+
paddingVertical: 16,
9+
},
10+
loadingContainer: {
11+
paddingVertical: 32,
12+
alignItems: 'center',
13+
justifyContent: 'center',
14+
},
15+
loadingText: {
16+
marginTop: 12,
17+
},
18+
emptyContainer: {
19+
paddingVertical: 32,
20+
paddingHorizontal: 16,
21+
alignItems: 'center',
22+
justifyContent: 'center',
23+
},
24+
footerContainer: {
25+
paddingHorizontal: 16,
26+
},
27+
});

0 commit comments

Comments
 (0)