diff --git a/src/components/dropdownButton/view/dropdownButtonView.tsx b/src/components/dropdownButton/view/dropdownButtonView.tsx index 4b84e0a1a..e575f0636 100644 --- a/src/components/dropdownButton/view/dropdownButtonView.tsx +++ b/src/components/dropdownButton/view/dropdownButtonView.tsx @@ -83,7 +83,7 @@ const DropdownButtonView = ({ defaultIndex={selectedOptionIndex} defaultValue={defaultText} renderSeparator={() => null} - + showsVerticalScrollIndicator={false} renderRow={(rowData, rowID, highlighted) => renderDropdownRow( rowData, diff --git a/src/components/organisms/inputSupportModal/container/inputSupportModal.tsx b/src/components/organisms/inputSupportModal/container/inputSupportModal.tsx index 208c0bbdc..4f5237404 100644 --- a/src/components/organisms/inputSupportModal/container/inputSupportModal.tsx +++ b/src/components/organisms/inputSupportModal/container/inputSupportModal.tsx @@ -26,7 +26,7 @@ export const InputSupportModal = ({children, visible, onClose}: InputSupportModa { Platform.select({ ios: ( - + {children} ), diff --git a/src/components/postCard/container/postCardContainer.js b/src/components/postCard/container/postCardContainer.js index 7e9a954d5..cbce0c64f 100644 --- a/src/components/postCard/container/postCardContainer.js +++ b/src/components/postCard/container/postCardContainer.js @@ -30,15 +30,14 @@ const PostCardContainer = ({ setImageHeight, pageType, showQuickReplyModal, + mutes, }) => { const dispatch = useAppDispatch(); const [_content, setContent] = useState(content); const [reblogs, setReblogs] = useState([]); const activeVotes = get(_content, 'active_votes', []); - const [isMuted, setIsMuted] = useState( - currentAccount.mutes && currentAccount.mutes.indexOf(content.author) > -1, - ); + const [isMuted, setIsMuted] = useState(!!mutes && mutes.indexOf(content.author) > -1); useEffect(() => { let isCancelled = false; diff --git a/src/components/postCard/view/postCardView.js b/src/components/postCard/view/postCardView.js index ef5ab077d..93e12e6ba 100644 --- a/src/components/postCard/view/postCardView.js +++ b/src/components/postCard/view/postCardView.js @@ -123,7 +123,12 @@ const PostCardView = ({ )} - + diff --git a/src/components/postDropdown/container/postDropdownContainer.tsx b/src/components/postDropdown/container/postDropdownContainer.tsx index 1abe2f91b..3aa04a158 100644 --- a/src/components/postDropdown/container/postDropdownContainer.tsx +++ b/src/components/postDropdown/container/postDropdownContainer.tsx @@ -6,7 +6,7 @@ import { injectIntl } from 'react-intl'; import get from 'lodash/get'; // Services and Actions -import { pinCommunityPost, profileUpdate, reblog } from '../../../providers/hive/dhive'; +import { followUser, getRelationship, ignoreUser, pinCommunityPost, profileUpdate, reblog, unfollowUser } from '../../../providers/hive/dhive'; import { addBookmark, addReport } from '../../../providers/ecency/ecency'; import { toastNotification, setRcOffer, showActionModal } from '../../../redux/actions/uiAction'; import { openPinCodeModal } from '../../../redux/actions/applicationActions'; @@ -23,6 +23,7 @@ import { getPostUrl } from '../../../utils/post'; import PostDropdownView from '../view/postDropdownView'; import { OptionsModal } from '../../atoms'; import { updateCurrentAccount } from '../../../redux/actions/accountAction'; +import showLoginAlert from '../../../utils/showLoginAlert'; /* * Props Name Description Value @@ -67,7 +68,7 @@ class PostDropdownContainer extends PureComponent { } }; - _initOptions = ({ content, currentAccount, pageType, subscribedCommunities } = this.props) => { + _initOptions = ({ content, currentAccount, pageType, subscribedCommunities, isMuted } = this.props) => { //check if post is owned by current user or not, if so pinned or not const _canUpdateBlogPin = !!pageType && !!content && !!currentAccount && currentAccount.name === content.author const _isPinnedInProfile = !!content && content.stats?.is_pinned_blog; @@ -82,6 +83,7 @@ class PostDropdownContainer extends PureComponent { return role; }, false) : false; const _isPinnedInCommunity = !!content && content.stats?.is_pinned; + //cook options list based on collected flags const options = OPTIONS.filter((option) => { @@ -104,9 +106,10 @@ class PostDropdownContainer extends PureComponent { // Component Functions _handleOnDropdownSelect = async (index) => { - const { content, dispatch, intl, navigation, } = this.props; + const {currentAccount, content, dispatch, intl, navigation, isMuted } = this.props as any; + const username = content.author; + const isOwnProfile = !username || currentAccount.username === username; const { options } = this.state; - console.log('content : ', content); switch (options[index]) { case 'copy': @@ -177,13 +180,72 @@ class PostDropdownContainer extends PureComponent { }, }); break; + case 'mute': + !isOwnProfile && this._muteUser(); + break; default: break; } }; + _muteUser = () => { + const { currentAccount, pinCode, dispatch, intl, content, isLoggedIn, navigation } = this.props as any; + const username = content.author; + const follower = currentAccount.name; + const following = username; + + if(!isLoggedIn){ + showLoginAlert({navigation, intl}); + return; + } + ignoreUser(currentAccount, pinCode, { + follower, + following, + }) + .then(() => { + const curMutes = currentAccount.mutes || []; + if (curMutes.indexOf(username) < 0) { + //check to avoid double entry corner case + currentAccount.mutes = [username, ...curMutes]; + } + dispatch(updateCurrentAccount(currentAccount)); + dispatch( + toastNotification( + intl.formatMessage({ + id: 'alert.success_mute', + }), + ), + ); + }) + .catch((err) => { + this._profileActionDone({ error: err }); + }); + + }; + + _profileActionDone = ({ error = null }) => { + const { intl, dispatch, content } = this.props as any; + + this.setState({ + isProfileLoading: false, + }); + if (error) { + if (error.jse_shortmsg && error.jse_shortmsg.includes('wait to transact')) { + //when RC is not enough, offer boosting account + dispatch(setRcOffer(true)); + } else { + Alert.alert( + intl.formatMessage({ + id: 'alert.fail', + }), + error.message || error.toString(), + ); + } + } + }; + _share = () => { - const { content } = this.props; + const { content } = this.props as any; const postUrl = getPostUrl(get(content, 'url')); Share.share({ @@ -192,7 +254,7 @@ class PostDropdownContainer extends PureComponent { }; _report = (url) => { - const { dispatch, intl } = this.props; + const { dispatch, intl } = this.props as any; const _onConfirm = () => { addReport('content', url) @@ -236,8 +298,11 @@ class PostDropdownContainer extends PureComponent { }; _addToBookmarks = () => { - const { content, dispatch, intl } = this.props; - + const { content, dispatch, intl, isLoggedIn, navigation } = this.props as any; + if(!isLoggedIn){ + showLoginAlert({navigation, intl}); + return; + } addBookmark(get(content, 'author'), get(content, 'permlink')) .then(() => { dispatch( @@ -260,7 +325,11 @@ class PostDropdownContainer extends PureComponent { }; _reblog = () => { - const { content, currentAccount, dispatch, intl, isLoggedIn, pinCode } = this.props; + const { content, currentAccount, dispatch, intl, isLoggedIn, pinCode, navigation } = this.props as any; + if(!isLoggedIn){ + showLoginAlert({navigation, intl}); + return; + } if (isLoggedIn) { reblog(currentAccount, pinCode, content.author, get(content, 'permlink', '')) .then(() => { @@ -344,7 +413,7 @@ class PostDropdownContainer extends PureComponent { } _redirectToReply = () => { - const { content, fetchPost, isLoggedIn, navigation } = this.props; + const { content, fetchPost, isLoggedIn, navigation } = this.props as any; if (isLoggedIn) { navigation.navigate({ @@ -360,7 +429,7 @@ class PostDropdownContainer extends PureComponent { }; _redirectToPromote = (routeName, from, redeemType) => { - const { content, isLoggedIn, navigation, dispatch, isPinCodeOpen } = this.props; + const { content, isLoggedIn, navigation, dispatch, isPinCodeOpen } = this.props as any; const params = { from, permlink: `${get(content, 'author')}/${get(content, 'permlink')}`, @@ -372,7 +441,7 @@ class PostDropdownContainer extends PureComponent { openPinCodeModal({ navigateTo: routeName, navigateParams: params, - }), + } as any) ); } else if (isLoggedIn) { navigation.navigate({ @@ -387,9 +456,10 @@ class PostDropdownContainer extends PureComponent { intl, currentAccount: { name }, content, + isMuted } = this.props; const { options } = this.state; - + return ( state.account.currentAccount.mutes); + const scrollPosition = useSelector((state) => { return isFeedScreen ? state.posts.feedScrollPosition @@ -76,7 +77,6 @@ const postsListContainer = ({ }, [scrollPosition]) - const _setImageHeightInMap = (mapKey:string, height:number) => { if(mapKey && height){ setImageHeights(imageHeights.set(mapKey, height)); @@ -106,17 +106,19 @@ const postsListContainer = ({ const _renderItem = ({ item, index }:{item:any, index:number}) => { - const e = []; - + const e = [] as any; + if (index % 3 === 0) { const ix = index / 3 - 1; if (promotedPosts[ix] !== undefined) { const p = promotedPosts[ix]; - if (get(p, 'author', null) && posts && posts.filter((x) => x.permlink === p.permlink).length <= 0) { + let isMuted = mutes && mutes.indexOf(p.author) > -1; + + if (!isMuted && get(p, 'author', null) && posts && posts.filter((x) => x.permlink === p.permlink).length <= 0) { //get image height from cache if available const localId = p.author + p.permlink; - const imgHeight = imageHeights.get(localId) + const imgHeight = imageHeights.get(localId); e.push( , + mutes={mutes} + /> ); } } } - if (get(item, 'author', null)) { + + let isMuted = mutes && mutes.indexOf(item.author) > -1; + if (!isMuted && get(item, 'author', null)) { //get image height from cache if available const localId = item.author + item.permlink; const imgHeight = imageHeights.get(localId) @@ -146,13 +151,13 @@ const postsListContainer = ({ setImageHeight = {_setImageHeightInMap} pageType={pageType} showQuickReplyModal={showQuickReplyModal} + mutes={mutes} />, ); } return e; }; - - + return ( {({ isDarkTheme }) => ( diff --git a/src/components/settingsItem/view/settingsItemStyles.js b/src/components/settingsItem/view/settingsItemStyles.js index aab1d9573..d2ab1fb5d 100644 --- a/src/components/settingsItem/view/settingsItemStyles.js +++ b/src/components/settingsItem/view/settingsItemStyles.js @@ -54,4 +54,8 @@ export default EStyleSheet.create({ textButton: { justifyContent: 'center', }, + iconBtn: { + borderRadius: 0, + width: 50, + }, }); diff --git a/src/components/settingsItem/view/settingsItemView.js b/src/components/settingsItem/view/settingsItemView.js index 8f6b1fa6b..63561e89e 100644 --- a/src/components/settingsItem/view/settingsItemView.js +++ b/src/components/settingsItem/view/settingsItemView.js @@ -4,11 +4,13 @@ import { View, Text } from 'react-native'; // Constants // Components +import EStyleSheet from 'react-native-extended-stylesheet'; import { DropdownButton } from '../../dropdownButton'; import { TextButton } from '../../buttons'; import { ToggleSwitch } from '../../toggleSwitch'; // Styles import styles from './settingsItemStyles'; +import IconButton from '../../iconButton'; class SettingsItemView extends PureComponent { /* Props @@ -73,6 +75,17 @@ class SettingsItemView extends PureComponent { /> ); + case 'icon': + return ( + handleOnButtonPress(actionType)} + name="trash-bin-outline" + size={24} + color={EStyleSheet.value('$primaryRed')} + style={styles.iconBtn} + /> + ); + default: return ( { } } +export const deleteAccount = async (username: string) => { + try { + const response = await api + .post('/request-delete', { + username, + }) + return response.data + } catch (err) { + console.warn("Failed to report to ecency") + bugsnagInstance.notify(err); + throw err; + } +} + /** * ************************************ diff --git a/src/redux/reducers/accountReducer.ts b/src/redux/reducers/accountReducer.ts index cdaf80ae5..d2f465411 100644 --- a/src/redux/reducers/accountReducer.ts +++ b/src/redux/reducers/accountReducer.ts @@ -93,13 +93,12 @@ export default function (state = initialState, action) { }; case UPDATE_CURRENT_ACCOUNT: - return { - ...state, + return Object.assign({}, state, { currentAccount: action.payload, isFetching: false, hasError: false, errorMessage: null, - }; + }); case UPDATE_UNREAD_ACTIVITY_COUNT: return { diff --git a/src/screens/settings/container/settingsContainer.tsx b/src/screens/settings/container/settingsContainer.tsx index ae3d07601..4e8b5db3f 100644 --- a/src/screens/settings/container/settingsContainer.tsx +++ b/src/screens/settings/container/settingsContainer.tsx @@ -41,9 +41,10 @@ import { setIsBiometricEnabled, setEncryptedUnlockPin, setHidePostsThumbnails, + logout, } from '../../../redux/actions/applicationActions'; -import { toastNotification } from '../../../redux/actions/uiAction'; -import { setPushToken, getNodes } from '../../../providers/ecency/ecency'; +import { showActionModal, toastNotification } from '../../../redux/actions/uiAction'; +import { setPushToken, getNodes, deleteAccount } from '../../../providers/ecency/ecency'; import { checkClient } from '../../../providers/hive/dhive'; import { removeOtherAccount, updateCurrentAccount } from '../../../redux/actions/accountAction'; // Middleware @@ -312,6 +313,11 @@ class SettingsContainer extends Component { case 'feedback': this._handleSendFeedback(); break; + + case settingsTypes.DELETE_ACCOUNT: + this._handleDeleteAccount(); + break; + default: break; } @@ -389,6 +395,51 @@ class SettingsContainer extends Component { } }; + _handleDeleteAccount = () => { + const { dispatch, intl, currentAccount } = this.props as any; + + const _onConfirm = () => { + deleteAccount(currentAccount.username) + .then(() => { + dispatch( + toastNotification( + intl.formatMessage({ + id: 'delete.request_sent', + }), + ), + ); + dispatch(logout()); + }) + .catch(() => { + dispatch( + toastNotification( + intl.formatMessage({ + id: 'delete.request_sent', + }), + ), + ); + dispatch(logout()); + }); + }; + + dispatch( + showActionModal({ + title: intl.formatMessage({ id: 'delete.confirm_delete_title' }), + body: intl.formatMessage({ id: 'delete.confirm_delete_body' }), + buttons: [ + { + text: intl.formatMessage({ id: 'alert.cancel' }), + onPress: () => {}, + }, + { + text: intl.formatMessage({ id: 'alert.delete' }), + onPress: _onConfirm, + }, + ], + }), + ); + + } _clearUserData = async () => { const { otherAccounts, dispatch } = this.props; diff --git a/src/screens/settings/screen/settingsScreen.js b/src/screens/settings/screen/settingsScreen.js index 27a95a937..b935fa0a7 100644 --- a/src/screens/settings/screen/settingsScreen.js +++ b/src/screens/settings/screen/settingsScreen.js @@ -277,6 +277,19 @@ const SettingsScreen = ({ actionType="feedback" handleOnButtonPress={handleOnButtonPress} /> + {!!isLoggedIn && ( + + )} diff --git a/src/utils/showLoginAlert.ts b/src/utils/showLoginAlert.ts new file mode 100644 index 000000000..6ddb07953 --- /dev/null +++ b/src/utils/showLoginAlert.ts @@ -0,0 +1,24 @@ +import { Alert } from 'react-native'; +import ROUTES from '../../src/constants/routeNames'; + +const showLoginAlert = ({ navigation, intl }) => { + return Alert.alert( + intl.formatMessage({ id: 'login.not_loggedin_alert' }), + intl.formatMessage({ id: 'login.not_loggedin_alert_desc' }), + [ + { + text: intl.formatMessage({ id: 'login.cancel' }), + onPress: () => console.log('Cancel Pressed'), + style: 'cancel', + }, + { + text: intl.formatMessage({ id: 'login.login' }), + onPress: () => { + navigation.navigate(ROUTES.SCREENS.LOGIN); + }, + }, + ], + ); +}; + +export default showLoginAlert;