This commit is contained in:
Zhiyuan Zheng 2021-03-21 23:06:53 +01:00
parent d15e8cb652
commit 543ac86d03
No known key found for this signature in database
GPG Key ID: 078A93AB607D85E0
14 changed files with 174 additions and 46 deletions

View File

@ -60,7 +60,6 @@ declare namespace Nav {
url: Mastodon.AttachmentImage['url']
width?: number
height?: number
preview_url: Mastodon.AttachmentImage['preview_url']
remote_url?: Mastodon.AttachmentImage['remote_url']
}[]
id: Mastodon.Attachment['id']

View File

@ -1,5 +1,5 @@
import { RootState } from '@root/store'
import axios from 'axios'
import axios, { AxiosRequestConfig } from 'axios'
import chalk from 'chalk'
import li from 'li'
@ -14,7 +14,10 @@ export type Params = {
}
headers?: { [key: string]: string }
body?: FormData
onUploadProgress?: (progressEvent: any) => void
extras?: Omit<
AxiosRequestConfig,
'method' | 'url' | 'params' | 'headers' | 'data'
>
}
const apiInstance = async <T = unknown>({
@ -24,7 +27,7 @@ const apiInstance = async <T = unknown>({
params,
headers,
body,
onUploadProgress
extras
}: Params): Promise<{ body: T; links: { prev?: string; next?: string } }> => {
const { store } = require('@root/store')
const state = store.getState() as RootState
@ -70,7 +73,7 @@ const apiInstance = async <T = unknown>({
})
},
...(body && { data: body }),
...(onUploadProgress && { onUploadProgress: onUploadProgress })
...extras
})
.then(response => {
let prev

View File

@ -102,7 +102,11 @@ const GracefullyImage = React.memo(
return (
<Pressable
style={[style, dimension, { backgroundColor: theme.shimmerDefault }]}
style={[
style,
dimension,
{ backgroundColor: theme.backgroundOverlayDefault }
]}
{...(onPress
? hidden
? { disabled: true }

View File

@ -121,7 +121,7 @@ const renderNode = ({
onPress={async () => {
analytics('status_link_press')
!disableDetails && !shouldBeTag
? await openLink(href)
? await openLink(href, navigation)
: navigation.push('Tab-Shared-Hashtag', {
hashtag: content.substring(1)
})

View File

@ -48,7 +48,6 @@ const TimelineAttachment = React.memo(
imageUrls.push({
id: attachment.id,
url: attachment.url,
preview_url: attachment.preview_url,
remote_url: attachment.remote_url,
width: attachment.meta?.original?.width,
height: attachment.meta?.original?.height

View File

@ -1,6 +1,7 @@
import analytics from '@components/analytics'
import GracefullyImage from '@components/GracefullyImage'
import openLink from '@components/openLink'
import { useNavigation } from '@react-navigation/native'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React from 'react'
@ -13,13 +14,14 @@ export interface Props {
const TimelineCard = React.memo(
({ card }: Props) => {
const { theme } = useTheme()
const navigation = useNavigation()
return (
<Pressable
style={[styles.card, { borderColor: theme.border }]}
onPress={async () => {
analytics('timeline_shared_card_press')
await openLink(card.url)
await openLink(card.url, navigation)
}}
testID='base'
>

View File

@ -1,9 +1,127 @@
import apiInstance from '@api/instance'
import { NavigationProp, ParamListBase } from '@react-navigation/native'
import { navigationRef } from '@root/Screens'
import { store } from '@root/store'
import { SearchResult } from '@utils/queryHooks/search'
import { getInstanceUrl } from '@utils/slices/instancesSlice'
import { getSettingsBrowser } from '@utils/slices/settingsSlice'
import * as Linking from 'expo-linking'
import * as WebBrowser from 'expo-web-browser'
const openLink = async (url: string) => {
// https://social.xmflsct.com/web/statuses/105590085754428765 <- default
// https://social.xmflsct.com/@tooot/105590085754428765 <- pretty
const matcherStatus = new RegExp(
/http[s]?:\/\/(.*)\/(web\/statuses|@.*)\/([0-9]*)/
)
// https://social.xmflsct.com/web/accounts/14195 <- default
// https://social.xmflsct.com/@tooot <- pretty
const matcherAccount = new RegExp(
/http[s]?:\/\/(.*)\/(web\/accounts\/([0-9]*)|@.*)/
)
export let loadingLink = false
const openLink = async (
url: string,
navigation?: NavigationProp<
ParamListBase,
string,
Readonly<{
key: string
index: number
routeNames: string[]
history?: unknown[] | undefined
routes: any[]
type: string
stale: false
}>,
{},
{}
>
) => {
if (loadingLink) {
return
}
const handleNavigation = (
page: 'Tab-Shared-Toot' | 'Tab-Shared-Account',
options: {}
) => {
if (navigation) {
// @ts-ignore
navigation.push(page, options)
} else {
navigationRef.current?.navigate(page, options)
}
}
// If a tooot can be found
const matchedStatus = url.match(matcherStatus)
if (matchedStatus) {
// If the link in current instance
const instanceUrl = getInstanceUrl(store.getState())
if (matchedStatus[1] === instanceUrl) {
handleNavigation('Tab-Shared-Toot', {
toot: { id: matchedStatus[3] }
})
return
}
loadingLink = true
let response
try {
response = await apiInstance<SearchResult>({
version: 'v2',
method: 'get',
url: 'search',
params: { type: 'statuses', q: url, limit: 1, resolve: true }
})
} catch {}
if (response && response.body && response.body.statuses.length) {
handleNavigation('Tab-Shared-Toot', {
toot: response.body.statuses[0]
})
loadingLink = false
return
}
}
// If an account can be found
const matchedAccount = url.match(matcherAccount)
console.log(matchedAccount)
if (matchedAccount) {
// If the link in current instance
const instanceUrl = getInstanceUrl(store.getState())
if (matchedAccount[1] === instanceUrl) {
if (matchedAccount[3] && matchedAccount[3].match(/[0-9]*/)) {
handleNavigation('Tab-Shared-Account', {
account: { id: matchedAccount[3] }
})
return
}
}
loadingLink = true
let response
try {
response = await apiInstance<SearchResult>({
version: 'v2',
method: 'get',
url: 'search',
params: { type: 'accounts', q: url, limit: 1, resolve: true }
})
} catch {}
if (response && response.body && response.body.accounts.length) {
handleNavigation('Tab-Shared-Account', {
account: response.body.accounts[0]
})
loadingLink = false
return
}
}
loadingLink = false
switch (getSettingsBrowser(store.getState())) {
case 'internal':
await WebBrowser.openBrowserAsync(url, {

View File

@ -15,12 +15,3 @@ export type Position = {
x: number
y: number
}
export type ImageSource = {
id: string
preview_url: string
remote_url?: string
url: string
width?: number
height?: number
}

View File

@ -14,17 +14,18 @@ import {
View,
VirtualizedList
} from 'react-native'
import { ImageSource } from './@types'
import ImageItem from './components/ImageItem'
import useAnimatedComponents from './hooks/useAnimatedComponents'
import useImageIndexChange from './hooks/useImageIndexChange'
import useRequestClose from './hooks/useRequestClose'
type Props = {
images: ImageSource[]
images: Nav.RootStackParamList['Screen-ImagesViewer']['imageUrls']
imageIndex: number
onRequestClose: () => void
onLongPress?: (image: ImageSource) => void
onLongPress?: (
image: Nav.RootStackParamList['Screen-ImagesViewer']['imageUrls'][0]
) => void
onImageIndexChange?: (imageIndex: number) => void
backgroundColor?: string
swipeToCloseEnabled?: boolean
@ -48,7 +49,11 @@ function ImageViewer ({
delayLongPress = DEFAULT_DELAY_LONG_PRESS,
HeaderComponent
}: Props) {
const imageList = React.createRef<VirtualizedList<ImageSource>>()
const imageList = React.createRef<
VirtualizedList<
Nav.RootStackParamList['Screen-ImagesViewer']['imageUrls'][0]
>
>()
const [opacity, onRequestCloseEnhanced] = useRequestClose(onRequestClose)
const [currentImageIndex, onScroll] = useImageIndexChange(imageIndex, SCREEN)
const [headerTransform, toggleBarsVisible] = useAnimatedComponents()

View File

@ -9,7 +9,6 @@
import GracefullyImage from '@components/GracefullyImage'
import React, { useState, useCallback } from 'react'
import { Animated, Dimensions, StyleSheet } from 'react-native'
import { ImageSource } from '../@types'
import usePanResponder from '../hooks/usePanResponder'
import { getImageStyles, getImageTransform } from '../utils'
@ -18,10 +17,12 @@ const SCREEN_WIDTH = SCREEN.width
const SCREEN_HEIGHT = SCREEN.height
type Props = {
imageSrc: ImageSource
imageSrc: Nav.RootStackParamList['Screen-ImagesViewer']['imageUrls'][0]
onRequestClose: () => void
onZoom: (isZoomed: boolean) => void
onLongPress: (image: ImageSource) => void
onLongPress: (
image: Nav.RootStackParamList['Screen-ImagesViewer']['imageUrls'][0]
) => void
delayLongPress: number
swipeToCloseEnabled?: boolean
doubleTapToZoomEnabled?: boolean
@ -89,7 +90,6 @@ const ImageItem = ({
children={
<GracefullyImage
uri={{
preview: imageSrc.preview_url,
original: imageSrc.url,
remote: imageSrc.remote_url
}}

View File

@ -21,7 +21,6 @@ import {
State,
TapGestureHandler
} from 'react-native-gesture-handler'
import { ImageSource } from '../@types'
import useDoubleTapToZoom from '../hooks/useDoubleTapToZoom'
import { getImageStyles, getImageTransform } from '../utils'
@ -32,10 +31,12 @@ const SCREEN_WIDTH = SCREEN.width
const SCREEN_HEIGHT = SCREEN.height
type Props = {
imageSrc: ImageSource
imageSrc: Nav.RootStackParamList['Screen-ImagesViewer']['imageUrls'][0]
onRequestClose: () => void
onZoom: (scaled: boolean) => void
onLongPress: (image: ImageSource) => void
onLongPress: (
image: Nav.RootStackParamList['Screen-ImagesViewer']['imageUrls'][0]
) => void
swipeToCloseEnabled?: boolean
}
@ -144,7 +145,6 @@ const ImageItem = ({
children={
<GracefullyImage
uri={{
preview: imageSrc.preview_url,
original: imageSrc.url,
remote: imageSrc.remote_url
}}

View File

@ -6,7 +6,7 @@
*
*/
import { useMemo, useEffect, useCallback } from 'react'
import { useMemo, useEffect } from 'react'
import {
Animated,
Dimensions,

View File

@ -20,15 +20,9 @@ import {
} from 'react-native-safe-area-context'
import ImageViewer from './ImageViewer/Root'
type ImageUrl = {
url: string
width?: number | undefined
height?: number | undefined
preview_url: string
remote_url?: string | undefined
}
const saveImage = async (image: ImageUrl) => {
const saveImage = async (
image: Nav.RootStackParamList['Screen-ImagesViewer']['imageUrls'][0]
) => {
const hasAndroidPermission = async () => {
const permission = PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE
@ -44,9 +38,17 @@ const saveImage = async (image: ImageUrl) => {
if (Platform.OS === 'android' && !(await hasAndroidPermission())) {
return
}
CameraRoll.save(image.url || image.remote_url || image.preview_url)
CameraRoll.save(image.url)
.then(() => haptics('Success'))
.catch(() => haptics('Error'))
.catch(() => {
if (image.remote_url) {
CameraRoll.save(image.remote_url)
.then(() => haptics('Success'))
.catch(() => haptics('Error'))
} else {
haptics('Error')
}
})
}
const HeaderComponent = React.memo(
@ -57,7 +59,7 @@ const HeaderComponent = React.memo(
}: {
navigation: ScreenImagesViewerProp['navigation']
currentIndex: number
imageUrls: ImageUrl[]
imageUrls: Nav.RootStackParamList['Screen-ImagesViewer']['imageUrls']
}) => {
const insets = useSafeAreaInsets()
const { t } = useTranslation('screenImageViewer')

View File

@ -11,7 +11,7 @@ export type QueryKey = [
}
]
type SearchResult = {
export type SearchResult = {
accounts: Mastodon.Account[]
hashtags: Mastodon.Tag[]
statuses: Mastodon.Status[]
@ -23,7 +23,12 @@ const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => {
version: 'v2',
method: 'get',
url: 'search',
params: { ...(type && { type }), ...(term && { q: term }), limit }
params: {
...(type && { type }),
...(term && { q: term }),
limit,
resolve: true
}
}).then(res => res.body)
}