@matiks/rn-app-state 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/FLOW.md +239 -0
  2. package/NitroAppState.podspec +31 -0
  3. package/README.md +72 -0
  4. package/android/CMakeLists.txt +29 -0
  5. package/android/build.gradle +140 -0
  6. package/android/fix-prefab.gradle +51 -0
  7. package/android/gradle.properties +5 -0
  8. package/android/src/main/AndroidManifest.xml +2 -0
  9. package/android/src/main/cpp/cpp-adapter.cpp +6 -0
  10. package/android/src/main/java/com/margelo/nitro/appstate/HybridAppState.kt +188 -0
  11. package/android/src/main/java/com/margelo/nitro/appstate/NitroAppStatePackage.kt +18 -0
  12. package/ios/HybridAppState.swift +224 -0
  13. package/nitro.json +24 -0
  14. package/nitrogen/generated/.gitattributes +1 -0
  15. package/nitrogen/generated/android/NitroAppState+autolinking.cmake +81 -0
  16. package/nitrogen/generated/android/NitroAppState+autolinking.gradle +27 -0
  17. package/nitrogen/generated/android/NitroAppStateOnLoad.cpp +48 -0
  18. package/nitrogen/generated/android/NitroAppStateOnLoad.hpp +25 -0
  19. package/nitrogen/generated/android/c++/JAppStateChangeEvent.hpp +57 -0
  20. package/nitrogen/generated/android/c++/JAppStateListener.hpp +67 -0
  21. package/nitrogen/generated/android/c++/JFunc_void.hpp +75 -0
  22. package/nitrogen/generated/android/c++/JFunc_void_AppStateChangeEvent.hpp +78 -0
  23. package/nitrogen/generated/android/c++/JHybridAppStateModuleSpec.cpp +84 -0
  24. package/nitrogen/generated/android/c++/JHybridAppStateModuleSpec.hpp +68 -0
  25. package/nitrogen/generated/android/kotlin/com/margelo/nitro/appstate/AppStateChangeEvent.kt +38 -0
  26. package/nitrogen/generated/android/kotlin/com/margelo/nitro/appstate/AppStateListener.kt +42 -0
  27. package/nitrogen/generated/android/kotlin/com/margelo/nitro/appstate/Func_void.kt +80 -0
  28. package/nitrogen/generated/android/kotlin/com/margelo/nitro/appstate/Func_void_AppStateChangeEvent.kt +80 -0
  29. package/nitrogen/generated/android/kotlin/com/margelo/nitro/appstate/HybridAppStateModuleSpec.kt +72 -0
  30. package/nitrogen/generated/android/kotlin/com/margelo/nitro/appstate/NitroAppStateOnLoad.kt +35 -0
  31. package/nitrogen/generated/ios/NitroAppState+autolinking.rb +60 -0
  32. package/nitrogen/generated/ios/NitroAppState-Swift-Cxx-Bridge.cpp +49 -0
  33. package/nitrogen/generated/ios/NitroAppState-Swift-Cxx-Bridge.hpp +112 -0
  34. package/nitrogen/generated/ios/NitroAppState-Swift-Cxx-Umbrella.hpp +51 -0
  35. package/nitrogen/generated/ios/NitroAppStateAutolinking.mm +33 -0
  36. package/nitrogen/generated/ios/NitroAppStateAutolinking.swift +26 -0
  37. package/nitrogen/generated/ios/c++/HybridAppStateModuleSpecSwift.cpp +11 -0
  38. package/nitrogen/generated/ios/c++/HybridAppStateModuleSpecSwift.hpp +106 -0
  39. package/nitrogen/generated/ios/swift/AppStateChangeEvent.swift +29 -0
  40. package/nitrogen/generated/ios/swift/AppStateListener.swift +37 -0
  41. package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
  42. package/nitrogen/generated/ios/swift/Func_void_AppStateChangeEvent.swift +46 -0
  43. package/nitrogen/generated/ios/swift/HybridAppStateModuleSpec.swift +57 -0
  44. package/nitrogen/generated/ios/swift/HybridAppStateModuleSpec_cxx.swift +172 -0
  45. package/nitrogen/generated/shared/c++/AppStateChangeEvent.hpp +83 -0
  46. package/nitrogen/generated/shared/c++/AppStateListener.hpp +83 -0
  47. package/nitrogen/generated/shared/c++/HybridAppStateModuleSpec.cpp +24 -0
  48. package/nitrogen/generated/shared/c++/HybridAppStateModuleSpec.hpp +70 -0
  49. package/package.json +54 -0
  50. package/src/index.ts +2 -0
  51. package/src/specs/AppState.nitro.ts +57 -0
  52. package/src/useNitroAppState/index.ts +2 -0
  53. package/src/useNitroAppState/types.ts +21 -0
  54. package/src/useNitroAppState/useNitroAppState.native.ts +142 -0
  55. package/src/useNitroAppState/useNitroAppState.web.ts +56 -0
@@ -0,0 +1,142 @@
1
+ import { useState, useEffect, useRef, useCallback } from 'react'
2
+ import { NitroModules } from 'react-native-nitro-modules'
3
+ import type {
4
+ AppStateModule,
5
+ AppStateChangeEvent,
6
+ } from '../specs/AppState.nitro'
7
+ import type { AppStateType, NitroAppStateResult } from './types'
8
+
9
+ // Lazily create the HybridObject singleton.
10
+ // This is safe because on native, NitroModules.createHybridObject is synchronous.
11
+ let _module: AppStateModule | null = null
12
+
13
+ function getModule(): AppStateModule {
14
+ if (_module == null) {
15
+ _module = NitroModules.createHybridObject<AppStateModule>('AppStateModule')
16
+ }
17
+ return _module
18
+ }
19
+
20
+ /**
21
+ * A reactive React hook that provides the current app lifecycle state
22
+ * using native code via Nitro Modules (JSI).
23
+ *
24
+ * States: 'active' | 'background' | 'killed'
25
+ *
26
+ * - 'active' and 'background' are live states that update reactively.
27
+ * - 'killed' is only the initial state on cold start if the previous
28
+ * session was force-killed (it transitions to 'active' immediately).
29
+ *
30
+ * @example
31
+ * ```tsx
32
+ * const { appState, wasKilledLastSession } = useNitroAppState()
33
+ *
34
+ * useEffect(() => {
35
+ * if (appState === 'active') {
36
+ * // Refresh data, resume animations, etc.
37
+ * }
38
+ * }, [appState])
39
+ * ```
40
+ */
41
+ export function useNitroAppState(): NitroAppStateResult {
42
+ const moduleRef = useRef<AppStateModule | null>(null)
43
+
44
+ // Initialize module lazily
45
+ if (moduleRef.current == null) {
46
+ moduleRef.current = getModule()
47
+ }
48
+
49
+ const mod = moduleRef.current
50
+
51
+ // Determine initial state
52
+ const getInitialState = (): AppStateType => {
53
+ try {
54
+ if (mod.wasKilledLastSession) {
55
+ return 'killed'
56
+ }
57
+ return mod.getCurrentState() as AppStateType
58
+ } catch {
59
+ return 'active'
60
+ }
61
+ }
62
+
63
+ const [appState, setAppState] = useState<AppStateType>(getInitialState)
64
+ const [wasKilledLastSession] = useState<boolean>(() => {
65
+ try {
66
+ return mod.wasKilledLastSession
67
+ } catch {
68
+ return false
69
+ }
70
+ })
71
+
72
+ // Ref to track the latest appState for use in stable callbacks.
73
+ const appStateRef = useRef<AppStateType>(appState)
74
+ appStateRef.current = appState
75
+
76
+ useEffect(() => {
77
+ let listener: { remove: () => void } | null = null
78
+ let isMounted = true
79
+
80
+ try {
81
+ listener = mod.addListener((event: AppStateChangeEvent) => {
82
+ if (isMounted) {
83
+ const newState = event.state as AppStateType
84
+ setAppState(newState)
85
+ }
86
+ })
87
+ } catch (e) {
88
+ console.warn('[useNitroAppState] Failed to add listener:', e)
89
+ }
90
+
91
+ // If initial state was 'killed', transition to the real native state
92
+ // after mount. We use setTimeout with a mounted guard to avoid
93
+ // updating state on an unmounted component.
94
+ if (appStateRef.current === 'killed') {
95
+ const timer = setTimeout(() => {
96
+ if (isMounted) {
97
+ try {
98
+ const currentNativeState = mod.getCurrentState() as AppStateType
99
+ setAppState(currentNativeState)
100
+ } catch {
101
+ setAppState('active')
102
+ }
103
+ }
104
+ }, 0)
105
+
106
+ return () => {
107
+ isMounted = false
108
+ clearTimeout(timer)
109
+ try {
110
+ listener?.remove()
111
+ } catch {
112
+ // Ignore cleanup errors
113
+ }
114
+ }
115
+ }
116
+
117
+ return () => {
118
+ isMounted = false
119
+ try {
120
+ listener?.remove()
121
+ } catch {
122
+ // Ignore cleanup errors
123
+ }
124
+ }
125
+ }, [mod])
126
+
127
+ // Stable callback that doesn't change identity on every state transition.
128
+ // Uses the module's synchronous native call instead of stale JS state.
129
+ const getStateOnNotification = useCallback((): AppStateType => {
130
+ try {
131
+ return mod.getStateOnNotification() as AppStateType
132
+ } catch {
133
+ return appStateRef.current
134
+ }
135
+ }, [mod])
136
+
137
+ return {
138
+ appState,
139
+ wasKilledLastSession,
140
+ getStateOnNotification,
141
+ }
142
+ }
@@ -0,0 +1,56 @@
1
+ import { useState, useEffect, useCallback } from 'react';
2
+ import { AppState } from 'react-native';
3
+ import type { AppStateType, NitroAppStateResult } from './types';
4
+
5
+ /**
6
+ * Web fallback for useNitroAppState.
7
+ *
8
+ * On web, NitroModules.createHybridObject() is not available.
9
+ * This fallback uses the standard React Native AppState API
10
+ * (which works on web via react-native-web's Page Visibility API).
11
+ *
12
+ * - 'killed' is never reported on web (no concept of app termination).
13
+ * - wasKilledLastSession is always false on web.
14
+ */
15
+ export function useNitroAppState(): NitroAppStateResult {
16
+ const [appState, setAppState] = useState<AppStateType>(() => {
17
+ try {
18
+ const current = AppState.currentState;
19
+ if (current === 'active') return 'active';
20
+ return 'background';
21
+ } catch {
22
+ return 'active';
23
+ }
24
+ });
25
+
26
+ useEffect(() => {
27
+ const subscription = AppState.addEventListener('change', (nextAppState) => {
28
+ if (nextAppState === 'active') {
29
+ setAppState('active');
30
+ } else {
31
+ // 'inactive', 'background', 'unknown', 'extension' all map to 'background'
32
+ setAppState('background');
33
+ }
34
+ });
35
+
36
+ return () => {
37
+ subscription.remove();
38
+ };
39
+ }, []);
40
+
41
+ const getStateOnNotification = useCallback((): AppStateType => {
42
+ try {
43
+ const current = AppState.currentState;
44
+ if (current === 'active') return 'active';
45
+ return 'background';
46
+ } catch {
47
+ return appState;
48
+ }
49
+ }, [appState]);
50
+
51
+ return {
52
+ appState,
53
+ wasKilledLastSession: false,
54
+ getStateOnNotification,
55
+ };
56
+ }