Actions are working well!

This commit is contained in:
Zhiyuan Zheng 2020-11-05 00:47:31 +01:00
parent 631636db15
commit c825895b92
No known key found for this signature in database
GPG Key ID: 078A93AB607D85E0
11 changed files with 4280 additions and 118 deletions

4150
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -24,6 +24,7 @@
"expo-splash-screen": "~0.6.1",
"expo-status-bar": "~1.0.2",
"ky": "^0.24.0",
"lodash": "^4.17.20",
"react": "16.13.1",
"react-dom": "16.13.1",
"react-native": "https://github.com/expo/react-native/archive/sdk-39.0.3.tar.gz",
@ -44,6 +45,7 @@
"devDependencies": {
"@babel/core": "~7.9.0",
"@babel/plugin-proposal-optional-chaining": "^7.12.1",
"@types/lodash": "^4.14.164",
"@types/react": "^16.9.55",
"@types/react-dom": "^16.9.9",
"@types/react-native": "^0.63.30",

38
src/@types/store.d.ts vendored
View File

@ -1,13 +1,11 @@
declare namespace store {
type AsyncStatus = 'idle' | 'loading' | 'succeeded' | 'failed'
type InstanceInfoState = {
local: string
localToken: string
remote: string
}
type TimelinePage =
type Pages =
| 'Following'
| 'Local'
| 'LocalPublic'
@ -20,28 +18,14 @@ declare namespace store {
| 'Account_All'
| 'Account_Media'
type TimelineState = {
toots: mastodon.Status[] | []
pointer?: string
status: AsyncStatus
}
type TimelinesState = {
Following: TimelineState
Local: TimelineState
LocalPublic: TimelineState
RemotePublic: TimelineState
Notifications: TimelineState
Hashtag: TimelineState
List: TimelineState
Toot: TimelineState
Account_Default: TimelineState
Account_All: TimelineState
Account_Media: TimelineState
}
type AccountState = {
account: mastodon.Account | {}
status: AsyncStatus
}
type QueryKey = [
Pages,
{
page: Pages
hashtag?: string
list?: string
toot?: string
account?: string
}
]
}

View File

@ -1,6 +1,7 @@
import React, { useEffect, useState } from 'react'
import {
ActionSheetIOS,
Alert,
Clipboard,
Modal,
Pressable,
@ -8,13 +9,51 @@ import {
Text,
View
} from 'react-native'
import { useDispatch } from 'react-redux'
import { useMutation, useQueryCache } from 'react-query'
import { Feather } from '@expo/vector-icons'
import action from './action'
import client from 'src/api/client'
import Success from './Responses/Success'
const fireMutation = async ({
id,
type,
stateKey,
prevState
}: {
id: string
type: 'favourite' | 'reblog' | 'bookmark' | 'mute' | 'pin'
stateKey: 'favourited' | 'reblogged' | 'bookmarked' | 'muted' | 'pinned'
prevState: boolean
}) => {
let res = await client({
method: 'post',
instance: 'local',
endpoint: `statuses/${id}/${prevState ? 'un' : ''}${type}`
})
res = await client({
method: 'post',
instance: 'local',
endpoint: `statuses/${id}/${prevState ? 'un' : ''}${type}`
})
if (!res.body[stateKey] === prevState) {
return Promise.resolve(res.body)
} else {
const alert = {
title: 'This is a title',
message: 'This is a message'
}
Alert.alert(alert.title, alert.message, [
{ text: 'OK', onPress: () => console.log('OK Pressed') }
])
return Promise.reject()
}
}
export interface Props {
queryKey: store.QueryKey
id: string
url: string
replies_count: number
@ -22,10 +61,11 @@ export interface Props {
reblogged?: boolean
favourites_count: number
favourited?: boolean
bookmarked: boolean
bookmarked?: boolean
}
const Actions: React.FC<Props> = ({
queryKey,
id,
url,
replies_count,
@ -35,7 +75,6 @@ const Actions: React.FC<Props> = ({
favourited,
bookmarked
}) => {
const dispatch = useDispatch()
const [modalVisible, setModalVisible] = useState(false)
const [successMessage, setSuccessMessage] = useState()
@ -46,10 +85,42 @@ const Actions: React.FC<Props> = ({
return () => {}
}, [successMessage])
const queryCache = useQueryCache()
const [mutateAction] = useMutation(fireMutation, {
onMutate: () => {
queryCache.cancelQueries(queryKey)
const prevData = queryCache.getQueryData(queryKey)
return prevData
},
onSuccess: (newData, params) => {
if (params.type === 'reblog') {
queryCache.invalidateQueries(['Following', { page: 'Following' }])
}
// queryCache.setQueryData(queryKey, (oldData: any) => {
// oldData &&
// oldData.map((paging: any) => {
// paging.toots.map(
// (status: mastodon.Status | mastodon.Notification, i: number) => {
// if (status.id === newData.id) {
// paging.toots[i] = newData
// }
// }
// )
// })
// return oldData
// })
return Promise.resolve()
},
onError: (err, variables, prevData) => {
queryCache.setQueryData(queryKey, prevData)
},
onSettled: () => {
queryCache.invalidateQueries(queryKey)
}
})
return (
<>
<Success message={successMessage} />
<View style={styles.actions}>
<Pressable style={styles.action}>
<Feather name='message-circle' color='gray' />
@ -59,12 +130,11 @@ const Actions: React.FC<Props> = ({
<Pressable
style={styles.action}
onPress={() =>
action({
dispatch,
mutateAction({
id,
type: 'reblog',
stateKey: 'reblogged',
statePrev: reblogged || false
prevState: reblogged || false
})
}
>
@ -74,12 +144,11 @@ const Actions: React.FC<Props> = ({
<Pressable
style={styles.action}
onPress={() =>
action({
dispatch,
mutateAction({
id,
type: 'favourite',
stateKey: 'favourited',
statePrev: favourited || false
prevState: favourited || false
})
}
>
@ -89,12 +158,11 @@ const Actions: React.FC<Props> = ({
<Pressable
style={styles.action}
onPress={() =>
action({
dispatch,
mutateAction({
id,
type: 'bookmark',
stateKey: 'bookmarked',
statePrev: bookmarked
prevState: bookmarked || false
})
}
>

View File

@ -1,46 +0,0 @@
import { Dispatch } from '@reduxjs/toolkit'
import { Alert } from 'react-native'
import client from 'src/api/client'
// import { updateStatus } from 'src/stacks/common/timelineSlice'
const action = async ({
dispatch,
id,
type,
stateKey,
statePrev
}: {
dispatch: Dispatch
id: string
type: 'favourite' | 'reblog' | 'bookmark' | 'mute' | 'pin'
stateKey: 'favourited' | 'reblogged' | 'bookmarked' | 'muted' | 'pinned'
statePrev: boolean
}): Promise<void> => {
const alert = {
title: 'This is a title',
message: 'This is a message'
}
// ISSUE: https://github.com/tootsuite/mastodon/issues/3166
let res = await client({
method: 'post',
instance: 'local',
endpoint: `statuses/${id}/${statePrev ? 'un' : ''}${type}`
})
res = await client({
method: 'post',
instance: 'local',
endpoint: `statuses/${id}/${statePrev ? 'un' : ''}${type}`
})
if (!res.body[stateKey] === statePrev) {
// dispatch(updateStatus(res.body))
} else {
Alert.alert(alert.title, alert.message, [
{ text: 'OK', onPress: () => console.log('OK Pressed') }
])
}
}
export default action

View File

@ -13,9 +13,10 @@ import Actions from './Status/Actions'
export interface Props {
status: mastodon.Status
queryKey: store.QueryKey
}
const StatusInTimeline: React.FC<Props> = ({ status }) => {
const StatusInTimeline: React.FC<Props> = ({ status, queryKey }) => {
const navigation = useNavigation()
let actualContent = status.reblog ? status.reblog : status
@ -75,6 +76,7 @@ const StatusInTimeline: React.FC<Props> = ({ status }) => {
{actualContent.card && <Card card={actualContent.card} />}
</Pressable>
<Actions
queryKey={queryKey}
id={actualContent.id}
url={actualContent.url}
replies_count={actualContent.replies_count}

View File

@ -28,6 +28,7 @@ const Header = ({
size: { width: number; height: number }
}) => {
if (uri) {
const heightRatio = size ? size.height / size.width : 1 / 2
return (
<Image
source={{ uri: uri }}
@ -36,7 +37,7 @@ const Header = ({
{
height:
Dimensions.get('window').width *
(size ? size.height / size.width : 1 / 2)
(heightRatio > 0.5 ? 1 / 2 : heightRatio)
}
]}
/>
@ -144,6 +145,7 @@ const Toots = ({ account }: { account: string }) => {
page={item}
account={account}
disableRefresh
scrollEnabled={false}
/>
</View>
)
@ -156,17 +158,6 @@ const Toots = ({ account }: { account: string }) => {
index
})}
horizontal
ListHeaderComponent={
<View
style={{
width: Dimensions.get('window').width,
height: 100,
position: 'absolute'
}}
>
<Text>Test</Text>
</View>
}
onMomentumScrollEnd={() => {
setSegmentManuallyTriggered(false)
}}
@ -204,7 +195,6 @@ const Account: React.FC<Props> = ({
)
// const stateRelationships = useSelector(relationshipsState)
const [loaded, setLoaded] = useState(false)
interface isHeaderImageSize {
width: number
height: number
@ -214,19 +204,18 @@ const Account: React.FC<Props> = ({
>(undefined)
useEffect(() => {
if (data.header) {
if (isSuccess && data.header) {
Image.getSize(data.header, (width, height) => {
setHeaderImageSize({ width, height })
setLoaded(true)
})
} else {
setLoaded(true)
setHeaderImageSize({ width: 3, height: 1 })
}
}, [data])
}, [data, isSuccess])
// add emoji support
return isSuccess ? (
<View>
return isSuccess && headerImageSize ? (
<ScrollView>
{headerImageSize && (
<Header
uri={data.header}
@ -238,7 +227,7 @@ const Account: React.FC<Props> = ({
)}
<Information account={data} emojis={data.emojis} />
<Toots account={id} />
</View>
</ScrollView>
) : (
<></>
)

View File

@ -4,17 +4,19 @@ import { setFocusHandler, useInfiniteQuery } from 'react-query'
import StatusInNotifications from 'src/components/StatusInNotifications'
import StatusInTimeline from 'src/components/StatusInTimeline'
import store from './store'
import { timelineFetch } from './timelineFetch'
// Opening nesting hashtag pages
export interface Props {
page: store.TimelinePage
page: store.Pages
hashtag?: string
list?: string
toot?: string
account?: string
disableRefresh?: boolean
scrollEnabled?: boolean
}
const Timeline: React.FC<Props> = ({
@ -23,7 +25,8 @@ const Timeline: React.FC<Props> = ({
list,
toot,
account,
disableRefresh = false
disableRefresh = false,
scrollEnabled = true
}) => {
setFocusHandler(handleFocus => {
const handleAppStateChange = (appState: string) => {
@ -35,6 +38,10 @@ const Timeline: React.FC<Props> = ({
return () => AppState.removeEventListener('change', handleAppStateChange)
})
const queryKey: store.QueryKey = [
page,
{ page, hashtag, list, toot, account }
]
const {
isLoading,
isFetchingMore,
@ -42,10 +49,7 @@ const Timeline: React.FC<Props> = ({
isSuccess,
data,
fetchMore
} = useInfiniteQuery(
[page, { page, hashtag, list, toot, account }],
timelineFetch
)
} = useInfiniteQuery(queryKey, timelineFetch)
const flattenData = data ? data.flatMap(d => [...d?.toots]) : []
let content
@ -58,13 +62,14 @@ const Timeline: React.FC<Props> = ({
<>
<FlatList
style={{ minHeight: '100%' }}
scrollEnabled={scrollEnabled} // For timeline in Account view
data={flattenData}
keyExtractor={({ id }) => id}
renderItem={({ item, index, separators }) =>
page === 'Notifications' ? (
<StatusInNotifications key={index} status={item} />
) : (
<StatusInTimeline key={index} status={item} />
<StatusInTimeline key={index} status={item} queryKey={queryKey} />
)
}
// {...(state.pointer && { initialScrollIndex: state.pointer })}

View File

@ -1,3 +1,5 @@
import { uniqBy } from 'lodash'
import client from 'src/api/client'
export const timelineFetch = async (
@ -93,7 +95,7 @@ export const timelineFetch = async (
pinned: 'true'
}
})
const toots = res.body
let toots: mastodon.Status[] = res.body
res = await client({
method: 'get',
instance: 'local',
@ -102,7 +104,7 @@ export const timelineFetch = async (
exclude_replies: 'true'
}
})
toots.push(...res.body)
toots = uniqBy([...toots, ...res.body], 'id')
return Promise.resolve({ toots: toots })
case 'Account_All':

View File

@ -1,5 +1,6 @@
{
"compilerOptions": {
"target": "ES6",
"allowSyntheticDefaultImports": true,
"jsx": "react-native",
"lib": ["dom", "esnext"],

View File

@ -1380,6 +1380,11 @@
"@types/istanbul-lib-coverage" "*"
"@types/istanbul-lib-report" "*"
"@types/lodash@^4.14.164":
version "4.14.164"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.164.tgz#52348bcf909ac7b4c1bcbeda5c23135176e5dfa0"
integrity sha512-fXCEmONnrtbYUc5014avwBeMdhHHO8YJCkOBflUL9EoJBSKZ1dei+VO74fA7JkTHZ1GvZack2TyIw5U+1lT8jg==
"@types/prop-types@*":
version "15.7.3"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"