Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat(Mobile): move merged app repo changes #4709

Open
wants to merge 5 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ When contributing to this repository, please first discuss the change you wish t

Please note we have a Code of Conduct (see below), please follow it in all your interactions with the project.

## Code Style
clovisdasilvaneto marked this conversation as resolved.
Show resolved Hide resolved

More information [here](./docs/code-style.md)

## CLA

It is a requirement for all contributors to sign the [Contributor License Agreement (CLA)](https://safe.global/cla) in order to proceed with their contribution.
Expand Down
60 changes: 43 additions & 17 deletions apps/mobile/.gitignore
Original file line number Diff line number Diff line change
@@ -1,15 +1,3 @@
node_modules/
.expo/
dist/
npm-debug.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
*.orig.*
web-build/

# Auto generated storybook file
.storybook/storybook.requires.ts

Expand All @@ -20,11 +8,6 @@ coverage
# macOS
.DS_Store

# @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb
# The following patterns were generated by expo-cli

expo-env.d.ts
# @end expo-cli
/.idea
# Tamagui UI generates a lot of cache files
.tamagui
Expand All @@ -35,3 +18,46 @@ expo-env.d.ts
# Android and iOS build files
/android/*
/ios/*

# @generated expo-cli sync-8d4afeec25ea8a192358fae2f8e2fc766bdce4ec
# The following patterns were generated by expo-cli

# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files

# dependencies
node_modules/

# Expo
.expo/
dist/
web-build/
expo-env.d.ts

# Native
*.orig.*
*.
*.jks
*.p8
*.p12
*.key
*.mobileprovision

# Metro
.metro-health-check*

# debug
npm-debug.*
yarn-debug.*
yarn-error.*

# macOS
.DS_Store
*.pem

# local env files
.env*.local

# typescript
*.tsbuildinfo

# @end expo-cli
3 changes: 3 additions & 0 deletions apps/mobile/app.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ export default {
config: {
usesNonExemptEncryption: false,
},
infoPlist: {
NSFaceIDUsageDescription: 'Enabling Face ID allows you to create/access secure keys.',
},
supportsTablet: true,
appleTeamId: 'MXRS32BBL4',
bundleIdentifier: IS_DEV ? 'global.safe.mobileapp.dev' : 'global.safe.mobileapp',
Expand Down
19 changes: 11 additions & 8 deletions apps/mobile/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import { PortalProvider } from '@tamagui/portal'
import { SafeToastProvider } from '@/src/theme/provider/toastProvider'
import { configureReanimatedLogger, ReanimatedLogLevel } from 'react-native-reanimated'
import { OnboardingHeader } from '@/src/features/Onboarding/components/OnboardingHeader'
import { install } from 'react-native-quick-crypto'

install()

configureReanimatedLogger({
level: ReanimatedLogLevel.warn,
Expand All @@ -23,10 +26,10 @@ function RootLayout() {
store.dispatch(apiSliceWithChainsConfig.endpoints.getChainsConfig.initiate())

return (
<PortalProvider shouldAddRootHost>
<GestureHandlerRootView>
<BottomSheetModalProvider>
<Provider store={store}>
<Provider store={store}>
<PortalProvider shouldAddRootHost>
<GestureHandlerRootView>
<BottomSheetModalProvider>
<PersistGate loading={null} persistor={persistor}>
<SafeThemeProvider>
<SafeToastProvider>
Expand Down Expand Up @@ -60,10 +63,10 @@ function RootLayout() {
</SafeToastProvider>
</SafeThemeProvider>
</PersistGate>
</Provider>
</BottomSheetModalProvider>
</GestureHandlerRootView>
</PortalProvider>
</BottomSheetModalProvider>
</GestureHandlerRootView>
</PortalProvider>
</Provider>
)
}

Expand Down
8 changes: 8 additions & 0 deletions apps/mobile/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { registerRootComponent } from 'expo';

import App from './App';

// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in Expo Go or in a native build,
// the environment is set up appropriately
registerRootComponent(App);
5 changes: 5 additions & 0 deletions apps/mobile/metro.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ config.resolver.resolveRequest = (context, moduleName, platform) => {
}
}

if (moduleName === 'crypto') {
// when importing crypto, resolve to react-native-quick-crypto
return context.resolveRequest(context, 'react-native-quick-crypto', platform)
}

return defaultResolveResult
}

Expand Down
6 changes: 6 additions & 0 deletions apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
},
"dependencies": {
"@cowprotocol/app-data": "^2.3.0",
"@ethersproject/shims": "^5.7.0",
"@expo/config-plugins": "^9.0.10",
"@expo/vector-icons": "^14.0.2",
"@react-native-clipboard/clipboard": "^1.15.0",
Expand All @@ -60,6 +61,7 @@
"burnt": "^0.12.2",
"date-fns": "^4.1.0",
"deepmerge": "^4.3.1",
"ethers": "^6.13.4",
"expo": "~52.0.14",
"expo-blur": "~14.0.1",
"expo-constants": "~17.0.2",
Expand All @@ -79,9 +81,13 @@
"react-dom": "^18.3.1",
"react-native": "0.76.3",
"react-native-collapsible-tab-view": "^8.0.0",
"react-native-device-crypto": "^0.1.7",
"react-native-device-info": "^14.0.1",
"react-native-gesture-handler": "~2.20.2",
"react-native-keychain": "^9.2.2",
"react-native-mmkv": "^3.1.0",
"react-native-pager-view": "6.5.1",
"react-native-quick-crypto": "^0.7.10",
"react-native-reanimated": "^3.16.2",
"react-native-safe-area-context": "4.12.0",
"react-native-screens": "^4.0.0",
Expand Down
5 changes: 4 additions & 1 deletion apps/mobile/src/components/Badge/Badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ interface BadgeProps {
circleProps?: Partial<CircleProps>
textContentProps?: Partial<TextProps>
circular?: boolean
testID?: string
}

export const Badge = ({
Expand All @@ -25,6 +26,7 @@ export const Badge = ({
circular = true,
circleProps,
textContentProps,
testID,
}: BadgeProps) => {
let contentToRender = content
if (typeof content === 'string') {
Expand All @@ -38,7 +40,7 @@ export const Badge = ({
if (circular) {
return (
<Theme name={themeName}>
<Circle size={circleSize} backgroundColor={'$background'} {...circleProps}>
<Circle testID={testID} size={circleSize} backgroundColor={'$background'} {...circleProps}>
{contentToRender}
</Circle>
</Theme>
Expand All @@ -47,6 +49,7 @@ export const Badge = ({
return (
<Theme name={themeName}>
<View
testID={testID}
alignSelf={'flex-start'}
paddingVertical="$1"
paddingHorizontal="$3"
Expand Down
8 changes: 8 additions & 0 deletions apps/mobile/src/components/Badge/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,12 @@ export const badgeTheme = {
color: tokens.color.warning1ContrastTextDark,
background: tokens.color.warningDarkDark,
},
dark_badge_background: {
color: tokens.color.textPrimaryDark,
background: tokens.color.logoBackgroundDark,
},
light_badge_background: {
color: tokens.color.textPrimaryLight,
background: tokens.color.logoBackgroundLight,
},
}
45 changes: 45 additions & 0 deletions apps/mobile/src/components/ChainsDisplay/ChainsDisplay.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { Meta, StoryObj } from '@storybook/react'
import { ChainsDisplay } from '@/src/components/ChainsDisplay'
import { mockedChains } from '@/src/store/constants'
import { Chain } from '@safe-global/store/gateway/AUTO_GENERATED/chains'

const meta: Meta<typeof ChainsDisplay> = {
title: 'ChainsDisplay',
component: ChainsDisplay,
argTypes: {},
}

export default meta

type Story = StoryObj<typeof ChainsDisplay>

export const Default: Story = {
args: {
chains: mockedChains as unknown as Chain[],
max: 3,
},
parameters: {
layout: 'fullscreen',
},
}

export const Truncated: Story = {
args: {
chains: mockedChains as unknown as Chain[],
max: 1,
},
parameters: {
layout: 'fullscreen',
},
}

export const ActiveChain: Story = {
args: {
chains: mockedChains as unknown as Chain[],
activeChainId: mockedChains[1].chainId,
max: 1,
},
parameters: {
layout: 'fullscreen',
},
}
30 changes: 30 additions & 0 deletions apps/mobile/src/components/ChainsDisplay/ChainsDisplay.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { mockedChains } from '@/src/store/constants'
import { ChainsDisplay } from './ChainsDisplay'
import { render } from '@testing-library/react-native'
import { Chain } from '@safe-global/store/gateway/AUTO_GENERATED/chains'

describe('ChainsDisplay', () => {
it('should render all chains next each other', () => {
const container = render(<ChainsDisplay chains={mockedChains as unknown as Chain[]} max={mockedChains.length} />)

expect(container.getAllByTestId('chain-display')).toHaveLength(3)
})
it('should truncate the chains when the provided chains length is greatter than the max', () => {
const container = render(<ChainsDisplay chains={mockedChains as unknown as Chain[]} max={2} />)
const moreChainsBadge = container.getByTestId('more-chains-badge')

expect(container.getAllByTestId('chain-display')).toHaveLength(2)
expect(moreChainsBadge).toBeVisible()
expect(moreChainsBadge).toHaveTextContent('+1')
})

it('should always show the selected chain as the first column of the row', () => {
const container = render(
<ChainsDisplay chains={mockedChains as unknown as Chain[]} max={2} activeChainId={mockedChains[2].chainId} />,
)

expect(container.getAllByTestId('chain-display')[0].children[0].props.accessibilityLabel).toBe(
mockedChains[2].chainName,
)
})
})
34 changes: 34 additions & 0 deletions apps/mobile/src/components/ChainsDisplay/ChainsDisplay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Chain } from '@safe-global/store/gateway/AUTO_GENERATED/chains'
import React, { useMemo } from 'react'
import { View } from 'tamagui'
import { Logo } from '../Logo'
import { Badge } from '../Badge'

interface ChainsDisplayProps {
chains: Chain[]
max?: number
activeChainId?: string
}

export function ChainsDisplay({ chains, activeChainId, max }: ChainsDisplayProps) {
const orderedChains = useMemo(
() => [...chains].sort((a, b) => (a.chainId === activeChainId ? -1 : b.chainId === activeChainId ? 1 : 0)),
[chains],
)
const slicedChains = max ? orderedChains.slice(0, max) : chains
const showBadge = max && chains.length > max

return (
<View flexDirection="row">
{slicedChains.map(({ chainLogoUri, chainName, chainId }, index) => (
<View key={chainId} testID="chain-display" marginRight={(showBadge || index !== slicedChains.length - 1) && -8}>
<Logo size="$7" logoUri={chainLogoUri} accessibilityLabel={chainName} />
</View>
))}

{showBadge && (
<Badge testID="more-chains-badge" content={`+${chains.length - max}`} themeName="badge_background" />
)}
</View>
)
}
1 change: 1 addition & 0 deletions apps/mobile/src/components/ChainsDisplay/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ChainsDisplay } from './ChainsDisplay'
Loading
Loading