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

@ -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 }) => {