tooot/src/screens/Me/Root/Login.tsx

347 lines
10 KiB
TypeScript

import React, { useCallback, useEffect, useState } from 'react'
import {
Dimensions,
Image,
StyleSheet,
Text,
TextInput,
View
} from 'react-native'
import { useQuery } from 'react-query'
import { debounce } from 'lodash'
import { instanceFetch } from '@utils/fetches/instanceFetch'
import * as AuthSession from 'expo-auth-session'
import { useDispatch } from 'react-redux'
import { updateLocal } from '@utils/slices/instancesSlice'
import { useNavigation } from '@react-navigation/native'
import { useTheme } from '@utils/styles/ThemeManager'
import { useTranslation } from 'react-i18next'
import { StyleConstants } from '@utils/styles/constants'
import { ButtonRow } from '@components/Button'
import ParseContent from '@root/components/ParseContent'
import ShimmerPlaceholder from 'react-native-shimmer-placeholder'
import { Feather } from '@expo/vector-icons'
import { applicationFetch } from '@root/utils/fetches/applicationFetch'
const Login: React.FC = () => {
const { t } = useTranslation('meRoot')
const { theme } = useTheme()
const navigation = useNavigation()
const dispatch = useDispatch()
const [instanceDomain, setInstanceDomain] = useState<string | undefined>()
const [applicationData, setApplicationData] = useState<{
clientId: string
clientSecret: string
}>()
const instanceQuery = useQuery(
['Instance', { instanceDomain }],
instanceFetch,
{
enabled: false,
retry: false
}
)
const applicationQuery = useQuery(
['Application', { instanceDomain }],
applicationFetch,
{
enabled: false,
retry: false
}
)
useEffect(() => {
if (
applicationQuery.data?.client_id.length &&
applicationQuery.data?.client_secret.length
) {
setApplicationData({
clientId: applicationQuery.data.client_id,
clientSecret: applicationQuery.data.client_secret
})
}
}, [applicationQuery.data?.client_id])
const [request, response, promptAsync] = AuthSession.useAuthRequest(
{
clientId: applicationData?.clientId!,
clientSecret: applicationData?.clientSecret,
scopes: ['read', 'write', 'follow', 'push'],
redirectUri: 'exp://127.0.0.1:19000'
},
{
authorizationEndpoint: `https://${instanceDomain}/oauth/authorize`
}
)
useEffect(() => {
;(async () => {
if (request?.clientId) {
await promptAsync()
}
})()
}, [request])
useEffect(() => {
;(async () => {
if (response?.type === 'success') {
const { accessToken } = await AuthSession.exchangeCodeAsync(
{
clientId: applicationData?.clientId!,
clientSecret: applicationData?.clientSecret,
scopes: ['read', 'write', 'follow', 'push'],
redirectUri: 'exp://127.0.0.1:19000',
code: response.params.code,
extraParams: {
grant_type: 'authorization_code'
}
},
{
tokenEndpoint: `https://${instanceDomain}/oauth/token`
}
)
dispatch(updateLocal({ url: instanceDomain, token: accessToken }))
navigation.navigate('Screen-Local')
}
})()
}, [response])
const onChangeText = useCallback(
debounce(
text => {
setInstanceDomain(text)
setApplicationData(undefined)
if (text) {
instanceQuery.refetch()
}
},
1000,
{
trailing: true
}
),
[]
)
const instanceInfo = useCallback(
({
header,
content,
parse
}: {
header: string
content: string
parse?: boolean
}) => {
return (
<View style={styles.instanceInfo}>
<Text style={[styles.instanceInfoHeader, { color: theme.primary }]}>
{header}
</Text>
<ShimmerPlaceholder
visible={instanceQuery.data?.uri}
stopAutoRun
width={
Dimensions.get('screen').width -
StyleConstants.Spacing.Global.PagePadding * 4
}
height={StyleConstants.Font.Size.M}
>
<Text
style={[styles.instanceInfoContent, { color: theme.primary }]}
>
{parse ? (
<ParseContent content={content} size={'M'} numberOfLines={5} />
) : (
content
)}
</Text>
</ShimmerPlaceholder>
</View>
)
},
[instanceQuery.data?.uri]
)
return (
<>
<View style={{ flexDirection: 'row' }}>
<Image
source={require('assets/screens/meRoot/welcome.png')}
style={{ resizeMode: 'contain', flex: 1, aspectRatio: 16 / 9 }}
/>
</View>
<View style={styles.base}>
<View style={styles.inputRow}>
<TextInput
style={[
styles.textInput,
{
color: theme.primary,
borderBottomColor: theme.secondary
}
]}
onChangeText={onChangeText}
autoCapitalize='none'
autoCorrect={false}
clearButtonMode='never'
keyboardType='url'
textContentType='URL'
onSubmitEditing={() =>
instanceQuery.isSuccess &&
instanceQuery.data &&
instanceQuery.data.uri &&
applicationQuery.refetch()
}
placeholder={t('content.login.server.placeholder')}
placeholderTextColor={theme.secondary}
returnKeyType='go'
/>
<ButtonRow
onPress={() => {
applicationQuery.refetch()
}}
{...(instanceQuery.isFetching || applicationQuery.isFetching
? { icon: 'loader' }
: { text: t('content.login.button') as string })}
disabled={
!instanceQuery.data?.uri ||
instanceQuery.isFetching ||
applicationQuery.isFetching
}
/>
</View>
<View>
{instanceInfo({
header: '实例名称',
content: instanceQuery.data?.title
})}
{instanceInfo({
header: '实例介绍',
content: instanceQuery.data?.short_description,
parse: true
})}
<View style={styles.instanceStats}>
<View style={styles.instanceStat}>
<Text
style={[styles.instanceInfoHeader, { color: theme.primary }]}
>
</Text>
<ShimmerPlaceholder
visible={instanceQuery.data?.stats?.user_count}
stopAutoRun
width={StyleConstants.Font.Size.M * 4}
height={StyleConstants.Font.Size.M}
>
<Text
style={[styles.instanceInfoContent, { color: theme.primary }]}
>
{instanceQuery.data?.stats?.user_count}
</Text>
</ShimmerPlaceholder>
</View>
<View style={[styles.instanceStat, { alignItems: 'center' }]}>
<Text
style={[styles.instanceInfoHeader, { color: theme.primary }]}
>
</Text>
<ShimmerPlaceholder
visible={instanceQuery.data?.stats?.user_count}
stopAutoRun
width={StyleConstants.Font.Size.M * 4}
height={StyleConstants.Font.Size.M}
>
<Text
style={[styles.instanceInfoContent, { color: theme.primary }]}
>
{instanceQuery.data?.stats?.status_count}
</Text>
</ShimmerPlaceholder>
</View>
<View style={[styles.instanceStat, { alignItems: 'flex-end' }]}>
<Text
style={[styles.instanceInfoHeader, { color: theme.primary }]}
>
</Text>
<ShimmerPlaceholder
visible={instanceQuery.data?.stats?.user_count}
stopAutoRun
width={StyleConstants.Font.Size.M * 4}
height={StyleConstants.Font.Size.M}
>
<Text
style={[styles.instanceInfoContent, { color: theme.primary }]}
>
{instanceQuery.data?.stats?.domain_count}
</Text>
</ShimmerPlaceholder>
</View>
</View>
<Text style={[styles.disclaimer, { color: theme.secondary }]}>
<Feather
name='lock'
size={StyleConstants.Font.Size.M}
color={theme.secondary}
/>{' '}
</Text>
</View>
</View>
</>
)
}
const styles = StyleSheet.create({
base: {
padding: StyleConstants.Spacing.Global.PagePadding
},
inputRow: {
flex: 1,
flexDirection: 'row'
},
textInput: {
flex: 1,
borderBottomWidth: 1.25,
paddingTop: StyleConstants.Spacing.S - 1.5,
paddingBottom: StyleConstants.Spacing.S - 1.5,
paddingLeft: StyleConstants.Spacing.Global.PagePadding,
paddingRight: StyleConstants.Spacing.Global.PagePadding,
fontSize: StyleConstants.Font.Size.M,
marginRight: StyleConstants.Spacing.M
},
instanceInfo: {
marginTop: StyleConstants.Spacing.M,
paddingLeft: StyleConstants.Spacing.Global.PagePadding,
paddingRight: StyleConstants.Spacing.Global.PagePadding
},
instanceInfoHeader: {
fontSize: StyleConstants.Font.Size.S,
fontWeight: StyleConstants.Font.Weight.Bold,
marginBottom: StyleConstants.Spacing.XS
},
instanceInfoContent: { fontSize: StyleConstants.Font.Size.M },
instanceStats: {
flex: 1,
flexDirection: 'row',
marginTop: StyleConstants.Spacing.M,
paddingLeft: StyleConstants.Spacing.Global.PagePadding,
paddingRight: StyleConstants.Spacing.Global.PagePadding,
marginBottom: StyleConstants.Spacing.M
},
instanceStat: {
flex: 1
},
disclaimer: {
fontSize: StyleConstants.Font.Size.S,
paddingLeft: StyleConstants.Spacing.Global.PagePadding,
paddingRight: StyleConstants.Spacing.Global.PagePadding,
marginBottom: StyleConstants.Spacing.M
}
})
export default Login