Restyle login

This commit is contained in:
Zhiyuan Zheng 2020-12-13 21:09:21 +01:00
parent 48cab6053c
commit dc9870beaf
No known key found for this signature in database
GPG Key ID: 078A93AB607D85E0
16 changed files with 313 additions and 83 deletions

View File

@ -4,8 +4,8 @@ import { QueryCache, ReactQueryCacheProvider, setConsole } from 'react-query'
import { Provider } from 'react-redux' import { Provider } from 'react-redux'
import { PersistGate } from 'redux-persist/integration/react' import { PersistGate } from 'redux-persist/integration/react'
import { Index } from './src/Index' import { Index } from '@root/Index'
import { persistor, store } from './src/store' import { persistor, store } from '@root/store'
import ThemeManager from '@utils/styles/ThemeManager' import ThemeManager from '@utils/styles/ThemeManager'
const queryCache = new QueryCache() const queryCache = new QueryCache()

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 KiB

View File

@ -9,6 +9,7 @@ module.exports = function (api) {
{ {
root: ['./'], root: ['./'],
alias: { alias: {
'@assets': './assets',
'@root': './src', '@root': './src',
'@api': './src/api', '@api': './src/api',
'@components': './src/components', '@components': './src/components',

View File

@ -226,6 +226,32 @@ declare namespace Mastodon {
title: string title: string
} }
type Instance = {
// Base
uri: string
title: string
description: string
short_description: string
email: string
version: string
languages: string[]
registrations: boolean
approval_required: boolean
invites_enabled: boolean
urls: {
streaming_api: string
}
stats: {
user_count: number
status_count: number
domain_count: number
}
// Others
thumbnail?: string
contact_account?: Account
}
type Mention = { type Mention = {
// Base // Base
id: string id: string

View File

@ -18,6 +18,8 @@ import { useTheme } from '@utils/styles/ThemeManager'
import getCurrentTab from '@utils/getCurrentTab' import getCurrentTab from '@utils/getCurrentTab'
import { toastConfig } from '@components/toast' import { toastConfig } from '@components/toast'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
import { getLocalUrl } from './utils/slices/instancesSlice'
enableScreens() enableScreens()
const Tab = createBottomTabNavigator<RootStackParamList>() const Tab = createBottomTabNavigator<RootStackParamList>()
@ -31,6 +33,7 @@ export type RootStackParamList = {
} }
export const Index: React.FC = () => { export const Index: React.FC = () => {
const localInstance = useSelector(getLocalUrl)
const { i18n } = useTranslation() const { i18n } = useTranslation()
const { mode, theme } = useTheme() const { mode, theme } = useTheme()
enum barStyle { enum barStyle {
@ -46,12 +49,14 @@ export const Index: React.FC = () => {
screenOptions={({ route }) => ({ screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => { tabBarIcon: ({ focused, color, size }) => {
let name: any let name: any
let updateColor: string = color
switch (route.name) { switch (route.name) {
case 'Screen-Local': case 'Screen-Local':
name = 'home' name = 'home'
break break
case 'Screen-Public': case 'Screen-Public':
name = 'globe' name = 'globe'
!focused && (updateColor = theme.secondary)
break break
case 'Screen-Post': case 'Screen-Post':
name = 'plus' name = 'plus'
@ -61,30 +66,42 @@ export const Index: React.FC = () => {
break break
case 'Screen-Me': case 'Screen-Me':
name = focused ? 'meh' : 'smile' name = focused ? 'meh' : 'smile'
!focused && (updateColor = theme.secondary)
break break
default: default:
name = 'alert-octagon' name = 'alert-octagon'
break break
} }
return <Feather name={name} size={size} color={color} /> return <Feather name={name} size={size} color={updateColor} />
} }
})} })}
tabBarOptions={{ tabBarOptions={{
activeTintColor: theme.primary, activeTintColor: theme.primary,
inactiveTintColor: theme.secondary, inactiveTintColor: localInstance ? theme.secondary : theme.disabled,
showLabel: false showLabel: false
}} }}
> >
<Tab.Screen name='Screen-Local' component={ScreenLocal} /> <Tab.Screen
name='Screen-Local'
component={ScreenLocal}
listeners={{
tabPress: e => {
if (!localInstance) {
e.preventDefault()
}
}
}}
/>
<Tab.Screen name='Screen-Public' component={ScreenPublic} /> <Tab.Screen name='Screen-Public' component={ScreenPublic} />
<Tab.Screen <Tab.Screen
name='Screen-Post' name='Screen-Post'
listeners={({ navigation, route }) => ({ listeners={({ navigation, route }) => ({
tabPress: e => { tabPress: e => {
e.preventDefault() e.preventDefault()
navigation.navigate(getCurrentTab(navigation), { localInstance &&
screen: 'Screen-Shared-Compose' navigation.navigate(getCurrentTab(navigation), {
}) screen: 'Screen-Shared-Compose'
})
} }
})} })}
> >
@ -93,6 +110,13 @@ export const Index: React.FC = () => {
<Tab.Screen <Tab.Screen
name='Screen-Notifications' name='Screen-Notifications'
component={ScreenNotifications} component={ScreenNotifications}
listeners={{
tabPress: e => {
if (!localInstance) {
e.preventDefault()
}
}
}}
/> />
<Tab.Screen name='Screen-Me' component={ScreenMe} /> <Tab.Screen name='Screen-Me' component={ScreenMe} />
</Tab.Navigator> </Tab.Navigator>

View File

@ -1,5 +1,5 @@
import axios from 'axios' import axios from 'axios'
import { store, RootState } from '@root/store' import { store, RootState } from 'src/store'
const client = async ({ const client = async ({
method, method,

View File

@ -8,18 +8,17 @@ type PropsBase = {
onPress: () => void onPress: () => void
disabled?: boolean disabled?: boolean
buttonSize?: 'S' | 'M' buttonSize?: 'S' | 'M'
size?: 'S' | 'M' | 'L'
} }
export interface PropsText extends PropsBase { export interface PropsText extends PropsBase {
text: string text: string
icon?: any icon?: any
size?: 'S' | 'M' | 'L'
} }
export interface PropsIcon extends PropsBase { export interface PropsIcon extends PropsBase {
text?: string text?: string
icon: any icon: any
size?: 'S' | 'M' | 'L'
} }
const ButtonRow: React.FC<PropsText | PropsIcon> = ({ const ButtonRow: React.FC<PropsText | PropsIcon> = ({
@ -71,16 +70,13 @@ const styles = StyleSheet.create({
button: { button: {
paddingLeft: StyleConstants.Spacing.M, paddingLeft: StyleConstants.Spacing.M,
paddingRight: StyleConstants.Spacing.M, paddingRight: StyleConstants.Spacing.M,
borderWidth: 1, borderWidth: 1.25,
borderRadius: 100 borderRadius: 100,
alignItems: 'center'
}, },
text: { text: {
textAlign: 'center' textAlign: 'center'
} }
}) })
export default React.memo(ButtonRow, (prev, next) => { export default ButtonRow
let skipUpdate = true
skipUpdate = prev.disabled === next.disabled
return skipUpdate
})

View File

@ -1,8 +0,0 @@
import React from 'react'
import { Text } from 'react-native'
const PleaseLogin = () => {
return <Text></Text>
}
export default PleaseLogin

View File

@ -15,7 +15,6 @@ import {
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import { useNavigation } from '@react-navigation/native' import { useNavigation } from '@react-navigation/native'
import getCurrentTab from '@utils/getCurrentTab' import getCurrentTab from '@utils/getCurrentTab'
import PleaseLogin from '@components/PleaseLogin'
const Stack = createNativeStackNavigator() const Stack = createNativeStackNavigator()
@ -30,9 +29,7 @@ const Page = ({
<View style={{ width: Dimensions.get('window').width }}> <View style={{ width: Dimensions.get('window').width }}>
{localRegistered || page === 'RemotePublic' ? ( {localRegistered || page === 'RemotePublic' ? (
<Timeline page={page} /> <Timeline page={page} />
) : ( ) : null}
<PleaseLogin />
)}
</View> </View>
) )
} }

View File

@ -28,13 +28,13 @@ const ScreenMe: React.FC = () => {
name='Screen-Me-Root' name='Screen-Me-Root'
component={ScreenMeRoot} component={ScreenMeRoot}
options={ options={
localRegistered // localRegistered ?
? { {
headerTranslucent: true, headerTranslucent: true,
headerStyle: { backgroundColor: 'rgba(255, 255, 255, 0)' }, headerStyle: { backgroundColor: 'rgba(255, 255, 255, 0)' },
headerCenter: () => <></> headerCenter: () => <></>
} }
: { headerTitle: t('headers.me.root') } // : { headerTitle: t('meRoot:heading') }
} }
/> />
<Stack.Screen <Stack.Screen

View File

@ -14,7 +14,7 @@ const ScreenMeRoot: React.FC = () => {
const localRegistered = useSelector(getLocalUrl) const localRegistered = useSelector(getLocalUrl)
return ( return (
<ScrollView> <ScrollView keyboardShouldPersistTaps='handled'>
{localRegistered ? <MyInfo /> : <Login />} {localRegistered ? <MyInfo /> : <Login />}
{localRegistered && <Collections />} {localRegistered && <Collections />}
<Settings /> <Settings />

View File

@ -1,5 +1,19 @@
import React, { useCallback, useEffect, useState } from 'react' import React, {
import { StyleSheet, Text, TextInput, View } from 'react-native' createRef,
useCallback,
useEffect,
useRef,
useState
} from 'react'
import {
Animated,
Dimensions,
Image,
StyleSheet,
Text,
TextInput,
View
} from 'react-native'
import { useQuery } from 'react-query' import { useQuery } from 'react-query'
import { debounce } from 'lodash' import { debounce } from 'lodash'
@ -14,6 +28,9 @@ import { useTheme } from '@utils/styles/ThemeManager'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import { ButtonRow } from '@components/Button' import { ButtonRow } from '@components/Button'
import ParseContent from '@root/components/ParseContent'
import ShimmerPlaceholder from 'react-native-shimmer-placeholder'
import { Feather } from '@expo/vector-icons'
const Login: React.FC = () => { const Login: React.FC = () => {
const { t } = useTranslation('meRoot') const { t } = useTranslation('meRoot')
@ -26,7 +43,7 @@ const Login: React.FC = () => {
clientSecret: string clientSecret: string
}>() }>()
const { isSuccess, refetch, data } = useQuery( const { isSuccess, isFetching, refetch, data } = useQuery(
['Instance', { instance }], ['Instance', { instance }],
instanceFetch, instanceFetch,
{ {
@ -40,11 +57,13 @@ const Login: React.FC = () => {
text => { text => {
setInstance(text) setInstance(text)
setApplicationData(undefined) setApplicationData(undefined)
refetch() if (text) {
refetch()
}
}, },
1000, 1000,
{ {
leading: true trailing: true
} }
), ),
[] []
@ -121,47 +140,217 @@ const Login: React.FC = () => {
})() })()
}, [response]) }, [response])
return ( const infoRef = createRef<ShimmerPlaceholder>()
<View style={styles.base}>
<TextInput const instanceInfo = useCallback(
style={{ ({
height: 50, header,
color: theme.primary, content,
borderColor: theme.border, parse
borderWidth: 1, }: {
padding: StyleConstants.Spacing.M header: string
}} content: string
onChangeText={onChangeText} parse?: boolean
autoCapitalize='none' }) => {
autoCorrect={false} if (isFetching) {
autoFocus Animated.loop(infoRef.current?.getAnimated()!).start()
clearButtonMode='unless-editing' }
keyboardType='url'
textContentType='URL' return (
onSubmitEditing={async () => <View style={styles.instanceInfo}>
isSuccess && data && data.uri && (await createApplication()) <Text style={[styles.instanceInfoHeader, { color: theme.primary }]}>
} {header}
placeholder={t('content.login.server.placeholder')} </Text>
placeholderTextColor={theme.secondary} <ShimmerPlaceholder
returnKeyType='go' visible={data?.uri}
/> stopAutoRun
<ButtonRow width={
onPress={async () => await createApplication()} Dimensions.get('screen').width -
text={t('content.login.button')} StyleConstants.Spacing.Global.PagePadding * 4
disabled={!data?.uri} }
/> height={StyleConstants.Font.Size.M}
{isSuccess && data && data.uri && ( >
<View> <Text
<Text style={{ color: theme.primary }}>{data.title}</Text> style={[styles.instanceInfoContent, { color: theme.primary }]}
>
{parse ? (
<ParseContent content={content} size={'M'} numberOfLines={5} />
) : (
content
)}
</Text>
</ShimmerPlaceholder>
</View> </View>
)} )
</View> },
[data?.uri, isFetching]
)
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}
autoFocus
clearButtonMode='unless-editing'
keyboardType='url'
textContentType='URL'
onSubmitEditing={async () =>
isSuccess && data && data.uri && (await createApplication())
}
placeholder={t('content.login.server.placeholder')}
placeholderTextColor={theme.secondary}
returnKeyType='go'
/>
<ButtonRow
onPress={async () => await createApplication()}
{...(isFetching
? { icon: 'loader' }
: { text: t('content.login.button') as string })}
disabled={!data?.uri}
/>
</View>
<View>
{instanceInfo({ header: '实例名称', content: data?.title })}
{instanceInfo({
header: '实例介绍',
content: data?.short_description,
parse: true
})}
<View style={styles.instanceStats}>
<View style={styles.instanceStat}>
<Text
style={[styles.instanceInfoHeader, { color: theme.primary }]}
>
</Text>
<ShimmerPlaceholder
visible={data?.stats?.user_count}
stopAutoRun
width={StyleConstants.Font.Size.M * 4}
height={StyleConstants.Font.Size.M}
>
<Text
style={[styles.instanceInfoContent, { color: theme.primary }]}
>
{data?.stats?.user_count}
</Text>
</ShimmerPlaceholder>
</View>
<View style={[styles.instanceStat, { alignItems: 'center' }]}>
<Text
style={[styles.instanceInfoHeader, { color: theme.primary }]}
>
</Text>
<ShimmerPlaceholder
visible={data?.stats?.user_count}
stopAutoRun
width={StyleConstants.Font.Size.M * 4}
height={StyleConstants.Font.Size.M}
>
<Text
style={[styles.instanceInfoContent, { color: theme.primary }]}
>
{data?.stats?.status_count}
</Text>
</ShimmerPlaceholder>
</View>
<View style={[styles.instanceStat, { alignItems: 'flex-end' }]}>
<Text
style={[styles.instanceInfoHeader, { color: theme.primary }]}
>
</Text>
<ShimmerPlaceholder
visible={data?.stats?.user_count}
stopAutoRun
width={StyleConstants.Font.Size.M * 4}
height={StyleConstants.Font.Size.M}
>
<Text
style={[styles.instanceInfoContent, { color: theme.primary }]}
>
{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({ const styles = StyleSheet.create({
base: { base: {
padding: StyleConstants.Spacing.Global.PagePadding 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
} }
}) })

View File

@ -6,11 +6,13 @@ import MenuButton from '@components/Menu/Button'
import { MenuContainer } from '@components/Menu' import { MenuContainer } from '@components/Menu'
import { useNavigation } from '@react-navigation/native' import { useNavigation } from '@react-navigation/native'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useQueryCache } from 'react-query'
const Logout: React.FC = () => { const Logout: React.FC = () => {
const { t } = useTranslation('meRoot') const { t } = useTranslation('meRoot')
const dispatch = useDispatch() const dispatch = useDispatch()
const navigation = useNavigation() const navigation = useNavigation()
const queryCache = useQueryCache()
const alertOption = { const alertOption = {
title: t('content.logout.alert.title'), title: t('content.logout.alert.title'),
@ -20,6 +22,7 @@ const Logout: React.FC = () => {
text: t('content.logout.alert.buttons.logout'), text: t('content.logout.alert.buttons.logout'),
style: 'destructive' as const, style: 'destructive' as const,
onPress: () => { onPress: () => {
queryCache.clear()
dispatch(updateLocal({})) dispatch(updateLocal({}))
navigation.navigate('Screen-Public', { navigation.navigate('Screen-Public', {
screen: 'Screen-Public-Root', screen: 'Screen-Public-Root',

View File

@ -5,7 +5,6 @@ import Timeline from '@components/Timelines/Timeline'
import sharedScreens from '@screens/Shared/sharedScreens' import sharedScreens from '@screens/Shared/sharedScreens'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { RootState } from '@root/store' import { RootState } from '@root/store'
import PleaseLogin from '@components/PleaseLogin'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
const Stack = createNativeStackNavigator() const Stack = createNativeStackNavigator()
@ -21,9 +20,7 @@ const ScreenNotifications: React.FC = () => {
screenOptions={{ headerTitle: t('notifications:heading') }} screenOptions={{ headerTitle: t('notifications:heading') }}
> >
<Stack.Screen name='Screen-Notifications-Root'> <Stack.Screen name='Screen-Notifications-Root'>
{() => {() => (localRegistered ? <Timeline page='Notifications' /> : null)}
localRegistered ? <Timeline page='Notifications' /> : <PleaseLogin />
}
</Stack.Screen> </Stack.Screen>
{sharedScreens(Stack)} {sharedScreens(Stack)}

View File

@ -25,7 +25,11 @@ import { emojisFetch } from '@utils/fetches/emojisFetch'
import { searchFetch } from '@utils/fetches/searchFetch' import { searchFetch } from '@utils/fetches/searchFetch'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import { PostAction, ComposeState, ComposeContext } from '@screens/Shared/Compose' import {
PostAction,
ComposeState,
ComposeContext
} from '@screens/Shared/Compose'
import ComposeActions from '@screens/Shared/Compose/Actions' import ComposeActions from '@screens/Shared/Compose/Actions'
import updateText from './updateText' import updateText from './updateText'
import * as Permissions from 'expo-permissions' import * as Permissions from 'expo-permissions'
@ -189,7 +193,7 @@ const ComposeRoot: React.FC = () => {
/> />
<FlatList <FlatList
keyboardShouldPersistTaps='handled' keyboardShouldPersistTaps='handled'
ListHeaderComponent={<ComposeRootHeader textInputRef={textInputRef} />} ListHeaderComponent={<ComposeRootHeader />}
ListFooterComponent={<ComposeRootFooter textInputRef={textInputRef} />} ListFooterComponent={<ComposeRootFooter textInputRef={textInputRef} />}
ListEmptyComponent={listEmpty} ListEmptyComponent={listEmpty}
data={data} data={data}

View File

@ -11,6 +11,7 @@
"strict": true, "strict": true,
"baseUrl": "./", "baseUrl": "./",
"paths": { "paths": {
// "@assets/*": ["./assets/*"],
"@root/*": ["./src/*"], "@root/*": ["./src/*"],
"@api/*": ["./src/api/*"], "@api/*": ["./src/api/*"],
"@components/*": ["./src/components/*"], "@components/*": ["./src/components/*"],