mirror of https://github.com/tooot-app/app
Updates
This commit is contained in:
parent
c46888acab
commit
a40a645337
|
@ -20,12 +20,10 @@ jobs:
|
|||
expo-username: ${{ secrets.EXPO_USERNAME }}
|
||||
expo-token: ${{ secrets.EXPO_TOKEN }}
|
||||
- name: -- Step 4 -- Install node dependencies
|
||||
run: yarn install
|
||||
- name: -- Step 5 -- Install native dependencies
|
||||
run: npx pod-install
|
||||
- name: -- Step 6 -- Install ruby dependencies
|
||||
- name: -- Step 5 -- Install ruby dependencies
|
||||
run: bundle install
|
||||
- name: -- Step 7 -- Run fastlane
|
||||
- name: -- Step 6 -- Run fastlane
|
||||
env:
|
||||
TOOOT_ENVIRONMENT: development
|
||||
SENTRY_ORGANIZATION: ${{ secrets.SENTRY_ORGANIZATION }}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "src/modules/react-native-image-viewing"]
|
||||
path = src/modules/react-native-image-viewing
|
||||
url = https://github.com/xmflsct/react-native-image-viewing.git
|
|
@ -54,19 +54,16 @@
|
|||
"react-native-feather": "^1.0.2",
|
||||
"react-native-gesture-handler": "~1.9.0",
|
||||
"react-native-htmlview": "^0.16.0",
|
||||
"react-native-image-zoom-viewer": "^3.0.1",
|
||||
"react-native-reanimated": "^2.0.0-rc.2",
|
||||
"react-native-safe-area-context": "3.1.9",
|
||||
"react-native-screens": "~2.17.1",
|
||||
"react-native-shared-element": "^0.7.0",
|
||||
"react-native-svg": "12.1.0",
|
||||
"react-native-swipe-list-view": "^3.2.6",
|
||||
"react-native-tab-view": "^2.15.2",
|
||||
"react-native-tab-view-viewpager-adapter": "^1.1.0",
|
||||
"react-native-toast-message": "^1.4.3",
|
||||
"react-native-unimodules": "~0.12.0",
|
||||
"react-navigation-shared-element": "^3.0.0",
|
||||
"react-query": "^3.6.0",
|
||||
"react-query": "^3.8.2",
|
||||
"react-redux": "^7.2.2",
|
||||
"react-timeago": "^5.2.0",
|
||||
"reconnecting-websocket": "^4.4.0",
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import client from '@api/client'
|
||||
import { HeaderCenter, HeaderLeft } from '@components/Header'
|
||||
import { toast, toastConfig } from '@components/toast'
|
||||
import {
|
||||
NavigationContainer,
|
||||
|
@ -21,11 +20,11 @@ import * as Analytics from 'expo-firebase-analytics'
|
|||
import React, { createRef, useCallback, useEffect, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Platform, StatusBar } from 'react-native'
|
||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||
import Toast from 'react-native-toast-message'
|
||||
import { createSharedElementStackNavigator } from 'react-navigation-shared-element'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
const Stack = createSharedElementStackNavigator<Nav.RootStackParamList>()
|
||||
const Stack = createNativeStackNavigator<Nav.RootStackParamList>()
|
||||
|
||||
export interface Props {
|
||||
localCorrupt?: string
|
||||
|
@ -133,11 +132,7 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
|
|||
onReady={navigationContainerOnReady}
|
||||
onStateChange={navigationContainerOnStateChange}
|
||||
>
|
||||
<Stack.Navigator
|
||||
mode='modal'
|
||||
initialRouteName='Screen-Tabs'
|
||||
screenOptions={{ cardStyle: { backgroundColor: theme.background } }}
|
||||
>
|
||||
<Stack.Navigator initialRouteName='Screen-Tabs'>
|
||||
<Stack.Screen
|
||||
name='Screen-Tabs'
|
||||
component={ScreenTabs}
|
||||
|
@ -148,80 +143,31 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
|
|||
name='Screen-Actions'
|
||||
component={ScreenActions}
|
||||
options={{
|
||||
headerShown: false,
|
||||
cardStyle: { backgroundColor: 'transparent' },
|
||||
cardStyleInterpolator: ({ current: { progress } }) => ({
|
||||
cardStyle: {
|
||||
opacity: progress.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, 1]
|
||||
})
|
||||
}
|
||||
})
|
||||
stackPresentation: 'transparentModal',
|
||||
stackAnimation: 'fade'
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Screen-Announcements'
|
||||
component={ScreenAnnouncements}
|
||||
options={{
|
||||
gestureEnabled: false,
|
||||
headerTitle: t('sharedAnnouncements:heading'),
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => (
|
||||
<HeaderCenter content={t('sharedAnnouncements:heading')} />
|
||||
)
|
||||
}),
|
||||
headerTransparent: true,
|
||||
headerLeft: () => (
|
||||
<HeaderLeft
|
||||
content='X'
|
||||
native={false}
|
||||
onPress={() => navigationRef.current?.goBack()}
|
||||
/>
|
||||
),
|
||||
animationTypeForReplace: 'pop',
|
||||
cardStyle: { backgroundColor: 'transparent' },
|
||||
cardStyleInterpolator: ({ current: { progress } }) => ({
|
||||
cardStyle: {
|
||||
opacity: progress.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, 1]
|
||||
})
|
||||
}
|
||||
})
|
||||
stackPresentation: 'transparentModal',
|
||||
stackAnimation: 'fade'
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Screen-Compose'
|
||||
component={ScreenCompose}
|
||||
options={{ gestureEnabled: false, headerShown: false }}
|
||||
options={{
|
||||
stackPresentation: 'fullScreenModal'
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Screen-ImagesViewer'
|
||||
component={ScreenImagesViewer}
|
||||
options={{
|
||||
gestureEnabled: false,
|
||||
headerTransparent: true,
|
||||
headerLeft: () => (
|
||||
<HeaderLeft
|
||||
content='X'
|
||||
native={false}
|
||||
onPress={() => navigationRef.current?.goBack()}
|
||||
/>
|
||||
),
|
||||
cardStyle: { backgroundColor: 'transparent' },
|
||||
cardStyleInterpolator: ({ current: { progress } }) => ({
|
||||
cardStyle: {
|
||||
opacity: progress.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, 1]
|
||||
})
|
||||
}
|
||||
})
|
||||
}}
|
||||
sharedElements={route => {
|
||||
const { imageIndex, imageUrls } = route.params
|
||||
return [{ id: `image.${imageUrls[imageIndex].url}` }]
|
||||
stackPresentation: 'fullScreenModal',
|
||||
stackAnimation: 'fade'
|
||||
}}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
updateLocalNotification
|
||||
} from '@utils/slices/instancesSlice'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { InfiniteData, useQueryClient } from 'react-query'
|
||||
import { useQueryClient } from 'react-query'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import ReconnectingWebSocket from 'reconnecting-websocket'
|
||||
|
||||
|
@ -17,7 +17,12 @@ const useWebsocket = ({
|
|||
}) => {
|
||||
const queryClient = useQueryClient()
|
||||
const dispatch = useDispatch()
|
||||
const localInstance = useSelector(getLocalInstance)
|
||||
const localInstance = useSelector(
|
||||
getLocalInstance,
|
||||
(prev, next) =>
|
||||
prev?.urls.streaming_api === next?.urls.streaming_api &&
|
||||
prev?.token === next?.token
|
||||
)
|
||||
|
||||
const rws = useRef<ReconnectingWebSocket>()
|
||||
useEffect(() => {
|
||||
|
@ -40,16 +45,7 @@ const useWebsocket = ({
|
|||
'Timeline',
|
||||
{ page: 'Notifications' }
|
||||
]
|
||||
const queryData = queryClient.getQueryData(queryKey)
|
||||
queryData !== undefined &&
|
||||
queryClient.setQueryData<
|
||||
InfiniteData<Mastodon.Notification[]> | undefined
|
||||
>(queryKey, old => {
|
||||
if (old) {
|
||||
old.pages[0].unshift(payload)
|
||||
return old
|
||||
}
|
||||
})
|
||||
queryClient.invalidateQueries(queryKey)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ import React, { useCallback, useMemo, useState } from 'react'
|
|||
import { Pressable, StyleProp, StyleSheet, ViewStyle } from 'react-native'
|
||||
import { Blurhash } from 'react-native-blurhash'
|
||||
import FastImage, { ImageStyle } from 'react-native-fast-image'
|
||||
import { SharedElement } from 'react-navigation-shared-element'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
|
||||
export interface Props {
|
||||
|
@ -73,23 +72,12 @@ const GracefullyImage = React.memo(
|
|||
const children = useCallback(() => {
|
||||
return (
|
||||
<>
|
||||
{sharedElement ? (
|
||||
<SharedElement id={`image.${sharedElement}`} style={[styles.image]}>
|
||||
<FastImage
|
||||
source={{ uri: sourceUri }}
|
||||
style={[styles.image, imageStyle]}
|
||||
onLoad={onLoad}
|
||||
onError={onError}
|
||||
/>
|
||||
</SharedElement>
|
||||
) : (
|
||||
<FastImage
|
||||
source={{ uri: sourceUri }}
|
||||
style={[styles.image, imageStyle]}
|
||||
onLoad={onLoad}
|
||||
onError={onError}
|
||||
/>
|
||||
)}
|
||||
<FastImage
|
||||
source={{ uri: sourceUri }}
|
||||
style={[styles.image, imageStyle]}
|
||||
onLoad={onLoad}
|
||||
onError={onError}
|
||||
/>
|
||||
{blurhash &&
|
||||
(hidden || !(previewLoaded || originalLoaded || remoteLoaded)) ? (
|
||||
<Blurhash
|
||||
|
|
|
@ -5,16 +5,20 @@ import { StyleSheet, Text } from 'react-native'
|
|||
|
||||
export interface Props {
|
||||
content: string
|
||||
inverted?: boolean
|
||||
}
|
||||
|
||||
// Used for Android mostly
|
||||
const HeaderCenter = React.memo(
|
||||
({ content }: Props) => {
|
||||
({ content, inverted = false }: Props) => {
|
||||
const { theme } = useTheme()
|
||||
|
||||
return (
|
||||
<Text
|
||||
style={[styles.text, { color: theme.primary }]}
|
||||
style={[
|
||||
styles.text,
|
||||
{ color: inverted ? theme.primaryOverlay : theme.primary }
|
||||
]}
|
||||
children={content}
|
||||
/>
|
||||
)
|
||||
|
|
|
@ -29,7 +29,7 @@ const ComponentInstance: React.FC<Props> = ({
|
|||
const { t } = useTranslation('componentInstance')
|
||||
const { theme } = useTheme()
|
||||
|
||||
const localInstances = useSelector(getLocalInstances)
|
||||
const localInstances = useSelector(getLocalInstances, () => true)
|
||||
const [instanceDomain, setInstanceDomain] = useState<string>()
|
||||
|
||||
const instanceQuery = useInstanceQuery({
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import ComponentSeparator from '@components/Separator'
|
||||
import { useScrollToTop } from '@react-navigation/native'
|
||||
import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline'
|
||||
import { getLocalActiveIndex } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { findIndex } from 'lodash'
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
|
||||
|
@ -12,6 +13,7 @@ import Animated, {
|
|||
withTiming
|
||||
} from 'react-native-reanimated'
|
||||
import { InfiniteData, useQueryClient } from 'react-query'
|
||||
import { useSelector } from 'react-redux'
|
||||
import TimelineConversation from './Timeline/Conversation'
|
||||
import TimelineDefault from './Timeline/Default'
|
||||
import TimelineEmpty from './Timeline/Empty'
|
||||
|
@ -40,6 +42,8 @@ const Timeline: React.FC<Props> = ({
|
|||
disableInfinity = false,
|
||||
customProps
|
||||
}) => {
|
||||
// Update timeline when account switched
|
||||
useSelector(getLocalActiveIndex)
|
||||
const queryKeyParams = {
|
||||
page,
|
||||
...(hashtag && { hashtag }),
|
||||
|
@ -218,7 +222,7 @@ const Timeline: React.FC<Props> = ({
|
|||
ref={flRef}
|
||||
windowSize={8}
|
||||
data={flattenData}
|
||||
initialNumToRender={3}
|
||||
initialNumToRender={6}
|
||||
maxToRenderPerBatch={3}
|
||||
style={styles.flatList}
|
||||
renderItem={renderItem}
|
||||
|
|
|
@ -58,7 +58,10 @@ const TimelineConversation: React.FC<Props> = ({
|
|||
queryKey,
|
||||
highlighted = false
|
||||
}) => {
|
||||
const localAccount = useSelector(getLocalAccount)
|
||||
const localAccount = useSelector(
|
||||
getLocalAccount,
|
||||
(prev, next) => prev?.id === next?.id
|
||||
)
|
||||
const { theme } = useTheme()
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
|
|
|
@ -36,7 +36,10 @@ const TimelineDefault: React.FC<Props> = ({
|
|||
disableOnPress = false
|
||||
}) => {
|
||||
const { theme } = useTheme()
|
||||
const localAccount = useSelector(getLocalAccount)
|
||||
const localAccount = useSelector(
|
||||
getLocalAccount,
|
||||
(prev, next) => prev?.id === next?.id
|
||||
)
|
||||
const navigation = useNavigation<
|
||||
StackNavigationProp<Nav.TabLocalStackParamList>
|
||||
>()
|
||||
|
|
|
@ -29,7 +29,10 @@ const TimelineNotifications: React.FC<Props> = ({
|
|||
highlighted = false
|
||||
}) => {
|
||||
const { theme } = useTheme()
|
||||
const localAccount = useSelector(getLocalAccount)
|
||||
const localAccount = useSelector(
|
||||
getLocalAccount,
|
||||
(prev, next) => prev?.id === next?.id
|
||||
)
|
||||
const navigation = useNavigation<
|
||||
StackNavigationProp<Nav.TabLocalStackParamList>
|
||||
>()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export default {
|
||||
heading: 'Direct messages',
|
||||
heading: 'Direct Messages',
|
||||
content: {}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,8 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
settings: '$t(meSettings:heading)',
|
||||
accountSettings: 'Account Settings',
|
||||
appSettings: '$t(meSettings:heading)',
|
||||
logout: {
|
||||
button: 'Log out',
|
||||
alert: {
|
||||
|
|
|
@ -14,7 +14,8 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
settings: '$t(meSettings:heading)',
|
||||
accountSettings: '账户设置',
|
||||
appSettings: '$t(meSettings:heading)',
|
||||
logout: {
|
||||
button: '退出当前账号',
|
||||
alert: {
|
||||
|
|
|
@ -1,32 +1,7 @@
|
|||
import analytics from '@components/analytics'
|
||||
import Button from '@components/Button'
|
||||
import { StackScreenProps } from '@react-navigation/stack'
|
||||
import { getLocalAccount, getLocalUrl } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useEffect, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Dimensions, StyleSheet, View } from 'react-native'
|
||||
import {
|
||||
PanGestureHandler,
|
||||
State,
|
||||
TapGestureHandler
|
||||
} from 'react-native-gesture-handler'
|
||||
import Animated, {
|
||||
Extrapolate,
|
||||
interpolate,
|
||||
runOnJS,
|
||||
useAnimatedGestureHandler,
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
withTiming
|
||||
} from 'react-native-reanimated'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import { useSelector } from 'react-redux'
|
||||
import ActionsAccount from './Actions/Account'
|
||||
import ActionsDomain from './Actions/Domain'
|
||||
import ActionsShare from './Actions/Share'
|
||||
import ActionsStatus from './Actions/Status'
|
||||
import React from 'react'
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context'
|
||||
import ScreenActionsRoot from './Actions/Root'
|
||||
|
||||
export type ScreenAccountProp = StackScreenProps<
|
||||
Nav.RootStackParamList,
|
||||
|
@ -34,188 +9,14 @@ export type ScreenAccountProp = StackScreenProps<
|
|||
>
|
||||
|
||||
const ScreenActions = React.memo(
|
||||
({ route: { params }, navigation }: ScreenAccountProp) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const localAccount = useSelector(getLocalAccount)
|
||||
let sameAccount = false
|
||||
switch (params.type) {
|
||||
case 'status':
|
||||
sameAccount = localAccount?.id === params.status.account.id
|
||||
break
|
||||
case 'account':
|
||||
sameAccount = localAccount?.id === params.account.id
|
||||
break
|
||||
}
|
||||
|
||||
const localDomain = useSelector(getLocalUrl)
|
||||
let sameDomain = true
|
||||
let statusDomain: string
|
||||
switch (params.type) {
|
||||
case 'status':
|
||||
statusDomain = params.status.uri
|
||||
? params.status.uri.split(new RegExp(/\/\/(.*?)\//))[1]
|
||||
: ''
|
||||
sameDomain = localDomain === statusDomain
|
||||
break
|
||||
}
|
||||
|
||||
const { theme } = useTheme()
|
||||
const insets = useSafeAreaInsets()
|
||||
|
||||
const DEFAULT_VALUE = 350
|
||||
const screenHeight = Dimensions.get('screen').height
|
||||
const panY = useSharedValue(DEFAULT_VALUE)
|
||||
useEffect(() => {
|
||||
panY.value = withTiming(0)
|
||||
}, [])
|
||||
const styleTop = useAnimatedStyle(() => {
|
||||
return {
|
||||
bottom: interpolate(
|
||||
panY.value,
|
||||
[0, screenHeight],
|
||||
[0, -screenHeight],
|
||||
Extrapolate.CLAMP
|
||||
)
|
||||
}
|
||||
})
|
||||
const dismiss = useCallback(() => {
|
||||
panY.value = withTiming(DEFAULT_VALUE)
|
||||
navigation.goBack()
|
||||
}, [])
|
||||
const onGestureEvent = useAnimatedGestureHandler({
|
||||
onActive: ({ translationY }) => {
|
||||
panY.value = translationY
|
||||
},
|
||||
onEnd: ({ velocityY }) => {
|
||||
if (velocityY > 500) {
|
||||
runOnJS(dismiss)()
|
||||
} else {
|
||||
panY.value = withTiming(0)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const actions = useMemo(() => {
|
||||
switch (params.type) {
|
||||
case 'status':
|
||||
return (
|
||||
<>
|
||||
{!sameAccount && (
|
||||
<ActionsAccount
|
||||
queryKey={params.queryKey}
|
||||
account={params.status.account}
|
||||
dismiss={dismiss}
|
||||
/>
|
||||
)}
|
||||
{sameAccount && params.status && (
|
||||
<ActionsStatus
|
||||
navigation={navigation}
|
||||
queryKey={params.queryKey}
|
||||
status={params.status}
|
||||
dismiss={dismiss}
|
||||
/>
|
||||
)}
|
||||
{!sameDomain && statusDomain && (
|
||||
<ActionsDomain
|
||||
queryKey={params.queryKey}
|
||||
domain={statusDomain}
|
||||
dismiss={dismiss}
|
||||
/>
|
||||
)}
|
||||
<ActionsShare
|
||||
url={params.status.url || params.status.uri}
|
||||
type={params.type}
|
||||
dismiss={dismiss}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
case 'account':
|
||||
return (
|
||||
<>
|
||||
{!sameAccount && (
|
||||
<ActionsAccount account={params.account} dismiss={dismiss} />
|
||||
)}
|
||||
<ActionsShare
|
||||
url={params.account.url}
|
||||
type={params.type}
|
||||
dismiss={dismiss}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}, [])
|
||||
|
||||
(props: ScreenAccountProp) => {
|
||||
return (
|
||||
<Animated.View style={{ flex: 1 }}>
|
||||
<TapGestureHandler
|
||||
onHandlerStateChange={({ nativeEvent }) => {
|
||||
if (nativeEvent.state === State.ACTIVE) {
|
||||
dismiss()
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.overlay,
|
||||
{ backgroundColor: theme.backgroundOverlay }
|
||||
]}
|
||||
>
|
||||
<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 }
|
||||
]}
|
||||
/>
|
||||
{actions}
|
||||
<Button
|
||||
type='text'
|
||||
content={t('common:buttons.cancel')}
|
||||
onPress={() => {
|
||||
analytics('bottomsheet_cancel')
|
||||
// dismiss()
|
||||
}}
|
||||
style={styles.button}
|
||||
/>
|
||||
</Animated.View>
|
||||
</PanGestureHandler>
|
||||
</Animated.View>
|
||||
</TapGestureHandler>
|
||||
</Animated.View>
|
||||
<SafeAreaProvider>
|
||||
<ScreenActionsRoot {...props} />
|
||||
</SafeAreaProvider>
|
||||
)
|
||||
},
|
||||
() => true
|
||||
)
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
overlay: {
|
||||
flex: 1,
|
||||
justifyContent: 'flex-end'
|
||||
},
|
||||
container: {
|
||||
paddingTop: StyleConstants.Spacing.M
|
||||
},
|
||||
handle: {
|
||||
alignSelf: 'center',
|
||||
width: StyleConstants.Spacing.S * 8,
|
||||
height: StyleConstants.Spacing.S / 2,
|
||||
borderRadius: 100,
|
||||
top: -StyleConstants.Spacing.M * 2
|
||||
},
|
||||
button: {
|
||||
marginHorizontal: StyleConstants.Spacing.Global.PagePadding * 2
|
||||
}
|
||||
})
|
||||
|
||||
export default ScreenActions
|
||||
|
|
|
@ -0,0 +1,224 @@
|
|||
import analytics from '@components/analytics'
|
||||
import Button from '@components/Button'
|
||||
import { StackScreenProps } from '@react-navigation/stack'
|
||||
import { getLocalAccount, getLocalUrl } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useEffect, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Dimensions, StyleSheet, View } from 'react-native'
|
||||
import {
|
||||
PanGestureHandler,
|
||||
State,
|
||||
TapGestureHandler
|
||||
} from 'react-native-gesture-handler'
|
||||
import Animated, {
|
||||
Extrapolate,
|
||||
interpolate,
|
||||
runOnJS,
|
||||
useAnimatedGestureHandler,
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
withTiming
|
||||
} from 'react-native-reanimated'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import { useSelector } from 'react-redux'
|
||||
import ActionsAccount from './Account'
|
||||
import ActionsDomain from './Domain'
|
||||
import ActionsShare from './Share'
|
||||
import ActionsStatus from './Status'
|
||||
|
||||
export type ScreenAccountProp = StackScreenProps<
|
||||
Nav.RootStackParamList,
|
||||
'Screen-Actions'
|
||||
>
|
||||
|
||||
const ScreenActionsRoot = React.memo(
|
||||
({ route: { params }, navigation }: ScreenAccountProp) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const localAccount = useSelector(
|
||||
getLocalAccount,
|
||||
(prev, next) => prev?.id === next?.id
|
||||
)
|
||||
let sameAccount = false
|
||||
switch (params.type) {
|
||||
case 'status':
|
||||
sameAccount = localAccount?.id === params.status.account.id
|
||||
break
|
||||
case 'account':
|
||||
sameAccount = localAccount?.id === params.account.id
|
||||
break
|
||||
}
|
||||
|
||||
const localDomain = useSelector(getLocalUrl)
|
||||
let sameDomain = true
|
||||
let statusDomain: string
|
||||
switch (params.type) {
|
||||
case 'status':
|
||||
statusDomain = params.status.uri
|
||||
? params.status.uri.split(new RegExp(/\/\/(.*?)\//))[1]
|
||||
: ''
|
||||
sameDomain = localDomain === statusDomain
|
||||
break
|
||||
}
|
||||
|
||||
const { theme } = useTheme()
|
||||
const insets = useSafeAreaInsets()
|
||||
|
||||
const DEFAULT_VALUE = 350
|
||||
const screenHeight = Dimensions.get('screen').height
|
||||
const panY = useSharedValue(DEFAULT_VALUE)
|
||||
useEffect(() => {
|
||||
panY.value = withTiming(0)
|
||||
}, [])
|
||||
const styleTop = useAnimatedStyle(() => {
|
||||
return {
|
||||
bottom: interpolate(
|
||||
panY.value,
|
||||
[0, screenHeight],
|
||||
[0, -screenHeight],
|
||||
Extrapolate.CLAMP
|
||||
)
|
||||
}
|
||||
})
|
||||
const dismiss = useCallback(() => {
|
||||
panY.value = withTiming(DEFAULT_VALUE)
|
||||
navigation.goBack()
|
||||
}, [])
|
||||
const onGestureEvent = useAnimatedGestureHandler({
|
||||
onActive: ({ translationY }) => {
|
||||
panY.value = translationY
|
||||
},
|
||||
onEnd: ({ velocityY }) => {
|
||||
if (velocityY > 500) {
|
||||
runOnJS(dismiss)()
|
||||
} else {
|
||||
panY.value = withTiming(0)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const actions = useMemo(() => {
|
||||
switch (params.type) {
|
||||
case 'status':
|
||||
return (
|
||||
<>
|
||||
{!sameAccount && (
|
||||
<ActionsAccount
|
||||
queryKey={params.queryKey}
|
||||
account={params.status.account}
|
||||
dismiss={dismiss}
|
||||
/>
|
||||
)}
|
||||
{sameAccount && params.status && (
|
||||
<ActionsStatus
|
||||
navigation={navigation}
|
||||
queryKey={params.queryKey}
|
||||
status={params.status}
|
||||
dismiss={dismiss}
|
||||
/>
|
||||
)}
|
||||
{!sameDomain && statusDomain && (
|
||||
<ActionsDomain
|
||||
queryKey={params.queryKey}
|
||||
domain={statusDomain}
|
||||
dismiss={dismiss}
|
||||
/>
|
||||
)}
|
||||
<ActionsShare
|
||||
url={params.status.url || params.status.uri}
|
||||
type={params.type}
|
||||
dismiss={dismiss}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
case 'account':
|
||||
return (
|
||||
<>
|
||||
{!sameAccount && (
|
||||
<ActionsAccount account={params.account} dismiss={dismiss} />
|
||||
)}
|
||||
<ActionsShare
|
||||
url={params.account.url}
|
||||
type={params.type}
|
||||
dismiss={dismiss}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Animated.View style={{ flex: 1 }}>
|
||||
<TapGestureHandler
|
||||
onHandlerStateChange={({ nativeEvent }) => {
|
||||
if (nativeEvent.state === State.ACTIVE) {
|
||||
dismiss()
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.overlay,
|
||||
{ backgroundColor: theme.backgroundOverlay }
|
||||
]}
|
||||
>
|
||||
<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 }
|
||||
]}
|
||||
/>
|
||||
{actions}
|
||||
<Button
|
||||
type='text'
|
||||
content={t('common:buttons.cancel')}
|
||||
onPress={() => {
|
||||
analytics('bottomsheet_cancel')
|
||||
// dismiss()
|
||||
}}
|
||||
style={styles.button}
|
||||
/>
|
||||
</Animated.View>
|
||||
</PanGestureHandler>
|
||||
</Animated.View>
|
||||
</TapGestureHandler>
|
||||
</Animated.View>
|
||||
)
|
||||
},
|
||||
() => true
|
||||
)
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
overlay: {
|
||||
flex: 1,
|
||||
justifyContent: 'flex-end'
|
||||
},
|
||||
container: {
|
||||
paddingTop: StyleConstants.Spacing.M
|
||||
},
|
||||
handle: {
|
||||
alignSelf: 'center',
|
||||
width: StyleConstants.Spacing.S * 8,
|
||||
height: StyleConstants.Spacing.S / 2,
|
||||
borderRadius: 100,
|
||||
top: -StyleConstants.Spacing.M * 2
|
||||
},
|
||||
button: {
|
||||
marginHorizontal: StyleConstants.Spacing.Global.PagePadding * 2
|
||||
}
|
||||
})
|
||||
|
||||
export default ScreenActionsRoot
|
|
@ -1,6 +1,7 @@
|
|||
import analytics from '@components/analytics'
|
||||
import Button from '@components/Button'
|
||||
import haptics from '@components/haptics'
|
||||
import { HeaderCenter, HeaderLeft, HeaderRight } from '@components/Header'
|
||||
import { ParseHTML } from '@components/Parse'
|
||||
import RelativeTime from '@components/RelativeTime'
|
||||
import { BlurView } from '@react-native-community/blur'
|
||||
|
@ -203,7 +204,29 @@ const ScreenAnnouncements: React.FC<ScreenAnnouncementsProp> = ({
|
|||
style={styles.base}
|
||||
reducedTransparencyFallbackColor={theme.background}
|
||||
>
|
||||
<SafeAreaView style={styles.base}>
|
||||
<SafeAreaView style={styles.base} edges={['bottom']}>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
<HeaderLeft
|
||||
content='X'
|
||||
native={false}
|
||||
onPress={() => navigation.goBack()}
|
||||
/>
|
||||
<HeaderCenter content={t('sharedAnnouncements:heading')} />
|
||||
<View style={{ opacity: 0 }}>
|
||||
<HeaderRight
|
||||
content='MoreHorizontal'
|
||||
native={false}
|
||||
onPress={() => {}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<FlatList
|
||||
horizontal
|
||||
data={query.data}
|
||||
|
|
|
@ -75,7 +75,12 @@ const ScreenCompose: React.FC<ScreenComposeProp> = ({
|
|||
setHasKeyboard(false)
|
||||
}
|
||||
|
||||
// const draft = useSelector(getLocalDraft, () => true)
|
||||
const localAccount = useSelector(getLocalAccount, (prev, next) =>
|
||||
prev?.preferences && next?.preferences
|
||||
? prev?.preferences['posting:default:visibility'] ===
|
||||
next?.preferences['posting:default:visibility']
|
||||
: true
|
||||
)
|
||||
const initialReducerState = useMemo(() => {
|
||||
if (params) {
|
||||
return composeParseState(params)
|
||||
|
@ -92,7 +97,6 @@ const ScreenCompose: React.FC<ScreenComposeProp> = ({
|
|||
}
|
||||
}, [])
|
||||
|
||||
const localAccount = useSelector(getLocalAccount)
|
||||
const [composeState, composeDispatch] = useReducer(
|
||||
composeReducer,
|
||||
initialReducerState
|
||||
|
|
|
@ -18,12 +18,12 @@ const SingleEmoji = ({ emoji }: { emoji: Mastodon.Emoji }) => {
|
|||
newText: `:${emoji.shortcode}:`,
|
||||
type: 'emoji'
|
||||
})
|
||||
composeDispatch({
|
||||
type: 'emoji',
|
||||
payload: { ...composeState.emoji, active: false }
|
||||
})
|
||||
// composeDispatch({
|
||||
// type: 'emoji',
|
||||
// payload: { ...composeState.emoji, active: false }
|
||||
// })
|
||||
haptics('Success')
|
||||
}, [])
|
||||
}, [composeState])
|
||||
const children = useMemo(
|
||||
() => <FastImage source={{ uri: emoji.url }} style={styles.emoji} />,
|
||||
[]
|
||||
|
|
|
@ -14,7 +14,10 @@ import ComposeTextInput from './Header/TextInput'
|
|||
const ComposeRootHeader: React.FC = () => {
|
||||
const { composeState } = useContext(ComposeContext)
|
||||
const localActiveIndex = useSelector(getLocalActiveIndex)
|
||||
const localInstances = useSelector(getLocalInstances)
|
||||
const localInstances = useSelector(
|
||||
getLocalInstances,
|
||||
(prev, next) => prev.length === next.length
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -11,7 +11,10 @@ const ComposePostingAs = React.memo(
|
|||
const { t } = useTranslation('sharedCompose')
|
||||
const { theme } = useTheme()
|
||||
|
||||
const localAccount = useSelector(getLocalAccount)
|
||||
const localAccount = useSelector(
|
||||
getLocalAccount,
|
||||
(prev, next) => prev?.acct === next?.acct
|
||||
)
|
||||
const localUri = useSelector(getLocalUri)
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
import analytics from '@components/analytics'
|
||||
import { HeaderRight } from '@components/Header'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { StackScreenProps } from '@react-navigation/stack'
|
||||
import CameraRoll from '@react-native-community/cameraroll'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { findIndex } from 'lodash'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { PermissionsAndroid, Platform, Share } from 'react-native'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import ImageViewer from 'react-native-image-zoom-viewer'
|
||||
import { SharedElement } from 'react-navigation-shared-element'
|
||||
import { HeaderCenter, HeaderLeft, HeaderRight } from '@components/Header'
|
||||
import { toast } from '@components/toast'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import CameraRoll from '@react-native-community/cameraroll'
|
||||
import { StackScreenProps } from '@react-navigation/stack'
|
||||
import ImageView from '@root/modules/react-native-image-viewing/src/index'
|
||||
import { findIndex } from 'lodash'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
PermissionsAndroid,
|
||||
Platform,
|
||||
Share,
|
||||
StyleSheet,
|
||||
View
|
||||
} from 'react-native'
|
||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||
|
||||
export type ScreenImagesViewerProp = StackScreenProps<
|
||||
Nav.RootStackParamList,
|
||||
|
@ -24,7 +28,6 @@ const ScreenImagesViewer = ({
|
|||
},
|
||||
navigation
|
||||
}: ScreenImagesViewerProp) => {
|
||||
const { theme } = useTheme()
|
||||
const [currentIndex, setCurrentIndex] = useState(
|
||||
findIndex(imageUrls, ['imageIndex', imageIndex])
|
||||
)
|
||||
|
@ -95,45 +98,53 @@ const ScreenImagesViewer = ({
|
|||
)
|
||||
}, [currentIndex])
|
||||
|
||||
useEffect(
|
||||
() =>
|
||||
navigation.setOptions({
|
||||
headerTitle: `${currentIndex + 1} / ${imageUrls.length}`,
|
||||
headerTintColor: theme.primaryOverlay,
|
||||
headerRight: () => (
|
||||
<HeaderRight
|
||||
content='MoreHorizontal'
|
||||
native={false}
|
||||
onPress={onPress}
|
||||
/>
|
||||
)
|
||||
}),
|
||||
const HeaderComponent = useCallback(
|
||||
() => (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
<HeaderLeft
|
||||
content='X'
|
||||
native={false}
|
||||
onPress={() => navigation.goBack()}
|
||||
/>
|
||||
<HeaderCenter
|
||||
inverted
|
||||
content={`${currentIndex + 1} / ${imageUrls.length}`}
|
||||
/>
|
||||
<HeaderRight
|
||||
content='MoreHorizontal'
|
||||
native={false}
|
||||
onPress={onPress}
|
||||
/>
|
||||
</View>
|
||||
),
|
||||
[currentIndex]
|
||||
)
|
||||
|
||||
const renderImage = useCallback(
|
||||
prop => (
|
||||
<SharedElement id={`imageFail.${imageUrls[imageIndex].url}`}>
|
||||
<FastImage {...prop} />
|
||||
</SharedElement>
|
||||
),
|
||||
[]
|
||||
)
|
||||
|
||||
return (
|
||||
<ImageViewer
|
||||
index={imageIndex}
|
||||
imageUrls={imageUrls}
|
||||
enableSwipeDown
|
||||
useNativeDriver
|
||||
swipeDownThreshold={100}
|
||||
renderIndicator={() => <></>}
|
||||
saveToLocalByLongPress={false}
|
||||
onSwipeDown={() => navigation.goBack()}
|
||||
onChange={index => index && setCurrentIndex(index)}
|
||||
renderImage={renderImage}
|
||||
/>
|
||||
<SafeAreaView style={styles.base} edges={['top']}>
|
||||
<ImageView
|
||||
images={imageUrls.map(urls => ({ uri: urls.url }))}
|
||||
imageIndex={imageIndex}
|
||||
onImageIndexChange={index => setCurrentIndex(index)}
|
||||
onRequestClose={() => navigation.goBack()}
|
||||
HeaderComponent={HeaderComponent}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
base: {
|
||||
flex: 1,
|
||||
backgroundColor: 'black'
|
||||
}
|
||||
})
|
||||
|
||||
export default ScreenImagesViewer
|
||||
|
|
|
@ -7,17 +7,18 @@ import {
|
|||
} from '@react-navigation/bottom-tabs'
|
||||
import { NavigatorScreenParams } from '@react-navigation/native'
|
||||
import { StackScreenProps } from '@react-navigation/stack'
|
||||
import { useTimelineQuery } from '@utils/queryHooks/timeline'
|
||||
import {
|
||||
getLocalAccount,
|
||||
getLocalActiveIndex,
|
||||
getLocalInstances,
|
||||
getLocalNotification
|
||||
getLocalNotification,
|
||||
updateLocalNotification
|
||||
} from '@utils/slices/instancesSlice'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useEffect, useMemo } from 'react'
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import { Platform } from 'react-native'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import TabLocal from './Tabs/Local'
|
||||
import TabMe from './Tabs/Me'
|
||||
import TabNotifications from './Tabs/Notifications'
|
||||
|
@ -38,143 +39,171 @@ export type ScreenTabsProp = StackScreenProps<
|
|||
|
||||
const Tab = createBottomTabNavigator<Nav.ScreenTabsStackParamList>()
|
||||
|
||||
const ScreenTabs: React.FC<ScreenTabsProp> = ({ navigation }) => {
|
||||
const { theme } = useTheme()
|
||||
const localActiveIndex = useSelector(getLocalActiveIndex)
|
||||
const localAccount = useSelector(getLocalAccount)
|
||||
const ScreenTabs = React.memo(
|
||||
({ navigation }: ScreenTabsProp) => {
|
||||
const { theme } = useTheme()
|
||||
const dispatch = useDispatch()
|
||||
const localActiveIndex = useSelector(getLocalActiveIndex)
|
||||
const localAccount = useSelector(
|
||||
getLocalAccount,
|
||||
(prev, next) => prev?.avatarStatic === next?.avatarStatic
|
||||
)
|
||||
|
||||
const screenOptions = useCallback(
|
||||
({ route }): BottomTabNavigationOptions => ({
|
||||
tabBarIcon: ({
|
||||
focused,
|
||||
color,
|
||||
size
|
||||
}: {
|
||||
focused: boolean
|
||||
color: string
|
||||
size: number
|
||||
}) => {
|
||||
switch (route.name) {
|
||||
case 'Tab-Local':
|
||||
return <Icon name='Home' size={size} color={color} />
|
||||
case 'Tab-Public':
|
||||
return <Icon name='Globe' size={size} color={color} />
|
||||
case 'Tab-Compose':
|
||||
return <Icon name='Plus' size={size} color={color} />
|
||||
case 'Tab-Notifications':
|
||||
return <Icon name='Bell' size={size} color={color} />
|
||||
case 'Tab-Me':
|
||||
return localActiveIndex !== null ? (
|
||||
<FastImage
|
||||
source={{ uri: localAccount?.avatarStatic }}
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
borderRadius: size,
|
||||
borderWidth: focused ? 2 : 0,
|
||||
borderColor: focused ? theme.secondary : color
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
name={focused ? 'Meh' : 'Smile'}
|
||||
size={size}
|
||||
color={!focused ? theme.secondary : color}
|
||||
/>
|
||||
)
|
||||
default:
|
||||
return <Icon name='AlertOctagon' size={size} color={color} />
|
||||
}
|
||||
}
|
||||
}),
|
||||
[localActiveIndex, localAccount]
|
||||
)
|
||||
const tabBarOptions = useMemo(
|
||||
() => ({
|
||||
activeTintColor: theme.primary,
|
||||
inactiveTintColor:
|
||||
localActiveIndex !== null ? theme.secondary : theme.disabled,
|
||||
showLabel: false,
|
||||
...(Platform.OS === 'android' && { keyboardHidesTabBar: true })
|
||||
}),
|
||||
[theme, localActiveIndex]
|
||||
)
|
||||
const localListeners = useCallback(
|
||||
() => ({
|
||||
tabPress: (e: any) => {
|
||||
if (!(localActiveIndex !== null)) {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
}),
|
||||
[localActiveIndex]
|
||||
)
|
||||
const composeListeners = useMemo(
|
||||
() => ({
|
||||
tabPress: (e: any) => {
|
||||
e.preventDefault()
|
||||
if (localActiveIndex !== null) {
|
||||
haptics('Light')
|
||||
navigation.navigate('Screen-Compose')
|
||||
}
|
||||
}
|
||||
}),
|
||||
[localActiveIndex]
|
||||
)
|
||||
const composeComponent = useCallback(() => null, [])
|
||||
const notificationsListeners = useCallback(
|
||||
() => ({
|
||||
tabPress: (e: any) => {
|
||||
if (!(localActiveIndex !== null)) {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
}),
|
||||
[localActiveIndex]
|
||||
)
|
||||
|
||||
// On launch check if there is any unread noficiations
|
||||
useWebsocket({ stream: 'user', event: 'notification' })
|
||||
const localNotification = useSelector(getLocalNotification)
|
||||
|
||||
return (
|
||||
<Tab.Navigator
|
||||
initialRouteName={localActiveIndex !== null ? 'Tab-Local' : 'Tab-Me'}
|
||||
screenOptions={screenOptions}
|
||||
tabBarOptions={tabBarOptions}
|
||||
>
|
||||
<Tab.Screen
|
||||
name='Tab-Local'
|
||||
component={TabLocal}
|
||||
listeners={localListeners}
|
||||
/>
|
||||
<Tab.Screen name='Tab-Public' component={TabPublic} />
|
||||
<Tab.Screen
|
||||
name='Tab-Compose'
|
||||
component={composeComponent}
|
||||
listeners={composeListeners}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name='Tab-Notifications'
|
||||
component={TabNotifications}
|
||||
listeners={notificationsListeners}
|
||||
options={{
|
||||
tabBarBadge: localNotification?.latestTime
|
||||
? !localNotification.readTime ||
|
||||
new Date(localNotification.readTime) <
|
||||
new Date(localNotification.latestTime)
|
||||
? ''
|
||||
: undefined
|
||||
: undefined,
|
||||
tabBarBadgeStyle: {
|
||||
transform: [{ scale: 0.5 }],
|
||||
backgroundColor: theme.red
|
||||
const screenOptions = useCallback(
|
||||
({ route }): BottomTabNavigationOptions => ({
|
||||
tabBarIcon: ({
|
||||
focused,
|
||||
color,
|
||||
size
|
||||
}: {
|
||||
focused: boolean
|
||||
color: string
|
||||
size: number
|
||||
}) => {
|
||||
switch (route.name) {
|
||||
case 'Tab-Local':
|
||||
return <Icon name='Home' size={size} color={color} />
|
||||
case 'Tab-Public':
|
||||
return <Icon name='Globe' size={size} color={color} />
|
||||
case 'Tab-Compose':
|
||||
return <Icon name='Plus' size={size} color={color} />
|
||||
case 'Tab-Notifications':
|
||||
return <Icon name='Bell' size={size} color={color} />
|
||||
case 'Tab-Me':
|
||||
return localActiveIndex !== null ? (
|
||||
<FastImage
|
||||
source={{ uri: localAccount?.avatarStatic }}
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
borderRadius: size,
|
||||
borderWidth: focused ? 2 : 0,
|
||||
borderColor: focused ? theme.secondary : color
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
name={focused ? 'Meh' : 'Smile'}
|
||||
size={size}
|
||||
color={!focused ? theme.secondary : color}
|
||||
/>
|
||||
)
|
||||
default:
|
||||
return <Icon name='AlertOctagon' size={size} color={color} />
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Tab.Screen name='Tab-Me' component={TabMe} />
|
||||
</Tab.Navigator>
|
||||
)
|
||||
}
|
||||
}
|
||||
}),
|
||||
[localActiveIndex, localAccount?.avatarStatic]
|
||||
)
|
||||
const tabBarOptions = useMemo(
|
||||
() => ({
|
||||
activeTintColor: theme.primary,
|
||||
inactiveTintColor:
|
||||
localActiveIndex !== null ? theme.secondary : theme.disabled,
|
||||
showLabel: false,
|
||||
...(Platform.OS === 'android' && { keyboardHidesTabBar: true })
|
||||
}),
|
||||
[theme, localActiveIndex]
|
||||
)
|
||||
const localListeners = useCallback(
|
||||
() => ({
|
||||
tabPress: (e: any) => {
|
||||
if (!(localActiveIndex !== null)) {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
}),
|
||||
[localActiveIndex]
|
||||
)
|
||||
const composeListeners = useMemo(
|
||||
() => ({
|
||||
tabPress: (e: any) => {
|
||||
e.preventDefault()
|
||||
if (localActiveIndex !== null) {
|
||||
haptics('Light')
|
||||
navigation.navigate('Screen-Compose')
|
||||
}
|
||||
}
|
||||
}),
|
||||
[localActiveIndex]
|
||||
)
|
||||
const composeComponent = useCallback(() => null, [])
|
||||
const notificationsListeners = useCallback(
|
||||
() => ({
|
||||
tabPress: (e: any) => {
|
||||
if (!(localActiveIndex !== null)) {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
}),
|
||||
[localActiveIndex]
|
||||
)
|
||||
|
||||
// On launch check if there is any unread noficiations
|
||||
useTimelineQuery({
|
||||
page: 'Notifications',
|
||||
options: {
|
||||
notifyOnChangeProps: [],
|
||||
select: data => {
|
||||
if (data.pages[0].length) {
|
||||
dispatch(
|
||||
updateLocalNotification({
|
||||
latestTime: data.pages[0][0].created_at
|
||||
})
|
||||
)
|
||||
}
|
||||
return data
|
||||
}
|
||||
}
|
||||
})
|
||||
useWebsocket({ stream: 'user', event: 'notification' })
|
||||
const localNotification = useSelector(
|
||||
getLocalNotification,
|
||||
(prev, next) =>
|
||||
prev?.readTime === next?.readTime &&
|
||||
prev?.latestTime === next?.latestTime
|
||||
)
|
||||
|
||||
return (
|
||||
<Tab.Navigator
|
||||
initialRouteName={localActiveIndex !== null ? 'Tab-Local' : 'Tab-Me'}
|
||||
screenOptions={screenOptions}
|
||||
tabBarOptions={tabBarOptions}
|
||||
>
|
||||
<Tab.Screen
|
||||
name='Tab-Local'
|
||||
component={TabLocal}
|
||||
listeners={localListeners}
|
||||
/>
|
||||
<Tab.Screen name='Tab-Public' component={TabPublic} />
|
||||
<Tab.Screen
|
||||
name='Tab-Compose'
|
||||
component={composeComponent}
|
||||
listeners={composeListeners}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name='Tab-Notifications'
|
||||
component={TabNotifications}
|
||||
listeners={notificationsListeners}
|
||||
options={{
|
||||
tabBarBadge: localNotification?.latestTime
|
||||
? !localNotification.readTime ||
|
||||
new Date(localNotification.readTime) <
|
||||
new Date(localNotification.latestTime)
|
||||
? ''
|
||||
: undefined
|
||||
: undefined,
|
||||
tabBarBadgeStyle: {
|
||||
transform: [{ scale: 0.5 }],
|
||||
backgroundColor: theme.red
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Tab.Screen name='Tab-Me' component={TabMe} />
|
||||
</Tab.Navigator>
|
||||
)
|
||||
},
|
||||
() => true
|
||||
)
|
||||
|
||||
export default ScreenTabs
|
||||
|
|
|
@ -44,7 +44,7 @@ const Collections: React.FC = () => {
|
|||
onPress={() => navigation.navigate('Tab-Me-Bookmarks')}
|
||||
/>
|
||||
<MenuRow
|
||||
iconFront='Star'
|
||||
iconFront='Heart'
|
||||
iconBack='ChevronRight'
|
||||
title={t('content.collections.favourites')}
|
||||
onPress={() => navigation.navigate('Tab-Me-Favourites')}
|
||||
|
|
|
@ -10,7 +10,10 @@ export interface Props {
|
|||
}
|
||||
|
||||
const MyInfo: React.FC<Props> = ({ setData }) => {
|
||||
const localAccount = useSelector(getLocalAccount)
|
||||
const localAccount = useSelector(
|
||||
getLocalAccount,
|
||||
(prev, next) => prev?.id === next?.id
|
||||
)
|
||||
const { data } = useAccountQuery({ id: localAccount!.id })
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -1,26 +1,32 @@
|
|||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { getLocalUrl } from '@utils/slices/instancesSlice'
|
||||
import * as WebBrowser from 'expo-web-browser'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
const Settings: React.FC = () => {
|
||||
const { t } = useTranslation('meRoot')
|
||||
const navigation = useNavigation()
|
||||
|
||||
const [loadingState, setLoadingState] = React.useState(false)
|
||||
React.useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setLoadingState(!loadingState)
|
||||
}, 5000)
|
||||
return () => clearTimeout(timer)
|
||||
}, [loadingState])
|
||||
const localUrl = useSelector(getLocalUrl)
|
||||
|
||||
return (
|
||||
<MenuContainer>
|
||||
{/* <MenuRow
|
||||
iconFront='User'
|
||||
iconBack='ExternalLink'
|
||||
title={t('content.accountSettings')}
|
||||
onPress={() =>
|
||||
localUrl &&
|
||||
WebBrowser.openBrowserAsync(`https://${localUrl}/settings/profile`)
|
||||
}
|
||||
/> */}
|
||||
<MenuRow
|
||||
iconFront='Settings'
|
||||
iconBack='ChevronRight'
|
||||
title={t('content.settings')}
|
||||
title={t('content.appSettings')}
|
||||
onPress={() => navigation.navigate('Tab-Me-Settings')}
|
||||
/>
|
||||
</MenuContainer>
|
||||
|
|
|
@ -45,8 +45,8 @@ const AccountButton: React.FC<Props> = ({ instance, disabled = false }) => {
|
|||
onPress={() => {
|
||||
haptics('Light')
|
||||
analytics('switch_existing_press')
|
||||
queryClient.clear()
|
||||
dispatch(updateLocalActiveIndex(instance))
|
||||
queryClient.clear()
|
||||
navigation.goBack()
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -21,7 +21,9 @@ export interface Props {
|
|||
}
|
||||
|
||||
const AccountInformation: React.FC<Props> = ({ account, myInfo = false }) => {
|
||||
const ownAccount = account?.id === useSelector(getLocalAccount)?.id
|
||||
const ownAccount =
|
||||
account?.id ===
|
||||
useSelector(getLocalAccount, (prev, next) => prev?.id === next?.id)?.id
|
||||
const { mode, theme } = useTheme()
|
||||
|
||||
const animation = useCallback(
|
||||
|
|
|
@ -14,7 +14,10 @@ export interface Props {
|
|||
|
||||
const AccountInformationAccount: React.FC<Props> = ({ account, myInfo }) => {
|
||||
const { theme } = useTheme()
|
||||
const localAccount = useSelector(getLocalAccount)
|
||||
const localAccount = useSelector(
|
||||
getLocalAccount,
|
||||
(prev, next) => prev?.acct === next?.acct
|
||||
)
|
||||
const localUri = useSelector(getLocalUri)
|
||||
|
||||
const movedStyle = useMemo(
|
||||
|
|
32
yarn.lock
32
yarn.lock
|
@ -8595,18 +8595,6 @@ react-native-htmlview@^0.16.0:
|
|||
entities "^1.1.1"
|
||||
htmlparser2-without-node-native "^3.9.2"
|
||||
|
||||
react-native-image-pan-zoom@^2.1.12:
|
||||
version "2.1.12"
|
||||
resolved "https://registry.yarnpkg.com/react-native-image-pan-zoom/-/react-native-image-pan-zoom-2.1.12.tgz#eb98bf56fb5610379bdbfdb63219cc1baca98fd2"
|
||||
integrity sha512-BF66XeP6dzuANsPmmFsJshM2Jyh/Mo1t8FsGc1L9Q9/sVP8MJULDabB1hms+eAoqgtyhMr5BuXV3E1hJ5U5H6Q==
|
||||
|
||||
react-native-image-zoom-viewer@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/react-native-image-zoom-viewer/-/react-native-image-zoom-viewer-3.0.1.tgz#a2bd5fb3bda15e0686ce88fcde8576726495d7fb"
|
||||
integrity sha512-la6s5DNSuq4GCRLsi5CZ29FPjgTpdCuGIRdO5T9rUrAtxrlpBPhhSnHrbmPVxsdtOUvxHacTh2Gfa9+RraMZQA==
|
||||
dependencies:
|
||||
react-native-image-pan-zoom "^2.1.12"
|
||||
|
||||
react-native-iphone-x-helper@^1.3.0:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.3.1.tgz#20c603e9a0e765fd6f97396638bdeb0e5a60b010"
|
||||
|
@ -8638,11 +8626,6 @@ react-native-screens@~2.17.1:
|
|||
resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-2.17.1.tgz#c3c0ac750af48741c5b1635511e6af2a27b74309"
|
||||
integrity sha512-B4gD5e4csvlVwlhf+RNqjQZ9mHTwe/iL3rXondgZxnKz4oW0QAmtLnLRKOrYVxoaJaF9Fy7jhjo//24/472APQ==
|
||||
|
||||
react-native-shared-element@^0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/react-native-shared-element/-/react-native-shared-element-0.7.0.tgz#c5e02eb372f6e38e48eb1030fd59be8f3d8c7a1f"
|
||||
integrity sha512-TJTGwQceABYete+vH3ahNSgzVzXz7X2SPv3thT91gdcFCrm7ht7IKXBXJiYjOA+4TfdqnGEAWkspCy80oEnOgw==
|
||||
|
||||
react-native-svg@12.1.0:
|
||||
version "12.1.0"
|
||||
resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-12.1.0.tgz#acfe48c35cd5fca3d5fd767abae0560c36cfc03d"
|
||||
|
@ -8732,13 +8715,6 @@ react-native@~0.63.4:
|
|||
use-subscription "^1.0.0"
|
||||
whatwg-fetch "^3.0.0"
|
||||
|
||||
react-navigation-shared-element@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/react-navigation-shared-element/-/react-navigation-shared-element-3.0.0.tgz#3dfa6a71f138e4ceb457a91aea3ef2a8ab2a6a32"
|
||||
integrity sha512-nSmbgf4+hKSZU1Rzi5Bm1Is5mt0Z+xY5sXeTx4t5KMnmNW4lz5M83p9oTW6mKDoXUeCocUfeVwbCmsAjlyTLQw==
|
||||
dependencies:
|
||||
hoist-non-react-statics "^3.3.2"
|
||||
|
||||
react-navigation-stack@^2.10.2:
|
||||
version "2.10.2"
|
||||
resolved "https://registry.yarnpkg.com/react-navigation-stack/-/react-navigation-stack-2.10.2.tgz#9b6d38503496f9ef519acc6cad270ec0ace4ccbd"
|
||||
|
@ -8755,10 +8731,10 @@ react-navigation@*, react-navigation@^4.4.3:
|
|||
"@react-navigation/core" "^3.7.9"
|
||||
"@react-navigation/native" "^3.8.3"
|
||||
|
||||
react-query@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.6.0.tgz#3da307a4a4cb6ea95f9c95b7e50b5281d5244e4d"
|
||||
integrity sha512-39ptLt4qaKO1DE+ta6SpPutweEgDvUQj/KlebC+okJ9Nxbs5ExxKV8RYlLeop6vdDFyiMmwYrt1POiF8oWGh1A==
|
||||
react-query@^3.8.2:
|
||||
version "3.8.2"
|
||||
resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.8.2.tgz#e2dac76b5d9b3465d854f4ca040a35ba4bcae1ae"
|
||||
integrity sha512-Ha8+WZLIHOUkKhqE4+2RZZB03WvEWmNhN4WIIw3zWVCtR7blo2n2TXtu+d3YhaY1o6dt+sHT+J+zNV8IzR583w==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.5.5"
|
||||
match-sorter "^6.0.2"
|
||||
|
|
Loading…
Reference in New Issue