mirror of https://github.com/tooot-app/app
Rewrite all buttons
This commit is contained in:
parent
3ea88d025b
commit
da79674548
|
@ -43,6 +43,7 @@
|
||||||
"react-dom": "16.13.1",
|
"react-dom": "16.13.1",
|
||||||
"react-i18next": "^11.8.4",
|
"react-i18next": "^11.8.4",
|
||||||
"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-expo-image-cache": "^4.1.0",
|
"react-native-expo-image-cache": "^4.1.0",
|
||||||
"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",
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { ButtonRow } from '@components/Button'
|
import Button from '@components/Button'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
|
@ -84,12 +84,12 @@ const BottomSheet: React.FC<Props> = ({ children, visible, handleDismiss }) => {
|
||||||
style={[styles.handle, { backgroundColor: theme.background }]}
|
style={[styles.handle, { backgroundColor: theme.background }]}
|
||||||
/>
|
/>
|
||||||
{children}
|
{children}
|
||||||
<View style={styles.button}>
|
<Button
|
||||||
<ButtonRow
|
type='text'
|
||||||
onPress={() => closeModal.start(() => handleDismiss())}
|
content='取消'
|
||||||
text='取消'
|
onPress={() => closeModal.start(() => handleDismiss())}
|
||||||
/>
|
style={styles.button}
|
||||||
</View>
|
/>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</View>
|
</View>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -112,8 +112,7 @@ const styles = StyleSheet.create({
|
||||||
top: -StyleConstants.Spacing.M * 2
|
top: -StyleConstants.Spacing.M * 2
|
||||||
},
|
},
|
||||||
button: {
|
button: {
|
||||||
paddingLeft: StyleConstants.Spacing.Global.PagePadding * 2,
|
marginHorizontal: StyleConstants.Spacing.Global.PagePadding * 2
|
||||||
paddingRight: StyleConstants.Spacing.Global.PagePadding * 2
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,166 @@
|
||||||
import ButtonRound from '@components/Button/ButtonRound'
|
import { Feather } from '@expo/vector-icons'
|
||||||
import ButtonRow from '@components/Button/ButtonRow'
|
import React, { useLayoutEffect, useMemo } from 'react'
|
||||||
|
import {
|
||||||
|
Pressable,
|
||||||
|
StyleProp,
|
||||||
|
StyleSheet,
|
||||||
|
Text,
|
||||||
|
View,
|
||||||
|
ViewStyle
|
||||||
|
} from 'react-native'
|
||||||
|
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 { ButtonRound, ButtonRow }
|
export interface Props {
|
||||||
|
style?: StyleProp<ViewStyle>
|
||||||
|
|
||||||
|
type: 'icon' | 'text'
|
||||||
|
content: string
|
||||||
|
|
||||||
|
loading?: boolean
|
||||||
|
destructive?: boolean
|
||||||
|
disabled?: boolean
|
||||||
|
|
||||||
|
size?: 'S' | 'M' | 'L'
|
||||||
|
spacing?: 'XS' | 'S' | 'M' | 'L'
|
||||||
|
round?: boolean
|
||||||
|
overlay?: boolean
|
||||||
|
|
||||||
|
onPress: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const Button: React.FC<Props> = ({
|
||||||
|
style: customStyle,
|
||||||
|
type,
|
||||||
|
content,
|
||||||
|
loading = false,
|
||||||
|
destructive = false,
|
||||||
|
disabled = false,
|
||||||
|
size = 'M',
|
||||||
|
spacing = 'S',
|
||||||
|
round = false,
|
||||||
|
overlay = false,
|
||||||
|
onPress
|
||||||
|
}) => {
|
||||||
|
const { theme } = useTheme()
|
||||||
|
|
||||||
|
useLayoutEffect(() => layoutAnimation(), [loading, disabled])
|
||||||
|
|
||||||
|
const loadingSpinkit = useMemo(
|
||||||
|
() => (
|
||||||
|
<View style={{ position: 'absolute' }}>
|
||||||
|
<Chase size={StyleConstants.Font.Size[size]} color={theme.secondary} />
|
||||||
|
</View>
|
||||||
|
),
|
||||||
|
[theme]
|
||||||
|
)
|
||||||
|
|
||||||
|
const colorContent = useMemo(() => {
|
||||||
|
if (overlay) {
|
||||||
|
return theme.primaryOverlay
|
||||||
|
} else {
|
||||||
|
if (disabled) {
|
||||||
|
return theme.secondary
|
||||||
|
} else {
|
||||||
|
if (destructive) {
|
||||||
|
return theme.red
|
||||||
|
} else {
|
||||||
|
return theme.primary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [theme, disabled])
|
||||||
|
|
||||||
|
const children = useMemo(() => {
|
||||||
|
switch (type) {
|
||||||
|
case 'icon':
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Feather
|
||||||
|
name={content as any}
|
||||||
|
size={StyleConstants.Font.Size[size] * (size === 'M' ? 1 : 1.5)}
|
||||||
|
color={colorContent}
|
||||||
|
style={{ opacity: loading ? 0 : 1 }}
|
||||||
|
/>
|
||||||
|
{loading && loadingSpinkit}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
case 'text':
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
color: colorContent,
|
||||||
|
fontSize:
|
||||||
|
StyleConstants.Font.Size[size] * (size === 'M' ? 1 : 1.5),
|
||||||
|
fontWeight: destructive
|
||||||
|
? StyleConstants.Font.Weight.Bold
|
||||||
|
: undefined,
|
||||||
|
opacity: loading ? 0 : 1
|
||||||
|
}}
|
||||||
|
children={content}
|
||||||
|
/>
|
||||||
|
{loading && loadingSpinkit}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, [theme, content, loading, disabled])
|
||||||
|
|
||||||
|
const colorBorder = useMemo(() => {
|
||||||
|
if (disabled || loading) {
|
||||||
|
return theme.secondary
|
||||||
|
} else {
|
||||||
|
if (destructive) {
|
||||||
|
return theme.red
|
||||||
|
} else {
|
||||||
|
return theme.primary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [theme, loading, disabled])
|
||||||
|
const colorBackground = useMemo(() => {
|
||||||
|
if (overlay) {
|
||||||
|
return theme.backgroundOverlay
|
||||||
|
} else {
|
||||||
|
return theme.background
|
||||||
|
}
|
||||||
|
}, [theme])
|
||||||
|
|
||||||
|
enum spacingMapping {
|
||||||
|
XS = 'S',
|
||||||
|
S = 'M',
|
||||||
|
M = 'L',
|
||||||
|
L = 'XL'
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Pressable
|
||||||
|
{...(!disabled && !loading && { onPress })}
|
||||||
|
children={children}
|
||||||
|
style={[
|
||||||
|
styles.button,
|
||||||
|
{
|
||||||
|
borderWidth: overlay ? 0 : 1,
|
||||||
|
borderColor: colorBorder,
|
||||||
|
backgroundColor: colorBackground,
|
||||||
|
paddingVertical: StyleConstants.Spacing[spacing],
|
||||||
|
paddingHorizontal:
|
||||||
|
StyleConstants.Spacing[round ? spacing : spacingMapping[spacing]]
|
||||||
|
},
|
||||||
|
customStyle
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
button: {
|
||||||
|
borderRadius: 100,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default Button
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
import { Feather } from '@expo/vector-icons'
|
|
||||||
import React from 'react'
|
|
||||||
import { Pressable, StyleSheet } from 'react-native'
|
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
|
||||||
|
|
||||||
export interface Props {
|
|
||||||
styles: any
|
|
||||||
onPress: () => void
|
|
||||||
icon: any
|
|
||||||
size?: 'S' | 'M' | 'L'
|
|
||||||
coordinate?: 'center' | 'default'
|
|
||||||
}
|
|
||||||
|
|
||||||
const ButtomRound: React.FC<Props> = ({
|
|
||||||
styles: extraStyles,
|
|
||||||
onPress,
|
|
||||||
icon,
|
|
||||||
size = 'M',
|
|
||||||
coordinate = 'default'
|
|
||||||
}) => {
|
|
||||||
const { theme } = useTheme()
|
|
||||||
const dimension =
|
|
||||||
StyleConstants.Spacing.S * 1.5 + StyleConstants.Font.Size[size]
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Pressable
|
|
||||||
style={[
|
|
||||||
styles.base,
|
|
||||||
extraStyles,
|
|
||||||
{
|
|
||||||
backgroundColor: theme.backgroundOverlay,
|
|
||||||
...(coordinate === 'center' && {
|
|
||||||
transform: [
|
|
||||||
{ translateX: -dimension / 2 },
|
|
||||||
{ translateY: -dimension / 2 }
|
|
||||||
]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
onPress={onPress}
|
|
||||||
>
|
|
||||||
<Feather
|
|
||||||
name={icon}
|
|
||||||
size={StyleConstants.Font.Size[size]}
|
|
||||||
color={theme.primaryOverlay}
|
|
||||||
/>
|
|
||||||
</Pressable>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
base: {
|
|
||||||
position: 'absolute',
|
|
||||||
padding: StyleConstants.Spacing.S * 1.5,
|
|
||||||
borderRadius: StyleConstants.Spacing.XL
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export default ButtomRound
|
|
|
@ -1,89 +0,0 @@
|
||||||
import { Feather } from '@expo/vector-icons'
|
|
||||||
import React from 'react'
|
|
||||||
import { Pressable, StyleProp, StyleSheet, Text, ViewStyle } from 'react-native'
|
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
|
||||||
|
|
||||||
type PropsBase = {
|
|
||||||
onPress: () => void
|
|
||||||
disabled?: boolean
|
|
||||||
buttonSize?: 'S' | 'M'
|
|
||||||
size?: 'S' | 'M' | 'L'
|
|
||||||
style?: StyleProp<ViewStyle>
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PropsText extends PropsBase {
|
|
||||||
text: string
|
|
||||||
icon?: any
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PropsIcon extends PropsBase {
|
|
||||||
text?: string
|
|
||||||
icon: any
|
|
||||||
}
|
|
||||||
|
|
||||||
const ButtonRow: React.FC<PropsText | PropsIcon> = ({
|
|
||||||
onPress,
|
|
||||||
disabled = false,
|
|
||||||
buttonSize = 'M',
|
|
||||||
text,
|
|
||||||
icon,
|
|
||||||
size = 'M',
|
|
||||||
style: customStyle
|
|
||||||
}) => {
|
|
||||||
const { theme } = useTheme()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Pressable
|
|
||||||
{...(!disabled && { onPress })}
|
|
||||||
style={[
|
|
||||||
customStyle,
|
|
||||||
styles.button,
|
|
||||||
{
|
|
||||||
paddingLeft:
|
|
||||||
StyleConstants.Spacing.M -
|
|
||||||
(icon ? StyleConstants.Font.Size[size] / 2 : 0),
|
|
||||||
paddingRight:
|
|
||||||
StyleConstants.Spacing.M -
|
|
||||||
(icon ? StyleConstants.Font.Size[size] / 2 : 0),
|
|
||||||
borderColor: disabled ? theme.secondary : theme.primary,
|
|
||||||
paddingTop: StyleConstants.Spacing[buttonSize === 'M' ? 'S' : 'XS'],
|
|
||||||
paddingBottom: StyleConstants.Spacing[buttonSize === 'M' ? 'S' : 'XS']
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{icon ? (
|
|
||||||
<Feather
|
|
||||||
name={icon}
|
|
||||||
size={StyleConstants.Font.Size[size]}
|
|
||||||
color={disabled ? theme.secondary : theme.primary}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
styles.text,
|
|
||||||
{
|
|
||||||
color: disabled ? theme.secondary : theme.primary,
|
|
||||||
fontSize: StyleConstants.Font.Size[size]
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{text}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</Pressable>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
button: {
|
|
||||||
borderWidth: 1.25,
|
|
||||||
borderRadius: 100,
|
|
||||||
alignItems: 'center'
|
|
||||||
},
|
|
||||||
text: {
|
|
||||||
textAlign: 'center'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export default ButtonRow
|
|
|
@ -1,78 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import {
|
|
||||||
Alert,
|
|
||||||
AlertButton,
|
|
||||||
AlertOptions,
|
|
||||||
Pressable,
|
|
||||||
StyleSheet,
|
|
||||||
Text,
|
|
||||||
View
|
|
||||||
} from 'react-native'
|
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
|
||||||
|
|
||||||
export interface Props {
|
|
||||||
text: string
|
|
||||||
destructive?: boolean
|
|
||||||
alertOption?: {
|
|
||||||
title: string
|
|
||||||
message?: string | undefined
|
|
||||||
buttons?: AlertButton[] | undefined
|
|
||||||
options?: AlertOptions | undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const Core: React.FC<Props> = ({ text, destructive = false }) => {
|
|
||||||
const { theme } = useTheme()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={styles.core}>
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
color: destructive ? theme.red : theme.primary,
|
|
||||||
fontWeight: destructive ? StyleConstants.Font.Weight.Bold : undefined
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{text}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const MenuButton: React.FC<Props> = ({ ...props }) => {
|
|
||||||
const { theme } = useTheme()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Pressable
|
|
||||||
style={[styles.base, { borderBottomColor: theme.separator }]}
|
|
||||||
onPress={() =>
|
|
||||||
props.alertOption &&
|
|
||||||
Alert.alert(
|
|
||||||
props.alertOption.title,
|
|
||||||
props.alertOption.message,
|
|
||||||
props.alertOption.buttons,
|
|
||||||
props.alertOption.options
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Core {...props} />
|
|
||||||
</Pressable>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
base: {
|
|
||||||
height: 50,
|
|
||||||
borderBottomWidth: 1
|
|
||||||
},
|
|
||||||
core: {
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
paddingLeft: StyleConstants.Spacing.Global.PagePadding,
|
|
||||||
paddingRight: StyleConstants.Spacing.Global.PagePadding
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export default MenuButton
|
|
|
@ -1,6 +1,5 @@
|
||||||
import React, { Children } from 'react'
|
import React from 'react'
|
||||||
import { StyleSheet, View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
@ -8,25 +7,7 @@ export interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
const MenuContainer: React.FC<Props> = ({ children }) => {
|
const MenuContainer: React.FC<Props> = ({ children }) => {
|
||||||
const { theme } = useTheme()
|
return <View style={styles.base}>{children}</View>
|
||||||
// @ts-ignore
|
|
||||||
const firstChild = Children.toArray(children)[0].type.name
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
styles.base,
|
|
||||||
{
|
|
||||||
...(firstChild !== 'MenuHeader' && {
|
|
||||||
borderTopColor: theme.separator,
|
|
||||||
borderTopWidth: 1
|
|
||||||
})
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
|
|
@ -11,21 +11,21 @@ const MenuHeader: React.FC<Props> = ({ heading }) => {
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.base, { borderBottomColor: theme.separator }]}>
|
<View style={styles.base}>
|
||||||
<Text style={[styles.text, { color: theme.primary }]}>{heading}</Text>
|
<Text style={[styles.text, { color: theme.secondary }]}>{heading}</Text>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
base: {
|
base: {
|
||||||
borderBottomWidth: 1,
|
|
||||||
paddingLeft: StyleConstants.Spacing.Global.PagePadding,
|
paddingLeft: StyleConstants.Spacing.Global.PagePadding,
|
||||||
paddingRight: StyleConstants.Spacing.Global.PagePadding,
|
paddingRight: StyleConstants.Spacing.Global.PagePadding,
|
||||||
paddingBottom: StyleConstants.Spacing.S
|
paddingBottom: StyleConstants.Spacing.S
|
||||||
},
|
},
|
||||||
text: {
|
text: {
|
||||||
fontSize: StyleConstants.Font.Size.S
|
fontSize: StyleConstants.Font.Size.S,
|
||||||
|
fontWeight: StyleConstants.Font.Weight.Bold
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -71,14 +71,11 @@ const MenuRow: React.FC<Props> = ({ ...props }) => {
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
return props.onPress ? (
|
return props.onPress ? (
|
||||||
<Pressable
|
<Pressable style={styles.base} onPress={props.onPress}>
|
||||||
style={[styles.base, { borderBottomColor: theme.separator }]}
|
|
||||||
onPress={props.onPress}
|
|
||||||
>
|
|
||||||
<Core {...props} />
|
<Core {...props} />
|
||||||
</Pressable>
|
</Pressable>
|
||||||
) : (
|
) : (
|
||||||
<View style={[styles.base, { borderBottomColor: theme.separator }]}>
|
<View style={styles.base}>
|
||||||
<Core {...props} />
|
<Core {...props} />
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
@ -86,8 +83,7 @@ const MenuRow: React.FC<Props> = ({ ...props }) => {
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
base: {
|
base: {
|
||||||
height: 50,
|
height: 50
|
||||||
borderBottomWidth: 1
|
|
||||||
},
|
},
|
||||||
core: {
|
core: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|
|
@ -152,83 +152,86 @@ const ParseContent: React.FC<Props> = ({
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
const rootComponent = useCallback(({ children }) => {
|
const rootComponent = useCallback(
|
||||||
const lineHeight = StyleConstants.Font.LineHeight[size]
|
({ children }) => {
|
||||||
|
const lineHeight = StyleConstants.Font.LineHeight[size]
|
||||||
|
|
||||||
const [heightOriginal, setHeightOriginal] = useState<number>()
|
const [heightOriginal, setHeightOriginal] = useState<number>()
|
||||||
const [heightTruncated, setHeightTruncated] = useState<number>()
|
const [heightTruncated, setHeightTruncated] = useState<number>()
|
||||||
const [allowExpand, setAllowExpand] = useState(false)
|
const [allowExpand, setAllowExpand] = useState(false)
|
||||||
const [showAllText, setShowAllText] = useState(false)
|
const [showAllText, setShowAllText] = useState(false)
|
||||||
|
|
||||||
const calNumberOfLines = useMemo(() => {
|
const calNumberOfLines = useMemo(() => {
|
||||||
if (heightOriginal) {
|
if (heightOriginal) {
|
||||||
if (!heightTruncated) {
|
if (!heightTruncated) {
|
||||||
return numberOfLines
|
|
||||||
} else {
|
|
||||||
if (allowExpand && !showAllText) {
|
|
||||||
return numberOfLines
|
return numberOfLines
|
||||||
} else {
|
} else {
|
||||||
return undefined
|
if (allowExpand && !showAllText) {
|
||||||
}
|
return numberOfLines
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
}, [heightOriginal, heightTruncated, allowExpand, showAllText])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View>
|
|
||||||
<Text
|
|
||||||
style={{ lineHeight }}
|
|
||||||
children={children}
|
|
||||||
numberOfLines={calNumberOfLines}
|
|
||||||
onLayout={({ nativeEvent }) => {
|
|
||||||
if (!heightOriginal) {
|
|
||||||
setHeightOriginal(nativeEvent.layout.height)
|
|
||||||
} else {
|
} else {
|
||||||
if (!heightTruncated) {
|
return undefined
|
||||||
setHeightTruncated(nativeEvent.layout.height)
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}, [heightOriginal, heightTruncated, allowExpand, showAllText])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
<Text
|
||||||
|
style={{ lineHeight, color: theme.primary }}
|
||||||
|
children={children}
|
||||||
|
numberOfLines={calNumberOfLines}
|
||||||
|
onLayout={({ nativeEvent }) => {
|
||||||
|
if (!heightOriginal) {
|
||||||
|
setHeightOriginal(nativeEvent.layout.height)
|
||||||
} else {
|
} else {
|
||||||
if (heightOriginal > heightTruncated) {
|
if (!heightTruncated) {
|
||||||
setAllowExpand(true)
|
setHeightTruncated(nativeEvent.layout.height)
|
||||||
|
} else {
|
||||||
|
if (heightOriginal > heightTruncated) {
|
||||||
|
setAllowExpand(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{allowExpand && (
|
|
||||||
<Pressable
|
|
||||||
onPress={() => {
|
|
||||||
setShowAllText(!showAllText)
|
|
||||||
}}
|
}}
|
||||||
style={{ marginTop: showAllText ? 0 : -lineHeight * 1.25 }}
|
/>
|
||||||
>
|
{allowExpand && (
|
||||||
<LinearGradient
|
<Pressable
|
||||||
colors={[
|
onPress={() => {
|
||||||
theme.backgroundGradientStart,
|
setShowAllText(!showAllText)
|
||||||
theme.backgroundGradientEnd
|
|
||||||
]}
|
|
||||||
locations={[0, lineHeight / (StyleConstants.Font.Size.S * 4)]}
|
|
||||||
style={{
|
|
||||||
paddingTop: StyleConstants.Font.Size.S * 2,
|
|
||||||
paddingBottom: StyleConstants.Font.Size.S
|
|
||||||
}}
|
}}
|
||||||
|
style={{ marginTop: showAllText ? 0 : -lineHeight * 1.25 }}
|
||||||
>
|
>
|
||||||
<Text
|
<LinearGradient
|
||||||
|
colors={[
|
||||||
|
theme.backgroundGradientStart,
|
||||||
|
theme.backgroundGradientEnd
|
||||||
|
]}
|
||||||
|
locations={[0, lineHeight / (StyleConstants.Font.Size.S * 4)]}
|
||||||
style={{
|
style={{
|
||||||
textAlign: 'center',
|
paddingTop: StyleConstants.Font.Size.S * 2,
|
||||||
fontSize: StyleConstants.Font.Size.S,
|
paddingBottom: StyleConstants.Font.Size.S
|
||||||
color: theme.primary
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{`${showAllText ? '折叠' : '展开'}${expandHint}`}
|
<Text
|
||||||
</Text>
|
style={{
|
||||||
</LinearGradient>
|
textAlign: 'center',
|
||||||
</Pressable>
|
fontSize: StyleConstants.Font.Size.S,
|
||||||
)}
|
color: theme.primary
|
||||||
</View>
|
}}
|
||||||
)
|
>
|
||||||
}, [])
|
{`${showAllText ? '折叠' : '展开'}${expandHint}`}
|
||||||
|
</Text>
|
||||||
|
</LinearGradient>
|
||||||
|
</Pressable>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[theme]
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HTMLView
|
<HTMLView
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Feather } from '@expo/vector-icons'
|
||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import { ActivityIndicator, StyleSheet, Text, View } from 'react-native'
|
import { ActivityIndicator, StyleSheet, Text, View } from 'react-native'
|
||||||
import { QueryStatus } from 'react-query'
|
import { QueryStatus } from 'react-query'
|
||||||
import { ButtonRow } from '@components/Button'
|
import Button from '@components/Button'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ const TimelineEmpty: React.FC<Props> = ({ status, refetch }) => {
|
||||||
<Text style={[styles.error, { color: theme.primary }]}>
|
<Text style={[styles.error, { color: theme.primary }]}>
|
||||||
加载错误
|
加载错误
|
||||||
</Text>
|
</Text>
|
||||||
<ButtonRow text='重试' onPress={() => refetch()} />
|
<Button type='text' content='重试' onPress={() => refetch()} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
case 'success':
|
case 'success':
|
||||||
|
|
|
@ -16,7 +16,7 @@ const TimelineSeparator: React.FC<Props> = ({ highlighted = false }) => {
|
||||||
style={[
|
style={[
|
||||||
styles.base,
|
styles.base,
|
||||||
{
|
{
|
||||||
borderTopColor: theme.separator,
|
borderTopColor: theme.border,
|
||||||
marginLeft: highlighted
|
marginLeft: highlighted
|
||||||
? StyleConstants.Spacing.Global.PagePadding
|
? StyleConstants.Spacing.Global.PagePadding
|
||||||
: StyleConstants.Spacing.Global.PagePadding +
|
: StyleConstants.Spacing.Global.PagePadding +
|
||||||
|
@ -30,7 +30,7 @@ const TimelineSeparator: React.FC<Props> = ({ highlighted = false }) => {
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
base: {
|
base: {
|
||||||
borderTopWidth: 1,
|
borderTopWidth: StyleSheet.hairlineWidth,
|
||||||
marginRight: StyleConstants.Spacing.Global.PagePadding
|
marginRight: StyleConstants.Spacing.Global.PagePadding
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -78,7 +78,7 @@ const TimelineActions: React.FC<Props> = ({
|
||||||
case 'reblog':
|
case 'reblog':
|
||||||
case 'bookmark':
|
case 'bookmark':
|
||||||
if (type === 'favourite' && queryKey[0] === 'Favourites') {
|
if (type === 'favourite' && queryKey[0] === 'Favourites') {
|
||||||
queryClient.invalidateQueries(['Favourites'])
|
queryClient.invalidateQueries(['Favourites', {}])
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
|
@ -91,7 +91,7 @@ const TimelineActions: React.FC<Props> = ({
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if (type === 'bookmark' && queryKey[0] === 'Bookmarks') {
|
if (type === 'bookmark' && queryKey[0] === 'Bookmarks') {
|
||||||
queryClient.invalidateQueries(['Bookmarks'])
|
queryClient.invalidateQueries(['Bookmarks', {}])
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useCallback, useState } from 'react'
|
import React, { useCallback, useState } from 'react'
|
||||||
import { Image, Pressable, StyleSheet, View } from 'react-native'
|
import { Image, Pressable, StyleSheet, View } from 'react-native'
|
||||||
import { Audio } from 'expo-av'
|
import { Audio } from 'expo-av'
|
||||||
import { ButtonRow } from '@components/Button'
|
import Button from '@components/Button'
|
||||||
import { Surface } from 'gl-react-expo'
|
import { Surface } from 'gl-react-expo'
|
||||||
import { Blurhash } from 'gl-react-blurhash'
|
import { Blurhash } from 'gl-react-blurhash'
|
||||||
import Slider from '@react-native-community/slider'
|
import Slider from '@react-native-community/slider'
|
||||||
|
@ -21,7 +21,10 @@ const AttachmentAudio: React.FC<Props> = ({ sensitiveShown, audio }) => {
|
||||||
const [audioPosition, setAudioPosition] = useState(0)
|
const [audioPosition, setAudioPosition] = useState(0)
|
||||||
const playAudio = useCallback(async () => {
|
const playAudio = useCallback(async () => {
|
||||||
if (!audioPlayer) {
|
if (!audioPlayer) {
|
||||||
await Audio.setAudioModeAsync({ interruptionModeIOS: 1 })
|
await Audio.setAudioModeAsync({
|
||||||
|
playsInSilentModeIOS: true,
|
||||||
|
interruptionModeIOS: 1
|
||||||
|
})
|
||||||
const { sound } = await Audio.Sound.createAsync(
|
const { sound } = await Audio.Sound.createAsync(
|
||||||
{ uri: audio.url },
|
{ uri: audio.url },
|
||||||
{},
|
{},
|
||||||
|
@ -42,7 +45,7 @@ const AttachmentAudio: React.FC<Props> = ({ sensitiveShown, audio }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Pressable style={styles.overlay}>
|
<View style={styles.overlay}>
|
||||||
{sensitiveShown ? (
|
{sensitiveShown ? (
|
||||||
audio.blurhash && (
|
audio.blurhash && (
|
||||||
<Surface
|
<Surface
|
||||||
|
@ -62,16 +65,18 @@ const AttachmentAudio: React.FC<Props> = ({ sensitiveShown, audio }) => {
|
||||||
source={{ uri: audio.preview_url || audio.preview_remote_url }}
|
source={{ uri: audio.preview_url || audio.preview_remote_url }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<ButtonRow
|
<Button
|
||||||
icon={audioPlaying ? 'pause' : 'play'}
|
type='icon'
|
||||||
|
content={audioPlaying ? 'pause' : 'play'}
|
||||||
size='L'
|
size='L'
|
||||||
|
overlay
|
||||||
{...(audioPlaying
|
{...(audioPlaying
|
||||||
? { onPress: pauseAudio }
|
? { onPress: pauseAudio }
|
||||||
: { onPress: playAudio })}
|
: { onPress: playAudio })}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Pressable>
|
</View>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { StyleSheet, Text, View } from 'react-native'
|
import { StyleSheet, Text, View } from 'react-native'
|
||||||
import { ButtonRow } from '@components/Button'
|
import Button from '@components/Button'
|
||||||
import { useTheme } from '@root/utils/styles/ThemeManager'
|
import { useTheme } from '@root/utils/styles/ThemeManager'
|
||||||
import { StyleConstants } from '@root/utils/styles/constants'
|
import { StyleConstants } from '@root/utils/styles/constants'
|
||||||
import openLink from '@root/utils/openLink'
|
import openLink from '@root/utils/openLink'
|
||||||
|
@ -15,8 +15,9 @@ const AttachmentUnsupported: React.FC<Props> = ({ attachment }) => {
|
||||||
<View style={styles.base}>
|
<View style={styles.base}>
|
||||||
<Text style={[styles.text, { color: theme.primary }]}>文件不支持</Text>
|
<Text style={[styles.text, { color: theme.primary }]}>文件不支持</Text>
|
||||||
{attachment.remote_url ? (
|
{attachment.remote_url ? (
|
||||||
<ButtonRow
|
<Button
|
||||||
text='尝试远程链接'
|
type='text'
|
||||||
|
content='尝试远程链接'
|
||||||
size='S'
|
size='S'
|
||||||
onPress={async () => await openLink(attachment.remote_url!)}
|
onPress={async () => await openLink(attachment.remote_url!)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useCallback, useRef, useState } from 'react'
|
import React, { useCallback, useRef, useState } from 'react'
|
||||||
import { Pressable, StyleSheet } from 'react-native'
|
import { Pressable, StyleSheet } from 'react-native'
|
||||||
import { Video } from 'expo-av'
|
import { Video } from 'expo-av'
|
||||||
import { ButtonRow } from '@components/Button'
|
import Button from '@components/Button'
|
||||||
import { Surface } from 'gl-react-expo'
|
import { Surface } from 'gl-react-expo'
|
||||||
import { Blurhash } from 'gl-react-blurhash'
|
import { Blurhash } from 'gl-react-blurhash'
|
||||||
|
|
||||||
|
@ -64,7 +64,13 @@ const AttachmentVideo: React.FC<Props> = ({
|
||||||
<Blurhash hash={video.blurhash} />
|
<Blurhash hash={video.blurhash} />
|
||||||
</Surface>
|
</Surface>
|
||||||
) : (
|
) : (
|
||||||
<ButtonRow icon='play' size='L' onPress={playOnPress} />
|
<Button
|
||||||
|
type='icon'
|
||||||
|
content='play'
|
||||||
|
size='L'
|
||||||
|
overlay
|
||||||
|
onPress={playOnPress}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { Feather } from '@expo/vector-icons'
|
import { Feather } from '@expo/vector-icons'
|
||||||
import React, { useMemo, useState } from 'react'
|
import React, { useCallback, useMemo, useState } from 'react'
|
||||||
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
||||||
import { useMutation, useQueryClient } from 'react-query'
|
import { useMutation, useQueryClient } from 'react-query'
|
||||||
import client from '@api/client'
|
import client from '@api/client'
|
||||||
import { ButtonRow } from '@components/Button'
|
import Button from '@components/Button'
|
||||||
import { toast } from '@components/toast'
|
import { toast } from '@components/toast'
|
||||||
import relativeTime from '@utils/relativeTime'
|
import relativeTime from '@utils/relativeTime'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
@ -18,14 +18,13 @@ const fireMutation = async ({
|
||||||
options
|
options
|
||||||
}: {
|
}: {
|
||||||
id: string
|
id: string
|
||||||
options?: { [key: number]: boolean }
|
options?: boolean[]
|
||||||
}) => {
|
}) => {
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
options &&
|
options &&
|
||||||
Object.keys(options).forEach(option => {
|
options.forEach((o, i) => {
|
||||||
// @ts-ignore
|
if (options[i]) {
|
||||||
if (options[option]) {
|
formData.append('choices[]', i.toString())
|
||||||
formData.append('choices[]', option)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -63,6 +62,11 @@ const TimelinePoll: React.FC<Props> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
const [allOptions, setAllOptions] = useState(
|
||||||
|
new Array(poll.options.length).fill(false)
|
||||||
|
)
|
||||||
|
|
||||||
const mutation = useMutation(fireMutation, {
|
const mutation = useMutation(fireMutation, {
|
||||||
onSuccess: (data, { id }) => {
|
onSuccess: (data, { id }) => {
|
||||||
queryClient.cancelQueries(queryKey)
|
queryClient.cancelQueries(queryKey)
|
||||||
|
@ -99,26 +103,26 @@ const TimelinePoll: React.FC<Props> = ({
|
||||||
if (!sameAccount && !poll.voted) {
|
if (!sameAccount && !poll.voted) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.button}>
|
<View style={styles.button}>
|
||||||
<ButtonRow
|
<Button
|
||||||
onPress={() => {
|
onPress={() =>
|
||||||
if (poll.multiple) {
|
mutation.mutate({ id: poll.id, options: allOptions })
|
||||||
mutation.mutate({ id: poll.id, options: multipleOptions })
|
}
|
||||||
} else {
|
type='text'
|
||||||
mutation.mutate({ id: poll.id, options: singleOptions })
|
content='投票'
|
||||||
}
|
loading={mutation.isLoading}
|
||||||
}}
|
disabled={allOptions.filter(o => o !== false).length === 0}
|
||||||
{...(mutation.isLoading ? { icon: 'loader' } : { text: '投票' })}
|
|
||||||
disabled={mutation.isLoading}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<View style={styles.button}>
|
<View style={styles.button}>
|
||||||
<ButtonRow
|
<Button
|
||||||
onPress={() => mutation.mutate({ id: poll.id })}
|
onPress={() => mutation.mutate({ id: poll.id })}
|
||||||
{...(mutation.isLoading ? { icon: 'loader' } : { text: '刷新' })}
|
{...(mutation.isLoading ? { icon: 'loader' } : { text: '刷新' })}
|
||||||
disabled={mutation.isLoading}
|
type='text'
|
||||||
|
content='刷新'
|
||||||
|
loading={mutation.isLoading}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
@ -142,19 +146,13 @@ const TimelinePoll: React.FC<Props> = ({
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const [singleOptions, setSingleOptions] = useState({
|
const isSelected = useCallback(
|
||||||
...[false, false, false, false].slice(0, poll.options.length)
|
(index: number): any =>
|
||||||
})
|
allOptions[index]
|
||||||
const [multipleOptions, setMultipleOptions] = useState({
|
? `check-${poll.multiple ? 'square' : 'circle'}`
|
||||||
...[false, false, false, false].slice(0, poll.options.length)
|
: `${poll.multiple ? 'square' : 'circle'}`,
|
||||||
})
|
[allOptions]
|
||||||
const isSelected = (index: number) => {
|
)
|
||||||
if (poll.multiple) {
|
|
||||||
return multipleOptions[index] ? 'check-square' : 'square'
|
|
||||||
} else {
|
|
||||||
return singleOptions[index] ? 'check-circle' : 'circle'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.base}>
|
<View style={styles.base}>
|
||||||
|
@ -162,21 +160,23 @@ const TimelinePoll: React.FC<Props> = ({
|
||||||
poll.voted ? (
|
poll.voted ? (
|
||||||
<View key={index} style={styles.poll}>
|
<View key={index} style={styles.poll}>
|
||||||
<View style={styles.optionSelected}>
|
<View style={styles.optionSelected}>
|
||||||
|
<Feather
|
||||||
|
style={styles.voted}
|
||||||
|
name={poll.multiple ? 'check-square' : 'check-circle'}
|
||||||
|
size={StyleConstants.Font.Size.M}
|
||||||
|
color={
|
||||||
|
poll.own_votes!.includes(index)
|
||||||
|
? theme.primary
|
||||||
|
: theme.background
|
||||||
|
}
|
||||||
|
/>
|
||||||
<View style={styles.contentSelected}>
|
<View style={styles.contentSelected}>
|
||||||
<Emojis
|
<Emojis
|
||||||
content={option.title}
|
content={option.title}
|
||||||
emojis={poll.emojis}
|
emojis={poll.emojis}
|
||||||
size={StyleConstants.Font.Size.M}
|
size={StyleConstants.Font.Size.M}
|
||||||
numberOfLines={1}
|
numberOfLines={2}
|
||||||
/>
|
/>
|
||||||
{poll.own_votes!.includes(index) && (
|
|
||||||
<Feather
|
|
||||||
style={styles.voted}
|
|
||||||
name={poll.multiple ? 'check-square' : 'check-circle'}
|
|
||||||
size={StyleConstants.Font.Size.M}
|
|
||||||
color={theme.primary}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
<Text style={[styles.percentage, { color: theme.primary }]}>
|
<Text style={[styles.percentage, { color: theme.primary }]}>
|
||||||
{poll.votes_count
|
{poll.votes_count
|
||||||
|
@ -193,7 +193,7 @@ const TimelinePoll: React.FC<Props> = ({
|
||||||
width: `${Math.round(
|
width: `${Math.round(
|
||||||
(option.votes_count / poll.voters_count) * 100
|
(option.votes_count / poll.voters_count) * 100
|
||||||
)}%`,
|
)}%`,
|
||||||
backgroundColor: theme.border
|
backgroundColor: theme.disabled
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
@ -204,19 +204,15 @@ const TimelinePoll: React.FC<Props> = ({
|
||||||
style={[styles.optionUnselected]}
|
style={[styles.optionUnselected]}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
if (poll.multiple) {
|
if (poll.multiple) {
|
||||||
setMultipleOptions({
|
setAllOptions(
|
||||||
...multipleOptions,
|
allOptions.map((o, i) => (i === index ? !o : o))
|
||||||
[index]: !multipleOptions[index]
|
)
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
setSingleOptions({
|
setAllOptions(
|
||||||
...[
|
allOptions.map((o, i) =>
|
||||||
index === 0,
|
i === index ? !allOptions[index] : allOptions[index]
|
||||||
index === 1,
|
)
|
||||||
index === 2,
|
)
|
||||||
index === 3
|
|
||||||
].slice(0, poll.options.length)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -231,6 +227,7 @@ const TimelinePoll: React.FC<Props> = ({
|
||||||
content={option.title}
|
content={option.title}
|
||||||
emojis={poll.emojis}
|
emojis={poll.emojis}
|
||||||
size={StyleConstants.Font.Size.M}
|
size={StyleConstants.Font.Size.M}
|
||||||
|
numberOfLines={2}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
|
@ -253,15 +250,15 @@ const styles = StyleSheet.create({
|
||||||
marginTop: StyleConstants.Spacing.M
|
marginTop: StyleConstants.Spacing.M
|
||||||
},
|
},
|
||||||
poll: {
|
poll: {
|
||||||
minHeight: StyleConstants.Font.LineHeight.M * 1.5,
|
flex: 1,
|
||||||
marginBottom: StyleConstants.Spacing.XS
|
minHeight: StyleConstants.Font.LineHeight.M * 2,
|
||||||
|
paddingVertical: StyleConstants.Spacing.XS
|
||||||
},
|
},
|
||||||
optionSelected: {
|
optionSelected: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
paddingLeft: StyleConstants.Spacing.M,
|
|
||||||
paddingRight: StyleConstants.Spacing.M
|
paddingRight: StyleConstants.Spacing.M
|
||||||
},
|
},
|
||||||
optionUnselected: {
|
optionUnselected: {
|
||||||
|
@ -269,30 +266,30 @@ const styles = StyleSheet.create({
|
||||||
flexDirection: 'row'
|
flexDirection: 'row'
|
||||||
},
|
},
|
||||||
contentSelected: {
|
contentSelected: {
|
||||||
flexBasis: '80%',
|
flexShrink: 1,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center'
|
alignItems: 'center',
|
||||||
|
paddingRight: StyleConstants.Spacing.S
|
||||||
},
|
},
|
||||||
contentUnselected: {
|
contentUnselected: {
|
||||||
flexBasis: '90%'
|
flexShrink: 1
|
||||||
},
|
},
|
||||||
voted: {
|
voted: {
|
||||||
marginLeft: StyleConstants.Spacing.S
|
marginRight: StyleConstants.Spacing.S
|
||||||
},
|
},
|
||||||
votedNot: {
|
votedNot: {
|
||||||
marginRight: StyleConstants.Spacing.S
|
paddingRight: StyleConstants.Spacing.S
|
||||||
},
|
},
|
||||||
percentage: {
|
percentage: {
|
||||||
fontSize: StyleConstants.Font.Size.M
|
fontSize: StyleConstants.Font.Size.M
|
||||||
},
|
},
|
||||||
background: {
|
background: {
|
||||||
position: 'absolute',
|
height: StyleConstants.Spacing.XS,
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
height: '100%',
|
|
||||||
minWidth: 2,
|
minWidth: 2,
|
||||||
borderTopRightRadius: 6,
|
borderTopRightRadius: 10,
|
||||||
borderBottomRightRadius: 6
|
borderBottomRightRadius: 10,
|
||||||
|
marginTop: StyleConstants.Spacing.XS,
|
||||||
|
marginBottom: StyleConstants.Spacing.S
|
||||||
},
|
},
|
||||||
meta: {
|
meta: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { ButtonRow } from '@components/Button'
|
import Button from '@components/Button'
|
||||||
import ParseContent from '@root/components/ParseContent'
|
import ParseContent from '@root/components/ParseContent'
|
||||||
import { createShimmerPlaceholder } from 'react-native-shimmer-placeholder'
|
import { createShimmerPlaceholder } from 'react-native-shimmer-placeholder'
|
||||||
import { Feather } from '@expo/vector-icons'
|
import { Feather } from '@expo/vector-icons'
|
||||||
|
@ -124,15 +124,7 @@ const Login: React.FC = () => {
|
||||||
}, [instanceDomain])
|
}, [instanceDomain])
|
||||||
|
|
||||||
const instanceInfo = useCallback(
|
const instanceInfo = useCallback(
|
||||||
({
|
({ header, content }: { header: string; content?: string }) => {
|
||||||
header,
|
|
||||||
content,
|
|
||||||
parse
|
|
||||||
}: {
|
|
||||||
header: string
|
|
||||||
content?: string
|
|
||||||
parse?: boolean
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.instanceInfo}>
|
<View style={styles.instanceInfo}>
|
||||||
<Text style={[styles.instanceInfoHeader, { color: theme.primary }]}>
|
<Text style={[styles.instanceInfoHeader, { color: theme.primary }]}>
|
||||||
|
@ -147,15 +139,7 @@ const Login: React.FC = () => {
|
||||||
}
|
}
|
||||||
height={StyleConstants.Font.Size.M}
|
height={StyleConstants.Font.Size.M}
|
||||||
>
|
>
|
||||||
<Text
|
<ParseContent content={content!} size={'M'} numberOfLines={5} />
|
||||||
style={[styles.instanceInfoContent, { color: theme.primary }]}
|
|
||||||
>
|
|
||||||
{parse ? (
|
|
||||||
<ParseContent content={content!} size={'M'} numberOfLines={5} />
|
|
||||||
) : (
|
|
||||||
content
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
</ShimmerPlaceholder>
|
</ShimmerPlaceholder>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
@ -204,18 +188,12 @@ const Login: React.FC = () => {
|
||||||
placeholderTextColor={theme.secondary}
|
placeholderTextColor={theme.secondary}
|
||||||
returnKeyType='go'
|
returnKeyType='go'
|
||||||
/>
|
/>
|
||||||
<ButtonRow
|
<Button
|
||||||
onPress={() => {
|
type='text'
|
||||||
applicationQuery.refetch()
|
content={t('content.login.button')}
|
||||||
}}
|
onPress={() => applicationQuery.refetch()}
|
||||||
{...(instanceQuery.isFetching || applicationQuery.isFetching
|
disabled={!instanceQuery.data?.uri}
|
||||||
? { icon: 'loader' }
|
loading={instanceQuery.isFetching || applicationQuery.isFetching}
|
||||||
: { text: t('content.login.button') as string })}
|
|
||||||
disabled={
|
|
||||||
!instanceQuery.data?.uri ||
|
|
||||||
instanceQuery.isFetching ||
|
|
||||||
applicationQuery.isFetching
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View>
|
<View>
|
||||||
|
@ -225,8 +203,7 @@ const Login: React.FC = () => {
|
||||||
})}
|
})}
|
||||||
{instanceInfo({
|
{instanceInfo({
|
||||||
header: '实例介绍',
|
header: '实例介绍',
|
||||||
content: instanceQuery.data?.short_description,
|
content: instanceQuery.data?.short_description
|
||||||
parse: true
|
|
||||||
})}
|
})}
|
||||||
<View style={styles.instanceStats}>
|
<View style={styles.instanceStats}>
|
||||||
<View style={styles.instanceStat}>
|
<View style={styles.instanceStat}>
|
||||||
|
@ -306,18 +283,15 @@ const styles = StyleSheet.create({
|
||||||
padding: StyleConstants.Spacing.Global.PagePadding
|
padding: StyleConstants.Spacing.Global.PagePadding
|
||||||
},
|
},
|
||||||
inputRow: {
|
inputRow: {
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'row'
|
flexDirection: 'row'
|
||||||
},
|
},
|
||||||
textInput: {
|
textInput: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
borderBottomWidth: 1.25,
|
borderBottomWidth: 1,
|
||||||
paddingTop: StyleConstants.Spacing.S - 1.5,
|
|
||||||
paddingBottom: StyleConstants.Spacing.S - 1.5,
|
|
||||||
paddingLeft: StyleConstants.Spacing.Global.PagePadding,
|
paddingLeft: StyleConstants.Spacing.Global.PagePadding,
|
||||||
paddingRight: StyleConstants.Spacing.Global.PagePadding,
|
paddingRight: StyleConstants.Spacing.Global.PagePadding,
|
||||||
fontSize: StyleConstants.Font.Size.M,
|
fontSize: StyleConstants.Font.Size.M,
|
||||||
marginRight: StyleConstants.Spacing.M
|
marginRight: StyleConstants.Spacing.S
|
||||||
},
|
},
|
||||||
instanceInfo: {
|
instanceInfo: {
|
||||||
marginTop: StyleConstants.Spacing.M,
|
marginTop: StyleConstants.Spacing.M,
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import { useDispatch } from 'react-redux'
|
import { useDispatch } from 'react-redux'
|
||||||
import { resetLocal } from '@utils/slices/instancesSlice'
|
import { resetLocal } from '@utils/slices/instancesSlice'
|
||||||
import MenuButton from '@components/Menu/Button'
|
|
||||||
import { MenuContainer } from '@components/Menu'
|
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useQueryClient } from 'react-query'
|
import { useQueryClient } from 'react-query'
|
||||||
|
import Button from '@root/components/Button'
|
||||||
|
import { Alert } from 'react-native'
|
||||||
|
import { StyleConstants } from '@root/utils/styles/constants'
|
||||||
|
|
||||||
const Logout: React.FC = () => {
|
const Logout: React.FC = () => {
|
||||||
const { t } = useTranslation('meRoot')
|
const { t } = useTranslation('meRoot')
|
||||||
|
@ -14,37 +14,40 @@ const Logout: React.FC = () => {
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
const alertOption = {
|
|
||||||
title: t('content.logout.alert.title'),
|
|
||||||
message: t('content.logout.alert.message'),
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
text: t('content.logout.alert.buttons.logout'),
|
|
||||||
style: 'destructive' as const,
|
|
||||||
onPress: () => {
|
|
||||||
queryClient.clear()
|
|
||||||
dispatch(resetLocal())
|
|
||||||
navigation.navigate('Screen-Public', {
|
|
||||||
screen: 'Screen-Public-Root',
|
|
||||||
params: { publicTab: true }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: t('content.logout.alert.buttons.cancel'),
|
|
||||||
style: 'cancel' as const
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuContainer>
|
<Button
|
||||||
<MenuButton
|
type='text'
|
||||||
text={t('content.logout.button')}
|
content={t('content.logout.button')}
|
||||||
destructive={true}
|
style={{
|
||||||
alertOption={alertOption}
|
marginHorizontal: StyleConstants.Spacing.Global.PagePadding * 2,
|
||||||
/>
|
marginBottom: StyleConstants.Spacing.Global.PagePadding * 2
|
||||||
</MenuContainer>
|
}}
|
||||||
|
destructive
|
||||||
|
onPress={() =>
|
||||||
|
Alert.alert(
|
||||||
|
t('content.logout.alert.title'),
|
||||||
|
t('content.logout.alert.message'),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: t('content.logout.alert.buttons.logout'),
|
||||||
|
style: 'destructive' as const,
|
||||||
|
onPress: () => {
|
||||||
|
queryClient.clear()
|
||||||
|
dispatch(resetLocal())
|
||||||
|
navigation.navigate('Screen-Public', {
|
||||||
|
screen: 'Screen-Public-Root',
|
||||||
|
params: { publicTab: true }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: t('content.logout.alert.buttons.cancel'),
|
||||||
|
style: 'cancel' as const
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next'
|
||||||
import Emojis from '@components/Timelines/Timeline/Shared/Emojis'
|
import Emojis from '@components/Timelines/Timeline/Shared/Emojis'
|
||||||
import { LinearGradient } from 'expo-linear-gradient'
|
import { LinearGradient } from 'expo-linear-gradient'
|
||||||
import { AccountAction } from '../Account'
|
import { AccountAction } from '../Account'
|
||||||
import { ButtonRow } from '@root/components/Button'
|
import Button from '@root/components/Button'
|
||||||
import { useMutation, useQuery, useQueryClient } from 'react-query'
|
import { useMutation, useQuery, useQueryClient } from 'react-query'
|
||||||
import { relationshipFetch } from '@root/utils/fetches/relationshipFetch'
|
import { relationshipFetch } from '@root/utils/fetches/relationshipFetch'
|
||||||
import client from '@root/api/client'
|
import client from '@root/api/client'
|
||||||
|
@ -121,13 +121,9 @@ const AccountInformation: React.FC<Props> = ({
|
||||||
|
|
||||||
const followingButton = useMemo(
|
const followingButton = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<ButtonRow
|
<Button
|
||||||
{...(data
|
type='text'
|
||||||
? status !== 'success' ||
|
content={`${data?.following ? '正在' : ''}关注`}
|
||||||
(mutateStatus !== 'success' && mutateStatus !== 'idle')
|
|
||||||
? { icon: 'loader' }
|
|
||||||
: { text: `${data.following ? '正在' : ''}关注` }
|
|
||||||
: { icon: 'loader' })}
|
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
mutate({
|
mutate({
|
||||||
type: 'follow',
|
type: 'follow',
|
||||||
|
@ -136,7 +132,7 @@ const AccountInformation: React.FC<Props> = ({
|
||||||
prevState: data!.following
|
prevState: data!.following
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
disabled={
|
loading={
|
||||||
status !== 'success' ||
|
status !== 'success' ||
|
||||||
(mutateStatus !== 'success' && mutateStatus !== 'idle')
|
(mutateStatus !== 'success' && mutateStatus !== 'idle')
|
||||||
}
|
}
|
||||||
|
@ -175,8 +171,10 @@ const AccountInformation: React.FC<Props> = ({
|
||||||
</ShimmerPlaceholder>
|
</ShimmerPlaceholder>
|
||||||
{!disableActions && (
|
{!disableActions && (
|
||||||
<View style={styles.actions}>
|
<View style={styles.actions}>
|
||||||
<ButtonRow
|
<Button
|
||||||
icon='mail'
|
type='icon'
|
||||||
|
content='mail'
|
||||||
|
round
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
navigation.navigate(getCurrentTab(navigation), {
|
navigation.navigate(getCurrentTab(navigation), {
|
||||||
screen: 'Screen-Shared-Compose',
|
screen: 'Screen-Shared-Compose',
|
||||||
|
@ -294,7 +292,8 @@ const AccountInformation: React.FC<Props> = ({
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{account?.note && (
|
{account?.note && account.note.length && account.note !== '<p></p>' ? (
|
||||||
|
// Empty notes might generate empty p tag
|
||||||
<View style={styles.note}>
|
<View style={styles.note}>
|
||||||
<ParseContent
|
<ParseContent
|
||||||
content={account.note}
|
content={account.note}
|
||||||
|
@ -302,7 +301,7 @@ const AccountInformation: React.FC<Props> = ({
|
||||||
emojis={account.emojis}
|
emojis={account.emojis}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)}
|
) : null}
|
||||||
|
|
||||||
<ShimmerPlaceholder
|
<ShimmerPlaceholder
|
||||||
ref={shimmerCreatedRef}
|
ref={shimmerCreatedRef}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'
|
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'
|
||||||
import client from '@root/api/client'
|
import client from '@root/api/client'
|
||||||
import { ButtonRow } from '@root/components/Button'
|
import Button from '@root/components/Button'
|
||||||
import ParseContent from '@root/components/ParseContent'
|
import ParseContent from '@root/components/ParseContent'
|
||||||
import { announcementFetch } from '@root/utils/fetches/announcementsFetch'
|
import { announcementFetch } from '@root/utils/fetches/announcementsFetch'
|
||||||
import relativeTime from '@root/utils/relativeTime'
|
import relativeTime from '@root/utils/relativeTime'
|
||||||
|
@ -147,8 +147,9 @@ const ScreenSharedAnnouncements: React.FC = ({
|
||||||
</Pressable> */}
|
</Pressable> */}
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
<ButtonRow
|
<Button
|
||||||
text='标记已读'
|
type='text'
|
||||||
|
content='标记已读'
|
||||||
onPress={() => mutate({ type: 'dismiss', announcementId: item.id })}
|
onPress={() => mutate({ type: 'dismiss', announcementId: item.id })}
|
||||||
style={styles.button}
|
style={styles.button}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import { createShimmerPlaceholder } from 'react-native-shimmer-placeholder'
|
import { createShimmerPlaceholder } from 'react-native-shimmer-placeholder'
|
||||||
import { ButtonRound } from '@components/Button'
|
import Button from '@components/Button'
|
||||||
import addAttachments from '@screens/Shared/Compose/addAttachments'
|
import addAttachments from '@screens/Shared/Compose/addAttachments'
|
||||||
import { Feather } from '@expo/vector-icons'
|
import { Feather } from '@expo/vector-icons'
|
||||||
import { LinearGradient } from 'expo-linear-gradient'
|
import { LinearGradient } from 'expo-linear-gradient'
|
||||||
|
@ -67,8 +67,13 @@ const ComposeAttachments: React.FC = () => {
|
||||||
{(item as Mastodon.AttachmentVideo).meta?.original.duration}
|
{(item as Mastodon.AttachmentVideo).meta?.original.duration}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
<ButtonRound
|
<Button
|
||||||
icon='x'
|
type='icon'
|
||||||
|
content='x'
|
||||||
|
spacing='M'
|
||||||
|
round
|
||||||
|
overlay
|
||||||
|
style={styles.delete}
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
composeDispatch({
|
composeDispatch({
|
||||||
type: 'attachments',
|
type: 'attachments',
|
||||||
|
@ -79,17 +84,20 @@ const ComposeAttachments: React.FC = () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
styles={styles.delete}
|
|
||||||
/>
|
/>
|
||||||
<ButtonRound
|
<Button
|
||||||
icon='edit'
|
type='icon'
|
||||||
|
content='edit'
|
||||||
|
spacing='M'
|
||||||
|
round
|
||||||
|
overlay
|
||||||
|
style={styles.edit}
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
navigation.navigate('Screen-Shared-Compose-EditAttachment', {
|
navigation.navigate('Screen-Shared-Compose-EditAttachment', {
|
||||||
attachment: item,
|
attachment: item,
|
||||||
composeDispatch
|
composeDispatch
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
styles={styles.edit}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
@ -123,22 +131,28 @@ const ComposeAttachments: React.FC = () => {
|
||||||
await addAttachments({ composeState, composeDispatch })
|
await addAttachments({ composeState, composeDispatch })
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ButtonRound
|
<Button
|
||||||
icon='upload-cloud'
|
type='icon'
|
||||||
|
content='upload-cloud'
|
||||||
|
spacing='M'
|
||||||
|
round
|
||||||
|
overlay
|
||||||
onPress={async () =>
|
onPress={async () =>
|
||||||
await addAttachments({ composeState, composeDispatch })
|
await addAttachments({ composeState, composeDispatch })
|
||||||
}
|
}
|
||||||
styles={{
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
top:
|
top:
|
||||||
(DEFAULT_HEIGHT -
|
(DEFAULT_HEIGHT -
|
||||||
StyleConstants.Spacing.Global.PagePadding) /
|
StyleConstants.Spacing.M * 2 -
|
||||||
|
StyleConstants.Font.Size.M) /
|
||||||
2,
|
2,
|
||||||
left:
|
left:
|
||||||
(DEFAULT_HEIGHT -
|
(DEFAULT_HEIGHT -
|
||||||
StyleConstants.Spacing.Global.PagePadding) /
|
StyleConstants.Spacing.M * 2 -
|
||||||
|
StyleConstants.Font.Size.M) /
|
||||||
2
|
2
|
||||||
}}
|
}}
|
||||||
coordinate='center'
|
|
||||||
/>
|
/>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
)}
|
)}
|
||||||
|
@ -217,13 +231,15 @@ const styles = StyleSheet.create({
|
||||||
paddingTop: StyleConstants.Spacing.XS,
|
paddingTop: StyleConstants.Spacing.XS,
|
||||||
paddingBottom: StyleConstants.Spacing.XS
|
paddingBottom: StyleConstants.Spacing.XS
|
||||||
},
|
},
|
||||||
edit: {
|
delete: {
|
||||||
bottom:
|
position: 'absolute',
|
||||||
StyleConstants.Spacing.Global.PagePadding + StyleConstants.Spacing.S,
|
top: StyleConstants.Spacing.Global.PagePadding + StyleConstants.Spacing.S,
|
||||||
right: StyleConstants.Spacing.S
|
right: StyleConstants.Spacing.S
|
||||||
},
|
},
|
||||||
delete: {
|
edit: {
|
||||||
top: StyleConstants.Spacing.Global.PagePadding + StyleConstants.Spacing.S,
|
position: 'absolute',
|
||||||
|
bottom:
|
||||||
|
StyleConstants.Spacing.Global.PagePadding + StyleConstants.Spacing.S,
|
||||||
right: StyleConstants.Spacing.S
|
right: StyleConstants.Spacing.S
|
||||||
},
|
},
|
||||||
progressContainer: {
|
progressContainer: {
|
||||||
|
|
|
@ -5,8 +5,8 @@ import { Feather } from '@expo/vector-icons'
|
||||||
import { ComposeContext } from '@screens/Shared/Compose'
|
import { ComposeContext } from '@screens/Shared/Compose'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { ButtonRow } from '@components/Button'
|
import Button from '@components/Button'
|
||||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
import { MenuRow } from '@components/Menu'
|
||||||
|
|
||||||
const ComposePoll: React.FC = () => {
|
const ComposePoll: React.FC = () => {
|
||||||
const {
|
const {
|
||||||
|
@ -80,8 +80,7 @@ const ComposePoll: React.FC = () => {
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.controlAmount}>
|
<View style={styles.controlAmount}>
|
||||||
<View style={styles.firstButton}>
|
<View style={styles.firstButton}>
|
||||||
<ButtonRow
|
<Button
|
||||||
key={total + 'minus'}
|
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
total > 2 &&
|
total > 2 &&
|
||||||
composeDispatch({
|
composeDispatch({
|
||||||
|
@ -89,13 +88,13 @@ const ComposePoll: React.FC = () => {
|
||||||
payload: { total: total - 1 }
|
payload: { total: total - 1 }
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
icon='minus'
|
type='icon'
|
||||||
|
content='minus'
|
||||||
|
round
|
||||||
disabled={!(total > 2)}
|
disabled={!(total > 2)}
|
||||||
buttonSize='S'
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<ButtonRow
|
<Button
|
||||||
key={total + 'plus'}
|
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
total < 4 &&
|
total < 4 &&
|
||||||
composeDispatch({
|
composeDispatch({
|
||||||
|
@ -103,51 +102,50 @@ const ComposePoll: React.FC = () => {
|
||||||
payload: { total: total + 1 }
|
payload: { total: total + 1 }
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
icon='plus'
|
type='icon'
|
||||||
|
content='plus'
|
||||||
|
round
|
||||||
disabled={!(total < 4)}
|
disabled={!(total < 4)}
|
||||||
buttonSize='S'
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<MenuContainer>
|
<MenuRow
|
||||||
<MenuRow
|
title='可选项'
|
||||||
title='可选项'
|
content={multiple ? '多选' : '单选'}
|
||||||
content={multiple ? '多选' : '单选'}
|
onPress={() =>
|
||||||
onPress={() =>
|
ActionSheetIOS.showActionSheetWithOptions(
|
||||||
ActionSheetIOS.showActionSheetWithOptions(
|
{
|
||||||
{
|
options: ['单选', '多选', '取消'],
|
||||||
options: ['单选', '多选', '取消'],
|
cancelButtonIndex: 2
|
||||||
cancelButtonIndex: 2
|
},
|
||||||
},
|
index =>
|
||||||
index =>
|
index < 2 &&
|
||||||
index < 2 &&
|
composeDispatch({
|
||||||
composeDispatch({
|
type: 'poll',
|
||||||
type: 'poll',
|
payload: { multiple: index === 1 }
|
||||||
payload: { multiple: index === 1 }
|
})
|
||||||
})
|
)
|
||||||
)
|
}
|
||||||
}
|
iconBack='chevron-right'
|
||||||
iconBack='chevron-right'
|
/>
|
||||||
/>
|
<MenuRow
|
||||||
<MenuRow
|
title='有效期'
|
||||||
title='有效期'
|
content={expireMapping[expire]}
|
||||||
content={expireMapping[expire]}
|
onPress={() =>
|
||||||
onPress={() =>
|
ActionSheetIOS.showActionSheetWithOptions(
|
||||||
ActionSheetIOS.showActionSheetWithOptions(
|
{
|
||||||
{
|
options: [...Object.values(expireMapping), '取消'],
|
||||||
options: [...Object.values(expireMapping), '取消'],
|
cancelButtonIndex: 7
|
||||||
cancelButtonIndex: 7
|
},
|
||||||
},
|
index =>
|
||||||
index =>
|
index < 7 &&
|
||||||
index < 7 &&
|
composeDispatch({
|
||||||
composeDispatch({
|
type: 'poll',
|
||||||
type: 'poll',
|
payload: { expire: Object.keys(expireMapping)[index] }
|
||||||
payload: { expire: Object.keys(expireMapping)[index] }
|
})
|
||||||
})
|
)
|
||||||
)
|
}
|
||||||
}
|
iconBack='chevron-right'
|
||||||
iconBack='chevron-right'
|
/>
|
||||||
/>
|
|
||||||
</MenuContainer>
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -181,8 +179,7 @@ const styles = StyleSheet.create({
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'flex-end',
|
justifyContent: 'flex-end',
|
||||||
marginRight: StyleConstants.Spacing.M,
|
marginRight: StyleConstants.Spacing.M
|
||||||
marginBottom: StyleConstants.Spacing.M
|
|
||||||
},
|
},
|
||||||
firstButton: {
|
firstButton: {
|
||||||
marginRight: StyleConstants.Spacing.S
|
marginRight: StyleConstants.Spacing.S
|
||||||
|
|
|
@ -12,7 +12,6 @@ export type ColorDefinitions =
|
||||||
| 'backgroundGradientEnd'
|
| 'backgroundGradientEnd'
|
||||||
| 'backgroundOverlay'
|
| 'backgroundOverlay'
|
||||||
| 'border'
|
| 'border'
|
||||||
| 'separator'
|
|
||||||
|
|
||||||
const themeColors: {
|
const themeColors: {
|
||||||
[key in ColorDefinitions]: {
|
[key in ColorDefinitions]: {
|
||||||
|
@ -62,12 +61,8 @@ const themeColors: {
|
||||||
dark: 'rgba(0, 0, 0, 0.5)'
|
dark: 'rgba(0, 0, 0, 0.5)'
|
||||||
},
|
},
|
||||||
border: {
|
border: {
|
||||||
light: 'rgba(0, 0, 0, 0.3)',
|
light: 'rgba(18, 18, 18, 0.3)',
|
||||||
dark: 'rgba(255, 255, 255, 0.16)'
|
dark: 'rgba(255, 255, 255, 0.3)'
|
||||||
},
|
|
||||||
separator: {
|
|
||||||
light: 'rgba(0, 0, 0, 0.1)',
|
|
||||||
dark: 'rgba(255, 255, 255, 0.1)'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5389,6 +5389,11 @@ react-is@^17.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339"
|
||||||
integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==
|
integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==
|
||||||
|
|
||||||
|
react-native-animated-spinkit@^1.4.2:
|
||||||
|
version "1.4.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-native-animated-spinkit/-/react-native-animated-spinkit-1.4.2.tgz#cb60ff8bcc2bb848409d9aa85ed528646ad1f953"
|
||||||
|
integrity sha512-CLFkynkqRueQK/XUknleiYQr3T0E8cY0KbuaV78JGiHhnF12C3k0ngDAOk6FHssZn3JXnTOVkJsrIBOYkttRmA==
|
||||||
|
|
||||||
react-native-expo-image-cache@^4.1.0:
|
react-native-expo-image-cache@^4.1.0:
|
||||||
version "4.1.0"
|
version "4.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-native-expo-image-cache/-/react-native-expo-image-cache-4.1.0.tgz#649cbe9786249134d3eafed5baba50bbfa80c029"
|
resolved "https://registry.yarnpkg.com/react-native-expo-image-cache/-/react-native-expo-image-cache-4.1.0.tgz#649cbe9786249134d3eafed5baba50bbfa80c029"
|
||||||
|
|
Loading…
Reference in New Issue