diff options
Diffstat (limited to 'src/view/com')
-rw-r--r-- | src/view/com/modals/AddAppPasswords.tsx | 216 | ||||
-rw-r--r-- | src/view/com/modals/Modal.tsx | 4 | ||||
-rw-r--r-- | src/view/com/util/ViewHeader.tsx | 25 |
3 files changed, 244 insertions, 1 deletions
diff --git a/src/view/com/modals/AddAppPasswords.tsx b/src/view/com/modals/AddAppPasswords.tsx new file mode 100644 index 000000000..1d2f80ff0 --- /dev/null +++ b/src/view/com/modals/AddAppPasswords.tsx @@ -0,0 +1,216 @@ +import React, {useState} from 'react' +import {StyleSheet, TextInput, View, TouchableOpacity} from 'react-native' +import {Text} from '../util/text/Text' +import {Button} from '../util/forms/Button' +import {s} from 'lib/styles' +import {useStores} from 'state/index' +import {usePalette} from 'lib/hooks/usePalette' +import {isDesktopWeb} from 'platform/detection' +import { + FontAwesomeIcon, + FontAwesomeIconStyle, +} from '@fortawesome/react-native-fontawesome' +import Clipboard from '@react-native-clipboard/clipboard' +import * as Toast from '../util/Toast' + +export const snapPoints = ['70%'] + +const shadesOfBlue: string[] = [ + 'AliceBlue', + 'Aqua', + 'Aquamarine', + 'Azure', + 'BabyBlue', + 'Blue', + 'BlueViolet', + 'CadetBlue', + 'CornflowerBlue', + 'Cyan', + 'DarkBlue', + 'DarkCyan', + 'DarkSlateBlue', + 'DeepSkyBlue', + 'DodgerBlue', + 'ElectricBlue', + 'LightBlue', + 'LightCyan', + 'LightSkyBlue', + 'LightSteelBlue', + 'MediumAquaMarine', + 'MediumBlue', + 'MediumSlateBlue', + 'MidnightBlue', + 'Navy', + 'PowderBlue', + 'RoyalBlue', + 'SkyBlue', + 'SlateBlue', + 'SteelBlue', + 'Teal', + 'Turquoise', +] + +export function Component({}: {}) { + const pal = usePalette('default') + const store = useStores() + const [name, setName] = useState( + shadesOfBlue[Math.floor(Math.random() * shadesOfBlue.length)], + ) + const [appPassword, setAppPassword] = useState<string>() + const [wasCopied, setWasCopied] = useState(false) + + const onCopy = React.useCallback(() => { + if (appPassword) { + Clipboard.setString(appPassword) + Toast.show('Copied to clipboard') + setWasCopied(true) + } + }, [appPassword]) + + const onDone = React.useCallback(() => { + store.shell.closeModal() + }, [store]) + + const createAppPassword = async () => { + try { + const newPassword = await store.me.createAppPassword(name) + if (newPassword) { + setAppPassword(newPassword.password) + } else { + Toast.show('Failed to create app password.') + // TODO: better error handling (?) + } + } catch (e) { + Toast.show('Failed to create app password.') + store.log.error('Failed to create app password', {e}) + } + } + + return ( + <View style={[styles.container, pal.view]} testID="addAppPasswordsModal"> + <View> + {!appPassword ? ( + <Text type="lg"> + Please enter a unique name for this App Password. We have generated + a random name for you. + </Text> + ) : ( + <Text type="lg"> + <Text type="lg-bold">Here is your app password.</Text> Use this to + sign into the other app along with your handle. + </Text> + )} + {!appPassword ? ( + <View style={[pal.btn, styles.textInputWrapper]}> + <TextInput + style={[styles.input, pal.text]} + onChangeText={setName} + value={name} + placeholder="Enter a name for this App Password" + placeholderTextColor={pal.colors.textLight} + autoCorrect={false} + autoComplete="off" + autoCapitalize="none" + autoFocus={true} + selectTextOnFocus={true} + multiline={true} // need this to be true otherwise selectTextOnFocus doesn't work + numberOfLines={1} // hack for multiline so only one line shows (android) + scrollEnabled={false} // hack for multiline so only one line shows (ios) + blurOnSubmit={true} // hack for multiline so it submits + editable={!appPassword} + returnKeyType="done" + onEndEditing={createAppPassword} + /> + </View> + ) : ( + <TouchableOpacity + style={[pal.border, styles.passwordContainer, pal.btn]} + onPress={onCopy}> + <Text type="2xl-bold">{appPassword}</Text> + {wasCopied ? ( + <Text style={[pal.textLight]}>Copied</Text> + ) : ( + <FontAwesomeIcon + icon={['far', 'clone']} + style={pal.text as FontAwesomeIconStyle} + size={18} + /> + )} + </TouchableOpacity> + )} + </View> + {appPassword ? ( + <Text type="lg" style={[pal.textLight, s.mb10]}> + For security reasons, you won't be able to view this again. If you + lose this password, you'll need to generate a new one. + </Text> + ) : null} + <View style={styles.btnContainer}> + <Button + type="primary" + label={!appPassword ? 'Create App Password' : 'Done'} + style={styles.btn} + labelStyle={styles.btnLabel} + onPress={!appPassword ? createAppPassword : onDone} + /> + </View> + </View> + ) +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + paddingBottom: isDesktopWeb ? 0 : 50, + marginHorizontal: 16, + }, + textInputWrapper: { + borderRadius: 8, + flexDirection: 'row', + alignItems: 'center', + marginTop: 16, + marginBottom: 8, + }, + input: { + flex: 1, + width: '100%', + paddingVertical: 10, + paddingHorizontal: 8, + marginTop: 6, + fontSize: 17, + letterSpacing: 0.25, + fontWeight: '400', + borderRadius: 10, + }, + passwordContainer: { + flexDirection: 'row', + justifyContent: 'space-between', + paddingVertical: 8, + paddingHorizontal: 16, + alignItems: 'center', + borderRadius: 10, + marginTop: 16, + marginBottom: 12, + }, + btnContainer: { + flexDirection: 'row', + justifyContent: 'center', + marginTop: 12, + }, + btn: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + borderRadius: 32, + paddingHorizontal: 60, + paddingVertical: 14, + }, + btnLabel: { + fontSize: 18, + }, + groupContent: { + borderTopWidth: 1, + flexDirection: 'row', + alignItems: 'center', + }, +}) diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx index a83cdfdae..5d034a19d 100644 --- a/src/view/com/modals/Modal.tsx +++ b/src/view/com/modals/Modal.tsx @@ -17,6 +17,7 @@ import * as DeleteAccountModal from './DeleteAccount' import * as ChangeHandleModal from './ChangeHandle' import * as WaitlistModal from './Waitlist' import * as InviteCodesModal from './InviteCodes' +import * as AddAppPassword from './AddAppPasswords' import * as ContentFilteringSettingsModal from './ContentFilteringSettings' const DEFAULT_SNAPPOINTS = ['90%'] @@ -81,6 +82,9 @@ export const ModalsContainer = observer(function ModalsContainer() { } else if (activeModal?.name === 'invite-codes') { snapPoints = InviteCodesModal.snapPoints element = <InviteCodesModal.Component /> + } else if (activeModal?.name === 'add-app-password') { + snapPoints = AddAppPassword.snapPoints + element = <AddAppPassword.Component /> } else if (activeModal?.name === 'content-filtering-settings') { snapPoints = ContentFilteringSettingsModal.snapPoints element = <ContentFilteringSettingsModal.Component /> diff --git a/src/view/com/util/ViewHeader.tsx b/src/view/com/util/ViewHeader.tsx index ad0a5a1d2..816c835cc 100644 --- a/src/view/com/util/ViewHeader.tsx +++ b/src/view/com/util/ViewHeader.tsx @@ -3,6 +3,7 @@ import {observer} from 'mobx-react-lite' import {Animated, StyleSheet, TouchableOpacity, View} from 'react-native' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {useNavigation} from '@react-navigation/native' +import {CenteredView} from './Views' import {UserAvatar} from './UserAvatar' import {Text} from './text/Text' import {useStores} from 'state/index' @@ -18,10 +19,12 @@ export const ViewHeader = observer(function ({ title, canGoBack, hideOnScroll, + showOnDesktop, }: { title: string canGoBack?: boolean hideOnScroll?: boolean + showOnDesktop?: boolean }) { const pal = usePalette('default') const store = useStores() @@ -42,7 +45,10 @@ export const ViewHeader = observer(function ({ }, [track, store]) if (isDesktopWeb) { - return <></> + if (showOnDesktop) { + return <DesktopWebHeader title={title} /> + } + return null } else { if (typeof canGoBack === 'undefined') { canGoBack = navigation.canGoBack() @@ -76,6 +82,19 @@ export const ViewHeader = observer(function ({ } }) +function DesktopWebHeader({title}: {title: string}) { + const pal = usePalette('default') + return ( + <CenteredView style={[styles.header, styles.desktopHeader, pal.border]}> + <View style={styles.titleContainer} pointerEvents="none"> + <Text type="title-lg" style={[pal.text, styles.title]}> + {title} + </Text> + </View> + </CenteredView> + ) +} + const Container = observer( ({ children, @@ -133,6 +152,10 @@ const styles = StyleSheet.create({ top: 0, width: '100%', }, + desktopHeader: { + borderBottomWidth: 1, + paddingVertical: 12, + }, titleContainer: { marginLeft: 'auto', |