1
0
mirror of https://github.com/tooot-app/app synced 2025-06-05 22:19:13 +02:00

More adaption to Android

This commit is contained in:
Zhiyuan Zheng
2021-01-14 00:43:35 +01:00
parent 49715bba0d
commit 95f500ae72
28 changed files with 376 additions and 169 deletions

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