diff options
author | Paul Frazee <pfrazee@gmail.com> | 2022-11-15 15:09:50 -0600 |
---|---|---|
committer | Paul Frazee <pfrazee@gmail.com> | 2022-11-15 15:09:50 -0600 |
commit | 3725a2eed10707194bc9554a9c58166e9324dfc8 (patch) | |
tree | f167b4541f86eb5b9ff8629c0bbb6486644f498b | |
parent | 9a6df95adecaf3935fdbd58d893fca6489a040b9 (diff) | |
download | voidsky-3725a2eed10707194bc9554a9c58166e9324dfc8.tar.zst |
Add a server instance selector and drop env vars
-rw-r--r-- | .env.development | 1 | ||||
-rw-r--r-- | .env.production | 1 | ||||
-rw-r--r-- | src/env.native.ts | 13 | ||||
-rw-r--r-- | src/env.ts | 10 | ||||
-rw-r--r-- | src/state/index.ts | 14 | ||||
-rw-r--r-- | src/state/models/shell-ui.ts | 15 | ||||
-rw-r--r-- | src/view/com/modals/Modal.tsx | 8 | ||||
-rw-r--r-- | src/view/com/modals/ServerInput.tsx | 140 | ||||
-rw-r--r-- | src/view/index.ts | 4 | ||||
-rw-r--r-- | src/view/lib/strings.ts | 13 | ||||
-rw-r--r-- | src/view/screens/Home.tsx | 6 | ||||
-rw-r--r-- | src/view/screens/Login.tsx | 322 | ||||
-rw-r--r-- | src/view/shell/mobile/index.tsx | 2 | ||||
-rw-r--r-- | todos.txt | 8 |
14 files changed, 383 insertions, 174 deletions
diff --git a/.env.development b/.env.development deleted file mode 100644 index d0f279676..000000000 --- a/.env.development +++ /dev/null @@ -1 +0,0 @@ -REACT_APP_BUILD = 'staging' diff --git a/.env.production b/.env.production deleted file mode 100644 index d0f279676..000000000 --- a/.env.production +++ /dev/null @@ -1 +0,0 @@ -REACT_APP_BUILD = 'staging' diff --git a/src/env.native.ts b/src/env.native.ts deleted file mode 100644 index a2ec3a4dc..000000000 --- a/src/env.native.ts +++ /dev/null @@ -1,13 +0,0 @@ -// @ts-ignore types not available -prf -import {REACT_APP_BUILD} from '@env' - -if (typeof REACT_APP_BUILD !== 'string') { - throw new Error('ENV: No env provided') -} -if (!['dev', 'staging', 'prod'].includes(REACT_APP_BUILD)) { - throw new Error( - `ENV: Env must be "dev", "staging", or "prod," got "${REACT_APP_BUILD}"`, - ) -} - -export const BUILD = REACT_APP_BUILD diff --git a/src/env.ts b/src/env.ts deleted file mode 100644 index a379e435f..000000000 --- a/src/env.ts +++ /dev/null @@ -1,10 +0,0 @@ -if (typeof process.env.REACT_APP_BUILD !== 'string') { - throw new Error('ENV: No env provided') -} -if (!['dev', 'staging', 'prod'].includes(process.env.REACT_APP_BUILD)) { - throw new Error( - `ENV: Env must be "dev", "staging", or "prod," got "${process.env.REACT_APP_BUILD}"`, - ) -} - -export const BUILD = process.env.REACT_APP_BUILD diff --git a/src/state/index.ts b/src/state/index.ts index a886e7611..b16b51648 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -3,14 +3,12 @@ import {sessionClient as AtpApi} from '../third-party/api' import {RootStoreModel} from './models/root-store' import * as libapi from './lib/api' import * as storage from './lib/storage' -import {BUILD} from '../env' - -export const DEFAULT_SERVICE = - BUILD === 'prod' - ? 'http://localhost:2583' // TODO - : BUILD === 'staging' - ? 'https://pds.staging.bsky.dev' // TODO - : 'http://localhost:2583' + +export const IS_PROD_BUILD = true +export const LOCAL_DEV_SERVICE = 'http://localhost:2583' +export const STAGING_SERVICE = 'https://pds.staging.bsky.dev' +export const PROD_SERVICE = 'https://plc.bsky.social' +export const DEFAULT_SERVICE = IS_PROD_BUILD ? PROD_SERVICE : LOCAL_DEV_SERVICE const ROOT_STATE_STORAGE_KEY = 'root' const STATE_FETCH_INTERVAL = 15e3 diff --git a/src/state/models/shell-ui.ts b/src/state/models/shell-ui.ts index cc884f1c3..73b1bd56e 100644 --- a/src/state/models/shell-ui.ts +++ b/src/state/models/shell-ui.ts @@ -66,6 +66,17 @@ export class InviteToSceneModel { } } +export class ServerInputModel { + name = 'server-input' + + constructor( + public initialService: string, + public onSelect: (url: string) => void, + ) { + makeAutoObservable(this) + } +} + export interface ComposerOpts { replyTo?: Post.PostRef onPost?: () => void @@ -79,6 +90,7 @@ export class ShellUiModel { | SharePostModel | EditProfileModel | CreateSceneModel + | ServerInputModel | undefined isComposerActive = false composerOpts: ComposerOpts | undefined @@ -93,7 +105,8 @@ export class ShellUiModel { | ConfirmModel | SharePostModel | EditProfileModel - | CreateSceneModel, + | CreateSceneModel + | ServerInputModel, ) { this.isModalActive = true this.activeModal = modal diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx index f2c61a6ae..210cdc41f 100644 --- a/src/view/com/modals/Modal.tsx +++ b/src/view/com/modals/Modal.tsx @@ -13,6 +13,7 @@ import * as SharePostModal from './SharePost.native' import * as EditProfileModal from './EditProfile' import * as CreateSceneModal from './CreateScene' import * as InviteToSceneModal from './InviteToScene' +import * as ServerInputModal from './ServerInput' const CLOSED_SNAPPOINTS = ['10%'] @@ -77,6 +78,13 @@ export const Modal = observer(function Modal() { {...(store.shell.activeModal as models.InviteToSceneModel)} /> ) + } else if (store.shell.activeModal?.name === 'server-input') { + snapPoints = ServerInputModal.snapPoints + element = ( + <ServerInputModal.Component + {...(store.shell.activeModal as models.ServerInputModel)} + /> + ) } else { element = <View /> } diff --git a/src/view/com/modals/ServerInput.tsx b/src/view/com/modals/ServerInput.tsx new file mode 100644 index 000000000..1f3cc90f9 --- /dev/null +++ b/src/view/com/modals/ServerInput.tsx @@ -0,0 +1,140 @@ +import React, {useState} from 'react' +import Toast from '../util/Toast' +import {StyleSheet, Text, TextInput, TouchableOpacity, View} from 'react-native' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import LinearGradient from 'react-native-linear-gradient' +import {ErrorMessage} from '../util/ErrorMessage' +import {useStores} from '../../../state' +import {ProfileViewModel} from '../../../state/models/profile-view' +import {s, colors, gradients} from '../../lib/styles' +import {enforceLen, MAX_DISPLAY_NAME, MAX_DESCRIPTION} from '../../lib/strings' +import { + IS_PROD_BUILD, + LOCAL_DEV_SERVICE, + STAGING_SERVICE, + PROD_SERVICE, +} from '../../../state/index' + +export const snapPoints = ['80%'] + +export function Component({ + initialService, + onSelect, +}: { + initialService: string + onSelect: (url: string) => void +}) { + const store = useStores() + const [customUrl, setCustomUrl] = useState<string>('') + + const doSelect = (url: string) => { + if (!url.startsWith('http://') && !url.startsWith('https://')) { + url = `https://${url}` + } + store.shell.closeModal() + onSelect(url) + } + + return ( + <View style={s.flex1}> + <Text style={[s.textCenter, s.bold, s.f18]}>Choose Service</Text> + <View style={styles.inner}> + <View style={styles.group}> + {!IS_PROD_BUILD ? ( + <> + <TouchableOpacity + style={styles.btn} + onPress={() => doSelect(LOCAL_DEV_SERVICE)}> + <Text style={styles.btnText}>Local dev server</Text> + <FontAwesomeIcon icon="arrow-right" style={s.white} /> + </TouchableOpacity> + <TouchableOpacity + style={styles.btn} + onPress={() => doSelect(STAGING_SERVICE)}> + <Text style={styles.btnText}>Staging</Text> + <FontAwesomeIcon icon="arrow-right" style={s.white} /> + </TouchableOpacity> + </> + ) : undefined} + <TouchableOpacity + style={styles.btn} + onPress={() => doSelect(PROD_SERVICE)}> + <Text style={styles.btnText}>Bluesky.Social</Text> + <FontAwesomeIcon icon="arrow-right" style={s.white} /> + </TouchableOpacity> + </View> + <View style={styles.group}> + <Text style={styles.label}>Other service</Text> + <View style={{flexDirection: 'row'}}> + <TextInput + style={styles.textInput} + placeholder="e.g. https://bsky.app" + autoCapitalize="none" + autoComplete="off" + autoCorrect={false} + value={customUrl} + onChangeText={setCustomUrl} + /> + <TouchableOpacity + style={styles.textInputBtn} + onPress={() => doSelect(customUrl)}> + <FontAwesomeIcon + icon="check" + style={[s.black, {position: 'relative', top: 2}]} + size={18} + /> + </TouchableOpacity> + </View> + </View> + </View> + </View> + ) +} + +const styles = StyleSheet.create({ + inner: { + padding: 14, + }, + group: { + marginBottom: 20, + }, + label: { + fontWeight: 'bold', + paddingHorizontal: 4, + paddingBottom: 4, + }, + textInput: { + flex: 1, + borderWidth: 1, + borderColor: colors.gray3, + borderTopLeftRadius: 6, + borderBottomLeftRadius: 6, + paddingHorizontal: 14, + paddingVertical: 12, + fontSize: 16, + }, + textInputBtn: { + borderWidth: 1, + borderLeftWidth: 0, + borderColor: colors.gray3, + borderTopRightRadius: 6, + borderBottomRightRadius: 6, + paddingHorizontal: 14, + paddingVertical: 10, + }, + btn: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: colors.blue3, + borderRadius: 6, + paddingHorizontal: 14, + paddingVertical: 10, + marginBottom: 6, + }, + btnText: { + flex: 1, + fontSize: 18, + fontWeight: '500', + color: colors.white, + }, +}) diff --git a/src/view/index.ts b/src/view/index.ts index 341051d4e..78361e75b 100644 --- a/src/view/index.ts +++ b/src/view/index.ts @@ -5,6 +5,7 @@ import {faAngleDown} from '@fortawesome/free-solid-svg-icons/faAngleDown' import {faAngleLeft} from '@fortawesome/free-solid-svg-icons/faAngleLeft' import {faAngleRight} from '@fortawesome/free-solid-svg-icons/faAngleRight' import {faArrowLeft} from '@fortawesome/free-solid-svg-icons/faArrowLeft' +import {faArrowRight} from '@fortawesome/free-solid-svg-icons/faArrowRight' import {faArrowRightFromBracket} from '@fortawesome/free-solid-svg-icons' import {faArrowUpFromBracket} from '@fortawesome/free-solid-svg-icons/faArrowUpFromBracket' import {faArrowUpRightFromSquare} from '@fortawesome/free-solid-svg-icons/faArrowUpRightFromSquare' @@ -35,6 +36,7 @@ import {faLock} from '@fortawesome/free-solid-svg-icons/faLock' import {faMagnifyingGlass} from '@fortawesome/free-solid-svg-icons/faMagnifyingGlass' import {faMessage} from '@fortawesome/free-regular-svg-icons/faMessage' import {faNoteSticky} from '@fortawesome/free-solid-svg-icons/faNoteSticky' +import {faPen} from '@fortawesome/free-solid-svg-icons/faPen' import {faPenNib} from '@fortawesome/free-solid-svg-icons/faPenNib' import {faPenToSquare} from '@fortawesome/free-solid-svg-icons/faPenToSquare' import {faPlus} from '@fortawesome/free-solid-svg-icons/faPlus' @@ -59,6 +61,7 @@ export function setup() { faAngleLeft, faAngleRight, faArrowLeft, + faArrowRight, faArrowRightFromBracket, faArrowUpFromBracket, faArrowUpRightFromSquare, @@ -89,6 +92,7 @@ export function setup() { faMagnifyingGlass, faMessage, faNoteSticky, + faPen, faPenNib, faPenToSquare, faPlus, diff --git a/src/view/lib/strings.ts b/src/view/lib/strings.ts index 214bb51d6..05b23331f 100644 --- a/src/view/lib/strings.ts +++ b/src/view/lib/strings.ts @@ -1,5 +1,6 @@ import {AtUri} from '../../third-party/uri' import {Entity} from '../../third-party/api/src/client/types/app/bsky/feed/post' +import {PROD_SERVICE} from '../../state' export const MAX_DISPLAY_NAME = 64 export const MAX_DESCRIPTION = 256 @@ -106,3 +107,15 @@ export function cleanError(str: string): string { } return str } + +export function toNiceDomain(url: string): string { + try { + const urlp = new URL(url) + if (`https://${urlp.host}` === PROD_SERVICE) { + return 'Bluesky.Social' + } + return urlp.host + } catch (e) { + return url + } +} diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx index 036f7d148..04ce2d0cb 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -8,7 +8,6 @@ import {useStores} from '../../state' import {FeedModel} from '../../state/models/feed-view' import {ScreenParams} from '../routes' import {s} from '../lib/styles' -import {BUILD} from '../../env' export const Home = observer(function Home({ visible, @@ -57,10 +56,7 @@ export const Home = observer(function Home({ return ( <View style={s.flex1}> - <ViewHeader - title="Bluesky" - subtitle={`Private Beta${BUILD !== 'prod' ? ` [${BUILD}]` : ''}`} - /> + <ViewHeader title="Bluesky" subtitle="Private Beta" /> <Feed key="default" feed={defaultFeedView} diff --git a/src/view/screens/Login.tsx b/src/view/screens/Login.tsx index ac93613eb..328a56e9a 100644 --- a/src/view/screens/Login.tsx +++ b/src/view/screens/Login.tsx @@ -2,6 +2,7 @@ import React, {useState, useEffect} from 'react' import { ActivityIndicator, KeyboardAvoidingView, + ScrollView, StyleSheet, Text, TextInput, @@ -15,10 +16,10 @@ import * as EmailValidator from 'email-validator' import {observer} from 'mobx-react-lite' import {Picker} from '../com/util/Picker' import {s, colors} from '../lib/styles' -import {makeValidHandle, createFullHandle} from '../lib/strings' +import {makeValidHandle, createFullHandle, toNiceDomain} from '../lib/strings' import {useStores, DEFAULT_SERVICE} from '../../state' import {ServiceDescription} from '../../state/models/session' -import {BUILD} from '../../env' +import {ServerInputModel} from '../../state/models/shell-ui' enum ScreenState { SigninOrCreateAccount, @@ -72,9 +73,7 @@ const SigninOrCreateAccount = ({ <View style={styles.hero}> <Logo /> <Text style={styles.title}>Bluesky</Text> - <Text style={styles.subtitle}> - [ private beta {BUILD !== 'prod' ? `- ${BUILD} ` : ''}] - </Text> + <Text style={styles.subtitle}>[ private beta ]</Text> </View> <View style={s.flex1}> <TouchableOpacity style={styles.btn} onPress={onPressCreateAccount}> @@ -112,6 +111,7 @@ const SigninOrCreateAccount = ({ const Signin = ({onPressBack}: {onPressBack: () => void}) => { const store = useStores() const [isProcessing, setIsProcessing] = useState<boolean>(false) + const [serviceUrl, setServiceUrl] = useState<string>(DEFAULT_SERVICE) const [serviceDescription, setServiceDescription] = useState< ServiceDescription | undefined >(undefined) @@ -121,10 +121,9 @@ const Signin = ({onPressBack}: {onPressBack: () => void}) => { useEffect(() => { let aborted = false - if (serviceDescription || error) { - return - } - store.session.describeService(DEFAULT_SERVICE).then( + setError('') + console.log('Fetching service description', serviceUrl) + store.session.describeService(serviceUrl).then( desc => { if (aborted) return setServiceDescription(desc) @@ -140,7 +139,11 @@ const Signin = ({onPressBack}: {onPressBack: () => void}) => { return () => { aborted = true } - }, []) + }, [serviceUrl]) + + const onPressSelectService = () => { + store.shell.openModal(new ServerInputModel(serviceUrl, setServiceUrl)) + } const onPressNext = async () => { setError('') @@ -168,7 +171,7 @@ const Signin = ({onPressBack}: {onPressBack: () => void}) => { } await store.session.login({ - service: DEFAULT_SERVICE, + service: serviceUrl, handle: fullHandle, password, }) @@ -194,9 +197,14 @@ const Signin = ({onPressBack}: {onPressBack: () => void}) => { <Logo /> </View> <View style={styles.group}> - <View style={styles.groupTitle}> - <Text style={[s.white, s.f18, s.bold]}>Sign in</Text> - </View> + <TouchableOpacity + style={styles.groupTitle} + onPress={onPressSelectService}> + <Text style={[s.white, s.f18, s.bold]} numberOfLines={1}> + Sign in to {toNiceDomain(serviceUrl)} + </Text> + <FontAwesomeIcon icon="pen" size={10} style={styles.groupTitleIcon} /> + </TouchableOpacity> {error ? ( <View style={styles.error}> <View style={styles.errorIcon}> @@ -256,6 +264,7 @@ const Signin = ({onPressBack}: {onPressBack: () => void}) => { const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => { const store = useStores() const [isProcessing, setIsProcessing] = useState<boolean>(false) + const [serviceUrl, setServiceUrl] = useState<string>(DEFAULT_SERVICE) const [error, setError] = useState<string>('') const [serviceDescription, setServiceDescription] = useState< ServiceDescription | undefined @@ -268,10 +277,9 @@ const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => { useEffect(() => { let aborted = false - if (serviceDescription || error) { - return - } - store.session.describeService(DEFAULT_SERVICE).then( + setError('') + console.log('Fetching service description', serviceUrl) + store.session.describeService(serviceUrl).then( desc => { if (aborted) return setServiceDescription(desc) @@ -288,7 +296,11 @@ const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => { return () => { aborted = true } - }, []) + }, [serviceUrl]) + + const onPressSelectService = () => { + store.shell.openModal(new ServerInputModel(serviceUrl, setServiceUrl)) + } const onPressNext = async () => { if (!email) { @@ -307,7 +319,7 @@ const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => { setIsProcessing(true) try { await store.session.createAccount({ - service: DEFAULT_SERVICE, + service: serviceUrl, email, handle: createFullHandle(handle, userDomain), password, @@ -346,136 +358,164 @@ const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => { ) return ( - <KeyboardAvoidingView behavior="padding" style={{flex: 1}}> - <View style={styles.logoHero}> - <Logo /> - </View> - {serviceDescription ? ( - <> - {error ? ( - <View style={[styles.error, styles.errorFloating]}> - <View style={styles.errorIcon}> - <FontAwesomeIcon icon="exclamation" style={s.white} size={10} /> + <ScrollView style={{flex: 1}}> + <KeyboardAvoidingView behavior="padding" style={{flex: 1}}> + <View style={styles.logoHero}> + <Logo /> + </View> + {serviceDescription ? ( + <> + {error ? ( + <View style={[styles.error, styles.errorFloating]}> + <View style={styles.errorIcon}> + <FontAwesomeIcon + icon="exclamation" + style={s.white} + size={10} + /> + </View> + <View style={s.flex1}> + <Text style={[s.white, s.bold]}>{error}</Text> + </View> </View> - <View style={s.flex1}> - <Text style={[s.white, s.bold]}>{error}</Text> + ) : undefined} + <View style={[styles.group]}> + <View style={styles.groupTitle}> + <Text style={[s.white, s.f18, s.bold]}> + Create a new account + </Text> </View> - </View> - ) : undefined} - <View style={styles.group}> - <View style={styles.groupTitle}> - <Text style={[s.white, s.f18, s.bold]}>Create a new account</Text> - </View> - {serviceDescription?.inviteCodeRequired ? ( + <View style={styles.groupContent}> + <FontAwesomeIcon icon="globe" style={styles.groupContentIcon} /> + <TouchableOpacity + style={styles.textBtn} + onPress={onPressSelectService}> + <Text style={styles.textBtnLabel}> + {toNiceDomain(serviceUrl)} + </Text> + <FontAwesomeIcon + icon="pen" + size={12} + style={styles.textBtnIcon} + /> + </TouchableOpacity> + </View> + {serviceDescription?.inviteCodeRequired ? ( + <View style={styles.groupContent}> + <FontAwesomeIcon + icon="ticket" + style={styles.groupContentIcon} + /> + <TextInput + style={[styles.textInput]} + placeholder="Invite code" + placeholderTextColor={colors.blue0} + autoCapitalize="none" + autoCorrect={false} + autoFocus + value={inviteCode} + onChangeText={setInviteCode} + editable={!isProcessing} + /> + </View> + ) : undefined} <View style={styles.groupContent}> <FontAwesomeIcon - icon="ticket" + icon="envelope" style={styles.groupContentIcon} /> <TextInput style={[styles.textInput]} - placeholder="Invite code" + placeholder="Email address" placeholderTextColor={colors.blue0} autoCapitalize="none" autoCorrect={false} - autoFocus - value={inviteCode} - onChangeText={setInviteCode} + value={email} + onChangeText={setEmail} + editable={!isProcessing} + /> + </View> + <View style={styles.groupContent}> + <FontAwesomeIcon icon="lock" style={styles.groupContentIcon} /> + <TextInput + style={[styles.textInput]} + placeholder="Choose your password" + placeholderTextColor={colors.blue0} + autoCapitalize="none" + autoCorrect={false} + secureTextEntry + value={password} + onChangeText={setPassword} editable={!isProcessing} /> </View> - ) : undefined} - <View style={styles.groupContent}> - <FontAwesomeIcon - icon="envelope" - style={styles.groupContentIcon} - /> - <TextInput - style={[styles.textInput]} - placeholder="Email address" - placeholderTextColor={colors.blue0} - autoCapitalize="none" - autoCorrect={false} - value={email} - onChangeText={setEmail} - editable={!isProcessing} - /> - </View> - <View style={styles.groupContent}> - <FontAwesomeIcon icon="lock" style={styles.groupContentIcon} /> - <TextInput - style={[styles.textInput]} - placeholder="Choose your password" - placeholderTextColor={colors.blue0} - autoCapitalize="none" - autoCorrect={false} - secureTextEntry - value={password} - onChangeText={setPassword} - editable={!isProcessing} - /> - </View> - </View> - <View style={styles.group}> - <View style={styles.groupTitle}> - <Text style={[s.white, s.f18, s.bold]}>Choose your username</Text> - </View> - <View style={styles.groupContent}> - <FontAwesomeIcon icon="at" style={styles.groupContentIcon} /> - <TextInput - style={[styles.textInput]} - placeholder="eg alice" - placeholderTextColor={colors.blue0} - autoCapitalize="none" - value={handle} - onChangeText={v => setHandle(makeValidHandle(v))} - editable={!isProcessing} - /> </View> - {serviceDescription.availableUserDomains.length > 1 && ( + <View style={styles.group}> + <View style={styles.groupTitle}> + <Text style={[s.white, s.f18, s.bold]}> + Choose your username + </Text> + </View> <View style={styles.groupContent}> - <FontAwesomeIcon icon="globe" style={styles.groupContentIcon} /> - <Picker - style={styles.picker} - labelStyle={styles.pickerLabel} - iconStyle={styles.pickerIcon} - value={userDomain} - items={serviceDescription.availableUserDomains.map(d => ({ - label: `.${d}`, - value: d, - }))} - onChange={itemValue => setUserDomain(itemValue)} - enabled={!isProcessing} + <FontAwesomeIcon icon="at" style={styles.groupContentIcon} /> + <TextInput + style={[styles.textInput]} + placeholder="eg alice" + placeholderTextColor={colors.blue0} + autoCapitalize="none" + value={handle} + onChangeText={v => setHandle(makeValidHandle(v))} + editable={!isProcessing} /> </View> - )} - <View style={styles.groupContent}> - <Text style={[s.white, s.p10]}> - Your full username will be{' '} - <Text style={s.bold}> - @{createFullHandle(handle, userDomain)} + {serviceDescription.availableUserDomains.length > 1 && ( + <View style={styles.groupContent}> + <FontAwesomeIcon + icon="globe" + style={styles.groupContentIcon} + /> + <Picker + style={styles.picker} + labelStyle={styles.pickerLabel} + iconStyle={styles.pickerIcon} + value={userDomain} + items={serviceDescription.availableUserDomains.map(d => ({ + label: `.${d}`, + value: d, + }))} + onChange={itemValue => setUserDomain(itemValue)} + enabled={!isProcessing} + /> + </View> + )} + <View style={styles.groupContent}> + <Text style={[s.white, s.p10]}> + Your full username will be{' '} + <Text style={s.bold}> + @{createFullHandle(handle, userDomain)} + </Text> </Text> - </Text> + </View> </View> - </View> - <View style={[s.flexRow, s.pl20, s.pr20]}> - <TouchableOpacity onPress={onPressBack}> - <Text style={[s.white, s.f18, s.pl5]}>Back</Text> - </TouchableOpacity> - <View style={s.flex1} /> - <TouchableOpacity onPress={onPressNext}> - {isProcessing ? ( - <ActivityIndicator color="#fff" /> - ) : ( - <Text style={[s.white, s.f18, s.bold, s.pr5]}>Next</Text> - )} - </TouchableOpacity> - </View> - </> - ) : ( - <InitialLoadView /> - )} - </KeyboardAvoidingView> + <View style={[s.flexRow, s.pl20, s.pr20, {paddingBottom: 200}]}> + <TouchableOpacity onPress={onPressBack}> + <Text style={[s.white, s.f18, s.pl5]}>Back</Text> + </TouchableOpacity> + <View style={s.flex1} /> + <TouchableOpacity onPress={onPressNext}> + {isProcessing ? ( + <ActivityIndicator color="#fff" /> + ) : ( + <Text style={[s.white, s.f18, s.bold, s.pr5]}>Next</Text> + )} + </TouchableOpacity> + </View> + </> + ) : ( + <InitialLoadView /> + )} + </KeyboardAvoidingView> + </ScrollView> ) } @@ -577,9 +617,15 @@ const styles = StyleSheet.create({ backgroundColor: colors.blue3, }, groupTitle: { + flexDirection: 'row', + alignItems: 'center', paddingVertical: 8, paddingHorizontal: 12, }, + groupTitleIcon: { + color: colors.white, + marginHorizontal: 6, + }, groupContent: { borderTopWidth: 1, borderTopColor: colors.blue1, @@ -600,6 +646,22 @@ const styles = StyleSheet.create({ fontSize: 18, borderRadius: 10, }, + textBtn: { + flexDirection: 'row', + flex: 1, + alignItems: 'center', + }, + textBtnLabel: { + flex: 1, + color: colors.white, + paddingVertical: 10, + paddingHorizontal: 12, + fontSize: 18, + }, + textBtnIcon: { + color: colors.white, + marginHorizontal: 12, + }, picker: { flex: 1, width: '100%', diff --git a/src/view/shell/mobile/index.tsx b/src/view/shell/mobile/index.tsx index 712d6dc23..96390e9b8 100644 --- a/src/view/shell/mobile/index.tsx +++ b/src/view/shell/mobile/index.tsx @@ -170,6 +170,7 @@ export const MobileShell: React.FC = observer(() => { <SafeAreaView style={styles.innerContainer}> <Login /> </SafeAreaView> + <Modal /> </LinearGradient> ) } @@ -294,6 +295,7 @@ function constructScreenRenderDesc(nav: NavigationModel): { const styles = StyleSheet.create({ outerContainer: { height: '100%', + flex: 1, }, innerContainer: { flex: 1, diff --git a/todos.txt b/todos.txt index e9879bf4c..da966aa24 100644 --- a/todos.txt +++ b/todos.txt @@ -6,6 +6,7 @@ Paul's todo list - Cursor behaviors on all views - Update swipe behaviors: edge always goes back, leftmost always goes back, main connects to selector if present - Onboarding flow + > Invite codes - Confirm email - Setup profile? - Onboarding @@ -23,7 +24,7 @@ Paul's todo list - View on post - Linking - Web linking - - App linking + > App linking - Pagination - Liked by - Reposted by @@ -33,7 +34,4 @@ Paul's todo list - Bugs - Auth token refresh seems broken - Check that sub components arent reloading too much - - Titles are getting screwed up (possibly swipe related) - > Dont suggest self for follows - > Double post on post? - > Handle no displayname everywhere \ No newline at end of file + - Titles are getting screwed up (possibly swipe related) \ No newline at end of file |