Add a server instance selector and drop env vars

zio/stable
Paul Frazee 2022-11-15 15:09:50 -06:00
parent 9a6df95ade
commit 3725a2eed1
14 changed files with 383 additions and 174 deletions

View File

@ -1 +0,0 @@
REACT_APP_BUILD = 'staging'

View File

@ -1 +0,0 @@
REACT_APP_BUILD = 'staging'

View File

@ -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

View File

@ -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

View File

@ -3,14 +3,12 @@ import {sessionClient as AtpApi} from '../third-party/api'
import {RootStoreModel} from './models/root-store' import {RootStoreModel} from './models/root-store'
import * as libapi from './lib/api' import * as libapi from './lib/api'
import * as storage from './lib/storage' import * as storage from './lib/storage'
import {BUILD} from '../env'
export const DEFAULT_SERVICE = export const IS_PROD_BUILD = true
BUILD === 'prod' export const LOCAL_DEV_SERVICE = 'http://localhost:2583'
? 'http://localhost:2583' // TODO export const STAGING_SERVICE = 'https://pds.staging.bsky.dev'
: BUILD === 'staging' export const PROD_SERVICE = 'https://plc.bsky.social'
? 'https://pds.staging.bsky.dev' // TODO export const DEFAULT_SERVICE = IS_PROD_BUILD ? PROD_SERVICE : LOCAL_DEV_SERVICE
: 'http://localhost:2583'
const ROOT_STATE_STORAGE_KEY = 'root' const ROOT_STATE_STORAGE_KEY = 'root'
const STATE_FETCH_INTERVAL = 15e3 const STATE_FETCH_INTERVAL = 15e3

View File

@ -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 { export interface ComposerOpts {
replyTo?: Post.PostRef replyTo?: Post.PostRef
onPost?: () => void onPost?: () => void
@ -79,6 +90,7 @@ export class ShellUiModel {
| SharePostModel | SharePostModel
| EditProfileModel | EditProfileModel
| CreateSceneModel | CreateSceneModel
| ServerInputModel
| undefined | undefined
isComposerActive = false isComposerActive = false
composerOpts: ComposerOpts | undefined composerOpts: ComposerOpts | undefined
@ -93,7 +105,8 @@ export class ShellUiModel {
| ConfirmModel | ConfirmModel
| SharePostModel | SharePostModel
| EditProfileModel | EditProfileModel
| CreateSceneModel, | CreateSceneModel
| ServerInputModel,
) { ) {
this.isModalActive = true this.isModalActive = true
this.activeModal = modal this.activeModal = modal

View File

@ -13,6 +13,7 @@ import * as SharePostModal from './SharePost.native'
import * as EditProfileModal from './EditProfile' import * as EditProfileModal from './EditProfile'
import * as CreateSceneModal from './CreateScene' import * as CreateSceneModal from './CreateScene'
import * as InviteToSceneModal from './InviteToScene' import * as InviteToSceneModal from './InviteToScene'
import * as ServerInputModal from './ServerInput'
const CLOSED_SNAPPOINTS = ['10%'] const CLOSED_SNAPPOINTS = ['10%']
@ -77,6 +78,13 @@ export const Modal = observer(function Modal() {
{...(store.shell.activeModal as models.InviteToSceneModel)} {...(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 { } else {
element = <View /> element = <View />
} }

View File

@ -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,
},
})

View File

@ -5,6 +5,7 @@ import {faAngleDown} from '@fortawesome/free-solid-svg-icons/faAngleDown'
import {faAngleLeft} from '@fortawesome/free-solid-svg-icons/faAngleLeft' import {faAngleLeft} from '@fortawesome/free-solid-svg-icons/faAngleLeft'
import {faAngleRight} from '@fortawesome/free-solid-svg-icons/faAngleRight' import {faAngleRight} from '@fortawesome/free-solid-svg-icons/faAngleRight'
import {faArrowLeft} from '@fortawesome/free-solid-svg-icons/faArrowLeft' 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 {faArrowRightFromBracket} from '@fortawesome/free-solid-svg-icons'
import {faArrowUpFromBracket} from '@fortawesome/free-solid-svg-icons/faArrowUpFromBracket' import {faArrowUpFromBracket} from '@fortawesome/free-solid-svg-icons/faArrowUpFromBracket'
import {faArrowUpRightFromSquare} from '@fortawesome/free-solid-svg-icons/faArrowUpRightFromSquare' 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 {faMagnifyingGlass} from '@fortawesome/free-solid-svg-icons/faMagnifyingGlass'
import {faMessage} from '@fortawesome/free-regular-svg-icons/faMessage' import {faMessage} from '@fortawesome/free-regular-svg-icons/faMessage'
import {faNoteSticky} from '@fortawesome/free-solid-svg-icons/faNoteSticky' 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 {faPenNib} from '@fortawesome/free-solid-svg-icons/faPenNib'
import {faPenToSquare} from '@fortawesome/free-solid-svg-icons/faPenToSquare' import {faPenToSquare} from '@fortawesome/free-solid-svg-icons/faPenToSquare'
import {faPlus} from '@fortawesome/free-solid-svg-icons/faPlus' import {faPlus} from '@fortawesome/free-solid-svg-icons/faPlus'
@ -59,6 +61,7 @@ export function setup() {
faAngleLeft, faAngleLeft,
faAngleRight, faAngleRight,
faArrowLeft, faArrowLeft,
faArrowRight,
faArrowRightFromBracket, faArrowRightFromBracket,
faArrowUpFromBracket, faArrowUpFromBracket,
faArrowUpRightFromSquare, faArrowUpRightFromSquare,
@ -89,6 +92,7 @@ export function setup() {
faMagnifyingGlass, faMagnifyingGlass,
faMessage, faMessage,
faNoteSticky, faNoteSticky,
faPen,
faPenNib, faPenNib,
faPenToSquare, faPenToSquare,
faPlus, faPlus,

View File

@ -1,5 +1,6 @@
import {AtUri} from '../../third-party/uri' import {AtUri} from '../../third-party/uri'
import {Entity} from '../../third-party/api/src/client/types/app/bsky/feed/post' 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_DISPLAY_NAME = 64
export const MAX_DESCRIPTION = 256 export const MAX_DESCRIPTION = 256
@ -106,3 +107,15 @@ export function cleanError(str: string): string {
} }
return str 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
}
}

View File

@ -8,7 +8,6 @@ import {useStores} from '../../state'
import {FeedModel} from '../../state/models/feed-view' import {FeedModel} from '../../state/models/feed-view'
import {ScreenParams} from '../routes' import {ScreenParams} from '../routes'
import {s} from '../lib/styles' import {s} from '../lib/styles'
import {BUILD} from '../../env'
export const Home = observer(function Home({ export const Home = observer(function Home({
visible, visible,
@ -57,10 +56,7 @@ export const Home = observer(function Home({
return ( return (
<View style={s.flex1}> <View style={s.flex1}>
<ViewHeader <ViewHeader title="Bluesky" subtitle="Private Beta" />
title="Bluesky"
subtitle={`Private Beta${BUILD !== 'prod' ? ` [${BUILD}]` : ''}`}
/>
<Feed <Feed
key="default" key="default"
feed={defaultFeedView} feed={defaultFeedView}

View File

@ -2,6 +2,7 @@ import React, {useState, useEffect} from 'react'
import { import {
ActivityIndicator, ActivityIndicator,
KeyboardAvoidingView, KeyboardAvoidingView,
ScrollView,
StyleSheet, StyleSheet,
Text, Text,
TextInput, TextInput,
@ -15,10 +16,10 @@ import * as EmailValidator from 'email-validator'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import {Picker} from '../com/util/Picker' import {Picker} from '../com/util/Picker'
import {s, colors} from '../lib/styles' 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 {useStores, DEFAULT_SERVICE} from '../../state'
import {ServiceDescription} from '../../state/models/session' import {ServiceDescription} from '../../state/models/session'
import {BUILD} from '../../env' import {ServerInputModel} from '../../state/models/shell-ui'
enum ScreenState { enum ScreenState {
SigninOrCreateAccount, SigninOrCreateAccount,
@ -72,9 +73,7 @@ const SigninOrCreateAccount = ({
<View style={styles.hero}> <View style={styles.hero}>
<Logo /> <Logo />
<Text style={styles.title}>Bluesky</Text> <Text style={styles.title}>Bluesky</Text>
<Text style={styles.subtitle}> <Text style={styles.subtitle}>[ private beta ]</Text>
[ private beta {BUILD !== 'prod' ? `- ${BUILD} ` : ''}]
</Text>
</View> </View>
<View style={s.flex1}> <View style={s.flex1}>
<TouchableOpacity style={styles.btn} onPress={onPressCreateAccount}> <TouchableOpacity style={styles.btn} onPress={onPressCreateAccount}>
@ -112,6 +111,7 @@ const SigninOrCreateAccount = ({
const Signin = ({onPressBack}: {onPressBack: () => void}) => { const Signin = ({onPressBack}: {onPressBack: () => void}) => {
const store = useStores() const store = useStores()
const [isProcessing, setIsProcessing] = useState<boolean>(false) const [isProcessing, setIsProcessing] = useState<boolean>(false)
const [serviceUrl, setServiceUrl] = useState<string>(DEFAULT_SERVICE)
const [serviceDescription, setServiceDescription] = useState< const [serviceDescription, setServiceDescription] = useState<
ServiceDescription | undefined ServiceDescription | undefined
>(undefined) >(undefined)
@ -121,10 +121,9 @@ const Signin = ({onPressBack}: {onPressBack: () => void}) => {
useEffect(() => { useEffect(() => {
let aborted = false let aborted = false
if (serviceDescription || error) { setError('')
return console.log('Fetching service description', serviceUrl)
} store.session.describeService(serviceUrl).then(
store.session.describeService(DEFAULT_SERVICE).then(
desc => { desc => {
if (aborted) return if (aborted) return
setServiceDescription(desc) setServiceDescription(desc)
@ -140,7 +139,11 @@ const Signin = ({onPressBack}: {onPressBack: () => void}) => {
return () => { return () => {
aborted = true aborted = true
} }
}, []) }, [serviceUrl])
const onPressSelectService = () => {
store.shell.openModal(new ServerInputModel(serviceUrl, setServiceUrl))
}
const onPressNext = async () => { const onPressNext = async () => {
setError('') setError('')
@ -168,7 +171,7 @@ const Signin = ({onPressBack}: {onPressBack: () => void}) => {
} }
await store.session.login({ await store.session.login({
service: DEFAULT_SERVICE, service: serviceUrl,
handle: fullHandle, handle: fullHandle,
password, password,
}) })
@ -194,9 +197,14 @@ const Signin = ({onPressBack}: {onPressBack: () => void}) => {
<Logo /> <Logo />
</View> </View>
<View style={styles.group}> <View style={styles.group}>
<View style={styles.groupTitle}> <TouchableOpacity
<Text style={[s.white, s.f18, s.bold]}>Sign in</Text> style={styles.groupTitle}
</View> 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 ? ( {error ? (
<View style={styles.error}> <View style={styles.error}>
<View style={styles.errorIcon}> <View style={styles.errorIcon}>
@ -256,6 +264,7 @@ const Signin = ({onPressBack}: {onPressBack: () => void}) => {
const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => { const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => {
const store = useStores() const store = useStores()
const [isProcessing, setIsProcessing] = useState<boolean>(false) const [isProcessing, setIsProcessing] = useState<boolean>(false)
const [serviceUrl, setServiceUrl] = useState<string>(DEFAULT_SERVICE)
const [error, setError] = useState<string>('') const [error, setError] = useState<string>('')
const [serviceDescription, setServiceDescription] = useState< const [serviceDescription, setServiceDescription] = useState<
ServiceDescription | undefined ServiceDescription | undefined
@ -268,10 +277,9 @@ const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => {
useEffect(() => { useEffect(() => {
let aborted = false let aborted = false
if (serviceDescription || error) { setError('')
return console.log('Fetching service description', serviceUrl)
} store.session.describeService(serviceUrl).then(
store.session.describeService(DEFAULT_SERVICE).then(
desc => { desc => {
if (aborted) return if (aborted) return
setServiceDescription(desc) setServiceDescription(desc)
@ -288,7 +296,11 @@ const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => {
return () => { return () => {
aborted = true aborted = true
} }
}, []) }, [serviceUrl])
const onPressSelectService = () => {
store.shell.openModal(new ServerInputModel(serviceUrl, setServiceUrl))
}
const onPressNext = async () => { const onPressNext = async () => {
if (!email) { if (!email) {
@ -307,7 +319,7 @@ const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => {
setIsProcessing(true) setIsProcessing(true)
try { try {
await store.session.createAccount({ await store.session.createAccount({
service: DEFAULT_SERVICE, service: serviceUrl,
email, email,
handle: createFullHandle(handle, userDomain), handle: createFullHandle(handle, userDomain),
password, password,
@ -346,136 +358,164 @@ const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => {
) )
return ( return (
<KeyboardAvoidingView behavior="padding" style={{flex: 1}}> <ScrollView style={{flex: 1}}>
<View style={styles.logoHero}> <KeyboardAvoidingView behavior="padding" style={{flex: 1}}>
<Logo /> <View style={styles.logoHero}>
</View> <Logo />
{serviceDescription ? ( </View>
<> {serviceDescription ? (
{error ? ( <>
<View style={[styles.error, styles.errorFloating]}> {error ? (
<View style={styles.errorIcon}> <View style={[styles.error, styles.errorFloating]}>
<FontAwesomeIcon icon="exclamation" style={s.white} size={10} /> <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>
<View style={s.flex1}> ) : undefined}
<Text style={[s.white, s.bold]}>{error}</Text> <View style={[styles.group]}>
<View style={styles.groupTitle}>
<Text style={[s.white, s.f18, s.bold]}>
Create a new account
</Text>
</View> </View>
</View> <View style={styles.groupContent}>
) : undefined} <FontAwesomeIcon icon="globe" style={styles.groupContentIcon} />
<View style={styles.group}> <TouchableOpacity
<View style={styles.groupTitle}> style={styles.textBtn}
<Text style={[s.white, s.f18, s.bold]}>Create a new account</Text> onPress={onPressSelectService}>
</View> <Text style={styles.textBtnLabel}>
{serviceDescription?.inviteCodeRequired ? ( {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}> <View style={styles.groupContent}>
<FontAwesomeIcon <FontAwesomeIcon
icon="ticket" icon="envelope"
style={styles.groupContentIcon} style={styles.groupContentIcon}
/> />
<TextInput <TextInput
style={[styles.textInput]} style={[styles.textInput]}
placeholder="Invite code" placeholder="Email address"
placeholderTextColor={colors.blue0} placeholderTextColor={colors.blue0}
autoCapitalize="none" autoCapitalize="none"
autoCorrect={false} autoCorrect={false}
autoFocus value={email}
value={inviteCode} onChangeText={setEmail}
onChangeText={setInviteCode}
editable={!isProcessing} editable={!isProcessing}
/> />
</View> </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.groupContent}> <View style={styles.groupContent}>
<FontAwesomeIcon icon="globe" style={styles.groupContentIcon} /> <FontAwesomeIcon icon="lock" style={styles.groupContentIcon} />
<Picker <TextInput
style={styles.picker} style={[styles.textInput]}
labelStyle={styles.pickerLabel} placeholder="Choose your password"
iconStyle={styles.pickerIcon} placeholderTextColor={colors.blue0}
value={userDomain} autoCapitalize="none"
items={serviceDescription.availableUserDomains.map(d => ({ autoCorrect={false}
label: `.${d}`, secureTextEntry
value: d, value={password}
}))} onChangeText={setPassword}
onChange={itemValue => setUserDomain(itemValue)} editable={!isProcessing}
enabled={!isProcessing}
/> />
</View> </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>
</View> </View>
</View> <View style={styles.group}>
<View style={[s.flexRow, s.pl20, s.pr20]}> <View style={styles.groupTitle}>
<TouchableOpacity onPress={onPressBack}> <Text style={[s.white, s.f18, s.bold]}>
<Text style={[s.white, s.f18, s.pl5]}>Back</Text> Choose your username
</TouchableOpacity> </Text>
<View style={s.flex1} /> </View>
<TouchableOpacity onPress={onPressNext}> <View style={styles.groupContent}>
{isProcessing ? ( <FontAwesomeIcon icon="at" style={styles.groupContentIcon} />
<ActivityIndicator color="#fff" /> <TextInput
) : ( style={[styles.textInput]}
<Text style={[s.white, s.f18, s.bold, s.pr5]}>Next</Text> 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.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>
)} )}
</TouchableOpacity> <View style={styles.groupContent}>
</View> <Text style={[s.white, s.p10]}>
</> Your full username will be{' '}
) : ( <Text style={s.bold}>
<InitialLoadView /> @{createFullHandle(handle, userDomain)}
)} </Text>
</KeyboardAvoidingView> </Text>
</View>
</View>
<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, backgroundColor: colors.blue3,
}, },
groupTitle: { groupTitle: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 8, paddingVertical: 8,
paddingHorizontal: 12, paddingHorizontal: 12,
}, },
groupTitleIcon: {
color: colors.white,
marginHorizontal: 6,
},
groupContent: { groupContent: {
borderTopWidth: 1, borderTopWidth: 1,
borderTopColor: colors.blue1, borderTopColor: colors.blue1,
@ -600,6 +646,22 @@ const styles = StyleSheet.create({
fontSize: 18, fontSize: 18,
borderRadius: 10, 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: { picker: {
flex: 1, flex: 1,
width: '100%', width: '100%',

View File

@ -170,6 +170,7 @@ export const MobileShell: React.FC = observer(() => {
<SafeAreaView style={styles.innerContainer}> <SafeAreaView style={styles.innerContainer}>
<Login /> <Login />
</SafeAreaView> </SafeAreaView>
<Modal />
</LinearGradient> </LinearGradient>
) )
} }
@ -294,6 +295,7 @@ function constructScreenRenderDesc(nav: NavigationModel): {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
outerContainer: { outerContainer: {
height: '100%', height: '100%',
flex: 1,
}, },
innerContainer: { innerContainer: {
flex: 1, flex: 1,

View File

@ -6,6 +6,7 @@ Paul's todo list
- Cursor behaviors on all views - Cursor behaviors on all views
- Update swipe behaviors: edge always goes back, leftmost always goes back, main connects to selector if present - Update swipe behaviors: edge always goes back, leftmost always goes back, main connects to selector if present
- Onboarding flow - Onboarding flow
> Invite codes
- Confirm email - Confirm email
- Setup profile? - Setup profile?
- Onboarding - Onboarding
@ -23,7 +24,7 @@ Paul's todo list
- View on post - View on post
- Linking - Linking
- Web linking - Web linking
- App linking > App linking
- Pagination - Pagination
- Liked by - Liked by
- Reposted by - Reposted by
@ -33,7 +34,4 @@ Paul's todo list
- Bugs - Bugs
- Auth token refresh seems broken - Auth token refresh seems broken
- Check that sub components arent reloading too much - Check that sub components arent reloading too much
- Titles are getting screwed up (possibly swipe related) - Titles are getting screwed up (possibly swipe related)
> Dont suggest self for follows
> Double post on post?
> Handle no displayname everywhere