Skip to content

Commit fd72ead

Browse files
Add interactions
1 parent 6e91115 commit fd72ead

File tree

7 files changed

+547
-85
lines changed

7 files changed

+547
-85
lines changed

apps/wallet-mobile/src/features/Settings/SettingsScreenNavigator.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import {ManageCollateralScreen} from './useCases/changeWalletSettings/ManageColl
4343
import {RemoveWalletScreen} from './useCases/changeWalletSettings/RemoveWallet'
4444
import {RenameWalletScreen} from './useCases/changeWalletSettings/RenameWalletScreen/RenameWalletScreen'
4545
import {WalletSettingsScreen} from './useCases/changeWalletSettings/WalletSettingsScreen'
46+
import {ManageNotificationDisplayDurationScreen} from './useCases/changeWalletSettings/ManageNotificationDisplayDuration/ManageNotificationDisplayDurationScreen'
4647

4748
const Stack = createStackNavigator<SettingsStackRoutes>()
4849
export const SettingsScreenNavigator = () => {
@@ -196,7 +197,7 @@ export const SettingsScreenNavigator = () => {
196197
options={{
197198
title: strings.displayDuration,
198199
}}
199-
component={ManageCollateralScreen}
200+
component={ManageNotificationDisplayDurationScreen}
200201
/>
201202

202203
<Stack.Screen
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
import {ScrollView, StyleSheet, Text, TouchableOpacity, View} from 'react-native'
2+
import * as React from 'react'
3+
import {useTheme} from '@yoroi/theme'
4+
import {KeyboardAvoidingView} from '../../../../../components/KeyboardAvoidingView/KeyboardAvoidingView'
5+
import {SafeAreaView} from 'react-native-safe-area-context'
6+
import {TextInput} from '../../../../../components/TextInput/TextInput'
7+
import {Button} from '../../../../../components/Button/Button'
8+
import {useFormatNumber} from '../../../../../kernel/i18n'
9+
import {parseNumber} from '@yoroi/common'
10+
import {useStrings} from './strings'
11+
12+
type ManualChoice = {
13+
id: 'Manual'
14+
}
15+
16+
type GivenChoice = {
17+
id: '2s' | '4s' | '6s' | '8s' | '10s' | '12s'
18+
value: number
19+
}
20+
21+
type Choice = ManualChoice | GivenChoice
22+
23+
type ChoiceKind = Choice['id']
24+
25+
const CHOICES: Readonly<Choice[]> = [
26+
{id: '2s', value: 2},
27+
{id: '4s', value: 4},
28+
{id: '6s', value: 6},
29+
{id: '8s', value: 8},
30+
{id: '10s', value: 10},
31+
{id: '12s', value: 12},
32+
{id: 'Manual'},
33+
] as const
34+
35+
const defaultChoice = CHOICES[1]
36+
37+
export const ManageNotificationDisplayDurationScreen = ({}) => {
38+
const {styles, colors} = useStyles()
39+
const formatNumber = useFormatNumber()
40+
41+
const strings = useStrings()
42+
const [selectedChoiceId, setSelectedChoiceId] = React.useState<ChoiceKind>(defaultChoice.id)
43+
const selectedChoice = getChoiceById(selectedChoiceId)
44+
const [inputValue, setInputValue] = React.useState('')
45+
46+
const isSelectedChoiceManual = selectedChoiceId === 'Manual'
47+
const isInputEnabled = isSelectedChoiceManual
48+
const hasError = isSelectedChoiceManual && !isInputValid(inputValue)
49+
const isButtonDisabled = hasError || (isSelectedChoiceManual && inputValue === '')
50+
51+
const handleChoicePress = (id: ChoiceKind) => {
52+
setSelectedChoiceId(id)
53+
}
54+
55+
const handleInputChange = (text: string) => {
56+
setInputValue(text)
57+
}
58+
59+
const handleSubmit = async () => {}
60+
61+
return (
62+
<KeyboardAvoidingView style={[styles.flex, styles.root]}>
63+
<SafeAreaView edges={['bottom', 'left', 'right']} style={[styles.flex, styles.safeAreaView]}>
64+
<ScrollView bounces={false} style={styles.flex}>
65+
<Text style={styles.description}>{strings.description}</Text>
66+
<View style={styles.choicesContainer}>
67+
{CHOICES.map((choice, index) => {
68+
const isSelected = selectedChoiceId === choice.id
69+
return (
70+
<TouchableOpacity
71+
key={index}
72+
style={[styles.choiceButton, isSelected && styles.selectedChoiceButton]}
73+
onPress={() => handleChoicePress(choice.id)}
74+
>
75+
<Text style={[styles.choiceLabel, isSelected && styles.selectedChoiceLabel]}>
76+
{getLabelById(choice.id, strings)}
77+
</Text>
78+
</TouchableOpacity>
79+
)
80+
})}
81+
</View>
82+
83+
<View style={styles.inputContainer}>
84+
<Text style={styles.label}>{strings.displayDuration}</Text>
85+
86+
<TextInput
87+
value={selectedChoice.id === 'Manual' ? inputValue : formatNumber(selectedChoice.value)}
88+
onChangeText={handleInputChange}
89+
editable={isInputEnabled}
90+
key={isInputEnabled ? 'enabled' : 'disabled'}
91+
selectTextOnFocus={isInputEnabled}
92+
autoFocus={isInputEnabled}
93+
style={[styles.input, !isSelectedChoiceManual && {backgroundColor: colors.background}]}
94+
keyboardType="numeric"
95+
selectionColor={colors.cursor}
96+
right={<Text style={styles.percentLabel}>{strings.seconds}</Text>}
97+
error={hasError}
98+
/>
99+
</View>
100+
</ScrollView>
101+
<Button testID="applyButton" title={strings.apply} disabled={isButtonDisabled} onPress={handleSubmit} />
102+
</SafeAreaView>
103+
</KeyboardAvoidingView>
104+
)
105+
}
106+
107+
const getLabelById = (id: ChoiceKind, strings: ReturnType<typeof useStrings>) => {
108+
switch (id) {
109+
case '2s':
110+
return strings.twoSeconds
111+
case '4s':
112+
return strings.fourSeconds
113+
case '6s':
114+
return strings.sixSeconds
115+
case '8s':
116+
return strings.eightSeconds
117+
case '10s':
118+
return strings.tenSeconds
119+
case '12s':
120+
return strings.twelveSeconds
121+
case 'Manual':
122+
return strings.manual
123+
}
124+
}
125+
126+
const useStyles = () => {
127+
const {atoms, color} = useTheme()
128+
const styles = StyleSheet.create({
129+
flex: {
130+
...atoms.flex_1,
131+
},
132+
root: {
133+
backgroundColor: color.bg_color_max,
134+
},
135+
safeAreaView: {
136+
...atoms.p_lg,
137+
},
138+
textInfo: {
139+
...atoms.body_3_sm_regular,
140+
color: color.text_gray_medium,
141+
},
142+
description: {
143+
...atoms.py_lg,
144+
...atoms.body_1_lg_regular,
145+
color: color.gray_900,
146+
},
147+
choicesContainer: {
148+
...atoms.flex_row,
149+
...atoms.pb_xl,
150+
...atoms.flex_wrap,
151+
},
152+
choiceButton: {
153+
...atoms.p_sm,
154+
},
155+
selectedChoiceButton: {
156+
backgroundColor: color.el_gray_min,
157+
borderRadius: 8,
158+
},
159+
choiceLabel: {
160+
...atoms.body_1_lg_medium,
161+
color: color.text_gray_max,
162+
},
163+
selectedChoiceLabel: {
164+
color: color.text_gray_max,
165+
},
166+
errorText: {
167+
color: color.sys_magenta_500,
168+
...atoms.body_3_sm_regular,
169+
},
170+
input: {
171+
color: color.text_gray_medium,
172+
...atoms.body_1_lg_regular,
173+
},
174+
percentLabel: {
175+
color: color.text_gray_medium,
176+
...atoms.body_1_lg_regular,
177+
...atoms.p_lg,
178+
...atoms.absolute,
179+
right: 0,
180+
top: 0,
181+
},
182+
inputContainer: {
183+
...atoms.relative,
184+
},
185+
label: {
186+
color: color.text_gray_max,
187+
backgroundColor: color.bg_color_max,
188+
...atoms.z_20,
189+
...atoms.absolute,
190+
...atoms.body_3_sm_regular,
191+
top: -3,
192+
left: 11,
193+
paddingHorizontal: 3,
194+
},
195+
})
196+
const colors = {
197+
background: color.gray_100,
198+
cursor: color.input_selected,
199+
}
200+
201+
return {styles, colors}
202+
}
203+
204+
const getChoiceById = (id: ChoiceKind): Choice => {
205+
return CHOICES.find((choice) => choice.id === id) ?? {id: 'Manual'}
206+
}
207+
208+
const isInputValid = (text: string) => {
209+
const isNumeric = /^[0-9]*$/.test(text)
210+
const parsed = parseNumber(text)
211+
return isNumeric && typeof parsed === 'number' && parsed >= 1 && parsed <= 60
212+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import {defineMessages, useIntl} from 'react-intl'
2+
3+
export const useStrings = () => {
4+
const intl = useIntl()
5+
return {
6+
description: intl.formatMessage(messages.description),
7+
apply: intl.formatMessage(messages.apply),
8+
displayDuration: intl.formatMessage(messages.displayDuration),
9+
manual: intl.formatMessage(messages.manual),
10+
seconds: intl.formatMessage(messages.seconds),
11+
twoSeconds: intl.formatMessage(messages.twoSeconds),
12+
fourSeconds: intl.formatMessage(messages.fourSeconds),
13+
sixSeconds: intl.formatMessage(messages.sixSeconds),
14+
eightSeconds: intl.formatMessage(messages.eightSeconds),
15+
tenSeconds: intl.formatMessage(messages.tenSeconds),
16+
twelveSeconds: intl.formatMessage(messages.twelveSeconds),
17+
}
18+
}
19+
20+
const messages = defineMessages({
21+
description: {
22+
id: 'components.settings.manageNotificationDisplayDuration.description',
23+
defaultMessage: '!!!Adjust the display duration of in-app notifications to suit your preferences.',
24+
},
25+
apply: {
26+
id: 'components.settings.manageNotificationDisplayDuration.apply',
27+
defaultMessage: '!!!Apply',
28+
},
29+
displayDuration: {
30+
id: 'components.settings.manageNotificationDisplayDuration.displayDuration',
31+
defaultMessage: '!!!Display duration',
32+
},
33+
manual: {
34+
id: 'components.settings.manageNotificationDisplayDuration.manual',
35+
defaultMessage: '!!!Manual',
36+
},
37+
seconds: {
38+
id: 'components.settings.manageNotificationDisplayDuration.seconds',
39+
defaultMessage: '!!!seconds',
40+
},
41+
twoSeconds: {
42+
id: 'components.settings.manageNotificationDisplayDuration.twoSeconds',
43+
defaultMessage: '!!!2s',
44+
},
45+
fourSeconds: {
46+
id: 'components.settings.manageNotificationDisplayDuration.fourSeconds',
47+
defaultMessage: '!!!4s',
48+
},
49+
sixSeconds: {
50+
id: 'components.settings.manageNotificationDisplayDuration.sixSeconds',
51+
defaultMessage: '!!!6s',
52+
},
53+
eightSeconds: {
54+
id: 'components.settings.manageNotificationDisplayDuration.eightSeconds',
55+
defaultMessage: '!!!8s',
56+
},
57+
tenSeconds: {
58+
id: 'components.settings.manageNotificationDisplayDuration.tenSeconds',
59+
defaultMessage: '!!!10s',
60+
},
61+
twelveSeconds: {
62+
id: 'components.settings.manageNotificationDisplayDuration.twelveSeconds',
63+
defaultMessage: '!!!12s',
64+
},
65+
})

apps/wallet-mobile/src/kernel/i18n/LanguageProvider.tsx

+6
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
import {numberLocale, systemLocale} from './initialization'
2121
import {LanguageCode, NumberLocale, supportedLanguages} from './languages'
2222
import translations from './translations'
23+
import BigNumber from 'bignumber.js'
2324

2425
const LanguageContext = React.createContext<undefined | LanguageContext>(undefined)
2526
export const LanguageProvider = ({children}: {children: React.ReactNode}) => {
@@ -86,6 +87,11 @@ export const useLanguage = ({onChange}: {onChange?: (languageCode: LanguageCode)
8687
return value
8788
}
8889

90+
export const useFormatNumber = () => {
91+
const {numberLocale} = useLanguage()
92+
return React.useCallback((value: number) => new BigNumber(value, 10).toFormat(numberLocale), [numberLocale])
93+
}
94+
8995
const missingProvider = () => {
9096
throw new Error('LanguageProvider is missing')
9197
}

apps/wallet-mobile/src/kernel/i18n/locales/en-US.json

+11
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,17 @@
490490
"components.settings.walletsettingscreen.walletType": "Wallet type:",
491491
"components.settings.walletsettingscreen.inAppNotifications": "In-app notifications",
492492
"components.settings.walletsettingscreen.allowNotifications": "Allow notifications",
493+
"components.settings.manageNotificationDisplayDuration.description": "Adjust the display duration of in-app notifications to suit your preferences.",
494+
"components.settings.manageNotificationDisplayDuration.apply": "Apply",
495+
"components.settings.manageNotificationDisplayDuration.displayDuration": "Display duration",
496+
"components.settings.manageNotificationDisplayDuration.manual": "Manual",
497+
"components.settings.manageNotificationDisplayDuration.seconds": "seconds",
498+
"components.settings.manageNotificationDisplayDuration.twoSeconds": "2s",
499+
"components.settings.manageNotificationDisplayDuration.fourSeconds": "4s",
500+
"components.settings.manageNotificationDisplayDuration.sixSeconds": "6s",
501+
"components.settings.manageNotificationDisplayDuration.eightSeconds": "8s",
502+
"components.settings.manageNotificationDisplayDuration.tenSeconds": "10s",
503+
"components.settings.manageNotificationDisplayDuration.twelveSeconds": "12s",
493504
"components.somethingWentWrong.title": "Oops!",
494505
"components.somethingWentWrong.description": "Something went wrong.\nTry to reload this page or restart the app.",
495506
"components.stakingcenter.confirmDelegation.delegateButtonLabel": "Delegate",

0 commit comments

Comments
 (0)