diff --git a/apps/mobile/native/ios/FollowNative.podspec b/apps/mobile/native/ios/FollowNative.podspec index 1e7701d9b..df9b89f0b 100644 --- a/apps/mobile/native/ios/FollowNative.podspec +++ b/apps/mobile/native/ios/FollowNative.podspec @@ -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'], diff --git a/apps/mobile/native/ios/Toaster/ToasterModule.swift b/apps/mobile/native/ios/Toaster/ToasterModule.swift index 1cac4d3a2..4abfdca5d 100644 --- a/apps/mobile/native/ios/Toaster/ToasterModule.swift +++ b/apps/mobile/native/ios/Toaster/ToasterModule.swift @@ -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()) } } diff --git a/apps/mobile/src/components/native/webview/EntryContentWebView.tsx b/apps/mobile/src/components/native/webview/EntryContentWebView.tsx index 699bbe3ce..e58bae93d 100644 --- a/apps/mobile/src/components/native/webview/EntryContentWebView.tsx +++ b/apps/mobile/src/components/native/webview/EntryContentWebView.tsx @@ -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]) diff --git a/apps/mobile/src/components/ui/toast/types.ts b/apps/mobile/src/components/ui/toast/types.ts index d176d94cc..4402b0e1f 100644 --- a/apps/mobile/src/components/ui/toast/types.ts +++ b/apps/mobile/src/components/ui/toast/types.ts @@ -13,6 +13,8 @@ export interface ToastProps { duration: number icon?: React.ReactNode | false canClose?: boolean + + position?: "top" | "center" | "bottom" } export type CenterToastProps = Partial< diff --git a/apps/mobile/src/lib/toast.tsx b/apps/mobile/src/lib/toast.tsx index 38dca77b3..2b27b4804 100644 --- a/apps/mobile/src/lib/toast.tsx +++ b/apps/mobile/src/lib/toast.tsx @@ -6,7 +6,9 @@ import type { ToastProps } from "../components/ui/toast/types" export const toastInstance = new ToastManager() -type CommandToastOptions = Partial> +type CommandToastOptions = Partial< + Pick +> 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 } diff --git a/apps/mobile/src/modules/screen/action.tsx b/apps/mobile/src/modules/screen/action.tsx index c673b13ac..1adf9eee8 100644 --- a/apps/mobile/src/modules/screen/action.tsx +++ b/apps/mobile/src/modules/screen/action.tsx @@ -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={} onPress={() => { setGeneralSetting("unreadOnly", !unreadOnly) + toast.info(`Showing ${unreadOnly ? "all" : "unread"} entries`, { position: "bottom" }) }} selected={unreadOnly} overlay={false} diff --git a/apps/mobile/src/screens/(stack)/entries/[entryId]/index.tsx b/apps/mobile/src/screens/(stack)/entries/[entryId]/index.tsx index 0b26555e9..c761321c7 100644 --- a/apps/mobile/src/screens/(stack)/entries/[entryId]/index.tsx +++ b/apps/mobile/src/screens/(stack)/entries/[entryId]/index.tsx @@ -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() { {entry && ( - + )} {viewType === FeedViewType.SocialMedia && ( @@ -86,6 +87,12 @@ export default function EntryDetailPage() { ) } +const EntryContentWebViewWithContext = ({ entry }: { entry: EntryModel }) => { + const { showSourceAtom } = useEntryContentContext() + const showSource = useAtomValue(showSourceAtom) + return +} + const EntryInfo = ({ entryId }: { entryId: string }) => { const entry = useEntry(entryId)