Rewrite all buttons

This commit is contained in:
Zhiyuan Zheng 2020-12-26 23:05:17 +01:00
parent 3ea88d025b
commit da79674548
No known key found for this signature in database
GPG Key ID: 078A93AB607D85E0
25 changed files with 497 additions and 583 deletions

View File

@ -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",

View File

@ -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
} }
}) })

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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({

View File

@ -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
} }
}) })

View File

@ -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,

View File

@ -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

View File

@ -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':

View File

@ -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
} }
}) })

View File

@ -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
} }

View File

@ -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,

View File

@ -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!)}
/> />

View File

@ -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>
</> </>

View File

@ -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,

View File

@ -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,

View File

@ -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
}
]
)
}
/>
) )
} }

View File

@ -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}

View File

@ -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}
/> />

View File

@ -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: {

View File

@ -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

View File

@ -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)'
} }
} }

View File

@ -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"