From c0d7f379b3281081cbeae7340902fc70084f6cba Mon Sep 17 00:00:00 2001 From: Zhiyuan Zheng Date: Tue, 17 Nov 2020 23:57:23 +0100 Subject: [PATCH] Posting using formData --- App.tsx | 5 + babel.config.js | 10 +- package.json | 3 + src/api/client.ts | 42 +++---- src/stacks/Shared/PostToot.tsx | 145 ++++++++++++++++++------ src/stacks/Shared/PostToot/PostMain.tsx | 35 +++++- src/stacks/Shared/PostToot/PostPoll.tsx | 144 +++++++++++++++++++++++ yarn.lock | 27 ++++- 8 files changed, 345 insertions(+), 66 deletions(-) create mode 100644 src/stacks/Shared/PostToot/PostPoll.tsx diff --git a/App.tsx b/App.tsx index 4a618c33..cd44b8ae 100644 --- a/App.tsx +++ b/App.tsx @@ -11,6 +11,11 @@ setConsole({ error: console.warn }) +if (__DEV__) { + const whyDidYouRender = require('@welldone-software/why-did-you-render') + whyDidYouRender(React) +} + const App: React.FC = () => ( diff --git a/babel.config.js b/babel.config.js index 0d70d123..630bfdc8 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,10 +1,16 @@ module.exports = function (api) { api.cache(true) return { - presets: ['babel-preset-expo'], + presets: [ + 'babel-preset-expo', + // { + // runtime: 'automatic', + // development: process.env.NODE_ENV === 'development', + // importSource: '@welldone-software/why-did-you-render' + // } + ], plugins: [ ['@babel/plugin-proposal-optional-chaining'], - // ['babel-plugin-typescript-to-proptypes'], [ 'module-resolver', { diff --git a/package.json b/package.json index 8845d9b3..eb2b673f 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "expo": "~39.0.4", "expo-app-auth": "~9.2.0", "expo-av": "~8.6.0", + "expo-image-picker": "~9.1.1", "expo-secure-store": "~9.2.0", "expo-splash-screen": "~0.6.1", "expo-status-bar": "~1.0.2", @@ -48,12 +49,14 @@ "@babel/core": "~7.12.3", "@babel/plugin-proposal-optional-chaining": "^7.12.1", "@types/lodash": "^4.14.164", + "@types/node": "^14.14.7", "@types/react": "~16.9.35", "@types/react-dom": "^16.9.9", "@types/react-native": "~0.63.2", "@types/react-native-htmlview": "^0.12.2", "@types/react-navigation": "^3.4.0", "@types/react-redux": "^7.1.11", + "@welldone-software/why-did-you-render": "^6.0.0-rc.1", "babel-plugin-module-resolver": "^4.0.0", "babel-plugin-typescript-to-proptypes": "^1.4.1", "typescript": "~3.9.2" diff --git a/src/api/client.ts b/src/api/client.ts index d684a4c4..8d149e8e 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -18,28 +18,29 @@ const client = async ({ query?: { [key: string]: string | number | boolean } - body?: object + body?: FormData }): Promise => { const state: RootState['instanceInfo'] = store.getState().instanceInfo let response - try { - response = await ky(endpoint, { - method: method, - prefixUrl: `https://${state[instance]}/api/${version}`, - searchParams: query, - headers: { - 'Content-Type': 'application/json', - ...headers, - ...(instance === 'local' && { - Authorization: `Bearer ${state.localToken}` - }) - }, - ...(body && { json: body }) - }) - } catch (error) { - return Promise.reject('ky error: ' + error) - } + // try { + response = await ky(endpoint, { + method: method, + prefixUrl: `https://${state[instance]}/api/${version}`, + searchParams: query, + headers: { + 'Content-Type': 'application/json', + ...headers, + ...(instance === 'local' && { + Authorization: `Bearer ${state.localToken}` + }) + }, + ...(body && { body: body }), + throwHttpErrors: false + }) + // } catch (error) { + // return Promise.reject('ky error: ' + error.json()) + // } if (response.ok) { return Promise.resolve({ @@ -47,8 +48,9 @@ const client = async ({ body: await response.json() }) } else { - console.error(response.status + ': ' + response.statusText) - return Promise.reject({ body: response.statusText }) + const errorResponse = await response.json() + console.error(response.status + ': ' + errorResponse.error) + return Promise.reject({ body: errorResponse.error }) } } diff --git a/src/stacks/Shared/PostToot.tsx b/src/stacks/Shared/PostToot.tsx index 8ab8cf55..35d415a1 100644 --- a/src/stacks/Shared/PostToot.tsx +++ b/src/stacks/Shared/PostToot.tsx @@ -31,6 +31,27 @@ export type PostState = { } | undefined emojis: mastodon.Emoji[] | undefined + poll: { + active: boolean + total: number + options: { + '1': string + '2': string + '3': string + '4': string + [key: string]: string + } + multiple: boolean + expire: + | '300' + | '1800' + | '3600' + | '21600' + | '86400' + | '259200' + | '604800' + | string + } } export type PostAction = @@ -54,6 +75,10 @@ export type PostAction = type: 'emojis' payload: PostState['emojis'] } + | { + type: 'poll' + payload: PostState['poll'] + } const postInitialState: PostState = { text: { @@ -64,7 +89,19 @@ const postInitialState: PostState = { selection: { start: 0, end: 0 }, overlay: null, tag: undefined, - emojis: undefined + emojis: undefined, + poll: { + active: false, + total: 2, + options: { + '1': '', + '2': '', + '3': '', + '4': '' + }, + multiple: false, + expire: '86400' + } } const postReducer = (state: PostState, action: PostAction): PostState => { switch (action.type) { @@ -78,6 +115,8 @@ const postReducer = (state: PostState, action: PostAction): PostState => { return { ...state, tag: action.payload } case 'emojis': return { ...state, emojis: action.payload } + case 'poll': + return { ...state, poll: action.payload } default: throw new Error('Unexpected action') } @@ -106,6 +145,72 @@ const PostToot: React.FC = () => { const [postState, postDispatch] = useReducer(postReducer, postInitialState) + const tootPost = async () => { + if (postState.text.count < 0) { + Alert.alert('字数超限', '', [ + { + text: '返回继续编辑' + } + ]) + } else { + const formData = new FormData() + formData.append('status', postState.text.raw) + if (postState.poll.active) { + 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', + instance: 'local', + endpoint: 'statuses', + headers: { + 'Idempotency-Key': Date.now().toString() + Math.random().toString() + }, + body: formData + }) + .then( + res => { + if (res.body.id) { + Alert.alert('发布成功', '', [ + { + text: '好的', + onPress: () => navigation.goBack() + } + ]) + } else { + Alert.alert('发布失败', '', [ + { + text: '返回重试' + } + ]) + } + }, + error => { + Alert.alert('发布失败', error.body, [ + { + text: '返回重试' + } + ]) + } + ) + .catch(() => { + Alert.alert('发布失败', '', [ + { + text: '返回重试' + } + ]) + }) + } + } + return ( { ), headerCenter: () => <>, headerRight: () => ( - { - if (postState.text.count < 0) { - Alert.alert('字数超限', '', [ - { - text: '返回继续编辑' - } - ]) - } else { - const res = await client({ - method: 'post', - instance: 'local', - endpoint: 'statuses', - headers: { - 'Idempotency-Key': - Date.now().toString() + Math.random().toString() - }, - query: { status: postState.text.raw } - }) - if (res.body.id) { - Alert.alert('发布成功', '', [ - { - text: '好的', - onPress: () => navigation.goBack() - } - ]) - } else { - Alert.alert('发布失败', '', [ - { - text: '返回重试' - } - ]) - } - } - }} - > + tootPost()}> 发嘟嘟 ) @@ -184,5 +254,6 @@ const PostToot: React.FC = () => { ) } +;(PostMain as any).whyDidYouRender = true export default PostToot diff --git a/src/stacks/Shared/PostToot/PostMain.tsx b/src/stacks/Shared/PostToot/PostMain.tsx index ec5cdc41..2d49dd18 100644 --- a/src/stacks/Shared/PostToot/PostMain.tsx +++ b/src/stacks/Shared/PostToot/PostMain.tsx @@ -13,15 +13,16 @@ import { TextInput, View } from 'react-native' +import { useQuery } from 'react-query' import { Feather } from '@expo/vector-icons' import { debounce, differenceWith, isEqual } from 'lodash' import Autolinker from 'src/modules/autolinker' -import PostSuggestions from './PostSuggestions' import PostEmojis from './PostEmojis' -import { useQuery } from 'react-query' +import PostPoll from './PostPoll' +import PostSuggestions from './PostSuggestions' import { emojisFetch } from 'src/stacks/common/emojisFetch' -import { PostAction, PostState } from '../PostToot' +import { PostAction, PostState } from 'src/stacks/Shared/PostToot' export interface Props { postState: PostState @@ -169,8 +170,13 @@ const PostMain: React.FC = ({ postState, postDispatch }) => { > {postState.text.formatted} + {postState.poll.active && ( + + + + )} {postState.overlay === 'suggestions' ? ( - + = ({ postState, postDispatch }) => { <> )} {postState.overlay === 'emojis' ? ( - + = ({ postState, postDispatch }) => { )} Keyboard.dismiss()}> - + + postDispatch({ + type: 'poll', + payload: { ...postState.poll, active: !postState.poll.active } + }) + } + /> = ({ postState, postDispatch }) => { ) } +// (PostSuggestions as any).whyDidYouRender = true, +// (PostPoll as any).whyDidYouRender = true, +// (PostEmojis as any).whyDidYouRender = true + const styles = StyleSheet.create({ main: { flex: 1 @@ -218,6 +237,10 @@ const styles = StyleSheet.create({ textInput: { backgroundColor: 'gray' }, + poll: { + flex: 1, + height: 100 + }, suggestions: { flex: 1, backgroundColor: 'lightyellow' diff --git a/src/stacks/Shared/PostToot/PostPoll.tsx b/src/stacks/Shared/PostToot/PostPoll.tsx new file mode 100644 index 00000000..7908ac1a --- /dev/null +++ b/src/stacks/Shared/PostToot/PostPoll.tsx @@ -0,0 +1,144 @@ +import React, { Dispatch, useState } from 'react' +import { + ActionSheetIOS, + Pressable, + StyleSheet, + Text, + TextInput, + View +} from 'react-native' +import { Feather } from '@expo/vector-icons' + +import { PostAction, PostState } from '../PostToot' + +export interface Props { + postState: PostState + postDispatch: Dispatch +} + +const PostPoll: React.FC = ({ postState, postDispatch }) => { + const expireMapping: { [key: string]: string } = { + '300': '5分钟', + '1800': '30分钟', + '3600': '1小时', + '21600': '6小时', + '86400': '1天', + '259200': '3天', + '604800': '7天' + } + + return ( + + {[...Array(postState.poll.total)].map((e, i) => ( + + {postState.poll.multiple ? ( + + ) : ( + + )} + + postDispatch({ + type: 'poll', + payload: { + ...postState.poll, + options: { ...postState.poll.options, [i]: e } + } + }) + } + /> + + ))} + + 2 ? 'black' : 'grey'} + onPress={() => + postState.poll.total > 2 && + postDispatch({ + type: 'poll', + payload: { ...postState.poll, total: postState.poll.total - 1 } + }) + } + /> + + postState.poll.total < 4 && + postDispatch({ + type: 'poll', + payload: { ...postState.poll, total: postState.poll.total + 1 } + }) + } + /> + + + ActionSheetIOS.showActionSheetWithOptions( + { + options: ['单选', '多选', '取消'], + cancelButtonIndex: 2 + }, + index => + index < 2 && + postDispatch({ + type: 'poll', + payload: { ...postState.poll, multiple: index === 1 } + }) + ) + } + > + {postState.poll.multiple ? '多选' : '单选'} + + + ActionSheetIOS.showActionSheetWithOptions( + { + options: [...Object.values(expireMapping), '取消'], + cancelButtonIndex: 7 + }, + index => + index < 7 && + postDispatch({ + type: 'poll', + payload: { + ...postState.poll, + expire: Object.keys(expireMapping)[index] + } + }) + ) + } + > + {expireMapping[postState.poll.expire]} + + + ) +} + +const styles = StyleSheet.create({ + base: { + flex: 1, + backgroundColor: 'green' + }, + option: { + height: 30, + margin: 5, + flexDirection: 'row' + }, + textInput: { + flex: 1, + backgroundColor: 'white' + }, + totalControl: { + alignSelf: 'flex-end', + flexDirection: 'row' + } +}) + +export default PostPoll diff --git a/yarn.lock b/yarn.lock index 9cf0dff2..c60ea007 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1363,6 +1363,11 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.164.tgz#52348bcf909ac7b4c1bcbeda5c23135176e5dfa0" integrity sha512-fXCEmONnrtbYUc5014avwBeMdhHHO8YJCkOBflUL9EoJBSKZ1dei+VO74fA7JkTHZ1GvZack2TyIw5U+1lT8jg== +"@types/node@^14.14.7": + version "14.14.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.7.tgz#8ea1e8f8eae2430cf440564b98c6dfce1ec5945d" + integrity sha512-Zw1vhUSQZYw+7u5dAwNbIA9TuTotpzY/OF7sJM9FqPOF3SPjKnxrjoTktXDZgUjybf4cWVBP7O8wvKdSaGHweg== + "@types/prop-types@*": version "15.7.3" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" @@ -1454,6 +1459,13 @@ invariant "^2.2.4" lodash "^4.5.0" +"@welldone-software/why-did-you-render@^6.0.0-rc.1": + version "6.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@welldone-software/why-did-you-render/-/why-did-you-render-6.0.0-rc.1.tgz#b0e92edb2e34e7af695cca1822844f02018d9814" + integrity sha512-qQe5w89tYnYtwRqlhdF33ivWjsQlGXkan5lFzNwpAoMEUFIbDuwvFiBUAbE76Lfz63GabSaf1vyuCusgJ7Rtqg== + dependencies: + lodash "^4" + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -2700,6 +2712,14 @@ expo-font@~8.3.0: fbjs "1.0.0" fontfaceobserver "^2.1.0" +expo-image-picker@~9.1.1: + version "9.1.1" + resolved "https://registry.yarnpkg.com/expo-image-picker/-/expo-image-picker-9.1.1.tgz#42abe9deb595fa9f9d8ac0d2ba8aad2cd6f69581" + integrity sha512-Etz2OQhRflfx+xFbSdma8QLZsnV/yq0M/yqYlsi3/RLiWAQYM/D/VmRfDDPiG10gm+KX3Xb5iKplNjPrWeTuQg== + dependencies: + expo-permissions "~9.3.0" + uuid "7.0.2" + expo-keep-awake@~8.3.0: version "8.3.0" resolved "https://registry.yarnpkg.com/expo-keep-awake/-/expo-keep-awake-8.3.0.tgz#11bb8073dfe453259926855c81d9f35db03a79b9" @@ -3845,7 +3865,7 @@ lodash.throttle@^4.1.1: resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ= -lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.3.0, lodash@^4.5.0, lodash@^4.6.0: +lodash@^4, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.3.0, lodash@^4.5.0, lodash@^4.6.0: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== @@ -5980,6 +6000,11 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= +uuid@7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.2.tgz#7ff5c203467e91f5e0d85cfcbaaf7d2ebbca9be6" + integrity sha512-vy9V/+pKG+5ZTYKf+VcphF5Oc6EFiu3W8Nv3P3zIh0EqVI80ZxOzuPfe9EHjkFNvf8+xuTHVeei4Drydlx4zjw== + uuid@^3.3.2, uuid@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"