diff options
Diffstat (limited to 'src/view/com/profile')
-rw-r--r-- | src/view/com/profile/ProfileCard.tsx | 138 | ||||
-rw-r--r-- | src/view/com/profile/ProfileFollowers.tsx | 68 | ||||
-rw-r--r-- | src/view/com/profile/ProfileFollows.tsx | 75 | ||||
-rw-r--r-- | src/view/com/profile/ProfileHeader.tsx | 57 |
4 files changed, 182 insertions, 156 deletions
diff --git a/src/view/com/profile/ProfileCard.tsx b/src/view/com/profile/ProfileCard.tsx index 0cda3ba2a..2f93e59e6 100644 --- a/src/view/com/profile/ProfileCard.tsx +++ b/src/view/com/profile/ProfileCard.tsx @@ -1,23 +1,29 @@ import React from 'react' import {StyleSheet, TouchableOpacity, View} from 'react-native' +import {observer} from 'mobx-react-lite' import {Link} from '../util/Link' import {Text} from '../util/text/Text' import {UserAvatar} from '../util/UserAvatar' -import {s} from '../../lib/styles' -import {usePalette} from '../../lib/hooks/usePalette' +import * as Toast from '../util/Toast' +import {s} from 'lib/styles' +import {usePalette} from 'lib/hooks/usePalette' +import {useStores} from 'state/index' +import * as apilib from 'lib/api/index' export function ProfileCard({ handle, displayName, avatar, + description, + isFollowedBy, renderButton, - onPressButton, }: { handle: string displayName?: string avatar?: string + description?: string + isFollowedBy?: boolean renderButton?: () => JSX.Element - onPressButton?: () => void }) { const pal = usePalette('default') return ( @@ -36,30 +42,117 @@ export function ProfileCard({ /> </View> <View style={styles.layoutContent}> - <Text style={[s.bold, pal.text]} numberOfLines={1}> + <Text type="lg" style={[s.bold, pal.text]} numberOfLines={1}> {displayName || handle} </Text> - <Text type="sm" style={[pal.textLight]} numberOfLines={1}> + <Text type="md" style={[pal.textLight]} numberOfLines={1}> @{handle} </Text> + {isFollowedBy && ( + <View style={s.flexRow}> + <View style={[s.mt5, pal.btn, styles.pill]}> + <Text type="xs">Follows You</Text> + </View> + </View> + )} </View> {renderButton ? ( - <View style={styles.layoutButton}> - <TouchableOpacity - onPress={onPressButton} - style={[styles.btn, pal.btn]}> - {renderButton()} - </TouchableOpacity> - </View> + <View style={styles.layoutButton}>{renderButton()}</View> ) : undefined} </View> + {description ? ( + <View style={styles.details}> + <Text style={pal.text} numberOfLines={4}> + {description} + </Text> + </View> + ) : undefined} </Link> ) } +export const ProfileCardWithFollowBtn = observer( + ({ + did, + declarationCid, + handle, + displayName, + avatar, + description, + isFollowedBy, + }: { + did: string + declarationCid: string + handle: string + displayName?: string + avatar?: string + description?: string + isFollowedBy?: boolean + }) => { + const store = useStores() + const isMe = store.me.handle === handle + const isFollowing = store.me.follows.isFollowing(did) + const onToggleFollow = async () => { + if (store.me.follows.isFollowing(did)) { + try { + await apilib.unfollow(store, store.me.follows.getFollowUri(did)) + store.me.follows.removeFollow(did) + } catch (e: any) { + store.log.error('Failed fo delete follow', e) + Toast.show('An issue occurred, please try again.') + } + } else { + try { + const res = await apilib.follow(store, did, declarationCid) + store.me.follows.addFollow(did, res.uri) + } catch (e: any) { + store.log.error('Failed fo create follow', e) + Toast.show('An issue occurred, please try again.') + } + } + } + return ( + <ProfileCard + handle={handle} + displayName={displayName} + avatar={avatar} + description={description} + isFollowedBy={isFollowedBy} + renderButton={ + isMe + ? undefined + : () => ( + <FollowBtn isFollowing={isFollowing} onPress={onToggleFollow} /> + ) + } + /> + ) + }, +) + +function FollowBtn({ + isFollowing, + onPress, +}: { + isFollowing: boolean + onPress: () => void +}) { + const pal = usePalette('default') + return ( + <TouchableOpacity onPress={onPress}> + <View style={[styles.btn, pal.btn]}> + <Text type="button" style={[pal.text]}> + {isFollowing ? 'Unfollow' : 'Follow'} + </Text> + </View> + </TouchableOpacity> + ) +} + const styles = StyleSheet.create({ outer: { borderTopWidth: 1, + paddingHorizontal: 6, }, layout: { flexDirection: 'row', @@ -68,7 +161,7 @@ const styles = StyleSheet.create({ layoutAvi: { width: 60, paddingLeft: 10, - paddingTop: 10, + paddingTop: 8, paddingBottom: 10, }, avi: { @@ -80,19 +173,26 @@ const styles = StyleSheet.create({ layoutContent: { flex: 1, paddingRight: 10, - paddingTop: 12, + paddingTop: 10, paddingBottom: 10, }, layoutButton: { paddingRight: 10, }, + details: { + paddingLeft: 60, + paddingRight: 10, + paddingBottom: 10, + }, + pill: { + borderRadius: 4, + paddingHorizontal: 6, + paddingVertical: 2, + }, btn: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', paddingVertical: 7, - paddingHorizontal: 14, borderRadius: 50, marginLeft: 6, + paddingHorizontal: 14, }, }) diff --git a/src/view/com/profile/ProfileFollowers.tsx b/src/view/com/profile/ProfileFollowers.tsx index 13d134c39..7db770e4b 100644 --- a/src/view/com/profile/ProfileFollowers.tsx +++ b/src/view/com/profile/ProfileFollowers.tsx @@ -4,15 +4,11 @@ import {ActivityIndicator, StyleSheet, View} from 'react-native' import { UserFollowersViewModel, FollowerItem, -} from '../../../state/models/user-followers-view' +} from 'state/models/user-followers-view' import {CenteredView, FlatList} from '../util/Views' -import {Link} from '../util/Link' -import {Text} from '../util/text/Text' import {ErrorMessage} from '../util/error/ErrorMessage' -import {UserAvatar} from '../util/UserAvatar' -import {useStores} from '../../../state' -import {s} from '../../lib/styles' -import {usePalette} from '../../lib/hooks/usePalette' +import {ProfileCardWithFollowBtn} from './ProfileCard' +import {useStores} from 'state/index' export const ProfileFollowers = observer(function ProfileFollowers({ name, @@ -63,7 +59,15 @@ export const ProfileFollowers = observer(function ProfileFollowers({ // loaded // = const renderItem = ({item}: {item: FollowerItem}) => ( - <User key={item.did} item={item} /> + <ProfileCardWithFollowBtn + key={item.did} + did={item.did} + declarationCid={item.declaration.cid} + handle={item.handle} + displayName={item.displayName} + avatar={item.avatar} + isFollowedBy={!!item.viewer?.followedBy} + /> ) return ( <FlatList @@ -84,55 +88,7 @@ export const ProfileFollowers = observer(function ProfileFollowers({ ) }) -const User = ({item}: {item: FollowerItem}) => { - const pal = usePalette('default') - return ( - <Link - style={[styles.outer, pal.view, pal.border]} - href={`/profile/${item.handle}`} - title={item.handle} - noFeedback> - <View style={styles.layout}> - <View style={styles.layoutAvi}> - <UserAvatar - size={40} - displayName={item.displayName} - handle={item.handle} - avatar={item.avatar} - /> - </View> - <View style={styles.layoutContent}> - <Text style={[s.bold, pal.text]}> - {item.displayName || item.handle} - </Text> - <Text type="sm" style={[pal.textLight]}> - @{item.handle} - </Text> - </View> - </View> - </Link> - ) -} - const styles = StyleSheet.create({ - outer: { - borderTopWidth: 1, - }, - layout: { - flexDirection: 'row', - }, - layoutAvi: { - width: 60, - paddingLeft: 10, - paddingTop: 10, - paddingBottom: 10, - }, - layoutContent: { - flex: 1, - paddingRight: 10, - paddingTop: 10, - paddingBottom: 10, - }, footer: { height: 200, paddingTop: 20, diff --git a/src/view/com/profile/ProfileFollows.tsx b/src/view/com/profile/ProfileFollows.tsx index de2fe3324..fb7f08ed8 100644 --- a/src/view/com/profile/ProfileFollows.tsx +++ b/src/view/com/profile/ProfileFollows.tsx @@ -2,17 +2,10 @@ import React, {useEffect} from 'react' import {observer} from 'mobx-react-lite' import {ActivityIndicator, StyleSheet, View} from 'react-native' import {CenteredView, FlatList} from '../util/Views' -import { - UserFollowsViewModel, - FollowItem, -} from '../../../state/models/user-follows-view' -import {Link} from '../util/Link' -import {Text} from '../util/text/Text' +import {UserFollowsViewModel, FollowItem} from 'state/models/user-follows-view' import {ErrorMessage} from '../util/error/ErrorMessage' -import {UserAvatar} from '../util/UserAvatar' -import {useStores} from '../../../state' -import {s} from '../../lib/styles' -import {usePalette} from '../../lib/hooks/usePalette' +import {ProfileCardWithFollowBtn} from './ProfileCard' +import {useStores} from 'state/index' export const ProfileFollows = observer(function ProfileFollows({ name, @@ -63,7 +56,15 @@ export const ProfileFollows = observer(function ProfileFollows({ // loaded // = const renderItem = ({item}: {item: FollowItem}) => ( - <User key={item.did} item={item} /> + <ProfileCardWithFollowBtn + key={item.did} + did={item.did} + declarationCid={item.declaration.cid} + handle={item.handle} + displayName={item.displayName} + avatar={item.avatar} + isFollowedBy={!!item.viewer?.followedBy} + /> ) return ( <FlatList @@ -84,59 +85,7 @@ export const ProfileFollows = observer(function ProfileFollows({ ) }) -const User = ({item}: {item: FollowItem}) => { - const pal = usePalette('default') - return ( - <Link - style={[styles.outer, pal.view, pal.border]} - href={`/profile/${item.handle}`} - title={item.handle} - noFeedback> - <View style={styles.layout}> - <View style={styles.layoutAvi}> - <UserAvatar - size={40} - displayName={item.displayName} - handle={item.handle} - avatar={ - item.avatar as - | string - | undefined /* HACK: type signature is wrong in the api */ - } - /> - </View> - <View style={styles.layoutContent}> - <Text style={[s.bold, pal.text]}> - {item.displayName || item.handle} - </Text> - <Text type="sm" style={[pal.textLight]}> - @{item.handle} - </Text> - </View> - </View> - </Link> - ) -} - const styles = StyleSheet.create({ - outer: { - borderTopWidth: 1, - }, - layout: { - flexDirection: 'row', - }, - layoutAvi: { - width: 60, - paddingLeft: 10, - paddingTop: 10, - paddingBottom: 10, - }, - layoutContent: { - flex: 1, - paddingRight: 10, - paddingTop: 10, - paddingBottom: 10, - }, footer: { height: 200, paddingTop: 20, diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx index cebeea788..0ca6e1e74 100644 --- a/src/view/com/profile/ProfileHeader.tsx +++ b/src/view/com/profile/ProfileHeader.tsx @@ -13,15 +13,16 @@ import { FontAwesomeIconStyle, } from '@fortawesome/react-native-fontawesome' import {BlurView} from '../util/BlurView' -import {ProfileViewModel} from '../../../state/models/profile-view' -import {useStores} from '../../../state' +import {ProfileViewModel} from 'state/models/profile-view' +import {useStores} from 'state/index' import { EditProfileModal, ReportAccountModal, ProfileImageLightbox, -} from '../../../state/models/shell-ui' -import {pluralize, toShareUrl} from '../../../lib/strings' -import {s, gradients} from '../../lib/styles' +} from 'state/models/shell-ui' +import {pluralize} from 'lib/strings/helpers' +import {toShareUrl} from 'lib/strings/url-helpers' +import {s, gradients} from 'lib/styles' import {DropdownButton, DropdownItem} from '../util/forms/DropdownButton' import * as Toast from '../util/Toast' import {LoadingPlaceholder} from '../util/LoadingPlaceholder' @@ -29,7 +30,8 @@ import {Text} from '../util/text/Text' import {RichText} from '../util/text/RichText' import {UserAvatar} from '../util/UserAvatar' import {UserBanner} from '../util/UserBanner' -import {usePalette} from '../../lib/hooks/usePalette' +import {usePalette} from 'lib/hooks/usePalette' +import {useAnalytics} from 'lib/analytics' export const ProfileHeader = observer(function ProfileHeader({ view, @@ -40,7 +42,7 @@ export const ProfileHeader = observer(function ProfileHeader({ }) { const pal = usePalette('default') const store = useStores() - + const {track} = useAnalytics() const onPressBack = () => { store.nav.tab.goBack() } @@ -53,7 +55,7 @@ export const ProfileHeader = observer(function ProfileHeader({ view?.toggleFollowing().then( () => { Toast.show( - `${view.myState.follow ? 'Following' : 'No longer following'} ${ + `${view.viewer.following ? 'Following' : 'No longer following'} ${ view.displayName || view.handle }`, ) @@ -62,18 +64,23 @@ export const ProfileHeader = observer(function ProfileHeader({ ) } const onPressEditProfile = () => { + track('ProfileHeader:EditProfileButtonClicked') store.shell.openModal(new EditProfileModal(view, onRefreshAll)) } const onPressFollowers = () => { + track('ProfileHeader:FollowersButtonClicked') store.nav.navigate(`/profile/${view.handle}/followers`) } const onPressFollows = () => { + track('ProfileHeader:FollowsButtonClicked') store.nav.navigate(`/profile/${view.handle}/follows`) } const onPressShare = () => { + track('ProfileHeader:ShareButtonClicked') Share.share({url: toShareUrl(`/profile/${view.handle}`)}) } const onPressMuteAccount = async () => { + track('ProfileHeader:MuteAccountButtonClicked') try { await view.muteAccount() Toast.show('Account muted') @@ -83,6 +90,7 @@ export const ProfileHeader = observer(function ProfileHeader({ } } const onPressUnmuteAccount = async () => { + track('ProfileHeader:UnmuteAccountButtonClicked') try { await view.unmuteAccount() Toast.show('Account unmuted') @@ -92,6 +100,7 @@ export const ProfileHeader = observer(function ProfileHeader({ } } const onPressReportAccount = () => { + track('ProfileHeader:ReportAccountButtonClicked') store.shell.openModal(new ReportAccountModal(view.did)) } @@ -110,7 +119,7 @@ export const ProfileHeader = observer(function ProfileHeader({ <LoadingPlaceholder width={100} height={31} style={styles.br50} /> </View> <View style={styles.displayNameLine}> - <Text type="title-xl" style={[pal.text, styles.title]}> + <Text type="title-2xl" style={[pal.text, styles.title]}> {view.displayName || view.handle} </Text> </View> @@ -135,8 +144,8 @@ export const ProfileHeader = observer(function ProfileHeader({ let dropdownItems: DropdownItem[] = [{label: 'Share', onPress: onPressShare}] if (!isMe) { dropdownItems.push({ - label: view.myState.muted ? 'Unmute Account' : 'Mute Account', - onPress: view.myState.muted ? onPressUnmuteAccount : onPressMuteAccount, + label: view.viewer.muted ? 'Unmute Account' : 'Mute Account', + onPress: view.viewer.muted ? onPressUnmuteAccount : onPressMuteAccount, }) dropdownItems.push({ label: 'Report Account', @@ -159,7 +168,7 @@ export const ProfileHeader = observer(function ProfileHeader({ </TouchableOpacity> ) : ( <> - {view.myState.follow ? ( + {store.me.follows.isFollowing(view.did) ? ( <TouchableOpacity onPress={onPressToggleFollow} style={[styles.btn, styles.mainBtn, pal.btn]}> @@ -206,11 +215,18 @@ export const ProfileHeader = observer(function ProfileHeader({ ) : undefined} </View> <View style={styles.displayNameLine}> - <Text type="title-xl" style={[pal.text, styles.title]}> + <Text type="title-2xl" style={[pal.text, styles.title]}> {view.displayName || view.handle} </Text> </View> <View style={styles.handleLine}> + {view.viewer.followedBy ? ( + <View style={[styles.pill, pal.btn, s.mr5]}> + <Text type="xs" style={[pal.text]}> + Follows you + </Text> + </View> + ) : undefined} <Text style={pal.textLight}>@{view.handle}</Text> </View> <View style={styles.metricsLine}> @@ -247,22 +263,21 @@ export const ProfileHeader = observer(function ProfileHeader({ </Text> </View> </View> - {view.description ? ( + {view.descriptionRichText ? ( <RichText style={[styles.description, pal.text]} numberOfLines={15} - text={view.description} - entities={view.descriptionEntities} + richText={view.descriptionRichText} /> ) : undefined} - {view.myState.muted ? ( + {view.viewer.muted ? ( <View style={[styles.detailLine, pal.btn, s.p5]}> <FontAwesomeIcon icon={['far', 'eye-slash']} style={[pal.text, s.mr5]} /> <Text type="md" style={[s.mr2, pal.text]}> - Account muted. + Account muted </Text> </View> ) : undefined} @@ -369,6 +384,12 @@ const styles = StyleSheet.create({ marginBottom: 5, }, + pill: { + borderRadius: 4, + paddingHorizontal: 6, + paddingVertical: 2, + }, + br40: {borderRadius: 40}, br50: {borderRadius: 50}, }) |