fix(mobile): toast when toggling unread, fix entry context crash (#2997)

This commit is contained in:
Stephen Zhou 2025-03-07 20:16:49 +08:00 committed by GitHub
parent 047c423987
commit ea53d1b9b5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 52 additions and 17 deletions

View File

@ -21,14 +21,14 @@ Pod::Spec.new do |s|
s.dependency 'ExpoModulesCore'
s.dependency 'SnapKit', '~> 5.7.0'
s.dependency 'SDWebImage', '~> 5.0'
s.dependency 'SPIndicator', '~> 1.0.0'
s.dependency 'SPIndicator', '~> 1.6.4'
# Swift/Objective-C compatibility
s.pod_target_xcconfig = {
'DEFINES_MODULE' => 'YES',
}
s.source_files = "**/*.{h,m,mm,swift,hpp,cpp,js}"
s.resource_bundles = {
'js' => ['SharedWebView/injected/**/*'],
'FollowNative' => ['Media.xcassets'],

View File

@ -34,17 +34,34 @@ extension ToastType {
}
}
}
enum Position: String, Enumerable {
case top
case center
case bottom
}
extension Position {
func toSPPresentSide() -> SPIndicatorPresentSide {
switch self {
case .top: .top
case .center: .center
case .bottom: .bottom
}
}
}
struct ToastOptions: Record {
@Field
var message: String?
@Field
var type: ToastType = .info
@Field
var duration: Double = 1.5
@Field
var title: String
@Field
var position: Position?
}
public class ToasterModule: Module {
@ -56,6 +73,11 @@ public class ToasterModule: Module {
DispatchQueue.main.sync {
let indicatorView = SPIndicatorView(
title: value.title, message: value.message, preset: value.type.type())
if value.position != nil {
indicatorView.presentSide = value.position!.toSPPresentSide()
}
indicatorView.present(duration: value.duration, haptic: value.type.haptic())
}
}

View File

@ -1,6 +1,6 @@
import { clsx } from "@follow/utils"
import { requireNativeView } from "expo"
import { useAtom, useAtomValue } from "jotai"
import { useAtom } from "jotai"
import * as React from "react"
import { useEffect } from "react"
import type { ViewProps } from "react-native"
@ -8,7 +8,6 @@ import { ActivityIndicator, TouchableOpacity, View } from "react-native"
import { useUISettingKey } from "@/src/atoms/settings/ui"
import { BugCuteReIcon } from "@/src/icons/bug_cute_re"
import { useEntryContentContext } from "@/src/modules/entry-content/ctx"
import type { EntryModel } from "@/src/store/entry/types"
import { Portal } from "../../ui/portal"
@ -26,6 +25,7 @@ const NativeView: React.ComponentType<
type EntryContentWebViewProps = {
entry: EntryModel
noMedia?: boolean
showSource?: boolean
}
const setCodeTheme = (light: string, dark: string) => {
@ -54,21 +54,19 @@ const setShowSource = (value: boolean) => {
}
export function EntryContentWebView(props: EntryContentWebViewProps) {
const { showSourceAtom } = useEntryContentContext()
const showSource = useAtomValue(showSourceAtom)
useEffect(() => {
setShowSource(!!showSource)
}, [showSource])
const [contentHeight, setContentHeight] = useAtom(sharedWebViewHeightAtom)
const codeThemeLight = useUISettingKey("codeHighlightThemeLight")
const codeThemeDark = useUISettingKey("codeHighlightThemeDark")
const readerRenderInlineStyle = useUISettingKey("readerRenderInlineStyle")
const { entry, noMedia } = props
const { entry, noMedia, showSource } = props
const [mode, setMode] = React.useState<"normal" | "debug">("normal")
useEffect(() => {
setShowSource(!!showSource)
}, [showSource])
useEffect(() => {
setNoMedia(!!noMedia)
}, [noMedia, mode])

View File

@ -13,6 +13,8 @@ export interface ToastProps {
duration: number
icon?: React.ReactNode | false
canClose?: boolean
position?: "top" | "center" | "bottom"
}
export type CenterToastProps = Partial<

View File

@ -6,7 +6,9 @@ import type { ToastProps } from "../components/ui/toast/types"
export const toastInstance = new ToastManager()
type CommandToastOptions = Partial<Pick<ToastProps, "duration" | "icon" | "render" | "message">>
type CommandToastOptions = Partial<
Pick<ToastProps, "duration" | "icon" | "render" | "message" | "position">
>
type Toast = {
// [key in "error" | "success" | "info"]: (message: string) => void;
show: typeof toastInstance.show
@ -23,6 +25,7 @@ interface NativeToasterOptions {
* seconds
*/
duration?: number
position?: "top" | "center" | "bottom"
}
export const toast = {
show: toastInstance.show.bind(toastInstance),
@ -36,6 +39,7 @@ export const toast = {
message,
type,
duration: options.duration ? options.duration / 1000 : 1.5,
position: options.position,
} as NativeToasterOptions)
return
}

View File

@ -12,6 +12,7 @@ import { CheckCircleCuteReIcon } from "@/src/icons/check_circle_cute_re"
import { RoundCuteFiIcon } from "@/src/icons/round_cute_fi"
import { RoundCuteReIcon } from "@/src/icons/round_cute_re"
import { Dialog } from "@/src/lib/dialog"
import { toast } from "@/src/lib/toast"
import { useWhoami } from "@/src/store/user/hooks"
import { accentColor, useColor } from "@/src/theme/colors"
@ -83,6 +84,7 @@ export const UnreadOnlyActionButton = ({ variant = "primary" }: HeaderActionButt
selectedIcon={<RoundCuteFiIcon height={size} width={size} color={color} />}
onPress={() => {
setGeneralSetting("unreadOnly", !unreadOnly)
toast.info(`Showing ${unreadOnly ? "all" : "unread"} entries`, { position: "bottom" })
}}
selected={unreadOnly}
overlay={false}

View File

@ -1,6 +1,6 @@
import { FeedViewType } from "@follow/constants"
import { useLocalSearchParams } from "expo-router"
import { atom } from "jotai"
import { atom, useAtomValue } from "jotai"
import { useMemo } from "react"
import { Pressable, Text, View } from "react-native"
import Animated, { FadeIn, FadeOut } from "react-native-reanimated"
@ -11,9 +11,10 @@ import { SafeNavigationScrollView } from "@/src/components/layouts/views/SafeNav
import { EntryContentWebView } from "@/src/components/native/webview/EntryContentWebView"
import { PortalHost } from "@/src/components/ui/portal"
import { openLink } from "@/src/lib/native"
import { EntryContentContext } from "@/src/modules/entry-content/ctx"
import { EntryContentContext, useEntryContentContext } from "@/src/modules/entry-content/ctx"
import { EntryAISummary } from "@/src/modules/entry-content/EntryAISummary"
import { useEntry, usePrefetchEntryContent } from "@/src/store/entry/hooks"
import type { EntryModel } from "@/src/store/entry/types"
import { useFeed } from "@/src/store/feed/hooks"
import { useAutoMarkAsRead } from "@/src/store/unread/hooks"
@ -71,7 +72,7 @@ export default function EntryDetailPage() {
<EntryAISummary entryId={entryId as string} />
{entry && (
<View className="mt-3">
<EntryContentWebView entry={entry} />
<EntryContentWebViewWithContext entry={entry} />
</View>
)}
{viewType === FeedViewType.SocialMedia && (
@ -86,6 +87,12 @@ export default function EntryDetailPage() {
)
}
const EntryContentWebViewWithContext = ({ entry }: { entry: EntryModel }) => {
const { showSourceAtom } = useEntryContentContext()
const showSource = useAtomValue(showSourceAtom)
return <EntryContentWebView entry={entry} showSource={showSource} />
}
const EntryInfo = ({ entryId }: { entryId: string }) => {
const entry = useEntry(entryId)