diff --git a/App.tsx b/App.tsx
index 73ace6f4..a206104d 100644
--- a/App.tsx
+++ b/App.tsx
@@ -1,4 +1,11 @@
+import { ActionSheetProvider } from '@expo/react-native-action-sheet'
import Index from '@root/Index'
+import dev from '@root/startup/dev'
+import sentry from '@root/startup/sentry'
+import log from '@root/startup/log'
+import audio from '@root/startup/audio'
+import onlineStatus from '@root/startup/onlineStatus'
+import netInfo from '@root/startup/netInfo'
import { persistor, store } from '@root/store'
import ThemeManager from '@utils/styles/ThemeManager'
import * as SplashScreen from 'expo-splash-screen'
@@ -7,17 +14,22 @@ import { enableScreens } from 'react-native-screens'
import { QueryClient, QueryClientProvider } from 'react-query'
import { Provider } from 'react-redux'
import { PersistGate } from 'redux-persist/integration/react'
-import dev from '@root/startup/dev'
-import sentry from '@root/startup/sentry'
-import log from '@root/startup/log'
-import audio from '@root/startup/audio'
-import onlineStatus from '@root/startup/onlineStatus'
-import netInfo from '@root/startup/netInfo'
+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'])
+}
dev()
sentry()
audio()
onlineStatus()
+Moment.globalMoment = moment
+Moment.startPooledTimer(1000)
log('log', 'react-query', 'initializing')
const queryClient = new QueryClient()
@@ -68,9 +80,11 @@ const App: React.FC = () => {
log('log', 'App', 'loading actual app :)')
require('@root/i18n/i18n')
return (
-
-
-
+
+
+
+
+
)
} else {
return null
diff --git a/app.config.ts b/app.config.ts
index 21a2580e..5ee1889b 100644
--- a/app.config.ts
+++ b/app.config.ts
@@ -24,6 +24,10 @@ export default (): ExpoConfig => ({
},
googleServicesFile: './configs/GoogleService-Info.plist'
},
+ android: {
+ package: 'com.xmflsct.app.mastodon',
+ googleServicesFile: './configs/google-services.json'
+ },
// locales: {
// zh: {
// CFBundleDisplayName: '我的嘟嘟'
@@ -57,8 +61,5 @@ export default (): ExpoConfig => ({
measurementId: 'G-3J0FS8WV5J'
}
}
- },
- experiments: {
- turboModules: true
}
})
diff --git a/configs/Microsoft_AutoUpdate_4.30.20121301_Updater.pkg b/configs/Microsoft_AutoUpdate_4.30.20121301_Updater.pkg
deleted file mode 100644
index f54c3035..00000000
Binary files a/configs/Microsoft_AutoUpdate_4.30.20121301_Updater.pkg and /dev/null differ
diff --git a/configs/google-services.json b/configs/google-services.json
new file mode 100644
index 00000000..c074d550
--- /dev/null
+++ b/configs/google-services.json
@@ -0,0 +1,46 @@
+{
+ "project_info": {
+ "project_number": "661638997772",
+ "project_id": "xmflsct-mastodon-app",
+ "storage_bucket": "xmflsct-mastodon-app.appspot.com"
+ },
+ "client": [
+ {
+ "client_info": {
+ "mobilesdk_app_id": "1:661638997772:android:0f929e1c9ebd7f969f8b29",
+ "android_client_info": {
+ "package_name": "com.xmflsct.app.mastodon"
+ }
+ },
+ "oauth_client": [
+ {
+ "client_id": "661638997772-6aiqk97aema0rt280i7nfar3ha2mlgno.apps.googleusercontent.com",
+ "client_type": 3
+ }
+ ],
+ "api_key": [
+ {
+ "current_key": "AIzaSyDUw4s-mhQsHvs4hdIsldsi68ZIygM5MC4"
+ }
+ ],
+ "services": {
+ "appinvite_service": {
+ "other_platform_oauth_client": [
+ {
+ "client_id": "661638997772-6aiqk97aema0rt280i7nfar3ha2mlgno.apps.googleusercontent.com",
+ "client_type": 3
+ },
+ {
+ "client_id": "661638997772-sqa4raeghhrieqt9guljhcul9b51dvna.apps.googleusercontent.com",
+ "client_type": 2,
+ "ios_info": {
+ "bundle_id": "com.xmflsct.app.mastodon"
+ }
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "configuration_version": "1"
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index 90f86a6e..965720d0 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,7 @@
"test": "jest --watchAll"
},
"dependencies": {
+ "@expo/react-native-action-sheet": "^3.8.0",
"@react-native-async-storage/async-storage": "^1.13.2",
"@react-native-community/masked-view": "0.1.10",
"@react-native-community/netinfo": "^5.9.7",
@@ -43,10 +44,12 @@
"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",
@@ -100,4 +103,4 @@
"typescript": "~4.1.3"
},
"private": true
-}
\ No newline at end of file
+}
diff --git a/src/Index.tsx b/src/Index.tsx
index 457a082f..35a76da9 100644
--- a/src/Index.tsx
+++ b/src/Index.tsx
@@ -108,7 +108,6 @@ const Index: React.FC = ({ localCorrupt }) => {
refetchIntervalInBackground: true
}
})
-
const prevNotification = useSelector(getLocalNotification)
useEffect(() => {
if (queryNotification.data?.pages) {
@@ -244,7 +243,7 @@ const Index: React.FC = ({ localCorrupt }) => {
)
return (
<>
-
+
= ({ localCorrupt }) => {
-
+ {/* */}
>
)
diff --git a/src/components/CustomRefreshControl.tsx b/src/components/CustomRefreshControl.tsx
deleted file mode 100644
index 18369a28..00000000
--- a/src/components/CustomRefreshControl.tsx
+++ /dev/null
@@ -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 (
- {
- if (refreshCount.current < 2) {
- await fetchPreviousPage()
- refreshCount.current++
- } else {
- queryClient.setQueryData | 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
diff --git a/src/components/Menu/Row.tsx b/src/components/Menu/Row.tsx
index 3ba447a8..026bb1bd 100644
--- a/src/components/Menu/Row.tsx
+++ b/src/components/Menu/Row.tsx
@@ -65,7 +65,7 @@ const MenuRow: React.FC = ({
{iconFront && (
@@ -118,7 +118,7 @@ const MenuRow: React.FC = ({
<>
@@ -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
},
diff --git a/src/components/Parse/HTML.tsx b/src/components/Parse/HTML.tsx
index b25f2cb8..d2bfc527 100644
--- a/src/components/Parse/HTML.tsx
+++ b/src/components/Parse/HTML.tsx
@@ -45,12 +45,9 @@ const renderNode = ({
? routeParams.hashtag !== tag[1] && routeParams.hashtag !== tag[2]
: true
return (
- {
!disableDetails &&
differentTag &&
@@ -59,9 +56,16 @@ const renderNode = ({
})
}}
>
- {node.children[0].data}
- {node.children[1]?.children[0].data}
-
+
+ {node.children[0].data}
+ {node.children[1]?.children[0].data}
+
+
)
} else if (classes.includes('mention') && mentions) {
const accountIndex = mentions.findIndex(
@@ -71,12 +75,9 @@ const renderNode = ({
? routeParams.account.id !== mentions[accountIndex].id
: true
return (
- {
accountIndex !== -1 &&
!disableDetails &&
@@ -86,9 +87,16 @@ const renderNode = ({
})
}}
>
- {node.children[0].data}
- {node.children[1]?.children[0].data}
-
+
+ {node.children[0].data}
+ {node.children[1]?.children[0].data}
+
+
)
}
} else {
@@ -99,12 +107,9 @@ const renderNode = ({
const shouldBeTag =
tags && tags.filter(tag => `#${tag.name}` === content).length > 0
return (
-
!disableDetails && !shouldBeTag
? await openLink(href)
@@ -113,15 +118,22 @@ const renderNode = ({
})
}
>
- {!shouldBeTag ? (
-
- ) : null}
- {content || (showFullLink ? href : domain[1])}
-
+
+ {!shouldBeTag ? (
+
+ ) : null}
+ {content || (showFullLink ? href : domain[1])}
+
+
)
}
break
@@ -206,7 +218,7 @@ const ParseHTML: React.FC = ({
}, [])
return (
-
+
= ({
layoutAnimation()
setExpanded(!expanded)
}}
- style={{ marginTop: expanded ? 0 : -lineHeight * 2.25 }}
+ style={{
+ marginTop: expanded
+ ? 0
+ : -lineHeight * (numberOfLines === 0 ? 1 : 2)
+ }}
>
= ({ name, content }) => {
)
return (
-
+
= ({
() => ,
[hasNextPage]
)
+
+ const queryClient = useQueryClient()
+ const refreshCount = useRef(0)
const refreshControl = useMemo(
() => (
- {
+ if (refreshCount.current < 2) {
+ await fetchPreviousPage()
+ refreshCount.current++
+ } else {
+ queryClient.setQueryData | 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 = ({
{...(queryKey &&
queryKey[1].page === 'RemotePublic' && { ListHeaderComponent })}
{...(toot && isSuccess && { onScrollToIndexFailed })}
- maintainVisibleContentPosition={{
- minIndexForVisible: 0,
- autoscrollToTopThreshold: 2
- }}
+ // maintainVisibleContentPosition={{
+ // minIndexForVisible: 0,
+ // autoscrollToTopThreshold: 2
+ // }}
/>
)
}
diff --git a/src/components/Timelines/Timeline/End.tsx b/src/components/Timelines/Timeline/End.tsx
index 12474a33..5c381fc5 100644
--- a/src/components/Timelines/Timeline/End.tsx
+++ b/src/components/Timelines/Timeline/End.tsx
@@ -20,7 +20,7 @@ const TimelineEnd: React.FC = ({ hasNextPage }) => {
) : (
fallbacks to defaults if not provided
+ i18nKey='timeline:shared.end.message'
components={[
= ({ 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 = ({ 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 = ({ queryKey, status, reblog }) => {
{status.replies_count > 0 && (
= ({ 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 = ({ queryKey, status, reblog }) => {
),
[status.favourited]
@@ -185,18 +214,14 @@ const TimelineActions: React.FC = ({ queryKey, status, reblog }) => {
),
[status.bookmarked]
)
const childrenShare = useMemo(
() => (
-
+
),
[]
)
@@ -252,6 +277,7 @@ const styles = StyleSheet.create({
width: '20%',
flexDirection: 'row',
justifyContent: 'center',
+ alignItems: 'center',
paddingVertical: StyleConstants.Spacing.S
}
})
diff --git a/src/components/Timelines/Timeline/Shared/HeaderActions/Root.tsx b/src/components/Timelines/Timeline/Shared/HeaderActions/Root.tsx
index 808ed97f..2f9f3964 100644
--- a/src/components/Timelines/Timeline/Shared/HeaderActions/Root.tsx
+++ b/src/components/Timelines/Timeline/Shared/HeaderActions/Root.tsx
@@ -36,7 +36,7 @@ const HeaderActions = React.memo(
),
[]
diff --git a/src/components/Timelines/Timeline/Shared/HeaderConversation.tsx b/src/components/Timelines/Timeline/Shared/HeaderConversation.tsx
index 6c1f5ee8..177a3fc9 100644
--- a/src/components/Timelines/Timeline/Shared/HeaderConversation.tsx
+++ b/src/components/Timelines/Timeline/Shared/HeaderConversation.tsx
@@ -65,7 +65,7 @@ const HeaderConversation: React.FC = ({ queryKey, conversation }) => {
),
[]
diff --git a/src/components/Timelines/Timeline/Shared/HeaderShared/Created.tsx b/src/components/Timelines/Timeline/Shared/HeaderShared/Created.tsx
index a47f4e9e..b576a3a1 100644
--- a/src/components/Timelines/Timeline/Shared/HeaderShared/Created.tsx
+++ b/src/components/Timelines/Timeline/Shared/HeaderShared/Created.tsx
@@ -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 = ({ 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 (
- {since}
+
+
+
)
}
diff --git a/src/components/Timelines/Timeline/Shared/Poll.tsx b/src/components/Timelines/Timeline/Shared/Poll.tsx
index d318c351..b31e7952 100644
--- a/src/components/Timelines/Timeline/Shared/Poll.tsx
+++ b/src/components/Timelines/Timeline/Shared/Poll.tsx
@@ -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 = ({
} else {
return (
- {t('shared.poll.meta.expiration.until', {
- at: relativeTime(poll.expires_at, i18n.language)
- })}
+
+ ]}
+ />
)
}
}, [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 = ({
)
const pollBodyDisallow = useMemo(() => {
+ const maxValue = maxBy(poll.options, option => option.votes_count)
+ ?.votes_count
return poll.options.map((option, index) => (
@@ -152,7 +164,7 @@ const TimelinePoll: React.FC = ({
}
size={StyleConstants.Font.Size.M}
color={
- poll.own_votes?.includes(index) ? theme.primary : theme.disabled
+ poll.own_votes?.includes(index) ? theme.blue : theme.disabled
}
/>
@@ -160,7 +172,11 @@ const TimelinePoll: React.FC = ({
{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}
%
@@ -171,9 +187,11 @@ const TimelinePoll: React.FC = ({
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 = ({
))
}, [mode, allOptions])
+ const pollVoteCounts = useMemo(() => {
+ if (poll.voters_count !== null) {
+ return (
+
+ {t('shared.poll.meta.count.voters', { count: poll.voters_count })}
+
+ )
+ } else if (poll.votes_count !== null) {
+ return (
+
+ {t('shared.poll.meta.count.votes', { count: poll.votes_count })}
+
+ )
+ }
+ }, [poll.voters_count, poll.votes_count])
+
return (
{poll.expired || poll.voted ? pollBodyDisallow : pollBodyAllow}
{pollButton}
-
- {t('shared.poll.meta.voted', { count: poll.voters_count })}
-
+ {pollVoteCounts}
{pollExpiration}
diff --git a/src/components/relativeTime.ts b/src/components/relativeTime.ts
deleted file mode 100644
index d4b103c3..00000000
--- a/src/components/relativeTime.ts
+++ /dev/null
@@ -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
diff --git a/src/i18n/i18n.ts b/src/i18n/i18n.ts
index 5ae6ba83..fc9520e1 100644
--- a/src/i18n/i18n.ts
+++ b/src/i18n/i18n.ts
@@ -13,29 +13,24 @@ import { store } from '@root/store'
if (!getSettingsLanguage(store.getState())) {
const deviceLocal = Localization.locale
if (deviceLocal.startsWith('zh')) {
- store.dispatch(changeLanguage('zh'))
+ store.dispatch(changeLanguage('zh-CN'))
} else {
- store.dispatch(changeLanguage('en'))
+ store.dispatch(changeLanguage('en-US'))
}
}
-
i18next.use(initReactI18next).init({
- lng: getSettingsLanguage(store.getState()),
- fallbackLng: 'en',
- supportedLngs: ['zh', 'en'],
- nonExplicitSupportedLngs: true,
+ lng: 'zh-CN',
+ fallbackLng: 'en-US',
+ supportedLngs: ['zh-CN', 'en-US'],
ns: ['common'],
defaultNS: 'common',
- resources: {
- zh: zh,
- en: en
- },
+ resources: { 'zh-CN': zh, 'en-US': en },
saveMissing: true,
missingKeyHandler: (lng, ns, key, fallbackValue) => {
- console.warn('i18n missing: ' + ns + ' : ' + key)
+ console.log('i18n missing: ' + lng + ' - ' + ns + ' : ' + key)
},
// react options
diff --git a/src/i18n/zh/components/timeline.ts b/src/i18n/zh/components/timeline.ts
index a98e56f2..246213d5 100644
--- a/src/i18n/zh/components/timeline.ts
+++ b/src/i18n/zh/components/timeline.ts
@@ -123,11 +123,14 @@ export default {
vote: '投票',
refresh: '刷新'
},
+ count: {
+ voters: '已投{{count}}人 • ',
+ votes: '{{count}}票 • '
+ },
expiration: {
expired: '投票已结束',
- until: '{{at}}截止'
- },
- voted: '已投{{count}}人 • '
+ until: '<0 />截止'
+ }
}
}
}
diff --git a/src/screens/Me.tsx b/src/screens/Me.tsx
index ed0d46e9..6dd68a24 100644
--- a/src/screens/Me.tsx
+++ b/src/screens/Me.tsx
@@ -19,7 +19,7 @@ const ScreenMe: React.FC = () => {
const { t } = useTranslation()
return (
-
+
> = ({
- route: {
- params: { navigateAway }
- },
- navigation
-}) => {
+>> = ({ route: { params }, navigation }) => {
useEffect(() => {
- if (navigateAway) {
- navigation.navigate(navigateAway)
+ if (params && params.navigateAway) {
+ console.log('oops')
+ navigation.navigate(params.navigateAway)
}
- }, [navigateAway])
+ }, [params])
const localActiveIndex = useSelector(getLocalActiveIndex)
const scrollRef = useRef(null)
diff --git a/src/screens/Me/Settings.tsx b/src/screens/Me/Settings.tsx
index 963daf3b..e05cb521 100644
--- a/src/screens/Me/Settings.tsx
+++ b/src/screens/Me/Settings.tsx
@@ -1,5 +1,6 @@
import Button from '@components/Button'
import { MenuContainer, MenuRow } from '@components/Menu'
+import { useActionSheet } from '@expo/react-native-action-sheet'
import { useNavigation } from '@react-navigation/native'
import haptics from '@root/components/haptics'
import { persistor } from '@root/store'
@@ -23,11 +24,13 @@ import { useTheme } from '@utils/styles/ThemeManager'
import prettyBytes from 'pretty-bytes'
import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
-import { ActionSheetIOS, StyleSheet, Text } from 'react-native'
+import { StyleSheet, Text } from 'react-native'
import { CacheManager } from 'react-native-expo-image-cache'
+import { ScrollView } from 'react-native-gesture-handler'
import { useDispatch, useSelector } from 'react-redux'
const DevDebug: React.FC = () => {
+ const { showActionSheetWithOptions } = useActionSheet()
const localActiveIndex = useSelector(getLocalActiveIndex)
const localInstances = useSelector(getLocalInstances)
@@ -43,7 +46,7 @@ const DevDebug: React.FC = () => {
content={localInstances.length.toString()}
iconBack='ChevronRight'
onPress={() =>
- ActionSheetIOS.showActionSheetWithOptions(
+ showActionSheetWithOptions(
{
options: localInstances
.map(instance => {
@@ -71,6 +74,7 @@ const DevDebug: React.FC = () => {
}
const ScreenMeSettings: React.FC = () => {
+ const { showActionSheetWithOptions } = useActionSheet()
const navigation = useNavigation()
const { t, i18n } = useTranslation('meSettings')
const { setTheme, theme } = useTheme()
@@ -87,15 +91,16 @@ const ScreenMeSettings: React.FC = () => {
}, [])
return (
- <>
+
- ActionSheetIOS.showActionSheetWithOptions(
+ showActionSheetWithOptions(
{
+ title: t('content.language.heading'),
options: [
t('content.language.options.zh'),
t('content.language.options.en'),
@@ -107,13 +112,13 @@ const ScreenMeSettings: React.FC = () => {
switch (buttonIndex) {
case 0:
haptics('Success')
- dispatch(changeLanguage('zh'))
- i18n.changeLanguage('zh')
+ dispatch(changeLanguage('zh-CN'))
+ i18n.changeLanguage('zh-CN')
break
case 1:
haptics('Success')
- dispatch(changeLanguage('en'))
- i18n.changeLanguage('en')
+ dispatch(changeLanguage('en-US'))
+ i18n.changeLanguage('en-US')
break
}
}
@@ -125,8 +130,9 @@ const ScreenMeSettings: React.FC = () => {
content={t(`content.theme.options.${settingsTheme}`)}
iconBack='ChevronRight'
onPress={() =>
- ActionSheetIOS.showActionSheetWithOptions(
+ showActionSheetWithOptions(
{
+ title: t('content.theme.heading'),
options: [
t('content.theme.options.auto'),
t('content.theme.options.light'),
@@ -161,8 +167,9 @@ const ScreenMeSettings: React.FC = () => {
content={t(`content.browser.options.${settingsBrowser}`)}
iconBack='ChevronRight'
onPress={() =>
- ActionSheetIOS.showActionSheetWithOptions(
+ showActionSheetWithOptions(
{
+ title: t('content.browser.heading'),
options: [
t('content.browser.options.internal'),
t('content.browser.options.external'),
@@ -226,7 +233,7 @@ const ScreenMeSettings: React.FC = () => {
{__DEV__ ? : null}
- >
+
)
}
diff --git a/src/screens/Me/Switch.tsx b/src/screens/Me/Switch.tsx
index 65007df2..391c10db 100644
--- a/src/screens/Me/Switch.tsx
+++ b/src/screens/Me/Switch.tsx
@@ -8,7 +8,7 @@ const Stack = createNativeStackNavigator()
const ScreenMeSwitch: React.FC = ({ navigation }) => {
return (
-
+
{
diff --git a/src/screens/Public.tsx b/src/screens/Public.tsx
index 95a6587d..bf1b5516 100644
--- a/src/screens/Public.tsx
+++ b/src/screens/Public.tsx
@@ -1,5 +1,4 @@
import Timelines from '@components/Timelines'
-import { getRemoteUrl } from '@utils/slices/instancesSlice'
import React from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/src/screens/Shared/Account/Information.tsx b/src/screens/Shared/Account/Information.tsx
index 4c9a6883..4a3ac645 100644
--- a/src/screens/Shared/Account/Information.tsx
+++ b/src/screens/Shared/Account/Information.tsx
@@ -73,19 +73,24 @@ const AccountInformation: React.FC = ({
- {account?.fields && account.fields.length > 0 ? (
-
+ {!ownAccount ? (
+ <>
+ {account?.fields && account.fields.length > 0 ? (
+
+ ) : null}
+ {account?.note &&
+ account.note.length > 0 &&
+ account.note !== '' ? (
+ // Empty notes might generate empty p tag
+
+ ) : null}
+
+ >
) : null}
- {account?.note &&
- account.note.length > 0 &&
- account.note !== '' ? (
- // Empty notes might generate empty p tag
-
- ) : null}
-
-
-
)
diff --git a/src/screens/Shared/Announcements.tsx b/src/screens/Shared/Announcements.tsx
index 5c7e013c..1970e599 100644
--- a/src/screens/Shared/Announcements.tsx
+++ b/src/screens/Shared/Announcements.tsx
@@ -1,8 +1,6 @@
-import client from '@api/client'
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,
@@ -12,6 +10,7 @@ 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,
@@ -25,33 +24,6 @@ import { FlatList, ScrollView } from 'react-native-gesture-handler'
import { SafeAreaView } from 'react-native-safe-area-context'
import { SharedAnnouncementsProp } from './sharedScreens'
-const fireMutation = async ({
- announcementId,
- type,
- name,
- me
-}: {
- announcementId: Mastodon.Announcement['id']
- type: 'reaction' | 'dismiss'
- name?: Mastodon.AnnouncementReaction['name']
- me?: boolean
-}) => {
- switch (type) {
- case 'reaction':
- return client<{}>({
- method: me ? 'delete' : 'put',
- instance: 'local',
- url: `announcements/${announcementId}/reactions/${name}`
- })
- case 'dismiss':
- return client<{}>({
- method: 'post',
- instance: 'local',
- url: `announcements/${announcementId}/dismiss`
- })
- }
-}
-
const ScreenSharedAnnouncements: React.FC = ({
route: {
params: { showAll = false }
@@ -108,7 +80,13 @@ const ScreenSharedAnnouncements: React.FC = ({
]}
>
- 发布于 {relativeTime(item.published_at, i18n.language)}
+ 发布于{' '}
+
= ({
edges={hasKeyboard ? ['left', 'right'] : ['left', 'right', 'bottom']}
>
-
+
{
+ const { showActionSheetWithOptions } = useActionSheet()
const { composeState, composeDispatch } = useContext(ComposeContext)
const { theme } = useTheme()
@@ -24,7 +26,10 @@ const ComposeActions: React.FC = () => {
if (composeState.poll.active) return
if (composeState.attachments.uploads.length < 4) {
- return await addAttachment({ composeDispatch })
+ return await addAttachment({
+ composeDispatch,
+ showActionSheetWithOptions
+ })
}
}, [composeState.poll.active, composeState.attachments.uploads])
@@ -64,7 +69,7 @@ const ComposeActions: React.FC = () => {
}, [composeState.visibility])
const visibilityOnPress = useCallback(() => {
if (!composeState.visibilityLock) {
- ActionSheetIOS.showActionSheetWithOptions(
+ showActionSheetWithOptions(
{
options: ['公开', '不公开', '仅关注着', '私信', '取消'],
cancelButtonIndex: 4
diff --git a/src/screens/Shared/Compose/Attachments.tsx b/src/screens/Shared/Compose/Attachments.tsx
index 6c8a6684..6f8cf070 100644
--- a/src/screens/Shared/Compose/Attachments.tsx
+++ b/src/screens/Shared/Compose/Attachments.tsx
@@ -1,6 +1,7 @@
import Button from '@components/Button'
import haptics from '@components/haptics'
import Icon from '@components/Icon'
+import { useActionSheet } from '@expo/react-native-action-sheet'
import { useNavigation } from '@react-navigation/native'
import { StyleConstants } from '@utils/styles/constants'
import layoutAnimation from '@utils/styles/layoutAnimation'
@@ -28,6 +29,7 @@ import { ExtendedAttachment } from './utils/types'
const DEFAULT_HEIGHT = 200
const ComposeAttachments: React.FC = () => {
+ const { showActionSheetWithOptions } = useActionSheet()
const { composeState, composeDispatch } = useContext(ComposeContext)
const { theme } = useTheme()
const navigation = useNavigation()
@@ -192,7 +194,9 @@ const ComposeAttachments: React.FC = () => {
backgroundColor: theme.backgroundOverlay
}
]}
- onPress={async () => await addAttachment({ composeDispatch })}
+ onPress={async () =>
+ await addAttachment({ composeDispatch, showActionSheetWithOptions })
+ }
>
{
+ const { showActionSheetWithOptions } = useActionSheet()
const {
composeState: {
poll: { total, options, multiple, expire }
@@ -111,7 +113,7 @@ const ComposePoll: React.FC = () => {
title='可选项'
content={multiple ? '多选' : '单选'}
onPress={() =>
- ActionSheetIOS.showActionSheetWithOptions(
+ showActionSheetWithOptions(
{
options: ['单选', '多选', '取消'],
cancelButtonIndex: 2
@@ -130,7 +132,7 @@ const ComposePoll: React.FC = () => {
title='有效期'
content={expireMapping[expire]}
onPress={() =>
- ActionSheetIOS.showActionSheetWithOptions(
+ showActionSheetWithOptions(
{
options: [...Object.values(expireMapping), '取消'],
cancelButtonIndex: 7
diff --git a/src/screens/Shared/Compose/addAttachment.ts b/src/screens/Shared/Compose/addAttachment.ts
index 0977faa7..1fc63efc 100644
--- a/src/screens/Shared/Compose/addAttachment.ts
+++ b/src/screens/Shared/Compose/addAttachment.ts
@@ -4,14 +4,16 @@ import * as Crypto from 'expo-crypto'
import { ImageInfo } from 'expo-image-picker/build/ImagePicker.types'
import * as VideoThumbnails from 'expo-video-thumbnails'
import { Dispatch } from 'react'
-import { ActionSheetIOS, Alert, Linking } from 'react-native'
+import { Alert, Linking } from 'react-native'
import { ComposeAction } from './utils/types'
+import { ActionSheetOptions } from '@expo/react-native-action-sheet'
export interface Props {
composeDispatch: Dispatch
+ showActionSheetWithOptions: (options: ActionSheetOptions, callback: (i: number) => void) => void
}
-const addAttachment = async ({ composeDispatch }: Props): Promise => {
+const addAttachment = async ({ composeDispatch, showActionSheetWithOptions }: Props): Promise => {
const uploadAttachment = async (result: ImageInfo) => {
const hash = await Crypto.digestStringAsync(
Crypto.CryptoDigestAlgorithm.SHA256,
@@ -106,7 +108,7 @@ const addAttachment = async ({ composeDispatch }: Props): Promise => {
})
}
- ActionSheetIOS.showActionSheetWithOptions(
+ showActionSheetWithOptions(
{
options: ['从相册选取', '现照', '取消'],
cancelButtonIndex: 2
diff --git a/src/screens/Shared/ImagesViewer.tsx b/src/screens/Shared/ImagesViewer.tsx
index 735c8256..d6e061d8 100644
--- a/src/screens/Shared/ImagesViewer.tsx
+++ b/src/screens/Shared/ImagesViewer.tsx
@@ -1,9 +1,14 @@
-import haptics from '@components/haptics'
import { HeaderLeft, HeaderRight } from '@components/Header'
import { StyleConstants } from '@utils/styles/constants'
import { findIndex } from 'lodash'
import React, { useCallback, useState } from 'react'
-import { ActionSheetIOS, Image, StyleSheet, Text } from 'react-native'
+import {
+ Image,
+ Platform,
+ Share,
+ StyleSheet,
+ Text
+} from 'react-native'
import ImageViewer from 'react-native-image-zoom-viewer'
import { IImageInfo } from 'react-native-image-zoom-viewer/built/image-viewer.type'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
@@ -69,8 +74,18 @@ const ScreenSharedImagesViewer: React.FC = ({
),
[]
)
+
+ const onPress = useCallback(() => {
+ switch (Platform.OS) {
+ case 'ios':
+ return Share.share({ url: imageUrls[currentIndex].url })
+ case 'android':
+ return Share.share({ message: imageUrls[currentIndex].url })
+ }
+ }, [currentIndex])
+
return (
-
+
= ({
{currentIndex + 1} / {imageUrls.length}
),
- headerRight: () => (
-
- ActionSheetIOS.showShareActionSheetWithOptions(
- {
- url: imageUrls[currentIndex].url
- },
- () => haptics('Error'),
- () => haptics('Success')
- )
- }
- />
- )
+ headerRight: () =>
}}
/>
diff --git a/src/startup/dev.ts b/src/startup/dev.ts
index 4470036b..98a9d2de 100644
--- a/src/startup/dev.ts
+++ b/src/startup/dev.ts
@@ -6,12 +6,12 @@ const dev = () => {
if (__DEV__) {
Analytics.setDebugModeEnabled(true)
- log('log', 'devs', 'initializing wdyr')
- const whyDidYouRender = require('@welldone-software/why-did-you-render')
- whyDidYouRender(React, {
- trackHooks: true,
- hotReloadBufferMs: 1000
- })
+ // log('log', 'devs', 'initializing wdyr')
+ // const whyDidYouRender = require('@welldone-software/why-did-you-render')
+ // whyDidYouRender(React, {
+ // trackHooks: true,
+ // hotReloadBufferMs: 1000
+ // })
}
}
diff --git a/src/utils/slices/settingsSlice.ts b/src/utils/slices/settingsSlice.ts
index 1caa6f7d..29452615 100644
--- a/src/utils/slices/settingsSlice.ts
+++ b/src/utils/slices/settingsSlice.ts
@@ -3,7 +3,7 @@ import { RootState } from '@root/store'
import * as Analytics from 'expo-firebase-analytics'
export type SettingsState = {
- language: 'zh' | 'en' | undefined
+ language: 'zh-CN' | 'en-US' | undefined
theme: 'light' | 'dark' | 'auto'
browser: 'internal' | 'external'
analytics: boolean
diff --git a/yarn.lock b/yarn.lock
index c212bb0c..5b4f6c0c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1115,6 +1115,14 @@
"@expo/config" "3.3.22"
metro-react-native-babel-transformer "^0.58.0"
+"@expo/react-native-action-sheet@^3.8.0":
+ version "3.8.0"
+ resolved "https://registry.yarnpkg.com/@expo/react-native-action-sheet/-/react-native-action-sheet-3.8.0.tgz#0db8b70ea8550ceb2983abda8584efa3a61d7389"
+ integrity sha512-tCfwysuqy0sfaN+aA98IKUrwCLKsbDHSYLcnHrx9wNbawOHNez8rSeFtieAS48/HyrPI75yg/ZGvxe6UsJRS8Q==
+ dependencies:
+ "@types/hoist-non-react-statics" "^3.3.1"
+ hoist-non-react-statics "^3.3.0"
+
"@expo/spawn-async@^1.2.8":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@expo/spawn-async/-/spawn-async-1.5.0.tgz#799827edd8c10ef07eb1a2ff9dcfe081d596a395"
@@ -2091,7 +2099,7 @@
resolved "https://registry.yarnpkg.com/@types/hammerjs/-/hammerjs-2.0.36.tgz#17ce0a235e9ffbcdcdf5095646b374c2bf615a4c"
integrity sha512-7TUK/k2/QGpEAv/BCwSHlYu3NXZhQ9ZwBYpzr9tjlPIL2C5BeGhH3DmVavRx3ZNyELX5TLC91JTz/cen6AAtIQ==
-"@types/hoist-non-react-statics@^3.3.0":
+"@types/hoist-non-react-statics@^3.3.0", "@types/hoist-non-react-statics@^3.3.1":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
@@ -7307,6 +7315,11 @@ 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"
@@ -8173,6 +8186,11 @@ 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"