fix(mobile): toast when toggling unread, fix entry context crash (#2997)
This commit is contained in:
parent
047c423987
commit
ea53d1b9b5
|
|
@ -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'],
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ export interface ToastProps {
|
|||
duration: number
|
||||
icon?: React.ReactNode | false
|
||||
canClose?: boolean
|
||||
|
||||
position?: "top" | "center" | "bottom"
|
||||
}
|
||||
|
||||
export type CenterToastProps = Partial<
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue