mirror of
https://github.com/tooot-app/app
synced 2025-03-13 18:10:12 +01:00
Basic posting done
Except hidden text, layout and sensitive attachments
This commit is contained in:
parent
c0d7f379b3
commit
aecb5c5b3d
2
App.tsx
2
App.tsx
@ -13,7 +13,7 @@ setConsole({
|
||||
|
||||
if (__DEV__) {
|
||||
const whyDidYouRender = require('@welldone-software/why-did-you-render')
|
||||
whyDidYouRender(React)
|
||||
// whyDidYouRender(React)
|
||||
}
|
||||
|
||||
const App: React.FC = () => (
|
||||
|
@ -41,8 +41,9 @@ const client = async ({
|
||||
// } catch (error) {
|
||||
// return Promise.reject('ky error: ' + error.json())
|
||||
// }
|
||||
|
||||
console.log('upload done')
|
||||
if (response.ok) {
|
||||
console.log('returning ok')
|
||||
return Promise.resolve({
|
||||
headers: response.headers,
|
||||
body: await response.json()
|
||||
|
@ -10,6 +10,7 @@ import { SafeAreaView } from 'react-native-safe-area-context'
|
||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
|
||||
import store from 'src/stacks/common/store'
|
||||
import PostMain from './PostToot/PostMain'
|
||||
import client from 'src/api/client'
|
||||
|
||||
@ -52,6 +53,13 @@ export type PostState = {
|
||||
| '604800'
|
||||
| string
|
||||
}
|
||||
attachments: {
|
||||
id: string
|
||||
url: string
|
||||
preview_url: string
|
||||
description: string
|
||||
}[]
|
||||
visibility: 'public' | 'unlisted' | 'private' | 'direct'
|
||||
}
|
||||
|
||||
export type PostAction =
|
||||
@ -79,6 +87,25 @@ export type PostAction =
|
||||
type: 'poll'
|
||||
payload: PostState['poll']
|
||||
}
|
||||
| {
|
||||
type: 'attachments/add'
|
||||
payload: {
|
||||
id: string
|
||||
url: string
|
||||
preview_url: string
|
||||
description: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
type: 'attachments/remove'
|
||||
payload: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
type: 'visibility'
|
||||
payload: PostState['visibility']
|
||||
}
|
||||
|
||||
const postInitialState: PostState = {
|
||||
text: {
|
||||
@ -101,7 +128,11 @@ const postInitialState: PostState = {
|
||||
},
|
||||
multiple: false,
|
||||
expire: '86400'
|
||||
}
|
||||
},
|
||||
attachments: [],
|
||||
visibility: store.getState().instanceInfo.localAccount.locked
|
||||
? 'private'
|
||||
: 'public'
|
||||
}
|
||||
const postReducer = (state: PostState, action: PostAction): PostState => {
|
||||
switch (action.type) {
|
||||
@ -117,6 +148,15 @@ const postReducer = (state: PostState, action: PostAction): PostState => {
|
||||
return { ...state, emojis: action.payload }
|
||||
case 'poll':
|
||||
return { ...state, poll: action.payload }
|
||||
case 'attachments/add':
|
||||
return { ...state, attachments: state.attachments.concat(action.payload) }
|
||||
case 'attachments/remove':
|
||||
return {
|
||||
...state,
|
||||
attachments: state.attachments.filter(a => a.id !== action.payload.id)
|
||||
}
|
||||
case 'visibility':
|
||||
return { ...state, visibility: action.payload }
|
||||
default:
|
||||
throw new Error('Unexpected action')
|
||||
}
|
||||
@ -159,13 +199,11 @@ const PostToot: React.FC = () => {
|
||||
Object.values(postState.poll.options)
|
||||
.filter(e => e.length)
|
||||
.forEach(e => {
|
||||
console.log(e)
|
||||
formData.append('poll[options][]', e)
|
||||
})
|
||||
formData.append('poll[expires_in]', postState.poll.expire)
|
||||
formData.append('poll[multiple]', postState.poll.multiple.toString())
|
||||
}
|
||||
console.log(formData)
|
||||
|
||||
client({
|
||||
method: 'post',
|
||||
@ -254,6 +292,6 @@ const PostToot: React.FC = () => {
|
||||
</KeyboardAvoidingView>
|
||||
)
|
||||
}
|
||||
;(PostMain as any).whyDidYouRender = true
|
||||
// ;(PostMain as any).whyDidYouRender = true
|
||||
|
||||
export default PostToot
|
||||
|
66
src/stacks/Shared/PostToot/PostAttachments.tsx
Normal file
66
src/stacks/Shared/PostToot/PostAttachments.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
import React, { Dispatch } from 'react'
|
||||
import { Image, StyleSheet, View } from 'react-native'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
|
||||
import { PostAction, PostState } from '../PostToot'
|
||||
|
||||
export interface Props {
|
||||
postState: PostState
|
||||
postDispatch: Dispatch<PostAction>
|
||||
}
|
||||
|
||||
const PostAttachments: React.FC<Props> = ({ postState, postDispatch }) => {
|
||||
return (
|
||||
<View style={styles.base}>
|
||||
{postState.attachments.map((attachment, index) => (
|
||||
<View key={index} style={styles.imageContainer}>
|
||||
<Image
|
||||
style={styles.image}
|
||||
source={{ uri: attachment.preview_url }}
|
||||
/>
|
||||
<Feather
|
||||
name='edit'
|
||||
size={24}
|
||||
color='white'
|
||||
style={styles.buttonEdit}
|
||||
/>
|
||||
<Feather
|
||||
name='trash-2'
|
||||
size={24}
|
||||
color='white'
|
||||
style={styles.buttonRemove}
|
||||
onPress={() =>
|
||||
postDispatch({ type: 'attachments/remove', payload: attachment })
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
base: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
backgroundColor: 'lightgreen'
|
||||
},
|
||||
imageContainer: {
|
||||
flexBasis: 100
|
||||
},
|
||||
image: {
|
||||
flex: 1
|
||||
},
|
||||
buttonEdit: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0
|
||||
},
|
||||
buttonRemove: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0
|
||||
}
|
||||
})
|
||||
|
||||
export default PostAttachments
|
@ -6,6 +6,7 @@ import React, {
|
||||
useState
|
||||
} from 'react'
|
||||
import {
|
||||
ActionSheetIOS,
|
||||
Keyboard,
|
||||
Pressable,
|
||||
StyleSheet,
|
||||
@ -15,6 +16,7 @@ import {
|
||||
} from 'react-native'
|
||||
import { useQuery } from 'react-query'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
import * as ImagePicker from 'expo-image-picker'
|
||||
import { debounce, differenceWith, isEqual } from 'lodash'
|
||||
|
||||
import Autolinker from 'src/modules/autolinker'
|
||||
@ -23,6 +25,8 @@ import PostPoll from './PostPoll'
|
||||
import PostSuggestions from './PostSuggestions'
|
||||
import { emojisFetch } from 'src/stacks/common/emojisFetch'
|
||||
import { PostAction, PostState } from 'src/stacks/Shared/PostToot'
|
||||
import addAttachments from './addAttachments'
|
||||
import PostAttachments from './PostAttachments'
|
||||
|
||||
export interface Props {
|
||||
postState: PostState
|
||||
@ -30,6 +34,15 @@ export interface Props {
|
||||
}
|
||||
|
||||
const PostMain: React.FC<Props> = ({ postState, postDispatch }) => {
|
||||
useEffect(() => {
|
||||
;(async () => {
|
||||
const { status } = await ImagePicker.requestCameraRollPermissionsAsync()
|
||||
if (status !== 'granted') {
|
||||
alert('Sorry, we need camera roll permissions to make this work!')
|
||||
}
|
||||
})()
|
||||
}, [])
|
||||
|
||||
const [editorMinHeight, setEditorMinHeight] = useState(0)
|
||||
|
||||
const { data: emojisData } = useQuery(['Emojis'], emojisFetch)
|
||||
@ -139,6 +152,19 @@ const PostMain: React.FC<Props> = ({ postState, postDispatch }) => {
|
||||
})
|
||||
}, [])
|
||||
|
||||
const getVisibilityIcon = () => {
|
||||
switch (postState.visibility) {
|
||||
case 'public':
|
||||
return 'globe'
|
||||
case 'unlisted':
|
||||
return 'unlock'
|
||||
case 'private':
|
||||
return 'lock'
|
||||
case 'direct':
|
||||
return 'mail'
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<TextInput
|
||||
@ -170,6 +196,11 @@ const PostMain: React.FC<Props> = ({ postState, postDispatch }) => {
|
||||
>
|
||||
<Text>{postState.text.formatted}</Text>
|
||||
</TextInput>
|
||||
{postState.attachments.length > 0 && (
|
||||
<View style={styles.attachments}>
|
||||
<PostAttachments postState={postState} postDispatch={postDispatch} />
|
||||
</View>
|
||||
)}
|
||||
{postState.poll.active && (
|
||||
<View style={styles.poll}>
|
||||
<PostPoll postState={postState} postDispatch={postDispatch} />
|
||||
@ -198,7 +229,13 @@ const PostMain: React.FC<Props> = ({ postState, postDispatch }) => {
|
||||
<></>
|
||||
)}
|
||||
<Pressable style={styles.additions} onPress={() => Keyboard.dismiss()}>
|
||||
<Feather name='paperclip' size={24} />
|
||||
<Feather
|
||||
name='paperclip'
|
||||
size={24}
|
||||
onPress={async () =>
|
||||
await addAttachments({ postState, postDispatch })
|
||||
}
|
||||
/>
|
||||
<Feather
|
||||
name='bar-chart-2'
|
||||
size={24}
|
||||
@ -209,7 +246,34 @@ const PostMain: React.FC<Props> = ({ postState, postDispatch }) => {
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Feather name='eye-off' size={24} />
|
||||
<Feather
|
||||
name={getVisibilityIcon()}
|
||||
size={24}
|
||||
onPress={() =>
|
||||
ActionSheetIOS.showActionSheetWithOptions(
|
||||
{
|
||||
options: ['公开', '不公开', '仅关注着', '私信', '取消'],
|
||||
cancelButtonIndex: 4
|
||||
},
|
||||
buttonIndex => {
|
||||
switch (buttonIndex) {
|
||||
case 0:
|
||||
postDispatch({ type: 'visibility', payload: 'public' })
|
||||
break
|
||||
case 1:
|
||||
postDispatch({ type: 'visibility', payload: 'unlisted' })
|
||||
break
|
||||
case 2:
|
||||
postDispatch({ type: 'visibility', payload: 'private' })
|
||||
break
|
||||
case 3:
|
||||
postDispatch({ type: 'visibility', payload: 'direct' })
|
||||
break
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Feather
|
||||
name='smile'
|
||||
size={24}
|
||||
@ -237,6 +301,10 @@ const styles = StyleSheet.create({
|
||||
textInput: {
|
||||
backgroundColor: 'gray'
|
||||
},
|
||||
attachments: {
|
||||
flex: 1,
|
||||
height: 100
|
||||
},
|
||||
poll: {
|
||||
flex: 1,
|
||||
height: 100
|
||||
|
87
src/stacks/Shared/PostToot/addAttachments.ts
Normal file
87
src/stacks/Shared/PostToot/addAttachments.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import { Dispatch } from 'react'
|
||||
import { ActionSheetIOS, Alert } from 'react-native'
|
||||
import * as ImagePicker from 'expo-image-picker'
|
||||
import { ImageInfo } from 'expo-image-picker/build/ImagePicker.types'
|
||||
|
||||
import { PostAction, PostState } from '../PostToot'
|
||||
import client from 'src/api/client'
|
||||
|
||||
const uploadAttachment = async (uri: ImageInfo['uri']) => {
|
||||
const filename = uri.split('/').pop()
|
||||
|
||||
const match = /\.(\w+)$/.exec(filename!)
|
||||
const type = match ? `image/${match[1]}` : `image`
|
||||
|
||||
const formData = new FormData()
|
||||
formData.append('file', { uri: uri, name: filename, type: type })
|
||||
|
||||
return client({
|
||||
method: 'post',
|
||||
instance: 'local',
|
||||
endpoint: 'media',
|
||||
body: formData
|
||||
})
|
||||
.then(res => {
|
||||
if (res.body.id && res.body.type !== 'unknown') {
|
||||
console.log('url: ' + res.body.preview_url)
|
||||
return Promise.resolve({
|
||||
id: res.body.id,
|
||||
url: res.body.url,
|
||||
preview_url: res.body.preview_url,
|
||||
description: res.body.description
|
||||
})
|
||||
} else {
|
||||
Alert.alert('上传失败', '', [
|
||||
{
|
||||
text: '返回重试'
|
||||
}
|
||||
])
|
||||
return Promise.reject()
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
Alert.alert('上传失败', '', [
|
||||
{
|
||||
text: '返回重试'
|
||||
}
|
||||
])
|
||||
return Promise.reject()
|
||||
})
|
||||
}
|
||||
|
||||
const addAttachments = async ({
|
||||
postState,
|
||||
postDispatch
|
||||
}: {
|
||||
postState: PostState
|
||||
postDispatch: Dispatch<PostAction>
|
||||
}) => {
|
||||
ActionSheetIOS.showActionSheetWithOptions(
|
||||
{
|
||||
options: ['从相册选取', '现照', '取消'],
|
||||
cancelButtonIndex: 2
|
||||
},
|
||||
async buttonIndex => {
|
||||
if (buttonIndex === 0) {
|
||||
let result = await ImagePicker.launchImageLibraryAsync({
|
||||
mediaTypes: ImagePicker.MediaTypeOptions.All,
|
||||
exif: false
|
||||
})
|
||||
|
||||
if (!result.cancelled) {
|
||||
const response = await uploadAttachment(result.uri)
|
||||
if (response.id) {
|
||||
postDispatch({
|
||||
type: 'attachments/add',
|
||||
payload: response
|
||||
})
|
||||
}
|
||||
}
|
||||
} else if (buttonIndex === 1) {
|
||||
// setResult(Math.floor(Math.random() * 100) + 1)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export default addAttachments
|
@ -8,6 +8,9 @@ const preloadedState = {
|
||||
local: 'social.xmflsct.com',
|
||||
localToken: 'qjzJ0IjvZ1apsn0_wBkGcdjKgX7Dao9KEPhGwggPwAo',
|
||||
localAccountId: '1',
|
||||
localAccount: {
|
||||
locked: false
|
||||
},
|
||||
remote: 'mastodon.social'
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user