1
0
mirror of https://github.com/tooot-app/app synced 2025-06-05 22:19:13 +02:00
This commit is contained in:
Zhiyuan Zheng
2021-01-14 22:53:01 +01:00
parent 95f500ae72
commit d39bc82909
22 changed files with 300 additions and 163 deletions

View File

@ -1,10 +1,14 @@
import React from 'react'
import React, { useRef } from 'react'
import { Dimensions, Modal, StyleSheet, View } from 'react-native'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { useTheme } from '@utils/styles/ThemeManager'
import { StyleConstants } from '@utils/styles/constants'
import Button from '@components/Button'
import { PanGestureHandler } from 'react-native-gesture-handler'
import {
PanGestureHandler,
State,
TapGestureHandler
} from 'react-native-gesture-handler'
import Animated, {
Extrapolate,
interpolate,
@ -55,33 +59,44 @@ const BottomSheet: React.FC<Props> = ({ children, visible, handleDismiss }) => {
return (
<Modal animated animationType='fade' visible={visible} transparent>
<PanGestureHandler onGestureEvent={onGestureEvent}>
<TapGestureHandler
onHandlerStateChange={({ nativeEvent }) => {
if (nativeEvent.state === State.ACTIVE) {
callDismiss()
}
}}
>
<Animated.View
style={[styles.overlay, { backgroundColor: theme.backgroundOverlay }]}
>
<Animated.View
style={[
styles.container,
styleTop,
{
backgroundColor: theme.background,
paddingBottom: insets.bottom || StyleConstants.Spacing.L
}
]}
>
<View
style={[styles.handle, { backgroundColor: theme.primaryOverlay }]}
/>
{children}
<Button
type='text'
content='取消'
onPress={() => handleDismiss()}
style={styles.button}
/>
</Animated.View>
<PanGestureHandler onGestureEvent={onGestureEvent}>
<Animated.View
style={[
styles.container,
styleTop,
{
backgroundColor: theme.background,
paddingBottom: insets.bottom || StyleConstants.Spacing.L
}
]}
>
<View
style={[
styles.handle,
{ backgroundColor: theme.primaryOverlay }
]}
/>
{children}
<Button
type='text'
content='取消'
onPress={() => handleDismiss()}
style={styles.button}
/>
</Animated.View>
</PanGestureHandler>
</Animated.View>
</PanGestureHandler>
</TapGestureHandler>
</Modal>
)
}

View File

@ -1,7 +1,8 @@
import Icon from '@components/Icon'
import { StyleConstants } from '@utils/styles/constants'
import layoutAnimation from '@utils/styles/layoutAnimation'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useMemo } from 'react'
import React, { useEffect, useMemo, useRef } from 'react'
import { Pressable, StyleSheet, Text, View } from 'react-native'
import { Chase } from 'react-native-animated-spinkit'
@ -24,6 +25,15 @@ const HeaderRight: React.FC<Props> = ({
}) => {
const { theme } = useTheme()
const mounted = useRef(false)
useEffect(() => {
if (mounted.current) {
layoutAnimation()
} else {
mounted.current = true
}
}, [content, loading, disabled])
const loadingSpinkit = useMemo(
() => (
<View style={{ position: 'absolute' }}>

View File

@ -3,8 +3,9 @@ import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import { ColorDefinitions } from '@utils/styles/themes'
import React, { useMemo } from 'react'
import { Pressable, StyleSheet, Switch, Text, View } from 'react-native'
import { StyleSheet, Switch, Text, View } from 'react-native'
import { Chase } from 'react-native-animated-spinkit'
import { State, TapGestureHandler } from 'react-native-gesture-handler'
export interface Props {
iconFront?: any
@ -54,81 +55,86 @@ const MenuRow: React.FC<Props> = ({
)
return (
<Pressable
style={styles.base}
onPress={onPress}
disabled={loading}
testID='base'
>
<View style={styles.core}>
<View style={styles.front}>
{iconFront && (
<Icon
name={iconFront}
size={StyleConstants.Font.Size.L}
color={theme[iconFrontColor]}
style={styles.iconFront}
/>
)}
<View style={styles.main}>
<Text
style={[styles.title, { color: theme.primary }]}
numberOfLines={1}
>
{title}
</Text>
{description ? (
<Text style={[styles.description, { color: theme.secondary }]}>
{description}
</Text>
) : null}
</View>
</View>
{(content && content.length) ||
switchValue !== undefined ||
iconBack ? (
<View style={styles.back}>
{content && content.length ? (
<>
<Text
style={[
styles.content,
{
color: theme.secondary,
opacity: !iconBack && loading ? 0 : 1
}
]}
numberOfLines={1}
>
{content}
</Text>
{loading && !iconBack && loadingSpinkit}
</>
) : null}
{switchValue !== undefined ? (
<Switch
value={switchValue}
onValueChange={switchOnValueChange}
disabled={switchDisabled}
trackColor={{ true: theme.blue, false: theme.disabled }}
<View style={styles.base}>
<TapGestureHandler
onHandlerStateChange={({ nativeEvent }) => {
if (nativeEvent.state === State.ACTIVE) {
if (!loading) {
onPress && onPress()
}
}
}}
>
<View style={styles.core}>
<View style={styles.front}>
{iconFront && (
<Icon
name={iconFront}
size={StyleConstants.Font.Size.L}
color={theme[iconFrontColor]}
style={styles.iconFront}
/>
) : null}
{iconBack ? (
<>
<Icon
name={iconBack}
size={StyleConstants.Font.Size.L}
color={theme[iconBackColor]}
style={[styles.iconBack, { opacity: loading ? 0 : 1 }]}
/>
{loading && loadingSpinkit}
</>
) : null}
)}
<View style={styles.main}>
<Text
style={[styles.title, { color: theme.primary }]}
numberOfLines={1}
>
{title}
</Text>
{description ? (
<Text style={[styles.description, { color: theme.secondary }]}>
{description}
</Text>
) : null}
</View>
</View>
) : null}
</View>
</Pressable>
{(content && content.length) ||
switchValue !== undefined ||
iconBack ? (
<View style={styles.back}>
{content && content.length ? (
<>
<Text
style={[
styles.content,
{
color: theme.secondary,
opacity: !iconBack && loading ? 0 : 1
}
]}
numberOfLines={1}
>
{content}
</Text>
{loading && !iconBack && loadingSpinkit}
</>
) : null}
{switchValue !== undefined ? (
<Switch
value={switchValue}
onValueChange={switchOnValueChange}
disabled={switchDisabled}
trackColor={{ true: theme.blue, false: theme.disabled }}
/>
) : null}
{iconBack ? (
<>
<Icon
name={iconBack}
size={StyleConstants.Font.Size.L}
color={theme[iconBackColor]}
style={[styles.iconBack, { opacity: loading ? 0 : 1 }]}
/>
{loading && loadingSpinkit}
</>
) : null}
</View>
) : null}
</View>
</TapGestureHandler>
</View>
)
}

View File

@ -9,6 +9,7 @@ import React, { useCallback, useMemo, useState } from 'react'
import { Dimensions, Platform, StyleSheet, View } from 'react-native'
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
import { TabView } from 'react-native-tab-view'
import ViewPagerAdapter from 'react-native-tab-view-viewpager-adapter'
import { useSelector } from 'react-redux'
const Stack = createNativeStackNavigator<
@ -53,21 +54,6 @@ const Timelines: React.FC<Props> = ({ name, content }) => {
[localActiveIndex]
)
const screenComponent = useCallback(
() => (
<TabView
lazy
swipeEnabled
renderScene={renderScene}
renderTabBar={() => null}
onIndexChange={index => setSegment(index)}
navigationState={{ index: segment, routes }}
initialLayout={{ width: Dimensions.get('window').width }}
/>
),
[segment, localActiveIndex]
)
const screenOptions = useMemo(() => {
if (localActiveIndex === null) {
if (name === 'Public') {
@ -102,6 +88,8 @@ const Timelines: React.FC<Props> = ({ name, content }) => {
}
}, [localActiveIndex, mode, segment])
const renderPager = useCallback(props => <ViewPagerAdapter {...props} />, [])
return (
<Stack.Navigator
screenOptions={{ headerHideShadow: true, headerTopInsetEnabled: false }}
@ -109,9 +97,21 @@ const Timelines: React.FC<Props> = ({ name, content }) => {
<Stack.Screen
// @ts-ignore
name={`Screen-${name}-Root`}
component={screenComponent}
options={screenOptions}
/>
>
{() => (
<TabView
lazy
swipeEnabled
renderPager={renderPager}
renderScene={renderScene}
renderTabBar={() => null}
onIndexChange={index => setSegment(index)}
navigationState={{ index: segment, routes }}
initialLayout={{ width: Dimensions.get('window').width }}
/>
)}
</Stack.Screen>
{sharedScreens(Stack)}
</Stack.Navigator>

View File

@ -14,9 +14,7 @@ import { FlatList } from 'react-native-gesture-handler'
import { useDispatch } from 'react-redux'
import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline'
import { findIndex } from 'lodash'
import CustomRefreshControl from '@components/CustomRefreshControl'
import { InfiniteData, useQueryClient } from 'react-query'
import { useTheme } from '@utils/styles/ThemeManager'
export interface Props {
page: App.Pages
@ -37,8 +35,6 @@ const Timeline: React.FC<Props> = ({
disableRefresh = false,
disableInfinity = false
}) => {
const { theme } = useTheme()
const queryKeyParams = {
page,
...(hashtag && { hashtag }),
@ -212,6 +208,7 @@ const Timeline: React.FC<Props> = ({
return (
<FlatList
bounces={!disableRefresh}
ref={flRef}
windowSize={11}
data={flattenData}

View File

@ -103,7 +103,8 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
() =>
navigation.navigate('Screen-Shared-Compose', {
type: 'reply',
incomingStatus: status
incomingStatus: status,
queryKey
}),
[]
)

View File

@ -10,6 +10,32 @@ export interface Props {
card: Mastodon.Card
}
type CancelPromise = ((reason?: Error) => void) | undefined
type ImageSize = { width: number; height: number }
interface ImageSizeOperation {
start: () => Promise<ImageSize>
cancel: CancelPromise
}
const getImageSize = (uri: string): ImageSizeOperation => {
let cancel: CancelPromise
const start = (): Promise<ImageSize> =>
new Promise<{ width: number; height: number }>((resolve, reject) => {
cancel = reject
Image.getSize(
uri,
(width, height) => {
cancel = undefined
resolve({ width, height })
},
error => {
reject(error)
}
)
})
return { start, cancel }
}
const TimelineCard: React.FC<Props> = ({ card }) => {
const { theme } = useTheme()
@ -19,6 +45,26 @@ const TimelineCard: React.FC<Props> = ({ card }) => {
Image.getSize(card.image, () => setImageLoaded(true))
}
}, [])
useEffect(() => {
let cancel: CancelPromise
const sideEffect = async (): Promise<void> => {
try {
const operation = getImageSize(card.image)
cancel = operation.cancel
await operation.start()
} catch (error) {
if (__DEV__) console.warn(error)
}
}
if (card.image) {
sideEffect()
}
return () => {
if (cancel) {
cancel()
}
}
})
const cardVisual = useMemo(() => {
if (imageLoaded) {
return <Image source={{ uri: card.image }} style={styles.image} />

View File

@ -96,7 +96,8 @@ const HeaderActionsStatus: React.FC<Props> = ({
if (res.id) {
navigation.navigate('Screen-Shared-Compose', {
type: 'edit',
incomingStatus: res
incomingStatus: res,
queryKey
})
}
}