From 60b1c53d8571dfcb0b60e530e67ca311da82370a Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Sat, 5 Nov 2022 11:58:48 -0500 Subject: Add actor types to the profiles and clean up the UI --- src/state/models/profile-ui.ts | 41 ++++++-- src/state/models/profile-view.ts | 12 +++ src/view/com/profile/ProfileHeader.tsx | 168 ++++++++++++++++++++------------- src/view/screens/Profile.tsx | 72 +++++++------- 4 files changed, 185 insertions(+), 108 deletions(-) (limited to 'src') 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.displayName} - - { - undefined /* - - - - Employee @blueskyweb.xyz - - - */ - } {isMe ? ( + style={[styles.btn, styles.mainBtn]}> Edit Profile ) : ( @@ -127,28 +114,22 @@ export const ProfileHeader = observer(function ProfileHeader({ {view.myState.follow ? ( + style={[styles.btn, styles.mainBtn]}> Following ) : ( - - - Follow + + + + Follow + )} - - - - - - )} - + + {view.displayName} + + + {view.isScene ? ( + + Scene + + ) : undefined} + @{view.handle} + + @@ -166,20 +158,42 @@ export const ProfileHeader = observer(function ProfileHeader({ {pluralize(view.followersCount, 'follower')} - - {view.followsCount} - following - + {view.isUser ? ( + + {view.followsCount} + following + + ) : undefined} + {view.isScene ? ( + + {view.followsCount} + + {pluralize(view.followsCount, 'member')} + + + ) : undefined} {view.postsCount} {pluralize(view.postsCount, 'post')} {view.description && ( - {view.description} + {view.description} )} + { + undefined /* + + + + Employee @blueskyweb.xyz + + + */ + } ) @@ -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(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 } - return + return } 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 = () => Loading... - } 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) => ( @@ -95,9 +96,9 @@ export const Profile = observer(({visible, params}: ScreenParams) => { /> ) - } 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 } - } else if (profileUiState.currentView.isEmpty) { + } else if (uiState.currentView.isEmpty) { items.push(EMPTY_ITEM) renderItem = () => No posts yet! } - } - if (profileUiState.selectedViewIndex === SECTION_IDS.BADGES) { + } else { items.push(EMPTY_ITEM) renderItem = () => TODO } @@ -122,20 +122,20 @@ export const Profile = observer(({visible, params}: ScreenParams) => { return ( - {profileUiState?.profile.hasError ? ( + {uiState.profile.hasError ? ( ) : (