From cc3fcb1645060efb8765606d277b91ebb3303ae4 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Thu, 10 Aug 2023 12:50:37 -0500 Subject: [PATCH] Adds profile media tab (#1137) * add media tab * fix loading state * cleanup * update naming * upgrade api package * fix load state * add scroll view to tabs * fix overflow on mobile web --- package.json | 2 +- src/state/models/feeds/posts.ts | 18 ------ src/state/models/ui/profile.ts | 54 ++++++++++++++---- src/view/com/util/ViewSelector.tsx | 92 +++++++++++++++++++----------- yarn.lock | 8 +-- 5 files changed, 109 insertions(+), 65 deletions(-) diff --git a/package.json b/package.json index 64e7a4e6..0f7e17d5 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "e2e:run": "detox test --configuration ios.sim.debug --take-screenshots all" }, "dependencies": { - "@atproto/api": "^0.6.0", + "@atproto/api": "^0.6.1", "@bam.tech/react-native-image-resizer": "^3.0.4", "@braintree/sanitize-url": "^6.0.2", "@expo/html-elements": "^0.4.2", diff --git a/src/state/models/feeds/posts.ts b/src/state/models/feeds/posts.ts index bc1227fd..52717953 100644 --- a/src/state/models/feeds/posts.ts +++ b/src/state/models/feeds/posts.ts @@ -74,24 +74,6 @@ export class PostsFeedModel { return this.hasLoaded && !this.hasContent } - get nonReplyFeed() { - if (this.feedType === 'author') { - return this.slices.filter(slice => { - const params = this.params as GetAuthorFeed.QueryParams - const item = slice.rootItem - const isRepost = - item?.reasonRepost?.by?.handle === params.actor || - item?.reasonRepost?.by?.did === params.actor - const allow = - !item.postRecord?.reply || // not a reply - isRepost // but allow if it's a repost - return allow - }) - } else { - return this.slices - } - } - setHasNewLatest(v: boolean) { this.hasNewLatest = v } diff --git a/src/state/models/ui/profile.ts b/src/state/models/ui/profile.ts index a0249d76..a8c8ec0a 100644 --- a/src/state/models/ui/profile.ts +++ b/src/state/models/ui/profile.ts @@ -6,8 +6,9 @@ import {ActorFeedsModel} from '../lists/actor-feeds' import {ListsListModel} from '../lists/lists-list' export enum Sections { - Posts = 'Posts', + PostsNoReplies = 'Posts', PostsWithReplies = 'Posts & replies', + PostsWithMedia = 'Media', CustomAlgorithms = 'Feeds', Lists = 'Lists', } @@ -46,6 +47,7 @@ export class ProfileUiModel { this.feed = new PostsFeedModel(rootStore, 'author', { actor: params.user, limit: 10, + filter: 'posts_no_replies', }) this.algos = new ActorFeedsModel(rootStore, {actor: params.user}) this.lists = new ListsListModel(rootStore, params.user) @@ -53,8 +55,9 @@ export class ProfileUiModel { get currentView(): PostsFeedModel | ActorFeedsModel | ListsListModel { if ( - this.selectedView === Sections.Posts || - this.selectedView === Sections.PostsWithReplies + this.selectedView === Sections.PostsNoReplies || + this.selectedView === Sections.PostsWithReplies || + this.selectedView === Sections.PostsWithMedia ) { return this.feed } else if (this.selectedView === Sections.Lists) { @@ -76,7 +79,11 @@ export class ProfileUiModel { } get selectorItems() { - const items = [Sections.Posts, Sections.PostsWithReplies] + const items = [ + Sections.PostsNoReplies, + Sections.PostsWithReplies, + Sections.PostsWithMedia, + ] if (this.algos.hasLoaded && !this.algos.isEmpty) { items.push(Sections.CustomAlgorithms) } @@ -90,7 +97,7 @@ export class ProfileUiModel { // If, for whatever reason, the selected view index is not available, default back to posts // This can happen when the user was focused on a view but performed an action that caused // the view to disappear (e.g. deleting the last list in their list of lists https://imgflip.com/i/7txu1y) - return this.selectorItems[this.selectedViewIndex] || Sections.Posts + return this.selectorItems[this.selectedViewIndex] || Sections.PostsNoReplies } get uiItems() { @@ -109,16 +116,22 @@ export class ProfileUiModel { } else { // not loading, no error, show content if ( - this.selectedView === Sections.Posts || + this.selectedView === Sections.PostsNoReplies || this.selectedView === Sections.PostsWithReplies || + this.selectedView === Sections.PostsWithMedia || this.selectedView === Sections.CustomAlgorithms ) { if (this.feed.hasContent) { if (this.selectedView === Sections.CustomAlgorithms) { arr = this.algos.feeds - } else if (this.selectedView === Sections.Posts) { - arr = this.feed.nonReplyFeed + } else if ( + this.selectedView === Sections.PostsNoReplies || + this.selectedView === Sections.PostsWithReplies || + this.selectedView === Sections.PostsWithMedia + ) { + arr = this.feed.slices.slice() } else { + // posts with replies is also default arr = this.feed.slices.slice() } if (!this.feed.hasMore) { @@ -143,8 +156,9 @@ export class ProfileUiModel { get showLoadingMoreFooter() { if ( - this.selectedView === Sections.Posts || - this.selectedView === Sections.PostsWithReplies + this.selectedView === Sections.PostsNoReplies || + this.selectedView === Sections.PostsWithReplies || + this.selectedView === Sections.PostsWithMedia ) { return this.feed.hasContent && this.feed.hasMore && this.feed.isLoading } else if (this.selectedView === Sections.Lists) { @@ -157,7 +171,27 @@ export class ProfileUiModel { // = setSelectedViewIndex(index: number) { + // ViewSelector fires onSelectView on mount + if (index === this.selectedViewIndex) return + this.selectedViewIndex = index + + let filter = 'posts_no_replies' + if (this.selectedView === Sections.PostsWithReplies) { + filter = 'posts_with_replies' + } else if (this.selectedView === Sections.PostsWithMedia) { + filter = 'posts_with_media' + } + + this.feed = new PostsFeedModel(this.rootStore, 'author', { + actor: this.params.user, + limit: 10, + filter, + }) + + if (this.currentView instanceof PostsFeedModel) { + this.feed.setup() + } } async setup() { diff --git a/src/view/com/util/ViewSelector.tsx b/src/view/com/util/ViewSelector.tsx index e2f47ba8..c2912ba4 100644 --- a/src/view/com/util/ViewSelector.tsx +++ b/src/view/com/util/ViewSelector.tsx @@ -1,5 +1,11 @@ import React, {useEffect, useState} from 'react' -import {Pressable, RefreshControl, StyleSheet, View} from 'react-native' +import { + Pressable, + RefreshControl, + StyleSheet, + View, + ScrollView, +} from 'react-native' import {FlatList} from './Views' import {OnScrollCb} from 'lib/hooks/useOnMainScroll' import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle' @@ -131,6 +137,8 @@ export const ViewSelector = React.forwardRef< }, ) +const SCROLLBAR_OFFSET = 12 + export function Selector({ selectedIndex, items, @@ -140,6 +148,8 @@ export function Selector({ items: string[] onSelect?: (index: number) => void }) { + const [height, setHeight] = useState(0) + const pal = usePalette('default') const borderColor = useColorSchemeStyle( {borderColor: colors.black}, @@ -151,37 +161,55 @@ export function Selector({ } return ( - - {items.map((item, i) => { - const selected = i === selectedIndex - return ( - onPressItem(i)} - accessibilityLabel={item} - accessibilityHint={`Selects ${item}`} - // TODO: Modify the component API such that lint fails - // at the invocation site as well - > - - - {item} - - - - ) - })} + + + { + const {height} = e.nativeEvent.layout + setHeight(height || 60) + }}> + {items.map((item, i) => { + const selected = i === selectedIndex + return ( + onPressItem(i)} + accessibilityLabel={item} + accessibilityHint={`Selects ${item}`} + // TODO: Modify the component API such that lint fails + // at the invocation site as well + > + + + {item} + + + + ) + })} + + ) } diff --git a/yarn.lock b/yarn.lock index 6e13bd51..c418e9ac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -40,10 +40,10 @@ tlds "^1.234.0" typed-emitter "^2.1.0" -"@atproto/api@^0.6.0": - version "0.6.0" - resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.6.0.tgz#c4eea08ee4d1be522928cd016d7de8061d86e573" - integrity sha512-GkWHoGZfNneHarAYkIPJD1GGgKiI7OwnCtKS+J4AmlVKYijGEzOYgg1fY6rluT6XPT5TlQZiHUWpMlpqAkQIkQ== +"@atproto/api@^0.6.1": + version "0.6.1" + resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.6.1.tgz#1a4794c4e379f3790dbc1c2cc69e0700c711f634" + integrity sha512-Fwp3GxSxy04XCScLNb7gdYuITt3beUPM2gOmAaJJ/c0muvj3BS/lGeeEqHToSMlxyirfPQYiTHDGcDZgo6EpMQ== dependencies: "@atproto/common-web" "*" "@atproto/uri" "*"