* move `notifee.ts` to notifications folder * install expo notifications * add UIBackgroundMode `remote-notifications` to app.json * fix notifee import in Debug.tsx * add `google-services.json` * add `development-device` class to eas.json * Add `notifications.ts` for native notification handling * send push token to server * update `@atproto/api` * fix putting notif token to server * fix how push token is uploaded * fix lint * enable debug appview proxy header on all platforms * setup `notifications.ts` to work with app view notifs * clean up notification handler * add comments * update packages to correct versions * remove notifee * clean up code a lil * rename push token endpoint * remove unnecessary comments * fix comments * Remove old background scheduler * Fixes to push notifications API use * Bump @atproto/api@0.6.6 --------- Co-authored-by: Paul Frazee <pfrazee@gmail.com>
680 lines
23 KiB
TypeScript
680 lines
23 KiB
TypeScript
import React from 'react'
|
|
import {
|
|
ActivityIndicator,
|
|
Linking,
|
|
Platform,
|
|
Pressable,
|
|
StyleSheet,
|
|
TextStyle,
|
|
TouchableOpacity,
|
|
View,
|
|
ViewStyle,
|
|
} from 'react-native'
|
|
import {
|
|
useFocusEffect,
|
|
useNavigation,
|
|
StackActions,
|
|
} from '@react-navigation/native'
|
|
import {
|
|
FontAwesomeIcon,
|
|
FontAwesomeIconStyle,
|
|
} from '@fortawesome/react-native-fontawesome'
|
|
import {observer} from 'mobx-react-lite'
|
|
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
|
|
import {withAuthRequired} from 'view/com/auth/withAuthRequired'
|
|
import * as AppInfo from 'lib/app-info'
|
|
import {useStores} from 'state/index'
|
|
import {s, colors} from 'lib/styles'
|
|
import {ScrollView} from '../com/util/Views'
|
|
import {ViewHeader} from '../com/util/ViewHeader'
|
|
import {Link} from '../com/util/Link'
|
|
import {Text} from '../com/util/text/Text'
|
|
import * as Toast from '../com/util/Toast'
|
|
import {UserAvatar} from '../com/util/UserAvatar'
|
|
import {ToggleButton} from 'view/com/util/forms/ToggleButton'
|
|
import {SelectableBtn} from 'view/com/util/forms/SelectableBtn'
|
|
import {usePalette} from 'lib/hooks/usePalette'
|
|
import {useCustomPalette} from 'lib/hooks/useCustomPalette'
|
|
import {AccountData} from 'state/models/session'
|
|
import {useAnalytics} from 'lib/analytics/analytics'
|
|
import {NavigationProp} from 'lib/routes/types'
|
|
import {isDesktopWeb} from 'platform/detection'
|
|
import {pluralize} from 'lib/strings/helpers'
|
|
import {formatCount} from 'view/com/util/numeric/format'
|
|
import Clipboard from '@react-native-clipboard/clipboard'
|
|
import {reset as resetNavigation} from '../../Navigation'
|
|
import {makeProfileLink} from 'lib/routes/links'
|
|
|
|
// TEMPORARY (APP-700)
|
|
// remove after backend testing finishes
|
|
// -prf
|
|
import {useDebugHeaderSetting} from 'lib/api/debug-appview-proxy-header'
|
|
import {STATUS_PAGE_URL} from 'lib/constants'
|
|
import {DropdownItem, NativeDropdown} from 'view/com/util/forms/NativeDropdown'
|
|
|
|
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Settings'>
|
|
export const SettingsScreen = withAuthRequired(
|
|
observer(function Settings({}: Props) {
|
|
const pal = usePalette('default')
|
|
const store = useStores()
|
|
const navigation = useNavigation<NavigationProp>()
|
|
const {screen, track} = useAnalytics()
|
|
const [isSwitching, setIsSwitching] = React.useState(false)
|
|
const [debugHeaderEnabled, toggleDebugHeader] = useDebugHeaderSetting(
|
|
store.agent,
|
|
)
|
|
|
|
const primaryBg = useCustomPalette<ViewStyle>({
|
|
light: {backgroundColor: colors.blue0},
|
|
dark: {backgroundColor: colors.blue6},
|
|
})
|
|
const primaryText = useCustomPalette<TextStyle>({
|
|
light: {color: colors.blue3},
|
|
dark: {color: colors.blue2},
|
|
})
|
|
|
|
const dangerBg = useCustomPalette<ViewStyle>({
|
|
light: {backgroundColor: colors.red1},
|
|
dark: {backgroundColor: colors.red7},
|
|
})
|
|
const dangerText = useCustomPalette<TextStyle>({
|
|
light: {color: colors.red4},
|
|
dark: {color: colors.red2},
|
|
})
|
|
|
|
useFocusEffect(
|
|
React.useCallback(() => {
|
|
screen('Settings')
|
|
store.shell.setMinimalShellMode(false)
|
|
}, [screen, store]),
|
|
)
|
|
|
|
const onPressSwitchAccount = React.useCallback(
|
|
async (acct: AccountData) => {
|
|
track('Settings:SwitchAccountButtonClicked')
|
|
setIsSwitching(true)
|
|
if (await store.session.resumeSession(acct)) {
|
|
setIsSwitching(false)
|
|
resetNavigation()
|
|
Toast.show(`Signed in as ${acct.displayName || acct.handle}`)
|
|
return
|
|
}
|
|
setIsSwitching(false)
|
|
Toast.show('Sorry! We need you to enter your password.')
|
|
navigation.navigate('HomeTab')
|
|
navigation.dispatch(StackActions.popToTop())
|
|
store.session.clear()
|
|
},
|
|
[track, setIsSwitching, navigation, store],
|
|
)
|
|
|
|
const onPressAddAccount = React.useCallback(() => {
|
|
track('Settings:AddAccountButtonClicked')
|
|
navigation.navigate('HomeTab')
|
|
navigation.dispatch(StackActions.popToTop())
|
|
store.session.clear()
|
|
}, [track, navigation, store])
|
|
|
|
const onPressChangeHandle = React.useCallback(() => {
|
|
track('Settings:ChangeHandleButtonClicked')
|
|
store.shell.openModal({
|
|
name: 'change-handle',
|
|
onChanged() {
|
|
setIsSwitching(true)
|
|
store.session.reloadFromServer().then(
|
|
() => {
|
|
setIsSwitching(false)
|
|
Toast.show('Your handle has been updated')
|
|
},
|
|
err => {
|
|
store.log.error(
|
|
'Failed to reload from server after handle update',
|
|
{err},
|
|
)
|
|
setIsSwitching(false)
|
|
},
|
|
)
|
|
},
|
|
})
|
|
}, [track, store, setIsSwitching])
|
|
|
|
const onPressInviteCodes = React.useCallback(() => {
|
|
track('Settings:InvitecodesButtonClicked')
|
|
store.shell.openModal({name: 'invite-codes'})
|
|
}, [track, store])
|
|
|
|
const onPressContentLanguages = React.useCallback(() => {
|
|
track('Settings:ContentlanguagesButtonClicked')
|
|
store.shell.openModal({name: 'content-languages-settings'})
|
|
}, [track, store])
|
|
|
|
const onPressSignout = React.useCallback(() => {
|
|
track('Settings:SignOutButtonClicked')
|
|
store.session.logout()
|
|
}, [track, store])
|
|
|
|
const onPressDeleteAccount = React.useCallback(() => {
|
|
store.shell.openModal({name: 'delete-account'})
|
|
}, [store])
|
|
|
|
const onPressResetPreferences = React.useCallback(async () => {
|
|
await store.preferences.reset()
|
|
Toast.show('Preferences reset')
|
|
}, [store])
|
|
|
|
const onPressBuildInfo = React.useCallback(() => {
|
|
Clipboard.setString(
|
|
`Build version: ${AppInfo.appVersion}; Platform: ${Platform.OS}`,
|
|
)
|
|
Toast.show('Copied build version to clipboard')
|
|
}, [])
|
|
|
|
const openPreferencesModal = React.useCallback(() => {
|
|
store.shell.openModal({
|
|
name: 'preferences-home-feed',
|
|
})
|
|
}, [store])
|
|
|
|
const onPressAppPasswords = React.useCallback(() => {
|
|
navigation.navigate('AppPasswords')
|
|
}, [navigation])
|
|
|
|
const onPressSystemLog = React.useCallback(() => {
|
|
navigation.navigate('Log')
|
|
}, [navigation])
|
|
|
|
const onPressStorybook = React.useCallback(() => {
|
|
navigation.navigate('Debug')
|
|
}, [navigation])
|
|
|
|
const onPressSavedFeeds = React.useCallback(() => {
|
|
navigation.navigate('SavedFeeds')
|
|
}, [navigation])
|
|
|
|
const onPressStatusPage = React.useCallback(() => {
|
|
Linking.openURL(STATUS_PAGE_URL)
|
|
}, [])
|
|
|
|
return (
|
|
<View style={[s.hContentRegion]} testID="settingsScreen">
|
|
<ViewHeader title="Settings" />
|
|
<ScrollView
|
|
style={[s.hContentRegion]}
|
|
contentContainerStyle={!isDesktopWeb && pal.viewLight}
|
|
scrollIndicatorInsets={{right: 1}}>
|
|
<View style={styles.spacer20} />
|
|
{store.session.currentSession !== undefined ? (
|
|
<>
|
|
<Text type="xl-bold" style={[pal.text, styles.heading]}>
|
|
Account
|
|
</Text>
|
|
<View style={[styles.infoLine]}>
|
|
<Text type="lg-medium" style={pal.text}>
|
|
Email:{' '}
|
|
<Text type="lg" style={pal.text}>
|
|
{store.session.currentSession?.email}
|
|
</Text>
|
|
</Text>
|
|
</View>
|
|
<View style={styles.spacer20} />
|
|
</>
|
|
) : null}
|
|
<View style={[s.flexRow, styles.heading]}>
|
|
<Text type="xl-bold" style={pal.text}>
|
|
Signed in as
|
|
</Text>
|
|
<View style={s.flex1} />
|
|
</View>
|
|
{isSwitching ? (
|
|
<View style={[pal.view, styles.linkCard]}>
|
|
<ActivityIndicator />
|
|
</View>
|
|
) : (
|
|
<Link
|
|
href={makeProfileLink(store.me)}
|
|
title="Your profile"
|
|
noFeedback>
|
|
<View style={[pal.view, styles.linkCard]}>
|
|
<View style={styles.avi}>
|
|
<UserAvatar size={40} avatar={store.me.avatar} />
|
|
</View>
|
|
<View style={[s.flex1]}>
|
|
<Text type="md-bold" style={pal.text} numberOfLines={1}>
|
|
{store.me.displayName || store.me.handle}
|
|
</Text>
|
|
<Text type="sm" style={pal.textLight} numberOfLines={1}>
|
|
{store.me.handle}
|
|
</Text>
|
|
</View>
|
|
<TouchableOpacity
|
|
testID="signOutBtn"
|
|
onPress={isSwitching ? undefined : onPressSignout}
|
|
accessibilityRole="button"
|
|
accessibilityLabel="Sign out"
|
|
accessibilityHint={`Signs ${store.me.displayName} out of Bluesky`}>
|
|
<Text type="lg" style={pal.link}>
|
|
Sign out
|
|
</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
</Link>
|
|
)}
|
|
{store.session.switchableAccounts.map(account => (
|
|
<TouchableOpacity
|
|
testID={`switchToAccountBtn-${account.handle}`}
|
|
key={account.did}
|
|
style={[pal.view, styles.linkCard, isSwitching && styles.dimmed]}
|
|
onPress={
|
|
isSwitching ? undefined : () => onPressSwitchAccount(account)
|
|
}
|
|
accessibilityRole="button"
|
|
accessibilityLabel={`Switch to ${account.handle}`}
|
|
accessibilityHint="Switches the account you are logged in to">
|
|
<View style={styles.avi}>
|
|
<UserAvatar size={40} avatar={account.aviUrl} />
|
|
</View>
|
|
<View style={[s.flex1]}>
|
|
<Text type="md-bold" style={pal.text}>
|
|
{account.displayName || account.handle}
|
|
</Text>
|
|
<Text type="sm" style={pal.textLight}>
|
|
{account.handle}
|
|
</Text>
|
|
</View>
|
|
<AccountDropdownBtn handle={account.handle} />
|
|
</TouchableOpacity>
|
|
))}
|
|
<TouchableOpacity
|
|
testID="switchToNewAccountBtn"
|
|
style={[styles.linkCard, pal.view, isSwitching && styles.dimmed]}
|
|
onPress={isSwitching ? undefined : onPressAddAccount}
|
|
accessibilityRole="button"
|
|
accessibilityLabel="Add account"
|
|
accessibilityHint="Create a new Bluesky account">
|
|
<View style={[styles.iconContainer, pal.btn]}>
|
|
<FontAwesomeIcon
|
|
icon="plus"
|
|
style={pal.text as FontAwesomeIconStyle}
|
|
/>
|
|
</View>
|
|
<Text type="lg" style={pal.text}>
|
|
Add account
|
|
</Text>
|
|
</TouchableOpacity>
|
|
|
|
<View style={styles.spacer20} />
|
|
|
|
<Text type="xl-bold" style={[pal.text, styles.heading]}>
|
|
Invite a Friend
|
|
</Text>
|
|
<TouchableOpacity
|
|
testID="inviteFriendBtn"
|
|
style={[styles.linkCard, pal.view, isSwitching && styles.dimmed]}
|
|
onPress={isSwitching ? undefined : onPressInviteCodes}
|
|
accessibilityRole="button"
|
|
accessibilityLabel="Invite"
|
|
accessibilityHint="Opens invite code list">
|
|
<View
|
|
style={[
|
|
styles.iconContainer,
|
|
store.me.invitesAvailable > 0 ? primaryBg : pal.btn,
|
|
]}>
|
|
<FontAwesomeIcon
|
|
icon="ticket"
|
|
style={
|
|
(store.me.invitesAvailable > 0
|
|
? primaryText
|
|
: pal.text) as FontAwesomeIconStyle
|
|
}
|
|
/>
|
|
</View>
|
|
<Text
|
|
type="lg"
|
|
style={store.me.invitesAvailable > 0 ? pal.link : pal.text}>
|
|
{formatCount(store.me.invitesAvailable)} invite{' '}
|
|
{pluralize(store.me.invitesAvailable, 'code')} available
|
|
</Text>
|
|
</TouchableOpacity>
|
|
|
|
<View style={styles.spacer20} />
|
|
|
|
<Text type="xl-bold" style={[pal.text, styles.heading]}>
|
|
Accessibility
|
|
</Text>
|
|
<View style={[pal.view, styles.toggleCard]}>
|
|
<ToggleButton
|
|
type="default-light"
|
|
label="Require alt text before posting"
|
|
labelType="lg"
|
|
isSelected={store.preferences.requireAltTextEnabled}
|
|
onPress={store.preferences.toggleRequireAltTextEnabled}
|
|
/>
|
|
</View>
|
|
|
|
<View style={styles.spacer20} />
|
|
|
|
<Text type="xl-bold" style={[pal.text, styles.heading]}>
|
|
Appearance
|
|
</Text>
|
|
<View>
|
|
<View style={[styles.linkCard, pal.view, styles.selectableBtns]}>
|
|
<SelectableBtn
|
|
selected={store.shell.colorMode === 'system'}
|
|
label="System"
|
|
left
|
|
onSelect={() => store.shell.setColorMode('system')}
|
|
accessibilityHint="Set color theme to system setting"
|
|
/>
|
|
<SelectableBtn
|
|
selected={store.shell.colorMode === 'light'}
|
|
label="Light"
|
|
onSelect={() => store.shell.setColorMode('light')}
|
|
accessibilityHint="Set color theme to light"
|
|
/>
|
|
<SelectableBtn
|
|
selected={store.shell.colorMode === 'dark'}
|
|
label="Dark"
|
|
right
|
|
onSelect={() => store.shell.setColorMode('dark')}
|
|
accessibilityHint="Set color theme to dark"
|
|
/>
|
|
</View>
|
|
</View>
|
|
<View style={styles.spacer20} />
|
|
|
|
<Text type="xl-bold" style={[pal.text, styles.heading]}>
|
|
Advanced
|
|
</Text>
|
|
<TouchableOpacity
|
|
testID="preferencesHomeFeedModalButton"
|
|
style={[styles.linkCard, pal.view, isSwitching && styles.dimmed]}
|
|
onPress={openPreferencesModal}
|
|
accessibilityRole="button"
|
|
accessibilityHint="Open home feed preferences modal"
|
|
accessibilityLabel="Opens the home feed preferences modal">
|
|
<View style={[styles.iconContainer, pal.btn]}>
|
|
<FontAwesomeIcon
|
|
icon="sliders"
|
|
style={pal.text as FontAwesomeIconStyle}
|
|
/>
|
|
</View>
|
|
<Text type="lg" style={pal.text}>
|
|
Home Feed Preferences
|
|
</Text>
|
|
</TouchableOpacity>
|
|
<TouchableOpacity
|
|
testID="appPasswordBtn"
|
|
style={[styles.linkCard, pal.view, isSwitching && styles.dimmed]}
|
|
onPress={onPressAppPasswords}
|
|
accessibilityRole="button"
|
|
accessibilityHint="Open app password settings"
|
|
accessibilityLabel="Opens the app password settings page">
|
|
<View style={[styles.iconContainer, pal.btn]}>
|
|
<FontAwesomeIcon
|
|
icon="lock"
|
|
style={pal.text as FontAwesomeIconStyle}
|
|
/>
|
|
</View>
|
|
<Text type="lg" style={pal.text}>
|
|
App passwords
|
|
</Text>
|
|
</TouchableOpacity>
|
|
<TouchableOpacity
|
|
testID="savedFeedsBtn"
|
|
style={[styles.linkCard, pal.view, isSwitching && styles.dimmed]}
|
|
accessibilityHint="Saved Feeds"
|
|
accessibilityLabel="Opens screen with all saved feeds"
|
|
onPress={onPressSavedFeeds}>
|
|
<View style={[styles.iconContainer, pal.btn]}>
|
|
<FontAwesomeIcon
|
|
icon="satellite-dish"
|
|
style={pal.text as FontAwesomeIconStyle}
|
|
/>
|
|
</View>
|
|
<Text type="lg" style={pal.text}>
|
|
Saved Feeds
|
|
</Text>
|
|
</TouchableOpacity>
|
|
<TouchableOpacity
|
|
testID="contentLanguagesBtn"
|
|
style={[styles.linkCard, pal.view, isSwitching && styles.dimmed]}
|
|
onPress={isSwitching ? undefined : onPressContentLanguages}
|
|
accessibilityRole="button"
|
|
accessibilityHint="Content languages"
|
|
accessibilityLabel="Opens configurable content language settings">
|
|
<View style={[styles.iconContainer, pal.btn]}>
|
|
<FontAwesomeIcon
|
|
icon="language"
|
|
style={pal.text as FontAwesomeIconStyle}
|
|
/>
|
|
</View>
|
|
<Text type="lg" style={pal.text}>
|
|
Content languages
|
|
</Text>
|
|
</TouchableOpacity>
|
|
<TouchableOpacity
|
|
testID="changeHandleBtn"
|
|
style={[styles.linkCard, pal.view, isSwitching && styles.dimmed]}
|
|
onPress={isSwitching ? undefined : onPressChangeHandle}
|
|
accessibilityRole="button"
|
|
accessibilityLabel="Change handle"
|
|
accessibilityHint="Choose a new Bluesky username or create">
|
|
<View style={[styles.iconContainer, pal.btn]}>
|
|
<FontAwesomeIcon
|
|
icon="at"
|
|
style={pal.text as FontAwesomeIconStyle}
|
|
/>
|
|
</View>
|
|
<Text type="lg" style={pal.text} numberOfLines={1}>
|
|
Change handle
|
|
</Text>
|
|
</TouchableOpacity>
|
|
<View style={styles.spacer20} />
|
|
<Text type="xl-bold" style={[pal.text, styles.heading]}>
|
|
Danger Zone
|
|
</Text>
|
|
<TouchableOpacity
|
|
style={[pal.view, styles.linkCard]}
|
|
onPress={onPressDeleteAccount}
|
|
accessible={true}
|
|
accessibilityRole="button"
|
|
accessibilityLabel="Delete account"
|
|
accessibilityHint="Opens modal for account deletion confirmation. Requires email code.">
|
|
<View style={[styles.iconContainer, dangerBg]}>
|
|
<FontAwesomeIcon
|
|
icon={['far', 'trash-can']}
|
|
style={dangerText as FontAwesomeIconStyle}
|
|
size={18}
|
|
/>
|
|
</View>
|
|
<Text type="lg" style={dangerText}>
|
|
Delete my account…
|
|
</Text>
|
|
</TouchableOpacity>
|
|
<View style={styles.spacer20} />
|
|
<Text type="xl-bold" style={[pal.text, styles.heading]}>
|
|
Developer Tools
|
|
</Text>
|
|
<TouchableOpacity
|
|
style={[pal.view, styles.linkCardNoIcon]}
|
|
onPress={onPressSystemLog}
|
|
accessibilityRole="button"
|
|
accessibilityHint="Open system log"
|
|
accessibilityLabel="Opens the system log page">
|
|
<Text type="lg" style={pal.text}>
|
|
System log
|
|
</Text>
|
|
</TouchableOpacity>
|
|
{isDesktopWeb || __DEV__ ? (
|
|
<ToggleButton
|
|
type="default-light"
|
|
label="Experiment: Use AppView Proxy"
|
|
isSelected={debugHeaderEnabled}
|
|
onPress={toggleDebugHeader}
|
|
/>
|
|
) : null}
|
|
{__DEV__ ? (
|
|
<>
|
|
<TouchableOpacity
|
|
style={[pal.view, styles.linkCardNoIcon]}
|
|
onPress={onPressStorybook}
|
|
accessibilityRole="button"
|
|
accessibilityHint="Open storybook page"
|
|
accessibilityLabel="Opens the storybook page">
|
|
<Text type="lg" style={pal.text}>
|
|
Storybook
|
|
</Text>
|
|
</TouchableOpacity>
|
|
<TouchableOpacity
|
|
style={[pal.view, styles.linkCardNoIcon]}
|
|
onPress={onPressResetPreferences}
|
|
accessibilityRole="button"
|
|
accessibilityHint="Reset preferences"
|
|
accessibilityLabel="Resets the preferences state">
|
|
<Text type="lg" style={pal.text}>
|
|
Reset preferences state
|
|
</Text>
|
|
</TouchableOpacity>
|
|
</>
|
|
) : null}
|
|
<View style={[styles.footer]}>
|
|
<TouchableOpacity
|
|
accessibilityRole="button"
|
|
onPress={onPressBuildInfo}>
|
|
<Text type="sm" style={[styles.buildInfo, pal.textLight]}>
|
|
Build version {AppInfo.appVersion} {AppInfo.updateChannel}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
<Text type="sm" style={[pal.textLight]}>
|
|
·
|
|
</Text>
|
|
<TouchableOpacity
|
|
accessibilityRole="button"
|
|
onPress={onPressStatusPage}>
|
|
<Text type="sm" style={[styles.buildInfo, pal.textLight]}>
|
|
Status page
|
|
</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
<View style={s.footerSpacer} />
|
|
</ScrollView>
|
|
</View>
|
|
)
|
|
}),
|
|
)
|
|
|
|
function AccountDropdownBtn({handle}: {handle: string}) {
|
|
const store = useStores()
|
|
const pal = usePalette('default')
|
|
const items: DropdownItem[] = [
|
|
{
|
|
label: 'Remove account',
|
|
onPress: () => {
|
|
store.session.removeAccount(handle)
|
|
Toast.show('Account removed from quick access')
|
|
},
|
|
icon: {
|
|
ios: {
|
|
name: 'trash',
|
|
},
|
|
android: 'ic_delete',
|
|
web: 'trash',
|
|
},
|
|
},
|
|
]
|
|
return (
|
|
<Pressable accessibilityRole="button" style={s.pl10}>
|
|
<NativeDropdown testID="accountSettingsDropdownBtn" items={items}>
|
|
<FontAwesomeIcon
|
|
icon="ellipsis-h"
|
|
style={pal.textLight as FontAwesomeIconStyle}
|
|
/>
|
|
</NativeDropdown>
|
|
</Pressable>
|
|
)
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
dimmed: {
|
|
opacity: 0.5,
|
|
},
|
|
spacer20: {
|
|
height: 20,
|
|
},
|
|
heading: {
|
|
paddingHorizontal: 18,
|
|
paddingBottom: 6,
|
|
},
|
|
infoLine: {
|
|
paddingHorizontal: 18,
|
|
paddingBottom: 6,
|
|
},
|
|
profile: {
|
|
flexDirection: 'row',
|
|
marginVertical: 6,
|
|
borderRadius: 4,
|
|
paddingVertical: 10,
|
|
paddingHorizontal: 10,
|
|
},
|
|
linkCard: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
paddingVertical: 12,
|
|
paddingHorizontal: 18,
|
|
marginBottom: 1,
|
|
},
|
|
linkCardNoIcon: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
paddingVertical: 20,
|
|
paddingHorizontal: 18,
|
|
marginBottom: 1,
|
|
},
|
|
toggleCard: {
|
|
paddingVertical: 8,
|
|
paddingHorizontal: 6,
|
|
marginBottom: 1,
|
|
},
|
|
avi: {
|
|
marginRight: 12,
|
|
},
|
|
iconContainer: {
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
width: 40,
|
|
height: 40,
|
|
borderRadius: 30,
|
|
marginRight: 12,
|
|
},
|
|
buildInfo: {
|
|
paddingVertical: 8,
|
|
},
|
|
|
|
colorModeText: {
|
|
marginLeft: 10,
|
|
marginBottom: 6,
|
|
},
|
|
|
|
selectableBtns: {
|
|
flexDirection: 'row',
|
|
},
|
|
|
|
btn: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
width: '100%',
|
|
borderRadius: 32,
|
|
padding: 14,
|
|
backgroundColor: colors.gray1,
|
|
},
|
|
toggleBtn: {
|
|
paddingHorizontal: 0,
|
|
},
|
|
footer: {
|
|
flex: 1,
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
paddingLeft: 18,
|
|
},
|
|
})
|