diff --git a/ios/Podfile.lock b/ios/Podfile.lock index b8fa72a07..a0f02f3ba 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -442,7 +442,7 @@ PODS: - React-Core - RNScreens (2.18.1): - React-Core - - RNSVG (9.13.6): + - RNSVG (12.1.1): - React - RNVectorIcons (6.7.0): - React @@ -761,7 +761,7 @@ SPEC CHECKSUMS: RNOS: 6f2f9a70895bbbfbdad7196abd952e7b01d45027 RNReanimated: e03f7425cb7a38dcf1b644d680d1bfc91c3337ad RNScreens: f7ad633b2e0190b77b6a7aab7f914fad6f198d8d - RNSVG: 8ba35cbeb385a52fd960fd28db9d7d18b4c2974f + RNSVG: 551acb6562324b1d52a4e0758f7ca0ec234e278f RNVectorIcons: 368d6d8b8301224e5ffb6254191f4f8876c2be0d SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d SDWebImageWebPCoder: d0dac55073088d24b2ac1b191a71a8f8d0adac21 diff --git a/package.json b/package.json index 288221407..9c005511c 100644 --- a/package.json +++ b/package.json @@ -105,6 +105,7 @@ "react-native-modal-translucent": "^5.0.0", "react-native-navigation-bar-color": "^1.0.0", "react-native-os": "^1.0.1", + "react-native-progress": "^5.0.0", "react-native-push-notification": "^7.3.1", "react-native-qrcode-svg": "^6.0.3", "react-native-randombytes": "^3.6.1", @@ -117,7 +118,7 @@ "react-native-scrollable-tab-view": "ecency/react-native-scrollable-tab-view", "react-native-snap-carousel": "^3.8.0", "react-native-splash-screen": "^3.2.0", - "react-native-svg": "^9.5.3", + "react-native-svg": "^12.1.1", "react-native-swiper": "^1.6.0-rc.3", "react-native-tcp": "^3.2.1", "react-native-udp": "^2.1.0", diff --git a/src/components/icon/view/iconView.js b/src/components/icon/view/iconView.js index 8695f9565..247faf408 100644 --- a/src/components/icon/view/iconView.js +++ b/src/components/icon/view/iconView.js @@ -7,6 +7,7 @@ import FontAwesome from 'react-native-vector-icons/FontAwesome'; import Feather from 'react-native-vector-icons/Feather'; import AntDesign from 'react-native-vector-icons/AntDesign'; import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; +import FontAwesome5 from 'react-native-vector-icons/FontAwesome5'; import styles from './iconStyles'; @@ -46,6 +47,8 @@ class IconView extends PureComponent { return ; case 'FontAwesome': return ; + case 'FontAwesome5': + return ; case 'SimpleLineIcons': return {children}; case 'AntDesign': diff --git a/src/components/index.js b/src/components/index.js index ab033ece3..746d80704 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -91,6 +91,7 @@ import { ActionModal } from './actionModal'; import { CustomiseFiltersModal } from './customiseFiltersModal'; import { ForegroundNotification } from './foregroundNotification'; import { PostHtmlRenderer } from './postHtmlRenderer'; +import { QuickProfileModal } from './organisms'; // Basic UI Elements import { @@ -230,4 +231,5 @@ export { CustomiseFiltersModal, ForegroundNotification, PostHtmlRenderer, + QuickProfileModal, }; diff --git a/src/components/notification/view/notificationView.tsx b/src/components/notification/view/notificationView.tsx index 7f2a38522..853c1e250 100644 --- a/src/components/notification/view/notificationView.tsx +++ b/src/components/notification/view/notificationView.tsx @@ -188,6 +188,7 @@ class NotificationView extends PureComponent { {this.props.handleOnUserPress(item.source)}} /> ) diff --git a/src/components/notificationLine/view/notificationLineView.js b/src/components/notificationLine/view/notificationLineView.js index 259c7d405..d35909280 100644 --- a/src/components/notificationLine/view/notificationLineView.js +++ b/src/components/notificationLine/view/notificationLineView.js @@ -1,6 +1,6 @@ /* eslint-disable react/jsx-one-expression-per-line */ import React, { useState, useEffect } from 'react'; -import { View, Text, Image, TouchableHighlight } from 'react-native'; +import { View, Text, Image, TouchableHighlight, TouchableOpacity } from 'react-native'; import { useIntl } from 'react-intl'; import get from 'lodash/get'; @@ -10,7 +10,7 @@ import { UserAvatar } from '../../userAvatar'; // Styles import styles from './notificationLineStyles'; -const NotificationLineView = ({ notification, handleOnPressNotification }) => { +const NotificationLineView = ({ notification, handleOnPressNotification, handleOnUserPress }) => { const [isRead, setIsRead] = useState(notification.read); const intl = useIntl(); let _title; @@ -58,10 +58,14 @@ const NotificationLineView = ({ notification, handleOnPressNotification }) => { key={`${get(notification, 'id')}${_title}`} style={[styles.notificationWrapper, !isRead && styles.isNewNotification]} > - + + + + {notification.source} diff --git a/src/components/organisms/index.ts b/src/components/organisms/index.ts new file mode 100644 index 000000000..839b779e0 --- /dev/null +++ b/src/components/organisms/index.ts @@ -0,0 +1 @@ +export * from './quickProfileModal'; \ No newline at end of file diff --git a/src/components/organisms/quickProfileModal/children/actionPanel.tsx b/src/components/organisms/quickProfileModal/children/actionPanel.tsx new file mode 100644 index 000000000..367a29339 --- /dev/null +++ b/src/components/organisms/quickProfileModal/children/actionPanel.tsx @@ -0,0 +1,45 @@ +import * as React from 'react'; +import { Text, View, StyleSheet } from 'react-native'; +import EStyleSheet from 'react-native-extended-stylesheet'; +import { IconButton } from '../../..'; +import { useAppSelector } from '../../../../hooks'; +import styles from './quickProfileStyles'; + +interface ActionPanelProps { + isFollowing:boolean, + isFavourite:boolean, + onFollowPress:()=>void, + onFavouritePress:()=>void +} + +export const ActionPanel = ({isFollowing, isFavourite, onFavouritePress, onFollowPress}: ActionPanelProps) => { + + const heartColor = isFavourite + ? '$primaryRed' + : '$primaryDarkGray' + + const followIcon = isFollowing + ? 'user-check' + : 'user-plus' + + return ( + + + + + ); +}; \ No newline at end of file diff --git a/src/components/organisms/quickProfileModal/children/index.ts b/src/components/organisms/quickProfileModal/children/index.ts new file mode 100644 index 000000000..22716c28c --- /dev/null +++ b/src/components/organisms/quickProfileModal/children/index.ts @@ -0,0 +1 @@ +export * from './quickProfileContent'; \ No newline at end of file diff --git a/src/components/organisms/quickProfileModal/children/profileBasic.tsx b/src/components/organisms/quickProfileModal/children/profileBasic.tsx new file mode 100644 index 000000000..e4881a12f --- /dev/null +++ b/src/components/organisms/quickProfileModal/children/profileBasic.tsx @@ -0,0 +1,61 @@ +import React from 'react' +import { View, Text, TouchableOpacity } from 'react-native' +import FastImage from 'react-native-fast-image'; +import styles from './quickProfileStyles'; +import * as Progress from 'react-native-progress'; +import EStyleSheet from 'react-native-extended-stylesheet'; +import { useIntl } from 'react-intl'; + +interface Props { + avatarUrl:string, + username:string, + about:string, + created:{ + unit:string, + value:number + }, + votingPower:string, + isLoading:boolean, + onPress:()=>void +} + +export const ProfileBasic = ({avatarUrl, username, about, votingPower, isLoading, created, onPress}: Props) => { + const intl = useIntl(); + const progress = parseInt(votingPower || '0')/100; + + let joinedString = '---' + if(created){ + const timeString = `${(-created.value)} ${intl.formatMessage({id:`time.${created.unit}`})}`; + joinedString = intl.formatMessage({id:'profile.joined'}, {time:timeString}) + } + + return ( + + + + + + + + + + {`@${username}`} + {about} + {joinedString} + + + ) +} diff --git a/src/components/organisms/quickProfileModal/children/profileStats.tsx b/src/components/organisms/quickProfileModal/children/profileStats.tsx new file mode 100644 index 000000000..747e2da33 --- /dev/null +++ b/src/components/organisms/quickProfileModal/children/profileStats.tsx @@ -0,0 +1,41 @@ +import React from 'react' +import { View, Text } from 'react-native' +import styles from './quickProfileStyles' +import * as Animated from 'react-native-animatable' + + +export interface StatsData { + label:string, + value:number|string, + suffix?:string +} + +interface Props { + data:StatsData[], + horizontalMargin?:number +} + +export const ProfileStats = ({data, horizontalMargin}: Props) => { + return ( + + {data.map((item)=>)} + + ) +} + +const StatItem = (props:{label:string, value:number|string}) => ( + + {!!props.value ? ( + {props.value} + ):( + {'--'} + )} + + {props.label} + +) + + + + + diff --git a/src/components/organisms/quickProfileModal/children/quickProfileContent.tsx b/src/components/organisms/quickProfileModal/children/quickProfileContent.tsx new file mode 100644 index 000000000..c51e617fc --- /dev/null +++ b/src/components/organisms/quickProfileModal/children/quickProfileContent.tsx @@ -0,0 +1,273 @@ +import React, { useEffect, useState } from 'react' +import { useIntl } from 'react-intl' +import { View, Alert } from 'react-native' +import { ProfileStats, StatsData } from './profileStats' +import { MainButton } from '../../..' +import { addFavorite, checkFavorite, deleteFavorite } from '../../../../providers/ecency/ecency' +import { followUser, getFollows, getRelationship, getUser } from '../../../../providers/hive/dhive' +import { getRcPower, getVotingPower } from '../../../../utils/manaBar' +import styles from './quickProfileStyles' +import { ProfileBasic } from './profileBasic' +import { parseReputation } from '../../../../utils/user' +import { default as ROUTES } from '../../../../constants/routeNames'; +import { ActionPanel } from './actionPanel' +import { getTimeFromNowNative } from '../../../../utils/time' +import { useAppDispatch, useAppSelector } from '../../../../hooks' +import { toastNotification } from '../../../../redux/actions/uiAction' +import Bugsnag from '@bugsnag/react-native' + +interface QuickProfileContentProps { + username:string, + navigation:any; + onClose:()=>void; +} + +export const QuickProfileContent = ({ + username, + navigation, + onClose +}:QuickProfileContentProps) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + + const currentAccount = useAppSelector((state)=>state.account.currentAccount); + const pinCode = useAppSelector((state)=>state.application.pin); + const isLoggedIn = useAppSelector((state)=>state.application.isLoggedIn); + + const [isLoading, setIsLoading] = useState(false); + const [user, setUser] = useState(null); + const [follows, setFollows] = useState(null); + const [isFollowing, setIsFollowing] = useState(false); + const [isMuted, setIsMuted] = useState(false); + const [isFavourite, setIsFavourite] = useState(false); + + const isOwnProfile = currentAccount && currentAccount.name === username; + const currentAccountName = currentAccount ? currentAccount.name : null; + + useEffect(() => { + if(username) { + _fetchUser(); + _fetchExtraUserData(); + } else { + setUser(null); + } + }, [username]) + + + //NETWORK CALLS + const _fetchUser = async () => { + setIsLoading(true); + try { + const _user = await getUser(username, isOwnProfile); + setUser(_user) + } catch (error) { + setIsLoading(false); + } + }; + + + const _fetchExtraUserData = async () => { + try { + if (username) { + let _isFollowing; + let _isMuted; + let _isFavourite; + let follows; + + if (!isOwnProfile) { + const res = await getRelationship(currentAccountName, username); + _isFollowing = res && res.follows; + _isMuted = res && res.ignores; + _isFavourite = await checkFavorite(username); + } + + try { + follows = await getFollows(username); + } catch (err) { + follows = null; + } + + + setFollows(follows); + setIsFollowing(_isFollowing); + setIsMuted(_isMuted) + setIsFavourite(_isFavourite) + setIsLoading(false); + + } + } catch (error) { + console.warn('Failed to fetch complete profile data', error); + Alert.alert( + intl.formatMessage({ + id: 'alert.fail', + }), + error.message || error.toString(), + ); + setIsLoading(false); + } + }; + + + const _onFollowPress = async () => { + try{ + const follower = currentAccountName + const following = username; + + setIsLoading(true); + await followUser(currentAccount, pinCode, { + follower, + following, + }) + + setIsLoading(false); + setIsFollowing(true) + dispatch( + toastNotification( + intl.formatMessage({ + id: isFollowing ? 'alert.success_unfollow' : 'alert.success_follow', + }), + ), + ); + } + catch(err){ + setIsLoading(false); + console.warn("Failed to follow user", err) + Bugsnag.notify(err); + Alert.alert(intl.formatMessage({id:'alert.fail'}), err.message) + } + } + + const _onFavouritePress = async () => { + try{ + setIsLoading(true); + let favoriteAction; + + if (isFavourite) { + favoriteAction = deleteFavorite; + } else { + favoriteAction = addFavorite; + } + + await favoriteAction(username) + + dispatch( + toastNotification( + intl.formatMessage({ + id: isFavourite ? 'alert.success_unfavorite' : 'alert.success_favorite', + }), + ), + ); + setIsFavourite(!isFavourite); + setIsLoading(false); + } + + catch(error){ + console.warn('Failed to perform favorite action'); + setIsLoading(false); + Alert.alert( + intl.formatMessage({ + id: 'alert.fail', + }), + error.message || error.toString(), + ); + } + } + + + + //UI CALLBACKS + + const _openFullProfile = () => { + let params = { + username, + reputation: user ? user.reputation : null + }; + + if (isOwnProfile) { + navigation.navigate(ROUTES.TABBAR.PROFILE); + } else { + navigation.navigate({ + routeName: ROUTES.SCREENS.PROFILE, + params, + key: username, + }); + } + if(onClose){ + onClose(); + } + } + + //extract prop values + let _votingPower = ''; + let _resourceCredits = ''; + let _followerCount = 0; + let _followingCount = 0; + let _postCount = 0; + let _avatarUrl = ''; + let _about = ''; + let _reputation = 0; + let _createdData = null; + + if (user && follows) { + _votingPower = getVotingPower(user).toFixed(1); + _resourceCredits = getRcPower(user).toFixed(0); + _postCount = user.post_count || 0; + _avatarUrl = user.avatar || ''; + _about = user.about?.profile?.about || ''; + _reputation = parseReputation(user.reputation); + _createdData = getTimeFromNowNative(user.created) + + if(follows){ + _followerCount = follows.follower_count || 0; + _followingCount = follows.following_count || 0 + } + } + + + + const statsData1 = [ + {label:'Follower', value:_followerCount}, + {label:'Following', value:_followingCount}, + {label:'Posts', value:_postCount}, + ] as StatsData[] + + const statsData2 = [ + {label:'Resource Credits', value:_resourceCredits, suffix:'%'}, + {label:'Reputation', value:_reputation}, + ] as StatsData[] + + return ( + + + + + + {isLoggedIn && ( + + )} + + + ) +}; diff --git a/src/components/organisms/quickProfileModal/children/quickProfileStyles.ts b/src/components/organisms/quickProfileModal/children/quickProfileStyles.ts new file mode 100644 index 000000000..51704565b --- /dev/null +++ b/src/components/organisms/quickProfileModal/children/quickProfileStyles.ts @@ -0,0 +1,109 @@ +import { TextStyle, ViewStyle, ImageStyle } from 'react-native'; +import EStyleSheet from 'react-native-extended-stylesheet'; +import { getBottomSpace } from 'react-native-iphone-x-helper'; + +export default EStyleSheet.create({ + modalStyle: { + backgroundColor: '$primaryBackgroundColor', + margin:0, + paddingTop:32, + paddingBottom: getBottomSpace() + 8, + marginHorizontal:24, + }, + + sheetContent: { + backgroundColor: '$primaryBackgroundColor', + position:'absolute', + bottom:0, + left:0, + right:0, + zIndex:999 + }, + + container:{ + alignItems:'center', + marginHorizontal:16 + } as ViewStyle, + + image:{ + width:128, + height:128, + borderRadius:64, + backgroundColor: '$primaryGray' + } as ImageStyle, + + textContainer:{ + marginTop:32, + marginBottom:44, + } as ViewStyle, + + title: { + color: '$primaryBlack', + alignSelf: 'center', + textAlign: 'center', + fontSize: 18, + fontWeight: 'bold', + marginTop:32, + } as TextStyle, + + statValue: { + fontFamily:'$editorFont', + color: '$primaryBlack', + alignSelf: 'center', + textAlign: 'center', + fontSize: 34, + fontWeight: 'normal', + } as TextStyle, + + statLabel: { + color: '$primaryBlack', + alignSelf: 'center', + textAlign: 'center', + fontSize: 16, + fontWeight: 'bold', + } as TextStyle, + + + bodyText: { + color: '$primaryBlack', + alignSelf: 'center', + textAlign: 'center', + fontSize: 18, + fontWeight: '500', + marginTop:6, + } as TextStyle, + + btnText:{ + color:'$pureWhite' + } as TextStyle, + + button:{ + marginTop: 40, + backgroundColor:'$primaryBlue', + paddingHorizontal:44, + paddingVertical:16, + borderRadius:32, + justifyContent:'center', + alignItems:'center' + } as ViewStyle, + + + actionPanel:{ + position: 'absolute', + right:0, + top:0, + flexDirection:'row', + alignItems:'center', + } as ViewStyle, + + progressCircle:{ + position:'absolute', + top:0, + bottom:0, + left:0, + right:0, + alignItems:'center', + justifyContent:'center' + } as ViewStyle + +}) \ No newline at end of file diff --git a/src/components/organisms/quickProfileModal/container/quickProfileModal.tsx b/src/components/organisms/quickProfileModal/container/quickProfileModal.tsx new file mode 100644 index 000000000..921f32bcc --- /dev/null +++ b/src/components/organisms/quickProfileModal/container/quickProfileModal.tsx @@ -0,0 +1,54 @@ +import React, { useEffect, useRef } from 'react'; +import { AlertButton } from 'react-native'; +import { Source } from 'react-native-fast-image'; +import ActionSheet from 'react-native-actions-sheet'; +import { QuickProfileContent } from '../children/quickProfileContent'; +import EStyleSheet from 'react-native-extended-stylesheet'; +import styles from '../children/quickProfileStyles'; +import { useAppDispatch, useAppSelector } from '../../../../hooks'; +import { hideProfileModal } from '../../../../redux/actions/uiAction'; + + + +export interface ActionModalData { + navigation:any +} + + +export const QuickProfileModal = ({navigation}) => { + const sheetModalRef = useRef(); + const dispatch = useAppDispatch(); + + const profileModalUsername = useAppSelector((state)=>state.ui.profileModalUsername); + + + useEffect(() => { + if(profileModalUsername){ + sheetModalRef.current.show(); + }else { + sheetModalRef.current.hide(); + } + }, [profileModalUsername]) + + + const _onClose = () => { + dispatch(hideProfileModal()); + } + + + return ( + + + + + ); +}; diff --git a/src/components/organisms/quickProfileModal/index.ts b/src/components/organisms/quickProfileModal/index.ts new file mode 100644 index 000000000..d14f44d9e --- /dev/null +++ b/src/components/organisms/quickProfileModal/index.ts @@ -0,0 +1 @@ +export * from './container/quickProfileModal'; \ No newline at end of file diff --git a/src/components/postCard/container/postCardContainer.js b/src/components/postCard/container/postCardContainer.js index 621013a00..a1c8ff9c9 100644 --- a/src/components/postCard/container/postCardContainer.js +++ b/src/components/postCard/container/postCardContainer.js @@ -4,16 +4,15 @@ import { connect } from 'react-redux'; import get from 'lodash/get'; // Services -import { act } from 'react-test-renderer'; -import { getPost, getActiveVotes } from '../../../providers/hive/dhive'; +import { getPost } from '../../../providers/hive/dhive'; import { getPostReblogs } from '../../../providers/ecency/ecency'; -import { parseActiveVotes } from '../../../utils/postParser'; - import PostCardView from '../view/postCardView'; // Constants import { default as ROUTES } from '../../../constants/routeNames'; +import { useAppDispatch } from '../../../hooks'; +import { showProfileModal } from '../../../redux/actions/uiAction'; /* * Props Name Description Value *@props --> props name here description here Value Type Here @@ -31,6 +30,8 @@ const PostCardContainer = ({ setImageHeight, pageType, }) => { + const dispatch = useAppDispatch(); + const [_content, setContent] = useState(content); const [reblogs, setReblogs] = useState([]); const activeVotes = get(_content, 'active_votes', []); @@ -78,23 +79,8 @@ const PostCardContainer = ({ const _handleOnUserPress = (username) => { if (_content) { - let params = { - username: username || get(_content, 'author'), - reputation: !username && get(_content, 'author_reputation'), - }; - - if ( - get(currentAccount, 'name') === params.username && - (pageType === 'main' || pageType === 'ownProfile') - ) { - navigation.navigate(ROUTES.TABBAR.PROFILE); - } else { - navigation.navigate({ - routeName: ROUTES.SCREENS.PROFILE, - params, - key: get(_content, 'author'), - }); - } + username = username || get(_content, 'author'); + dispatch(showProfileModal(username)); } }; diff --git a/src/components/postElements/headerDescription/view/postHeaderDescription.js b/src/components/postElements/headerDescription/view/postHeaderDescription.js index fa6b047f4..cc225430b 100644 --- a/src/components/postElements/headerDescription/view/postHeaderDescription.js +++ b/src/components/postElements/headerDescription/view/postHeaderDescription.js @@ -1,5 +1,6 @@ import React, { PureComponent } from 'react'; import { View, Text, TouchableOpacity } from 'react-native'; +import { connect } from 'react-redux'; import { withNavigation } from 'react-navigation'; import { injectIntl } from 'react-intl'; @@ -12,6 +13,7 @@ import styles from './postHeaderDescriptionStyles'; import { default as ROUTES } from '../../../../constants/routeNames'; import { IconButton } from '../../..'; +import { showProfileModal } from '../../../../redux/actions/uiAction'; // Constants const DEFAULT_IMAGE = require('../../../../assets/ecency.png'); @@ -21,19 +23,12 @@ class PostHeaderDescription extends PureComponent { // Component Functions _handleOnUserPress = (username) => { - const { navigation, profileOnPress, reputation } = this.props; + const { profileOnPress, dispatch } = this.props; if (profileOnPress) { profileOnPress(username); } else { - navigation.navigate({ - routeName: ROUTES.SCREENS.PROFILE, - params: { - username, - reputation, - }, - key: username, - }); + dispatch(showProfileModal(username)); } }; @@ -181,4 +176,6 @@ class PostHeaderDescription extends PureComponent { } } -export default withNavigation(injectIntl(PostHeaderDescription)); +const mapStateToProps = () => ({}); + +export default connect(mapStateToProps)(withNavigation(injectIntl(PostHeaderDescription))); diff --git a/src/config/locales/en-US.json b/src/config/locales/en-US.json index ae0f94323..820566b76 100644 --- a/src/config/locales/en-US.json +++ b/src/config/locales/en-US.json @@ -174,6 +174,7 @@ "day": "day", "hive_dollars": "Hive Dollars", "savings": "Savings", + "joined": "Joined {time} ago", "edit": { "display_name": "Display Name", "about": "About", @@ -672,5 +673,14 @@ "line3_heading":"Join Ecency communities!", "line3_body":"Build community you own, get rewarded and reward others.", "get_started":"Get started!" + }, + "time":{ + "second":"seconds", + "minute":"minutes", + "hour":"hours", + "week":"weeks", + "day":"days", + "month":"months", + "year":"years" } } diff --git a/src/redux/actions/uiAction.ts b/src/redux/actions/uiAction.ts index cbf03406c..68d9568a4 100644 --- a/src/redux/actions/uiAction.ts +++ b/src/redux/actions/uiAction.ts @@ -9,6 +9,8 @@ import { SHOW_ACTION_MODAL, HIDE_ACTION_MODAL, SET_AVATAR_CACHE_STAMP, + SHOW_PROFILE_MODAL, + HIDE_PROFILE_MODAL } from '../constants/constants'; export const updateActiveBottomTab = (payload:string) => ({ @@ -35,10 +37,24 @@ export const showActionModal = (title:string, body?:string, buttons?:AlertButton type: SHOW_ACTION_MODAL, }); + + export const hideActionModal = () => ({ type: HIDE_ACTION_MODAL, }); + +export const showProfileModal = (username:string) => ({ + payload: { + profileModalUsername: username + }, + type: SHOW_PROFILE_MODAL, +}); + +export const hideProfileModal = () => ({ + type: HIDE_PROFILE_MODAL, +}); + export const setRcOffer = (payload:boolean) => ({ payload, type: RC_OFFER, diff --git a/src/redux/constants/constants.js b/src/redux/constants/constants.js index 8c5de7830..d90fef688 100644 --- a/src/redux/constants/constants.js +++ b/src/redux/constants/constants.js @@ -55,6 +55,8 @@ export const TOGGLE_ACCOUNTS_BOTTOM_SHEET = 'TOGGLE_ACCOUNTS_BOTTOM_SHEET'; export const SHOW_ACTION_MODAL = 'SHOW_ACTION_MODAL'; export const HIDE_ACTION_MODAL = 'HIDE_ACTION_MODAL'; export const SET_AVATAR_CACHE_STAMP = 'SET_AVATAR_CACHE_STAMP'; +export const SHOW_PROFILE_MODAL = 'SHOW_PROFILE_MODAL'; +export const HIDE_PROFILE_MODAL = 'HIDE_PROFILE_MODAL'; // POSTS export const SET_FEED_POSTS = 'SET_FEED_POSTS'; diff --git a/src/redux/reducers/uiReducer.ts b/src/redux/reducers/uiReducer.ts index b792ac16d..04652e1cf 100644 --- a/src/redux/reducers/uiReducer.ts +++ b/src/redux/reducers/uiReducer.ts @@ -7,6 +7,8 @@ import { SHOW_ACTION_MODAL, HIDE_ACTION_MODAL, SET_AVATAR_CACHE_STAMP, + SHOW_PROFILE_MODAL, + HIDE_PROFILE_MODAL, } from '../constants/constants'; interface UiState { @@ -17,7 +19,8 @@ interface UiState { isVisibleAccountsBottomSheet:boolean; actionModalVisible:boolean; actionModalData:any; - avatarCacheStamp:number + avatarCacheStamp:number; + profileModalUsername:string; } const initialState:UiState = { @@ -28,7 +31,8 @@ const initialState:UiState = { isVisibleAccountsBottomSheet: false, actionModalVisible: false, actionModalData: null, - avatarCacheStamp: 0 + avatarCacheStamp: 0, + profileModalUsername: '' }; export default function (state = initialState, action) { @@ -61,6 +65,20 @@ export default function (state = initialState, action) { }; } + case SHOW_PROFILE_MODAL: { + return { + ...state, + profileModalUsername: action.payload.profileModalUsername, + }; + } + + case HIDE_PROFILE_MODAL: { + return { + ...state, + profileModalUsername: '', + }; + } + case RC_OFFER: return { ...state, diff --git a/src/screens/application/container/applicationContainer.js b/src/screens/application/container/applicationContainer.js index abf621e3e..14ea83692 100644 --- a/src/screens/application/container/applicationContainer.js +++ b/src/screens/application/container/applicationContainer.js @@ -87,6 +87,7 @@ import { } from '../../../redux/actions/applicationActions'; import { hideActionModal, + hideProfileModal, setAvatarCacheStamp, setRcOffer, showActionModal, @@ -761,6 +762,7 @@ class ApplicationContainer extends Component { //reset certain properties dispatch(hideActionModal()); + dispatch(hideProfileModal()); dispatch(toastNotification('')); dispatch(resetLocalVoteMap()); dispatch(setRcOffer(false)); diff --git a/src/screens/application/screen/applicationScreen.js b/src/screens/application/screen/applicationScreen.js index db7755d72..6c0741b02 100644 --- a/src/screens/application/screen/applicationScreen.js +++ b/src/screens/application/screen/applicationScreen.js @@ -25,6 +25,7 @@ import { AccountsBottomSheet, ActionModal, ForegroundNotification, + QuickProfileModal, } from '../../../components'; // Themes (Styles) @@ -113,6 +114,7 @@ class ApplicationScreen extends Component { toastNotification, isReady, foregroundNotificationData, + navigation, } = this.props; const { isShowToastNotification, showWelcomeModal } = this.state; const barStyle = isDarkTheme ? 'light-content' : 'dark-content'; @@ -171,6 +173,7 @@ class ApplicationScreen extends Component { + ); } diff --git a/src/screens/notification/container/notificationContainer.js b/src/screens/notification/container/notificationContainer.js index c4d8b9f2d..08da60599 100644 --- a/src/screens/notification/container/notificationContainer.js +++ b/src/screens/notification/container/notificationContainer.js @@ -15,6 +15,7 @@ import ROUTES from '../../../constants/routeNames'; // Components import NotificationScreen from '../screen/notificationScreen'; +import { showProfileModal } from '../../../redux/actions/uiAction'; class NotificationContainer extends Component { constructor(props) { @@ -118,6 +119,11 @@ class NotificationContainer extends Component { } }; + _handleOnUserPress = (username) => { + const { dispatch } = this.props; + dispatch(showProfileModal(username)); + }; + _readAllNotification = () => { const { dispatch, intl, isConnected } = this.props; const { notifications } = this.state; @@ -174,6 +180,7 @@ class NotificationContainer extends Component { getActivities={this._getActivities} notifications={notifications} navigateToNotificationRoute={this._navigateToNotificationRoute} + handleOnUserPress={this._handleOnUserPress} readAllNotification={this._readAllNotification} handleLoginPress={this._handleOnPressLogin} isNotificationRefreshing={isRefreshing} diff --git a/src/screens/notification/screen/notificationScreen.js b/src/screens/notification/screen/notificationScreen.js index 74aff39e7..d63701998 100644 --- a/src/screens/notification/screen/notificationScreen.js +++ b/src/screens/notification/screen/notificationScreen.js @@ -16,6 +16,7 @@ const NotificationScreen = ({ getActivities, intl, navigateToNotificationRoute, + handleOnUserPress, readAllNotification, isNotificationRefreshing, changeSelectedFilter, @@ -42,6 +43,7 @@ const NotificationScreen = ({ getActivities={getActivities} notifications={notifications} navigateToNotificationRoute={navigateToNotificationRoute} + handleOnUserPress={handleOnUserPress} readAllNotification={readAllNotification} isNotificationRefreshing={isNotificationRefreshing} changeSelectedFilter={changeSelectedFilter} diff --git a/yarn.lock b/yarn.lock index b7711275a..42cbf86d0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3513,7 +3513,7 @@ css-mediaquery@^0.1.2: resolved "https://registry.yarnpkg.com/css-mediaquery/-/css-mediaquery-0.1.2.tgz#6a2c37344928618631c54bd33cedd301da18bea0" integrity sha1-aiw3NEkoYYYxxUvTPO3TAdoYvqA= -css-select@^2.0.2: +css-select@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef" integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ== @@ -3532,10 +3532,10 @@ css-to-react-native@^3.0.0: css-color-keywords "^1.0.0" postcss-value-parser "^4.0.2" -css-tree@^1.0.0-alpha.37: - version "1.1.2" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.2.tgz#9ae393b5dafd7dae8a622475caec78d3d8fbd7b5" - integrity sha512-wCoWush5Aeo48GLhfHPbmvZs59Z+M7k5+B1xDnXbdWNcEF423DoFdqSWE0PM5aNk5nI5cp1q7ms36zGApY/sKQ== +css-tree@^1.0.0-alpha.39: + version "1.1.3" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" + integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== dependencies: mdn-data "2.0.14" source-map "^0.6.1" @@ -8877,6 +8877,13 @@ react-native-os@^1.0.1: resolved "https://registry.yarnpkg.com/react-native-os/-/react-native-os-1.2.6.tgz#1bb16d78ccad1143972183a04f443cf1af9fbefa" integrity sha512-OlT+xQAcvkcnf7imgXiu+myMkqDt4xw2bP5SlVo19hEn5XHBkPMLX7dk3sSGxxncH/ToMDsf1KLyrPabNVtadA== +react-native-progress@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/react-native-progress/-/react-native-progress-5.0.0.tgz#f5ac6ceaeee27f184c660b00f29419e82a9d0ab0" + integrity sha512-KjnGIt3r9i5Kn2biOD9fXLJocf0bwxPRxOyAgXEnZTJQU2O+HyzgGFRCbM5h3izm9kKIkSc1txh8aGmMafCD9A== + dependencies: + prop-types "^15.7.2" + react-native-push-notification@^7.3.1: version "7.3.1" resolved "https://registry.yarnpkg.com/react-native-push-notification/-/react-native-push-notification-7.3.1.tgz#1495feacd25169b998446dcf7b448a197ae5dca0" @@ -8975,13 +8982,13 @@ react-native-splash-screen@^3.2.0: resolved "https://registry.yarnpkg.com/react-native-splash-screen/-/react-native-splash-screen-3.2.0.tgz#d47ec8557b1ba988ee3ea98d01463081b60fff45" integrity sha512-Ls9qiNZzW/OLFoI25wfjjAcrf2DZ975hn2vr6U9gyuxi2nooVbzQeFoQS5vQcbCt9QX5NY8ASEEAtlLdIa6KVg== -react-native-svg@^9.5.3: - version "9.13.6" - resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-9.13.6.tgz#5365fba2bc460054b90851e71f2a71006a5d373f" - integrity sha512-vjjuJhEhQCwWjqsgWyGy6/C/LIBM2REDxB40FU1PMhi8T3zQUwUHnA6M15pJKlQG8vaZyA+QnLyIVhjtujRgig== +react-native-svg@^12.1.1: + version "12.1.1" + resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-12.1.1.tgz#5f292410b8bcc07bbc52b2da7ceb22caf5bcaaee" + integrity sha512-NIAJ8jCnXGCqGWXkkJ1GTzO4a3Md5at5sagYV8Vh4MXYnL4z5Rh428Wahjhh+LIjx40EE5xM5YtwyJBqOIba2Q== dependencies: - css-select "^2.0.2" - css-tree "^1.0.0-alpha.37" + css-select "^2.1.0" + css-tree "^1.0.0-alpha.39" react-native-swiper@^1.6.0-rc.3: version "1.6.0"