1
0
mirror of https://github.com/tooot-app/app synced 2025-06-05 22:19:13 +02:00
This commit is contained in:
xmflsct
2022-08-12 16:44:28 +02:00
parent a2721d21c0
commit 397c6a2f44
24 changed files with 599 additions and 937 deletions

View File

@ -1,9 +1,11 @@
import analytics from '@components/analytics'
import { HeaderLeft } from '@components/Header'
import { displayMessage, Message } from '@components/Message'
import CustomText from '@components/Text'
import navigationRef from '@helpers/navigationRef'
import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
import ScreenAccountSelection from '@screens/AccountSelection'
import ScreenActions from '@screens/Actions'
import ScreenAnnouncements from '@screens/Announcements'
import ScreenCompose from '@screens/Compose'
@ -170,7 +172,6 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
}
| { data: string | string[]; mimeType: string }
) => {
console.log('item', item)
if (instanceActive < 0) {
return
}
@ -239,12 +240,6 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
if (!item.mimeType) {
return
}
let tempData: string[]
if (!Array.isArray(item.data)) {
tempData = [item.data]
} else {
tempData = item.data
}
for (const d of item.data) {
filterMedia({ uri: d, mime: item.mimeType })
}
@ -254,11 +249,20 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
if (!text && !media.length) {
return
} else {
console.log('media', media)
navigationRef.navigate('Screen-Compose', { type: 'share', text, media })
if (instances.length > 1) {
navigationRef.navigate('Screen-AccountSelection', {
share: { text, media }
})
} else {
navigationRef.navigate('Screen-Compose', {
type: 'share',
text,
media
})
}
}
},
[instanceActive]
[instances.length]
)
useEffect(() => {
ShareMenu.getInitialShare(handleShare)
@ -333,6 +337,23 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
animation: 'fade'
}}
/>
<Stack.Screen
name='Screen-AccountSelection'
component={ScreenAccountSelection}
options={({ navigation }) => ({
title: t('screenAccountSelection:heading'),
headerShadowVisible: false,
presentation: 'modal',
gestureEnabled: false,
headerLeft: () => (
<HeaderLeft
type='text'
content={t('common:buttons.cancel')}
onPress={() => navigation.goBack()}
/>
)
})}
/>
</Stack.Navigator>
<Message />

View File

@ -1,8 +1,6 @@
import axios from 'axios'
import chalk from 'chalk'
import Constants from 'expo-constants'
const ctx = new chalk.Instance({ level: 3 })
import handleError, { ctx } from './handleError'
export type Params = {
method: 'get' | 'post' | 'put' | 'delete'
@ -57,40 +55,7 @@ const apiGeneral = async <T = unknown>({
body: response.data
})
})
.catch(error => {
if (error?.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.error(
ctx.bold(' API general '),
ctx.bold('response'),
error.response.status,
error.response.data.error
)
return Promise.reject({
status: error?.response.status,
message: error?.response.data.error
})
} else if (error?.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.error(
ctx.bold(' API general '),
ctx.bold('request'),
error.request
)
return Promise.reject()
} else {
console.error(
ctx.bold(' API general '),
ctx.bold('internal'),
error?.message,
url
)
return Promise.reject()
}
})
.catch(handleError)
}
export default apiGeneral

38
src/api/handleError.ts Normal file
View File

@ -0,0 +1,38 @@
import chalk from 'chalk'
export const ctx = new chalk.Instance({ level: 3 })
const handleError = (error: any) => {
if (error?.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.error(
ctx.bold(' API instance '),
ctx.bold('response'),
error.response.status,
error?.response.data?.error || error?.response.message || 'Unknown error'
)
return Promise.reject({
status: error?.response.status,
message:
error?.response.data?.error ||
error?.response.message ||
'Unknown error'
})
} else if (error?.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.error(ctx.bold(' API instance '), ctx.bold('request'), error)
return Promise.reject()
} else {
console.error(
ctx.bold(' API instance '),
ctx.bold('internal'),
error?.message
)
return Promise.reject()
}
}
export default handleError

View File

@ -1,10 +1,8 @@
import { RootState } from '@root/store'
import axios, { AxiosRequestConfig } from 'axios'
import chalk from 'chalk'
import Constants from 'expo-constants'
import li from 'li'
const ctx = new chalk.Instance({ level: 3 })
import handleError, { ctx } from './handleError'
export type Params = {
method: 'get' | 'post' | 'put' | 'delete' | 'patch'
@ -99,36 +97,7 @@ const apiInstance = async <T = unknown>({
links: { prev, next }
})
})
.catch(error => {
if (error?.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.error(
ctx.bold(' API instance '),
ctx.bold('response'),
error.response.status,
error.response.data.error
)
return Promise.reject({
status: error?.response.status,
message: error?.response.data.error
})
} else if (error?.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.error(ctx.bold(' API instance '), ctx.bold('request'), error)
return Promise.reject()
} else {
console.error(
ctx.bold(' API instance '),
ctx.bold('internal'),
error?.message,
url
)
return Promise.reject()
}
})
.catch(handleError)
}
export default apiInstance

View File

@ -1,10 +1,8 @@
import { mapEnvironment } from '@utils/checkEnvironment'
import axios from 'axios'
import chalk from 'chalk'
import Constants from 'expo-constants'
import * as Sentry from 'sentry-expo'
const ctx = new chalk.Instance({ level: 3 })
import handleError, { ctx } from './handleError'
export type Params = {
method: 'get' | 'post' | 'put' | 'delete'
@ -75,38 +73,7 @@ const apiTooot = async <T = unknown>({
Sentry.Native.captureException(error)
}
if (error?.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.error(
ctx.bold(' API tooot '),
ctx.bold('response'),
error.response.status,
error.response.data.error
)
return Promise.reject({
status: error?.response.status,
message: error?.response.data.error
})
} else if (error?.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.error(
ctx.bold(' API tooot '),
ctx.bold('request'),
error.request
)
return Promise.reject()
} else {
console.error(
ctx.bold(' API tooot '),
ctx.bold('internal'),
error?.message,
url
)
return Promise.reject()
}
return handleError(error)
})
}

View File

@ -0,0 +1,45 @@
import { useNavigation } from '@react-navigation/native'
import initQuery from '@utils/initQuery'
import { InstanceLatest } from '@utils/migrations/instances/migration'
import { StyleConstants } from '@utils/styles/constants'
import React from 'react'
import Button from './Button'
import haptics from './haptics'
interface Props {
instance: InstanceLatest
selected?: boolean
additionalActions?: () => void
}
const AccountButton: React.FC<Props> = ({
instance,
selected = false,
additionalActions
}) => {
const navigation = useNavigation()
return (
<Button
type='text'
selected={selected}
style={{
marginBottom: StyleConstants.Spacing.M,
marginRight: StyleConstants.Spacing.M
}}
content={`@${instance.account.acct}@${instance.uri}${
selected ? ' ✓' : ''
}`}
onPress={() => {
haptics('Light')
initQuery({ instance, prefetch: { enabled: true } })
navigation.goBack()
if (additionalActions) {
additionalActions()
}
}}
/>
)
}
export default AccountButton

View File

@ -92,7 +92,6 @@ const TimelineHeaderDefault = ({ queryKey, status, highlighted }: Props) => {
dropdownMenuMode
actions={actions}
onPress={({ nativeEvent: { index } }) => {
console.log('index', index)
for (const on of [
shareOnPress,
statusOnPress,

View File

@ -72,7 +72,6 @@ const openLink = async (url: string, navigation?: any) => {
// 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())

View File

@ -2,6 +2,7 @@ export default {
common: require('./common'),
screens: require('./screens'),
screenAccountSelection: require('./screens/accountSelection.json'),
screenActions: require('./screens/actions'),
screenAnnouncements: require('./screens/announcements'),
screenCompose: require('./screens/compose'),

View File

@ -0,0 +1,6 @@
{
"heading": "Share to ...",
"content": {
"select_account": "Select account"
}
}

View File

@ -0,0 +1,163 @@
import AccountButton from '@components/AccountButton'
import CustomText from '@components/Text'
import navigationRef from '@helpers/navigationRef'
import { RootStackScreenProps } from '@utils/navigation/navigators'
import { getInstances } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import * as VideoThumbnails from 'expo-video-thumbnails'
import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { FlatList, Image, ScrollView, View } from 'react-native'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { useSelector } from 'react-redux'
const Share = ({
text,
media
}: {
text?: string | undefined
media?:
| {
uri: string
mime: string
}[]
| undefined
}) => {
const { colors } = useTheme()
const [images, setImages] = useState<string[]>([])
useEffect(() => {
const prepareThumbs = async (media: { uri: string; mime: string }[]) => {
const thumbs: string[] = []
for (const m of media) {
if (m.mime.startsWith('image/')) {
thumbs.push(m.uri)
} else if (m.mime.startsWith('video/')) {
const { uri } = await VideoThumbnails.getThumbnailAsync(m.uri)
thumbs.push(uri)
}
}
setImages(thumbs)
}
if (media) {
prepareThumbs(media)
}
}, [])
if (text) {
return (
<CustomText
fontSize='S'
style={{
color: colors.primaryDefault,
padding: StyleConstants.Spacing.M,
borderWidth: 1,
borderColor: colors.shimmerHighlight,
borderRadius: 8
}}
children={text}
/>
)
}
if (media) {
return (
<View
style={{
padding: StyleConstants.Spacing.M,
borderWidth: 1,
borderColor: colors.shimmerHighlight,
borderRadius: 8
}}
>
<FlatList
horizontal
data={images}
renderItem={({ item }) => (
<Image source={{ uri: item }} style={{ width: 88, height: 88 }} />
)}
ItemSeparatorComponent={() => (
<View style={{ width: StyleConstants.Spacing.S }} />
)}
/>
</View>
)
}
return null
}
// Only needed when data incoming into the app when there are multiple accounts
const ScreenAccountSelection = ({
route: {
params: { share }
}
}: RootStackScreenProps<'Screen-AccountSelection'>) => {
const { colors } = useTheme()
const { t } = useTranslation('screenAccountSelection')
const instances = useSelector(getInstances, () => true)
return (
<SafeAreaProvider>
<ScrollView
style={{ marginBottom: StyleConstants.Spacing.L * 2 }}
keyboardShouldPersistTaps='always'
>
<View
style={{
marginHorizontal: StyleConstants.Spacing.Global.PagePadding
}}
>
{share ? <Share {...share} /> : null}
<CustomText
fontStyle='M'
fontWeight='Bold'
style={{
textAlign: 'center',
marginTop: StyleConstants.Spacing.L,
marginBottom: StyleConstants.Spacing.S,
color: colors.primaryDefault
}}
>
{t('content.select_account')}
</CustomText>
<View
style={{
flex: 1,
flexDirection: 'row',
flexWrap: 'wrap',
marginTop: StyleConstants.Spacing.M
}}
>
{instances.length
? instances
.slice()
.sort((a, b) =>
`${a.uri}${a.account.acct}`.localeCompare(
`${b.uri}${b.account.acct}`
)
)
.map((instance, index) => {
return (
<AccountButton
key={index}
instance={instance}
additionalActions={() => {
navigationRef.navigate('Screen-Compose', {
type: 'share',
...share
})
}}
/>
)
})
: null}
</View>
</View>
</ScrollView>
</SafeAreaProvider>
)
}
export default ScreenAccountSelection

View File

@ -42,7 +42,6 @@ export const uploadAttachment = async ({
case 'video':
VideoThumbnails.getThumbnailAsync(media.uri)
.then(({ uri, width, height }) => {
console.log('new', uri, width, height)
composeDispatch({
type: 'attachment/upload/start',
payload: {

View File

@ -1,11 +1,6 @@
import analytics from '@components/analytics'
import Button from '@components/Button'
import haptics from '@components/haptics'
import AccountButton from '@components/AccountButton'
import ComponentInstance from '@components/Instance'
import CustomText from '@components/Text'
import { useNavigation } from '@react-navigation/native'
import initQuery from '@utils/initQuery'
import { InstanceLatest } from '@utils/migrations/instances/migration'
import { getInstanceActive, getInstances } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
@ -15,35 +10,6 @@ import { KeyboardAvoidingView, Platform, StyleSheet, View } from 'react-native'
import { ScrollView } from 'react-native-gesture-handler'
import { useSelector } from 'react-redux'
interface Props {
instance: InstanceLatest
selected?: boolean
}
const AccountButton: React.FC<Props> = ({ instance, selected = false }) => {
const navigation = useNavigation()
return (
<Button
type='text'
selected={selected}
style={{
marginBottom: StyleConstants.Spacing.M,
marginRight: StyleConstants.Spacing.M
}}
content={`@${instance.account.acct}@${instance.uri}${
selected ? ' ✓' : ''
}`}
onPress={() => {
haptics('Light')
analytics('switch_existing_press')
initQuery({ instance, prefetch: { enabled: true } })
navigation.goBack()
}}
/>
)
}
const TabMeSwitch: React.FC = () => {
const { t } = useTranslation('screenTabs')
const { colors } = useTheme()

View File

@ -6,7 +6,6 @@ const sentry = () => {
Sentry.init({
dsn: 'https://53348b60ff844d52886e90251b3a5f41@o917354.ingest.sentry.io/6410576',
enableInExpoDevelopment: false,
// debug: !isRelease,
autoSessionTracking: true
})
}

View File

@ -3,6 +3,7 @@ import { NavigatorScreenParams } from '@react-navigation/native'
import { NativeStackScreenProps } from '@react-navigation/native-stack'
import { StackNavigationProp } from '@react-navigation/stack'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import React from 'react'
export type RootStackParamList = {
'Screen-Tabs': NavigatorScreenParams<ScreenTabsStackParamList>
@ -57,6 +58,10 @@ export type RootStackParamList = {
}[]
id: Mastodon.Attachment['id']
}
'Screen-AccountSelection': {
component?: () => JSX.Element | undefined
share?: { text?: string; media?: { uri: string; mime: string }[] }
}
}
export type RootStackScreenProps<T extends keyof RootStackParamList> =
NativeStackScreenProps<RootStackParamList, T>