diff options
author | Ivan Chen <ivan@tagg.id> | 2021-05-18 13:00:14 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-05-18 13:00:14 -0400 |
commit | c8487348e1026a3a2c1a147d3eefe3ee0cc6528c (patch) | |
tree | 72061e400748adce01ad90b2b5943e135fa25323 /src/components | |
parent | efc84a7a5af59bcaf219d2ecb6767a3b29d01fae (diff) | |
parent | 4aca9fc0916240ce5e4284d625f240998db17bff (diff) |
Merge pull request #429 from brian-tagg/tma844-bugfix-plus-sign
[TMA-844] Plus sign for profile and header in profile
Diffstat (limited to 'src/components')
-rw-r--r-- | src/components/comments/CommentsContainer.tsx | 2 | ||||
-rw-r--r-- | src/components/common/Avatar.tsx | 23 | ||||
-rw-r--r-- | src/components/profile/Cover.tsx | 114 | ||||
-rw-r--r-- | src/components/profile/ProfileHeader.tsx | 1 | ||||
-rw-r--r-- | src/components/profile/TaggAvatar.tsx | 95 |
5 files changed, 215 insertions, 20 deletions
diff --git a/src/components/comments/CommentsContainer.tsx b/src/components/comments/CommentsContainer.tsx index 595ec743..d839ef38 100644 --- a/src/components/comments/CommentsContainer.tsx +++ b/src/components/comments/CommentsContainer.tsx @@ -49,7 +49,7 @@ const CommentsContainer: React.FC<CommentsContainerProps> = ({ count += 1 + comments[i].replies_count; } return count; - } + }; useEffect(() => { const loadComments = async () => { diff --git a/src/components/common/Avatar.tsx b/src/components/common/Avatar.tsx index 831cf906..86ebedf3 100644 --- a/src/components/common/Avatar.tsx +++ b/src/components/common/Avatar.tsx @@ -1,17 +1,30 @@ import React, {FC} from 'react'; -import {Image, ImageStyle, StyleProp} from 'react-native'; +import {Image, ImageStyle, StyleProp, ImageBackground} from 'react-native'; type AvatarProps = { style: StyleProp<ImageStyle>; uri: string | undefined; + loading: boolean; + loadingStyle: StyleProp<ImageStyle> | undefined; }; -const Avatar: FC<AvatarProps> = ({style, uri}) => { +const Avatar: FC<AvatarProps> = ({ + style, + uri, + loading = false, + loadingStyle, +}) => { return ( - <Image + <ImageBackground style={style} defaultSource={require('../../assets/images/avatar-placeholder.png')} - source={{uri, cache: 'reload'}} - /> + source={{uri, cache: 'reload'}}> + {loading && ( + <Image + source={require('../../assets/gifs/loading-animation.gif')} + style={loadingStyle} + /> + )} + </ImageBackground> ); }; diff --git a/src/components/profile/Cover.tsx b/src/components/profile/Cover.tsx index 27777b64..2b6268a6 100644 --- a/src/components/profile/Cover.tsx +++ b/src/components/profile/Cover.tsx @@ -1,26 +1,99 @@ -import React from 'react'; -import {Image, StyleSheet, View} from 'react-native'; -import {useSelector} from 'react-redux'; +import React, {useState, useEffect} from 'react'; +import { + Image, + StyleSheet, + View, + TouchableOpacity, + Text, + ImageBackground, +} from 'react-native'; import {COVER_HEIGHT, IMAGE_WIDTH} from '../../constants'; -import {RootState} from '../../store/rootreducer'; import {ScreenType} from '../../types'; +import GreyPurplePlus from '../../assets/icons/grey-purple-plus.svg'; +import {useDispatch, useSelector} from 'react-redux'; +import {loadUserData, resetHeaderAndProfileImage} from '../../store/actions'; +import {RootState} from '../../store/rootreducer'; +import {normalize, patchProfile, validateImageLink} from '../../utils'; +import {useIsFocused} from '@react-navigation/native'; interface CoverProps { userXId: string | undefined; screenType: ScreenType; } const Cover: React.FC<CoverProps> = ({userXId, screenType}) => { - const {cover} = useSelector((state: RootState) => + const dispatch = useDispatch(); + const {cover, user} = useSelector((state: RootState) => userXId ? state.userX[screenType][userXId] : state.user, ); + const [needsUpdate, setNeedsUpdate] = useState(false); + const [updating, setUpdating] = useState(false); + const [loading, setLoading] = useState(true); + const [validImage, setValidImage] = useState<boolean>(true); + const isFocused = useIsFocused(); + + useEffect(() => { + checkCover(cover); + setLoading(false); + }, []); + + useEffect(() => { + checkCover(cover); + }, [cover, isFocused]); + + useEffect(() => { + checkCover(cover); + if (needsUpdate) { + const userId = user.userId; + const username = user.username; + dispatch(resetHeaderAndProfileImage()); + dispatch(loadUserData({userId, username})); + } + }, [dispatch, needsUpdate]); + + const handleNewImage = async () => { + setLoading(true); + const result = await patchProfile('header', user.userId); + setLoading(true); + if (result) { + setUpdating(true); + setNeedsUpdate(true); + setLoading(false); + } else { + setLoading(false); + } + }; + + const checkCover = async (url: string | undefined) => { + const valid = await validateImageLink(url); + if (valid !== validImage) { + setValidImage(valid); + } + setLoading(false); + }; + return ( - <View style={[styles.container]}> - <Image + <View style={styles.container}> + <ImageBackground style={styles.image} defaultSource={require('../../assets/images/cover-placeholder.png')} - source={{uri: cover, cache: 'reload'}} - /> + source={{uri: cover, cache: 'reload'}}> + {loading && ( + <Image + source={require('../../assets/gifs/loading-animation.gif')} + style={styles.loadingLarge} + /> + )} + {!validImage && userXId === undefined && !loading && !updating && ( + <TouchableOpacity + accessible={true} + accessibilityLabel="ADD HEADER PICTURE" + onPress={() => handleNewImage()}> + <GreyPurplePlus style={styles.plus} /> + <Text style={styles.text}>Add Picture</Text> + </TouchableOpacity> + )} + </ImageBackground> </View> ); }; @@ -33,5 +106,28 @@ const styles = StyleSheet.create({ width: IMAGE_WIDTH, height: COVER_HEIGHT, }, + plus: { + position: 'absolute', + top: 75, + right: 125, + }, + text: { + color: 'white', + position: 'absolute', + fontSize: normalize(16), + top: 80, + right: 20, + }, + touch: { + flex: 1, + }, + loadingLarge: { + alignSelf: 'center', + justifyContent: 'center', + height: COVER_HEIGHT * 0.2, + width: IMAGE_WIDTH * 0.2, + aspectRatio: 1, + top: 100, + }, }); export default Cover; diff --git a/src/components/profile/ProfileHeader.tsx b/src/components/profile/ProfileHeader.tsx index db5308b9..2241899d 100644 --- a/src/components/profile/ProfileHeader.tsx +++ b/src/components/profile/ProfileHeader.tsx @@ -111,6 +111,7 @@ const ProfileHeader: React.FC<ProfileHeaderProps> = ({ style={styles.avatar} userXId={userXId} screenType={screenType} + editable={true} /> <View style={styles.header}> <Text style={styles.name} numberOfLines={2}> diff --git a/src/components/profile/TaggAvatar.tsx b/src/components/profile/TaggAvatar.tsx index ea0bdb65..8ccae2ef 100644 --- a/src/components/profile/TaggAvatar.tsx +++ b/src/components/profile/TaggAvatar.tsx @@ -1,9 +1,13 @@ -import React from 'react'; -import {StyleSheet} from 'react-native'; -import {useSelector} from 'react-redux'; +import React, {useState, useEffect} from 'react'; +import {StyleSheet, TouchableOpacity} from 'react-native'; import {RootState} from '../../store/rootreducer'; import {ScreenType} from '../../types'; import {Avatar} from '../common'; +import {useDispatch, useSelector} from 'react-redux'; +import {loadUserData, resetHeaderAndProfileImage} from '../../store/actions'; +import PurplePlus from '../../assets/icons/purple-plus.svg'; +import {patchProfile, validateImageLink} from '../../utils'; +import {useIsFocused} from '@react-navigation/native'; const PROFILE_DIM = 100; @@ -11,17 +15,85 @@ interface TaggAvatarProps { style?: object; userXId: string | undefined; screenType: ScreenType; + editable: boolean; } const TaggAvatar: React.FC<TaggAvatarProps> = ({ style, screenType, userXId, + editable = false, }) => { - const {avatar} = useSelector((state: RootState) => + const {avatar, user} = useSelector((state: RootState) => userXId ? state.userX[screenType][userXId] : state.user, ); + const dispatch = useDispatch(); + const [needsUpdate, setNeedsUpdate] = useState(false); + const [updating, setUpdating] = useState(false); + const [loading, setLoading] = useState(true); + const [validImage, setValidImage] = useState<boolean>(true); + const isFocused = useIsFocused(); - return <Avatar style={[styles.image, style]} uri={avatar} />; + useEffect(() => { + checkAvatar(avatar); + setLoading(false); + }, []); + + useEffect(() => { + checkAvatar(avatar); + }, [avatar, isFocused]); + + useEffect(() => { + checkAvatar(avatar); + if (needsUpdate) { + const userId = user.userId; + const username = user.username; + dispatch(resetHeaderAndProfileImage()); + dispatch(loadUserData({userId, username})); + } + }, [dispatch, needsUpdate]); + + const handleNewImage = async () => { + setLoading(true); + const result = await patchProfile('profile', user.userId); + setLoading(true); + if (result) { + setUpdating(true); + setNeedsUpdate(true); + setLoading(false); + } else { + setLoading(false); + } + }; + + const checkAvatar = async (url: string | undefined) => { + const valid = await validateImageLink(url); + if (valid !== validImage) { + setValidImage(valid); + } + }; + + return ( + <> + <Avatar + style={[styles.image, style]} + uri={avatar} + loading={loading} + loadingStyle={styles.loadingLarge} + /> + {editable && + !validImage && + userXId === undefined && + !loading && + !updating && ( + <TouchableOpacity + accessible={true} + accessibilityLabel="ADD PROFILE PICTURE" + onPress={() => handleNewImage()}> + <PurplePlus style={styles.plus} /> + </TouchableOpacity> + )} + </> + ); }; const styles = StyleSheet.create({ @@ -29,6 +101,19 @@ const styles = StyleSheet.create({ height: PROFILE_DIM, width: PROFILE_DIM, borderRadius: PROFILE_DIM / 2, + overflow: 'hidden', + }, + plus: { + position: 'absolute', + bottom: 35, + right: 0, + }, + loadingLarge: { + height: PROFILE_DIM * 0.8, + width: PROFILE_DIM * 0.8, + alignSelf: 'center', + justifyContent: 'center', + aspectRatio: 2, }, }); |