diff --git a/src/state/models/profile-ui.ts b/src/state/models/profile-ui.ts index 830dc22b..0ad893dd 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 ebb75bdb..09f1991e 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 e1b46f4c..d492aa1f 100644 --- a/src/view/com/profile/ProfileHeader.tsx +++ b/src/view/com/profile/ProfileHeader.tsx @@ -102,9 +102,88 @@ export const ProfileHeader = observer(function ProfileHeader({ /> - + + {isMe ? ( + + Edit Profile + + ) : ( + <> + {view.myState.follow ? ( + + + Following + + ) : ( + + + + Follow + + + )} + + )} + + + + + {view.displayName} + + {view.isScene ? ( + + Scene + + ) : undefined} + @{view.handle} + + + + {view.followersCount} + + {pluralize(view.followersCount, 'follower')} + + + {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} + )} { undefined /* @@ -115,71 +194,6 @@ export const ProfileHeader = observer(function ProfileHeader({ */ } - - {isMe ? ( - - Edit Profile - - ) : ( - <> - {view.myState.follow ? ( - - - Following - - ) : ( - - - Follow - - )} - - - - - - - - )} - - - - - - - {view.followersCount} - - {pluralize(view.followersCount, 'follower')} - - - - {view.followsCount} - following - - - {view.postsCount} - {pluralize(view.postsCount, 'post')} - - - {view.description && ( - {view.description} - )} ) @@ -222,46 +236,70 @@ const styles = StyleSheet.create({ paddingHorizontal: 14, paddingBottom: 4, }, + + buttonsLine: { + flexDirection: 'row', + marginLeft: 'auto', + marginBottom: 12, + }, + gradientBtn: { + paddingHorizontal: 24, + paddingVertical: 6, + }, + mainBtn: { + paddingHorizontal: 24, + }, + secondaryBtn: { + paddingHorizontal: 14, + }, + btn: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 7, + borderRadius: 50, + backgroundColor: colors.gray1, + marginLeft: 6, + }, + displayNameLine: { - paddingLeft: 86, - marginBottom: 14, + // paddingLeft: 86, + // marginBottom: 14, }, displayName: { fontSize: 24, fontWeight: 'bold', }, + + handleLine: { + flexDirection: 'row', + marginBottom: 8, + }, + 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, }, - buttonsLine: { - flexDirection: 'row', - marginBottom: 12, - }, - followBtn: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - paddingVertical: 6, - borderRadius: 6, - marginRight: 6, - }, - btn: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - paddingVertical: 7, - borderRadius: 4, - backgroundColor: colors.gray1, - marginRight: 6, - }, - mainBtn: { - flexDirection: 'row', - }, - secondaryBtn: { - flex: 0, - paddingHorizontal: 14, - marginRight: 0, - }, }) diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index f5f4f553..6f7281bd 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 ? ( ) : (