1
0
mirror of https://github.com/tooot-app/app synced 2024-12-27 09:52:41 +01:00

More adaption to Android

This commit is contained in:
Zhiyuan Zheng 2021-01-14 00:43:35 +01:00
parent 49715bba0d
commit 95f500ae72
No known key found for this signature in database
GPG Key ID: 078A93AB607D85E0
28 changed files with 376 additions and 169 deletions

View File

@ -15,10 +15,6 @@ import { QueryClient, QueryClientProvider } from 'react-query'
import { Provider } from 'react-redux'
import { PersistGate } from 'redux-persist/integration/react'
import { LogBox, Platform } from 'react-native'
import Moment from 'react-moment'
import moment from 'moment'
import 'moment/min/locales'
if (Platform.OS === 'android') {
LogBox.ignoreLogs(['Setting a timer for a long period of time'])
@ -28,8 +24,6 @@ dev()
sentry()
audio()
onlineStatus()
Moment.globalMoment = moment
Moment.startPooledTimer(1000)
log('log', 'react-query', 'initializing')
const queryClient = new QueryClient()

View File

@ -44,12 +44,10 @@
"gl-react-expo": "^4.0.1",
"i18next": "^19.8.4",
"lodash": "^4.17.20",
"moment": "^2.29.1",
"pretty-bytes": "^5.5.0",
"react": "16.13.1",
"react-dom": "16.13.1",
"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-animated-spinkit": "^1.4.2",
"react-native-expo-image-cache": "^4.1.0",
@ -67,6 +65,7 @@
"react-navigation": "^4.4.3",
"react-query": "^3.5.6",
"react-redux": "^7.2.2",
"react-timeago": "^5.2.0",
"redux-persist": "^6.0.0",
"redux-persist-expo-securestore": "^2.0.0",
"sentry-expo": "^3.0.4",
@ -93,6 +92,7 @@
"@types/react-navigation": "^3.4.0",
"@types/react-redux": "^7.1.12",
"@types/react-test-renderer": "^17.0.0",
"@types/react-timeago": "^4.1.2",
"@welldone-software/why-did-you-render": "^6.0.4",
"babel-plugin-module-resolver": "^4.1.0",
"chalk": "^4.1.0",

View File

@ -7,6 +7,7 @@ import {
createBottomTabNavigator
} from '@react-navigation/bottom-tabs'
import {
getFocusedRouteNameFromRoute,
NavigationContainer,
NavigationContainerRef
} from '@react-navigation/native'
@ -31,7 +32,7 @@ import React, {
useMemo,
useRef
} from 'react'
import { StatusBar } from 'react-native'
import { Platform, StatusBar } from 'react-native'
import Toast from 'react-native-toast-message'
import { useDispatch, useSelector } from 'react-redux'
@ -172,6 +173,7 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
}) => {
let name: any
let updateColor: string = color
console.log()
switch (route.name) {
case 'Screen-Local':
name = 'Home'
@ -195,7 +197,16 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
break
}
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,
inactiveTintColor:
localActiveIndex !== null ? theme.secondary : theme.disabled,
showLabel: false
showLabel: false,
...(Platform.OS === 'android' && { keyboardHidesTabBar: true })
}),
[theme, localActiveIndex]
)
@ -292,7 +304,9 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
<Tab.Screen name='Screen-Me' component={ScreenMe} />
</Tab.Navigator>
{/* <Toast ref={Toast.setRef} config={toastConfig} /> */}
{Platform.OS === 'ios' ? (
<Toast ref={Toast.setRef} config={toastConfig} />
) : null}
</NavigationContainer>
</>
)

View File

@ -1,4 +1,5 @@
import HeaderLeft from '@components/Header/Left'
import HeaderCenter from '@components/Header/Center'
import HeaderRight from '@components/Header/Right'
export { HeaderLeft, HeaderRight }
export { HeaderLeft, HeaderCenter, HeaderRight }

View 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

View File

@ -45,9 +45,12 @@ const renderNode = ({
? routeParams.hashtag !== tag[1] && routeParams.hashtag !== tag[2]
: true
return (
<Pressable
<Text
key={index}
hitSlop={StyleConstants.Font.Size[size] / 2}
style={{
color: theme.blue,
...StyleConstants.FontStyle[size]
}}
onPress={() => {
!disableDetails &&
differentTag &&
@ -56,16 +59,9 @@ const renderNode = ({
})
}}
>
<Text
style={{
color: theme.blue,
...StyleConstants.FontStyle[size]
}}
>
{node.children[0].data}
{node.children[1]?.children[0].data}
</Text>
</Pressable>
{node.children[0].data}
{node.children[1]?.children[0].data}
</Text>
)
} else if (classes.includes('mention') && mentions) {
const accountIndex = mentions.findIndex(
@ -75,9 +71,12 @@ const renderNode = ({
? routeParams.account.id !== mentions[accountIndex].id
: true
return (
<Pressable
<Text
key={index}
hitSlop={StyleConstants.Font.Size[size] / 2}
style={{
color: accountIndex !== -1 ? theme.blue : undefined,
...StyleConstants.FontStyle[size]
}}
onPress={() => {
accountIndex !== -1 &&
!disableDetails &&
@ -87,16 +86,9 @@ const renderNode = ({
})
}}
>
<Text
style={{
color: accountIndex !== -1 ? theme.blue : undefined,
...StyleConstants.FontStyle[size]
}}
>
{node.children[0].data}
{node.children[1]?.children[0].data}
</Text>
</Pressable>
{node.children[0].data}
{node.children[1]?.children[0].data}
</Text>
)
}
} else {
@ -107,9 +99,12 @@ const renderNode = ({
const shouldBeTag =
tags && tags.filter(tag => `#${tag.name}` === content).length > 0
return (
<Pressable
<Text
key={index}
hitSlop={StyleConstants.Font.Size[size] / 2}
style={{
color: theme.blue,
...StyleConstants.FontStyle[size]
}}
onPress={async () =>
!disableDetails && !shouldBeTag
? await openLink(href)
@ -118,22 +113,15 @@ const renderNode = ({
})
}
>
<Text
style={{
color: theme.blue,
...StyleConstants.FontStyle[size]
}}
>
{!shouldBeTag ? (
<Icon
color={theme.blue}
name='ExternalLink'
size={StyleConstants.Font.Size[size]}
/>
) : null}
{content || (showFullLink ? href : domain[1])}
</Text>
</Pressable>
{!shouldBeTag ? (
<Icon
color={theme.blue}
name='ExternalLink'
size={StyleConstants.Font.Size[size]}
/>
) : null}
{content || (showFullLink ? href : domain[1])}
</Text>
)
}
break

View 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

View File

@ -1,12 +1,12 @@
import { HeaderRight } from '@components/Header'
import { HeaderCenter, HeaderRight } from '@components/Header'
import Timeline from '@components/Timelines/Timeline'
import SegmentedControl from '@react-native-community/segmented-control'
import { useNavigation } from '@react-navigation/native'
import sharedScreens from '@screens/Shared/sharedScreens'
import { getLocalActiveIndex, getRemoteUrl } from '@utils/slices/instancesSlice'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback, useState } from 'react'
import { Dimensions, StyleSheet, View } from 'react-native'
import React, { useCallback, useMemo, useState } from 'react'
import { Dimensions, Platform, StyleSheet, View } from 'react-native'
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
import { TabView } from 'react-native-tab-view'
import { useSelector } from 'react-redux'
@ -68,6 +68,40 @@ const Timelines: React.FC<Props> = ({ name, content }) => {
[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 (
<Stack.Navigator
screenOptions={{ headerHideShadow: true, headerTopInsetEnabled: false }}
@ -76,29 +110,7 @@ const Timelines: React.FC<Props> = ({ name, content }) => {
// @ts-ignore
name={`Screen-${name}-Root`}
component={screenComponent}
options={{
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} />
)
})
}}
options={screenOptions}
/>
{sharedScreens(Stack)}

View File

@ -9,13 +9,14 @@ import { useScrollToTop } from '@react-navigation/native'
import { localUpdateNotification } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
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 { useDispatch } from 'react-redux'
import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline'
import { findIndex } from 'lodash'
import CustomRefreshControl from '@components/CustomRefreshControl'
import { InfiniteData, useQueryClient } from 'react-query'
import { useTheme } from '@utils/styles/ThemeManager'
export interface Props {
page: App.Pages
@ -36,6 +37,8 @@ const Timeline: React.FC<Props> = ({
disableRefresh = false,
disableInfinity = false
}) => {
const { theme } = useTheme()
const queryKeyParams = {
page,
...(hashtag && { hashtag }),
@ -163,8 +166,13 @@ const Timeline: React.FC<Props> = ({
const refreshControl = useMemo(
() => (
<RefreshControl
{...(Platform.OS === 'android' && { enabled: true })}
refreshing={
refreshCount.current < 2 ? isFetchingPreviousPage : isFetching
refreshCount.current < 2
? Platform.OS === 'ios'
? isFetchingPreviousPage
: isFetchingPreviousPage || isFetching
: isFetching
}
onRefresh={async () => {
if (refreshCount.current < 2) {

View File

@ -1,8 +1,8 @@
import RelativeTime from '@components/RelativeTime'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React from 'react'
import { useTranslation } from 'react-i18next'
import Moment from 'react-moment'
import { StyleSheet, Text } from 'react-native'
export interface Props {
@ -15,7 +15,7 @@ const HeaderSharedCreated: React.FC<Props> = ({ created_at }) => {
return (
<Text style={[styles.created_at, { color: theme.secondary }]}>
<Moment date={created_at} locale={i18n.language} element={Text} fromNow />
<RelativeTime date={created_at} />
</Text>
)
}

View File

@ -1,7 +1,7 @@
import Button from '@components/Button'
import haptics from '@components/haptics'
import Icon from '@components/Icon'
import relativeTime from '@components/relativeTime'
import RelativeTime from '@components/RelativeTime'
import { ParseEmojis } from '@root/components/Parse'
import { toast } from '@root/components/toast'
import {
@ -13,7 +13,6 @@ import { useTheme } from '@utils/styles/ThemeManager'
import { maxBy } from 'lodash'
import React, { useCallback, useMemo, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import Moment from 'react-moment'
import { Pressable, StyleSheet, Text, View } from 'react-native'
import { useQueryClient } from 'react-query'
@ -127,14 +126,7 @@ const TimelinePoll: React.FC<Props> = ({
<Text style={[styles.expiration, { color: theme.secondary }]}>
<Trans
i18nKey='timeline:shared.poll.meta.expiration.until'
components={[
<Moment
date={poll.expires_at}
locale={i18n.language}
element={Text}
fromNow
/>
]}
components={[<RelativeTime date={poll.expires_at} />]}
/>
</Text>
)

View File

@ -1,9 +1,17 @@
import * as Haptics from 'expo-haptics'
import { Platform } from 'react-native'
import * as Sentry from 'sentry-expo'
const haptics = (
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) {
case 'Success':
case 'Warning':

View File

@ -2,7 +2,7 @@ import Icon from '@components/Icon'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
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 Toast from 'react-native-toast-message'
import * as Sentry from 'sentry-expo'
@ -33,18 +33,23 @@ const toast = ({
onShow,
onHide
}: Params) => {
Toast.show({
type,
position,
text1: message,
text2: description,
visibilityTime: 1500,
autoHide,
topOffset: 0,
bottomOffset: 0,
onShow: onShow,
onHide: onHide
})
switch (Platform.OS) {
case 'ios':
return Toast.show({
type,
position,
text1: message,
text2: description,
visibilityTime: 1500,
autoHide,
topOffset: 0,
bottomOffset: 0,
onShow: onShow,
onHide: onHide
})
case 'android':
return ToastAndroid.show(message, ToastAndroid.SHORT)
}
}
const ToastBase = ({ config }: { config: Config }) => {

View 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

View 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

View File

@ -1,4 +1,4 @@
import { HeaderLeft } from '@components/Header'
import { HeaderCenter, HeaderLeft } from '@components/Header'
import ScreenMeBookmarks from '@screens/Me/Bookmarks'
import ScreenMeConversations from '@screens/Me/Cconversations'
import ScreenMeFavourites from '@screens/Me/Favourites'
@ -11,6 +11,7 @@ import UpdateRemote from '@screens/Me/UpdateRemote'
import sharedScreens from '@screens/Shared/sharedScreens'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { Platform } from 'react-native'
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
const Stack = createNativeStackNavigator<Nav.MeStackParamList>()
@ -19,7 +20,9 @@ const ScreenMe: React.FC = () => {
const { t } = useTranslation()
return (
<Stack.Navigator screenOptions={{ headerHideShadow: true, headerTopInsetEnabled: false }}>
<Stack.Navigator
screenOptions={{ headerHideShadow: true, headerTopInsetEnabled: false }}
>
<Stack.Screen
name='Screen-Me-Root'
component={ScreenMeRoot}
@ -34,6 +37,11 @@ const ScreenMe: React.FC = () => {
component={ScreenMeBookmarks}
options={({ navigation }: any) => ({
headerTitle: t('meBookmarks:heading'),
...(Platform.OS === 'android' && {
headerCenter: () => (
<HeaderCenter content={t('meBookmarks:heading')} />
)
}),
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
})}
/>
@ -42,6 +50,11 @@ const ScreenMe: React.FC = () => {
component={ScreenMeConversations}
options={({ navigation }: any) => ({
headerTitle: t('meConversations:heading'),
...(Platform.OS === 'android' && {
headerCenter: () => (
<HeaderCenter content={t('meConversations:heading')} />
)
}),
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
})}
/>
@ -50,6 +63,11 @@ const ScreenMe: React.FC = () => {
component={ScreenMeFavourites}
options={({ navigation }: any) => ({
headerTitle: t('meFavourites:heading'),
...(Platform.OS === 'android' && {
headerCenter: () => (
<HeaderCenter content={t('meFavourites:heading')} />
)
}),
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
})}
/>
@ -58,6 +76,9 @@ const ScreenMe: React.FC = () => {
component={ScreenMeLists}
options={({ navigation }: any) => ({
headerTitle: t('meLists:heading'),
...(Platform.OS === 'android' && {
headerCenter: () => <HeaderCenter content={t('meLists:heading')} />
}),
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
})}
/>
@ -66,6 +87,11 @@ const ScreenMe: React.FC = () => {
component={ScreenMeListsList}
options={({ route, navigation }: any) => ({
headerTitle: t('meListsList:heading', { list: route.params.title }),
...(Platform.OS === 'android' && {
headerCenter: () => (
<HeaderCenter content={t('meListsList:heading')} />
)
}),
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
})}
/>
@ -74,6 +100,11 @@ const ScreenMe: React.FC = () => {
component={ScreenMeSettings}
options={({ navigation }: any) => ({
headerTitle: t('meSettings:heading'),
...(Platform.OS === 'android' && {
headerCenter: () => (
<HeaderCenter content={t('meSettings:heading')} />
)
}),
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
})}
/>
@ -82,6 +113,11 @@ const ScreenMe: React.FC = () => {
component={UpdateRemote}
options={({ navigation }: any) => ({
headerTitle: t('meSettingsUpdateRemote:heading'),
...(Platform.OS === 'android' && {
headerCenter: () => (
<HeaderCenter content={t('meSettingsUpdateRemote:heading')} />
)
}),
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
})}
/>
@ -90,7 +126,13 @@ const ScreenMe: React.FC = () => {
component={ScreenMeSwitch}
options={({ navigation }: any) => ({
stackPresentation: 'fullScreenModal',
headerShown: false,
headerTitle: t('meSettings:heading'),
...(Platform.OS === 'android' && {
headerCenter: () => (
<HeaderCenter content={t('meSettings:heading')} />
)
}),
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
})}
/>

View File

@ -1,6 +1,6 @@
import { HeaderLeft, HeaderRight } from '@components/Header'
import { HeaderCenter, HeaderLeft } from '@components/Header'
import React from 'react'
import { StyleSheet } from 'react-native'
import { Platform, StyleSheet } from 'react-native'
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
import ScreenMeSwitchRoot from './Switch/Root'
@ -8,12 +8,17 @@ const Stack = createNativeStackNavigator()
const ScreenMeSwitch: React.FC = ({ navigation }) => {
return (
<Stack.Navigator screenOptions={{ headerHideShadow: true, headerTopInsetEnabled: false }}>
<Stack.Navigator
screenOptions={{ headerHideShadow: true, headerTopInsetEnabled: false }}
>
<Stack.Screen
name='Screen-Me-Switch-Root'
component={ScreenMeSwitchRoot}
options={{
headerTitle: '切换账号',
...(Platform.OS === 'android' && {
headerCenter: () => <HeaderCenter content='切换账号' />
}),
headerLeft: () => (
<HeaderLeft content='X' onPress={() => navigation.goBack()} />
)

View File

@ -12,7 +12,13 @@ import {
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
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 { useQueryClient } from 'react-query'
import { useDispatch, useSelector } from 'react-redux'
@ -59,7 +65,10 @@ const ScreenMeSwitchRoot = () => {
const localActiveIndex = useSelector(getLocalActiveIndex)
return (
<KeyboardAvoidingView behavior='padding' style={{ flex: 1 }}>
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={{ flex: 1 }}
>
<ScrollView keyboardShouldPersistTaps='handled'>
<View style={styles.firstSection}>
<Text style={[styles.header, { color: theme.primary }]}>

View File

@ -1,11 +1,14 @@
import ComponentInstance from '@components/Instance'
import React from 'react'
import { KeyboardAvoidingView } from 'react-native'
import { KeyboardAvoidingView, Platform } from 'react-native'
import { ScrollView } from 'react-native-gesture-handler'
const UpdateRemote: React.FC = () => {
return (
<KeyboardAvoidingView behavior='padding' style={{ flex: 1 }}>
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={{ flex: 1 }}
>
<ScrollView keyboardShouldPersistTaps='handled'>
<ComponentInstance type='remote' disableHeaderImage />
</ScrollView>

View File

@ -1,8 +1,10 @@
import { HeaderCenter } from '@components/Header'
import Timeline from '@components/Timelines/Timeline'
import sharedScreens from '@screens/Shared/sharedScreens'
import { getLocalActiveIndex } from '@utils/slices/instancesSlice'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { Platform } from 'react-native'
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
import { useSelector } from 'react-redux'
@ -15,7 +17,13 @@ const ScreenNotifications: React.FC = () => {
return (
<Stack.Navigator
screenOptions={{
headerLeft: () => null,
headerTitle: t('notifications:heading'),
...(Platform.OS === 'android' && {
headerCenter: () => (
<HeaderCenter content={t('notifications:heading')} />
)
}),
headerHideShadow: true,
headerTopInsetEnabled: false
}}

View File

@ -1,6 +1,7 @@
import Button from '@components/Button'
import haptics from '@components/haptics'
import { ParseHTML } from '@components/Parse'
import RelativeTime from '@components/RelativeTime'
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'
import {
useAnnouncementMutation,
@ -10,7 +11,6 @@ import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Moment from 'react-moment'
import {
Dimensions,
Image,
@ -80,13 +80,7 @@ const ScreenSharedAnnouncements: React.FC<SharedAnnouncementsProp> = ({
]}
>
<Text style={[styles.published, { color: theme.secondary }]}>
{' '}
<Moment
date={item.published_at}
locale={i18n.language}
element={Text}
fromNow
/>
<RelativeTime date={item.published_at} />
</Text>
<ScrollView style={styles.scrollView} showsVerticalScrollIndicator>
<ParseHTML

View File

@ -13,6 +13,7 @@ import {
Alert,
Keyboard,
KeyboardAvoidingView,
Platform,
StyleSheet,
Text
} from 'react-native'
@ -208,7 +209,10 @@ const Compose: React.FC<SharedComposeProp> = ({
)
return (
<KeyboardAvoidingView behavior='padding' style={{ flex: 1 }}>
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={{ flex: 1 }}
>
<SafeAreaView
style={{ flex: 1 }}
edges={hasKeyboard ? ['left', 'right'] : ['left', 'right', 'bottom']}
@ -223,7 +227,7 @@ const Compose: React.FC<SharedComposeProp> = ({
<Stack.Screen
name='Screen-Shared-Compose-EditAttachment'
component={ComposeEditAttachment}
options={{ stackPresentation: 'modal' }}
options={{ stackPresentation: 'modal', headerShown: false }}
/>
</Stack.Navigator>
</ComposeContext.Provider>

View File

@ -8,7 +8,7 @@ import React, {
useRef,
useState
} from 'react'
import { Alert, KeyboardAvoidingView } from 'react-native'
import { Alert, KeyboardAvoidingView, Platform } from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context'
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
import ComposeEditAttachmentRoot from './EditAttachment/Root'
@ -144,7 +144,10 @@ const ComposeEditAttachment: React.FC<Props> = ({
)
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']}>
<Stack.Navigator screenOptions={{ headerTopInsetEnabled: false }}>
<Stack.Screen

View File

@ -122,30 +122,26 @@ const ComposeEditAttachmentImage: React.FC<Props> = ({ index, focus }) => {
styleTransform,
{
position: 'absolute',
top: -1000 + imageDimensionis.height / 2,
left: -1000 + imageDimensionis.width / 2
top: -500 + imageDimensionis.height / 2,
left: -500 + imageDimensionis.width / 2
}
]}
>
<Svg width='2000' height='2000' viewBox='0 0 2000 2000'>
<G>
<G id='Mask'>
<Svg width='1000' height='1000' viewBox='0 0 1000 1000'>
<G stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'>
<G>
<Path
d={
'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'
}
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'
fill={theme.backgroundOverlay}
/>
<G transform='translate(967, 967)'>
<Circle
stroke={theme.primaryOverlay}
strokeWidth='2'
cx='33'
cy='33'
r='33'
/>
<Circle fill={theme.primaryOverlay} cx='33' cy='33' r='2' />
</G>
<Circle
stroke={theme.primaryOverlay}
stroke-width='2'
cx='500'
cy='500'
r='24'
/>
<Circle fill={theme.primaryOverlay} cx='500' cy='500' r='2' />
</G>
</G>
</Svg>

View File

@ -10,27 +10,40 @@ import { ActionSheetOptions } from '@expo/react-native-action-sheet'
export interface Props {
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 hash = await Crypto.digestStringAsync(
Crypto.CryptoDigestAlgorithm.SHA256,
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) {
case 'image':
attachmentType = `image/${attachmentUri.split('.')[1]}`
composeDispatch({
type: 'attachment/upload/start',
payload: {
local: { ...result, local_thumbnail: result.uri, hash },
local: { ...result, local_thumbnail: attachmentUri, hash },
uploading: true
}
})
break
case 'video':
VideoThumbnails.getThumbnailAsync(result.uri)
attachmentType = `video/${attachmentUri.split('.')[1]}`
VideoThumbnails.getThumbnailAsync(attachmentUri)
.then(({ uri }) =>
composeDispatch({
type: 'attachment/upload/start',
@ -51,6 +64,7 @@ const addAttachment = async ({ composeDispatch, showActionSheetWithOptions }: Pr
)
break
default:
attachmentType = 'unknown'
composeDispatch({
type: 'attachment/upload/start',
payload: {
@ -64,9 +78,9 @@ const addAttachment = async ({ composeDispatch, showActionSheetWithOptions }: Pr
const formData = new FormData()
formData.append('file', {
// @ts-ignore
uri: result.uri,
name: result.uri.split('/').pop(),
type: 'image/jpeg/jpg'
uri: attachmentUri,
name: attachmentType,
type: attachmentType
})
return client<Mastodon.Attachment>({

View File

@ -8,6 +8,7 @@ import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import {
KeyboardAvoidingView,
Platform,
Pressable,
SectionList,
StyleSheet,
@ -163,14 +164,17 @@ const ScreenSharedSearch: React.FC<Props> = ({ searchTerm }) => {
</Pressable>
)
case 'statuses':
return <TimelineDefault item={item} index={index} disableDetails />
return <TimelineDefault item={item} disableDetails />
default:
return null
}
}, [])
return (
<KeyboardAvoidingView behavior='padding' style={{ flex: 1 }}>
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={{ flex: 1 }}
>
<SectionList
style={styles.base}
renderItem={listItem}

View File

@ -107,7 +107,8 @@ const sharedScreens = (
component={ScreenSharedAnnouncements}
options={{
stackPresentation: 'transparentModal',
stackAnimation: 'fade'
stackAnimation: 'fade',
headerShown: false
}}
/>,
<Stack.Screen
@ -115,7 +116,8 @@ const sharedScreens = (
name='Screen-Shared-Compose'
component={Compose}
options={{
stackPresentation: 'fullScreenModal'
stackPresentation: 'fullScreenModal',
headerShown: false
}}
/>,
<Stack.Screen
@ -133,7 +135,8 @@ const sharedScreens = (
component={ScreenSharedImagesViewer}
options={{
stackPresentation: 'transparentModal',
stackAnimation: 'none'
stackAnimation: 'none',
headerShown: false
}}
/>,
<Stack.Screen

View File

@ -2210,6 +2210,13 @@
dependencies:
"@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@*":
version "17.0.0"
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"
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:
version "2.0.0"
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"
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:
version "1.4.2"
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"
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:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"