mirror of
https://github.com/tooot-app/app
synced 2025-06-05 22:19:13 +02:00
Updates
This commit is contained in:
parent
95f500ae72
commit
d39bc82909
@ -14,6 +14,7 @@
|
|||||||
"@react-native-community/masked-view": "0.1.10",
|
"@react-native-community/masked-view": "0.1.10",
|
||||||
"@react-native-community/netinfo": "^5.9.7",
|
"@react-native-community/netinfo": "^5.9.7",
|
||||||
"@react-native-community/segmented-control": "2.2.1",
|
"@react-native-community/segmented-control": "2.2.1",
|
||||||
|
"@react-native-community/viewpager": "4.2.0",
|
||||||
"@react-navigation/bottom-tabs": "^5.11.2",
|
"@react-navigation/bottom-tabs": "^5.11.2",
|
||||||
"@react-navigation/native": "^5.8.10",
|
"@react-navigation/native": "^5.8.10",
|
||||||
"@reduxjs/toolkit": "^1.5.0",
|
"@reduxjs/toolkit": "^1.5.0",
|
||||||
@ -61,6 +62,7 @@
|
|||||||
"react-native-shimmer-placeholder": "^2.0.6",
|
"react-native-shimmer-placeholder": "^2.0.6",
|
||||||
"react-native-svg": "12.1.0",
|
"react-native-svg": "12.1.0",
|
||||||
"react-native-tab-view": "^2.15.2",
|
"react-native-tab-view": "^2.15.2",
|
||||||
|
"react-native-tab-view-viewpager-adapter": "^1.1.0",
|
||||||
"react-native-toast-message": "^1.4.2",
|
"react-native-toast-message": "^1.4.2",
|
||||||
"react-navigation": "^4.4.3",
|
"react-navigation": "^4.4.3",
|
||||||
"react-query": "^3.5.6",
|
"react-query": "^3.5.6",
|
||||||
|
12
src/@types/react-navigation.d.ts
vendored
12
src/@types/react-navigation.d.ts
vendored
@ -14,8 +14,18 @@ declare namespace Nav {
|
|||||||
'Screen-Shared-Announcements': { showAll?: boolean }
|
'Screen-Shared-Announcements': { showAll?: boolean }
|
||||||
'Screen-Shared-Compose':
|
'Screen-Shared-Compose':
|
||||||
| {
|
| {
|
||||||
type?: 'reply' | 'conversation' | 'edit'
|
type: 'reply' | 'conversation' | 'edit'
|
||||||
incomingStatus: Mastodon.Status
|
incomingStatus: Mastodon.Status
|
||||||
|
queryKey?: [
|
||||||
|
'Timeline',
|
||||||
|
{
|
||||||
|
page: App.Pages
|
||||||
|
hashtag?: Mastodon.Tag['name']
|
||||||
|
list?: Mastodon.List['id']
|
||||||
|
toot?: Mastodon.Status['id']
|
||||||
|
account?: Mastodon.Account['id']
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
| undefined
|
| undefined
|
||||||
'Screen-Shared-Hashtag': {
|
'Screen-Shared-Hashtag': {
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
import React from 'react'
|
import React, { useRef } from 'react'
|
||||||
import { Dimensions, Modal, StyleSheet, View } from 'react-native'
|
import { Dimensions, Modal, StyleSheet, View } from 'react-native'
|
||||||
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 Button from '@components/Button'
|
import Button from '@components/Button'
|
||||||
import { PanGestureHandler } from 'react-native-gesture-handler'
|
import {
|
||||||
|
PanGestureHandler,
|
||||||
|
State,
|
||||||
|
TapGestureHandler
|
||||||
|
} from 'react-native-gesture-handler'
|
||||||
import Animated, {
|
import Animated, {
|
||||||
Extrapolate,
|
Extrapolate,
|
||||||
interpolate,
|
interpolate,
|
||||||
@ -55,33 +59,44 @@ const BottomSheet: React.FC<Props> = ({ children, visible, handleDismiss }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal animated animationType='fade' visible={visible} transparent>
|
<Modal animated animationType='fade' visible={visible} transparent>
|
||||||
<PanGestureHandler onGestureEvent={onGestureEvent}>
|
<TapGestureHandler
|
||||||
|
onHandlerStateChange={({ nativeEvent }) => {
|
||||||
|
if (nativeEvent.state === State.ACTIVE) {
|
||||||
|
callDismiss()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Animated.View
|
<Animated.View
|
||||||
style={[styles.overlay, { backgroundColor: theme.backgroundOverlay }]}
|
style={[styles.overlay, { backgroundColor: theme.backgroundOverlay }]}
|
||||||
>
|
>
|
||||||
<Animated.View
|
<PanGestureHandler onGestureEvent={onGestureEvent}>
|
||||||
style={[
|
<Animated.View
|
||||||
styles.container,
|
style={[
|
||||||
styleTop,
|
styles.container,
|
||||||
{
|
styleTop,
|
||||||
backgroundColor: theme.background,
|
{
|
||||||
paddingBottom: insets.bottom || StyleConstants.Spacing.L
|
backgroundColor: theme.background,
|
||||||
}
|
paddingBottom: insets.bottom || StyleConstants.Spacing.L
|
||||||
]}
|
}
|
||||||
>
|
]}
|
||||||
<View
|
>
|
||||||
style={[styles.handle, { backgroundColor: theme.primaryOverlay }]}
|
<View
|
||||||
/>
|
style={[
|
||||||
{children}
|
styles.handle,
|
||||||
<Button
|
{ backgroundColor: theme.primaryOverlay }
|
||||||
type='text'
|
]}
|
||||||
content='取消'
|
/>
|
||||||
onPress={() => handleDismiss()}
|
{children}
|
||||||
style={styles.button}
|
<Button
|
||||||
/>
|
type='text'
|
||||||
</Animated.View>
|
content='取消'
|
||||||
|
onPress={() => handleDismiss()}
|
||||||
|
style={styles.button}
|
||||||
|
/>
|
||||||
|
</Animated.View>
|
||||||
|
</PanGestureHandler>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</PanGestureHandler>
|
</TapGestureHandler>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
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 { Pressable, StyleSheet, Text, View } from 'react-native'
|
||||||
import { Chase } from 'react-native-animated-spinkit'
|
import { Chase } from 'react-native-animated-spinkit'
|
||||||
|
|
||||||
@ -24,6 +25,15 @@ const HeaderRight: React.FC<Props> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
|
const mounted = useRef(false)
|
||||||
|
useEffect(() => {
|
||||||
|
if (mounted.current) {
|
||||||
|
layoutAnimation()
|
||||||
|
} else {
|
||||||
|
mounted.current = true
|
||||||
|
}
|
||||||
|
}, [content, loading, disabled])
|
||||||
|
|
||||||
const loadingSpinkit = useMemo(
|
const loadingSpinkit = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<View style={{ position: 'absolute' }}>
|
<View style={{ position: 'absolute' }}>
|
||||||
|
@ -3,8 +3,9 @@ import { StyleConstants } from '@utils/styles/constants'
|
|||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import { ColorDefinitions } from '@utils/styles/themes'
|
import { ColorDefinitions } from '@utils/styles/themes'
|
||||||
import React, { useMemo } from 'react'
|
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 { Chase } from 'react-native-animated-spinkit'
|
||||||
|
import { State, TapGestureHandler } from 'react-native-gesture-handler'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
iconFront?: any
|
iconFront?: any
|
||||||
@ -54,81 +55,86 @@ const MenuRow: React.FC<Props> = ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<View style={styles.base}>
|
||||||
style={styles.base}
|
<TapGestureHandler
|
||||||
onPress={onPress}
|
onHandlerStateChange={({ nativeEvent }) => {
|
||||||
disabled={loading}
|
if (nativeEvent.state === State.ACTIVE) {
|
||||||
testID='base'
|
if (!loading) {
|
||||||
>
|
onPress && onPress()
|
||||||
<View style={styles.core}>
|
}
|
||||||
<View style={styles.front}>
|
}
|
||||||
{iconFront && (
|
}}
|
||||||
<Icon
|
>
|
||||||
name={iconFront}
|
<View style={styles.core}>
|
||||||
size={StyleConstants.Font.Size.L}
|
<View style={styles.front}>
|
||||||
color={theme[iconFrontColor]}
|
{iconFront && (
|
||||||
style={styles.iconFront}
|
<Icon
|
||||||
/>
|
name={iconFront}
|
||||||
)}
|
size={StyleConstants.Font.Size.L}
|
||||||
<View style={styles.main}>
|
color={theme[iconFrontColor]}
|
||||||
<Text
|
style={styles.iconFront}
|
||||||
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 }}
|
|
||||||
/>
|
/>
|
||||||
) : null}
|
)}
|
||||||
{iconBack ? (
|
<View style={styles.main}>
|
||||||
<>
|
<Text
|
||||||
<Icon
|
style={[styles.title, { color: theme.primary }]}
|
||||||
name={iconBack}
|
numberOfLines={1}
|
||||||
size={StyleConstants.Font.Size.L}
|
>
|
||||||
color={theme[iconBackColor]}
|
{title}
|
||||||
style={[styles.iconBack, { opacity: loading ? 0 : 1 }]}
|
</Text>
|
||||||
/>
|
{description ? (
|
||||||
{loading && loadingSpinkit}
|
<Text style={[styles.description, { color: theme.secondary }]}>
|
||||||
</>
|
{description}
|
||||||
) : null}
|
</Text>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
|
||||||
</View>
|
{(content && content.length) ||
|
||||||
</Pressable>
|
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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import React, { useCallback, useMemo, useState } from 'react'
|
|||||||
import { Dimensions, Platform, StyleSheet, View } from 'react-native'
|
import { Dimensions, Platform, StyleSheet, View } from 'react-native'
|
||||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||||
import { TabView } from 'react-native-tab-view'
|
import { TabView } from 'react-native-tab-view'
|
||||||
|
import ViewPagerAdapter from 'react-native-tab-view-viewpager-adapter'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
const Stack = createNativeStackNavigator<
|
const Stack = createNativeStackNavigator<
|
||||||
@ -53,21 +54,6 @@ const Timelines: React.FC<Props> = ({ name, content }) => {
|
|||||||
[localActiveIndex]
|
[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(() => {
|
const screenOptions = useMemo(() => {
|
||||||
if (localActiveIndex === null) {
|
if (localActiveIndex === null) {
|
||||||
if (name === 'Public') {
|
if (name === 'Public') {
|
||||||
@ -102,6 +88,8 @@ const Timelines: React.FC<Props> = ({ name, content }) => {
|
|||||||
}
|
}
|
||||||
}, [localActiveIndex, mode, segment])
|
}, [localActiveIndex, mode, segment])
|
||||||
|
|
||||||
|
const renderPager = useCallback(props => <ViewPagerAdapter {...props} />, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack.Navigator
|
<Stack.Navigator
|
||||||
screenOptions={{ headerHideShadow: true, headerTopInsetEnabled: false }}
|
screenOptions={{ headerHideShadow: true, headerTopInsetEnabled: false }}
|
||||||
@ -109,9 +97,21 @@ const Timelines: React.FC<Props> = ({ name, content }) => {
|
|||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
name={`Screen-${name}-Root`}
|
name={`Screen-${name}-Root`}
|
||||||
component={screenComponent}
|
|
||||||
options={screenOptions}
|
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)}
|
{sharedScreens(Stack)}
|
||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
|
@ -14,9 +14,7 @@ import { FlatList } from 'react-native-gesture-handler'
|
|||||||
import { useDispatch } from 'react-redux'
|
import { useDispatch } from 'react-redux'
|
||||||
import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline'
|
import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline'
|
||||||
import { findIndex } from 'lodash'
|
import { findIndex } from 'lodash'
|
||||||
import CustomRefreshControl from '@components/CustomRefreshControl'
|
|
||||||
import { InfiniteData, useQueryClient } from 'react-query'
|
import { InfiniteData, useQueryClient } from 'react-query'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
page: App.Pages
|
page: App.Pages
|
||||||
@ -37,8 +35,6 @@ const Timeline: React.FC<Props> = ({
|
|||||||
disableRefresh = false,
|
disableRefresh = false,
|
||||||
disableInfinity = false
|
disableInfinity = false
|
||||||
}) => {
|
}) => {
|
||||||
const { theme } = useTheme()
|
|
||||||
|
|
||||||
const queryKeyParams = {
|
const queryKeyParams = {
|
||||||
page,
|
page,
|
||||||
...(hashtag && { hashtag }),
|
...(hashtag && { hashtag }),
|
||||||
@ -212,6 +208,7 @@ const Timeline: React.FC<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<FlatList
|
<FlatList
|
||||||
|
bounces={!disableRefresh}
|
||||||
ref={flRef}
|
ref={flRef}
|
||||||
windowSize={11}
|
windowSize={11}
|
||||||
data={flattenData}
|
data={flattenData}
|
||||||
|
@ -103,7 +103,8 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
|||||||
() =>
|
() =>
|
||||||
navigation.navigate('Screen-Shared-Compose', {
|
navigation.navigate('Screen-Shared-Compose', {
|
||||||
type: 'reply',
|
type: 'reply',
|
||||||
incomingStatus: status
|
incomingStatus: status,
|
||||||
|
queryKey
|
||||||
}),
|
}),
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
@ -10,6 +10,32 @@ export interface Props {
|
|||||||
card: Mastodon.Card
|
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 TimelineCard: React.FC<Props> = ({ card }) => {
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
@ -19,6 +45,26 @@ const TimelineCard: React.FC<Props> = ({ card }) => {
|
|||||||
Image.getSize(card.image, () => setImageLoaded(true))
|
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(() => {
|
const cardVisual = useMemo(() => {
|
||||||
if (imageLoaded) {
|
if (imageLoaded) {
|
||||||
return <Image source={{ uri: card.image }} style={styles.image} />
|
return <Image source={{ uri: card.image }} style={styles.image} />
|
||||||
|
@ -96,7 +96,8 @@ const HeaderActionsStatus: React.FC<Props> = ({
|
|||||||
if (res.id) {
|
if (res.id) {
|
||||||
navigation.navigate('Screen-Shared-Compose', {
|
navigation.navigate('Screen-Shared-Compose', {
|
||||||
type: 'edit',
|
type: 'edit',
|
||||||
incomingStatus: res
|
incomingStatus: res,
|
||||||
|
queryKey
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,6 @@ const ScreenMeRoot: React.FC<StackScreenProps<
|
|||||||
>> = ({ route: { params }, navigation }) => {
|
>> = ({ route: { params }, navigation }) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (params && params.navigateAway) {
|
if (params && params.navigateAway) {
|
||||||
console.log('oops')
|
|
||||||
navigation.navigate(params.navigateAway)
|
navigation.navigate(params.navigateAway)
|
||||||
}
|
}
|
||||||
}, [params])
|
}, [params])
|
||||||
|
@ -43,8 +43,8 @@ const AccountHeader: React.FC<Props> = ({ account, limitHeight = false }) => {
|
|||||||
}, [account])
|
}, [account])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Animated.Image
|
<Animated.View
|
||||||
source={{ uri: account?.header }}
|
// source={{ uri: account?.header }}
|
||||||
style={[styleHeight, { backgroundColor: theme.disabled }]}
|
style={[styleHeight, { backgroundColor: theme.disabled }]}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import Timeline from '@components/Timelines/Timeline'
|
import Timeline from '@components/Timelines/Timeline'
|
||||||
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'
|
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import React, { useContext } from 'react'
|
import React, { useCallback, useContext } from 'react'
|
||||||
import { Dimensions, StyleSheet } from 'react-native'
|
import { Dimensions, StyleSheet } from 'react-native'
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||||
import { TabView } from 'react-native-tab-view'
|
import { TabView } from 'react-native-tab-view'
|
||||||
|
import ViewPagerAdapter from 'react-native-tab-view-viewpager-adapter'
|
||||||
import AccountContext from './utils/createContext'
|
import AccountContext from './utils/createContext'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@ -32,10 +33,13 @@ const AccountToots: React.FC<Props> = ({ id }) => {
|
|||||||
return <Timeline page={route.key} account={id} disableRefresh />
|
return <Timeline page={route.key} account={id} disableRefresh />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const renderPager = useCallback(props => <ViewPagerAdapter {...props} />, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TabView
|
<TabView
|
||||||
lazy
|
lazy
|
||||||
swipeEnabled
|
swipeEnabled
|
||||||
|
renderPager={renderPager}
|
||||||
renderScene={renderScene}
|
renderScene={renderScene}
|
||||||
renderTabBar={() => null}
|
renderTabBar={() => null}
|
||||||
initialLayout={{ width: Dimensions.get('window').width }}
|
initialLayout={{ width: Dimensions.get('window').width }}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { HeaderLeft, HeaderRight } from '@components/Header'
|
import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||||
import haptics from '@root/components/haptics'
|
import haptics from '@root/components/haptics'
|
||||||
import { store } from '@root/store'
|
import { store } from '@root/store'
|
||||||
import layoutAnimation from '@root/utils/styles/layoutAnimation'
|
|
||||||
import formatText from '@screens/Shared/Compose/formatText'
|
import formatText from '@screens/Shared/Compose/formatText'
|
||||||
import ComposeRoot from '@screens/Shared/Compose/Root'
|
import ComposeRoot from '@screens/Shared/Compose/Root'
|
||||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
@ -58,11 +57,8 @@ const Compose: React.FC<SharedComposeProp> = ({
|
|||||||
const localAccount = getLocalAccount(store.getState())
|
const localAccount = getLocalAccount(store.getState())
|
||||||
const [composeState, composeDispatch] = useReducer(
|
const [composeState, composeDispatch] = useReducer(
|
||||||
composeReducer,
|
composeReducer,
|
||||||
params?.type && params?.incomingStatus
|
params
|
||||||
? composeParseState({
|
? composeParseState(params)
|
||||||
type: params.type,
|
|
||||||
incomingStatus: params.incomingStatus
|
|
||||||
})
|
|
||||||
: {
|
: {
|
||||||
...composeInitialState,
|
...composeInitialState,
|
||||||
visibility:
|
visibility:
|
||||||
@ -72,7 +68,6 @@ const Compose: React.FC<SharedComposeProp> = ({
|
|||||||
: 'public'
|
: 'public'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
switch (params?.type) {
|
switch (params?.type) {
|
||||||
@ -163,8 +158,8 @@ const Compose: React.FC<SharedComposeProp> = ({
|
|||||||
type='text'
|
type='text'
|
||||||
content={params?.type ? postButtonText[params.type] : '发嘟嘟'}
|
content={params?.type ? postButtonText[params.type] : '发嘟嘟'}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
layoutAnimation()
|
composeDispatch({ type: 'posting', payload: true })
|
||||||
setIsSubmitting(true)
|
|
||||||
composePost(params, composeState)
|
composePost(params, composeState)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
haptics('Success')
|
haptics('Success')
|
||||||
@ -173,27 +168,15 @@ const Compose: React.FC<SharedComposeProp> = ({
|
|||||||
{ page: 'Following' }
|
{ page: 'Following' }
|
||||||
]
|
]
|
||||||
queryClient.invalidateQueries(queryKey)
|
queryClient.invalidateQueries(queryKey)
|
||||||
if (
|
|
||||||
params?.type &&
|
if (params?.queryKey && params.queryKey[1].page === 'Toot') {
|
||||||
(params.type === 'reply' || params.type === 'conversation')
|
queryClient.invalidateQueries(params.queryKey)
|
||||||
) {
|
|
||||||
queryClient.invalidateQueries(
|
|
||||||
[
|
|
||||||
'Toot',
|
|
||||||
{
|
|
||||||
toot: params.incomingStatus.reblog
|
|
||||||
? params.incomingStatus.reblog.id
|
|
||||||
: params.incomingStatus.id
|
|
||||||
}
|
|
||||||
],
|
|
||||||
{ exact: true, active: true }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
navigation.goBack()
|
navigation.goBack()
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
haptics('Error')
|
haptics('Error')
|
||||||
setIsSubmitting(false)
|
composeDispatch({ type: 'posting', payload: false })
|
||||||
Alert.alert('发布失败', '', [
|
Alert.alert('发布失败', '', [
|
||||||
{
|
{
|
||||||
text: '返回重试'
|
text: '返回重试'
|
||||||
@ -201,11 +184,11 @@ const Compose: React.FC<SharedComposeProp> = ({
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
loading={isSubmitting}
|
loading={composeState.posting}
|
||||||
disabled={composeState.text.raw.length < 1 || totalTextCount > 500}
|
disabled={composeState.text.raw.length < 1 || totalTextCount > 500}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
[isSubmitting, totalTextCount, composeState]
|
[totalTextCount, composeState]
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
43
src/screens/Shared/Compose/Posting.tsx
Normal file
43
src/screens/Shared/Compose/Posting.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import React, { useContext } from 'react'
|
||||||
|
import { Modal, StyleSheet, View } from 'react-native'
|
||||||
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
|
import ComposeContext from './utils/createContext'
|
||||||
|
import { Chase } from 'react-native-animated-spinkit'
|
||||||
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
|
||||||
|
const ComposePosting = React.memo(
|
||||||
|
() => {
|
||||||
|
const { composeState } = useContext(ComposeContext)
|
||||||
|
const { theme } = useTheme()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
transparent
|
||||||
|
animationType='fade'
|
||||||
|
visible={composeState.posting}
|
||||||
|
children={
|
||||||
|
<View
|
||||||
|
style={[styles.base, { backgroundColor: theme.backgroundOverlay }]}
|
||||||
|
children={
|
||||||
|
<Chase
|
||||||
|
size={StyleConstants.Font.Size.L * 2}
|
||||||
|
color={theme.primaryOverlay}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
() => true
|
||||||
|
)
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
base: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default ComposePosting
|
@ -7,6 +7,7 @@ import React, { useCallback, useContext, useEffect, useMemo } from 'react'
|
|||||||
import { View, FlatList, StyleSheet } from 'react-native'
|
import { View, FlatList, StyleSheet } from 'react-native'
|
||||||
import { Chase } from 'react-native-animated-spinkit'
|
import { Chase } from 'react-native-animated-spinkit'
|
||||||
import ComposeActions from './Actions'
|
import ComposeActions from './Actions'
|
||||||
|
import ComposePosting from './Posting'
|
||||||
import ComposeRootFooter from './Root/Footer'
|
import ComposeRootFooter from './Root/Footer'
|
||||||
import ComposeRootHeader from './Root/Header'
|
import ComposeRootHeader from './Root/Header'
|
||||||
import ComposeRootSuggestion from './Root/Suggestion'
|
import ComposeRootSuggestion from './Root/Suggestion'
|
||||||
@ -89,6 +90,7 @@ const ComposeRoot: React.FC = () => {
|
|||||||
keyExtractor={({ item }) => item.acct || item.name}
|
keyExtractor={({ item }) => item.acct || item.name}
|
||||||
/>
|
/>
|
||||||
<ComposeActions />
|
<ComposeActions />
|
||||||
|
<ComposePosting />
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { createRef } from "react"
|
import { createRef } from 'react'
|
||||||
import { ComposeState } from "./types"
|
import { ComposeState } from './types'
|
||||||
|
|
||||||
const composeInitialState: ComposeState = {
|
const composeInitialState: ComposeState = {
|
||||||
|
posting: false,
|
||||||
spoiler: {
|
spoiler: {
|
||||||
active: false,
|
active: false,
|
||||||
count: 0,
|
count: 0,
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import client from '@root/api/client'
|
import client from '@root/api/client'
|
||||||
import { Props } from '@screens/Shared/Compose'
|
|
||||||
import { ComposeState } from '@screens/Shared/Compose/utils/types'
|
import { ComposeState } from '@screens/Shared/Compose/utils/types'
|
||||||
|
import { SharedComposeProp } from '@screens/Shared/sharedScreens'
|
||||||
import * as Crypto from 'expo-crypto'
|
import * as Crypto from 'expo-crypto'
|
||||||
|
|
||||||
const composePost = async (
|
const composePost = async (
|
||||||
params: Props['route']['params'],
|
params: SharedComposeProp['route']['params'],
|
||||||
composeState: ComposeState
|
composeState: ComposeState
|
||||||
) => {
|
) => {
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
@ -43,7 +43,7 @@ const composePost = async (
|
|||||||
return client<Mastodon.Status>({
|
return client<Mastodon.Status>({
|
||||||
method: 'post',
|
method: 'post',
|
||||||
instance: 'local',
|
instance: 'local',
|
||||||
url: 'statuses',
|
url: 'statusess',
|
||||||
headers: {
|
headers: {
|
||||||
'Idempotency-Key': await Crypto.digestStringAsync(
|
'Idempotency-Key': await Crypto.digestStringAsync(
|
||||||
Crypto.CryptoDigestAlgorithm.SHA256,
|
Crypto.CryptoDigestAlgorithm.SHA256,
|
||||||
|
@ -5,6 +5,8 @@ const composeReducer = (
|
|||||||
action: ComposeAction
|
action: ComposeAction
|
||||||
): ComposeState => {
|
): ComposeState => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
case 'posting':
|
||||||
|
return { ...state, posting: action.payload }
|
||||||
case 'spoiler':
|
case 'spoiler':
|
||||||
return { ...state, spoiler: { ...state.spoiler, ...action.payload } }
|
return { ...state, spoiler: { ...state.spoiler, ...action.payload } }
|
||||||
case 'text':
|
case 'text':
|
||||||
|
5
src/screens/Shared/Compose/utils/types.d.ts
vendored
5
src/screens/Shared/Compose/utils/types.d.ts
vendored
@ -5,6 +5,7 @@ export type ExtendedAttachment = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type ComposeState = {
|
export type ComposeState = {
|
||||||
|
posting: boolean
|
||||||
spoiler: {
|
spoiler: {
|
||||||
active: boolean
|
active: boolean
|
||||||
count: number
|
count: number
|
||||||
@ -62,6 +63,10 @@ export type ComposeState = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type ComposeAction =
|
export type ComposeAction =
|
||||||
|
| {
|
||||||
|
type: 'posting'
|
||||||
|
payload: ComposeState['posting']
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
type: 'spoiler'
|
type: 'spoiler'
|
||||||
payload: Partial<ComposeState['spoiler']>
|
payload: Partial<ComposeState['spoiler']>
|
||||||
|
@ -33,7 +33,7 @@ const ScreenSharedRelationships: React.FC<SharedRelationshipsProp> = ({
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
return updateHeaderRight()
|
return updateHeaderRight()
|
||||||
}, [])
|
}, [segment, mode])
|
||||||
|
|
||||||
const routes: {
|
const routes: {
|
||||||
key: SharedRelationshipsProp['route']['params']['initialType']
|
key: SharedRelationshipsProp['route']['params']['initialType']
|
||||||
@ -55,9 +55,9 @@ const ScreenSharedRelationships: React.FC<SharedRelationshipsProp> = ({
|
|||||||
swipeEnabled
|
swipeEnabled
|
||||||
renderScene={renderScene}
|
renderScene={renderScene}
|
||||||
renderTabBar={() => null}
|
renderTabBar={() => null}
|
||||||
initialLayout={{ width: Dimensions.get('window').width }}
|
|
||||||
navigationState={{ index: segment, routes }}
|
|
||||||
onIndexChange={index => setSegment(index)}
|
onIndexChange={index => setSegment(index)}
|
||||||
|
navigationState={{ index: segment, routes }}
|
||||||
|
initialLayout={{ width: Dimensions.get('window').width }}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
10
yarn.lock
10
yarn.lock
@ -1727,6 +1727,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@react-native-community/segmented-control/-/segmented-control-2.2.1.tgz#5ca418d78c5f6051353c9586918458713b88a83c"
|
resolved "https://registry.yarnpkg.com/@react-native-community/segmented-control/-/segmented-control-2.2.1.tgz#5ca418d78c5f6051353c9586918458713b88a83c"
|
||||||
integrity sha512-BzxFbI9Iqv+31yVqEvCTzJYmwb8jOMTf/UPuC4Hj176tmEPqBpuDaGH+rkAFg1miOco3/43RQxiAZO+mkY40Fg==
|
integrity sha512-BzxFbI9Iqv+31yVqEvCTzJYmwb8jOMTf/UPuC4Hj176tmEPqBpuDaGH+rkAFg1miOco3/43RQxiAZO+mkY40Fg==
|
||||||
|
|
||||||
|
"@react-native-community/viewpager@4.2.0":
|
||||||
|
version "4.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@react-native-community/viewpager/-/viewpager-4.2.0.tgz#11a4453715eb45cb0f732c98666ce168b800dfa4"
|
||||||
|
integrity sha512-tptvkyStulE9Jv/LVYSldvydAq3DVOwsfqmy3mTh3NWl1LZwE4gZwZ455jiRmW5StsJ3Q/Od/GGsN0FI8gHAXQ==
|
||||||
|
|
||||||
"@react-navigation/bottom-tabs@^5.11.2":
|
"@react-navigation/bottom-tabs@^5.11.2":
|
||||||
version "5.11.2"
|
version "5.11.2"
|
||||||
resolved "https://registry.yarnpkg.com/@react-navigation/bottom-tabs/-/bottom-tabs-5.11.2.tgz#5b541612fcecdea2a5024a4028da35e4a727bde6"
|
resolved "https://registry.yarnpkg.com/@react-navigation/bottom-tabs/-/bottom-tabs-5.11.2.tgz#5b541612fcecdea2a5024a4028da35e4a727bde6"
|
||||||
@ -8280,6 +8285,11 @@ react-native-svg@12.1.0:
|
|||||||
css-select "^2.1.0"
|
css-select "^2.1.0"
|
||||||
css-tree "^1.0.0-alpha.39"
|
css-tree "^1.0.0-alpha.39"
|
||||||
|
|
||||||
|
react-native-tab-view-viewpager-adapter@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-native-tab-view-viewpager-adapter/-/react-native-tab-view-viewpager-adapter-1.1.0.tgz#d6e085ed1c91a13e714d87395d428f8afc2b3377"
|
||||||
|
integrity sha512-KLKw+Ay41xAl5F2eRscJd7DHN8JLx06E2lTQJrYAxtBcNKDKfrcYC3wmq7t72DAOBe/6niF0h44c/t/D/AoXsA==
|
||||||
|
|
||||||
react-native-tab-view@^2.15.2:
|
react-native-tab-view@^2.15.2:
|
||||||
version "2.15.2"
|
version "2.15.2"
|
||||||
resolved "https://registry.yarnpkg.com/react-native-tab-view/-/react-native-tab-view-2.15.2.tgz#4bc7832d33a119306614efee667509672a7ee64e"
|
resolved "https://registry.yarnpkg.com/react-native-tab-view/-/react-native-tab-view-2.15.2.tgz#4bc7832d33a119306614efee667509672a7ee64e"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user