Skip to content

Commit

Permalink
refactor(mobile): auto mark as read logic (#2919)
Browse files Browse the repository at this point in the history
* refactor(mobile): auto mark as read logic

* update
  • Loading branch information
hyoban authored Mar 1, 2025
1 parent 7fbd2d7 commit 872835a
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import { EntryNormalItem } from "./templates/EntryNormalItem"

export const EntryListContentArticle = forwardRef<
ElementRef<typeof TimelineSelectorList>,
{ entryIds: string[] }
>(({ entryIds }, ref) => {
{ entryIds: string[]; active?: boolean }
>(({ entryIds, active }, ref) => {
const playingAudioUrl = usePlayingUrl()

const { fetchNextPage, isFetching, refetch, isRefetching } = useFetchEntriesControls()
Expand All @@ -31,7 +31,9 @@ export const EntryListContentArticle = forwardRef<
[isFetching],
)

const { onViewableItemsChanged, onScroll } = useOnViewableItemsChanged()
const { onViewableItemsChanged, onScroll } = useOnViewableItemsChanged({
disabled: active === false || isFetching,
})

return (
<TimelineSelectorList
Expand Down
16 changes: 10 additions & 6 deletions apps/mobile/src/modules/entry-list/EntryListContentPicture.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@ import { EntryPictureItem } from "./templates/EntryPictureItem"

export const EntryListContentPicture = forwardRef<
ElementRef<typeof TimelineSelectorMasonryList>,
{
entryIds: string[]
} & Omit<MasonryFlashListProps<string>, "data" | "renderItem">
>(({ entryIds, ...rest }, ref) => {
const { fetchNextPage, refetch, isRefetching, hasNextPage } = useFetchEntriesControls()
const { onViewableItemsChanged, onScroll } = useOnViewableItemsChanged()
{ entryIds: string[]; active?: boolean } & Omit<
MasonryFlashListProps<string>,
"data" | "renderItem"
>
>(({ entryIds, active, ...rest }, ref) => {
const { fetchNextPage, refetch, isRefetching, hasNextPage, isFetching } =
useFetchEntriesControls()
const { onViewableItemsChanged, onScroll } = useOnViewableItemsChanged({
disabled: active === false || isFetching,
})

return (
<TimelineSelectorMasonryList
Expand Down
8 changes: 5 additions & 3 deletions apps/mobile/src/modules/entry-list/EntryListContentSocial.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import { EntrySocialItem } from "./templates/EntrySocialItem"

export const EntryListContentSocial = forwardRef<
ElementRef<typeof TimelineSelectorList>,
{ entryIds: string[] }
>(({ entryIds }, ref) => {
{ entryIds: string[]; active?: boolean }
>(({ entryIds, active }, ref) => {
const { fetchNextPage, isFetching, refetch, isRefetching } = useFetchEntriesControls()

const renderItem = useCallback(
Expand All @@ -25,7 +25,9 @@ export const EntryListContentSocial = forwardRef<
[isFetching],
)

const { onViewableItemsChanged, onScroll } = useOnViewableItemsChanged()
const { onViewableItemsChanged, onScroll } = useOnViewableItemsChanged({
disabled: active === false || isFetching,
})

return (
<TimelineSelectorList
Expand Down
13 changes: 8 additions & 5 deletions apps/mobile/src/modules/entry-list/EntryListContentVideo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ import { EntryVideoItem } from "./templates/EntryVideoItem"

export const EntryListContentVideo = forwardRef<
ElementRef<typeof TimelineSelectorMasonryList>,
{
entryIds: string[]
} & Omit<MasonryFlashListProps<string>, "data" | "renderItem">
>(({ entryIds, ...rest }, ref) => {
{ entryIds: string[]; active?: boolean } & Omit<
MasonryFlashListProps<string>,
"data" | "renderItem"
>
>(({ entryIds, active, ...rest }, ref) => {
const { fetchNextPage, refetch, isRefetching, isFetching } = useFetchEntriesControls()
const { onViewableItemsChanged, onScroll } = useOnViewableItemsChanged()
const { onViewableItemsChanged, onScroll } = useOnViewableItemsChanged({
disabled: active === false || isFetching,
})

const ListFooterComponent = useMemo(
() =>
Expand Down
9 changes: 6 additions & 3 deletions apps/mobile/src/modules/entry-list/EntryListSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ export function EntryListSelector({
}) {
const ref = useRegisterNavigationScrollView<FlashList<any>>(active)

let ContentComponent: typeof EntryListContentSocial | typeof EntryListContentPicture =
EntryListContentArticle
let ContentComponent:
| typeof EntryListContentSocial
| typeof EntryListContentPicture
| typeof EntryListContentVideo
| typeof EntryListContentArticle = EntryListContentArticle
switch (viewId) {
case FeedViewType.SocialMedia: {
ContentComponent = EntryListContentSocial
Expand All @@ -43,7 +46,7 @@ export function EntryListSelector({

return (
<EntryListContextViewContext.Provider value={viewId}>
<ContentComponent ref={ref} entryIds={entryIds} />
<ContentComponent ref={ref} entryIds={entryIds} active={active} />
</EntryListContextViewContext.Provider>
)
}
45 changes: 32 additions & 13 deletions apps/mobile/src/modules/entry-list/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type ViewToken from "@shopify/flash-list/dist/viewability/ViewToken"
import { useCallback, useInsertionEffect, useMemo, useRef, useState } from "react"
import { useCallback, useEffect, useInsertionEffect, useMemo, useRef, useState } from "react"
import type { NativeScrollEvent, NativeSyntheticEvent } from "react-native"

import { useGeneralSettingKey } from "@/src/atoms/settings/general"
Expand All @@ -8,15 +8,19 @@ import { unreadSyncService } from "@/src/store/unread/store"

const defaultIdExtractor = (item: ViewToken) => item.key
export function useOnViewableItemsChanged({
disabled,
idExtractor = defaultIdExtractor,
}: {
disabled?: boolean
idExtractor?: (item: ViewToken) => string
} = {}) {
const orientation = useRef<"down" | "up" | "initial">("initial")
const orientation = useRef<"down" | "up">("down")
const lastOffset = useRef(0)

const markAsReadWhenScrolling = useGeneralSettingKey("scrollMarkUnread")
const markAsReadWhenRendering = useGeneralSettingKey("renderMarkUnread")
const [lastViewableItems, setLastViewableItems] = useState<ViewToken[] | null>()
const [lastRemovedItems, setLastRemovedItems] = useState<ViewToken[] | null>(null)

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

Expand All @@ -26,22 +30,37 @@ export function useOnViewableItemsChanged({
}) => void = useNonReactiveCallback(({ viewableItems, changed }) => {
debouncedFetchEntryContentByStream(viewableItems.map((item) => stableIdExtractor(item)))

if (orientation.current !== "down") return
if (orientation.current === "down") {
setLastViewableItems(viewableItems)
setLastRemovedItems(changed.filter((item) => !item.isViewable))
} else {
setLastRemovedItems(null)
setLastViewableItems(null)
}
})

if (markAsReadWhenScrolling) {
changed
.filter((item) => !item.isViewable)
.forEach((item) => {
useEffect(() => {
if (!disabled) {
if (markAsReadWhenScrolling && lastRemovedItems) {
lastRemovedItems.forEach((item) => {
unreadSyncService.markEntryAsRead(stableIdExtractor(item))
})
}
}

if (markAsReadWhenRendering) {
viewableItems.forEach((item) => {
unreadSyncService.markEntryAsRead(stableIdExtractor(item))
})
if (markAsReadWhenRendering && lastViewableItems) {
lastViewableItems.forEach((item) => {
unreadSyncService.markEntryAsRead(stableIdExtractor(item))
})
}
}
})
}, [
disabled,
lastRemovedItems,
lastViewableItems,
markAsReadWhenRendering,
markAsReadWhenScrolling,
stableIdExtractor,
])

const onScroll = useCallback((e: NativeSyntheticEvent<NativeScrollEvent>) => {
const currentOffset = e.nativeEvent.contentOffset.y
Expand Down

0 comments on commit 872835a

Please sign in to comment.