mirror of
https://github.com/tooot-app/app
synced 2025-06-05 22:19:13 +02:00
First step to adapt to Android
This commit is contained in:
@ -1,61 +0,0 @@
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import React, { useRef } from 'react'
|
||||
import { RefreshControl } from 'react-native'
|
||||
import { InfiniteData, useQueryClient } from 'react-query'
|
||||
|
||||
export interface Props {
|
||||
queryKey: QueryKeyTimeline
|
||||
isFetchingPreviousPage: boolean
|
||||
isFetching: boolean
|
||||
fetchPreviousPage: () => void
|
||||
refetch: () => void
|
||||
}
|
||||
|
||||
const CustomRefreshControl = React.memo(
|
||||
({
|
||||
queryKey,
|
||||
isFetchingPreviousPage,
|
||||
isFetching,
|
||||
fetchPreviousPage,
|
||||
refetch
|
||||
}: Props) => {
|
||||
const queryClient = useQueryClient()
|
||||
const refreshCount = useRef(0)
|
||||
|
||||
return (
|
||||
<RefreshControl
|
||||
refreshing={
|
||||
refreshCount.current < 2 ? isFetchingPreviousPage : isFetching
|
||||
}
|
||||
onRefresh={async () => {
|
||||
if (refreshCount.current < 2) {
|
||||
await fetchPreviousPage()
|
||||
refreshCount.current++
|
||||
} else {
|
||||
queryClient.setQueryData<InfiniteData<any> | undefined>(
|
||||
queryKey,
|
||||
data => {
|
||||
if (data) {
|
||||
return {
|
||||
pages: data.pages.slice(1),
|
||||
pageParams: data.pageParams.slice(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
await refetch()
|
||||
refreshCount.current = 0
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
(prev, next) => {
|
||||
let skipUpdate = true
|
||||
skipUpdate = prev.isFetchingPreviousPage === next.isFetchingPreviousPage
|
||||
skipUpdate = prev.isFetching === next.isFetching
|
||||
return skipUpdate
|
||||
}
|
||||
)
|
||||
|
||||
export default CustomRefreshControl
|
@ -65,7 +65,7 @@ const MenuRow: React.FC<Props> = ({
|
||||
{iconFront && (
|
||||
<Icon
|
||||
name={iconFront}
|
||||
size={StyleConstants.Font.Size.M + 2}
|
||||
size={StyleConstants.Font.Size.L}
|
||||
color={theme[iconFrontColor]}
|
||||
style={styles.iconFront}
|
||||
/>
|
||||
@ -118,7 +118,7 @@ const MenuRow: React.FC<Props> = ({
|
||||
<>
|
||||
<Icon
|
||||
name={iconBack}
|
||||
size={StyleConstants.Font.Size.M + 2}
|
||||
size={StyleConstants.Font.Size.L}
|
||||
color={theme[iconBackColor]}
|
||||
style={[styles.iconBack, { opacity: loading ? 0 : 1 }]}
|
||||
/>
|
||||
@ -139,6 +139,7 @@ const styles = StyleSheet.create({
|
||||
core: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingLeft: StyleConstants.Spacing.Global.PagePadding,
|
||||
paddingRight: StyleConstants.Spacing.Global.PagePadding
|
||||
},
|
||||
|
@ -45,12 +45,9 @@ const renderNode = ({
|
||||
? routeParams.hashtag !== tag[1] && routeParams.hashtag !== tag[2]
|
||||
: true
|
||||
return (
|
||||
<Text
|
||||
<Pressable
|
||||
key={index}
|
||||
style={{
|
||||
color: theme.blue,
|
||||
...StyleConstants.FontStyle[size]
|
||||
}}
|
||||
hitSlop={StyleConstants.Font.Size[size] / 2}
|
||||
onPress={() => {
|
||||
!disableDetails &&
|
||||
differentTag &&
|
||||
@ -59,9 +56,16 @@ const renderNode = ({
|
||||
})
|
||||
}}
|
||||
>
|
||||
{node.children[0].data}
|
||||
{node.children[1]?.children[0].data}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.blue,
|
||||
...StyleConstants.FontStyle[size]
|
||||
}}
|
||||
>
|
||||
{node.children[0].data}
|
||||
{node.children[1]?.children[0].data}
|
||||
</Text>
|
||||
</Pressable>
|
||||
)
|
||||
} else if (classes.includes('mention') && mentions) {
|
||||
const accountIndex = mentions.findIndex(
|
||||
@ -71,12 +75,9 @@ const renderNode = ({
|
||||
? routeParams.account.id !== mentions[accountIndex].id
|
||||
: true
|
||||
return (
|
||||
<Text
|
||||
<Pressable
|
||||
key={index}
|
||||
style={{
|
||||
color: accountIndex !== -1 ? theme.blue : undefined,
|
||||
...StyleConstants.FontStyle[size]
|
||||
}}
|
||||
hitSlop={StyleConstants.Font.Size[size] / 2}
|
||||
onPress={() => {
|
||||
accountIndex !== -1 &&
|
||||
!disableDetails &&
|
||||
@ -86,9 +87,16 @@ const renderNode = ({
|
||||
})
|
||||
}}
|
||||
>
|
||||
{node.children[0].data}
|
||||
{node.children[1]?.children[0].data}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
color: accountIndex !== -1 ? theme.blue : undefined,
|
||||
...StyleConstants.FontStyle[size]
|
||||
}}
|
||||
>
|
||||
{node.children[0].data}
|
||||
{node.children[1]?.children[0].data}
|
||||
</Text>
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
} else {
|
||||
@ -99,12 +107,9 @@ const renderNode = ({
|
||||
const shouldBeTag =
|
||||
tags && tags.filter(tag => `#${tag.name}` === content).length > 0
|
||||
return (
|
||||
<Text
|
||||
<Pressable
|
||||
key={index}
|
||||
style={{
|
||||
color: theme.blue,
|
||||
...StyleConstants.FontStyle[size]
|
||||
}}
|
||||
hitSlop={StyleConstants.Font.Size[size] / 2}
|
||||
onPress={async () =>
|
||||
!disableDetails && !shouldBeTag
|
||||
? await openLink(href)
|
||||
@ -113,15 +118,22 @@ const renderNode = ({
|
||||
})
|
||||
}
|
||||
>
|
||||
{!shouldBeTag ? (
|
||||
<Icon
|
||||
color={theme.blue}
|
||||
name='ExternalLink'
|
||||
size={StyleConstants.Font.Size[size]}
|
||||
/>
|
||||
) : null}
|
||||
{content || (showFullLink ? href : domain[1])}
|
||||
</Text>
|
||||
<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>
|
||||
)
|
||||
}
|
||||
break
|
||||
@ -206,7 +218,7 @@ const ParseHTML: React.FC<Props> = ({
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View style={{ overflow: 'hidden' }}>
|
||||
<Text
|
||||
children={children}
|
||||
onTextLayout={onTextLayout}
|
||||
@ -222,14 +234,21 @@ const ParseHTML: React.FC<Props> = ({
|
||||
layoutAnimation()
|
||||
setExpanded(!expanded)
|
||||
}}
|
||||
style={{ marginTop: expanded ? 0 : -lineHeight * 2.25 }}
|
||||
style={{
|
||||
marginTop: expanded
|
||||
? 0
|
||||
: -lineHeight * (numberOfLines === 0 ? 1 : 2)
|
||||
}}
|
||||
>
|
||||
<LinearGradient
|
||||
colors={[
|
||||
theme.backgroundGradientStart,
|
||||
theme.backgroundGradientEnd
|
||||
]}
|
||||
locations={[0, lineHeight / (StyleConstants.Font.Size.S * 4)]}
|
||||
locations={[
|
||||
0,
|
||||
lineHeight / (StyleConstants.Font.Size[size] * 5)
|
||||
]}
|
||||
style={{
|
||||
paddingTop: StyleConstants.Font.Size.S * 2,
|
||||
paddingBottom: StyleConstants.Font.Size.S
|
||||
|
@ -69,7 +69,9 @@ const Timelines: React.FC<Props> = ({ name, content }) => {
|
||||
)
|
||||
|
||||
return (
|
||||
<Stack.Navigator screenOptions={{ headerHideShadow: true }}>
|
||||
<Stack.Navigator
|
||||
screenOptions={{ headerHideShadow: true, headerTopInsetEnabled: false }}
|
||||
>
|
||||
<Stack.Screen
|
||||
// @ts-ignore
|
||||
name={`Screen-${name}-Root`}
|
||||
|
@ -9,12 +9,13 @@ 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 { StyleSheet } from 'react-native'
|
||||
import { 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'
|
||||
|
||||
export interface Props {
|
||||
page: App.Pages
|
||||
@ -156,14 +157,35 @@ const Timeline: React.FC<Props> = ({
|
||||
() => <TimelineEnd hasNextPage={!disableInfinity ? hasNextPage : false} />,
|
||||
[hasNextPage]
|
||||
)
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const refreshCount = useRef(0)
|
||||
const refreshControl = useMemo(
|
||||
() => (
|
||||
<CustomRefreshControl
|
||||
queryKey={queryKey}
|
||||
isFetchingPreviousPage={isFetchingNextPage}
|
||||
isFetching={isFetching}
|
||||
fetchPreviousPage={fetchPreviousPage}
|
||||
refetch={refetch}
|
||||
<RefreshControl
|
||||
refreshing={
|
||||
refreshCount.current < 2 ? isFetchingPreviousPage : isFetching
|
||||
}
|
||||
onRefresh={async () => {
|
||||
if (refreshCount.current < 2) {
|
||||
await fetchPreviousPage()
|
||||
refreshCount.current++
|
||||
} else {
|
||||
queryClient.setQueryData<InfiniteData<any> | undefined>(
|
||||
queryKey,
|
||||
data => {
|
||||
if (data) {
|
||||
return {
|
||||
pages: data.pages.slice(1),
|
||||
pageParams: data.pageParams.slice(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
await refetch()
|
||||
refreshCount.current = 0
|
||||
}
|
||||
}}
|
||||
/>
|
||||
),
|
||||
[isFetchingPreviousPage, isFetching]
|
||||
@ -199,10 +221,10 @@ const Timeline: React.FC<Props> = ({
|
||||
{...(queryKey &&
|
||||
queryKey[1].page === 'RemotePublic' && { ListHeaderComponent })}
|
||||
{...(toot && isSuccess && { onScrollToIndexFailed })}
|
||||
maintainVisibleContentPosition={{
|
||||
minIndexForVisible: 0,
|
||||
autoscrollToTopThreshold: 2
|
||||
}}
|
||||
// maintainVisibleContentPosition={{
|
||||
// minIndexForVisible: 0,
|
||||
// autoscrollToTopThreshold: 2
|
||||
// }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ const TimelineEnd: React.FC<Props> = ({ hasNextPage }) => {
|
||||
) : (
|
||||
<Text style={[styles.text, { color: theme.secondary }]}>
|
||||
<Trans
|
||||
i18nKey='timeline:shared.end.message' // optional -> fallbacks to defaults if not provided
|
||||
i18nKey='timeline:shared.end.message'
|
||||
components={[
|
||||
<Icon
|
||||
name='Coffee'
|
||||
|
@ -11,7 +11,14 @@ import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ActionSheetIOS, Pressable, StyleSheet, Text, View } from 'react-native'
|
||||
import {
|
||||
Platform,
|
||||
Pressable,
|
||||
Share,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View
|
||||
} from 'react-native'
|
||||
import { useQueryClient } from 'react-query'
|
||||
|
||||
export interface Props {
|
||||
@ -35,12 +42,39 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
||||
onSuccess: (_, params) => {
|
||||
const theParams = params as MutationVarsTimelineUpdateStatusProperty
|
||||
if (
|
||||
// Un-bookmark from bookmarks page
|
||||
(queryKey[1].page === 'Bookmarks' &&
|
||||
theParams.payload.property === 'bookmarked') ||
|
||||
// Un-favourite from favourites page
|
||||
(queryKey[1].page === 'Favourites' &&
|
||||
theParams.payload.property === 'favourited')
|
||||
theParams.payload.property === 'favourited') ||
|
||||
// Un-reblog from following page
|
||||
(queryKey[1].page === 'Following' &&
|
||||
theParams.payload.property === 'reblogged' &&
|
||||
theParams.payload.currentValue === true)
|
||||
) {
|
||||
queryClient.invalidateQueries(queryKey)
|
||||
} else if (theParams.payload.property === 'reblogged') {
|
||||
// When reblogged, update cache of following page
|
||||
const tempQueryKey: QueryKeyTimeline = [
|
||||
'Timeline',
|
||||
{ page: 'Following' }
|
||||
]
|
||||
queryClient.invalidateQueries(tempQueryKey)
|
||||
} else if (theParams.payload.property === 'favourited') {
|
||||
// When favourited, update favourited page
|
||||
const tempQueryKey: QueryKeyTimeline = [
|
||||
'Timeline',
|
||||
{ page: 'Favourites' }
|
||||
]
|
||||
queryClient.invalidateQueries(tempQueryKey)
|
||||
} else if (theParams.payload.property === 'bookmarked') {
|
||||
// When bookmarked, update bookmark page
|
||||
const tempQueryKey: QueryKeyTimeline = [
|
||||
'Timeline',
|
||||
{ page: 'Bookmarks' }
|
||||
]
|
||||
queryClient.invalidateQueries(tempQueryKey)
|
||||
}
|
||||
},
|
||||
onError: (err: any, params, oldData) => {
|
||||
@ -115,23 +149,18 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
||||
}),
|
||||
[status.bookmarked]
|
||||
)
|
||||
const onPressShare = useCallback(
|
||||
() =>
|
||||
ActionSheetIOS.showShareActionSheetWithOptions(
|
||||
{
|
||||
url: status.uri,
|
||||
excludedActivityTypes: [
|
||||
'com.apple.UIKit.activity.Mail',
|
||||
'com.apple.UIKit.activity.Print',
|
||||
'com.apple.UIKit.activity.SaveToCameraRoll',
|
||||
'com.apple.UIKit.activity.OpenInIBooks'
|
||||
]
|
||||
},
|
||||
() => haptics('Error'),
|
||||
() => haptics('Success')
|
||||
),
|
||||
[]
|
||||
)
|
||||
const onPressShare = useCallback(() => {
|
||||
switch (Platform.OS) {
|
||||
case 'ios':
|
||||
return Share.share({
|
||||
url: status.uri
|
||||
})
|
||||
case 'android':
|
||||
return Share.share({
|
||||
message: status.uri
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
|
||||
const childrenReply = useMemo(
|
||||
() => (
|
||||
@ -139,7 +168,7 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
||||
<Icon
|
||||
name='MessageCircle'
|
||||
color={iconColor}
|
||||
size={StyleConstants.Font.Size.M + 2}
|
||||
size={StyleConstants.Font.Size.L}
|
||||
/>
|
||||
{status.replies_count > 0 && (
|
||||
<Text
|
||||
@ -165,7 +194,7 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
||||
? theme.disabled
|
||||
: iconColorAction(status.reblogged)
|
||||
}
|
||||
size={StyleConstants.Font.Size.M + 2}
|
||||
size={StyleConstants.Font.Size.L}
|
||||
/>
|
||||
),
|
||||
[status.reblogged]
|
||||
@ -175,7 +204,7 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
||||
<Icon
|
||||
name='Heart'
|
||||
color={iconColorAction(status.favourited)}
|
||||
size={StyleConstants.Font.Size.M + 2}
|
||||
size={StyleConstants.Font.Size.L}
|
||||
/>
|
||||
),
|
||||
[status.favourited]
|
||||
@ -185,18 +214,14 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
||||
<Icon
|
||||
name='Bookmark'
|
||||
color={iconColorAction(status.bookmarked)}
|
||||
size={StyleConstants.Font.Size.M + 2}
|
||||
size={StyleConstants.Font.Size.L}
|
||||
/>
|
||||
),
|
||||
[status.bookmarked]
|
||||
)
|
||||
const childrenShare = useMemo(
|
||||
() => (
|
||||
<Icon
|
||||
name='Share2'
|
||||
color={iconColor}
|
||||
size={StyleConstants.Font.Size.M + 2}
|
||||
/>
|
||||
<Icon name='Share2' color={iconColor} size={StyleConstants.Font.Size.L} />
|
||||
),
|
||||
[]
|
||||
)
|
||||
@ -252,6 +277,7 @@ const styles = StyleSheet.create({
|
||||
width: '20%',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingVertical: StyleConstants.Spacing.S
|
||||
}
|
||||
})
|
||||
|
@ -36,7 +36,7 @@ const HeaderActions = React.memo(
|
||||
<Icon
|
||||
name='MoreHorizontal'
|
||||
color={theme.secondary}
|
||||
size={StyleConstants.Font.Size.M + 2}
|
||||
size={StyleConstants.Font.Size.L}
|
||||
/>
|
||||
),
|
||||
[]
|
||||
|
@ -65,7 +65,7 @@ const HeaderConversation: React.FC<Props> = ({ queryKey, conversation }) => {
|
||||
<Icon
|
||||
name='Trash'
|
||||
color={theme.secondary}
|
||||
size={StyleConstants.Font.Size.M + 2}
|
||||
size={StyleConstants.Font.Size.L}
|
||||
/>
|
||||
),
|
||||
[]
|
||||
|
@ -1,8 +1,8 @@
|
||||
import relativeTime from '@components/relativeTime'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Moment from 'react-moment'
|
||||
import { StyleSheet, Text } from 'react-native'
|
||||
|
||||
export interface Props {
|
||||
@ -13,16 +13,10 @@ const HeaderSharedCreated: React.FC<Props> = ({ created_at }) => {
|
||||
const { theme } = useTheme()
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
const [since, setSince] = useState(relativeTime(created_at, i18n.language))
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setSince(relativeTime(created_at, i18n.language))
|
||||
}, 1000)
|
||||
return () => clearTimeout(timer)
|
||||
}, [since])
|
||||
|
||||
return (
|
||||
<Text style={[styles.created_at, { color: theme.secondary }]}>{since}</Text>
|
||||
<Text style={[styles.created_at, { color: theme.secondary }]}>
|
||||
<Moment date={created_at} locale={i18n.language} element={Text} fromNow />
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -10,8 +10,10 @@ import {
|
||||
} from '@utils/queryHooks/timeline'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { maxBy } from 'lodash'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import Moment from 'react-moment'
|
||||
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
||||
import { useQueryClient } from 'react-query'
|
||||
|
||||
@ -123,16 +125,24 @@ const TimelinePoll: React.FC<Props> = ({
|
||||
} else {
|
||||
return (
|
||||
<Text style={[styles.expiration, { color: theme.secondary }]}>
|
||||
{t('shared.poll.meta.expiration.until', {
|
||||
at: relativeTime(poll.expires_at, i18n.language)
|
||||
})}
|
||||
<Trans
|
||||
i18nKey='timeline:shared.poll.meta.expiration.until'
|
||||
components={[
|
||||
<Moment
|
||||
date={poll.expires_at}
|
||||
locale={i18n.language}
|
||||
element={Text}
|
||||
fromNow
|
||||
/>
|
||||
]}
|
||||
/>
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
}, [mode, poll.expired, poll.expires_at])
|
||||
|
||||
const isSelected = useCallback(
|
||||
(index: number): any =>
|
||||
(index: number): string =>
|
||||
allOptions[index]
|
||||
? `Check${poll.multiple ? 'Square' : 'Circle'}`
|
||||
: `${poll.multiple ? 'Square' : 'Circle'}`,
|
||||
@ -140,6 +150,8 @@ const TimelinePoll: React.FC<Props> = ({
|
||||
)
|
||||
|
||||
const pollBodyDisallow = useMemo(() => {
|
||||
const maxValue = maxBy(poll.options, option => option.votes_count)
|
||||
?.votes_count
|
||||
return poll.options.map((option, index) => (
|
||||
<View key={index} style={styles.optionContainer}>
|
||||
<View style={styles.optionContent}>
|
||||
@ -152,7 +164,7 @@ const TimelinePoll: React.FC<Props> = ({
|
||||
}
|
||||
size={StyleConstants.Font.Size.M}
|
||||
color={
|
||||
poll.own_votes?.includes(index) ? theme.primary : theme.disabled
|
||||
poll.own_votes?.includes(index) ? theme.blue : theme.disabled
|
||||
}
|
||||
/>
|
||||
<Text style={styles.optionText}>
|
||||
@ -160,7 +172,11 @@ const TimelinePoll: React.FC<Props> = ({
|
||||
</Text>
|
||||
<Text style={[styles.optionPercentage, { color: theme.primary }]}>
|
||||
{poll.votes_count
|
||||
? Math.round((option.votes_count / poll.voters_count) * 100)
|
||||
? Math.round(
|
||||
(option.votes_count /
|
||||
(poll.voters_count || poll.votes_count)) *
|
||||
100
|
||||
)
|
||||
: 0}
|
||||
%
|
||||
</Text>
|
||||
@ -171,9 +187,11 @@ const TimelinePoll: React.FC<Props> = ({
|
||||
styles.background,
|
||||
{
|
||||
width: `${Math.round(
|
||||
(option.votes_count / poll.voters_count) * 100
|
||||
(option.votes_count / (poll.voters_count || poll.votes_count)) *
|
||||
100
|
||||
)}%`,
|
||||
backgroundColor: theme.disabled
|
||||
backgroundColor:
|
||||
option.votes_count === maxValue ? theme.blue : theme.disabled
|
||||
}
|
||||
]}
|
||||
/>
|
||||
@ -221,14 +239,28 @@ const TimelinePoll: React.FC<Props> = ({
|
||||
))
|
||||
}, [mode, allOptions])
|
||||
|
||||
const pollVoteCounts = useMemo(() => {
|
||||
if (poll.voters_count !== null) {
|
||||
return (
|
||||
<Text style={[styles.votes, { color: theme.secondary }]}>
|
||||
{t('shared.poll.meta.count.voters', { count: poll.voters_count })}
|
||||
</Text>
|
||||
)
|
||||
} else if (poll.votes_count !== null) {
|
||||
return (
|
||||
<Text style={[styles.votes, { color: theme.secondary }]}>
|
||||
{t('shared.poll.meta.count.votes', { count: poll.votes_count })}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
}, [poll.voters_count, poll.votes_count])
|
||||
|
||||
return (
|
||||
<View style={styles.base}>
|
||||
{poll.expired || poll.voted ? pollBodyDisallow : pollBodyAllow}
|
||||
<View style={styles.meta}>
|
||||
{pollButton}
|
||||
<Text style={[styles.votes, { color: theme.secondary }]}>
|
||||
{t('shared.poll.meta.voted', { count: poll.voters_count })}
|
||||
</Text>
|
||||
{pollVoteCounts}
|
||||
{pollExpiration}
|
||||
</View>
|
||||
</View>
|
||||
|
@ -1,27 +0,0 @@
|
||||
const relativeTime = (date: string, language: string) => {
|
||||
const units = {
|
||||
year: 24 * 60 * 60 * 1000 * 365,
|
||||
month: (24 * 60 * 60 * 1000 * 365) / 12,
|
||||
day: 24 * 60 * 60 * 1000,
|
||||
hour: 60 * 60 * 1000,
|
||||
minute: 60 * 1000,
|
||||
second: 1000
|
||||
}
|
||||
|
||||
const rtf = new Intl.RelativeTimeFormat(language, {
|
||||
numeric: 'auto'
|
||||
})
|
||||
|
||||
const elapsed = +new Date(date) - +new Date()
|
||||
|
||||
// "Math.abs" accounts for both "past" & "future" scenarios
|
||||
for (const u in units) {
|
||||
// @ts-ignore
|
||||
if (Math.abs(elapsed) > units[u] || u == 'second') {
|
||||
// @ts-ignore
|
||||
return rtf.format(Math.round(elapsed / units[u]), u)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default relativeTime
|
Reference in New Issue
Block a user