Skip to content

Commit

Permalink
feat(mobile): add native iOS toast module with SPIndicator
Browse files Browse the repository at this point in the history
Signed-off-by: Innei <tukon479@gmail.com>
  • Loading branch information
Innei committed Feb 21, 2025
1 parent 7cfa888 commit 8dbb634
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 36 deletions.
2 changes: 1 addition & 1 deletion apps/mobile/native/expo-module.config.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"platforms": ["apple", "android"],
"apple": {
"modules": ["SharedWebViewModule", "HelperModule"]
"modules": ["SharedWebViewModule", "HelperModule", "ToasterModule"]
},
"android": {
"modules": []
Expand Down
1 change: 1 addition & 0 deletions apps/mobile/native/ios/FollowNative.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ 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'
# Swift/Objective-C compatibility
s.pod_target_xcconfig = {
'DEFINES_MODULE' => 'YES',
Expand Down
63 changes: 63 additions & 0 deletions apps/mobile/native/ios/Toaster/ToasterModule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//
// ToasterModule.swift
//
// Created by Innei on 2025/2/21.
//

import ExpoModulesCore
import SPIndicator

enum ToastType: String, Enumerable {
case error
case info
case warn
case success
}

extension ToastType {
func type() -> SPIndicatorIconPreset {
switch self
{
case .error: .error
case .warn: .custom(UIImage(systemName: "exclamationmark.triangle")!.withTintColor(.orange))
case .info: .custom(UIImage(systemName: "info.circle")!.withTintColor(.blue))
case .success: .done
}
}

func haptic() -> SPIndicatorHaptic {
switch self {
case .error: .error
case .info: .success
case .warn: .warning
case .success: .error
}
}
}
struct ToastOptions: Record {
@Field
var message: String?
@Field
var type: ToastType = .info

@Field
var duration: Double = 1.5
@Field
var title: String

}

public class ToasterModule: Module {
public func definition() -> ModuleDefinition {
Name("Toaster")

Function("toast") { (value: ToastOptions) in

DispatchQueue.main.sync {
let indicatorView = SPIndicatorView(
title: value.title, message: value.message, preset: value.type.type())
indicatorView.present(duration: value.duration, haptic: value.type.haptic())
}
}
}
}
24 changes: 24 additions & 0 deletions apps/mobile/src/lib/toast.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { requireNativeModule } from "expo"
import { Platform } from "react-native"

import { ToastManager } from "../components/ui/toast/manager"
import type { ToastProps } from "../components/ui/toast/types"

Expand All @@ -11,11 +14,32 @@ type Toast = {
success: (message: string, options?: CommandToastOptions) => void
info: (message: string, options?: CommandToastOptions) => void
}

interface NativeToasterOptions {
title: string
message: string
type: "error" | "success" | "info" | "warn"
/**
* seconds
*/
duration?: number
}
export const toast = {
show: toastInstance.show.bind(toastInstance),
} as Toast
;(["error", "success", "info"] as const).forEach((type) => {
toast[type] = (message: string, options: CommandToastOptions = {}) => {
if (Platform.OS === "ios") {
const NativeToaster = requireNativeModule("Toaster")
NativeToaster.toast({
title: "",
message,
type,
duration: options.duration ? options.duration / 1000 : 1.5,
} as NativeToasterOptions)
return
}

toastInstance.show({
type,
message,
Expand Down
6 changes: 1 addition & 5 deletions apps/mobile/src/screens/(headless)/debug.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,7 @@ export default function DebugPanel() {
{
title: "Toast",
onPress: () => {
toast.show({
message: "Hello, world!".repeat(10),
type: "success",
variant: "center-replace",
})
toast.error("Hello, world!".repeat(10))
},
},
{
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/src/screens/(headless)/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useWhoami } from "@/src/store/user/hooks"
export default function LoginPage() {
const whoami = useWhoami()

if (whoami?.id) {
if (whoami?.id && !__DEV__) {
return <Redirect href="/" />
}

Expand Down
1 change: 1 addition & 0 deletions apps/mobile/src/screens/(stack)/(tabs)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ export default function Index() {
useEffect(() => {
prepareEntryRenderWebView()
}, [])

return <EntryList />
}
58 changes: 29 additions & 29 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 8dbb634

Please sign in to comment.