diff options
author | Paul Frazee <pfrazee@gmail.com> | 2022-11-05 11:58:48 -0500 |
---|---|---|
committer | Paul Frazee <pfrazee@gmail.com> | 2022-11-05 11:58:48 -0500 |
commit | 60b1c53d8571dfcb0b60e530e67ca311da82370a (patch) | |
tree | 853293eb23e2433572bbfbaba1783b46b3fc1084 | |
parent | 3f730f1173a3c27a6bd54b91f48ecb9220a42730 (diff) | |
download | voidsky-60b1c53d8571dfcb0b60e530e67ca311da82370a.tar.zst |
Add actor types to the profiles and clean up the UI
-rw-r--r-- | src/state/models/profile-ui.ts | 41 | ||||
-rw-r--r-- | src/state/models/profile-view.ts | 12 | ||||
-rw-r--r-- | src/view/com/profile/ProfileHeader.tsx | 168 | ||||
-rw-r--r-- | src/view/screens/Profile.tsx | 72 |
4 files changed, 185 insertions, 108 deletions
diff --git a/src/state/models/profile-ui.ts b/src/state/models/profile-ui.ts index 830dc22b1..0ad893dd1 100644 --- a/src/state/models/profile-ui.ts +++ b/src/state/models/profile-ui.ts @@ -3,19 +3,21 @@ import {RootStoreModel} from './root-store' import {ProfileViewModel} from './profile-view' import {FeedModel} from './feed-view' -export const SECTION_IDS = { - POSTS: 0, - BADGES: 1, +export enum Sections { + Posts = 'Posts', + Scenes = 'Scenes', + Trending = 'Trending', + Members = 'Members', } +const USER_SELECTOR_ITEMS = [Sections.Posts, Sections.Scenes] +const SCENE_SELECTOR_ITEMS = [Sections.Trending, Sections.Members] + export interface ProfileUiParams { user: string } export class ProfileUiModel { - // constants - static SELECTOR_ITEMS = ['Posts', 'Scenes'] - // data profile: ProfileViewModel feed: FeedModel @@ -43,7 +45,10 @@ export class ProfileUiModel { } get currentView(): FeedModel { - if (this.selectedViewIndex === SECTION_IDS.POSTS) { + if ( + this.selectedView === Sections.Posts || + this.selectedView === Sections.Trending + ) { return this.feed } throw new Error(`Invalid selector value: ${this.selectedViewIndex}`) @@ -58,6 +63,28 @@ export class ProfileUiModel { return this.profile.isRefreshing || this.currentView.isRefreshing } + get isUser() { + return this.profile.isUser + } + + get isScene() { + return this.profile.isScene + } + + get selectorItems() { + if (this.isUser) { + return USER_SELECTOR_ITEMS + } else if (this.isScene) { + return SCENE_SELECTOR_ITEMS + } else { + return USER_SELECTOR_ITEMS + } + } + + get selectedView() { + return this.selectorItems[this.selectedViewIndex] + } + // public api // = diff --git a/src/state/models/profile-view.ts b/src/state/models/profile-view.ts index ebb75bdb6..09f1991e1 100644 --- a/src/state/models/profile-view.ts +++ b/src/state/models/profile-view.ts @@ -4,6 +4,9 @@ import * as Profile from '../../third-party/api/src/client/types/app/bsky/actor/ import {RootStoreModel} from './root-store' import * as apilib from '../lib/api' +export const ACTOR_TYPE_USER = 'app.bsky.system.actorUser' +export const ACTOR_TYPE_SCENE = 'app.bsky.system.actorScene' + export class ProfileViewMyStateModel { follow?: string @@ -23,6 +26,7 @@ export class ProfileViewModel { // data did: string = '' handle: string = '' + actorType = ACTOR_TYPE_USER displayName?: string description?: string followersCount: number = 0 @@ -57,6 +61,14 @@ export class ProfileViewModel { return this.hasLoaded && !this.hasContent } + get isUser() { + return this.actorType === ACTOR_TYPE_USER + } + + get isScene() { + return this.actorType === ACTOR_TYPE_SCENE + } + // public api // = diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx index e1b46f4c9..d492aa1f3 100644 --- a/src/view/com/profile/ProfileHeader.tsx +++ b/src/view/com/profile/ProfileHeader.tsx @@ -102,24 +102,11 @@ export const ProfileHeader = observer(function ProfileHeader({ /> </View> <View style={styles.content}> - <View style={[styles.displayNameLine]}> - <Text style={styles.displayName}>{view.displayName}</Text> - </View> - { - undefined /*<View style={styles.badgesLine}> - <FontAwesomeIcon icon="shield" style={s.mr5} size={12} /> - <Link href="/" title="Badge TODO"> - <Text style={[s.f12, s.bold]}> - Employee <Text style={[s.blue3]}>@blueskyweb.xyz</Text> - </Text> - </Link> - </View>*/ - } <View style={[styles.buttonsLine]}> {isMe ? ( <TouchableOpacity onPress={onPressEditProfile} - style={[styles.mainBtn, styles.btn]}> + style={[styles.btn, styles.mainBtn]}> <Text style={[s.fw400, s.f14]}>Edit Profile</Text> </TouchableOpacity> ) : ( @@ -127,28 +114,22 @@ export const ProfileHeader = observer(function ProfileHeader({ {view.myState.follow ? ( <TouchableOpacity onPress={onPressToggleFollow} - style={[styles.mainBtn, styles.btn]}> + style={[styles.btn, styles.mainBtn]}> <FontAwesomeIcon icon="check" style={[s.mr5]} size={14} /> <Text style={[s.fw400, s.f14]}>Following</Text> </TouchableOpacity> ) : ( - <TouchableOpacity - onPress={onPressToggleFollow} - style={[styles.mainBtn, styles.btn]}> - <FontAwesomeIcon icon="rss" style={[s.mr5]} size={13} /> - <Text style={[s.fw400, s.f14]}>Follow</Text> + <TouchableOpacity onPress={onPressToggleFollow}> + <LinearGradient + colors={[gradients.primary.start, gradients.primary.end]} + start={{x: 0, y: 0}} + end={{x: 1, y: 1}} + style={[styles.btn, styles.gradientBtn]}> + <FontAwesomeIcon icon="plus" style={[s.white, s.mr5]} /> + <Text style={[s.white, s.fw600, s.f16]}>Follow</Text> + </LinearGradient> </TouchableOpacity> )} - <TouchableOpacity - onPress={onPressMenu} - style={[styles.btn, styles.secondaryBtn, s.mr5]}> - <FontAwesomeIcon icon="user-plus" style={[s.gray5]} /> - </TouchableOpacity> - <TouchableOpacity - onPress={onPressMenu} - style={[styles.btn, styles.secondaryBtn, s.mr5]}> - <FontAwesomeIcon icon="note-sticky" style={[s.gray5]} /> - </TouchableOpacity> </> )} <TouchableOpacity @@ -157,7 +138,18 @@ export const ProfileHeader = observer(function ProfileHeader({ <FontAwesomeIcon icon="ellipsis" style={[s.gray5]} /> </TouchableOpacity> </View> - <View style={[s.flexRow]}> + <View style={styles.displayNameLine}> + <Text style={styles.displayName}>{view.displayName}</Text> + </View> + <View style={styles.handleLine}> + {view.isScene ? ( + <View style={styles.typeLabelWrapper}> + <Text style={styles.typeLabel}>Scene</Text> + </View> + ) : undefined} + <Text style={styles.handle}>@{view.handle}</Text> + </View> + <View style={styles.metricsLine}> <TouchableOpacity style={[s.flexRow, s.mr10]} onPress={onPressFollowers}> @@ -166,20 +158,42 @@ export const ProfileHeader = observer(function ProfileHeader({ {pluralize(view.followersCount, 'follower')} </Text> </TouchableOpacity> - <TouchableOpacity - style={[s.flexRow, s.mr10]} - onPress={onPressFollows}> - <Text style={[s.bold, s.mr2]}>{view.followsCount}</Text> - <Text style={s.gray5}>following</Text> - </TouchableOpacity> + {view.isUser ? ( + <TouchableOpacity + style={[s.flexRow, s.mr10]} + onPress={onPressFollows}> + <Text style={[s.bold, s.mr2]}>{view.followsCount}</Text> + <Text style={s.gray5}>following</Text> + </TouchableOpacity> + ) : undefined} + {view.isScene ? ( + <TouchableOpacity + style={[s.flexRow, s.mr10]} + onPress={onPressFollows}> + <Text style={[s.bold, s.mr2]}>{view.followsCount}</Text> + <Text style={s.gray5}> + {pluralize(view.followsCount, 'member')} + </Text> + </TouchableOpacity> + ) : undefined} <View style={[s.flexRow, s.mr10]}> <Text style={[s.bold, s.mr2]}>{view.postsCount}</Text> <Text style={s.gray5}>{pluralize(view.postsCount, 'post')}</Text> </View> </View> {view.description && ( - <Text style={[s.mt10, s.f15, s['lh15-1.3']]}>{view.description}</Text> + <Text style={[s.mb5, s.f15, s['lh15-1.3']]}>{view.description}</Text> )} + { + undefined /*<View style={styles.badgesLine}> + <FontAwesomeIcon icon="shield" style={s.mr5} size={12} /> + <Link href="/" title="Badge TODO"> + <Text style={[s.f12, s.bold]}> + Employee <Text style={[s.blue3]}>@blueskyweb.xyz</Text> + </Text> + </Link> + </View>*/ + } </View> </View> ) @@ -222,46 +236,70 @@ const styles = StyleSheet.create({ paddingHorizontal: 14, paddingBottom: 4, }, - displayNameLine: { - paddingLeft: 86, - marginBottom: 14, - }, - displayName: { - fontSize: 24, - fontWeight: 'bold', - }, - badgesLine: { - flexDirection: 'row', - alignItems: 'center', - marginBottom: 10, - }, + buttonsLine: { flexDirection: 'row', + marginLeft: 'auto', marginBottom: 12, }, - followBtn: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', + gradientBtn: { + paddingHorizontal: 24, paddingVertical: 6, - borderRadius: 6, - marginRight: 6, + }, + mainBtn: { + paddingHorizontal: 24, + }, + secondaryBtn: { + paddingHorizontal: 14, }, btn: { - flex: 1, + flexDirection: 'row', alignItems: 'center', justifyContent: 'center', paddingVertical: 7, - borderRadius: 4, + borderRadius: 50, backgroundColor: colors.gray1, - marginRight: 6, + marginLeft: 6, }, - mainBtn: { + + displayNameLine: { + // paddingLeft: 86, + // marginBottom: 14, + }, + displayName: { + fontSize: 24, + fontWeight: 'bold', + }, + + handleLine: { flexDirection: 'row', + marginBottom: 8, }, - secondaryBtn: { - flex: 0, - paddingHorizontal: 14, - marginRight: 0, + handle: { + fontSize: 14, + fontWeight: 'bold', + color: colors.gray5, + }, + typeLabelWrapper: { + backgroundColor: colors.gray1, + paddingHorizontal: 4, + borderRadius: 4, + marginRight: 5, + }, + typeLabel: { + fontSize: 14, + fontWeight: 'bold', + color: colors.gray5, + }, + + metricsLine: { + flexDirection: 'row', + marginBottom: 8, + }, + + badgesLine: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: 10, }, }) diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index f5f4f553e..6f7281bd9 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -1,9 +1,9 @@ -import React, {useEffect, useState} from 'react' +import React, {useEffect, useState, useMemo} from 'react' import {StyleSheet, Text, View} from 'react-native' import {observer} from 'mobx-react-lite' import {ViewSelector} from '../com/util/ViewSelector' import {ScreenParams} from '../routes' -import {ProfileUiModel, SECTION_IDS} from '../../state/models/profile-ui' +import {ProfileUiModel, Sections} from '../../state/models/profile-ui' import {useStores} from '../../state' import {ProfileHeader} from '../com/profile/ProfileHeader' import {FeedItem} from '../com/posts/FeedItem' @@ -18,25 +18,23 @@ const EMPTY_ITEM = {_reactKey: '__empty__'} export const Profile = observer(({visible, params}: ScreenParams) => { const store = useStores() const [hasSetup, setHasSetup] = useState<boolean>(false) - const [profileUiState, setProfileUiState] = useState< - ProfileUiModel | undefined - >() + const uiState = useMemo( + () => new ProfileUiModel(store, {user: params.name}), + [params.user], + ) useEffect(() => { let aborted = false if (!visible) { return } - const user = params.name if (hasSetup) { - console.log('Updating profile for', user) - profileUiState?.update() + console.log('Updating profile for', params.name) + uiState.update() } else { - console.log('Fetching profile for', user) - store.nav.setTitle(user) - const newProfileUiState = new ProfileUiModel(store, {user}) - setProfileUiState(newProfileUiState) - newProfileUiState.setup().then(() => { + console.log('Fetching profile for', params.name) + store.nav.setTitle(params.name) + uiState.setup().then(() => { if (aborted) return setHasSetup(true) }) @@ -50,42 +48,45 @@ export const Profile = observer(({visible, params}: ScreenParams) => { // = const onSelectView = (index: number) => { - profileUiState?.setSelectedViewIndex(index) + uiState.setSelectedViewIndex(index) } const onRefresh = () => { - profileUiState - ?.refresh() + uiState + .refresh() .catch((err: any) => console.error('Failed to refresh', err)) } const onEndReached = () => { - profileUiState - ?.loadMore() + uiState + .loadMore() .catch((err: any) => console.error('Failed to load more', err)) } const onPressTryAgain = () => { - profileUiState?.setup() + uiState.setup() } // rendering // = const renderHeader = () => { - if (!profileUiState) { + if (!uiState) { return <View /> } - return <ProfileHeader view={profileUiState.profile} /> + return <ProfileHeader view={uiState.profile} /> } let renderItem let items: any[] = [] - if (profileUiState) { - if (profileUiState.selectedViewIndex === SECTION_IDS.POSTS) { - if (profileUiState.isInitialLoading) { + if (uiState) { + if ( + uiState.selectedView === Sections.Posts || + uiState.selectedView === Sections.Trending + ) { + if (uiState.isInitialLoading) { items.push(LOADING_ITEM) renderItem = () => <Text style={styles.loading}>Loading...</Text> - } else if (profileUiState.feed.hasError) { + } else if (uiState.feed.hasError) { items.push({ _reactKey: '__error__', - error: profileUiState.feed.error, + error: uiState.feed.error, }) renderItem = (item: any) => ( <View style={s.p5}> @@ -95,9 +96,9 @@ export const Profile = observer(({visible, params}: ScreenParams) => { /> </View> ) - } else if (profileUiState.currentView.hasContent) { - items = profileUiState.feed.feed.slice() - if (profileUiState.feed.hasReachedEnd) { + } else if (uiState.currentView.hasContent) { + items = uiState.feed.feed.slice() + if (uiState.feed.hasReachedEnd) { items.push(END_ITEM) } renderItem = (item: any) => { @@ -106,12 +107,11 @@ export const Profile = observer(({visible, params}: ScreenParams) => { } return <FeedItem item={item} /> } - } else if (profileUiState.currentView.isEmpty) { + } else if (uiState.currentView.isEmpty) { items.push(EMPTY_ITEM) renderItem = () => <Text style={styles.loading}>No posts yet!</Text> } - } - if (profileUiState.selectedViewIndex === SECTION_IDS.BADGES) { + } else { items.push(EMPTY_ITEM) renderItem = () => <Text>TODO</Text> } @@ -122,20 +122,20 @@ export const Profile = observer(({visible, params}: ScreenParams) => { return ( <View style={styles.container}> - {profileUiState?.profile.hasError ? ( + {uiState.profile.hasError ? ( <ErrorScreen title="Failed to load profile" message={`There was an issue when attempting to load ${params.name}`} - details={profileUiState.profile.error} + details={uiState.profile.error} onPressTryAgain={onPressTryAgain} /> ) : ( <ViewSelector - sections={ProfileUiModel.SELECTOR_ITEMS} + sections={uiState.selectorItems} items={items} renderHeader={renderHeader} renderItem={renderItem} - refreshing={profileUiState?.isRefreshing || false} + refreshing={uiState.isRefreshing || false} onSelectView={onSelectView} onRefresh={onRefresh} onEndReached={onEndReached} |