diff --git a/src/state/models/badges-view.ts b/src/state/models/badges-view.ts new file mode 100644 index 0000000000..644ec7d9e4 --- /dev/null +++ b/src/state/models/badges-view.ts @@ -0,0 +1,49 @@ +import {makeAutoObservable} from 'mobx' +import {RootStoreModel} from './root-store' + +// TODO / DEBUG +// this is a temporary fake for the model until the view actually gets implemented in the bsky api +// -prf + +export class BadgesViewModel { + // state + isLoading = false + isRefreshing = false + hasLoaded = false + error = '' + + constructor(public rootStore: RootStoreModel) { + makeAutoObservable( + this, + { + rootStore: false, + }, + {autoBind: true}, + ) + } + + get hasContent() { + return false + } + + get hasError() { + return this.error !== '' + } + + get isEmpty() { + return this.hasLoaded && !this.hasContent + } + + // public api + // = + + async setup() { + this.hasLoaded = true + } + + async refresh() {} + + async loadMore() {} + + async update() {} +} diff --git a/src/state/models/feed-view.ts b/src/state/models/feed-view.ts index e9405773c8..9ba96764bd 100644 --- a/src/state/models/feed-view.ts +++ b/src/state/models/feed-view.ts @@ -95,6 +95,7 @@ export class FeedViewModel implements bsky.FeedView.Response { isLoading = false isRefreshing = false hasLoaded = false + hasReachedEnd = false error = '' params: bsky.FeedView.Params _loadPromise: Promise | undefined @@ -244,7 +245,13 @@ export class FeedViewModel implements bsky.FeedView.Response { 'blueskyweb.xyz:FeedView', params, )) as bsky.FeedView.Response - this._appendAll(res) + if (res.feed.length === 0) { + runInAction(() => { + this.hasReachedEnd = true + }) + } else { + this._appendAll(res) + } this._xIdle() } catch (e: any) { this._xIdle(`Failed to load feed: ${e.toString()}`) @@ -281,6 +288,7 @@ export class FeedViewModel implements bsky.FeedView.Response { private _replaceAll(res: bsky.FeedView.Response) { this.feed.length = 0 + this.hasReachedEnd = false this._appendAll(res) } diff --git a/src/state/models/profile-ui.ts b/src/state/models/profile-ui.ts new file mode 100644 index 0000000000..98a087aeb1 --- /dev/null +++ b/src/state/models/profile-ui.ts @@ -0,0 +1,98 @@ +import {makeAutoObservable} from 'mobx' +import {RootStoreModel} from './root-store' +import {ProfileViewModel} from './profile-view' +import {FeedViewModel} from './feed-view' +import {BadgesViewModel} from './badges-view' + +export const SECTION_IDS = { + POSTS: 0, + BADGES: 1, +} + +export interface ProfileUiParams { + user: string +} + +export class ProfileUiModel { + // constants + static SELECTOR_ITEMS = ['Posts', 'Badges'] + + // data + profile: ProfileViewModel + feed: FeedViewModel + badges: BadgesViewModel + + // ui state + selectedViewIndex = 0 + + constructor( + public rootStore: RootStoreModel, + public params: ProfileUiParams, + ) { + makeAutoObservable( + this, + { + rootStore: false, + params: false, + }, + {autoBind: true}, + ) + this.profile = new ProfileViewModel(rootStore, {user: params.user}) + this.feed = new FeedViewModel(rootStore, {author: params.user, limit: 10}) + this.badges = new BadgesViewModel(rootStore) + } + + get currentView(): FeedViewModel | BadgesViewModel { + if (this.selectedViewIndex === SECTION_IDS.POSTS) { + return this.feed + } + if (this.selectedViewIndex === SECTION_IDS.BADGES) { + return this.badges + } + throw new Error(`Invalid selector value: ${this.selectedViewIndex}`) + } + + get isInitialLoading() { + const view = this.currentView + return view.isLoading && !view.isRefreshing && !view.hasContent + } + + get isRefreshing() { + return this.profile.isRefreshing || this.currentView.isRefreshing + } + + // public api + // = + + setSelectedViewIndex(index: number) { + this.selectedViewIndex = index + } + + async setup() { + await Promise.all([ + this.profile + .setup() + .catch(err => console.error('Failed to fetch profile', err)), + this.feed + .setup() + .catch(err => console.error('Failed to fetch feed', err)), + this.badges + .setup() + .catch(err => console.error('Failed to fetch badges', err)), + ]) + } + + async update() { + await this.currentView.update() + } + + async refresh() { + await Promise.all([this.profile.refresh(), this.currentView.refresh()]) + } + + async loadMore() { + if (!this.currentView.isLoading && !this.currentView.hasError) { + await this.currentView.loadMore() + } + } +} diff --git a/src/state/models/profile-view.ts b/src/state/models/profile-view.ts index b245335f1e..89c8a75d06 100644 --- a/src/state/models/profile-view.ts +++ b/src/state/models/profile-view.ts @@ -65,7 +65,7 @@ export class ProfileViewModel implements bsky.ProfileView.Response { } async refresh() { - await this._load() + await this._load(true) } async toggleFollowing() { @@ -108,8 +108,8 @@ export class ProfileViewModel implements bsky.ProfileView.Response { // loader functions // = - private async _load() { - this._xLoading() + private async _load(isRefreshing = false) { + this._xLoading(isRefreshing) await new Promise(r => setTimeout(r, 250)) // DEBUG try { const res = (await this.rootStore.api.mainPds.view( @@ -119,7 +119,7 @@ export class ProfileViewModel implements bsky.ProfileView.Response { this._replaceAll(res) this._xIdle() } catch (e: any) { - this._xIdle(`Failed to load feed: ${e.toString()}`) + this._xIdle(e.toString()) } } diff --git a/src/view/com/modals/ComposePost.tsx b/src/view/com/modals/ComposePost.tsx index 253db37710..22b6b14bb0 100644 --- a/src/view/com/modals/ComposePost.tsx +++ b/src/view/com/modals/ComposePost.tsx @@ -1,12 +1,5 @@ import React, {useState} from 'react' -import { - KeyboardAvoidingView, - StyleSheet, - Text, - TextInput, - TouchableOpacity, - View, -} from 'react-native' +import {StyleSheet, Text, TouchableOpacity, View} from 'react-native' import {BottomSheetTextInput} from '@gorhom/bottom-sheet' import LinearGradient from 'react-native-linear-gradient' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index d500514efa..8752ee7f9d 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -238,7 +238,7 @@ export const PostThreadItem = observer(function PostThreadItem({ const styles = StyleSheet.create({ outer: { backgroundColor: colors.white, - borderRadius: 10, + borderRadius: 6, margin: 2, marginBottom: 0, }, diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx index a6580fa5a6..3dd5c00470 100644 --- a/src/view/com/post/Post.tsx +++ b/src/view/com/post/Post.tsx @@ -154,7 +154,7 @@ export const Post = observer(function Post({uri}: {uri: string}) { const styles = StyleSheet.create({ outer: { marginTop: 1, - borderRadius: 4, + borderRadius: 6, backgroundColor: colors.white, padding: 10, }, diff --git a/src/view/com/feed/Feed.tsx b/src/view/com/posts/Feed.tsx similarity index 86% rename from src/view/com/feed/Feed.tsx rename to src/view/com/posts/Feed.tsx index 4a2ecb6128..370a72e69c 100644 --- a/src/view/com/feed/Feed.tsx +++ b/src/view/com/posts/Feed.tsx @@ -3,21 +3,17 @@ import {observer} from 'mobx-react-lite' import {Text, View, FlatList} from 'react-native' import {FeedViewModel, FeedViewItemModel} from '../../../state/models/feed-view' import {FeedItem} from './FeedItem' -import {SharePostModel} from '../../../state/models/shell' import {useStores} from '../../../state' export const Feed = observer(function Feed({feed}: {feed: FeedViewModel}) { const store = useStores() - const onPressShare = (uri: string) => { - store.shell.openModal(new SharePostModel(uri)) - } // TODO optimize renderItem or FeedItem, we're getting this notice from RN: -prf // VirtualizedList: You have a large list that is slow to update - make sure your // renderItem function renders components that follow React performance best practices // like PureComponent, shouldComponentUpdate, etc const renderItem = ({item}: {item: FeedViewItemModel}) => ( - + ) const onRefresh = () => { feed.refresh().catch(err => console.error('Failed to refresh', err)) @@ -33,7 +29,7 @@ export const Feed = observer(function Feed({feed}: {feed: FeedViewModel}) { {feed.hasError && {feed.error}} {feed.hasContent && ( item._reactKey} renderItem={renderItem} refreshing={feed.isRefreshing} diff --git a/src/view/com/feed/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx similarity index 96% rename from src/view/com/feed/FeedItem.tsx rename to src/view/com/posts/FeedItem.tsx index e9cf833463..2376686df9 100644 --- a/src/view/com/feed/FeedItem.tsx +++ b/src/view/com/posts/FeedItem.tsx @@ -4,7 +4,7 @@ import {Image, StyleSheet, Text, TouchableOpacity, View} from 'react-native' import {bsky, AdxUri} from '@adxp/mock-api' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FeedViewItemModel} from '../../../state/models/feed-view' -import {ComposePostModel} from '../../../state/models/shell' +import {ComposePostModel, SharePostModel} from '../../../state/models/shell' import {Link} from '../util/Link' import {PostDropdownBtn} from '../util/DropdownBtn' import {s, colors} from '../../lib/styles' @@ -14,10 +14,8 @@ import {useStores} from '../../../state' export const FeedItem = observer(function FeedItem({ item, - onPressShare, }: { item: FeedViewItemModel - onPressShare: (_uri: string) => void }) { const store = useStores() const record = item.record as unknown as bsky.Post.Record @@ -41,6 +39,9 @@ export const FeedItem = observer(function FeedItem({ .toggleLike() .catch(e => console.error('Failed to toggle like', record, e)) } + const onPressShare = (uri: string) => { + store.shell.openModal(new SharePostModel(uri)) + } return ( @@ -151,7 +152,7 @@ export const FeedItem = observer(function FeedItem({ const styles = StyleSheet.create({ outer: { - borderRadius: 10, + borderRadius: 6, margin: 2, marginBottom: 0, backgroundColor: colors.white, diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx index 08d8955545..59af6b2001 100644 --- a/src/view/com/profile/ProfileHeader.tsx +++ b/src/view/com/profile/ProfileHeader.tsx @@ -17,31 +17,13 @@ import {s, gradients, colors} from '../../lib/styles' import {AVIS, BANNER} from '../../lib/assets' import Toast from '../util/Toast' import {Link} from '../util/Link' -import {Selector, SelectorItem} from '../util/Selector' export const ProfileHeader = observer(function ProfileHeader({ - user, + view, }: { - user: string + view: ProfileViewModel }) { const store = useStores() - const [view, setView] = useState() - - useEffect(() => { - if (view?.params.user === user) { - console.log('Profile header doing nothing') - return // no change needed? or trigger refresh? - } - console.log('Fetching profile', user) - const newView = new ProfileViewModel(store, {user: user}) - setView(newView) - newView.setup().catch(err => console.error('Failed to fetch profile', err)) - }, [user, view?.params.user, store]) - - const selectorItems: SelectorItem[] = [ - {label: 'Posts', onSelect() {}}, - {label: 'Badges', onSelect() {}}, - ] const onPressToggleFollow = () => { view?.toggleFollowing().then( @@ -66,19 +48,15 @@ export const ProfileHeader = observer(function ProfileHeader({ // TODO } const onPressFollowers = () => { - store.nav.navigate(`/profile/${user}/followers`) + store.nav.navigate(`/profile/${view.name}/followers`) } const onPressFollows = () => { - store.nav.navigate(`/profile/${user}/follows`) + store.nav.navigate(`/profile/${view.name}/follows`) } // loading // = - if ( - !view || - (view.isLoading && !view.isRefreshing) || - view.params.user !== user - ) { + if (!view || (view.isLoading && !view.isRefreshing)) { return ( @@ -120,13 +98,13 @@ export const ProfileHeader = observer(function ProfileHeader({ - Edit Profile + Edit Profile ) : view.myState.hasFollowed ? ( - Following + Following ) : ( @@ -146,7 +124,7 @@ export const ProfileHeader = observer(function ProfileHeader({ - + @@ -167,10 +145,9 @@ export const ProfileHeader = observer(function ProfileHeader({ {view.description && ( - {view.description} + {view.description} )} - ) }) @@ -178,8 +155,6 @@ export const ProfileHeader = observer(function ProfileHeader({ const styles = StyleSheet.create({ outer: { backgroundColor: colors.white, - borderBottomWidth: 1, - borderColor: colors.gray2, }, banner: { width: '100%', @@ -222,14 +197,17 @@ const styles = StyleSheet.create({ flexDirection: 'row', alignItems: 'center', justifyContent: 'center', - paddingVertical: 8, - paddingHorizontal: 60, + paddingVertical: 6, + paddingLeft: 55, + paddingRight: 60, borderRadius: 30, + borderWidth: 1, + borderColor: 'transparent', }, btn: { alignItems: 'center', justifyContent: 'center', - paddingVertical: 8, + paddingVertical: 7, borderRadius: 30, borderWidth: 1, borderColor: colors.gray2, diff --git a/src/view/com/util/ErrorMessage.tsx b/src/view/com/util/ErrorMessage.tsx new file mode 100644 index 0000000000..7c8670da3b --- /dev/null +++ b/src/view/com/util/ErrorMessage.tsx @@ -0,0 +1,66 @@ +import React from 'react' +import {StyleSheet, Text, TouchableOpacity, View} from 'react-native' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {colors} from '../../lib/styles' + +export function ErrorMessage({ + message, + onPressTryAgain, +}: { + message: string + onPressTryAgain?: () => void +}) { + return ( + + + + + {message} + {onPressTryAgain && ( + + + + )} + + ) +} + +const styles = StyleSheet.create({ + outer: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + backgroundColor: colors.red1, + borderWidth: 1, + borderColor: colors.red3, + borderRadius: 6, + paddingVertical: 8, + paddingHorizontal: 8, + }, + errorIcon: { + backgroundColor: colors.red4, + borderRadius: 12, + width: 24, + height: 24, + alignItems: 'center', + justifyContent: 'center', + marginRight: 8, + }, + message: { + flex: 1, + color: colors.red4, + paddingRight: 10, + }, + btn: { + paddingHorizontal: 4, + paddingVertical: 4, + }, +}) diff --git a/src/view/com/util/ErrorScreen.tsx b/src/view/com/util/ErrorScreen.tsx new file mode 100644 index 0000000000..4a3e41dc91 --- /dev/null +++ b/src/view/com/util/ErrorScreen.tsx @@ -0,0 +1,111 @@ +import React from 'react' +import {StyleSheet, Text, TouchableOpacity, View} from 'react-native' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {colors} from '../../lib/styles' + +export function ErrorScreen({ + title, + message, + details, + onPressTryAgain, +}: { + title: string + message: string + details?: string + onPressTryAgain?: () => void +}) { + return ( + + + + + + + {title} + {message} + {details && {details}} + {onPressTryAgain && ( + + + + Try again + + + )} + + ) +} + +const styles = StyleSheet.create({ + outer: { + flex: 1, + backgroundColor: colors.red1, + borderWidth: 1, + borderColor: colors.red3, + borderRadius: 6, + paddingVertical: 30, + paddingHorizontal: 14, + margin: 10, + }, + title: { + textAlign: 'center', + color: colors.red4, + fontSize: 24, + marginBottom: 10, + }, + message: { + textAlign: 'center', + color: colors.red4, + marginBottom: 20, + }, + details: { + textAlign: 'center', + color: colors.black, + backgroundColor: colors.white, + borderWidth: 1, + borderColor: colors.gray5, + borderRadius: 6, + paddingVertical: 10, + paddingHorizontal: 14, + overflow: 'hidden', + marginBottom: 20, + }, + btnContainer: { + alignItems: 'center', + }, + btn: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: colors.red4, + borderRadius: 6, + paddingHorizontal: 16, + paddingVertical: 10, + }, + btnText: { + marginLeft: 5, + color: colors.white, + fontSize: 16, + fontWeight: 'bold', + }, + errorIconContainer: { + alignItems: 'center', + marginBottom: 10, + }, + errorIcon: { + backgroundColor: colors.red4, + borderRadius: 30, + width: 50, + height: 50, + alignItems: 'center', + justifyContent: 'center', + marginRight: 5, + }, +}) diff --git a/src/view/com/util/Selector.tsx b/src/view/com/util/Selector.tsx index ef7c65d59f..adc393d89e 100644 --- a/src/view/com/util/Selector.tsx +++ b/src/view/com/util/Selector.tsx @@ -9,17 +9,13 @@ import { } from 'react-native' import {colors} from '../../lib/styles' -export interface SelectorItem { - label: string -} - export function Selector({ style, items, onSelect, }: { style?: StyleProp - items: SelectorItem[] + items: string[] onSelect?: (index: number) => void }) { const [selectedIndex, setSelectedIndex] = useState(0) @@ -36,7 +32,7 @@ export function Selector({ onPressItem(i)}> - {item.label} + {item} diff --git a/src/view/index.ts b/src/view/index.ts index c4b8fa9f90..d15fc6c25c 100644 --- a/src/view/index.ts +++ b/src/view/index.ts @@ -5,6 +5,7 @@ import {faAngleRight} from '@fortawesome/free-solid-svg-icons/faAngleRight' import {faArrowLeft} from '@fortawesome/free-solid-svg-icons/faArrowLeft' import {faArrowUpFromBracket} from '@fortawesome/free-solid-svg-icons/faArrowUpFromBracket' import {faArrowUpRightFromSquare} from '@fortawesome/free-solid-svg-icons/faArrowUpRightFromSquare' +import {faArrowsRotate} from '@fortawesome/free-solid-svg-icons/faArrowsRotate' import {faBars} from '@fortawesome/free-solid-svg-icons/faBars' import {faBell} from '@fortawesome/free-solid-svg-icons/faBell' import {faBell as farBell} from '@fortawesome/free-regular-svg-icons/faBell' @@ -39,6 +40,7 @@ export function setup() { faArrowLeft, faArrowUpFromBracket, faArrowUpRightFromSquare, + faArrowsRotate, faBars, faBell, farBell, diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx index 27a17d0e9f..5b4f1011d4 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -1,7 +1,7 @@ import React, {useState, useEffect} from 'react' import {View} from 'react-native' import {observer} from 'mobx-react-lite' -import {Feed} from '../com/feed/Feed' +import {Feed} from '../com/posts/Feed' import {FAB} from '../com/util/FloatingActionButton' import {useStores} from '../../state' import {FeedViewModel} from '../../state/models/feed-view' diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index 236f8f9083..6711f7e040 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -1,52 +1,213 @@ import React, {useState, useEffect} from 'react' -import {View, StyleSheet} from 'react-native' -import {FeedViewModel} from '../../state/models/feed-view' +import {SectionList, StyleSheet, Text, View} from 'react-native' +import {observer} from 'mobx-react-lite' +import {ProfileUiModel, SECTION_IDS} from '../../state/models/profile-ui' +import {FeedViewItemModel} from '../../state/models/feed-view' import {useStores} from '../../state' import {ProfileHeader} from '../com/profile/ProfileHeader' -import {Feed} from '../com/feed/Feed' +import {FeedItem} from '../com/posts/FeedItem' +import {Selector} from '../com/util/Selector' +import {ErrorScreen} from '../com/util/ErrorScreen' +import {ErrorMessage} from '../com/util/ErrorMessage' +import {s, colors} from '../lib/styles' import {ScreenParams} from '../routes' -export const Profile = ({visible, params}: ScreenParams) => { +const SECTION_HEADER_ITEM = Symbol('SectionHeaderItem') +const LOADING_ITEM = Symbol('LoadingItem') +const EMPTY_ITEM = Symbol('EmptyItem') +const END_ITEM = Symbol('EndItem') + +interface RenderItemParams { + item: any + index: number + section: Section +} + +interface ErrorItem { + error: string +} + +interface Section { + data: any[] + keyExtractor?: (v: any) => string + renderItem: (params: RenderItemParams) => JSX.Element +} + +export const Profile = observer(({visible, params}: ScreenParams) => { const store = useStores() const [hasSetup, setHasSetup] = useState(false) - const [feedView, setFeedView] = useState() + const [profileUiState, setProfileUiState] = useState< + ProfileUiModel | undefined + >() useEffect(() => { if (!visible) { return } - const author = params.name + const user = params.name if (hasSetup) { - console.log('Updating profile feed for', author) - feedView?.update() + console.log('Updating profile for', user) + profileUiState?.update() } else { - console.log('Fetching profile feed for', author) - const newFeedView = new FeedViewModel(store, {author}) - setFeedView(newFeedView) - newFeedView - .setup() - .catch(err => console.error('Failed to fetch feed', err)) - .then(() => { - setHasSetup(true) - store.nav.setTitle(author) - }) + console.log('Fetching profile for', user) + store.nav.setTitle(user) + const newProfileUiState = new ProfileUiModel(store, {user}) + setProfileUiState(newProfileUiState) + newProfileUiState.setup().then(() => { + setHasSetup(true) + }) } }, [visible, params.name, store]) - return ( - - - {feedView && } + // events + // = + + const onSelectViewSelector = (index: number) => + profileUiState?.setSelectedViewIndex(index) + const onRefresh = () => { + profileUiState + ?.refresh() + .catch((err: any) => console.error('Failed to refresh', err)) + } + const onEndReached = () => { + profileUiState + ?.loadMore() + .catch((err: any) => console.error('Failed to load more', err)) + } + const onPressTryAgain = () => { + profileUiState?.setup() + } + + // rendering + // = + + const renderItem = (_params: RenderItemParams) => + const renderLoadingItem = (_params: RenderItemParams) => ( + Loading... + ) + const renderErrorItem = ({item}: {item: ErrorItem}) => ( + + ) -} + const renderEmptyItem = (_params: RenderItemParams) => ( + No posts yet! + ) + const renderProfileItem = (_params: RenderItemParams) => { + if (!profileUiState) { + return + } + return + } + const renderSectionHeader = ({section}: {section: Section}) => { + if (section?.data?.[0] !== SECTION_HEADER_ITEM) { + return ( + + ) + } + return + } + const renderPostsItem = ({item}: {item: FeedViewItemModel | Symbol}) => { + if (item === END_ITEM || item instanceof Symbol) { + return - end of feed - + } + return + } + const renderBadgesItem = ({item}: {item: any}) => todo + + const sections = [ + {data: [SECTION_HEADER_ITEM], renderItem: renderProfileItem}, + ] + if (profileUiState) { + if (profileUiState.selectedViewIndex === SECTION_IDS.POSTS) { + if (profileUiState.isInitialLoading) { + sections.push({ + data: [LOADING_ITEM], + renderItem: renderLoadingItem, + } as Section) + } else if (profileUiState.feed.hasError) { + sections.push({ + data: [{error: profileUiState.feed.error}], + renderItem: renderErrorItem, + } as Section) + } else if (profileUiState.currentView.hasContent) { + const items: (FeedViewItemModel | Symbol)[] = + profileUiState.feed.feed.slice() + if (profileUiState.feed.hasReachedEnd) { + items.push(END_ITEM) + } + sections.push({ + data: items, + renderItem: renderPostsItem, + keyExtractor: (item: FeedViewItemModel) => item._reactKey, + } as Section) + } else if (profileUiState.currentView.isEmpty) { + sections.push({ + data: [EMPTY_ITEM], + renderItem: renderEmptyItem, + }) + } + } + if (profileUiState.selectedViewIndex === SECTION_IDS.BADGES) { + sections.push({ + data: [{}], + renderItem: renderBadgesItem, + } as Section) + } + } + + return ( + + + {profileUiState && + (profileUiState.profile.hasError ? ( + + ) : ( + + ))} + + + ) +}) const styles = StyleSheet.create({ container: { flexDirection: 'column', height: '100%', }, + selector: { + paddingTop: 8, + backgroundColor: colors.white, + borderBottomWidth: 1, + borderColor: colors.gray2, + }, feed: { flex: 1, }, + loading: { + paddingVertical: 10, + paddingHorizontal: 14, + }, + endItem: { + paddingTop: 20, + paddingBottom: 30, + color: colors.gray5, + textAlign: 'center', + }, }) diff --git a/src/view/shell/mobile/accounts-menu.tsx b/src/view/shell/mobile/accounts-menu.tsx index e3b61ce422..24b614cec5 100644 --- a/src/view/shell/mobile/accounts-menu.tsx +++ b/src/view/shell/mobile/accounts-menu.tsx @@ -12,9 +12,14 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {AVIS} from '../../lib/assets' import {s, colors} from '../../lib/styles' -export function createAccountsMenu(): RootSiblings { +export function createAccountsMenu({ + debug_onPressItem, +}: { + debug_onPressItem: () => void +}): RootSiblings { const onPressItem = (_index: number) => { sibling.destroy() + debug_onPressItem() // TODO } const onOuterPress = () => sibling.destroy() const sibling = new RootSiblings( diff --git a/src/view/shell/mobile/index.tsx b/src/view/shell/mobile/index.tsx index 35a1b39578..f049211e4a 100644 --- a/src/view/shell/mobile/index.tsx +++ b/src/view/shell/mobile/index.tsx @@ -99,7 +99,10 @@ export const MobileShell: React.FC = observer(() => { const [isLocationMenuActive, setLocationMenuActive] = useState(false) const screenRenderDesc = constructScreenRenderDesc(store.nav) - const onPressAvi = () => createAccountsMenu() + const onPressAvi = () => + createAccountsMenu({ + debug_onPressItem: () => store.nav.navigate('/profile/alice.com'), + }) const onPressLocation = () => setLocationMenuActive(true) const onPressEllipsis = () => createLocationMenu() diff --git a/todos.txt b/todos.txt index e9c96c8086..0f453350c0 100644 --- a/todos.txt +++ b/todos.txt @@ -1,7 +1,15 @@ Paul's todo list +- General + - Update to RN 0.70 + - Selector swipe gesture - Composer - Update the view after creating a post +- Profile + - Real badges + - Edit profile + - More button + - Followers & following as modal? - Search view - * - Linking