Skip to content

Commit 8d76c35

Browse files
authored
feat(mobile): toggle unread only (#2873)
1 parent e40f8a9 commit 8d76c35

File tree

10 files changed

+50
-21
lines changed

10 files changed

+50
-21
lines changed

apps/mobile/src/components/ui/button/UIBarButton.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@ interface UIBarButtonProps extends PressableProps {
99

1010
normalIcon: React.ReactNode
1111
selectedIcon?: React.ReactNode
12+
13+
noOverlay?: boolean
1214
}
1315
export const UIBarButton = ({
1416
normalIcon,
1517
selectedIcon,
1618
onPress,
1719
selected,
1820
label,
21+
noOverlay,
1922

2023
...props
2124
}: UIBarButtonProps) => {
@@ -30,7 +33,7 @@ export const UIBarButton = ({
3033
aria-label={label}
3134
{...props}
3235
>
33-
{selected && <ButtonOverlay />}
36+
{selected && !noOverlay && <ButtonOverlay />}
3437

3538
{!hasDifferentIcon ? (
3639
normalIcon

apps/mobile/src/modules/entry-list/EntryListContentArticle.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export const EntryListContentArticle = forwardRef<
3131
[isFetching],
3232
)
3333

34-
const onViewableItemsChanged = useOnViewableItemsChanged()
34+
const onViewableItemsChanged = useOnViewableItemsChanged({ isLoading: isRefetching })
3535

3636
return (
3737
<TimelineSelectorList

apps/mobile/src/modules/entry-list/EntryListContentGrid.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export const EntryListContentGrid = forwardRef<
1818
} & Omit<MasonryFlashListProps<string>, "data" | "renderItem">
1919
>(({ entryIds, ...rest }, ref) => {
2020
const { fetchNextPage, refetch, isRefetching, hasNextPage } = useFetchEntriesControls()
21-
const onViewableItemsChanged = useOnViewableItemsChanged()
21+
const onViewableItemsChanged = useOnViewableItemsChanged({ isLoading: isRefetching })
2222

2323
return (
2424
<TimelineSelectorMasonryList

apps/mobile/src/modules/entry-list/EntryListContentSocial.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export const EntryListContentSocial = forwardRef<
2525
[isFetching],
2626
)
2727

28-
const onViewableItemsChanged = useOnViewableItemsChanged()
28+
const onViewableItemsChanged = useOnViewableItemsChanged({ isLoading: isRefetching })
2929

3030
return (
3131
<TimelineSelectorList

apps/mobile/src/modules/entry-list/hooks.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,22 @@ import { debouncedFetchEntryContentByStream } from "@/src/store/entry/store"
66
import { unreadSyncService } from "@/src/store/unread/store"
77

88
const defaultIdExtractor = (item: ViewToken) => item.key
9-
export function useOnViewableItemsChanged(
10-
idExtractor: (item: ViewToken) => string = defaultIdExtractor,
11-
): (info: { viewableItems: ViewToken[]; changed: ViewToken[] }) => void {
9+
export function useOnViewableItemsChanged({
10+
idExtractor = defaultIdExtractor,
11+
isLoading,
12+
}: {
13+
isLoading?: boolean
14+
idExtractor?: (item: ViewToken) => string
15+
} = {}): (info: { viewableItems: ViewToken[]; changed: ViewToken[] }) => void {
1216
const markAsReadWhenScrolling = useGeneralSettingKey("scrollMarkUnread")
1317
const markAsReadWhenRendering = useGeneralSettingKey("renderMarkUnread")
1418

1519
const [stableIdExtractor] = useState(() => idExtractor)
1620

1721
return useCallback(
1822
({ viewableItems, changed }) => {
23+
if (isLoading) return
24+
1925
debouncedFetchEntryContentByStream(viewableItems.map((item) => stableIdExtractor(item)))
2026
if (markAsReadWhenScrolling) {
2127
changed
@@ -33,6 +39,6 @@ export function useOnViewableItemsChanged(
3339
})
3440
}
3541
},
36-
[markAsReadWhenRendering, markAsReadWhenScrolling, stableIdExtractor],
42+
[markAsReadWhenRendering, markAsReadWhenScrolling, stableIdExtractor, isLoading],
3743
)
3844
}

apps/mobile/src/modules/screen/action.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@ import { router } from "expo-router"
33
import type { PropsWithChildren } from "react"
44
import { TouchableOpacity, View } from "react-native"
55

6+
import { setGeneralSetting, useGeneralSettingKey } from "@/src/atoms/settings/general"
67
import { setUISetting, useUISettingKey } from "@/src/atoms/settings/ui"
78
import { UserAvatar } from "@/src/components/ui/avatar/UserAvatar"
89
import { UIBarButton } from "@/src/components/ui/button/UIBarButton"
910
import { AddCuteReIcon } from "@/src/icons/add_cute_re"
1011
import { CheckCircleCuteReIcon } from "@/src/icons/check_circle_cute_re"
1112
import { PhotoAlbumCuteFiIcon } from "@/src/icons/photo_album_cute_fi"
1213
import { PhotoAlbumCuteReIcon } from "@/src/icons/photo_album_cute_re"
14+
import { RoundCuteFiIcon } from "@/src/icons/round_cute_fi"
15+
import { RoundCuteReIcon } from "@/src/icons/round_cute_re"
1316
import { Dialog } from "@/src/lib/dialog"
1417
import { useWhoami } from "@/src/store/user/hooks"
1518
import { accentColor, useColor } from "@/src/theme/colors"
@@ -36,9 +39,20 @@ export function HomeLeftAction() {
3639
}
3740

3841
export function HomeSharedRightAction(props: PropsWithChildren) {
42+
const unreadOnly = useGeneralSettingKey("unreadOnly")
3943
return (
4044
<ActionGroup className="-mr-2">
4145
{props.children}
46+
<UIBarButton
47+
label={unreadOnly ? "Show All" : "Show Unread Only"}
48+
normalIcon={<RoundCuteReIcon height={20} width={20} color={accentColor} />}
49+
selectedIcon={<RoundCuteFiIcon height={20} width={20} color={accentColor} />}
50+
onPress={() => {
51+
setGeneralSetting("unreadOnly", !unreadOnly)
52+
}}
53+
selected={unreadOnly}
54+
noOverlay
55+
/>
4256
<UIBarButton
4357
label="Mark All as Read"
4458
normalIcon={<CheckCircleCuteReIcon height={20} width={20} color={accentColor} />}

apps/mobile/src/modules/screen/atoms.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -173,12 +173,6 @@ export function useSelectedFeed() {
173173

174174
const selectedTimeline = useAtomValue(selectedTimelineAtom)
175175
const selectedFeed = useAtomValue(selectedFeedAtom)
176-
177-
const payload = getFetchEntryPayload(
178-
entryListContext.type === "feed" ? selectedFeed : selectedTimeline,
179-
)
180-
usePrefetchEntries(payload)
181-
182176
return entryListContext.type === "feed" ? selectedFeed : selectedTimeline
183177
}
184178

apps/mobile/src/services/entry.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { eq, inArray, or } from "drizzle-orm"
22

3+
import { getGeneralSettings } from "../atoms/settings/general"
34
import { db } from "../database"
45
import { entriesTable } from "../database/schemas"
56
import type { EntrySchema } from "../database/schemas/types"
@@ -52,7 +53,11 @@ class EntryServiceStatic implements Hydratable, Resetable {
5253

5354
async hydrate() {
5455
const entries = await db.query.entriesTable.findMany()
55-
entryActions.upsertManyInSession(entries.map((e) => dbStoreMorph.toEntryModel(e)))
56+
const { unreadOnly } = getGeneralSettings()
57+
entryActions.upsertManyInSession(
58+
entries.map((e) => dbStoreMorph.toEntryModel(e)),
59+
{ unreadOnly },
60+
)
5661
}
5762
}
5863

apps/mobile/src/store/entry/hooks.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,19 @@ import type { FeedViewType } from "@follow/constants"
22
import { useInfiniteQuery, useMutation, useQuery } from "@tanstack/react-query"
33
import { useCallback, useEffect } from "react"
44

5+
import { useGeneralSettingKey } from "@/src/atoms/settings/general"
6+
57
import { getEntry } from "./getter"
68
import { entrySyncServices, useEntryStore } from "./store"
79
import type { EntryModel, FetchEntriesProps } from "./types"
810

9-
export const usePrefetchEntries = (props: Omit<FetchEntriesProps, "pageParam"> | null) => {
10-
const { feedId, inboxId, listId, view, read, limit } = props || {}
11+
export const usePrefetchEntries = (props: Omit<FetchEntriesProps, "pageParam" | "read"> | null) => {
12+
const { feedId, inboxId, listId, view, limit } = props || {}
13+
const unreadOnly = useGeneralSettingKey("unreadOnly")
1114
return useInfiniteQuery({
12-
queryKey: ["entries", feedId, inboxId, listId, view, read, limit],
13-
queryFn: ({ pageParam }) => entrySyncServices.fetchEntries({ ...props, pageParam }),
15+
queryKey: ["entries", feedId, inboxId, listId, view, unreadOnly, limit],
16+
queryFn: ({ pageParam }) =>
17+
entrySyncServices.fetchEntries({ ...props, pageParam, read: unreadOnly ? false : undefined }),
1418
getNextPageParam: (lastPage) =>
1519
listId
1620
? lastPage.data?.at(-1)?.entries.insertedAt

apps/mobile/src/store/entry/store.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,15 +124,18 @@ class EntryActions {
124124
}
125125
}
126126

127-
upsertManyInSession(entries: EntryModel[]) {
127+
upsertManyInSession(entries: EntryModel[], options?: { unreadOnly?: boolean }) {
128128
if (entries.length === 0) return
129+
const { unreadOnly } = options ?? {}
129130

130131
immerSet((draft) => {
131132
for (const entry of entries) {
132133
draft.entryIdSet.add(entry.id)
133134
draft.data[entry.id] = entry
134135

135-
const { feedId, inboxHandle } = entry
136+
const { feedId, inboxHandle, read } = entry
137+
if (unreadOnly && read) continue
138+
136139
this.addEntryIdToFeed({
137140
draft,
138141
feedId,

0 commit comments

Comments
 (0)