mirror of
https://github.com/tooot-app/app
synced 2025-04-15 10:47:46 +02:00
More adaption to Android
This commit is contained in:
parent
49715bba0d
commit
95f500ae72
6
App.tsx
6
App.tsx
@ -15,10 +15,6 @@ import { QueryClient, QueryClientProvider } from 'react-query'
|
|||||||
import { Provider } from 'react-redux'
|
import { Provider } from 'react-redux'
|
||||||
import { PersistGate } from 'redux-persist/integration/react'
|
import { PersistGate } from 'redux-persist/integration/react'
|
||||||
import { LogBox, Platform } from 'react-native'
|
import { LogBox, Platform } from 'react-native'
|
||||||
import Moment from 'react-moment'
|
|
||||||
|
|
||||||
import moment from 'moment'
|
|
||||||
import 'moment/min/locales'
|
|
||||||
|
|
||||||
if (Platform.OS === 'android') {
|
if (Platform.OS === 'android') {
|
||||||
LogBox.ignoreLogs(['Setting a timer for a long period of time'])
|
LogBox.ignoreLogs(['Setting a timer for a long period of time'])
|
||||||
@ -28,8 +24,6 @@ dev()
|
|||||||
sentry()
|
sentry()
|
||||||
audio()
|
audio()
|
||||||
onlineStatus()
|
onlineStatus()
|
||||||
Moment.globalMoment = moment
|
|
||||||
Moment.startPooledTimer(1000)
|
|
||||||
|
|
||||||
log('log', 'react-query', 'initializing')
|
log('log', 'react-query', 'initializing')
|
||||||
const queryClient = new QueryClient()
|
const queryClient = new QueryClient()
|
||||||
|
@ -44,12 +44,10 @@
|
|||||||
"gl-react-expo": "^4.0.1",
|
"gl-react-expo": "^4.0.1",
|
||||||
"i18next": "^19.8.4",
|
"i18next": "^19.8.4",
|
||||||
"lodash": "^4.17.20",
|
"lodash": "^4.17.20",
|
||||||
"moment": "^2.29.1",
|
|
||||||
"pretty-bytes": "^5.5.0",
|
"pretty-bytes": "^5.5.0",
|
||||||
"react": "16.13.1",
|
"react": "16.13.1",
|
||||||
"react-dom": "16.13.1",
|
"react-dom": "16.13.1",
|
||||||
"react-i18next": "^11.8.5",
|
"react-i18next": "^11.8.5",
|
||||||
"react-moment": "^1.1.1",
|
|
||||||
"react-native": "https://github.com/expo/react-native/archive/sdk-40.0.0.tar.gz",
|
"react-native": "https://github.com/expo/react-native/archive/sdk-40.0.0.tar.gz",
|
||||||
"react-native-animated-spinkit": "^1.4.2",
|
"react-native-animated-spinkit": "^1.4.2",
|
||||||
"react-native-expo-image-cache": "^4.1.0",
|
"react-native-expo-image-cache": "^4.1.0",
|
||||||
@ -67,6 +65,7 @@
|
|||||||
"react-navigation": "^4.4.3",
|
"react-navigation": "^4.4.3",
|
||||||
"react-query": "^3.5.6",
|
"react-query": "^3.5.6",
|
||||||
"react-redux": "^7.2.2",
|
"react-redux": "^7.2.2",
|
||||||
|
"react-timeago": "^5.2.0",
|
||||||
"redux-persist": "^6.0.0",
|
"redux-persist": "^6.0.0",
|
||||||
"redux-persist-expo-securestore": "^2.0.0",
|
"redux-persist-expo-securestore": "^2.0.0",
|
||||||
"sentry-expo": "^3.0.4",
|
"sentry-expo": "^3.0.4",
|
||||||
@ -93,6 +92,7 @@
|
|||||||
"@types/react-navigation": "^3.4.0",
|
"@types/react-navigation": "^3.4.0",
|
||||||
"@types/react-redux": "^7.1.12",
|
"@types/react-redux": "^7.1.12",
|
||||||
"@types/react-test-renderer": "^17.0.0",
|
"@types/react-test-renderer": "^17.0.0",
|
||||||
|
"@types/react-timeago": "^4.1.2",
|
||||||
"@welldone-software/why-did-you-render": "^6.0.4",
|
"@welldone-software/why-did-you-render": "^6.0.4",
|
||||||
"babel-plugin-module-resolver": "^4.1.0",
|
"babel-plugin-module-resolver": "^4.1.0",
|
||||||
"chalk": "^4.1.0",
|
"chalk": "^4.1.0",
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
createBottomTabNavigator
|
createBottomTabNavigator
|
||||||
} from '@react-navigation/bottom-tabs'
|
} from '@react-navigation/bottom-tabs'
|
||||||
import {
|
import {
|
||||||
|
getFocusedRouteNameFromRoute,
|
||||||
NavigationContainer,
|
NavigationContainer,
|
||||||
NavigationContainerRef
|
NavigationContainerRef
|
||||||
} from '@react-navigation/native'
|
} from '@react-navigation/native'
|
||||||
@ -31,7 +32,7 @@ import React, {
|
|||||||
useMemo,
|
useMemo,
|
||||||
useRef
|
useRef
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { StatusBar } from 'react-native'
|
import { Platform, StatusBar } from 'react-native'
|
||||||
import Toast from 'react-native-toast-message'
|
import Toast from 'react-native-toast-message'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
|
||||||
@ -172,6 +173,7 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
|
|||||||
}) => {
|
}) => {
|
||||||
let name: any
|
let name: any
|
||||||
let updateColor: string = color
|
let updateColor: string = color
|
||||||
|
console.log()
|
||||||
switch (route.name) {
|
switch (route.name) {
|
||||||
case 'Screen-Local':
|
case 'Screen-Local':
|
||||||
name = 'Home'
|
name = 'Home'
|
||||||
@ -195,7 +197,16 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
return <Icon name={name} size={size} color={updateColor} />
|
return <Icon name={name} size={size} color={updateColor} />
|
||||||
}
|
},
|
||||||
|
...(Platform.OS === 'android' && {
|
||||||
|
tabBarVisible:
|
||||||
|
getFocusedRouteNameFromRoute(route) !== 'Screen-Shared-Compose' &&
|
||||||
|
getFocusedRouteNameFromRoute(route) !==
|
||||||
|
'Screen-Shared-Announcements' &&
|
||||||
|
getFocusedRouteNameFromRoute(route) !==
|
||||||
|
'Screen-Shared-ImagesViewer' &&
|
||||||
|
getFocusedRouteNameFromRoute(route) !== 'Screen-Me-Switch'
|
||||||
|
})
|
||||||
}),
|
}),
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
@ -204,7 +215,8 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
|
|||||||
activeTintColor: theme.primary,
|
activeTintColor: theme.primary,
|
||||||
inactiveTintColor:
|
inactiveTintColor:
|
||||||
localActiveIndex !== null ? theme.secondary : theme.disabled,
|
localActiveIndex !== null ? theme.secondary : theme.disabled,
|
||||||
showLabel: false
|
showLabel: false,
|
||||||
|
...(Platform.OS === 'android' && { keyboardHidesTabBar: true })
|
||||||
}),
|
}),
|
||||||
[theme, localActiveIndex]
|
[theme, localActiveIndex]
|
||||||
)
|
)
|
||||||
@ -292,7 +304,9 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
|
|||||||
<Tab.Screen name='Screen-Me' component={ScreenMe} />
|
<Tab.Screen name='Screen-Me' component={ScreenMe} />
|
||||||
</Tab.Navigator>
|
</Tab.Navigator>
|
||||||
|
|
||||||
{/* <Toast ref={Toast.setRef} config={toastConfig} /> */}
|
{Platform.OS === 'ios' ? (
|
||||||
|
<Toast ref={Toast.setRef} config={toastConfig} />
|
||||||
|
) : null}
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import HeaderLeft from '@components/Header/Left'
|
import HeaderLeft from '@components/Header/Left'
|
||||||
|
import HeaderCenter from '@components/Header/Center'
|
||||||
import HeaderRight from '@components/Header/Right'
|
import HeaderRight from '@components/Header/Right'
|
||||||
|
|
||||||
export { HeaderLeft, HeaderRight }
|
export { HeaderLeft, HeaderCenter, HeaderRight }
|
||||||
|
31
src/components/Header/Center.tsx
Normal file
31
src/components/Header/Center.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
|
import React from 'react'
|
||||||
|
import { StyleSheet, Text } from 'react-native'
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
content: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used for Android mostly
|
||||||
|
const HeaderCenter = React.memo(
|
||||||
|
({ content }: Props) => {
|
||||||
|
const { theme } = useTheme()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
style={[styles.text, { color: theme.primary }]}
|
||||||
|
children={content}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
() => true
|
||||||
|
)
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
text: {
|
||||||
|
...StyleConstants.FontStyle.M
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default HeaderCenter
|
@ -45,9 +45,12 @@ const renderNode = ({
|
|||||||
? routeParams.hashtag !== tag[1] && routeParams.hashtag !== tag[2]
|
? routeParams.hashtag !== tag[1] && routeParams.hashtag !== tag[2]
|
||||||
: true
|
: true
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Text
|
||||||
key={index}
|
key={index}
|
||||||
hitSlop={StyleConstants.Font.Size[size] / 2}
|
style={{
|
||||||
|
color: theme.blue,
|
||||||
|
...StyleConstants.FontStyle[size]
|
||||||
|
}}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
!disableDetails &&
|
!disableDetails &&
|
||||||
differentTag &&
|
differentTag &&
|
||||||
@ -56,16 +59,9 @@ const renderNode = ({
|
|||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text
|
{node.children[0].data}
|
||||||
style={{
|
{node.children[1]?.children[0].data}
|
||||||
color: theme.blue,
|
</Text>
|
||||||
...StyleConstants.FontStyle[size]
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{node.children[0].data}
|
|
||||||
{node.children[1]?.children[0].data}
|
|
||||||
</Text>
|
|
||||||
</Pressable>
|
|
||||||
)
|
)
|
||||||
} else if (classes.includes('mention') && mentions) {
|
} else if (classes.includes('mention') && mentions) {
|
||||||
const accountIndex = mentions.findIndex(
|
const accountIndex = mentions.findIndex(
|
||||||
@ -75,9 +71,12 @@ const renderNode = ({
|
|||||||
? routeParams.account.id !== mentions[accountIndex].id
|
? routeParams.account.id !== mentions[accountIndex].id
|
||||||
: true
|
: true
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Text
|
||||||
key={index}
|
key={index}
|
||||||
hitSlop={StyleConstants.Font.Size[size] / 2}
|
style={{
|
||||||
|
color: accountIndex !== -1 ? theme.blue : undefined,
|
||||||
|
...StyleConstants.FontStyle[size]
|
||||||
|
}}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
accountIndex !== -1 &&
|
accountIndex !== -1 &&
|
||||||
!disableDetails &&
|
!disableDetails &&
|
||||||
@ -87,16 +86,9 @@ const renderNode = ({
|
|||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text
|
{node.children[0].data}
|
||||||
style={{
|
{node.children[1]?.children[0].data}
|
||||||
color: accountIndex !== -1 ? theme.blue : undefined,
|
</Text>
|
||||||
...StyleConstants.FontStyle[size]
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{node.children[0].data}
|
|
||||||
{node.children[1]?.children[0].data}
|
|
||||||
</Text>
|
|
||||||
</Pressable>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -107,9 +99,12 @@ const renderNode = ({
|
|||||||
const shouldBeTag =
|
const shouldBeTag =
|
||||||
tags && tags.filter(tag => `#${tag.name}` === content).length > 0
|
tags && tags.filter(tag => `#${tag.name}` === content).length > 0
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Text
|
||||||
key={index}
|
key={index}
|
||||||
hitSlop={StyleConstants.Font.Size[size] / 2}
|
style={{
|
||||||
|
color: theme.blue,
|
||||||
|
...StyleConstants.FontStyle[size]
|
||||||
|
}}
|
||||||
onPress={async () =>
|
onPress={async () =>
|
||||||
!disableDetails && !shouldBeTag
|
!disableDetails && !shouldBeTag
|
||||||
? await openLink(href)
|
? await openLink(href)
|
||||||
@ -118,22 +113,15 @@ const renderNode = ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Text
|
{!shouldBeTag ? (
|
||||||
style={{
|
<Icon
|
||||||
color: theme.blue,
|
color={theme.blue}
|
||||||
...StyleConstants.FontStyle[size]
|
name='ExternalLink'
|
||||||
}}
|
size={StyleConstants.Font.Size[size]}
|
||||||
>
|
/>
|
||||||
{!shouldBeTag ? (
|
) : null}
|
||||||
<Icon
|
{content || (showFullLink ? href : domain[1])}
|
||||||
color={theme.blue}
|
</Text>
|
||||||
name='ExternalLink'
|
|
||||||
size={StyleConstants.Font.Size[size]}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{content || (showFullLink ? href : domain[1])}
|
|
||||||
</Text>
|
|
||||||
</Pressable>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
26
src/components/RelativeTime.tsx
Normal file
26
src/components/RelativeTime.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Text } from 'react-native'
|
||||||
|
import TimeAgo from 'react-timeago'
|
||||||
|
// @ts-ignore
|
||||||
|
import buildFormatter from 'react-timeago/lib/formatters/buildFormatter'
|
||||||
|
|
||||||
|
import zh from '@root/i18n/zh/components/relativeTime'
|
||||||
|
import en from '@root/i18n/en/components/relativeTime'
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
date: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const RelativeTime: React.FC<Props> = ({ date }) => {
|
||||||
|
const { i18n } = useTranslation()
|
||||||
|
const mapLanguageToTranslation: { [key: string]: Object } = {
|
||||||
|
'zh-CN': zh,
|
||||||
|
'en-US': en
|
||||||
|
}
|
||||||
|
const formatter = buildFormatter(mapLanguageToTranslation[i18n.language])
|
||||||
|
|
||||||
|
return <TimeAgo date={date} formatter={formatter} component={Text} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RelativeTime
|
@ -1,12 +1,12 @@
|
|||||||
import { HeaderRight } from '@components/Header'
|
import { HeaderCenter, HeaderRight } from '@components/Header'
|
||||||
import Timeline from '@components/Timelines/Timeline'
|
import Timeline from '@components/Timelines/Timeline'
|
||||||
import SegmentedControl from '@react-native-community/segmented-control'
|
import SegmentedControl from '@react-native-community/segmented-control'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import sharedScreens from '@screens/Shared/sharedScreens'
|
import sharedScreens from '@screens/Shared/sharedScreens'
|
||||||
import { getLocalActiveIndex, getRemoteUrl } from '@utils/slices/instancesSlice'
|
import { getLocalActiveIndex, getRemoteUrl } from '@utils/slices/instancesSlice'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useCallback, useState } from 'react'
|
import React, { useCallback, useMemo, useState } from 'react'
|
||||||
import { Dimensions, 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 { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
@ -68,6 +68,40 @@ const Timelines: React.FC<Props> = ({ name, content }) => {
|
|||||||
[segment, localActiveIndex]
|
[segment, localActiveIndex]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const screenOptions = useMemo(() => {
|
||||||
|
if (localActiveIndex === null) {
|
||||||
|
if (name === 'Public') {
|
||||||
|
return {
|
||||||
|
headerTitle: publicDomain,
|
||||||
|
...(Platform.OS === 'android' && {
|
||||||
|
headerCenter: () => <HeaderCenter content={publicDomain} />
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
headerCenter: () => (
|
||||||
|
<View style={styles.segmentsContainer}>
|
||||||
|
<SegmentedControl
|
||||||
|
appearance={mode}
|
||||||
|
values={[
|
||||||
|
content[0].title,
|
||||||
|
content[1].remote ? remoteUrl : content[1].title
|
||||||
|
]}
|
||||||
|
selectedIndex={segment}
|
||||||
|
onChange={({ nativeEvent }) =>
|
||||||
|
setSegment(nativeEvent.selectedSegmentIndex)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
),
|
||||||
|
headerRight: () => (
|
||||||
|
<HeaderRight content='Search' onPress={onPressSearch} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [localActiveIndex, mode, segment])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack.Navigator
|
<Stack.Navigator
|
||||||
screenOptions={{ headerHideShadow: true, headerTopInsetEnabled: false }}
|
screenOptions={{ headerHideShadow: true, headerTopInsetEnabled: false }}
|
||||||
@ -76,29 +110,7 @@ const Timelines: React.FC<Props> = ({ name, content }) => {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
name={`Screen-${name}-Root`}
|
name={`Screen-${name}-Root`}
|
||||||
component={screenComponent}
|
component={screenComponent}
|
||||||
options={{
|
options={screenOptions}
|
||||||
headerTitle: name === 'Public' ? publicDomain : '',
|
|
||||||
...(localActiveIndex !== null && {
|
|
||||||
headerCenter: () => (
|
|
||||||
<View style={styles.segmentsContainer}>
|
|
||||||
<SegmentedControl
|
|
||||||
appearance={mode}
|
|
||||||
values={[
|
|
||||||
content[0].title,
|
|
||||||
content[1].remote ? remoteUrl : content[1].title
|
|
||||||
]}
|
|
||||||
selectedIndex={segment}
|
|
||||||
onChange={({ nativeEvent }) =>
|
|
||||||
setSegment(nativeEvent.selectedSegmentIndex)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
),
|
|
||||||
headerRight: () => (
|
|
||||||
<HeaderRight content='Search' onPress={onPressSearch} />
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{sharedScreens(Stack)}
|
{sharedScreens(Stack)}
|
||||||
|
@ -9,13 +9,14 @@ import { useScrollToTop } from '@react-navigation/native'
|
|||||||
import { localUpdateNotification } from '@utils/slices/instancesSlice'
|
import { localUpdateNotification } from '@utils/slices/instancesSlice'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
|
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
|
||||||
import { RefreshControl, StyleSheet } from 'react-native'
|
import { Platform, RefreshControl, StyleSheet } from 'react-native'
|
||||||
import { FlatList } from 'react-native-gesture-handler'
|
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 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
|
||||||
@ -36,6 +37,8 @@ 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 }),
|
||||||
@ -163,8 +166,13 @@ const Timeline: React.FC<Props> = ({
|
|||||||
const refreshControl = useMemo(
|
const refreshControl = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<RefreshControl
|
<RefreshControl
|
||||||
|
{...(Platform.OS === 'android' && { enabled: true })}
|
||||||
refreshing={
|
refreshing={
|
||||||
refreshCount.current < 2 ? isFetchingPreviousPage : isFetching
|
refreshCount.current < 2
|
||||||
|
? Platform.OS === 'ios'
|
||||||
|
? isFetchingPreviousPage
|
||||||
|
: isFetchingPreviousPage || isFetching
|
||||||
|
: isFetching
|
||||||
}
|
}
|
||||||
onRefresh={async () => {
|
onRefresh={async () => {
|
||||||
if (refreshCount.current < 2) {
|
if (refreshCount.current < 2) {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
|
import RelativeTime from '@components/RelativeTime'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Moment from 'react-moment'
|
|
||||||
import { StyleSheet, Text } from 'react-native'
|
import { StyleSheet, Text } from 'react-native'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@ -15,7 +15,7 @@ const HeaderSharedCreated: React.FC<Props> = ({ created_at }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Text style={[styles.created_at, { color: theme.secondary }]}>
|
<Text style={[styles.created_at, { color: theme.secondary }]}>
|
||||||
<Moment date={created_at} locale={i18n.language} element={Text} fromNow />
|
<RelativeTime date={created_at} />
|
||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import Button from '@components/Button'
|
import Button from '@components/Button'
|
||||||
import haptics from '@components/haptics'
|
import haptics from '@components/haptics'
|
||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
import relativeTime from '@components/relativeTime'
|
import RelativeTime from '@components/RelativeTime'
|
||||||
import { ParseEmojis } from '@root/components/Parse'
|
import { ParseEmojis } from '@root/components/Parse'
|
||||||
import { toast } from '@root/components/toast'
|
import { toast } from '@root/components/toast'
|
||||||
import {
|
import {
|
||||||
@ -13,7 +13,6 @@ import { useTheme } from '@utils/styles/ThemeManager'
|
|||||||
import { maxBy } from 'lodash'
|
import { maxBy } from 'lodash'
|
||||||
import React, { useCallback, useMemo, useState } from 'react'
|
import React, { useCallback, useMemo, useState } from 'react'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import Moment from 'react-moment'
|
|
||||||
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
||||||
import { useQueryClient } from 'react-query'
|
import { useQueryClient } from 'react-query'
|
||||||
|
|
||||||
@ -127,14 +126,7 @@ const TimelinePoll: React.FC<Props> = ({
|
|||||||
<Text style={[styles.expiration, { color: theme.secondary }]}>
|
<Text style={[styles.expiration, { color: theme.secondary }]}>
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey='timeline:shared.poll.meta.expiration.until'
|
i18nKey='timeline:shared.poll.meta.expiration.until'
|
||||||
components={[
|
components={[<RelativeTime date={poll.expires_at} />]}
|
||||||
<Moment
|
|
||||||
date={poll.expires_at}
|
|
||||||
locale={i18n.language}
|
|
||||||
element={Text}
|
|
||||||
fromNow
|
|
||||||
/>
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
|
@ -1,9 +1,17 @@
|
|||||||
import * as Haptics from 'expo-haptics'
|
import * as Haptics from 'expo-haptics'
|
||||||
|
import { Platform } from 'react-native'
|
||||||
import * as Sentry from 'sentry-expo'
|
import * as Sentry from 'sentry-expo'
|
||||||
|
|
||||||
const haptics = (
|
const haptics = (
|
||||||
type: 'Success' | 'Warning' | 'Error' | 'Light' | 'Medium' | 'Heavy'
|
type: 'Success' | 'Warning' | 'Error' | 'Light' | 'Medium' | 'Heavy'
|
||||||
) => {
|
) => {
|
||||||
|
if (Platform.OS === 'android') {
|
||||||
|
Haptics.impactAsync(Haptics.ImpactFeedbackStyle['Light']).catch(error =>
|
||||||
|
Sentry.Native.captureException(error)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'Success':
|
case 'Success':
|
||||||
case 'Warning':
|
case 'Warning':
|
||||||
|
@ -2,7 +2,7 @@ import Icon from '@components/Icon'
|
|||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { StyleSheet, Text, View } from 'react-native'
|
import { Platform, StyleSheet, Text, ToastAndroid, View } from 'react-native'
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||||
import Toast from 'react-native-toast-message'
|
import Toast from 'react-native-toast-message'
|
||||||
import * as Sentry from 'sentry-expo'
|
import * as Sentry from 'sentry-expo'
|
||||||
@ -33,18 +33,23 @@ const toast = ({
|
|||||||
onShow,
|
onShow,
|
||||||
onHide
|
onHide
|
||||||
}: Params) => {
|
}: Params) => {
|
||||||
Toast.show({
|
switch (Platform.OS) {
|
||||||
type,
|
case 'ios':
|
||||||
position,
|
return Toast.show({
|
||||||
text1: message,
|
type,
|
||||||
text2: description,
|
position,
|
||||||
visibilityTime: 1500,
|
text1: message,
|
||||||
autoHide,
|
text2: description,
|
||||||
topOffset: 0,
|
visibilityTime: 1500,
|
||||||
bottomOffset: 0,
|
autoHide,
|
||||||
onShow: onShow,
|
topOffset: 0,
|
||||||
onHide: onHide
|
bottomOffset: 0,
|
||||||
})
|
onShow: onShow,
|
||||||
|
onHide: onHide
|
||||||
|
})
|
||||||
|
case 'android':
|
||||||
|
return ToastAndroid.show(message, ToastAndroid.SHORT)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ToastBase = ({ config }: { config: Config }) => {
|
const ToastBase = ({ config }: { config: Config }) => {
|
||||||
|
20
src/i18n/en/components/relativeTime.ts
Normal file
20
src/i18n/en/components/relativeTime.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
const strings = {
|
||||||
|
prefixAgo: null,
|
||||||
|
prefixFromNow: null,
|
||||||
|
suffixAgo: 'ago',
|
||||||
|
suffixFromNow: 'from now',
|
||||||
|
seconds: '%d seconds',
|
||||||
|
minute: 'about a minute',
|
||||||
|
minutes: '%d minutes',
|
||||||
|
hour: 'about an hour',
|
||||||
|
hours: 'about %d hours',
|
||||||
|
day: 'a day',
|
||||||
|
days: '%d days',
|
||||||
|
month: 'about a month',
|
||||||
|
months: '%d months',
|
||||||
|
year: 'about a year',
|
||||||
|
years: '%d years',
|
||||||
|
wordSeparator: ' '
|
||||||
|
}
|
||||||
|
|
||||||
|
export default strings
|
21
src/i18n/zh/components/relativeTime.ts
Normal file
21
src/i18n/zh/components/relativeTime.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
const strings = {
|
||||||
|
prefixAgo: null,
|
||||||
|
prefixFromNow: null,
|
||||||
|
suffixAgo: '之前',
|
||||||
|
suffixFromNow: '之后',
|
||||||
|
seconds: '%d秒',
|
||||||
|
minute: '大约1分钟',
|
||||||
|
minutes: '%d分钟',
|
||||||
|
hour: '大约1小时',
|
||||||
|
hours: '大约%d小时',
|
||||||
|
day: '1天',
|
||||||
|
days: '%d天',
|
||||||
|
month: '大约1个月',
|
||||||
|
months: '%d月',
|
||||||
|
year: '大约1年',
|
||||||
|
years: '%d年',
|
||||||
|
|
||||||
|
wordSeparator: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
export default strings
|
@ -1,4 +1,4 @@
|
|||||||
import { HeaderLeft } from '@components/Header'
|
import { HeaderCenter, HeaderLeft } from '@components/Header'
|
||||||
import ScreenMeBookmarks from '@screens/Me/Bookmarks'
|
import ScreenMeBookmarks from '@screens/Me/Bookmarks'
|
||||||
import ScreenMeConversations from '@screens/Me/Cconversations'
|
import ScreenMeConversations from '@screens/Me/Cconversations'
|
||||||
import ScreenMeFavourites from '@screens/Me/Favourites'
|
import ScreenMeFavourites from '@screens/Me/Favourites'
|
||||||
@ -11,6 +11,7 @@ import UpdateRemote from '@screens/Me/UpdateRemote'
|
|||||||
import sharedScreens from '@screens/Shared/sharedScreens'
|
import sharedScreens from '@screens/Shared/sharedScreens'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Platform } from 'react-native'
|
||||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||||
|
|
||||||
const Stack = createNativeStackNavigator<Nav.MeStackParamList>()
|
const Stack = createNativeStackNavigator<Nav.MeStackParamList>()
|
||||||
@ -19,7 +20,9 @@ const ScreenMe: React.FC = () => {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack.Navigator screenOptions={{ headerHideShadow: true, headerTopInsetEnabled: false }}>
|
<Stack.Navigator
|
||||||
|
screenOptions={{ headerHideShadow: true, headerTopInsetEnabled: false }}
|
||||||
|
>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name='Screen-Me-Root'
|
name='Screen-Me-Root'
|
||||||
component={ScreenMeRoot}
|
component={ScreenMeRoot}
|
||||||
@ -34,6 +37,11 @@ const ScreenMe: React.FC = () => {
|
|||||||
component={ScreenMeBookmarks}
|
component={ScreenMeBookmarks}
|
||||||
options={({ navigation }: any) => ({
|
options={({ navigation }: any) => ({
|
||||||
headerTitle: t('meBookmarks:heading'),
|
headerTitle: t('meBookmarks:heading'),
|
||||||
|
...(Platform.OS === 'android' && {
|
||||||
|
headerCenter: () => (
|
||||||
|
<HeaderCenter content={t('meBookmarks:heading')} />
|
||||||
|
)
|
||||||
|
}),
|
||||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
@ -42,6 +50,11 @@ const ScreenMe: React.FC = () => {
|
|||||||
component={ScreenMeConversations}
|
component={ScreenMeConversations}
|
||||||
options={({ navigation }: any) => ({
|
options={({ navigation }: any) => ({
|
||||||
headerTitle: t('meConversations:heading'),
|
headerTitle: t('meConversations:heading'),
|
||||||
|
...(Platform.OS === 'android' && {
|
||||||
|
headerCenter: () => (
|
||||||
|
<HeaderCenter content={t('meConversations:heading')} />
|
||||||
|
)
|
||||||
|
}),
|
||||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
@ -50,6 +63,11 @@ const ScreenMe: React.FC = () => {
|
|||||||
component={ScreenMeFavourites}
|
component={ScreenMeFavourites}
|
||||||
options={({ navigation }: any) => ({
|
options={({ navigation }: any) => ({
|
||||||
headerTitle: t('meFavourites:heading'),
|
headerTitle: t('meFavourites:heading'),
|
||||||
|
...(Platform.OS === 'android' && {
|
||||||
|
headerCenter: () => (
|
||||||
|
<HeaderCenter content={t('meFavourites:heading')} />
|
||||||
|
)
|
||||||
|
}),
|
||||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
@ -58,6 +76,9 @@ const ScreenMe: React.FC = () => {
|
|||||||
component={ScreenMeLists}
|
component={ScreenMeLists}
|
||||||
options={({ navigation }: any) => ({
|
options={({ navigation }: any) => ({
|
||||||
headerTitle: t('meLists:heading'),
|
headerTitle: t('meLists:heading'),
|
||||||
|
...(Platform.OS === 'android' && {
|
||||||
|
headerCenter: () => <HeaderCenter content={t('meLists:heading')} />
|
||||||
|
}),
|
||||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
@ -66,6 +87,11 @@ const ScreenMe: React.FC = () => {
|
|||||||
component={ScreenMeListsList}
|
component={ScreenMeListsList}
|
||||||
options={({ route, navigation }: any) => ({
|
options={({ route, navigation }: any) => ({
|
||||||
headerTitle: t('meListsList:heading', { list: route.params.title }),
|
headerTitle: t('meListsList:heading', { list: route.params.title }),
|
||||||
|
...(Platform.OS === 'android' && {
|
||||||
|
headerCenter: () => (
|
||||||
|
<HeaderCenter content={t('meListsList:heading')} />
|
||||||
|
)
|
||||||
|
}),
|
||||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
@ -74,6 +100,11 @@ const ScreenMe: React.FC = () => {
|
|||||||
component={ScreenMeSettings}
|
component={ScreenMeSettings}
|
||||||
options={({ navigation }: any) => ({
|
options={({ navigation }: any) => ({
|
||||||
headerTitle: t('meSettings:heading'),
|
headerTitle: t('meSettings:heading'),
|
||||||
|
...(Platform.OS === 'android' && {
|
||||||
|
headerCenter: () => (
|
||||||
|
<HeaderCenter content={t('meSettings:heading')} />
|
||||||
|
)
|
||||||
|
}),
|
||||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
@ -82,6 +113,11 @@ const ScreenMe: React.FC = () => {
|
|||||||
component={UpdateRemote}
|
component={UpdateRemote}
|
||||||
options={({ navigation }: any) => ({
|
options={({ navigation }: any) => ({
|
||||||
headerTitle: t('meSettingsUpdateRemote:heading'),
|
headerTitle: t('meSettingsUpdateRemote:heading'),
|
||||||
|
...(Platform.OS === 'android' && {
|
||||||
|
headerCenter: () => (
|
||||||
|
<HeaderCenter content={t('meSettingsUpdateRemote:heading')} />
|
||||||
|
)
|
||||||
|
}),
|
||||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
@ -90,7 +126,13 @@ const ScreenMe: React.FC = () => {
|
|||||||
component={ScreenMeSwitch}
|
component={ScreenMeSwitch}
|
||||||
options={({ navigation }: any) => ({
|
options={({ navigation }: any) => ({
|
||||||
stackPresentation: 'fullScreenModal',
|
stackPresentation: 'fullScreenModal',
|
||||||
|
headerShown: false,
|
||||||
headerTitle: t('meSettings:heading'),
|
headerTitle: t('meSettings:heading'),
|
||||||
|
...(Platform.OS === 'android' && {
|
||||||
|
headerCenter: () => (
|
||||||
|
<HeaderCenter content={t('meSettings:heading')} />
|
||||||
|
)
|
||||||
|
}),
|
||||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { HeaderLeft, HeaderRight } from '@components/Header'
|
import { HeaderCenter, HeaderLeft } from '@components/Header'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { StyleSheet } from 'react-native'
|
import { Platform, StyleSheet } from 'react-native'
|
||||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||||
import ScreenMeSwitchRoot from './Switch/Root'
|
import ScreenMeSwitchRoot from './Switch/Root'
|
||||||
|
|
||||||
@ -8,12 +8,17 @@ const Stack = createNativeStackNavigator()
|
|||||||
|
|
||||||
const ScreenMeSwitch: React.FC = ({ navigation }) => {
|
const ScreenMeSwitch: React.FC = ({ navigation }) => {
|
||||||
return (
|
return (
|
||||||
<Stack.Navigator screenOptions={{ headerHideShadow: true, headerTopInsetEnabled: false }}>
|
<Stack.Navigator
|
||||||
|
screenOptions={{ headerHideShadow: true, headerTopInsetEnabled: false }}
|
||||||
|
>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name='Screen-Me-Switch-Root'
|
name='Screen-Me-Switch-Root'
|
||||||
component={ScreenMeSwitchRoot}
|
component={ScreenMeSwitchRoot}
|
||||||
options={{
|
options={{
|
||||||
headerTitle: '切换账号',
|
headerTitle: '切换账号',
|
||||||
|
...(Platform.OS === 'android' && {
|
||||||
|
headerCenter: () => <HeaderCenter content='切换账号' />
|
||||||
|
}),
|
||||||
headerLeft: () => (
|
headerLeft: () => (
|
||||||
<HeaderLeft content='X' onPress={() => navigation.goBack()} />
|
<HeaderLeft content='X' onPress={() => navigation.goBack()} />
|
||||||
)
|
)
|
||||||
|
@ -12,7 +12,13 @@ import {
|
|||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { KeyboardAvoidingView, StyleSheet, Text, View } from 'react-native'
|
import {
|
||||||
|
KeyboardAvoidingView,
|
||||||
|
Platform,
|
||||||
|
StyleSheet,
|
||||||
|
Text,
|
||||||
|
View
|
||||||
|
} from 'react-native'
|
||||||
import { ScrollView } from 'react-native-gesture-handler'
|
import { ScrollView } from 'react-native-gesture-handler'
|
||||||
import { useQueryClient } from 'react-query'
|
import { useQueryClient } from 'react-query'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
@ -59,7 +65,10 @@ const ScreenMeSwitchRoot = () => {
|
|||||||
const localActiveIndex = useSelector(getLocalActiveIndex)
|
const localActiveIndex = useSelector(getLocalActiveIndex)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyboardAvoidingView behavior='padding' style={{ flex: 1 }}>
|
<KeyboardAvoidingView
|
||||||
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
>
|
||||||
<ScrollView keyboardShouldPersistTaps='handled'>
|
<ScrollView keyboardShouldPersistTaps='handled'>
|
||||||
<View style={styles.firstSection}>
|
<View style={styles.firstSection}>
|
||||||
<Text style={[styles.header, { color: theme.primary }]}>
|
<Text style={[styles.header, { color: theme.primary }]}>
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
import ComponentInstance from '@components/Instance'
|
import ComponentInstance from '@components/Instance'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { KeyboardAvoidingView } from 'react-native'
|
import { KeyboardAvoidingView, Platform } from 'react-native'
|
||||||
import { ScrollView } from 'react-native-gesture-handler'
|
import { ScrollView } from 'react-native-gesture-handler'
|
||||||
|
|
||||||
const UpdateRemote: React.FC = () => {
|
const UpdateRemote: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<KeyboardAvoidingView behavior='padding' style={{ flex: 1 }}>
|
<KeyboardAvoidingView
|
||||||
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
>
|
||||||
<ScrollView keyboardShouldPersistTaps='handled'>
|
<ScrollView keyboardShouldPersistTaps='handled'>
|
||||||
<ComponentInstance type='remote' disableHeaderImage />
|
<ComponentInstance type='remote' disableHeaderImage />
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
|
import { HeaderCenter } from '@components/Header'
|
||||||
import Timeline from '@components/Timelines/Timeline'
|
import Timeline from '@components/Timelines/Timeline'
|
||||||
import sharedScreens from '@screens/Shared/sharedScreens'
|
import sharedScreens from '@screens/Shared/sharedScreens'
|
||||||
import { getLocalActiveIndex } from '@utils/slices/instancesSlice'
|
import { getLocalActiveIndex } from '@utils/slices/instancesSlice'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Platform } from 'react-native'
|
||||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
@ -15,7 +17,13 @@ const ScreenNotifications: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<Stack.Navigator
|
<Stack.Navigator
|
||||||
screenOptions={{
|
screenOptions={{
|
||||||
|
headerLeft: () => null,
|
||||||
headerTitle: t('notifications:heading'),
|
headerTitle: t('notifications:heading'),
|
||||||
|
...(Platform.OS === 'android' && {
|
||||||
|
headerCenter: () => (
|
||||||
|
<HeaderCenter content={t('notifications:heading')} />
|
||||||
|
)
|
||||||
|
}),
|
||||||
headerHideShadow: true,
|
headerHideShadow: true,
|
||||||
headerTopInsetEnabled: false
|
headerTopInsetEnabled: false
|
||||||
}}
|
}}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import Button from '@components/Button'
|
import Button from '@components/Button'
|
||||||
import haptics from '@components/haptics'
|
import haptics from '@components/haptics'
|
||||||
import { ParseHTML } from '@components/Parse'
|
import { ParseHTML } from '@components/Parse'
|
||||||
|
import RelativeTime from '@components/RelativeTime'
|
||||||
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'
|
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'
|
||||||
import {
|
import {
|
||||||
useAnnouncementMutation,
|
useAnnouncementMutation,
|
||||||
@ -10,7 +11,6 @@ import { StyleConstants } from '@utils/styles/constants'
|
|||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useCallback, useEffect, useState } from 'react'
|
import React, { useCallback, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Moment from 'react-moment'
|
|
||||||
import {
|
import {
|
||||||
Dimensions,
|
Dimensions,
|
||||||
Image,
|
Image,
|
||||||
@ -80,13 +80,7 @@ const ScreenSharedAnnouncements: React.FC<SharedAnnouncementsProp> = ({
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Text style={[styles.published, { color: theme.secondary }]}>
|
<Text style={[styles.published, { color: theme.secondary }]}>
|
||||||
发布于{' '}
|
发布于 <RelativeTime date={item.published_at} />
|
||||||
<Moment
|
|
||||||
date={item.published_at}
|
|
||||||
locale={i18n.language}
|
|
||||||
element={Text}
|
|
||||||
fromNow
|
|
||||||
/>
|
|
||||||
</Text>
|
</Text>
|
||||||
<ScrollView style={styles.scrollView} showsVerticalScrollIndicator>
|
<ScrollView style={styles.scrollView} showsVerticalScrollIndicator>
|
||||||
<ParseHTML
|
<ParseHTML
|
||||||
|
@ -13,6 +13,7 @@ import {
|
|||||||
Alert,
|
Alert,
|
||||||
Keyboard,
|
Keyboard,
|
||||||
KeyboardAvoidingView,
|
KeyboardAvoidingView,
|
||||||
|
Platform,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Text
|
Text
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
@ -208,7 +209,10 @@ const Compose: React.FC<SharedComposeProp> = ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyboardAvoidingView behavior='padding' style={{ flex: 1 }}>
|
<KeyboardAvoidingView
|
||||||
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
>
|
||||||
<SafeAreaView
|
<SafeAreaView
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
edges={hasKeyboard ? ['left', 'right'] : ['left', 'right', 'bottom']}
|
edges={hasKeyboard ? ['left', 'right'] : ['left', 'right', 'bottom']}
|
||||||
@ -223,7 +227,7 @@ const Compose: React.FC<SharedComposeProp> = ({
|
|||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name='Screen-Shared-Compose-EditAttachment'
|
name='Screen-Shared-Compose-EditAttachment'
|
||||||
component={ComposeEditAttachment}
|
component={ComposeEditAttachment}
|
||||||
options={{ stackPresentation: 'modal' }}
|
options={{ stackPresentation: 'modal', headerShown: false }}
|
||||||
/>
|
/>
|
||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
</ComposeContext.Provider>
|
</ComposeContext.Provider>
|
||||||
|
@ -8,7 +8,7 @@ import React, {
|
|||||||
useRef,
|
useRef,
|
||||||
useState
|
useState
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { Alert, KeyboardAvoidingView } from 'react-native'
|
import { Alert, KeyboardAvoidingView, Platform } from 'react-native'
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||||
import ComposeEditAttachmentRoot from './EditAttachment/Root'
|
import ComposeEditAttachmentRoot from './EditAttachment/Root'
|
||||||
@ -144,7 +144,10 @@ const ComposeEditAttachment: React.FC<Props> = ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyboardAvoidingView behavior='padding' style={{ flex: 1 }}>
|
<KeyboardAvoidingView
|
||||||
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
>
|
||||||
<SafeAreaView style={{ flex: 1 }} edges={['left', 'right', 'bottom']}>
|
<SafeAreaView style={{ flex: 1 }} edges={['left', 'right', 'bottom']}>
|
||||||
<Stack.Navigator screenOptions={{ headerTopInsetEnabled: false }}>
|
<Stack.Navigator screenOptions={{ headerTopInsetEnabled: false }}>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
|
@ -122,30 +122,26 @@ const ComposeEditAttachmentImage: React.FC<Props> = ({ index, focus }) => {
|
|||||||
styleTransform,
|
styleTransform,
|
||||||
{
|
{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: -1000 + imageDimensionis.height / 2,
|
top: -500 + imageDimensionis.height / 2,
|
||||||
left: -1000 + imageDimensionis.width / 2
|
left: -500 + imageDimensionis.width / 2
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Svg width='2000' height='2000' viewBox='0 0 2000 2000'>
|
<Svg width='1000' height='1000' viewBox='0 0 1000 1000'>
|
||||||
<G>
|
<G stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'>
|
||||||
<G id='Mask'>
|
<G>
|
||||||
<Path
|
<Path
|
||||||
d={
|
d='M1000,0 L1000,1000 L0,1000 L0,0 L1000,0 Z M500,475 C486.192881,475 475,486.192881 475,500 C475,513.807119 486.192881,525 500,525 C513.807119,525 525,513.807119 525,500 C525,486.192881 513.807119,475 500,475 Z'
|
||||||
'M2000,0 L2000,2000 L0,2000 L0,0 L2000,0 Z M1000,967 C981.774603,967 967,981.774603 967,1000 C967,1018.2254 981.774603,1033 1000,1033 C1018.2254,1033 1033,1018.2254 1033,1000 C1033,981.774603 1018.2254,967 1000,967 Z'
|
|
||||||
}
|
|
||||||
fill={theme.backgroundOverlay}
|
fill={theme.backgroundOverlay}
|
||||||
/>
|
/>
|
||||||
<G transform='translate(967, 967)'>
|
<Circle
|
||||||
<Circle
|
stroke={theme.primaryOverlay}
|
||||||
stroke={theme.primaryOverlay}
|
stroke-width='2'
|
||||||
strokeWidth='2'
|
cx='500'
|
||||||
cx='33'
|
cy='500'
|
||||||
cy='33'
|
r='24'
|
||||||
r='33'
|
/>
|
||||||
/>
|
<Circle fill={theme.primaryOverlay} cx='500' cy='500' r='2' />
|
||||||
<Circle fill={theme.primaryOverlay} cx='33' cy='33' r='2' />
|
|
||||||
</G>
|
|
||||||
</G>
|
</G>
|
||||||
</G>
|
</G>
|
||||||
</Svg>
|
</Svg>
|
||||||
|
@ -10,27 +10,40 @@ import { ActionSheetOptions } from '@expo/react-native-action-sheet'
|
|||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
composeDispatch: Dispatch<ComposeAction>
|
composeDispatch: Dispatch<ComposeAction>
|
||||||
showActionSheetWithOptions: (options: ActionSheetOptions, callback: (i: number) => void) => void
|
showActionSheetWithOptions: (
|
||||||
|
options: ActionSheetOptions,
|
||||||
|
callback: (i: number) => void
|
||||||
|
) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const addAttachment = async ({ composeDispatch, showActionSheetWithOptions }: Props): Promise<any> => {
|
const addAttachment = async ({
|
||||||
|
composeDispatch,
|
||||||
|
showActionSheetWithOptions
|
||||||
|
}: Props): Promise<any> => {
|
||||||
const uploadAttachment = async (result: ImageInfo) => {
|
const uploadAttachment = async (result: ImageInfo) => {
|
||||||
const hash = await Crypto.digestStringAsync(
|
const hash = await Crypto.digestStringAsync(
|
||||||
Crypto.CryptoDigestAlgorithm.SHA256,
|
Crypto.CryptoDigestAlgorithm.SHA256,
|
||||||
result.uri + Math.random()
|
result.uri + Math.random()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
let attachmentType: string
|
||||||
|
// https://github.com/expo/expo/issues/11214
|
||||||
|
const attachmentUri = result.uri.replace('file:/data', 'file:///data')
|
||||||
|
|
||||||
switch (result.type) {
|
switch (result.type) {
|
||||||
case 'image':
|
case 'image':
|
||||||
|
attachmentType = `image/${attachmentUri.split('.')[1]}`
|
||||||
composeDispatch({
|
composeDispatch({
|
||||||
type: 'attachment/upload/start',
|
type: 'attachment/upload/start',
|
||||||
payload: {
|
payload: {
|
||||||
local: { ...result, local_thumbnail: result.uri, hash },
|
local: { ...result, local_thumbnail: attachmentUri, hash },
|
||||||
uploading: true
|
uploading: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
case 'video':
|
case 'video':
|
||||||
VideoThumbnails.getThumbnailAsync(result.uri)
|
attachmentType = `video/${attachmentUri.split('.')[1]}`
|
||||||
|
VideoThumbnails.getThumbnailAsync(attachmentUri)
|
||||||
.then(({ uri }) =>
|
.then(({ uri }) =>
|
||||||
composeDispatch({
|
composeDispatch({
|
||||||
type: 'attachment/upload/start',
|
type: 'attachment/upload/start',
|
||||||
@ -51,6 +64,7 @@ const addAttachment = async ({ composeDispatch, showActionSheetWithOptions }: Pr
|
|||||||
)
|
)
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
|
attachmentType = 'unknown'
|
||||||
composeDispatch({
|
composeDispatch({
|
||||||
type: 'attachment/upload/start',
|
type: 'attachment/upload/start',
|
||||||
payload: {
|
payload: {
|
||||||
@ -64,9 +78,9 @@ const addAttachment = async ({ composeDispatch, showActionSheetWithOptions }: Pr
|
|||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('file', {
|
formData.append('file', {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
uri: result.uri,
|
uri: attachmentUri,
|
||||||
name: result.uri.split('/').pop(),
|
name: attachmentType,
|
||||||
type: 'image/jpeg/jpg'
|
type: attachmentType
|
||||||
})
|
})
|
||||||
|
|
||||||
return client<Mastodon.Attachment>({
|
return client<Mastodon.Attachment>({
|
||||||
|
@ -8,6 +8,7 @@ import { useTheme } from '@utils/styles/ThemeManager'
|
|||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
KeyboardAvoidingView,
|
KeyboardAvoidingView,
|
||||||
|
Platform,
|
||||||
Pressable,
|
Pressable,
|
||||||
SectionList,
|
SectionList,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
@ -163,14 +164,17 @@ const ScreenSharedSearch: React.FC<Props> = ({ searchTerm }) => {
|
|||||||
</Pressable>
|
</Pressable>
|
||||||
)
|
)
|
||||||
case 'statuses':
|
case 'statuses':
|
||||||
return <TimelineDefault item={item} index={index} disableDetails />
|
return <TimelineDefault item={item} disableDetails />
|
||||||
default:
|
default:
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyboardAvoidingView behavior='padding' style={{ flex: 1 }}>
|
<KeyboardAvoidingView
|
||||||
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
>
|
||||||
<SectionList
|
<SectionList
|
||||||
style={styles.base}
|
style={styles.base}
|
||||||
renderItem={listItem}
|
renderItem={listItem}
|
||||||
|
@ -107,7 +107,8 @@ const sharedScreens = (
|
|||||||
component={ScreenSharedAnnouncements}
|
component={ScreenSharedAnnouncements}
|
||||||
options={{
|
options={{
|
||||||
stackPresentation: 'transparentModal',
|
stackPresentation: 'transparentModal',
|
||||||
stackAnimation: 'fade'
|
stackAnimation: 'fade',
|
||||||
|
headerShown: false
|
||||||
}}
|
}}
|
||||||
/>,
|
/>,
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
@ -115,7 +116,8 @@ const sharedScreens = (
|
|||||||
name='Screen-Shared-Compose'
|
name='Screen-Shared-Compose'
|
||||||
component={Compose}
|
component={Compose}
|
||||||
options={{
|
options={{
|
||||||
stackPresentation: 'fullScreenModal'
|
stackPresentation: 'fullScreenModal',
|
||||||
|
headerShown: false
|
||||||
}}
|
}}
|
||||||
/>,
|
/>,
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
@ -133,7 +135,8 @@ const sharedScreens = (
|
|||||||
component={ScreenSharedImagesViewer}
|
component={ScreenSharedImagesViewer}
|
||||||
options={{
|
options={{
|
||||||
stackPresentation: 'transparentModal',
|
stackPresentation: 'transparentModal',
|
||||||
stackAnimation: 'none'
|
stackAnimation: 'none',
|
||||||
|
headerShown: false
|
||||||
}}
|
}}
|
||||||
/>,
|
/>,
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
|
22
yarn.lock
22
yarn.lock
@ -2210,6 +2210,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
|
|
||||||
|
"@types/react-timeago@^4.1.2":
|
||||||
|
version "4.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/react-timeago/-/react-timeago-4.1.2.tgz#fc365ac4483888e9b47267259416be2fd5cf765f"
|
||||||
|
integrity sha512-gkhU3rH7aZgeRybbm9ie9wHOM9i1I5YhUoto/uqY/DAbeRZuLU8ugl6E97jp65XCl9QTij32Vs7BAX2E/MqOAw==
|
||||||
|
dependencies:
|
||||||
|
"@types/react" "*"
|
||||||
|
|
||||||
"@types/react@*":
|
"@types/react@*":
|
||||||
version "17.0.0"
|
version "17.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.0.tgz#5af3eb7fad2807092f0046a1302b7823e27919b8"
|
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.0.tgz#5af3eb7fad2807092f0046a1302b7823e27919b8"
|
||||||
@ -7315,11 +7322,6 @@ mkdirp@^1.0.3:
|
|||||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||||
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||||
|
|
||||||
moment@^2.29.1:
|
|
||||||
version "2.29.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
|
|
||||||
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
|
|
||||||
|
|
||||||
ms@2.0.0:
|
ms@2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||||
@ -8186,11 +8188,6 @@ react-is@^17.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339"
|
||||||
integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==
|
integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==
|
||||||
|
|
||||||
react-moment@^1.1.1:
|
|
||||||
version "1.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/react-moment/-/react-moment-1.1.1.tgz#5fe9fb257039590c804e2b3aedfc3ceb0a6ffb16"
|
|
||||||
integrity sha512-WjwvxBSnmLMRcU33do0KixDB+9vP3e84eCse+rd+HNklAMNWyRgZTDEQlay/qK6lcXFPRuEIASJTpEt6pyK7Ww==
|
|
||||||
|
|
||||||
react-native-animated-spinkit@^1.4.2:
|
react-native-animated-spinkit@^1.4.2:
|
||||||
version "1.4.2"
|
version "1.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/react-native-animated-spinkit/-/react-native-animated-spinkit-1.4.2.tgz#cb60ff8bcc2bb848409d9aa85ed528646ad1f953"
|
resolved "https://registry.yarnpkg.com/react-native-animated-spinkit/-/react-native-animated-spinkit-1.4.2.tgz#cb60ff8bcc2bb848409d9aa85ed528646ad1f953"
|
||||||
@ -8380,6 +8377,11 @@ react-test-renderer@~16.11.0:
|
|||||||
react-is "^16.8.6"
|
react-is "^16.8.6"
|
||||||
scheduler "^0.17.0"
|
scheduler "^0.17.0"
|
||||||
|
|
||||||
|
react-timeago@^5.2.0:
|
||||||
|
version "5.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-timeago/-/react-timeago-5.2.0.tgz#d655d40aa55e4fe08a92234481a6aea7f656ab5d"
|
||||||
|
integrity sha512-wCEEDGQHMdFh/PLp+Hj5vk9ZoC4KjQ5u0u6+KrrY9rny5LqJ2gZvNNEAS4mhSZDV1i7JLgQI5VQTAux7f+vj2w==
|
||||||
|
|
||||||
react@16.13.1:
|
react@16.13.1:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
|
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user