mirror of
https://github.com/tooot-app/app
synced 2025-02-11 09:20:46 +01:00
A lot of updates
This commit is contained in:
parent
0e3528d2cd
commit
9bfee02484
@ -60,7 +60,7 @@ export const Index: React.FC = () => {
|
||||
name = 'bell'
|
||||
break
|
||||
case 'Screen-Me':
|
||||
name = focused ? 'smile' : 'meh'
|
||||
name = focused ? 'meh' : 'smile'
|
||||
break
|
||||
default:
|
||||
name = 'alert-octagon'
|
||||
|
@ -77,7 +77,7 @@ const BottomSheet: React.FC<Props> = ({ children, visible, handleDismiss }) => {
|
||||
{
|
||||
top,
|
||||
backgroundColor: theme.background,
|
||||
paddingBottom: insets.bottom
|
||||
paddingBottom: insets.bottom || StyleConstants.Spacing.L
|
||||
}
|
||||
]}
|
||||
>
|
||||
@ -108,15 +108,16 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
handle: {
|
||||
alignSelf: 'center',
|
||||
width: StyleConstants.Spacing.Global.PagePadding * 8,
|
||||
height: StyleConstants.Spacing.Global.PagePadding / 2,
|
||||
width: StyleConstants.Spacing.S * 8,
|
||||
height: StyleConstants.Spacing.S / 2,
|
||||
borderRadius: 100,
|
||||
top: -StyleConstants.Spacing.M * 2
|
||||
},
|
||||
cancel: {
|
||||
padding: StyleConstants.Spacing.S,
|
||||
borderWidth: 1,
|
||||
borderRadius: 100
|
||||
borderRadius: 100,
|
||||
// marginBottom: StyleConstants.Spacing.L
|
||||
},
|
||||
text: {
|
||||
fontSize: StyleConstants.Font.Size.L,
|
||||
|
@ -6,16 +6,16 @@ import { useTheme } from 'src/utils/styles/ThemeManager'
|
||||
import { StyleConstants } from 'src/utils/styles/constants'
|
||||
|
||||
export interface Props {
|
||||
onPressFunction: () => void
|
||||
onPress: () => void
|
||||
icon: string
|
||||
text: string
|
||||
}
|
||||
|
||||
const BottomSheetRow: React.FC<Props> = ({ onPressFunction, icon, text }) => {
|
||||
const BottomSheetRow: React.FC<Props> = ({ onPress, icon, text }) => {
|
||||
const { theme } = useTheme()
|
||||
|
||||
return (
|
||||
<Pressable onPress={() => onPressFunction()} style={styles.pressable}>
|
||||
<Pressable onPress={onPress} style={styles.pressable}>
|
||||
<Feather
|
||||
name={icon}
|
||||
color={theme.primary}
|
||||
@ -28,6 +28,7 @@ const BottomSheetRow: React.FC<Props> = ({ onPressFunction, icon, text }) => {
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
pressable: {
|
||||
width: '100%',
|
||||
flexDirection: 'row',
|
||||
marginBottom: StyleConstants.Spacing.L
|
||||
},
|
||||
|
@ -17,11 +17,11 @@ const HeaderLeft: React.FC<Props> = ({ onPress, text, icon }) => {
|
||||
return (
|
||||
<Pressable onPress={onPress} style={styles.base}>
|
||||
{text ? (
|
||||
<Text style={[styles.text, { color: theme.link }]}>{text}</Text>
|
||||
<Text style={[styles.text, { color: theme.primary }]}>{text}</Text>
|
||||
) : (
|
||||
<Feather
|
||||
name={icon || 'chevron-left'}
|
||||
color={theme.link}
|
||||
color={theme.primary}
|
||||
size={StyleConstants.Font.Size.L}
|
||||
/>
|
||||
)}
|
||||
|
@ -28,11 +28,11 @@ const HeaderRight: React.FC<PropsText | PropsIcon> = ({
|
||||
|
||||
return (
|
||||
<Pressable onPress={onPress} style={styles.base}>
|
||||
{text && <Text style={[styles.text, { color: theme.link }]}>{text}</Text>}
|
||||
{text && <Text style={[styles.text, { color: theme.primary }]}>{text}</Text>}
|
||||
{icon && (
|
||||
<Feather
|
||||
name={icon}
|
||||
color={theme.link}
|
||||
color={theme.primary}
|
||||
size={StyleConstants.Font.Size.L}
|
||||
/>
|
||||
)}
|
||||
|
@ -1,10 +1,12 @@
|
||||
import React, { useCallback } from 'react'
|
||||
import { Text } from 'react-native'
|
||||
import HTMLView, { HTMLViewNode } from 'react-native-htmlview'
|
||||
import { StyleSheet, Text, View } from 'react-native'
|
||||
import HTMLView from 'react-native-htmlview'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
|
||||
import Emojis from 'src/components/Timelines/Timeline/Shared/Emojis'
|
||||
import { useTheme } from 'src/utils/styles/ThemeManager'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
import { StyleConstants } from 'src/utils/styles/constants'
|
||||
|
||||
// Prevent going to the same hashtag multiple times
|
||||
const renderNode = ({
|
||||
@ -75,6 +77,11 @@ const renderNode = ({
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Feather
|
||||
name='external-link'
|
||||
size={StyleConstants.Font.Size.M}
|
||||
color={theme.link}
|
||||
/>{' '}
|
||||
{showFullLink ? href : domain[1]}
|
||||
</Text>
|
||||
)
|
||||
@ -88,7 +95,7 @@ export interface Props {
|
||||
emojis?: Mastodon.Emoji[]
|
||||
mentions?: Mastodon.Mention[]
|
||||
showFullLink?: boolean
|
||||
linesTruncated?: number
|
||||
numberOfLines?: number
|
||||
}
|
||||
|
||||
const ParseContent: React.FC<Props> = ({
|
||||
@ -97,7 +104,7 @@ const ParseContent: React.FC<Props> = ({
|
||||
emojis,
|
||||
mentions,
|
||||
showFullLink = false,
|
||||
linesTruncated = 10
|
||||
numberOfLines = 10
|
||||
}) => {
|
||||
const navigation = useNavigation()
|
||||
const { theme } = useTheme()
|
||||
@ -117,7 +124,11 @@ const ParseContent: React.FC<Props> = ({
|
||||
[]
|
||||
)
|
||||
const rootComponent = useCallback(({ children }) => {
|
||||
return <Text numberOfLines={linesTruncated}>{children}</Text>
|
||||
return (
|
||||
<Text numberOfLines={numberOfLines} style={styles.root}>
|
||||
{children}
|
||||
</Text>
|
||||
)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
@ -130,4 +141,10 @@ const ParseContent: React.FC<Props> = ({
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
lineHeight: StyleConstants.Font.LineHeight.M
|
||||
}
|
||||
})
|
||||
|
||||
export default ParseContent
|
||||
|
@ -80,7 +80,7 @@ const Timeline: React.FC<Props> = ({
|
||||
},
|
||||
{ previous: true }
|
||||
),
|
||||
[disableRefresh]
|
||||
[disableRefresh, flattenData]
|
||||
)
|
||||
const flOnEndReach = useCallback(
|
||||
() =>
|
||||
@ -89,7 +89,7 @@ const Timeline: React.FC<Props> = ({
|
||||
direction: 'next',
|
||||
id: flattenData[flattenData.length - 1].id
|
||||
}),
|
||||
[disableRefresh]
|
||||
[disableRefresh, flattenData]
|
||||
)
|
||||
|
||||
let content
|
||||
@ -110,7 +110,7 @@ const Timeline: React.FC<Props> = ({
|
||||
scrollEnabled={scrollEnabled} // For timeline in Account view
|
||||
ItemSeparatorComponent={flItemSeparatorComponent}
|
||||
refreshing={!disableRefresh && isLoading}
|
||||
onEndReachedThreshold={!disableRefresh ? 0.5 : null}
|
||||
onEndReachedThreshold={!disableRefresh ? 1 : null}
|
||||
// require getItemLayout
|
||||
// {...(flattenPointer[0] && { initialScrollIndex: flattenPointer[0] })}
|
||||
/>
|
||||
|
@ -106,7 +106,8 @@ const styles = StyleSheet.create({
|
||||
statusView: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
padding: StyleConstants.Spacing.Global.PagePadding
|
||||
padding: StyleConstants.Spacing.Global.PagePadding,
|
||||
paddingBottom: StyleConstants.Spacing.M
|
||||
},
|
||||
status: {
|
||||
flex: 1,
|
||||
|
@ -1,13 +1,5 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import {
|
||||
ActionSheetIOS,
|
||||
Clipboard,
|
||||
Modal,
|
||||
Pressable,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View
|
||||
} from 'react-native'
|
||||
import { ActionSheetIOS, Pressable, StyleSheet, Text, View } from 'react-native'
|
||||
import { useMutation, useQueryCache } from 'react-query'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
|
||||
@ -17,6 +9,8 @@ import { useTheme } from 'src/utils/styles/ThemeManager'
|
||||
import { toast } from 'src/components/toast'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { StyleConstants } from 'src/utils/styles/constants'
|
||||
import BottomSheet from 'src/components/BottomSheet'
|
||||
import BottomSheetRow from 'src/components/BottomSheet/Row'
|
||||
|
||||
const fireMutation = async ({
|
||||
id,
|
||||
@ -275,96 +269,85 @@ const ActionsStatus: React.FC<Props> = ({ queryKey, status }) => {
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Modal
|
||||
animationType='fade'
|
||||
presentationStyle='overFullScreen'
|
||||
transparent
|
||||
<BottomSheet
|
||||
visible={bottomSheetVisible}
|
||||
handleDismiss={() => setBottomSheetVisible(false)}
|
||||
>
|
||||
<Pressable
|
||||
style={styles.modalBackground}
|
||||
onPress={() => setBottomSheetVisible(false)}
|
||||
>
|
||||
<View style={styles.modalSheet}>
|
||||
<Pressable
|
||||
onPress={() =>
|
||||
ActionSheetIOS.showShareActionSheetWithOptions(
|
||||
{
|
||||
url: status.uri,
|
||||
excludedActivityTypes: [
|
||||
'com.apple.UIKit.activity.Mail',
|
||||
'com.apple.UIKit.activity.Print',
|
||||
'com.apple.UIKit.activity.SaveToCameraRoll',
|
||||
'com.apple.UIKit.activity.OpenInIBooks'
|
||||
]
|
||||
},
|
||||
() => {},
|
||||
() => {
|
||||
setBottomSheetVisible(false)
|
||||
toast({ type: 'success', content: '分享成功' })
|
||||
}
|
||||
)
|
||||
<BottomSheetRow
|
||||
onPress={() => {
|
||||
ActionSheetIOS.showShareActionSheetWithOptions(
|
||||
{
|
||||
url: status.uri,
|
||||
excludedActivityTypes: [
|
||||
'com.apple.UIKit.activity.Mail',
|
||||
'com.apple.UIKit.activity.Print',
|
||||
'com.apple.UIKit.activity.SaveToCameraRoll',
|
||||
'com.apple.UIKit.activity.OpenInIBooks'
|
||||
]
|
||||
},
|
||||
() => {},
|
||||
() => {
|
||||
setBottomSheetVisible(false)
|
||||
toast({ type: 'success', content: '分享成功' })
|
||||
}
|
||||
>
|
||||
<Text>分享</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
Clipboard.setString(status.uri)
|
||||
setBottomSheetVisible(false)
|
||||
toast({ type: 'success', content: '链接复制成功' })
|
||||
}}
|
||||
>
|
||||
<Text>复制链接</Text>
|
||||
</Pressable>
|
||||
{status.account.id === localAccountId && (
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
setBottomSheetVisible(false)
|
||||
mutateAction({
|
||||
id: status.id,
|
||||
type: 'delete',
|
||||
stateKey: 'id'
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Text>删除</Text>
|
||||
</Pressable>
|
||||
)}
|
||||
<Text>(删除并重发)</Text>
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
setBottomSheetVisible(false)
|
||||
mutateAction({
|
||||
id: status.id,
|
||||
type: 'mute',
|
||||
stateKey: 'muted',
|
||||
prevState: status.muted
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Text>{status.muted ? '取消静音' : '静音'}</Text>
|
||||
</Pressable>
|
||||
{/* Also note that reblogs cannot be pinned. */}
|
||||
{status.account.id === localAccountId && (
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
setBottomSheetVisible(false)
|
||||
mutateAction({
|
||||
id: status.id,
|
||||
type: 'pin',
|
||||
stateKey: 'pinned',
|
||||
prevState: status.pinned
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Text>{status.pinned ? '取消置顶' : '置顶'}</Text>
|
||||
</Pressable>
|
||||
)}
|
||||
<Text>静音用户,屏蔽用户,屏蔽域名,举报用户</Text>
|
||||
</View>
|
||||
</Pressable>
|
||||
</Modal>
|
||||
)
|
||||
}}
|
||||
icon='share'
|
||||
text={'分享嘟嘟'}
|
||||
/>
|
||||
{status.account.id === localAccountId && (
|
||||
<BottomSheetRow
|
||||
onPress={() => {
|
||||
setBottomSheetVisible(false)
|
||||
mutateAction({
|
||||
id: status.id,
|
||||
type: 'delete',
|
||||
stateKey: 'id'
|
||||
})
|
||||
}}
|
||||
icon='trash'
|
||||
text='删除嘟嘟'
|
||||
/>
|
||||
)}
|
||||
{status.account.id === localAccountId && (
|
||||
<BottomSheetRow
|
||||
onPress={() => {
|
||||
console.warn('功能未开发')
|
||||
}}
|
||||
icon='trash'
|
||||
text='删除并重发'
|
||||
/>
|
||||
)}
|
||||
<BottomSheetRow
|
||||
onPress={() => {
|
||||
setBottomSheetVisible(false)
|
||||
mutateAction({
|
||||
id: status.id,
|
||||
type: 'mute',
|
||||
stateKey: 'muted',
|
||||
prevState: status.muted
|
||||
})
|
||||
}}
|
||||
icon='volume-x'
|
||||
text={status.muted ? '取消静音' : '静音'}
|
||||
/>
|
||||
{/* Also note that reblogs cannot be pinned. */}
|
||||
{status.account.id === localAccountId && (
|
||||
<BottomSheetRow
|
||||
onPress={() => {
|
||||
setBottomSheetVisible(false)
|
||||
mutateAction({
|
||||
id: status.id,
|
||||
type: 'pin',
|
||||
stateKey: 'pinned',
|
||||
prevState: status.pinned
|
||||
})
|
||||
}}
|
||||
icon='anchor'
|
||||
text={status.pinned ? '取消置顶' : '置顶'}
|
||||
/>
|
||||
)}
|
||||
</BottomSheet>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -1,12 +1,15 @@
|
||||
import React, { useCallback } from 'react'
|
||||
import { Image, Pressable, StyleSheet, Text, View } from 'react-native'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StyleConstants } from 'src/utils/styles/constants'
|
||||
import { useTheme } from 'src/utils/styles/ThemeManager'
|
||||
|
||||
export interface Props {
|
||||
card: Mastodon.Card
|
||||
}
|
||||
|
||||
const Card: React.FC<Props> = ({ card }) => {
|
||||
const { theme } = useTheme()
|
||||
const navigation = useNavigation()
|
||||
const onPress = useCallback(() => {
|
||||
navigation.navigate('Screen-Shared-Webview', {
|
||||
@ -15,20 +18,35 @@ const Card: React.FC<Props> = ({ card }) => {
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Pressable style={styles.card} onPress={onPress}>
|
||||
<Pressable
|
||||
style={[styles.card, { borderColor: theme.border }]}
|
||||
onPress={onPress}
|
||||
>
|
||||
{card.image && (
|
||||
<View style={styles.left}>
|
||||
<Image source={{ uri: card.image }} style={styles.image} />
|
||||
</View>
|
||||
)}
|
||||
<View style={styles.right}>
|
||||
<Text numberOfLines={1}>{card.title}</Text>
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={[styles.rightTitle, { color: theme.primary }]}
|
||||
>
|
||||
{card.title}
|
||||
</Text>
|
||||
{card.description ? (
|
||||
<Text numberOfLines={2}>{card.description}</Text>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={[styles.rightDescription, { color: theme.primary }]}
|
||||
>
|
||||
{card.description}
|
||||
</Text>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<Text numberOfLines={1}>{card.url}</Text>
|
||||
<Text numberOfLines={1} style={{ color: theme.secondary }}>
|
||||
{card.url}
|
||||
</Text>
|
||||
</View>
|
||||
</Pressable>
|
||||
)
|
||||
@ -38,18 +56,30 @@ const styles = StyleSheet.create({
|
||||
card: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
height: 70,
|
||||
marginTop: 12
|
||||
height: StyleConstants.Avatar.L,
|
||||
marginTop: StyleConstants.Spacing.M,
|
||||
borderWidth: 0.5,
|
||||
borderRadius: 6
|
||||
},
|
||||
left: {
|
||||
width: 70
|
||||
width: StyleConstants.Avatar.L
|
||||
},
|
||||
image: {
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
height: '100%',
|
||||
borderTopLeftRadius: 6,
|
||||
borderBottomLeftRadius: 6
|
||||
},
|
||||
right: {
|
||||
flex: 1
|
||||
flex: 1,
|
||||
padding: StyleConstants.Spacing.S
|
||||
},
|
||||
rightTitle: {
|
||||
marginBottom: StyleConstants.Spacing.XS,
|
||||
fontWeight: StyleConstants.Font.Weight.Bold
|
||||
},
|
||||
rightDescription: {
|
||||
marginBottom: StyleConstants.Spacing.XS
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import { Image, StyleSheet, Text } from 'react-native'
|
||||
import { Image, StyleSheet, Text, View } from 'react-native'
|
||||
import { useTheme } from 'src/utils/styles/ThemeManager'
|
||||
import { StyleConstants } from 'src/utils/styles/constants'
|
||||
|
||||
@ -22,19 +22,20 @@ const Emojis: React.FC<Props> = ({
|
||||
const styles = StyleSheet.create({
|
||||
text: {
|
||||
fontSize: size,
|
||||
lineHeight: size + 2,
|
||||
color: theme.primary,
|
||||
...(fontBold && { fontWeight: StyleConstants.Font.Weight.Bold })
|
||||
},
|
||||
image: {
|
||||
width: size,
|
||||
height: size
|
||||
height: size,
|
||||
paddingTop: 1,
|
||||
marginBottom: -1
|
||||
}
|
||||
})
|
||||
const hasEmojis = content.match(regexEmoji)
|
||||
|
||||
return hasEmojis ? (
|
||||
<>
|
||||
return (
|
||||
<Text>
|
||||
{content.split(regexEmoji).map((str, i) => {
|
||||
if (str.match(regexEmoji)) {
|
||||
const emojiShortcode = str.split(regexEmoji)[1]
|
||||
@ -46,23 +47,26 @@ const Emojis: React.FC<Props> = ({
|
||||
{emojiShortcode}
|
||||
</Text>
|
||||
) : (
|
||||
<Image
|
||||
key={i}
|
||||
source={{ uri: emojis[emojiIndex].url }}
|
||||
style={styles.image}
|
||||
/>
|
||||
<View key={i} style={styles.image}>
|
||||
<Image
|
||||
key={i}
|
||||
resizeMode='contain'
|
||||
source={{ uri: emojis[emojiIndex].url }}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
return str ? (
|
||||
<Text key={i} style={styles.text}>
|
||||
{str}
|
||||
</Text>
|
||||
) : (
|
||||
undefined
|
||||
)
|
||||
}
|
||||
})}
|
||||
</>
|
||||
) : (
|
||||
<Text style={styles.text}>{content}</Text>
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -166,7 +166,7 @@ const HeaderDefault: React.FC<Props> = ({
|
||||
<View>
|
||||
<View style={styles.nameAndAction}>
|
||||
<View style={styles.name}>
|
||||
{emojis ? (
|
||||
{emojis?.length ? (
|
||||
<Emojis
|
||||
content={name}
|
||||
emojis={emojis}
|
||||
@ -174,7 +174,10 @@ const HeaderDefault: React.FC<Props> = ({
|
||||
fontBold={true}
|
||||
/>
|
||||
) : (
|
||||
<Text numberOfLines={1} style={{ color: theme.primary }}>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={[styles.nameWithoutEmoji, { color: theme.primary }]}
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
)}
|
||||
@ -193,6 +196,7 @@ const HeaderDefault: React.FC<Props> = ({
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View style={styles.meta}>
|
||||
<View>
|
||||
<Text style={[styles.created_at, { color: theme.secondary }]}>
|
||||
@ -224,7 +228,7 @@ const HeaderDefault: React.FC<Props> = ({
|
||||
>
|
||||
{accountId !== localAccountId && (
|
||||
<BottomSheetRow
|
||||
onPressFunction={() => {
|
||||
onPress={() => {
|
||||
setModalVisible(false)
|
||||
mutateAction({
|
||||
id: accountId,
|
||||
@ -238,7 +242,7 @@ const HeaderDefault: React.FC<Props> = ({
|
||||
)}
|
||||
{accountId !== localAccountId && (
|
||||
<BottomSheetRow
|
||||
onPressFunction={() => {
|
||||
onPress={() => {
|
||||
setModalVisible(false)
|
||||
mutateAction({
|
||||
id: accountId,
|
||||
@ -252,7 +256,7 @@ const HeaderDefault: React.FC<Props> = ({
|
||||
)}
|
||||
{domain !== localDomain && (
|
||||
<BottomSheetRow
|
||||
onPressFunction={() => {
|
||||
onPress={() => {
|
||||
setModalVisible(false)
|
||||
mutateAction({
|
||||
id: domain,
|
||||
@ -265,7 +269,7 @@ const HeaderDefault: React.FC<Props> = ({
|
||||
)}
|
||||
{accountId !== localAccountId && (
|
||||
<BottomSheetRow
|
||||
onPressFunction={() => {
|
||||
onPress={() => {
|
||||
setModalVisible(false)
|
||||
mutateAction({
|
||||
id: accountId,
|
||||
@ -288,17 +292,20 @@ const styles = StyleSheet.create({
|
||||
justifyContent: 'space-between'
|
||||
},
|
||||
name: {
|
||||
flexBasis: '80%',
|
||||
flexBasis: '90%',
|
||||
flexDirection: 'row'
|
||||
},
|
||||
nameWithoutEmoji: {
|
||||
fontSize: StyleConstants.Font.Size.M,
|
||||
fontWeight: StyleConstants.Font.Weight.Bold
|
||||
},
|
||||
action: {
|
||||
flexBasis: '20%',
|
||||
alignItems: 'center'
|
||||
alignItems: 'flex-end'
|
||||
},
|
||||
account: {
|
||||
flexShrink: 1,
|
||||
marginLeft: StyleConstants.Spacing.XS,
|
||||
lineHeight: StyleConstants.Font.Size.M + 2
|
||||
marginLeft: StyleConstants.Spacing.XS
|
||||
// lineHeight: StyleConstants.Font.LineHeight.M
|
||||
},
|
||||
meta: {
|
||||
flexDirection: 'row',
|
||||
|
@ -17,6 +17,10 @@ export default {
|
||||
dark: '深色模式',
|
||||
cancel: '$t(common:buttons.cancel)'
|
||||
}
|
||||
}
|
||||
},
|
||||
copyrights: {
|
||||
heading: '版权信息'
|
||||
},
|
||||
version: '版本 v{{version}}'
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ActionSheetIOS } from 'react-native'
|
||||
import { ActionSheetIOS, StyleSheet, Text } from 'react-native'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
import { MenuContainer, MenuItem } from 'src/components/Menu'
|
||||
@ -10,81 +10,101 @@ import {
|
||||
getSettingsLanguage,
|
||||
getSettingsTheme
|
||||
} from 'src/utils/slices/settingsSlice'
|
||||
import { StyleConstants } from 'src/utils/styles/constants'
|
||||
import { useTheme } from 'src/utils/styles/ThemeManager'
|
||||
|
||||
const ScreenMeSettings: React.FC = () => {
|
||||
const { t, i18n } = useTranslation('meSettings')
|
||||
const { setTheme } = useTheme()
|
||||
const { setTheme, theme } = useTheme()
|
||||
const settingsLanguage = useSelector(getSettingsLanguage)
|
||||
const settingsTheme = useSelector(getSettingsTheme)
|
||||
const dispatch = useDispatch()
|
||||
|
||||
return (
|
||||
<MenuContainer marginTop={true}>
|
||||
<MenuItem
|
||||
title={t('content.language.heading')}
|
||||
content={t(`content.language.options.${settingsLanguage}`)}
|
||||
iconBack='chevron-right'
|
||||
onPress={() =>
|
||||
ActionSheetIOS.showActionSheetWithOptions(
|
||||
{
|
||||
options: [
|
||||
t('content.language.options.zh'),
|
||||
t('content.language.options.en'),
|
||||
t('content.language.options.cancel')
|
||||
],
|
||||
cancelButtonIndex: 2
|
||||
},
|
||||
buttonIndex => {
|
||||
switch (buttonIndex) {
|
||||
case 0:
|
||||
dispatch(changeLanguage('zh'))
|
||||
i18n.changeLanguage('zh')
|
||||
break
|
||||
case 1:
|
||||
dispatch(changeLanguage('en'))
|
||||
i18n.changeLanguage('en')
|
||||
break
|
||||
<>
|
||||
<MenuContainer marginTop={true}>
|
||||
<MenuItem
|
||||
title={t('content.language.heading')}
|
||||
content={t(`content.language.options.${settingsLanguage}`)}
|
||||
iconBack='chevron-right'
|
||||
onPress={() =>
|
||||
ActionSheetIOS.showActionSheetWithOptions(
|
||||
{
|
||||
options: [
|
||||
t('content.language.options.zh'),
|
||||
t('content.language.options.en'),
|
||||
t('content.language.options.cancel')
|
||||
],
|
||||
cancelButtonIndex: 2
|
||||
},
|
||||
buttonIndex => {
|
||||
switch (buttonIndex) {
|
||||
case 0:
|
||||
dispatch(changeLanguage('zh'))
|
||||
i18n.changeLanguage('zh')
|
||||
break
|
||||
case 1:
|
||||
dispatch(changeLanguage('en'))
|
||||
i18n.changeLanguage('en')
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
/>
|
||||
<MenuItem
|
||||
title={t('content.theme.heading')}
|
||||
content={t(`content.theme.options.${settingsTheme}`)}
|
||||
iconBack='chevron-right'
|
||||
onPress={() =>
|
||||
ActionSheetIOS.showActionSheetWithOptions(
|
||||
{
|
||||
options: [
|
||||
t('content.theme.options.auto'),
|
||||
t('content.theme.options.light'),
|
||||
t('content.theme.options.dark'),
|
||||
t('content.theme.options.cancel')
|
||||
],
|
||||
cancelButtonIndex: 3
|
||||
},
|
||||
buttonIndex => {
|
||||
switch (buttonIndex) {
|
||||
case 0:
|
||||
dispatch(changeTheme('auto'))
|
||||
break
|
||||
case 1:
|
||||
dispatch(changeTheme('light'))
|
||||
setTheme('light')
|
||||
break
|
||||
case 2:
|
||||
dispatch(changeTheme('dark'))
|
||||
setTheme('dark')
|
||||
break
|
||||
)
|
||||
}
|
||||
/>
|
||||
<MenuItem
|
||||
title={t('content.theme.heading')}
|
||||
content={t(`content.theme.options.${settingsTheme}`)}
|
||||
iconBack='chevron-right'
|
||||
onPress={() =>
|
||||
ActionSheetIOS.showActionSheetWithOptions(
|
||||
{
|
||||
options: [
|
||||
t('content.theme.options.auto'),
|
||||
t('content.theme.options.light'),
|
||||
t('content.theme.options.dark'),
|
||||
t('content.theme.options.cancel')
|
||||
],
|
||||
cancelButtonIndex: 3
|
||||
},
|
||||
buttonIndex => {
|
||||
switch (buttonIndex) {
|
||||
case 0:
|
||||
dispatch(changeTheme('auto'))
|
||||
break
|
||||
case 1:
|
||||
dispatch(changeTheme('light'))
|
||||
setTheme('light')
|
||||
break
|
||||
case 2:
|
||||
dispatch(changeTheme('dark'))
|
||||
setTheme('dark')
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
/>
|
||||
</MenuContainer>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</MenuContainer>
|
||||
<MenuContainer>
|
||||
<MenuItem
|
||||
title={t('content.copyrights.heading')}
|
||||
iconBack='chevron-right'
|
||||
></MenuItem>
|
||||
<Text style={[styles.version, { color: theme.secondary }]}>
|
||||
{t('content.version', { version: '1.0.0' })}
|
||||
</Text>
|
||||
</MenuContainer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
version: {
|
||||
textAlign: 'center',
|
||||
fontSize: StyleConstants.Font.Size.S,
|
||||
marginTop: StyleConstants.Spacing.M
|
||||
}
|
||||
})
|
||||
|
||||
export default ScreenMeSettings
|
||||
|
@ -7,6 +7,7 @@ import ParseContent from 'src/components/ParseContent'
|
||||
import { useTheme } from 'src/utils/styles/ThemeManager'
|
||||
import { StyleConstants } from 'src/utils/styles/constants'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Emojis from 'src/components/Timelines/Timeline/Shared/Emojis'
|
||||
|
||||
export interface Props {
|
||||
account: Mastodon.Account | undefined
|
||||
@ -17,7 +18,6 @@ const AccountInformation: React.FC<Props> = ({ account }) => {
|
||||
const { theme } = useTheme()
|
||||
const [avatarLoaded, setAvatarLoaded] = useState(false)
|
||||
|
||||
// add emoji support
|
||||
return (
|
||||
<View style={styles.information}>
|
||||
{/* <Text>Moved or not: {account.moved}</Text> */}
|
||||
@ -29,27 +29,69 @@ const AccountInformation: React.FC<Props> = ({ account }) => {
|
||||
/>
|
||||
</ShimmerPlaceholder>
|
||||
|
||||
<Text style={[styles.display_name, { color: theme.primary }]}>
|
||||
{account?.display_name || account?.username}
|
||||
{account?.bot && (
|
||||
<Feather name='hard-drive' style={styles.display_name} />
|
||||
<View style={styles.display_name}>
|
||||
{account?.emojis ? (
|
||||
<Emojis
|
||||
content={account?.display_name || account?.username}
|
||||
emojis={account.emojis}
|
||||
size={StyleConstants.Font.Size.L}
|
||||
fontBold={true}
|
||||
/>
|
||||
) : (
|
||||
<Text
|
||||
style={{
|
||||
color: theme.primary,
|
||||
fontSize: StyleConstants.Font.Size.L,
|
||||
fontWeight: StyleConstants.Font.Weight.Bold
|
||||
}}
|
||||
>
|
||||
{account?.display_name || account?.username}
|
||||
</Text>
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<Text style={[styles.account, { color: theme.secondary }]}>
|
||||
@{account?.acct}
|
||||
{account?.locked && <Feather name='lock' />}
|
||||
</Text>
|
||||
<View style={styles.account}>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.secondary,
|
||||
fontSize: StyleConstants.Font.Size.M
|
||||
}}
|
||||
selectable
|
||||
>
|
||||
@{account?.acct}
|
||||
</Text>
|
||||
{account?.locked && (
|
||||
<Feather
|
||||
name='lock'
|
||||
style={styles.account_types}
|
||||
color={theme.secondary}
|
||||
/>
|
||||
)}
|
||||
{account?.bot && (
|
||||
<Feather
|
||||
name='hard-drive'
|
||||
style={styles.account_types}
|
||||
color={theme.secondary}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{account?.fields && (
|
||||
<View style={styles.fields}>
|
||||
{account?.fields && account.fields.length > 0 && (
|
||||
<View style={[styles.fields, { borderTopColor: theme.border }]}>
|
||||
{account.fields.map((field, index) => (
|
||||
<View key={index} style={{ flex: 1, flexDirection: 'row' }}>
|
||||
<Text
|
||||
<View
|
||||
key={index}
|
||||
style={[styles.field, { borderBottomColor: theme.border }]}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: '30%',
|
||||
alignSelf: 'center',
|
||||
color: theme.primary
|
||||
flexBasis: '30%',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: theme.border,
|
||||
paddingLeft: StyleConstants.Spacing.S,
|
||||
paddingRight: StyleConstants.Spacing.S
|
||||
}}
|
||||
>
|
||||
<ParseContent
|
||||
@ -57,17 +99,31 @@ const AccountInformation: React.FC<Props> = ({ account }) => {
|
||||
size={StyleConstants.Font.Size.M}
|
||||
emojis={account.emojis}
|
||||
showFullLink
|
||||
/>{' '}
|
||||
{field.verified_at && <Feather name='check-circle' />}
|
||||
</Text>
|
||||
<Text style={{ width: '70%', color: theme.primary }}>
|
||||
/>
|
||||
{field.verified_at && (
|
||||
<Feather
|
||||
name='check-circle'
|
||||
size={StyleConstants.Font.Size.M}
|
||||
color={theme.primary}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
flexBasis: '70%',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingLeft: StyleConstants.Spacing.S,
|
||||
paddingRight: StyleConstants.Spacing.S
|
||||
}}
|
||||
>
|
||||
<ParseContent
|
||||
content={field.value}
|
||||
size={StyleConstants.Font.Size.M}
|
||||
emojis={account.emojis}
|
||||
showFullLink
|
||||
/>
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
@ -83,41 +139,46 @@ const AccountInformation: React.FC<Props> = ({ account }) => {
|
||||
</View>
|
||||
)}
|
||||
|
||||
{account?.created_at && (
|
||||
<View style={styles.created_at}>
|
||||
<Feather name='calendar' size={StyleConstants.Font.Size.M + 2} />
|
||||
<Text
|
||||
style={{
|
||||
color: theme.primary,
|
||||
fontSize: StyleConstants.Font.Size.M,
|
||||
marginLeft: StyleConstants.Spacing.XS
|
||||
}}
|
||||
>
|
||||
{t('content.created_at', {
|
||||
date: new Date(account.created_at).toLocaleDateString('zh-CN', {
|
||||
<View style={styles.created_at}>
|
||||
<Feather
|
||||
name='calendar'
|
||||
size={StyleConstants.Font.Size.M + 2}
|
||||
color={theme.secondary}
|
||||
style={styles.created_at_icon}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.secondary,
|
||||
fontSize: StyleConstants.Font.Size.M
|
||||
}}
|
||||
>
|
||||
{t(
|
||||
'content.created_at',
|
||||
{
|
||||
date: new Date(account?.created_at!).toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
})
|
||||
})}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
} || null
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.summary}>
|
||||
<Text style={{ color: theme.primary }}>
|
||||
{t('content.summary.statuses_count', {
|
||||
count: account?.statuses_count
|
||||
count: account?.statuses_count || 0
|
||||
})}
|
||||
</Text>
|
||||
<Text style={{ color: theme.primary }}>
|
||||
<Text style={{ color: theme.primary, textAlign: 'center' }}>
|
||||
{t('content.summary.followers_count', {
|
||||
count: account?.followers_count
|
||||
count: account?.followers_count || 0
|
||||
})}
|
||||
</Text>
|
||||
<Text style={{ color: theme.primary }}>
|
||||
<Text style={{ color: theme.primary, textAlign: 'right' }}>
|
||||
{t('content.summary.following_count', {
|
||||
count: account?.following_count
|
||||
count: account?.following_count || 0
|
||||
})}
|
||||
</Text>
|
||||
</View>
|
||||
@ -136,26 +197,40 @@ const styles = StyleSheet.create({
|
||||
borderRadius: 8
|
||||
},
|
||||
display_name: {
|
||||
fontSize: StyleConstants.Font.Size.L,
|
||||
fontWeight: StyleConstants.Font.Weight.Bold,
|
||||
flexDirection: 'row',
|
||||
marginTop: StyleConstants.Spacing.M,
|
||||
marginBottom: StyleConstants.Spacing.XS
|
||||
},
|
||||
account: {
|
||||
fontSize: StyleConstants.Font.Size.M,
|
||||
marginBottom: StyleConstants.Spacing.S
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: StyleConstants.Spacing.L
|
||||
},
|
||||
account_types: { marginLeft: StyleConstants.Spacing.S },
|
||||
fields: {
|
||||
marginBottom: StyleConstants.Spacing.S
|
||||
borderTopWidth: 0.5,
|
||||
marginBottom: StyleConstants.Spacing.M
|
||||
},
|
||||
field: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
borderBottomWidth: 0.5,
|
||||
paddingTop: StyleConstants.Spacing.S,
|
||||
paddingBottom: StyleConstants.Spacing.S
|
||||
},
|
||||
note: {
|
||||
marginBottom: StyleConstants.Spacing.M
|
||||
marginBottom: StyleConstants.Spacing.L
|
||||
},
|
||||
created_at: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: StyleConstants.Spacing.M
|
||||
},
|
||||
created_at_icon: {
|
||||
marginRight: StyleConstants.Spacing.XS
|
||||
},
|
||||
summary: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between'
|
||||
}
|
||||
|
@ -1,8 +1,11 @@
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import React, { useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ActionSheetIOS } from 'react-native'
|
||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||
import { WebView } from 'react-native-webview'
|
||||
import BottomSheet from 'src/components/BottomSheet'
|
||||
import BottomSheetRow from 'src/components/BottomSheet/Row'
|
||||
|
||||
import { HeaderLeft, HeaderRight } from 'src/components/Header'
|
||||
|
||||
@ -24,6 +27,7 @@ const ScreenSharedWebview: React.FC<Props> = ({
|
||||
const navigation = useNavigation()
|
||||
const { t } = useTranslation('sharedWebview')
|
||||
const [title, setTitle] = useState<string>(t('heading.loading'))
|
||||
const [bottomSheet, showBottomSheet] = useState(false)
|
||||
const webview = useRef<WebView>(null)
|
||||
|
||||
return (
|
||||
@ -40,19 +44,56 @@ const ScreenSharedWebview: React.FC<Props> = ({
|
||||
),
|
||||
headerRight: () => (
|
||||
<HeaderRight
|
||||
icon='refresh-cw'
|
||||
onPress={() => webview.current?.reload()}
|
||||
icon='more-horizontal'
|
||||
onPress={() => showBottomSheet(true)}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
>
|
||||
{() => (
|
||||
<WebView
|
||||
ref={webview}
|
||||
source={{ uri }}
|
||||
onLoad={({ nativeEvent }) => setTitle(nativeEvent.title)}
|
||||
onError={() => setTitle(t('heading.error'))}
|
||||
/>
|
||||
<>
|
||||
<WebView
|
||||
ref={webview}
|
||||
source={{ uri }}
|
||||
decelerationRate='normal'
|
||||
onLoad={({ nativeEvent }) => setTitle(nativeEvent.title)}
|
||||
onError={() => setTitle(t('heading.error'))}
|
||||
/>
|
||||
<BottomSheet
|
||||
visible={bottomSheet}
|
||||
handleDismiss={() => showBottomSheet(false)}
|
||||
>
|
||||
<BottomSheetRow
|
||||
onPress={() => {
|
||||
ActionSheetIOS.showShareActionSheetWithOptions(
|
||||
{
|
||||
url: uri,
|
||||
excludedActivityTypes: [
|
||||
'com.apple.UIKit.activity.Mail',
|
||||
'com.apple.UIKit.activity.Print',
|
||||
'com.apple.UIKit.activity.SaveToCameraRoll',
|
||||
'com.apple.UIKit.activity.OpenInIBooks'
|
||||
]
|
||||
},
|
||||
() => {},
|
||||
() => {
|
||||
showBottomSheet(false)
|
||||
}
|
||||
)
|
||||
}}
|
||||
icon='share'
|
||||
text={'分享链接'}
|
||||
/>
|
||||
<BottomSheetRow
|
||||
onPress={() => {
|
||||
showBottomSheet(false)
|
||||
webview.current?.reload()
|
||||
}}
|
||||
icon='refresh-cw'
|
||||
text='刷新'
|
||||
/>
|
||||
</BottomSheet>
|
||||
</>
|
||||
)}
|
||||
</Stack.Screen>
|
||||
</Stack.Navigator>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { createContext, useContext, useEffect, useState } from 'react'
|
||||
import { Appearance } from 'react-native-appearance'
|
||||
import { useColorScheme } from 'react-native-appearance'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { ColorDefinitions, getTheme } from 'src/utils/styles/themes'
|
||||
import { getSettingsTheme } from '../slices/settingsSlice'
|
||||
@ -19,11 +19,10 @@ export const ManageThemeContext = createContext<ContextType>({
|
||||
export const useTheme = () => useContext(ManageThemeContext)
|
||||
|
||||
const ThemeManager: React.FC = ({ children }) => {
|
||||
const osTheme = useColorScheme()
|
||||
const userTheme = useSelector(getSettingsTheme)
|
||||
const currentMode =
|
||||
userTheme === 'auto'
|
||||
? (Appearance.getColorScheme() as 'light' | 'dark')
|
||||
: userTheme
|
||||
userTheme === 'auto' ? (osTheme as 'light' | 'dark') : userTheme
|
||||
|
||||
const [mode, setMode] = useState(currentMode)
|
||||
|
||||
|
@ -4,9 +4,10 @@ export const StyleConstants = {
|
||||
Font: {
|
||||
Size: {
|
||||
S: 12,
|
||||
M: 14,
|
||||
M: 16,
|
||||
L: 18
|
||||
},
|
||||
LineHeight: { M: 20 },
|
||||
Weight: {
|
||||
Bold: '600' as '600'
|
||||
}
|
||||
@ -19,7 +20,7 @@ export const StyleConstants = {
|
||||
L: Base * 6,
|
||||
XL: Base * 10,
|
||||
Global: {
|
||||
PagePadding: Base * 6
|
||||
PagePadding: Base * 4
|
||||
}
|
||||
},
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user