1
0
mirror of https://github.com/tooot-app/app synced 2025-06-05 22:19:13 +02:00

Use svg icons instead of expo ones

Possibility to control `strokeWidth`
This commit is contained in:
Zhiyuan Zheng
2021-01-03 02:00:26 +01:00
parent 5a80359739
commit dceaf8d25c
62 changed files with 495 additions and 427 deletions

12
App.tsx
View File

@ -57,7 +57,7 @@ enableScreens()
const App: React.FC = () => { const App: React.FC = () => {
startingLog('log', 'rendering App') startingLog('log', 'rendering App')
const [appLoaded, setAppLoaded] = useState(false) const [appLoaded, setAppLoaded] = useState(false)
const [localCorrupt, setLocalCorrupt] = useState(false) const [localCorrupt, setLocalCorrupt] = useState<string>()
useEffect(() => { useEffect(() => {
const onlineState = onlineManager.setEventListener(setOnline => { const onlineState = onlineManager.setEventListener(setOnline => {
@ -118,16 +118,20 @@ const App: React.FC = () => {
startingLog('log', 'local credential check passed') startingLog('log', 'local credential check passed')
if (res.body.id !== store.getState().instances.local.account.id) { if (res.body.id !== store.getState().instances.local.account.id) {
store.dispatch(resetLocal()) store.dispatch(resetLocal())
setLocalCorrupt(true) setLocalCorrupt('')
} }
setAppLoaded(true) setAppLoaded(true)
}) })
.catch(error => { .catch(error => {
startingLog('error', 'local credential check failed') startingLog('error', 'local credential check failed')
if (error.status && typeof error.status === 'number') { if (
error.status &&
typeof error.status === 'number' &&
error.status === 401
) {
store.dispatch(resetLocal()) store.dispatch(resetLocal())
setLocalCorrupt(true)
} }
setLocalCorrupt(error.data.error)
setAppLoaded(true) setAppLoaded(true)
}) })
} else { } else {

View File

@ -9,7 +9,6 @@
"test": "jest" "test": "jest"
}, },
"dependencies": { "dependencies": {
"@expo/vector-icons": "^12.0.0",
"@react-native-community/masked-view": "0.1.10", "@react-native-community/masked-view": "0.1.10",
"@react-native-community/netinfo": "^5.9.9", "@react-native-community/netinfo": "^5.9.9",
"@react-native-community/segmented-control": "2.2.1", "@react-native-community/segmented-control": "2.2.1",
@ -50,6 +49,7 @@
"react-native": "https://github.com/expo/react-native/archive/sdk-40.0.0.tar.gz", "react-native": "https://github.com/expo/react-native/archive/sdk-40.0.0.tar.gz",
"react-native-animated-spinkit": "^1.4.2", "react-native-animated-spinkit": "^1.4.2",
"react-native-expo-image-cache": "^4.1.0", "react-native-expo-image-cache": "^4.1.0",
"react-native-feather": "^1.0.2",
"react-native-gesture-handler": "~1.8.0", "react-native-gesture-handler": "~1.8.0",
"react-native-htmlview": "^0.16.0", "react-native-htmlview": "^0.16.0",
"react-native-image-zoom-viewer": "^3.0.1", "react-native-image-zoom-viewer": "^3.0.1",

2
src/@types/app.d.ts vendored
View File

@ -66,7 +66,7 @@ declare namespace QueryKey {
{ {
hashtag?: Mastodon.Tag['name'] hashtag?: Mastodon.Tag['name']
list?: Mastodon.List['id'] list?: Mastodon.List['id']
toot?: Mastodon.Status toot?: Mastodon.Status['id']
account?: Mastodon.Account['id'] account?: Mastodon.Account['id']
} }
] ]

View File

@ -1,3 +1,4 @@
declare module 'gl-react-blurhash' declare module 'gl-react-blurhash'
declare module 'react-native-toast-message' declare module 'react-native-feather'
declare module 'react-native-htmlview' declare module 'react-native-htmlview'
declare module 'react-native-toast-message'

View File

@ -1,5 +1,5 @@
import client from '@api/client' import client from '@api/client'
import { Feather } from '@expo/vector-icons' import Icon from '@components/Icon'
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs' import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
import { import {
NavigationContainer, NavigationContainer,
@ -37,7 +37,7 @@ export type RootStackParamList = {
} }
export interface Props { export interface Props {
localCorrupt: boolean localCorrupt?: string
} }
const Index: React.FC<Props> = ({ localCorrupt }) => { const Index: React.FC<Props> = ({ localCorrupt }) => {
@ -71,7 +71,7 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
? toast({ ? toast({
type: 'error', type: 'error',
message: '登录已过期', message: '登录已过期',
description: '请重新登录', description: localCorrupt.length ? localCorrupt : undefined,
autoHide: false autoHide: false
}) })
: undefined : undefined
@ -171,27 +171,27 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
let updateColor: string = color 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) !focused && (updateColor = theme.secondary)
break break
case 'Screen-Post': case 'Screen-Post':
name = 'plus' name = 'Plus'
break break
case 'Screen-Notifications': case 'Screen-Notifications':
name = 'bell' name = 'Bell'
break break
case 'Screen-Me': case 'Screen-Me':
name = focused ? 'meh' : 'smile' name = focused ? 'Meh' : 'Smile'
!focused && (updateColor = theme.secondary) !focused && (updateColor = theme.secondary)
break break
default: default:
name = 'alert-octagon' name = 'AlertOctagon'
break break
} }
return <Feather name={name} size={size} color={updateColor} /> return <Icon name={name} size={size} color={updateColor} />
} }
}), }),
[] []

View File

@ -1,4 +1,7 @@
import { Feather } from '@expo/vector-icons' import Icon from '@components/Icon'
import { StyleConstants } from '@utils/styles/constants'
import layoutAnimation from '@utils/styles/layoutAnimation'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useEffect, useMemo } from 'react' import React, { useEffect, useMemo } from 'react'
import { import {
Pressable, Pressable,
@ -9,9 +12,6 @@ import {
ViewStyle ViewStyle
} from 'react-native' } from 'react-native'
import { Chase } from 'react-native-animated-spinkit' import { Chase } from 'react-native-animated-spinkit'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import layoutAnimation from '@root/utils/styles/layoutAnimation'
export interface Props { export interface Props {
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
@ -23,6 +23,7 @@ export interface Props {
destructive?: boolean destructive?: boolean
disabled?: boolean disabled?: boolean
strokeWidth?: number
size?: 'S' | 'M' | 'L' size?: 'S' | 'M' | 'L'
spacing?: 'XS' | 'S' | 'M' | 'L' spacing?: 'XS' | 'S' | 'M' | 'L'
round?: boolean round?: boolean
@ -38,6 +39,7 @@ const Button: React.FC<Props> = ({
loading = false, loading = false,
destructive = false, destructive = false,
disabled = false, disabled = false,
strokeWidth,
size = 'M', size = 'M',
spacing = 'S', spacing = 'S',
round = false, round = false,
@ -78,12 +80,12 @@ const Button: React.FC<Props> = ({
case 'icon': case 'icon':
return ( return (
<> <>
<Feather <Icon
name={content as any} name={content}
size={StyleConstants.Font.Size[size] * (size === 'L' ? 1.25 : 1)}
color={colorContent} color={colorContent}
strokeWidth={strokeWidth}
style={{ opacity: loading ? 0 : 1 }} style={{ opacity: loading ? 0 : 1 }}
testID='icon' size={StyleConstants.Font.Size[size] * (size === 'L' ? 1.25 : 1)}
/> />
{loading && loadingSpinkit} {loading && loadingSpinkit}
</> </>

View File

@ -1,8 +1,8 @@
import React, { useMemo } from 'react' import Icon from '@components/Icon'
import { Pressable, StyleSheet, Text } from 'react-native'
import { Feather } from '@expo/vector-icons'
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 React, { useMemo } from 'react'
import { Pressable, StyleSheet, Text } from 'react-native'
export interface Props { export interface Props {
type?: 'icon' | 'text' type?: 'icon' | 'text'
@ -18,9 +18,9 @@ const HeaderLeft: React.FC<Props> = ({ type = 'icon', content, onPress }) => {
switch (type) { switch (type) {
case 'icon': case 'icon':
return ( return (
<Feather <Icon
name={content || ('chevron-left' as any)}
color={theme.primary} color={theme.primary}
name={content || 'ChevronLeft'}
size={StyleConstants.Spacing.M * 1.25} size={StyleConstants.Spacing.M * 1.25}
/> />
) )

View File

@ -1,13 +1,13 @@
import Icon from '@components/Icon'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useMemo } from 'react' import React, { useMemo } from 'react'
import { Pressable, StyleSheet, Text, View } from 'react-native' import { Pressable, StyleSheet, Text, View } from 'react-native'
import { Chase } from 'react-native-animated-spinkit' import { Chase } from 'react-native-animated-spinkit'
import { Feather } from '@expo/vector-icons'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
export interface Props { export interface Props {
type?: 'icon' | 'text' type?: 'icon' | 'text'
content?: string content: string
loading?: boolean loading?: boolean
disabled?: boolean disabled?: boolean
@ -41,11 +41,11 @@ const HeaderRight: React.FC<Props> = ({
case 'icon': case 'icon':
return ( return (
<> <>
<Feather <Icon
name={content as any} name={content}
color={disabled ? theme.secondary : theme.primary}
size={StyleConstants.Spacing.M * 1.25}
style={{ opacity: loading ? 0 : 1 }} style={{ opacity: loading ? 0 : 1 }}
size={StyleConstants.Spacing.M * 1.25}
color={disabled ? theme.secondary : theme.primary}
/> />
{loading && loadingSpinkit} {loading && loadingSpinkit}
</> </>

45
src/components/Icon.tsx Normal file
View File

@ -0,0 +1,45 @@
import React, { createElement } from 'react'
import { StyleProp, View, ViewStyle } from 'react-native'
import * as FeatherIcon from 'react-native-feather'
export interface Props {
name: string
size: number
color: string
strokeWidth?: number
inline?: boolean // When used in line of text, need to drag it down
style?: StyleProp<ViewStyle>
}
const Icon: React.FC<Props> = ({
name,
size,
color,
strokeWidth = 2,
inline = false,
style
}) => {
return (
<View
style={[
style,
{
width: size,
height: size,
justifyContent: 'center',
alignItems: 'center',
marginBottom: inline ? -size * 0.125 : undefined
}
]}
>
{createElement(FeatherIcon[name], {
width: size,
height: size,
color,
strokeWidth
})}
</View>
)
}
export default Icon

View File

@ -1,4 +1,4 @@
import { Feather } from '@expo/vector-icons' import Icon from '@components/Icon'
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 { ColorDefinitions } from '@utils/styles/themes' import { ColorDefinitions } from '@utils/styles/themes'
@ -18,7 +18,7 @@ export interface Props {
switchDisabled?: boolean switchDisabled?: boolean
switchOnValueChange?: () => void switchOnValueChange?: () => void
iconBack?: 'chevron-right' | 'check' iconBack?: 'ChevronRight' | 'Check'
iconBackColor?: ColorDefinitions iconBackColor?: ColorDefinitions
loading?: boolean loading?: boolean
@ -63,7 +63,7 @@ const MenuRow: React.FC<Props> = ({
<View style={styles.core}> <View style={styles.core}>
<View style={styles.front}> <View style={styles.front}>
{iconFront && ( {iconFront && (
<Feather <Icon
name={iconFront} name={iconFront}
size={StyleConstants.Font.Size.M + 2} size={StyleConstants.Font.Size.M + 2}
color={theme[iconFrontColor]} color={theme[iconFrontColor]}
@ -116,7 +116,7 @@ const MenuRow: React.FC<Props> = ({
) : null} ) : null}
{iconBack ? ( {iconBack ? (
<> <>
<Feather <Icon
name={iconBack} name={iconBack}
size={StyleConstants.Font.Size.M + 2} size={StyleConstants.Font.Size.M + 2}
color={theme[iconBackColor]} color={theme[iconBackColor]}

View File

@ -1,6 +1,6 @@
import Icon from '@components/Icon'
import openLink from '@components/openLink' import openLink from '@components/openLink'
import ParseEmojis from '@components/Parse/Emojis' import ParseEmojis from '@components/Parse/Emojis'
import { Feather } from '@expo/vector-icons'
import { useNavigation } from '@react-navigation/native' import { useNavigation } from '@react-navigation/native'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import layoutAnimation from '@utils/styles/layoutAnimation' import layoutAnimation from '@utils/styles/layoutAnimation'
@ -96,10 +96,11 @@ const renderNode = ({
} }
> >
{!shouldBeTag ? ( {!shouldBeTag ? (
<Feather <Icon
name='external-link' inline
size={StyleConstants.Font.Size[size]}
color={theme.blue} color={theme.blue}
name='ExternalLink'
size={StyleConstants.Font.Size[size]}
/> />
) : null} ) : null}
{content || (showFullLink ? href : domain[1])} {content || (showFullLink ? href : domain[1])}
@ -166,37 +167,45 @@ const ParseHTML: React.FC<Props> = ({
const [heightOriginal, setHeightOriginal] = useState<number>() const [heightOriginal, setHeightOriginal] = useState<number>()
const [heightTruncated, setHeightTruncated] = useState<number>() const [heightTruncated, setHeightTruncated] = useState<number>()
const [allowExpand, setAllowExpand] = useState( const [allowExpand, setAllowExpand] = useState(false)
numberOfLines === 0 ? true : undefined
)
const [showAllText, setShowAllText] = useState(false) const [showAllText, setShowAllText] = useState(false)
const calNumberOfLines = useMemo(() => { const calNumberOfLines = useMemo(() => {
if (heightOriginal) { if (numberOfLines === 0) {
if (!heightTruncated) { // For spoilers without calculation
return numberOfLines return showAllText ? undefined : 1
} else { } else {
if (allowExpand && !showAllText) { if (heightOriginal) {
if (!heightTruncated) {
return numberOfLines return numberOfLines
} else { } else {
return undefined if (allowExpand && !showAllText) {
return numberOfLines
} else {
return undefined
}
} }
} else {
return undefined
} }
} else {
return undefined
} }
}, [heightOriginal, heightTruncated, allowExpand, showAllText]) }, [heightOriginal, heightTruncated, allowExpand, showAllText])
const onLayout = useCallback( const onLayout = useCallback(
({ nativeEvent }) => { ({ nativeEvent }) => {
if (!heightOriginal) { if (numberOfLines === 0) {
setHeightOriginal(nativeEvent.layout.height) // For spoilers without calculation
setAllowExpand(true)
} else { } else {
if (!heightTruncated) { if (!heightOriginal) {
setHeightTruncated(nativeEvent.layout.height) setHeightOriginal(nativeEvent.layout.height)
} else { } else {
if (heightOriginal > heightTruncated) { if (!heightTruncated) {
setAllowExpand(true) setHeightTruncated(nativeEvent.layout.height)
} else {
if (heightOriginal > heightTruncated) {
setAllowExpand(true)
}
} }
} }
} }
@ -214,7 +223,7 @@ const ParseHTML: React.FC<Props> = ({
}} }}
children={children} children={children}
numberOfLines={calNumberOfLines} numberOfLines={calNumberOfLines}
onLayout={allowExpand === undefined ? onLayout : undefined} onLayout={onLayout}
/> />
{allowExpand ? ( {allowExpand ? (
<Pressable <Pressable

View File

@ -86,7 +86,7 @@ const Timelines: React.FC<Props> = ({ name, content }) => {
</View> </View>
), ),
headerRight: () => ( headerRight: () => (
<HeaderRight content='search' onPress={onPressSearch} /> <HeaderRight content='Search' onPress={onPressSearch} />
) )
}) })
}} }}

View File

@ -24,10 +24,10 @@ export type TimelineData =
export interface Props { export interface Props {
page: App.Pages page: App.Pages
hashtag?: string hashtag?: Mastodon.Tag['name']
list?: string list?: Mastodon.List['id']
toot?: Mastodon.Status toot?: Mastodon.Status['id']
account?: string account?: Mastodon.Account['id']
disableRefresh?: boolean disableRefresh?: boolean
disableInfinity?: boolean disableInfinity?: boolean
} }
@ -134,7 +134,7 @@ const Timeline: React.FC<Props> = ({
flattenPinnedLength[0] && { flattenPinnedLength[0] && {
pinnedLength: flattenPinnedLength[0] pinnedLength: flattenPinnedLength[0]
})} })}
{...(toot && toot.id === item.id && { highlighted: true })} {...(toot === item.id && { highlighted: true })}
/> />
) )
} }
@ -144,7 +144,7 @@ const Timeline: React.FC<Props> = ({
const ItemSeparatorComponent = useCallback( const ItemSeparatorComponent = useCallback(
({ leadingItem }) => ( ({ leadingItem }) => (
<TimelineSeparator <TimelineSeparator
{...(toot && toot.id === leadingItem.id && { highlighted: true })} {...(toot === leadingItem.id && { highlighted: true })}
/> />
), ),
[] []

View File

@ -44,6 +44,7 @@ const TimelineDefault: React.FC<Props> = ({
}), }),
[] []
) )
return ( return (
<Pressable style={styles.statusView} onPress={onPress}> <Pressable style={styles.statusView} onPress={onPress}>
{item.reblog ? ( {item.reblog ? (
@ -57,11 +58,11 @@ const TimelineDefault: React.FC<Props> = ({
{...(!isRemotePublic && { queryKey })} {...(!isRemotePublic && { queryKey })}
account={actualStatus.account} account={actualStatus.account}
/> />
<TimelineHeaderDefault {/* <TimelineHeaderDefault
{...(!isRemotePublic && { queryKey })} {...(!isRemotePublic && { queryKey })}
status={actualStatus} status={actualStatus}
sameAccount={actualStatus.account.id === localAccountId} sameAccount={actualStatus.account.id === localAccountId}
/> /> */}
</View> </View>
<View <View

View File

@ -1,5 +1,5 @@
import Button from '@components/Button' import Button from '@components/Button'
import { Feather } from '@expo/vector-icons' import Icon from '@components/Icon'
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 React, { useMemo } from 'react' import React, { useMemo } from 'react'
@ -26,8 +26,8 @@ const TimelineEmpty: React.FC<Props> = ({ status, refetch }) => {
case 'error': case 'error':
return ( return (
<> <>
<Feather <Icon
name='frown' name='Frown'
size={StyleConstants.Font.Size.L} size={StyleConstants.Font.Size.L}
color={theme.primary} color={theme.primary}
/> />
@ -44,8 +44,8 @@ const TimelineEmpty: React.FC<Props> = ({ status, refetch }) => {
case 'success': case 'success':
return ( return (
<> <>
<Feather <Icon
name='smartphone' name='Smartphone'
size={StyleConstants.Font.Size.L} size={StyleConstants.Font.Size.L}
color={theme.primary} color={theme.primary}
/> />

View File

@ -1,5 +1,5 @@
import Icon from '@components/Icon'
import { ParseEmojis } from '@components/Parse' import { ParseEmojis } from '@components/Parse'
import { Feather } from '@expo/vector-icons'
import { useNavigation } from '@react-navigation/native' import { useNavigation } from '@react-navigation/native'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
@ -37,8 +37,8 @@ const TimelineActioned: React.FC<Props> = ({
case 'pinned': case 'pinned':
return ( return (
<> <>
<Feather <Icon
name='anchor' name='Anchor'
size={StyleConstants.Font.Size.S} size={StyleConstants.Font.Size.S}
color={iconColor} color={iconColor}
style={styles.icon} style={styles.icon}
@ -50,8 +50,8 @@ const TimelineActioned: React.FC<Props> = ({
case 'favourite': case 'favourite':
return ( return (
<> <>
<Feather <Icon
name='heart' name='Heart'
size={StyleConstants.Font.Size.S} size={StyleConstants.Font.Size.S}
color={iconColor} color={iconColor}
style={styles.icon} style={styles.icon}
@ -65,8 +65,8 @@ const TimelineActioned: React.FC<Props> = ({
case 'follow': case 'follow':
return ( return (
<> <>
<Feather <Icon
name='user-plus' name='UserPlus'
size={StyleConstants.Font.Size.S} size={StyleConstants.Font.Size.S}
color={iconColor} color={iconColor}
style={styles.icon} style={styles.icon}
@ -80,8 +80,8 @@ const TimelineActioned: React.FC<Props> = ({
case 'poll': case 'poll':
return ( return (
<> <>
<Feather <Icon
name='bar-chart-2' name='BarChart2'
size={StyleConstants.Font.Size.S} size={StyleConstants.Font.Size.S}
color={iconColor} color={iconColor}
style={styles.icon} style={styles.icon}
@ -93,8 +93,8 @@ const TimelineActioned: React.FC<Props> = ({
case 'reblog': case 'reblog':
return ( return (
<> <>
<Feather <Icon
name='repeat' name='Repeat'
size={StyleConstants.Font.Size.S} size={StyleConstants.Font.Size.S}
color={iconColor} color={iconColor}
style={styles.icon} style={styles.icon}
@ -118,12 +118,13 @@ const TimelineActioned: React.FC<Props> = ({
const styles = StyleSheet.create({ const styles = StyleSheet.create({
actioned: { actioned: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center',
marginBottom: StyleConstants.Spacing.S, marginBottom: StyleConstants.Spacing.S,
paddingLeft: StyleConstants.Avatar.M - StyleConstants.Font.Size.S, paddingLeft: StyleConstants.Avatar.M - StyleConstants.Font.Size.S,
paddingRight: StyleConstants.Spacing.Global.PagePadding paddingRight: StyleConstants.Spacing.Global.PagePadding
}, },
icon: { icon: {
paddingRight: StyleConstants.Spacing.S marginRight: StyleConstants.Spacing.S
} }
}) })

View File

@ -1,8 +1,8 @@
import client from '@api/client' import client from '@api/client'
import haptics from '@components/haptics' import haptics from '@components/haptics'
import Icon from '@components/Icon'
import { TimelineData } from '@components/Timelines/Timeline' import { TimelineData } from '@components/Timelines/Timeline'
import { toast } from '@components/toast' import { toast } from '@components/toast'
import { Feather } from '@expo/vector-icons'
import { useNavigation } from '@react-navigation/native' import { useNavigation } from '@react-navigation/native'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
@ -171,8 +171,8 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
const childrenReply = useMemo( const childrenReply = useMemo(
() => ( () => (
<> <>
<Feather <Icon
name='message-circle' name='MessageCircle'
color={iconColor} color={iconColor}
size={StyleConstants.Font.Size.M + 2} size={StyleConstants.Font.Size.M + 2}
/> />
@ -193,8 +193,8 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
) )
const childrenReblog = useMemo( const childrenReblog = useMemo(
() => ( () => (
<Feather <Icon
name='repeat' name='Repeat'
color={ color={
status.visibility === 'private' || status.visibility === 'direct' status.visibility === 'private' || status.visibility === 'direct'
? theme.disabled ? theme.disabled
@ -207,8 +207,8 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
) )
const childrenFavourite = useMemo( const childrenFavourite = useMemo(
() => ( () => (
<Feather <Icon
name='heart' name='Heart'
color={iconColorAction(status.favourited)} color={iconColorAction(status.favourited)}
size={StyleConstants.Font.Size.M + 2} size={StyleConstants.Font.Size.M + 2}
/> />
@ -217,8 +217,8 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
) )
const childrenBookmark = useMemo( const childrenBookmark = useMemo(
() => ( () => (
<Feather <Icon
name='bookmark' name='Bookmark'
color={iconColorAction(status.bookmarked)} color={iconColorAction(status.bookmarked)}
size={StyleConstants.Font.Size.M + 2} size={StyleConstants.Font.Size.M + 2}
/> />
@ -227,8 +227,8 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
) )
const childrenShare = useMemo( const childrenShare = useMemo(
() => ( () => (
<Feather <Icon
name='share-2' name='Share2'
color={iconColor} color={iconColor}
size={StyleConstants.Font.Size.M + 2} size={StyleConstants.Font.Size.M + 2}
/> />

View File

@ -117,7 +117,7 @@ const TimelineAttachment: React.FC<Props> = ({ status }) => {
) : ( ) : (
<Button <Button
type='icon' type='icon'
content='eye-off' content='EyeOff'
round round
overlay overlay
onPress={onPressShow} onPress={onPressShow}

View File

@ -67,7 +67,7 @@ const AttachmentAudio: React.FC<Props> = ({ sensitiveShown, audio }) => {
)} )}
<Button <Button
type='icon' type='icon'
content={audioPlaying ? 'pause-circle' : 'play-circle'} content={audioPlaying ? 'PauseCircle' : 'PlayCircle'}
size='L' size='L'
round round
overlay overlay

View File

@ -64,7 +64,7 @@ const AttachmentVideo: React.FC<Props> = ({ sensitiveShown, video }) => {
) : ( ) : (
<Button <Button
type='icon' type='icon'
content='play-circle' content='PlayCircle'
size='L' size='L'
round round
overlay overlay

View File

@ -1,4 +1,4 @@
import { Feather } from '@expo/vector-icons' import Icon from '@components/Icon'
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 React from 'react' import React from 'react'
@ -22,8 +22,9 @@ const TimelineEnd: React.FC<Props> = ({ hasNextPage }) => {
<Trans <Trans
i18nKey='timeline:shared.end.message' // optional -> fallbacks to defaults if not provided i18nKey='timeline:shared.end.message' // optional -> fallbacks to defaults if not provided
components={[ components={[
<Feather <Icon
name='coffee' inline
name='Coffee'
size={StyleConstants.Font.Size.S} size={StyleConstants.Font.Size.S}
color={theme.secondary} color={theme.secondary}
/> />
@ -43,8 +44,7 @@ const styles = StyleSheet.create({
padding: StyleConstants.Spacing.M padding: StyleConstants.Spacing.M
}, },
text: { text: {
...StyleConstants.FontStyle.S, ...StyleConstants.FontStyle.S
marginLeft: StyleConstants.Spacing.S
} }
}) })

View File

@ -1,7 +1,7 @@
import client from '@api/client' import client from '@api/client'
import haptics from '@components/haptics' import haptics from '@components/haptics'
import Icon from '@components/Icon'
import { toast } from '@components/toast' import { toast } from '@components/toast'
import { Feather } from '@expo/vector-icons'
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 React, { useCallback, useMemo } from 'react' import React, { useCallback, useMemo } from 'react'
@ -66,8 +66,8 @@ const HeaderConversation: React.FC<Props> = ({ queryKey, conversation }) => {
const actionChildren = useMemo( const actionChildren = useMemo(
() => ( () => (
<Feather <Icon
name='trash' name='Trash'
color={theme.secondary} color={theme.secondary}
size={StyleConstants.Font.Size.M + 2} size={StyleConstants.Font.Size.M + 2}
/> />
@ -86,7 +86,7 @@ const HeaderConversation: React.FC<Props> = ({ queryKey, conversation }) => {
/> />
) : null} ) : null}
{conversation.unread && ( {conversation.unread && (
<Feather name='circle' color={theme.blue} style={styles.unread} /> <Icon name='Circle' color={theme.blue} style={styles.unread} />
)} )}
</View> </View>
</View> </View>

View File

@ -1,5 +1,5 @@
import BottomSheet from '@components/BottomSheet' import BottomSheet from '@components/BottomSheet'
import { Feather } from '@expo/vector-icons' import Icon from '@components/Icon'
import { getLocalUrl } from '@utils/slices/instancesSlice' import { getLocalUrl } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
@ -9,10 +9,10 @@ import HeaderDefaultActionsStatus from '@components/Timelines/Timeline/Shared/He
import React, { useCallback, useMemo, useState } from 'react' import React, { useCallback, useMemo, useState } from 'react'
import { Pressable, StyleSheet, View } from 'react-native' import { Pressable, StyleSheet, View } from 'react-native'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import HeaderSharedApplication from './HeaderShared/Application'
import HeaderSharedVisibility from './HeaderShared/Visibility'
import HeaderSharedCreated from './HeaderShared/Created'
import HeaderSharedAccount from './HeaderShared/Account' import HeaderSharedAccount from './HeaderShared/Account'
import HeaderSharedApplication from './HeaderShared/Application'
import HeaderSharedCreated from './HeaderShared/Created'
import HeaderSharedVisibility from './HeaderShared/Visibility'
export interface Props { export interface Props {
queryKey?: QueryKey.Timeline queryKey?: QueryKey.Timeline
@ -37,8 +37,8 @@ const TimelineHeaderDefault: React.FC<Props> = ({
const pressableAction = useMemo( const pressableAction = useMemo(
() => ( () => (
<Feather <Icon
name='more-horizontal' name='MoreHorizontal'
color={theme.secondary} color={theme.secondary}
size={StyleConstants.Font.Size.M + 2} size={StyleConstants.Font.Size.M + 2}
/> />
@ -102,8 +102,7 @@ const TimelineHeaderDefault: React.FC<Props> = ({
const styles = StyleSheet.create({ const styles = StyleSheet.create({
base: { base: {
flex: 1, flex: 1,
flexDirection: 'row', flexDirection: 'row'
alignItems: 'baseline'
}, },
accountAndMeta: { accountAndMeta: {
flex: 4 flex: 4

View File

@ -84,7 +84,7 @@ const HeaderDefaultActionsAccount: React.FC<Props> = ({
setBottomSheetVisible(false) setBottomSheetVisible(false)
mutate({ type: 'mute' }) mutate({ type: 'mute' })
}} }}
iconFront='eye-off' iconFront='EyeOff'
title={t('timeline:shared.header.default.actions.account.mute.button', { title={t('timeline:shared.header.default.actions.account.mute.button', {
acct: account.acct acct: account.acct
})} })}
@ -94,7 +94,7 @@ const HeaderDefaultActionsAccount: React.FC<Props> = ({
setBottomSheetVisible(false) setBottomSheetVisible(false)
mutate({ type: 'block' }) mutate({ type: 'block' })
}} }}
iconFront='x-circle' iconFront='XCircle'
title={t( title={t(
'timeline:shared.header.default.actions.account.block.button', 'timeline:shared.header.default.actions.account.block.button',
{ {
@ -107,7 +107,7 @@ const HeaderDefaultActionsAccount: React.FC<Props> = ({
setBottomSheetVisible(false) setBottomSheetVisible(false)
mutate({ type: 'reports' }) mutate({ type: 'reports' })
}} }}
iconFront='flag' iconFront='Flag'
title={t( title={t(
'timeline:shared.header.default.actions.account.report.button', 'timeline:shared.header.default.actions.account.report.button',
{ {

View File

@ -54,7 +54,7 @@ const HeaderDefaultActionsDomain: React.FC<Props> = ({
setBottomSheetVisible(false) setBottomSheetVisible(false)
mutate() mutate()
}} }}
iconFront='cloud-off' iconFront='CloudOff'
title={t(`timeline:shared.header.default.actions.domain.block.button`, { title={t(`timeline:shared.header.default.actions.domain.block.button`, {
domain domain
})} })}

View File

@ -128,7 +128,7 @@ const HeaderDefaultActionsStatus: React.FC<Props> = ({
setBottomSheetVisible(false) setBottomSheetVisible(false)
mutate({ type: 'delete' }) mutate({ type: 'delete' })
}} }}
iconFront='trash' iconFront='Trash'
title={t('timeline:shared.header.default.actions.status.delete.button')} title={t('timeline:shared.header.default.actions.status.delete.button')}
/> />
<MenuRow <MenuRow
@ -174,7 +174,7 @@ const HeaderDefaultActionsStatus: React.FC<Props> = ({
] ]
) )
}} }}
iconFront='trash' iconFront='Trash'
title={t('timeline:shared.header.default.actions.status.edit.button')} title={t('timeline:shared.header.default.actions.status.edit.button')}
/> />
<MenuRow <MenuRow
@ -182,7 +182,7 @@ const HeaderDefaultActionsStatus: React.FC<Props> = ({
setBottomSheetVisible(false) setBottomSheetVisible(false)
mutate({ type: 'mute', state: status.muted }) mutate({ type: 'mute', state: status.muted })
}} }}
iconFront='volume-x' iconFront='VolumeX'
title={ title={
status.muted status.muted
? t( ? t(
@ -200,7 +200,7 @@ const HeaderDefaultActionsStatus: React.FC<Props> = ({
setBottomSheetVisible(false) setBottomSheetVisible(false)
mutate({ type: 'pin', state: status.pinned }) mutate({ type: 'pin', state: status.pinned })
}} }}
iconFront='anchor' iconFront='Anchor'
title={ title={
status.pinned status.pinned
? t( ? t(

View File

@ -1,6 +1,6 @@
import client from '@api/client' import client from '@api/client'
import { Feather } from '@expo/vector-icons'
import haptics from '@components/haptics' import haptics from '@components/haptics'
import Icon from '@components/Icon'
import { toast } from '@components/toast' import { toast } from '@components/toast'
import { relationshipFetch } from '@utils/fetches/relationshipFetch' import { relationshipFetch } from '@utils/fetches/relationshipFetch'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
@ -9,10 +9,10 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { Pressable, StyleSheet, View } from 'react-native' import { Pressable, StyleSheet, View } from 'react-native'
import { Chase } from 'react-native-animated-spinkit' import { Chase } from 'react-native-animated-spinkit'
import { useQuery } from 'react-query' import { useQuery } from 'react-query'
import HeaderSharedApplication from './HeaderShared/Application'
import HeaderSharedVisibility from './HeaderShared/Visibility'
import HeaderSharedCreated from './HeaderShared/Created'
import HeaderSharedAccount from './HeaderShared/Account' import HeaderSharedAccount from './HeaderShared/Account'
import HeaderSharedApplication from './HeaderShared/Application'
import HeaderSharedCreated from './HeaderShared/Created'
import HeaderSharedVisibility from './HeaderShared/Visibility'
export interface Props { export interface Props {
notification: Mastodon.Notification notification: Mastodon.Notification
@ -73,19 +73,19 @@ const TimelineHeaderNotification: React.FC<Props> = ({ notification }) => {
case 'success': case 'success':
return ( return (
<Pressable onPress={relationshipOnPress}> <Pressable onPress={relationshipOnPress}>
<Feather <Icon
name={ name={
updateData updateData
? updateData.following ? updateData.following
? 'user-check' ? 'UserCheck'
: updateData.requested : updateData.requested
? 'loader' ? 'Loader'
: 'user-plus' : 'UserPlus'
: data!.following : data!.following
? 'user-check' ? 'UserCheck'
: data!.requested : data!.requested
? 'loader' ? 'Loader'
: 'user-plus' : 'UserPlus'
} }
color={ color={
updateData updateData

View File

@ -1,4 +1,4 @@
import { Feather } from '@expo/vector-icons' import Icon from '@components/Icon'
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 React from 'react' import React from 'react'
@ -12,8 +12,8 @@ const HeaderSharedVisibility: React.FC<Props> = ({ visibility }) => {
const { theme } = useTheme() const { theme } = useTheme()
return visibility && visibility === 'private' ? ( return visibility && visibility === 'private' ? (
<Feather <Icon
name='lock' name='Lock'
size={StyleConstants.Font.Size.S} size={StyleConstants.Font.Size.S}
color={theme.secondary} color={theme.secondary}
style={styles.visibility} style={styles.visibility}

View File

@ -1,9 +1,9 @@
import client from '@api/client' import client from '@api/client'
import Button from '@components/Button' import Button from '@components/Button'
import haptics from '@components/haptics' import haptics from '@components/haptics'
import Icon from '@components/Icon'
import relativeTime from '@components/relativeTime' import relativeTime from '@components/relativeTime'
import { TimelineData } from '@components/Timelines/Timeline' import { TimelineData } from '@components/Timelines/Timeline'
import { Feather } from '@expo/vector-icons'
import { ParseEmojis } from '@root/components/Parse' import { ParseEmojis } from '@root/components/Parse'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
@ -151,8 +151,8 @@ const TimelinePoll: React.FC<Props> = ({
const isSelected = useCallback( const isSelected = useCallback(
(index: number): any => (index: number): any =>
allOptions[index] allOptions[index]
? `check-${poll.multiple ? 'square' : 'circle'}` ? `Check${poll.multiple ? 'Square' : 'Circle'}`
: `${poll.multiple ? 'square' : 'circle'}`, : `${poll.multiple ? 'Square' : 'Circle'}`,
[allOptions] [allOptions]
) )
@ -160,11 +160,11 @@ const TimelinePoll: React.FC<Props> = ({
return poll.options.map((option, index) => ( return poll.options.map((option, index) => (
<View key={index} style={styles.optionContainer}> <View key={index} style={styles.optionContainer}>
<View style={styles.optionContent}> <View style={styles.optionContent}>
<Feather <Icon
style={styles.optionSelection} style={styles.optionSelection}
name={ name={
`${poll.own_votes?.includes(index) ? 'check-' : ''}${ `${poll.own_votes?.includes(index) ? 'Check' : ''}${
poll.multiple ? 'square' : 'circle' poll.multiple ? 'Square' : 'Circle'
}` as any }` as any
} }
size={StyleConstants.Font.Size.M} size={StyleConstants.Font.Size.M}
@ -224,7 +224,7 @@ const TimelinePoll: React.FC<Props> = ({
}} }}
> >
<View style={[styles.optionContent]}> <View style={[styles.optionContent]}>
<Feather <Icon
style={styles.optionSelection} style={styles.optionSelection}
name={isSelected(index)} name={isSelected(index)}
size={StyleConstants.Font.Size.L} size={StyleConstants.Font.Size.L}

View File

@ -1,10 +1,10 @@
import Icon from '@components/Icon'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React from 'react' import React from 'react'
import { StyleSheet, Text, View } from 'react-native' import { StyleSheet, Text, View } from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context' import { SafeAreaView } from 'react-native-safe-area-context'
import Toast from 'react-native-toast-message' import Toast from 'react-native-toast-message'
import { useTheme } from '@utils/styles/ThemeManager'
import { Feather } from '@expo/vector-icons'
import { StyleConstants } from '@utils/styles/constants'
export interface Params { export interface Params {
type: 'success' | 'error' | 'warning' type: 'success' | 'error' | 'warning'
@ -49,9 +49,9 @@ const toast = ({
const ToastBase = ({ config }: { config: Config }) => { const ToastBase = ({ config }: { config: Config }) => {
const { theme } = useTheme() const { theme } = useTheme()
const iconSet = { const iconSet = {
success: 'check-circle', success: 'CheckCircle',
error: 'x-circle', error: 'XCircle',
warning: 'alert-circle' warning: 'AlertCircle'
} }
enum colorMapping { enum colorMapping {
success = 'blue', success = 'blue',
@ -67,11 +67,10 @@ const ToastBase = ({ config }: { config: Config }) => {
]} ]}
> >
<View style={styles.container}> <View style={styles.container}>
<Feather <Icon
// @ts-ignore
name={iconSet[config.type]} name={iconSet[config.type]}
size={StyleConstants.Font.Size.M}
color={theme[colorMapping[config.type]]} color={theme[colorMapping[config.type]]}
size={StyleConstants.Font.Size.M + 2}
/> />
<View style={styles.texts}> <View style={styles.texts}>
<Text style={[styles.text1, { color: theme.primary }]}> <Text style={[styles.text1, { color: theme.primary }]}>
@ -104,7 +103,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
padding: StyleConstants.Spacing.L padding: StyleConstants.Spacing.M
}, },
texts: { texts: {
marginLeft: StyleConstants.Spacing.S marginLeft: StyleConstants.Spacing.S
@ -114,7 +113,7 @@ const styles = StyleSheet.create({
}, },
text2: { text2: {
...StyleConstants.FontStyle.S, ...StyleConstants.FontStyle.S,
marginTop: StyleConstants.Spacing.S marginTop: StyleConstants.Spacing.XS
} }
}) })

View File

@ -57,16 +57,16 @@ export default {
account: { account: {
heading: '关于用户', heading: '关于用户',
mute: { mute: {
function: '隐藏 {{acct}} 的嘟文', function: '隐藏 @{{acct}} 的嘟文',
button: '隐藏 {{acct}} 的嘟文' button: '隐藏 @{{acct}} 的嘟文'
}, },
block: { block: {
function: '屏蔽 {{acct}}', function: '屏蔽 @{{acct}}',
button: '屏蔽 {{acct}}' button: '屏蔽 @{{acct}}'
}, },
report: { report: {
function: '举报 {{acct}}', function: '举报 @{{acct}}',
button: '举报 {{acct}}' button: '举报 @{{acct}}'
} }
}, },
domain: { domain: {

View File

@ -15,7 +15,7 @@ const ScreenMeLists: React.FC = () => {
return data?.map((d: Mastodon.List, i: number) => ( return data?.map((d: Mastodon.List, i: number) => (
<MenuRow <MenuRow
key={i} key={i}
iconFront='list' iconFront='List'
title={d.title} title={d.title}
onPress={() => onPress={() =>
navigation.navigate('Screen-Me-Lists-List', { navigation.navigate('Screen-Me-Lists-List', {

View File

@ -1,17 +1,17 @@
import React, { useRef, useState } from 'react' import { useScrollToTop } from '@react-navigation/native'
import { Animated, ScrollView } from 'react-native' import Collections from '@screens/Me/Root/Collections'
import { useSelector } from 'react-redux'
import { getLocalUrl } from '@utils/slices/instancesSlice'
import Login from '@screens/Me/Root/Login' import Login from '@screens/Me/Root/Login'
import MyInfo from '@screens/Me/Root/MyInfo' import MyInfo from '@screens/Me/Root/MyInfo'
import Collections from '@screens/Me/Root/Collections'
import Settings from '@screens/Me/Root/Settings' import Settings from '@screens/Me/Root/Settings'
import Logout from '@screens/Me/Root/Logout' import Logout from '@screens/Me/Root/Logout'
import { useScrollToTop } from '@react-navigation/native' import AccountNav from '@screens/Shared/Account/Nav'
import { AccountState } from '../Shared/Account' import accountReducer from '@screens/Shared/Account/utils/reducer'
import AccountNav from '../Shared/Account/Nav' import accountInitialState from '@screens/Shared/Account/utils/initialState'
import AccountContext from '@screens/Shared/Account/utils/createContext'
import { getLocalUrl } from '@utils/slices/instancesSlice'
import React, { useReducer, useRef, useState } from 'react'
import { Animated, ScrollView } from 'react-native'
import { useSelector } from 'react-redux'
const ScreenMeRoot: React.FC = () => { const ScreenMeRoot: React.FC = () => {
const localRegistered = useSelector(getLocalUrl) const localRegistered = useSelector(getLocalUrl)
@ -19,24 +19,25 @@ const ScreenMeRoot: React.FC = () => {
const scrollRef = useRef<ScrollView>(null) const scrollRef = useRef<ScrollView>(null)
useScrollToTop(scrollRef) useScrollToTop(scrollRef)
const scrollY = useRef(new Animated.Value(0)).current const scrollY = useRef(new Animated.Value(0))
const [data, setData] = useState<Mastodon.Account>() const [data, setData] = useState<Mastodon.Account>()
const [accountState, accountDispatch] = useReducer(
accountReducer,
accountInitialState
)
return ( return (
<> <AccountContext.Provider value={{ accountState, accountDispatch }}>
{localRegistered && data ? ( {localRegistered && data ? (
<AccountNav <AccountNav scrollY={scrollY} account={data} />
accountState={{ headerRatio: 0.4 } as AccountState}
scrollY={scrollY}
account={data}
/>
) : null} ) : null}
<ScrollView <ScrollView
ref={scrollRef} ref={scrollRef}
keyboardShouldPersistTaps='handled' keyboardShouldPersistTaps='handled'
bounces={false} bounces={false}
onScroll={Animated.event( onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: scrollY } } }], [{ nativeEvent: { contentOffset: { y: scrollY.current } } }],
{ useNativeDriver: false } { useNativeDriver: false }
)} )}
scrollEventThrottle={8} scrollEventThrottle={8}
@ -46,7 +47,7 @@ const ScreenMeRoot: React.FC = () => {
<Settings /> <Settings />
{localRegistered && <Logout />} {localRegistered && <Logout />}
</ScrollView> </ScrollView>
</> </AccountContext.Provider>
) )
} }

View File

@ -27,32 +27,32 @@ const Collections: React.FC = () => {
return ( return (
<MenuContainer> <MenuContainer>
<MenuRow <MenuRow
iconFront='mail' iconFront='Mail'
iconBack='chevron-right' iconBack='ChevronRight'
title={t('content.collections.conversations')} title={t('content.collections.conversations')}
onPress={() => navigation.navigate('Screen-Me-Conversations')} onPress={() => navigation.navigate('Screen-Me-Conversations')}
/> />
<MenuRow <MenuRow
iconFront='bookmark' iconFront='Bookmark'
iconBack='chevron-right' iconBack='ChevronRight'
title={t('content.collections.bookmarks')} title={t('content.collections.bookmarks')}
onPress={() => navigation.navigate('Screen-Me-Bookmarks')} onPress={() => navigation.navigate('Screen-Me-Bookmarks')}
/> />
<MenuRow <MenuRow
iconFront='star' iconFront='Star'
iconBack='chevron-right' iconBack='ChevronRight'
title={t('content.collections.favourites')} title={t('content.collections.favourites')}
onPress={() => navigation.navigate('Screen-Me-Favourites')} onPress={() => navigation.navigate('Screen-Me-Favourites')}
/> />
<MenuRow <MenuRow
iconFront='list' iconFront='List'
iconBack='chevron-right' iconBack='ChevronRight'
title={t('content.collections.lists')} title={t('content.collections.lists')}
onPress={() => navigation.navigate('Screen-Me-Lists')} onPress={() => navigation.navigate('Screen-Me-Lists')}
/> />
<MenuRow <MenuRow
iconFront='clipboard' iconFront='Clipboard'
iconBack='chevron-right' iconBack='ChevronRight'
title={t('content.collections.announcements')} title={t('content.collections.announcements')}
content={announcementContent} content={announcementContent}
loading={isFetching} loading={isFetching}

View File

@ -1,8 +1,8 @@
import analytics from '@components/analytics' import analytics from '@components/analytics'
import Button from '@components/Button' import Button from '@components/Button'
import haptics from '@components/haptics' import haptics from '@components/haptics'
import Icon from '@components/Icon'
import { ParseHTML } from '@components/Parse' import { ParseHTML } from '@components/Parse'
import { Feather } from '@expo/vector-icons'
import { useNavigation } from '@react-navigation/native' import { useNavigation } from '@react-navigation/native'
import { applicationFetch } from '@utils/fetches/applicationFetch' import { applicationFetch } from '@utils/fetches/applicationFetch'
import { instanceFetch } from '@utils/fetches/instanceFetch' import { instanceFetch } from '@utils/fetches/instanceFetch'
@ -282,8 +282,8 @@ const Login: React.FC = () => {
</View> </View>
</View> </View>
<Text style={[styles.disclaimer, { color: theme.secondary }]}> <Text style={[styles.disclaimer, { color: theme.secondary }]}>
<Feather <Icon
name='lock' name='Lock'
size={StyleConstants.Font.Size.M} size={StyleConstants.Font.Size.M}
color={theme.secondary} color={theme.secondary}
/>{' '} />{' '}

View File

@ -1,12 +1,10 @@
import React, { useEffect } from 'react'
import { useQuery } from 'react-query'
import { accountFetch } from '@utils/fetches/accountFetch'
import AccountHeader from '@screens/Shared/Account/Header' import AccountHeader from '@screens/Shared/Account/Header'
import AccountInformation from '@screens/Shared/Account/Information' import AccountInformation from '@screens/Shared/Account/Information'
import { useSelector } from 'react-redux' import { accountFetch } from '@utils/fetches/accountFetch'
import { getLocalAccountId } from '@utils/slices/instancesSlice' import { getLocalAccountId } from '@utils/slices/instancesSlice'
import { AccountState } from '@root/screens/Shared/Account' import React, { useEffect } from 'react'
import { useQuery } from 'react-query'
import { useSelector } from 'react-redux'
export interface Props { export interface Props {
setData: React.Dispatch<React.SetStateAction<Mastodon.Account | undefined>> setData: React.Dispatch<React.SetStateAction<Mastodon.Account | undefined>>
@ -23,11 +21,7 @@ const MyInfo: React.FC<Props> = ({ setData }) => {
return ( return (
<> <>
<AccountHeader <AccountHeader account={data} limitHeight />
accountState={{ headerRatio: 0.4 } as AccountState}
account={data}
limitHeight
/>
<AccountInformation account={data} disableActions /> <AccountInformation account={data} disableActions />
</> </>
) )

View File

@ -11,8 +11,8 @@ const Settings: React.FC = () => {
return ( return (
<MenuContainer> <MenuContainer>
<MenuRow <MenuRow
iconFront='settings' iconFront='Settings'
iconBack='chevron-right' iconBack='ChevronRight'
title={t('content.settings')} title={t('content.settings')}
onPress={() => navigation.navigate('Screen-Me-Settings')} onPress={() => navigation.navigate('Screen-Me-Settings')}
/> />

View File

@ -39,7 +39,7 @@ const ScreenMeSettings: React.FC = () => {
<MenuRow <MenuRow
title={t('content.language.heading')} title={t('content.language.heading')}
content={t(`content.language.options.${settingsLanguage}`)} content={t(`content.language.options.${settingsLanguage}`)}
iconBack='chevron-right' iconBack='ChevronRight'
onPress={() => onPress={() =>
ActionSheetIOS.showActionSheetWithOptions( ActionSheetIOS.showActionSheetWithOptions(
{ {
@ -70,7 +70,7 @@ const ScreenMeSettings: React.FC = () => {
<MenuRow <MenuRow
title={t('content.theme.heading')} title={t('content.theme.heading')}
content={t(`content.theme.options.${settingsTheme}`)} content={t(`content.theme.options.${settingsTheme}`)}
iconBack='chevron-right' iconBack='ChevronRight'
onPress={() => onPress={() =>
ActionSheetIOS.showActionSheetWithOptions( ActionSheetIOS.showActionSheetWithOptions(
{ {
@ -106,7 +106,7 @@ const ScreenMeSettings: React.FC = () => {
<MenuRow <MenuRow
title={t('content.browser.heading')} title={t('content.browser.heading')}
content={t(`content.browser.options.${settingsBrowser}`)} content={t(`content.browser.options.${settingsBrowser}`)}
iconBack='chevron-right' iconBack='ChevronRight'
onPress={() => onPress={() =>
ActionSheetIOS.showActionSheetWithOptions( ActionSheetIOS.showActionSheetWithOptions(
{ {
@ -137,7 +137,7 @@ const ScreenMeSettings: React.FC = () => {
<MenuRow <MenuRow
title={t('content.cache.heading')} title={t('content.cache.heading')}
content={cacheSize ? prettyBytes(cacheSize) : '暂无缓存'} content={cacheSize ? prettyBytes(cacheSize) : '暂无缓存'}
iconBack='chevron-right' iconBack='ChevronRight'
onPress={async () => { onPress={async () => {
await CacheManager.clearCache() await CacheManager.clearCache()
haptics('Success') haptics('Success')
@ -154,7 +154,7 @@ const ScreenMeSettings: React.FC = () => {
/> />
<MenuRow <MenuRow
title={t('content.copyrights.heading')} title={t('content.copyrights.heading')}
iconBack='chevron-right' iconBack='ChevronRight'
/> />
<Text style={[styles.version, { color: theme.secondary }]}> <Text style={[styles.version, { color: theme.secondary }]}>
{t('content.version', { version: '1.0.0' })} {t('content.version', { version: '1.0.0' })}

View File

@ -1,18 +1,20 @@
import BottomSheet from '@components/BottomSheet'
import { HeaderRight } from '@components/Header'
import HeaderDefaultActionsAccount from '@components/Timelines/Timeline/Shared/HeaderDefault/ActionsAccount'
import { accountFetch } from '@utils/fetches/accountFetch'
import { getLocalAccountId } from '@utils/slices/instancesSlice'
import React, { useEffect, useReducer, useRef, useState } from 'react' import React, { useEffect, useReducer, useRef, useState } from 'react'
import { Animated, ScrollView } from 'react-native' import { Animated, ScrollView } from 'react-native'
import { useQuery } from 'react-query' import { useQuery } from 'react-query'
import { accountFetch } from '@utils/fetches/accountFetch' import { useSelector } from 'react-redux'
import AccountToots from '@screens/Shared/Account/Toots' import AccountHeader from './Account/Header'
import AccountHeader from '@screens/Shared/Account/Header' import AccountInformation from './Account/Information'
import AccountInformation from '@screens/Shared/Account/Information'
import AccountNav from './Account/Nav' import AccountNav from './Account/Nav'
import AccountSegmentedControl from './Account/SegmentedControl' import AccountSegmentedControl from './Account/SegmentedControl'
import { HeaderRight } from '@root/components/Header' import AccountToots from './Account/Toots'
import BottomSheet from '@root/components/BottomSheet' import AccountContext from './Account/utils/createContext'
import { useSelector } from 'react-redux' import accountInitialState from './Account/utils/initialState'
import { getLocalAccountId } from '@root/utils/slices/instancesSlice' import accountReducer from './Account/utils/reducer'
import HeaderDefaultActionsAccount from '@root/components/Timelines/Timeline/Shared/HeaderDefault/ActionsAccount'
// Moved account example: https://m.cmx.im/web/accounts/27812 // Moved account example: https://m.cmx.im/web/accounts/27812
@ -25,48 +27,6 @@ export interface Props {
navigation: any navigation: any
} }
export type AccountState = {
headerRatio: number
informationLayout?: {
y: number
height: number
}
segmentedIndex: number
}
export type AccountAction =
| {
type: 'headerRatio'
payload: AccountState['headerRatio']
}
| {
type: 'informationLayout'
payload: AccountState['informationLayout']
}
| {
type: 'segmentedIndex'
payload: AccountState['segmentedIndex']
}
const AccountInitialState: AccountState = {
headerRatio: 0.4,
informationLayout: { height: 0, y: 100 },
segmentedIndex: 0
}
const accountReducer = (
state: AccountState,
action: AccountAction
): AccountState => {
switch (action.type) {
case 'headerRatio':
return { ...state, headerRatio: action.payload }
case 'informationLayout':
return { ...state, informationLayout: action.payload }
case 'segmentedIndex':
return { ...state, segmentedIndex: action.payload }
default:
throw new Error('Unexpected action')
}
}
const ScreenSharedAccount: React.FC<Props> = ({ const ScreenSharedAccount: React.FC<Props> = ({
route: { route: {
params: { account } params: { account }
@ -76,10 +36,10 @@ const ScreenSharedAccount: React.FC<Props> = ({
const localAccountId = useSelector(getLocalAccountId) const localAccountId = useSelector(getLocalAccountId)
const { data } = useQuery(['Account', { id: account.id }], accountFetch) const { data } = useQuery(['Account', { id: account.id }], accountFetch)
const scrollY = useRef(new Animated.Value(0)).current const scrollY = useRef(new Animated.Value(0))
const [accountState, accountDispatch] = useReducer( const [accountState, accountDispatch] = useReducer(
accountReducer, accountReducer,
AccountInitialState accountInitialState
) )
const [modalVisible, setBottomSheetVisible] = useState(false) const [modalVisible, setBottomSheetVisible] = useState(false)
@ -88,7 +48,7 @@ const ScreenSharedAccount: React.FC<Props> = ({
navigation.setOptions({ navigation.setOptions({
headerRight: () => ( headerRight: () => (
<HeaderRight <HeaderRight
content='more-horizontal' content='MoreHorizontal'
onPress={() => setBottomSheetVisible(true)} onPress={() => setBottomSheetVisible(true)}
/> />
) )
@ -97,39 +57,23 @@ const ScreenSharedAccount: React.FC<Props> = ({
}, []) }, [])
return ( return (
<> <AccountContext.Provider value={{ accountState, accountDispatch }}>
<AccountNav <AccountNav scrollY={scrollY} account={data} />
accountState={accountState}
scrollY={scrollY}
account={data}
/>
{accountState.informationLayout?.height && {accountState.informationLayout?.height &&
accountState.informationLayout.y ? ( accountState.informationLayout.y ? (
<AccountSegmentedControl <AccountSegmentedControl scrollY={scrollY} />
accountState={accountState}
accountDispatch={accountDispatch}
scrollY={scrollY}
/>
) : null} ) : null}
<ScrollView <ScrollView
scrollEventThrottle={16} scrollEventThrottle={16}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
onScroll={Animated.event( onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: scrollY } } }], [{ nativeEvent: { contentOffset: { y: scrollY.current } } }],
{ useNativeDriver: false } { useNativeDriver: false }
)} )}
> >
<AccountHeader <AccountHeader account={data} />
accountState={accountState} <AccountInformation account={data} />
accountDispatch={accountDispatch} <AccountToots id={account.id} />
account={data}
/>
<AccountInformation accountDispatch={accountDispatch} account={data} />
<AccountToots
accountState={accountState}
accountDispatch={accountDispatch}
id={account.id}
/>
</ScrollView> </ScrollView>
<BottomSheet <BottomSheet
@ -144,7 +88,7 @@ const ScreenSharedAccount: React.FC<Props> = ({
/> />
)} )}
</BottomSheet> </BottomSheet>
</> </AccountContext.Provider>
) )
} }

View File

@ -1,21 +1,15 @@
import { useTheme } from '@root/utils/styles/ThemeManager' import { useTheme } from '@root/utils/styles/ThemeManager'
import React, { Dispatch, useEffect, useState } from 'react' import React, { useContext, useEffect, useState } from 'react'
import { Dimensions, Image, StyleSheet, View } from 'react-native' import { Dimensions, Image, StyleSheet, View } from 'react-native'
import { AccountAction, AccountState } from '../Account' import AccountContext from './utils/createContext'
export interface Props { export interface Props {
accountState: AccountState
accountDispatch?: Dispatch<AccountAction>
account?: Mastodon.Account account?: Mastodon.Account
limitHeight?: boolean limitHeight?: boolean
} }
const AccountHeader: React.FC<Props> = ({ const AccountHeader: React.FC<Props> = ({ account, limitHeight = false }) => {
accountState, const { accountState, accountDispatch } = useContext(AccountContext)
accountDispatch,
account,
limitHeight = false
}) => {
const { theme } = useTheme() const { theme } = useTheme()
const [ratio, setRatio] = useState(accountState.headerRatio) const [ratio, setRatio] = useState(accountState.headerRatio)
@ -70,4 +64,7 @@ const styles = StyleSheet.create({
} }
}) })
export default AccountHeader export default React.memo(
AccountHeader,
(_, next) => next.account === undefined
)

View File

@ -1,5 +1,5 @@
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import React, { createRef, Dispatch, useCallback, useEffect } from 'react' import React, { createRef, useCallback, useContext, useEffect } from 'react'
import { Animated, StyleSheet, View } from 'react-native' import { Animated, StyleSheet, View } from 'react-native'
import AccountInformationAvatar from './Information/Avatar' import AccountInformationAvatar from './Information/Avatar'
import AccountInformationName from './Information/Name' import AccountInformationName from './Information/Name'
@ -9,19 +9,18 @@ import AccountInformationStats from './Information/Stats'
import AccountInformationActions from './Information/Actions' import AccountInformationActions from './Information/Actions'
import AccountInformationFields from './Information/Fields' import AccountInformationFields from './Information/Fields'
import AccountInformationNotes from './Information/Notes' import AccountInformationNotes from './Information/Notes'
import { AccountAction } from '../Account' import AccountContext from './utils/createContext'
export interface Props { export interface Props {
accountDispatch?: Dispatch<AccountAction>
account: Mastodon.Account | undefined account: Mastodon.Account | undefined
disableActions?: boolean disableActions?: boolean
} }
const AccountInformation: React.FC<Props> = ({ const AccountInformation: React.FC<Props> = ({
accountDispatch,
account, account,
disableActions = false disableActions = false
}) => { }) => {
const { accountDispatch } = useContext(AccountContext)
const shimmerAvatarRef = createRef<any>() const shimmerAvatarRef = createRef<any>()
const shimmerNameRef = createRef<any>() const shimmerNameRef = createRef<any>()
const shimmerAccountRef = createRef<any>() const shimmerAccountRef = createRef<any>()
@ -98,4 +97,7 @@ const styles = StyleSheet.create({
} }
}) })
export default AccountInformation export default React.memo(
AccountInformation,
(_, next) => next.account === undefined
)

View File

@ -1,6 +1,6 @@
import { Feather } from '@expo/vector-icons' import Icon from '@components/Icon'
import { StyleConstants } from '@root/utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@root/utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import { LinearGradient } from 'expo-linear-gradient' import { LinearGradient } from 'expo-linear-gradient'
import React, { forwardRef } from 'react' import React, { forwardRef } from 'react'
import { StyleSheet, Text, View } from 'react-native' import { StyleSheet, Text, View } from 'react-native'
@ -38,13 +38,19 @@ const AccountInformationAccount = forwardRef<ShimmerPlaceholder, Props>(
@{account?.acct} @{account?.acct}
</Text> </Text>
{account?.locked ? ( {account?.locked ? (
<Feather name='lock' style={styles.type} color={theme.secondary} /> <Icon
) : null} name='Lock'
{account?.bot ? (
<Feather
name='hard-drive'
style={styles.type} style={styles.type}
color={theme.secondary} color={theme.secondary}
size={StyleConstants.Font.Size.M}
/>
) : null}
{account?.bot ? (
<Icon
name='HardDrive'
style={styles.type}
color={theme.secondary}
size={StyleConstants.Font.Size.M}
/> />
) : null} ) : null}
</View> </View>

View File

@ -131,7 +131,7 @@ const AccountInformationActions: React.FC<Props> = ({ account }) => {
{query.data && !query.data.blocked_by ? ( {query.data && !query.data.blocked_by ? (
<Button <Button
type='icon' type='icon'
content='mail' content='Mail'
round round
onPress={() => onPress={() =>
navigation.navigate('Screen-Shared-Compose', { navigation.navigate('Screen-Shared-Compose', {

View File

@ -1,6 +1,6 @@
import { Feather } from '@expo/vector-icons' import Icon from '@components/Icon'
import { StyleConstants } from '@root/utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@root/utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import { LinearGradient } from 'expo-linear-gradient' import { LinearGradient } from 'expo-linear-gradient'
import React, { forwardRef } from 'react' import React, { forwardRef } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -29,8 +29,8 @@ const AccountInformationCreated = forwardRef<ShimmerPlaceholder, Props>(
shimmerColors={theme.shimmer} shimmerColors={theme.shimmer}
> >
<View style={styles.created}> <View style={styles.created}>
<Feather <Icon
name='calendar' name='Calendar'
size={StyleConstants.Font.Size.S} size={StyleConstants.Font.Size.S}
color={theme.secondary} color={theme.secondary}
style={styles.icon} style={styles.icon}

View File

@ -1,5 +1,5 @@
import Icon from '@components/Icon'
import { ParseHTML } from '@components/Parse' import { ParseHTML } from '@components/Parse'
import { Feather } from '@expo/vector-icons'
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 React from 'react' import React from 'react'
@ -27,8 +27,8 @@ const AccountInformationFields: React.FC<Props> = ({ account }) => {
showFullLink showFullLink
/> />
{field.verified_at ? ( {field.verified_at ? (
<Feather <Icon
name='check-circle' name='CheckCircle'
size={StyleConstants.Font.Size.M} size={StyleConstants.Font.Size.M}
color={theme.primary} color={theme.primary}
style={styles.fieldCheck} style={styles.fieldCheck}

View File

@ -1,18 +1,18 @@
import { ParseEmojis } from '@components/Parse' import { ParseEmojis } from '@components/Parse'
import { AccountState } from '@screens/Shared/Account'
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 React from 'react' import React, { MutableRefObject, useContext } from 'react'
import { Animated, Dimensions, StyleSheet, Text, View } from 'react-native' import { Animated, Dimensions, StyleSheet, Text, View } from 'react-native'
import { useSafeAreaInsets } from 'react-native-safe-area-context' import { useSafeAreaInsets } from 'react-native-safe-area-context'
import AccountContext from './utils/createContext'
export interface Props { export interface Props {
accountState: AccountState scrollY: MutableRefObject<Animated.Value>
scrollY: Animated.Value
account: Mastodon.Account | undefined account: Mastodon.Account | undefined
} }
const AccountNav: React.FC<Props> = ({ accountState, scrollY, account }) => { const AccountNav: React.FC<Props> = ({ scrollY, account }) => {
const { accountState } = useContext(AccountContext)
const { theme } = useTheme() const { theme } = useTheme()
const headerHeight = useSafeAreaInsets().top + 44 const headerHeight = useSafeAreaInsets().top + 44
@ -29,7 +29,7 @@ const AccountNav: React.FC<Props> = ({ accountState, scrollY, account }) => {
styles.base, styles.base,
{ {
backgroundColor: theme.background, backgroundColor: theme.background,
opacity: scrollY.interpolate({ opacity: scrollY.current.interpolate({
inputRange: [0, 200], inputRange: [0, 200],
outputRange: [0, 1], outputRange: [0, 1],
extrapolate: 'clamp' extrapolate: 'clamp'
@ -51,7 +51,7 @@ const AccountNav: React.FC<Props> = ({ accountState, scrollY, account }) => {
style={[ style={[
styles.display_name, styles.display_name,
{ {
marginTop: scrollY.interpolate({ marginTop: scrollY.current.interpolate({
inputRange: [nameY, nameY + 20], inputRange: [nameY, nameY + 20],
outputRange: [50, 0], outputRange: [50, 0],
extrapolate: 'clamp' extrapolate: 'clamp'
@ -89,4 +89,4 @@ const styles = StyleSheet.create({
} }
}) })
export default AccountNav export default React.memo(AccountNav, (_, next) => next.account === undefined)

View File

@ -1,28 +1,23 @@
import SegmentedControl from '@react-native-community/segmented-control' import SegmentedControl from '@react-native-community/segmented-control'
import { StyleConstants } from '@root/utils/styles/constants' import { StyleConstants } from '@root/utils/styles/constants'
import { useTheme } from '@root/utils/styles/ThemeManager' import { useTheme } from '@root/utils/styles/ThemeManager'
import React, { Dispatch } from 'react' import React, { MutableRefObject, useContext } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Animated, StyleSheet } from 'react-native' import { Animated, StyleSheet } from 'react-native'
import { useSafeAreaInsets } from 'react-native-safe-area-context' import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { AccountAction, AccountState } from '../Account' import AccountContext from './utils/createContext'
export interface Props { export interface Props {
accountState: AccountState scrollY: MutableRefObject<Animated.Value>
accountDispatch: Dispatch<AccountAction>
scrollY: Animated.Value
} }
const AccountSegmentedControl: React.FC<Props> = ({ const AccountSegmentedControl: React.FC<Props> = ({ scrollY }) => {
accountState, const { accountState, accountDispatch } = useContext(AccountContext)
accountDispatch,
scrollY
}) => {
const { t } = useTranslation('sharedAccount') const { t } = useTranslation('sharedAccount')
const { mode, theme } = useTheme() const { mode, theme } = useTheme()
const headerHeight = useSafeAreaInsets().top + 44 const headerHeight = useSafeAreaInsets().top + 44
const translateY = scrollY.interpolate({ const translateY = scrollY.current.interpolate({
inputRange: [ inputRange: [
0, 0,
(accountState.informationLayout?.y || 0) + (accountState.informationLayout?.y || 0) +
@ -82,4 +77,4 @@ const styles = StyleSheet.create({
} }
}) })
export default AccountSegmentedControl export default React.memo(AccountSegmentedControl, () => true)

View File

@ -1,24 +1,18 @@
import React, { Dispatch, useCallback } from 'react'
import { Dimensions, StyleSheet } from 'react-native'
import { TabView } from 'react-native-tab-view'
import Timeline from '@components/Timelines/Timeline' import Timeline from '@components/Timelines/Timeline'
import { AccountAction, AccountState } from '../Account'
import { StyleConstants } from '@root/utils/styles/constants'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs' import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'
import { StyleConstants } from '@utils/styles/constants'
import React, { useContext } from 'react'
import { Dimensions, StyleSheet } from 'react-native'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { TabView } from 'react-native-tab-view'
import AccountContext from './utils/createContext'
export interface Props { export interface Props {
accountState: AccountState
accountDispatch: Dispatch<AccountAction>
id: Mastodon.Account['id'] id: Mastodon.Account['id']
} }
const AccountToots: React.FC<Props> = ({ const AccountToots: React.FC<Props> = ({ id }) => {
accountState, const { accountState, accountDispatch } = useContext(AccountContext)
accountDispatch,
id
}) => {
const headerHeight = useSafeAreaInsets().top + 44 const headerHeight = useSafeAreaInsets().top + 44
const footerHeight = useSafeAreaInsets().bottom + useBottomTabBarHeight() const footerHeight = useSafeAreaInsets().bottom + useBottomTabBarHeight()
@ -28,18 +22,15 @@ const AccountToots: React.FC<Props> = ({
{ key: 'Account_Media' } { key: 'Account_Media' }
] ]
const renderScene = useCallback( const renderScene = ({
({ route
route }: {
}: { route: {
route: { key: App.Pages
key: App.Pages }
} }) => {
}) => { return <Timeline page={route.key} account={id} disableRefresh />
return <Timeline page={route.key} account={id} disableRefresh /> }
},
[]
)
return ( return (
<TabView <TabView

View File

@ -0,0 +1,10 @@
import { createContext, Dispatch } from 'react'
import { AccountAction, AccountState } from './types'
type ContextType = {
accountState: AccountState
accountDispatch: Dispatch<AccountAction>
}
const AccountContext = createContext<ContextType>({} as ContextType)
export default AccountContext

View File

@ -0,0 +1,9 @@
import { AccountState } from "./types"
const accountInitialState: AccountState = {
headerRatio: 0.4,
informationLayout: { height: 0, y: 100 },
segmentedIndex: 0
}
export default accountInitialState

View File

@ -0,0 +1,19 @@
import { AccountAction, AccountState } from "./types"
const accountReducer = (
state: AccountState,
action: AccountAction
): AccountState => {
switch (action.type) {
case 'headerRatio':
return { ...state, headerRatio: action.payload }
case 'informationLayout':
return { ...state, informationLayout: action.payload }
case 'segmentedIndex':
return { ...state, segmentedIndex: action.payload }
default:
throw new Error('Unexpected action')
}
}
export default accountReducer

View File

@ -0,0 +1,22 @@
export type AccountState = {
headerRatio: number
informationLayout?: {
y: number
height: number
}
segmentedIndex: number
}
export type AccountAction =
| {
type: 'headerRatio'
payload: AccountState['headerRatio']
}
| {
type: 'informationLayout'
payload: AccountState['informationLayout']
}
| {
type: 'segmentedIndex'
payload: AccountState['segmentedIndex']
}

View File

@ -157,8 +157,8 @@ const ScreenSharedAnnouncements: React.FC = ({
style={[styles.reaction, { borderColor: theme.primary }]} style={[styles.reaction, { borderColor: theme.primary }]}
onPress={() => invisibleTextInputRef.current?.focus()} onPress={() => invisibleTextInputRef.current?.focus()}
> >
<Feather <Icon
name='plus' name='Plus'
size={StyleConstants.Font.Size.M} size={StyleConstants.Font.Size.M}
color={theme.primary} color={theme.primary}
/> />

View File

@ -1,9 +1,9 @@
import { Feather } from '@expo/vector-icons' import Icon from '@components/Icon'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import layoutAnimation from '@utils/styles/layoutAnimation' import layoutAnimation from '@utils/styles/layoutAnimation'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback, useContext, useMemo } from 'react' import React, { useCallback, useContext, useMemo } from 'react'
import { ActionSheetIOS, StyleSheet, View } from 'react-native' import { ActionSheetIOS, Pressable, StyleSheet, View } from 'react-native'
import addAttachment from './/addAttachment' import addAttachment from './/addAttachment'
import ComposeContext from './utils/createContext' import ComposeContext from './utils/createContext'
@ -53,13 +53,13 @@ const ComposeActions: React.FC = () => {
const visibilityIcon = useMemo(() => { const visibilityIcon = useMemo(() => {
switch (composeState.visibility) { switch (composeState.visibility) {
case 'public': case 'public':
return 'globe' return 'Globe'
case 'unlisted': case 'unlisted':
return 'unlock' return 'Unlock'
case 'private': case 'private':
return 'lock' return 'Lock'
case 'direct': case 'direct':
return 'mail' return 'Mail'
} }
}, [composeState.visibility]) }, [composeState.visibility])
const visibilityOnPress = useCallback(() => { const visibilityOnPress = useCallback(() => {
@ -134,35 +134,41 @@ const ComposeActions: React.FC = () => {
{ backgroundColor: theme.background, borderTopColor: theme.border } { backgroundColor: theme.background, borderTopColor: theme.border }
]} ]}
> >
<Feather <Pressable
name='aperture'
size={24}
color={attachmentColor}
onPress={attachmentOnPress} onPress={attachmentOnPress}
children={<Icon name='Aperture' size={24} color={attachmentColor} />}
/> />
<Feather <Pressable
name='bar-chart-2'
size={24}
color={pollColor}
onPress={pollOnPress} onPress={pollOnPress}
children={<Icon name='BarChart2' size={24} color={pollColor} />}
/> />
<Feather <Pressable
name={visibilityIcon}
size={24}
color={composeState.visibilityLock ? theme.disabled : theme.secondary}
onPress={visibilityOnPress} onPress={visibilityOnPress}
children={
<Icon
name={visibilityIcon}
size={24}
color={
composeState.visibilityLock ? theme.disabled : theme.secondary
}
/>
}
/> />
<Feather <Pressable
name='alert-triangle'
size={24}
color={composeState.spoiler.active ? theme.primary : theme.secondary}
onPress={spoilerOnPress} onPress={spoilerOnPress}
children={
<Icon
name='AlertTriangle'
size={24}
color={
composeState.spoiler.active ? theme.primary : theme.secondary
}
/>
}
/> />
<Feather <Pressable
name='smile'
size={24}
color={emojiColor}
onPress={emojiOnPress} onPress={emojiOnPress}
children={<Icon name='Smile' size={24} color={emojiColor} />}
/> />
</View> </View>
) )

View File

@ -1,6 +1,6 @@
import { Feather } from '@expo/vector-icons'
import Button from '@components/Button' import Button from '@components/Button'
import haptics from '@components/haptics' import haptics from '@components/haptics'
import Icon from '@components/Icon'
import { useNavigation } from '@react-navigation/native' import { useNavigation } from '@react-navigation/native'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import layoutAnimation from '@utils/styles/layoutAnimation' import layoutAnimation from '@utils/styles/layoutAnimation'
@ -149,7 +149,7 @@ const ComposeAttachments: React.FC = () => {
<> <>
<Button <Button
type='icon' type='icon'
content='x' content='X'
spacing='M' spacing='M'
round round
overlay overlay
@ -165,7 +165,7 @@ const ComposeAttachments: React.FC = () => {
/> />
<Button <Button
type='icon' type='icon'
content='edit' content='Edit'
spacing='M' spacing='M'
round round
overlay overlay
@ -198,7 +198,7 @@ const ComposeAttachments: React.FC = () => {
> >
<Button <Button
type='icon' type='icon'
content='upload-cloud' content='UploadCloud'
spacing='M' spacing='M'
round round
overlay overlay
@ -224,8 +224,8 @@ const ComposeAttachments: React.FC = () => {
return ( return (
<View style={styles.base}> <View style={styles.base}>
<Pressable style={styles.sensitive} onPress={sensitiveOnPress}> <Pressable style={styles.sensitive} onPress={sensitiveOnPress}>
<Feather <Icon
name={composeState.attachments.sensitive ? 'check-circle' : 'circle'} name={composeState.attachments.sensitive ? 'CheckCircle' : 'Circle'}
size={StyleConstants.Font.Size.L} size={StyleConstants.Font.Size.L}
color={theme.primary} color={theme.primary}
/> />

View File

@ -1,6 +1,6 @@
import Button from '@components/Button' import Button from '@components/Button'
import Icon from '@components/Icon'
import { MenuRow } from '@components/Menu' import { MenuRow } from '@components/Menu'
import { Feather } from '@expo/vector-icons'
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 React, { useContext, useEffect, useState } from 'react' import React, { useContext, useEffect, useState } from 'react'
@ -47,8 +47,8 @@ const ComposePoll: React.FC = () => {
}) })
return ( return (
<View key={i} style={styles.option}> <View key={i} style={styles.option}>
<Feather <Icon
name={multiple ? 'square' : 'circle'} name={multiple ? 'Square' : 'Circle'}
size={StyleConstants.Font.Size.L} size={StyleConstants.Font.Size.L}
color={theme.secondary} color={theme.secondary}
/> />
@ -88,7 +88,7 @@ const ComposePoll: React.FC = () => {
}) })
}} }}
type='icon' type='icon'
content='minus' content='Minus'
round round
disabled={!(total > 2)} disabled={!(total > 2)}
/> />
@ -102,7 +102,7 @@ const ComposePoll: React.FC = () => {
}) })
}} }}
type='icon' type='icon'
content='plus' content='Plus'
round round
disabled={!(total < 4)} disabled={!(total < 4)}
/> />
@ -124,7 +124,7 @@ const ComposePoll: React.FC = () => {
}) })
) )
} }
iconBack='chevron-right' iconBack='ChevronRight'
/> />
<MenuRow <MenuRow
title='有效期' title='有效期'
@ -143,7 +143,7 @@ const ComposePoll: React.FC = () => {
}) })
) )
} }
iconBack='chevron-right' iconBack='ChevronRight'
/> />
</View> </View>
) )

View File

@ -94,7 +94,7 @@ const ScreenSharedImagesViewer: React.FC<Props> = ({
contentStyle: { backgroundColor: 'black' }, contentStyle: { backgroundColor: 'black' },
headerStyle: { backgroundColor: 'black' }, headerStyle: { backgroundColor: 'black' },
headerLeft: () => ( headerLeft: () => (
<HeaderLeft content='x' onPress={() => navigation.goBack()} /> <HeaderLeft content='X' onPress={() => navigation.goBack()} />
), ),
headerCenter: () => ( headerCenter: () => (
<Text style={styles.headerCenter}> <Text style={styles.headerCenter}>
@ -103,7 +103,7 @@ const ScreenSharedImagesViewer: React.FC<Props> = ({
), ),
headerRight: () => ( headerRight: () => (
<HeaderRight <HeaderRight
content='share' content='Share'
onPress={() => onPress={() =>
ActionSheetIOS.showShareActionSheetWithOptions( ActionSheetIOS.showShareActionSheetWithOptions(
{ {

View File

@ -1,6 +1,6 @@
import { HeaderRight } from '@components/Header' import { HeaderRight } from '@components/Header'
import Icon from '@components/Icon'
import { ParseEmojis, ParseHTML } from '@components/Parse' import { ParseEmojis, ParseHTML } from '@components/Parse'
import { Feather } from '@expo/vector-icons'
import { useNavigation } from '@react-navigation/native' import { useNavigation } from '@react-navigation/native'
import { searchFetch } from '@utils/fetches/searchFetch' import { searchFetch } from '@utils/fetches/searchFetch'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
@ -262,8 +262,8 @@ const ScreenSharedSearch: React.FC = () => {
<View <View
style={[styles.searchField, { borderBottomColor: theme.secondary }]} style={[styles.searchField, { borderBottomColor: theme.secondary }]}
> >
<Feather <Icon
name='search' name='Search'
color={theme.primary} color={theme.primary}
size={StyleConstants.Font.Size.M} size={StyleConstants.Font.Size.M}
style={styles.searchIcon} style={styles.searchIcon}

View File

@ -15,7 +15,7 @@ const ScreenSharedToot: React.FC<Props> = ({
params: { toot } params: { toot }
} }
}) => { }) => {
return <Timeline page='Toot' toot={toot} disableRefresh disableInfinity /> return <Timeline page='Toot' toot={toot.id} disableRefresh disableInfinity />
} }
export default ScreenSharedToot export default ScreenSharedToot

View File

@ -194,10 +194,16 @@ export const timelineFetch = async ({
res = await client({ res = await client({
method: 'get', method: 'get',
instance: 'local', instance: 'local',
url: `statuses/${toot!.id}/context` url: `statuses/${toot}`
})
const theToot = res.body
res = await client({
method: 'get',
instance: 'local',
url: `statuses/${toot}/context`
}) })
return Promise.resolve({ return Promise.resolve({
toots: [...res.body.ancestors, toot, ...res.body.descendants], toots: [...res.body.ancestors, theToot, ...res.body.descendants],
pointer: res.body.ancestors.length pointer: res.body.ancestors.length
}) })
default: default:

View File

@ -12,11 +12,11 @@
"baseUrl": "./", "baseUrl": "./",
"paths": { "paths": {
// "@assets/*": ["./assets/*"], // "@assets/*": ["./assets/*"],
"@root/*": ["./src/*"],
"@api/*": ["./src/api/*"], "@api/*": ["./src/api/*"],
"@components/*": ["./src/components/*"], "@components/*": ["./src/components/*"],
"@screens/*": ["./src/screens/*"], "@screens/*": ["./src/screens/*"],
"@utils/*": ["./src/utils/*"] "@utils/*": ["./src/utils/*"],
"@root/*": ["./src/*"],
} }
}, },
"exclude": ["node_modules"] "exclude": ["node_modules"]

View File

@ -8119,6 +8119,11 @@ react-native-expo-image-cache@^4.1.0:
crypto-js "^3.1.9-1" crypto-js "^3.1.9-1"
lodash "^4.17.4" lodash "^4.17.4"
react-native-feather@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/react-native-feather/-/react-native-feather-1.0.2.tgz#9b02a32f313088084da5ebb0130659fa582edf43"
integrity sha512-SxMCMyGQeDtZtl2mhssoFTsfFKh/eH6S11+720BPGYYXz1iaYwQ4G/xqFxWOvQOQK2qtOTvkFOBlabbKwBMuhQ==
react-native-gesture-handler@~1.8.0: react-native-gesture-handler@~1.8.0:
version "1.8.0" version "1.8.0"
resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-1.8.0.tgz#18f61f51da50320f938957b0ee79bc58f47449dc" resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-1.8.0.tgz#18f61f51da50320f938957b0ee79bc58f47449dc"