Merge pull request #2328 from ecency/nt/reply-modal-tweaks

Nt/reply modal tweaks
This commit is contained in:
Feruz M 2022-05-31 09:51:15 +03:00 committed by GitHub
commit d73a79bb3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 313 additions and 272 deletions

View File

@ -833,4 +833,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 0282022703ad578ab2d9afbf3147ba3b373b4311 PODFILE CHECKSUM: 0282022703ad578ab2d9afbf3147ba3b373b4311
COCOAPODS: 1.11.3 COCOAPODS: 1.11.2

View File

@ -181,7 +181,7 @@ const MarkdownEditorView = ({
}, [draftBody]); }, [draftBody]);
useEffect(() => { useEffect(() => {
if (autoFocusText && inputRef && inputRef.current && draftBtnTooltipRegistered) { if (isReply || (autoFocusText && inputRef && inputRef.current && draftBtnTooltipRegistered)) {
// added delay to open keyboard, solves the issue of keyboard not opening // added delay to open keyboard, solves the issue of keyboard not opening
setTimeout(() => { setTimeout(() => {
inputRef.current.focus(); inputRef.current.focus();

View File

@ -0,0 +1,259 @@
import React, { useEffect, useState } from 'react';
import EStyleSheet from 'react-native-extended-stylesheet';
import styles from './quickReplyModalStyles';
import { View, Text, Alert, TouchableOpacity, Keyboard, Platform } from 'react-native';
import { useIntl } from 'react-intl';
import { IconButton, MainButton, TextButton, TextInput, UserAvatar } from '..';
import { useSelector, useDispatch } from 'react-redux';
import { delay, generateReplyPermlink } from '../../utils/editor';
import { postComment } from '../../providers/hive/dhive';
import { toastNotification } from '../../redux/actions/uiAction';
import { updateCommentCache } from '../../redux/actions/cacheActions';
import { default as ROUTES } from '../../constants/routeNames';
import get from 'lodash/get';
import { navigate } from '../../navigation/service';
import { postBodySummary } from '@ecency/render-helper';
export interface QuickReplyModalContentProps {
fetchPost?: any;
selectedPost?: any;
inputRef?: any;
sheetModalRef?: any;
}
export const QuickReplyModalContent = ({
fetchPost,
selectedPost,
inputRef,
sheetModalRef,
}: QuickReplyModalContentProps) => {
const intl = useIntl();
const dispatch = useDispatch();
const currentAccount = useSelector((state) => state.account.currentAccount);
const pinCode = useSelector((state) => state.application.pin);
const [commentValue, setCommentValue] = useState('');
const [isSending, setIsSending] = useState(false);
const headerText =
selectedPost && (selectedPost.summary || postBodySummary(selectedPost, 150, Platform.OS));
// reset the state when post changes
useEffect(() => {
setCommentValue('');
}, [selectedPost]);
// handlers
// handle close press
const _handleClosePress = () => {
sheetModalRef.current?.setModalVisible(false);
};
// navigate to post on summary press
const _handleOnSummaryPress = () => {
Keyboard.dismiss();
sheetModalRef.current?.setModalVisible(false);
navigate({
routeName: ROUTES.SCREENS.POST,
params: {
content: selectedPost,
},
key: get(selectedPost, 'permlink'),
});
};
// handle submit reply
const _submitReply = async () => {
let stateTimer;
if (!commentValue) {
return;
}
if (isSending) {
return;
}
if (currentAccount) {
setIsSending(true);
const permlink = generateReplyPermlink(selectedPost.author);
const parentAuthor = selectedPost.author;
const parentPermlink = selectedPost.permlink;
const parentTags = selectedPost.json_metadata.tags;
console.log(
currentAccount,
pinCode,
parentAuthor,
parentPermlink,
permlink,
commentValue,
parentTags,
);
const status = await postComment(
currentAccount,
pinCode,
parentAuthor,
parentPermlink,
permlink,
commentValue,
parentTags,
)
.then(() => {
stateTimer = setTimeout(() => {
setIsSending(false);
sheetModalRef.current?.setModalVisible(false);
setCommentValue('');
dispatch(
toastNotification(
intl.formatMessage({
id: 'alert.success',
}),
),
);
//add comment cache entry
dispatch(
updateCommentCache(
`${parentAuthor}/${parentPermlink}`,
{
author: currentAccount.name,
permlink,
parent_author: parentAuthor,
parent_permlink: parentPermlink,
markdownBody: commentValue,
},
{
parentTags: parentTags || ['ecency'],
},
),
);
clearTimeout(stateTimer);
}, 3000);
})
.catch((error) => {
console.log(error);
Alert.alert(
intl.formatMessage({
id: 'alert.fail',
}),
error.message || JSON.stringify(error),
);
stateTimer = setTimeout(() => {
setIsSending(false);
clearTimeout(stateTimer);
}, 500);
});
console.log('status : ', status);
}
};
const _handleExpandBtn = async () => {
if (selectedPost) {
Keyboard.dismiss();
sheetModalRef.current?.setModalVisible(false);
await delay(50);
navigate({
routeName: ROUTES.SCREENS.EDITOR,
key: 'editor_replay',
params: {
isReply: true,
post: selectedPost,
quickReplyText: commentValue,
fetchPost,
},
});
}
};
//VIEW_RENDERERS
const _renderSheetHeader = () => (
<View style={styles.modalHeader}>
<IconButton
name="close"
iconType="MaterialCommunityIcons"
size={28}
color={EStyleSheet.value('$primaryBlack')}
iconStyle={{}}
onPress={() => _handleClosePress()}
/>
</View>
);
const _renderSummary = () => (
<TouchableOpacity onPress={() => _handleOnSummaryPress()}>
<Text numberOfLines={2} style={styles.summaryStyle}>
{headerText}
</Text>
</TouchableOpacity>
);
const _renderAvatar = () => (
<View style={styles.avatarAndNameContainer}>
<UserAvatar noAction username={currentAccount.username} />
<View style={styles.nameContainer}>
<Text style={styles.name}>{`@${currentAccount.username}`}</Text>
</View>
</View>
);
const _renderExpandBtn = () => (
<View style={styles.expandBtnContainer}>
<IconButton
iconStyle={styles.backIcon}
iconType="MaterialCommunityIcons"
name="arrow-expand"
onPress={_handleExpandBtn}
size={28}
color={EStyleSheet.value('$primaryBlack')}
/>
</View>
);
const _renderReplyBtn = () => (
<View style={styles.replyBtnContainer}>
<TextButton
style={styles.cancelButton}
onPress={_handleClosePress}
text={intl.formatMessage({
id: 'quick_reply.close',
})}
/>
<MainButton
style={styles.commentBtn}
onPress={() => _submitReply()}
text={intl.formatMessage({
id: 'quick_reply.reply',
})}
isLoading={isSending}
/>
</View>
);
return (
<View style={styles.modalContainer}>
{_renderSummary()}
{_renderAvatar()}
<View style={styles.inputContainer}>
<TextInput
innerRef={inputRef}
onChangeText={setCommentValue}
value={commentValue}
// autoFocus
placeholder={intl.formatMessage({
id: 'quick_reply.placeholder',
})}
placeholderTextColor="#c1c5c7"
style={styles.textInput}
multiline={true}
numberOfLines={5}
textAlignVertical="top"
/>
</View>
<View style={styles.footer}>
{_renderExpandBtn()}
{_renderReplyBtn()}
</View>
</View>
);
};

View File

@ -1,4 +1,6 @@
import { Platform } from 'react-native';
import EStyleSheet from 'react-native-extended-stylesheet'; import EStyleSheet from 'react-native-extended-stylesheet';
import { getBottomSpace, isIphoneX } from 'react-native-iphone-x-helper';
export default EStyleSheet.create({ export default EStyleSheet.create({
sheetContent: { sheetContent: {
@ -12,10 +14,14 @@ export default EStyleSheet.create({
container: { container: {
flex: 1, flex: 1,
}, },
modalContainer: {
// paddingVertical: 4,
},
modalContainer: {
paddingVertical: 4,
paddingBottom: Platform.select({
ios:isIphoneX() ? getBottomSpace() - 20 : 12,
android: 20
}) ,
},
cancelButton: { cancelButton: {
marginRight: 10, marginRight: 10,
@ -30,13 +36,15 @@ export default EStyleSheet.create({
color: '$primaryBlack', color: '$primaryBlack',
}, },
summaryStyle: { summaryStyle: {
fontSize:12, fontSize: 16,
paddingHorizontal: 16,
color: '$primaryDarkGray',
fontWeight: '500',
}, },
inputContainer: { inputContainer: {
paddingHorizontal: 16, paddingHorizontal: 16,
paddingVertical: 8, paddingVertical: 8,
height: 120, height: 120,
}, },
textInput: { textInput: {
color: '$primaryBlack', color: '$primaryBlack',

View File

@ -1,46 +1,21 @@
import React, { useEffect, useImperativeHandle, useRef, useState } from 'react'; import React, { useImperativeHandle, useRef, useState } from 'react';
import ActionSheet from 'react-native-actions-sheet'; import ActionSheet from 'react-native-actions-sheet';
import EStyleSheet from 'react-native-extended-stylesheet'; import EStyleSheet from 'react-native-extended-stylesheet';
import styles from './quickReplyModalStyles';
import { forwardRef } from 'react'; import { forwardRef } from 'react';
import { View, Text, Alert, TouchableOpacity, Keyboard, Platform } from 'react-native'; import { Portal } from 'react-native-portalize';
import { useIntl } from 'react-intl'; import { QuickReplyModalContent } from './quickReplyModalContent';
import { IconButton, MainButton, SummaryArea, TextButton, TextInput, UserAvatar } from '..'; import styles from './quickReplyModalStyles';
import { useSelector, useDispatch } from 'react-redux';
import { generateReplyPermlink } from '../../utils/editor';
import { postComment } from '../../providers/hive/dhive';
import { toastNotification } from '../../redux/actions/uiAction';
import { updateCommentCache } from '../../redux/actions/cacheActions';
import { default as ROUTES } from '../../constants/routeNames';
import get from 'lodash/get';
import { navigate } from '../../navigation/service';
import { postBodySummary } from '@ecency/render-helper';
export interface QuickReplyModalProps { export interface QuickReplyModalProps {
fetchPost?: any; fetchPost?: any;
} }
const QuickReplyModal = ({ fetchPost }: QuickReplyModalProps, ref) => { const QuickReplyModal = ({ fetchPost }: QuickReplyModalProps, ref) => {
const intl = useIntl();
const dispatch = useDispatch();
const currentAccount = useSelector((state) => state.account.currentAccount);
const pinCode = useSelector((state) => state.application.pin);
const [selectedPost, setSelectedPost] = useState(null); const [selectedPost, setSelectedPost] = useState(null);
const [commentValue, setCommentValue] = useState('');
const [isSending, setIsSending] = useState(false);
const sheetModalRef = useRef<ActionSheet>(); const sheetModalRef = useRef<ActionSheet>();
const inputRef = useRef<TextInput>(null); const inputRef = useRef<TextInput>(null);
const headerText = //CALLBACK_METHOD
selectedPost && (selectedPost.summary || postBodySummary(selectedPost, 150, Platform.OS));
// reset the state when post changes
useEffect(() => {
setCommentValue('');
}, [selectedPost]);
//CALLBACK_METHODS
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
show: (post: any) => { show: (post: any) => {
setSelectedPost(post); setSelectedPost(post);
@ -48,222 +23,12 @@ const QuickReplyModal = ({ fetchPost }: QuickReplyModalProps, ref) => {
// wait for modal to open and then show the keyboard // wait for modal to open and then show the keyboard
setTimeout(() => { setTimeout(() => {
inputRef.current?.focus(); inputRef.current?.focus();
}, 1000); }, 500);
}, },
})); }));
// handlers
// handle close press
const _handleClosePress = () => {
sheetModalRef.current?.setModalVisible(false);
};
// navigate to post on summary press
const _handleOnSummaryPress = () => {
Keyboard.dismiss();
sheetModalRef.current?.setModalVisible(false);
navigate({
routeName: ROUTES.SCREENS.POST,
params: {
content: selectedPost,
},
key: get(selectedPost, 'permlink'),
});
};
// handle submit reply
const _submitReply = async () => {
let stateTimer;
if (!commentValue) {
return;
}
if (isSending) {
return;
}
if (currentAccount) {
setIsSending(true);
const permlink = generateReplyPermlink(selectedPost.author);
const parentAuthor = selectedPost.author;
const parentPermlink = selectedPost.permlink;
const parentTags = selectedPost.json_metadata.tags;
console.log(
currentAccount,
pinCode,
parentAuthor,
parentPermlink,
permlink,
commentValue,
parentTags,
);
const status = await postComment(
currentAccount,
pinCode,
parentAuthor,
parentPermlink,
permlink,
commentValue,
parentTags,
)
.then(() => {
stateTimer = setTimeout(() => {
setIsSending(false);
sheetModalRef.current?.setModalVisible(false);
setCommentValue('');
dispatch(
toastNotification(
intl.formatMessage({
id: 'alert.success',
}),
),
);
//add comment cache entry
dispatch(
updateCommentCache(
`${parentAuthor}/${parentPermlink}`,
{
author: currentAccount.name,
permlink,
parent_author: parentAuthor,
parent_permlink: parentPermlink,
markdownBody: commentValue,
},
{
parentTags: parentTags || ['ecency'],
},
),
);
clearTimeout(stateTimer);
}, 3000);
})
.catch((error) => {
console.log(error);
Alert.alert(
intl.formatMessage({
id: 'alert.fail',
}),
error.message || JSON.stringify(error),
);
stateTimer = setTimeout(() => {
setIsSending(false);
clearTimeout(stateTimer);
}, 500);
});
console.log('status : ', status);
}
};
const _handleExpandBtn = () => {
if (selectedPost) {
navigate({
routeName: ROUTES.SCREENS.EDITOR,
key: 'editor_replay',
params: {
isReply: true,
post: selectedPost,
quickReplyText: commentValue,
fetchPost,
},
});
sheetModalRef.current?.setModalVisible(false);
}
};
//VIEW_RENDERERS
const _renderSheetHeader = () => (
<View style={styles.modalHeader}>
<IconButton
name="close"
iconType="MaterialCommunityIcons"
size={28}
color={EStyleSheet.value('$primaryBlack')}
iconStyle={{}}
onPress={() => _handleClosePress()}
/>
</View>
);
const _renderSummary = () => (
<TouchableOpacity onPress={() => _handleOnSummaryPress()}>
<SummaryArea style={styles.summaryStyle} summary={headerText} />
</TouchableOpacity>
);
const _renderAvatar = () => (
<View style={styles.avatarAndNameContainer}>
<UserAvatar noAction username={currentAccount.username} />
<View style={styles.nameContainer}>
<Text style={styles.name}>{`@${currentAccount.username}`}</Text>
</View>
</View>
);
const _renderExpandBtn = () => (
<View style={styles.expandBtnContainer}>
<IconButton
iconStyle={styles.backIcon}
iconType="MaterialCommunityIcons"
name="arrow-expand"
onPress={_handleExpandBtn}
size={28}
color={EStyleSheet.value('$primaryBlack')}
/>
</View>
);
const _renderReplyBtn = () => (
<View style={styles.replyBtnContainer}>
<TextButton
style={styles.cancelButton}
onPress={_handleClosePress}
text={intl.formatMessage({
id: 'quick_reply.close',
})}
/>
<MainButton
style={styles.commentBtn}
onPress={() => _submitReply()}
text={intl.formatMessage({
id: 'quick_reply.reply',
})}
isLoading={isSending}
/>
</View>
);
const _renderContent = () => {
return (
<View style={styles.modalContainer}>
{_renderSummary()}
{_renderAvatar()}
<View style={styles.inputContainer}>
<TextInput
innerRef={inputRef}
onChangeText={setCommentValue}
value={commentValue}
// autoFocus
placeholder={intl.formatMessage({
id: 'quick_reply.placeholder',
})}
placeholderTextColor="#c1c5c7"
style={styles.textInput}
multiline={true}
numberOfLines={5}
textAlignVertical="top"
/>
</View>
<View style={styles.footer}>
{_renderExpandBtn()}
{_renderReplyBtn()}
</View>
</View>
);
};
return ( return (
<Portal>
<ActionSheet <ActionSheet
ref={sheetModalRef} ref={sheetModalRef}
gestureEnabled={true} gestureEnabled={true}
@ -272,8 +37,14 @@ const QuickReplyModal = ({ fetchPost }: QuickReplyModalProps, ref) => {
keyboardHandlerEnabled keyboardHandlerEnabled
indicatorColor={EStyleSheet.value('$primaryWhiteLightBackground')} indicatorColor={EStyleSheet.value('$primaryWhiteLightBackground')}
> >
{selectedPost && _renderContent()} <QuickReplyModalContent
fetchPost={fetchPost}
selectedPost={selectedPost}
inputRef={inputRef}
sheetModalRef={sheetModalRef}
/>
</ActionSheet> </ActionSheet>
</Portal>
); );
}; };

View File

@ -134,7 +134,8 @@ class EditorContainer extends Component {
({ isReply, quickReplyText } = navigationParams); ({ isReply, quickReplyText } = navigationParams);
this.setState({ this.setState({
isReply, isReply,
quickReplyText quickReplyText,
autoFocusText: true,
}); });
} }
@ -923,6 +924,8 @@ class EditorContainer extends Component {
this.stateTimer = setTimeout(() => { this.stateTimer = setTimeout(() => {
if (navigation) { if (navigation) {
navigation.goBack(); navigation.goBack();
}
if(navigation && navigation.state && navigation.state.params && navigation.state.params.fetchPost ){
navigation.state.params.fetchPost(); navigation.state.params.fetchPost();
} }
this.setState({ this.setState({