mirror of
https://github.com/ecency/ecency-mobile.git
synced 2024-11-29 11:12:18 +03:00
Merge pull request #2720 from ecency/mb/reblog
add a reblog functionality into reblogScreen
This commit is contained in:
commit
06deb8ed0c
@ -35,15 +35,16 @@ export const PostCardActionsPanel = ({ content, handleCardInteraction }: Props)
|
||||
};
|
||||
|
||||
const _onReblogsPress = () => {
|
||||
if (content.reblogs > 0) {
|
||||
const { author, permlink } = content;
|
||||
|
||||
handleCardInteraction(PostCardActionIds.NAVIGATE, {
|
||||
name: ROUTES.SCREENS.REBLOGS,
|
||||
params: {
|
||||
author: content.author,
|
||||
permlink: content.permlink,
|
||||
author,
|
||||
permlink,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -71,8 +71,9 @@ const PostDisplayContainer = ({
|
||||
navigation.navigate({
|
||||
name: ROUTES.SCREENS.REBLOGS,
|
||||
params: {
|
||||
author: post.author,
|
||||
permlink: post.permlink,
|
||||
reblogs,
|
||||
author,
|
||||
permlink,
|
||||
},
|
||||
key: post.permlink + post.reblogs.length,
|
||||
} as never);
|
||||
|
@ -960,7 +960,9 @@
|
||||
"time": "TIME"
|
||||
},
|
||||
"reblog": {
|
||||
"title": "Reblog Info"
|
||||
"title": "Reblog Info",
|
||||
"reblog_post": "Reblog Post",
|
||||
"reblog_delete": "Undo Reblog"
|
||||
},
|
||||
"dsteem": {
|
||||
"date_error": {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import EStyleSheet from 'react-native-extended-stylesheet';
|
||||
import { FlipInEasyX } from 'react-native-reanimated';
|
||||
|
||||
export default EStyleSheet.create({
|
||||
containerHorizontal16: {
|
||||
@ -87,4 +88,11 @@ export default EStyleSheet.create({
|
||||
tabBarBottom: {
|
||||
paddingBottom: 60,
|
||||
},
|
||||
mainbutton: {
|
||||
width: '40%',
|
||||
justifyContent: 'center',
|
||||
position: 'relative',
|
||||
left: 180,
|
||||
bottom: 20,
|
||||
},
|
||||
});
|
||||
|
@ -1751,62 +1751,30 @@ const _postContent = async (
|
||||
|
||||
// Re-blog
|
||||
// TODO: remove pinCode
|
||||
export const reblog = (account, pinCode, author, permlink) =>
|
||||
_reblog(account, pinCode, author, permlink).then((resp) => {
|
||||
export const reblog = (account, pinCode, author, permlink, undo = false) =>
|
||||
_reblog(account, pinCode, author, permlink, undo).then((resp) => {
|
||||
return resp;
|
||||
});
|
||||
|
||||
const _reblog = async (account, pinCode, author, permlink) => {
|
||||
const pin = getDigitPinCode(pinCode);
|
||||
const key = getAnyPrivateKey(account.local, pin);
|
||||
|
||||
if (account.local.authType === AUTH_TYPE.STEEM_CONNECT) {
|
||||
const token = decryptKey(account.local.accessToken, pin);
|
||||
const api = new hsClient({
|
||||
accessToken: token,
|
||||
});
|
||||
const _reblog = async (account, pinCode, author, permlink, undo = false) => {
|
||||
|
||||
const follower = account.name;
|
||||
|
||||
return api.reblog(follower, author, permlink).then((resp) => resp.result);
|
||||
}
|
||||
|
||||
if (key) {
|
||||
const privateKey = PrivateKey.fromString(key);
|
||||
const follower = account.name;
|
||||
|
||||
const json = {
|
||||
id: 'follow',
|
||||
json: jsonStringify([
|
||||
const json = [
|
||||
'reblog',
|
||||
{
|
||||
account: follower,
|
||||
account: account.name,
|
||||
author,
|
||||
permlink,
|
||||
delete: undo ? 'delete' : undefined
|
||||
},
|
||||
]),
|
||||
required_auths: [],
|
||||
required_posting_auths: [follower],
|
||||
};
|
||||
];
|
||||
|
||||
const opArray = [['custom_json', json]];
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
sendHiveOperations(opArray, privateKey)
|
||||
.then((result) => {
|
||||
resolve(result);
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
return broadcastPostingJSON('follow', json, account, pinCode)
|
||||
|
||||
return Promise.reject(
|
||||
new Error('Check private key permission! Required private posting key or above.'),
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export const claimRewardBalance = (account, pinCode, rewardHive, rewardHbd, rewardVests) => {
|
||||
const pin = getDigitPinCode(pinCode);
|
||||
const key = getAnyPrivateKey(get(account, 'local'), pin);
|
||||
|
@ -1,5 +1,6 @@
|
||||
import * as postQueries from './postQueries';
|
||||
import * as wavesQueries from './wavesQueries';
|
||||
import * as pollQueries from './pollQueries';
|
||||
import * as reblogQueries from './reblogQueries';
|
||||
|
||||
export { postQueries, wavesQueries, pollQueries };
|
||||
export { postQueries, wavesQueries, pollQueries, reblogQueries };
|
||||
|
128
src/providers/queries/postQueries/reblogQueries.ts
Normal file
128
src/providers/queries/postQueries/reblogQueries.ts
Normal file
@ -0,0 +1,128 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import QUERIES from "../queryKeys";
|
||||
import { useAppSelector } from "../../../hooks";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { setRcOffer, toastNotification } from "../../../redux/actions/uiAction";
|
||||
import { useIntl } from "react-intl";
|
||||
import { getPostReblogs, reblog } from "../../hive/dhive";
|
||||
import { get } from 'lodash';
|
||||
import { PointActivityIds } from "../../ecency/ecency.types";
|
||||
import { useUserActivityMutation } from "../pointQueries";
|
||||
|
||||
|
||||
|
||||
/** hook used to return post poll */
|
||||
export const useGetReblogsQuery = (author: string, permlink: string) => {
|
||||
|
||||
|
||||
const query = useQuery<string[]>(
|
||||
[QUERIES.POST.GET_REBLOGS, author, permlink],
|
||||
async () => {
|
||||
if (!author || !permlink) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const reblogs = await getPostReblogs(author, permlink);
|
||||
if (!reblogs) {
|
||||
new Error('Reblog data unavailable');
|
||||
}
|
||||
|
||||
return reblogs
|
||||
} catch (err) {
|
||||
console.warn('Failed to get post', err);
|
||||
return []
|
||||
}
|
||||
},
|
||||
{
|
||||
initialData: [],
|
||||
cacheTime: 30 * 60 * 1000, // keeps cache for 30 minutes
|
||||
},
|
||||
);
|
||||
|
||||
return query;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
export function useReblogMutation(author: string, permlink: string) {
|
||||
// const { activeUser } = useMappedStore();
|
||||
const intl = useIntl();
|
||||
const dispatch = useDispatch();
|
||||
const queryClient = useQueryClient()
|
||||
const currentAccount = useAppSelector(state => state.account.currentAccount);
|
||||
const pinHash = useAppSelector(state => state.application.pin);
|
||||
|
||||
const userActivityMutation = useUserActivityMutation();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: [QUERIES.POST.REBLOG_POST],
|
||||
mutationFn: async ({ undo }:{undo:boolean}) => {
|
||||
|
||||
if (!author || !permlink || !currentAccount) {
|
||||
throw new Error("Not enough data to reblog post")
|
||||
}
|
||||
|
||||
const resp = await reblog(currentAccount, pinHash, author, permlink, undo)
|
||||
|
||||
// track user activity points ty=130
|
||||
userActivityMutation.mutate({
|
||||
pointsTy: PointActivityIds.REBLOG,
|
||||
transactionId: resp.id,
|
||||
|
||||
});
|
||||
|
||||
return resp;
|
||||
|
||||
},
|
||||
retry: 3,
|
||||
|
||||
onSuccess: (resp, vars) => {
|
||||
console.log("reblog response", resp);
|
||||
//update poll cache here
|
||||
queryClient.setQueryData<ReturnType<typeof useGetReblogsQuery>["data"]>(
|
||||
[QUERIES.POST.GET_REBLOGS, author, permlink],
|
||||
(data) => {
|
||||
if (!data || !resp) {
|
||||
return data;
|
||||
}
|
||||
|
||||
const _curIndex = data.indexOf(currentAccount.username)
|
||||
if(vars.undo){
|
||||
data.splice(_curIndex, 1)
|
||||
}
|
||||
else if (_curIndex < 0) {
|
||||
data.splice(0, 0, currentAccount.username);
|
||||
}
|
||||
|
||||
return [...data] as ReturnType<typeof useGetReblogsQuery>["data"];
|
||||
}
|
||||
)
|
||||
},
|
||||
onError: (error) => {
|
||||
if (String(get(error, 'jse_shortmsg', '')).indexOf('has already reblogged') > -1) {
|
||||
dispatch(
|
||||
toastNotification(
|
||||
intl.formatMessage({
|
||||
id: 'alert.already_rebloged',
|
||||
}),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
if (error && error.jse_shortmsg.split(': ')[1].includes('wait to transact')) {
|
||||
// when RC is not enough, offer boosting account
|
||||
dispatch(setRcOffer(true));
|
||||
} else {
|
||||
// when other errors
|
||||
dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' })));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,9 @@ const QUERIES = {
|
||||
GET: 'QUERY_GET_POST',
|
||||
GET_POLL: 'QUERY_GET_POLL',
|
||||
GET_DISCUSSION: 'QUERY_GET_DISCUSSION',
|
||||
SIGN_POLL_VOTE:"SIGN_POLL_VOTE"
|
||||
SIGN_POLL_VOTE:"SIGN_POLL_VOTE",
|
||||
GET_REBLOGS:"GET_REBLOGS",
|
||||
REBLOG_POST:"REBLOG_POST"
|
||||
},
|
||||
LEADERBOARD: {
|
||||
GET: 'QUERY_GET_LEADERBOARD',
|
||||
|
@ -1,74 +0,0 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { FlatList, SafeAreaView } from 'react-native';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
// Components
|
||||
import { gestureHandlerRootHOC } from 'react-native-gesture-handler';
|
||||
import { BasicHeader, UserListItem } from '../../../components';
|
||||
|
||||
// Container
|
||||
import AccountListContainer from '../../../containers/accountListContainer';
|
||||
|
||||
// Utils
|
||||
import globalStyles from '../../../globalStyles';
|
||||
import { getTimeFromNow } from '../../../utils/time';
|
||||
import { getPostReblogs } from '../../../providers/hive/dhive';
|
||||
|
||||
const renderUserListItem = (item, index, handleOnUserPress) => {
|
||||
return (
|
||||
<UserListItem
|
||||
index={index}
|
||||
username={item.account}
|
||||
description={getTimeFromNow(item.timestamp)}
|
||||
handleOnPress={() => handleOnUserPress(item.account)}
|
||||
isClickable
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ReblogScreen = ({ route }) => {
|
||||
const intl = useIntl();
|
||||
const headerTitle = intl.formatMessage({
|
||||
id: 'reblog.title',
|
||||
});
|
||||
|
||||
const [reblogs, setReblogs] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
_fetchReblogs();
|
||||
}, []);
|
||||
|
||||
const _fetchReblogs = async () => {
|
||||
const author = route.params?.author;
|
||||
const permlink = route.params?.permlink;
|
||||
|
||||
if (author && permlink) {
|
||||
const _reblogs = await getPostReblogs(author, permlink);
|
||||
|
||||
setReblogs(_reblogs.map((account) => ({ account })));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AccountListContainer data={reblogs}>
|
||||
{({ data, filterResult, handleSearch, handleOnUserPress }) => (
|
||||
<SafeAreaView style={[globalStyles.container, { paddingBottom: 40 }]}>
|
||||
<BasicHeader
|
||||
title={`${headerTitle} (${data && data.length})`}
|
||||
backIconName="close"
|
||||
isHasSearch
|
||||
handleOnSearch={(text) => handleSearch(text, 'account')}
|
||||
/>
|
||||
<FlatList
|
||||
data={filterResult || data}
|
||||
keyExtractor={(item) => item.account}
|
||||
removeClippedSubviews={false}
|
||||
renderItem={({ item, index }) => renderUserListItem(item, index, handleOnUserPress)}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
)}
|
||||
</AccountListContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default gestureHandlerRootHOC(ReblogScreen);
|
148
src/screens/reblogs/screen/reblogScreen.tsx
Normal file
148
src/screens/reblogs/screen/reblogScreen.tsx
Normal file
@ -0,0 +1,148 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { FlatList, RefreshControl, SafeAreaView } from 'react-native';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { gestureHandlerRootHOC } from 'react-native-gesture-handler';
|
||||
import { useAppSelector } from '../../../hooks';
|
||||
import showLoginAlert from '../../../utils/showLoginAlert';
|
||||
|
||||
// Components
|
||||
import { BasicHeader, MainButton, UserListItem } from '../../../components';
|
||||
|
||||
// Container
|
||||
import AccountListContainer from '../../../containers/accountListContainer';
|
||||
|
||||
// Utils
|
||||
import globalStyles from '../../../globalStyles';
|
||||
import styles from '../styles/reblogScreen.styles';
|
||||
import { getTimeFromNow } from '../../../utils/time';
|
||||
import Animated, { BounceInRight } from 'react-native-reanimated';
|
||||
import { reblogQueries } from '../../../providers/queries';
|
||||
;
|
||||
|
||||
const renderUserListItem = (item, index, handleOnUserPress) => {
|
||||
return (
|
||||
<UserListItem
|
||||
index={index}
|
||||
username={item.account}
|
||||
description={getTimeFromNow(item.timestamp)}
|
||||
handleOnPress={() => handleOnUserPress(item.account)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ReblogScreen = ({ route }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const author = route.params?.author;
|
||||
const permlink = route.params?.permlink;
|
||||
|
||||
const currentAccount = useAppSelector((state) => state.account.currentAccount);
|
||||
const isLoggedIn = useAppSelector((state) => state.application.isLoggedIn);
|
||||
const isDarkTheme = useAppSelector((state) => state.application.isDarkTheme);
|
||||
|
||||
|
||||
const [isReblogging, setIsReblogging] = useState(false);
|
||||
|
||||
|
||||
const reblogsQuery = reblogQueries.useGetReblogsQuery(author, permlink);
|
||||
const reblogMutation = reblogQueries.useReblogMutation(author, permlink);
|
||||
|
||||
|
||||
//map reblogs data for account list
|
||||
const { reblogs, deleteEnabled } = useMemo(() => {
|
||||
let _reblogs: any[] = [];
|
||||
let _deleteEnabled = false;
|
||||
if (reblogsQuery.data instanceof Array) {
|
||||
_reblogs = reblogsQuery.data.map((username) => ({ account: username }));
|
||||
_deleteEnabled = currentAccount ? reblogsQuery.data.includes(currentAccount.username) : false;
|
||||
}
|
||||
return {
|
||||
reblogs: _reblogs,
|
||||
deleteEnabled: _deleteEnabled
|
||||
}
|
||||
}, [reblogsQuery.data?.length])
|
||||
|
||||
|
||||
|
||||
const headerTitle = intl.formatMessage({
|
||||
id: 'reblog.title',
|
||||
});
|
||||
|
||||
const _actionBtnTitle = intl.formatMessage({ id: deleteEnabled ? 'reblog.reblog_delete' : 'reblog.reblog_post' })
|
||||
const _actionBtnIcon = deleteEnabled ? "repeat-off" : "repeat";
|
||||
|
||||
const _handleReblogPost = async () => {
|
||||
if (!isLoggedIn) {
|
||||
showLoginAlert({ intl });
|
||||
return;
|
||||
}
|
||||
|
||||
if (isLoggedIn) {
|
||||
setIsReblogging(true);
|
||||
await reblogMutation.mutateAsync({ undo:deleteEnabled });
|
||||
setIsReblogging(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
const _renderFloatingButton = () => {
|
||||
|
||||
return (
|
||||
<Animated.View style={styles.floatingContainer} entering={BounceInRight.delay(300)}>
|
||||
<MainButton
|
||||
onPress={_handleReblogPost}
|
||||
iconName={_actionBtnIcon}
|
||||
iconType="MaterialCommunityIcons"
|
||||
iconColor="white"
|
||||
text={_actionBtnTitle}
|
||||
isLoading={isReblogging}
|
||||
/>
|
||||
</Animated.View>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<AccountListContainer data={reblogs}>
|
||||
{({ data, filterResult, handleSearch, handleOnUserPress }) => (
|
||||
|
||||
<SafeAreaView style={[globalStyles.container, { paddingBottom: 40 }]}>
|
||||
|
||||
{/* Your content goes here */}
|
||||
<BasicHeader
|
||||
title={`${headerTitle} (${data && data.length})`}
|
||||
backIconName="close"
|
||||
isHasSearch
|
||||
handleOnSearch={(text) => handleSearch(text, 'account')}
|
||||
/>
|
||||
<FlatList
|
||||
data={filterResult || data}
|
||||
keyExtractor={(item) => item.account}
|
||||
removeClippedSubviews={false}
|
||||
renderItem={({ item, index }) =>
|
||||
renderUserListItem(item, index, handleOnUserPress)
|
||||
}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={reblogsQuery.isLoading || reblogsQuery.isFetching}
|
||||
onRefresh={() => reblogsQuery.refetch()}
|
||||
progressBackgroundColor="#357CE6"
|
||||
tintColor={!isDarkTheme ? '#357ce6' : '#96c0ff'}
|
||||
titleColor="#fff"
|
||||
colors={['#fff']}
|
||||
/>}
|
||||
/>
|
||||
|
||||
{_renderFloatingButton()}
|
||||
</SafeAreaView>
|
||||
|
||||
)
|
||||
}
|
||||
</AccountListContainer >
|
||||
);
|
||||
};
|
||||
|
||||
export default gestureHandlerRootHOC(ReblogScreen);
|
9
src/screens/reblogs/styles/reblogScreen.styles.ts
Normal file
9
src/screens/reblogs/styles/reblogScreen.styles.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import EStyleSheet from 'react-native-extended-stylesheet';
|
||||
|
||||
export default EStyleSheet.create({
|
||||
floatingContainer: {
|
||||
position: 'absolute',
|
||||
right: 16,
|
||||
bottom: 56,
|
||||
}
|
||||
})
|
Loading…
Reference in New Issue
Block a user