feat(mobile): add native iOS toast module with SPIndicator
Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
parent
7cfa888a9e
commit
8dbb6343fb
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"platforms": ["apple", "android"],
|
||||
"apple": {
|
||||
"modules": ["SharedWebViewModule", "HelperModule"]
|
||||
"modules": ["SharedWebViewModule", "HelperModule", "ToasterModule"]
|
||||
},
|
||||
"android": {
|
||||
"modules": []
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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="/" />
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,5 +7,6 @@ export default function Index() {
|
|||
useEffect(() => {
|
||||
prepareEntryRenderWebView()
|
||||
}, [])
|
||||
|
||||
return <EntryList />
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4004,8 +4004,8 @@ packages:
|
|||
'@radix-ui/react-avatar@1.1.3':
|
||||
resolution: {integrity: sha512-Paen00T4P8L8gd9bNsRMw7Cbaz85oxiv+hzomsRZgFm2byltPFDtfcoqlWJ8GyZlIBWgLssJlzLCnKU0G0302g==}
|
||||
peerDependencies:
|
||||
'@types/react': 18.3.12
|
||||
'@types/react-dom': 18.3.1
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
|
|
@ -4057,9 +4057,9 @@ packages:
|
|||
'@radix-ui/react-context-menu@2.2.6':
|
||||
resolution: {integrity: sha512-aUP99QZ3VU84NPsHeaFt4cQUNgJqFsLLOt/RbbWXszZ6MP0DpDyjkFZORr4RpAEx3sUBk+Kc8h13yGtC5Qw8dg==}
|
||||
peerDependencies:
|
||||
'@types/react': 18.3.12
|
||||
'@types/react-dom': 18.3.1
|
||||
react: 18.3.1
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
|
|
@ -4070,7 +4070,7 @@ packages:
|
|||
'@radix-ui/react-context@1.1.1':
|
||||
resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==}
|
||||
peerDependencies:
|
||||
'@types/react': npm:@types/react@^18.3.12
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
|
|
@ -4101,8 +4101,8 @@ packages:
|
|||
'@radix-ui/react-dismissable-layer@1.1.5':
|
||||
resolution: {integrity: sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg==}
|
||||
peerDependencies:
|
||||
'@types/react': 18.3.12
|
||||
'@types/react-dom': 18.3.1
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
|
|
@ -4127,7 +4127,7 @@ packages:
|
|||
'@radix-ui/react-focus-guards@1.1.1':
|
||||
resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==}
|
||||
peerDependencies:
|
||||
'@types/react': npm:@types/react@^18.3.12
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
|
|
@ -4184,8 +4184,8 @@ packages:
|
|||
'@radix-ui/react-menu@2.1.6':
|
||||
resolution: {integrity: sha512-tBBb5CXDJW3t2mo9WlO7r6GTmWV0F0uzHZVFmlRmYpiSK1CDU5IKojP1pm7oknpBOrFZx/YgBRW9oorPO2S/Lg==}
|
||||
peerDependencies:
|
||||
'@types/react': 18.3.12
|
||||
'@types/react-dom': 18.3.1
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
|
|
@ -4223,8 +4223,8 @@ packages:
|
|||
'@radix-ui/react-popper@1.2.2':
|
||||
resolution: {integrity: sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA==}
|
||||
peerDependencies:
|
||||
'@types/react': 18.3.12
|
||||
'@types/react-dom': 18.3.1
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
|
|
@ -4236,8 +4236,8 @@ packages:
|
|||
'@radix-ui/react-portal@1.1.4':
|
||||
resolution: {integrity: sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA==}
|
||||
peerDependencies:
|
||||
'@types/react': 18.3.12
|
||||
'@types/react-dom': 18.3.1
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
|
|
@ -4288,8 +4288,8 @@ packages:
|
|||
'@radix-ui/react-roving-focus@1.1.2':
|
||||
resolution: {integrity: sha512-zgMQWkNO169GtGqRvYrzb0Zf8NhMHS2DuEB/TiEmVnpr5OqPU3i8lfbxaAmC2J/KYuIQxyoQQ6DxepyXp61/xw==}
|
||||
peerDependencies:
|
||||
'@types/react': 18.3.12
|
||||
'@types/react-dom': 18.3.1
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
|
|
@ -4406,7 +4406,7 @@ packages:
|
|||
'@radix-ui/react-use-callback-ref@1.1.0':
|
||||
resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==}
|
||||
peerDependencies:
|
||||
'@types/react': npm:@types/react@^18.3.12
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
|
|
@ -4415,7 +4415,7 @@ packages:
|
|||
'@radix-ui/react-use-controllable-state@1.1.0':
|
||||
resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==}
|
||||
peerDependencies:
|
||||
'@types/react': npm:@types/react@^18.3.12
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
|
|
@ -4469,8 +4469,8 @@ packages:
|
|||
'@radix-ui/react-visually-hidden@1.1.2':
|
||||
resolution: {integrity: sha512-1SzA4ns2M1aRlvxErqhLHsBHoS5eI5UUcI2awAMgGUp4LoaoWOKYmvqDY2s/tltuPkh3Yk77YF/r3IRj+Amx4Q==}
|
||||
peerDependencies:
|
||||
'@types/react': 18.3.12
|
||||
'@types/react-dom': 18.3.1
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
|
|
@ -4552,7 +4552,7 @@ packages:
|
|||
'@react-native-menu/menu@1.2.2':
|
||||
resolution: {integrity: sha512-Uk65PAhwNkCVBAqJu5t2H9biV+m0JLwJc7m3v2X2A/W8SFJmUqYabBsLH4fOWKI3a7kkR9QDT6HruliIKSfM8w==}
|
||||
peerDependencies:
|
||||
react: 18.3.1
|
||||
react: '*'
|
||||
react-native: '*'
|
||||
|
||||
'@react-native-picker/picker@2.11.0':
|
||||
|
|
@ -5263,7 +5263,7 @@ packages:
|
|||
'@tanstack/react-query@5.66.7':
|
||||
resolution: {integrity: sha512-qd3q/tUpF2K1xItfPZddk1k/8pSXnovg41XyCqJgPoyYEirMBtB0sVEVVQ/CsAOngzgWtBPXimVf4q4kM9uO6A==}
|
||||
peerDependencies:
|
||||
react: 18.3.1
|
||||
react: ^18 || ^19
|
||||
|
||||
'@tanstack/react-virtual@3.13.0':
|
||||
resolution: {integrity: sha512-CchF0NlLIowiM2GxtsoKBkXA4uqSnY2KvnXo+kyUFD4a4ll6+J0qzoRsUPMwXV/H26lRsxgJIr/YmjYum2oEjg==}
|
||||
|
|
@ -8486,7 +8486,7 @@ packages:
|
|||
resolution: {integrity: sha512-LUnfrddmee1xLOkyG2NN1l9xQbtvMX3fbM1brEGHg0SKSndvjod3FQdhTzZEYAariqW2RSxQR8v1IsheIoLQXg==}
|
||||
peerDependencies:
|
||||
expo: '*'
|
||||
react-native: '*'
|
||||
react-native: npm:react-native@0.77.0
|
||||
|
||||
expo-module-scripts@4.0.4:
|
||||
resolution: {integrity: sha512-ytFufVi7HTFLxxf8fbtz3DJuegq3489WPk9pz8Yqm3tYfZ+6/yufPYxLvk6N8b4yBW05oNr/Cqp7MWLpm0xPcA==}
|
||||
|
|
@ -12422,7 +12422,7 @@ packages:
|
|||
resolution: {integrity: sha512-sc5qgX4YtM6IxjtosjgQLdsal120MvU+YWs0F2MdgQWijps22AXLDCUoBnZZ8vxVhVyJ2WnnIPrmtVBvVJjSuQ==}
|
||||
engines: {node: '>= 18.0.0'}
|
||||
peerDependencies:
|
||||
react: 18.3.1
|
||||
react: '*'
|
||||
react-native: '*'
|
||||
react-native-gesture-handler: '>=2.0.0'
|
||||
react-native-reanimated: '>=3.0.0'
|
||||
|
|
@ -12536,7 +12536,7 @@ packages:
|
|||
resolution: {integrity: sha512-9PPREdwH3tMR8otUK1voiFw9AFNtC37Del2fZwbCMBKjOyClC+f3Fwp7KqUouP0A5c3zjEc3T8TetBGQGPQE9A==}
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
react-native: npm:react-native@0.77.0
|
||||
react-native: '*'
|
||||
react-native-gesture-handler: '*'
|
||||
react-native-reanimated: '*'
|
||||
|
||||
|
|
@ -12550,7 +12550,7 @@ packages:
|
|||
resolution: {integrity: sha512-E5N/eK/+HtAVJUAzXpm1cWz8ROheV9jb0TI6h2bM+333U+DWibTTnT2T1122FkCoXLXIYavtm2FR2if+5jH8cA==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.6'
|
||||
react-native: npm:react-native@0.77.0
|
||||
react-native: '>=0.60.0-rc.2'
|
||||
react-native-windows: '>=0.63.0'
|
||||
shaka-player: ^4.7.9
|
||||
peerDependenciesMeta:
|
||||
|
|
@ -12564,13 +12564,13 @@ packages:
|
|||
peerDependencies:
|
||||
nativewind: '>=4.1.0'
|
||||
react: '>=18.0.0'
|
||||
react-native: '>=0.76.0'
|
||||
react-native: npm:react-native@0.77.0
|
||||
tailwindcss: '>=3.0.0'
|
||||
|
||||
react-native-volume-manager@2.0.8:
|
||||
resolution: {integrity: sha512-aZM47/mYkdQ4CbXpKYO6Ajiczv7fxbQXZ9c0H8gRuQUaS3OCz/MZABer6o9aDWq0KMNsQ7q7GVFLRPnSSeeMmw==}
|
||||
peerDependencies:
|
||||
react: 18.3.1
|
||||
react: '*'
|
||||
react-native: '*'
|
||||
|
||||
react-native-web@0.19.13:
|
||||
|
|
|
|||
Loading…
Reference in New Issue