react-native-step-tracker-pro 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 (110) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +148 -0
  3. package/android/build.gradle +119 -0
  4. package/android/consumer-rules.pro +13 -0
  5. package/android/gradle.properties +3 -0
  6. package/android/src/main/AndroidManifest.xml +78 -0
  7. package/android/src/main/java/com/steptrackerpro/StepTrackerProModule.kt +598 -0
  8. package/android/src/main/java/com/steptrackerpro/StepTrackerProPackage.kt +26 -0
  9. package/android/src/main/java/com/steptrackerpro/core/DateKeys.kt +90 -0
  10. package/android/src/main/java/com/steptrackerpro/core/GoalTracker.kt +66 -0
  11. package/android/src/main/java/com/steptrackerpro/core/MetricsCalculator.kt +48 -0
  12. package/android/src/main/java/com/steptrackerpro/core/Models.kt +73 -0
  13. package/android/src/main/java/com/steptrackerpro/core/StepCounterEngine.kt +338 -0
  14. package/android/src/main/java/com/steptrackerpro/core/StepStateStore.kt +137 -0
  15. package/android/src/main/java/com/steptrackerpro/core/StepTrackerConfig.kt +184 -0
  16. package/android/src/main/java/com/steptrackerpro/core/StepTrackerCore.kt +280 -0
  17. package/android/src/main/java/com/steptrackerpro/db/Entities.kt +67 -0
  18. package/android/src/main/java/com/steptrackerpro/db/StepDao.kt +118 -0
  19. package/android/src/main/java/com/steptrackerpro/db/StepDatabase.kt +60 -0
  20. package/android/src/main/java/com/steptrackerpro/db/StepRepository.kt +146 -0
  21. package/android/src/main/java/com/steptrackerpro/health/HealthConnectManager.kt +198 -0
  22. package/android/src/main/java/com/steptrackerpro/health/HealthPermissionActivity.kt +99 -0
  23. package/android/src/main/java/com/steptrackerpro/service/BootReceiver.kt +54 -0
  24. package/android/src/main/java/com/steptrackerpro/service/NotificationActionReceiver.kt +17 -0
  25. package/android/src/main/java/com/steptrackerpro/service/NotificationFactory.kt +170 -0
  26. package/android/src/main/java/com/steptrackerpro/service/ServiceCommands.kt +42 -0
  27. package/android/src/main/java/com/steptrackerpro/service/StepTrackerService.kt +409 -0
  28. package/android/src/main/java/com/steptrackerpro/sync/SyncScheduler.kt +102 -0
  29. package/android/src/main/java/com/steptrackerpro/sync/SyncWorkers.kt +175 -0
  30. package/android/src/main/java/com/steptrackerpro/util/BatteryOptimizationHelper.kt +94 -0
  31. package/android/src/main/java/com/steptrackerpro/util/Bridge.kt +149 -0
  32. package/android/src/main/java/com/steptrackerpro/util/PermissionHelper.kt +92 -0
  33. package/android/src/main/java/com/steptrackerpro/util/StepEventBus.kt +53 -0
  34. package/android/src/main/res/drawable/stp_ic_steps.xml +9 -0
  35. package/android/src/main/res/values/strings.xml +9 -0
  36. package/android/src/main/res/values/styles.xml +8 -0
  37. package/android/src/newarch/java/com/steptrackerpro/StepTrackerProSpec.kt +10 -0
  38. package/android/src/oldarch/java/com/steptrackerpro/StepTrackerProSpec.kt +60 -0
  39. package/docs/API.md +329 -0
  40. package/docs/ARCHITECTURE.md +203 -0
  41. package/docs/INSTALLATION.md +175 -0
  42. package/docs/PLAY_STORE_COMPLIANCE.md +122 -0
  43. package/docs/TESTING.md +312 -0
  44. package/docs/TROUBLESHOOTING.md +96 -0
  45. package/lib/commonjs/NativeStepTrackerPro.js +14 -0
  46. package/lib/commonjs/NativeStepTrackerPro.js.map +1 -0
  47. package/lib/commonjs/StepTracker.js +290 -0
  48. package/lib/commonjs/StepTracker.js.map +1 -0
  49. package/lib/commonjs/constants.js +30 -0
  50. package/lib/commonjs/constants.js.map +1 -0
  51. package/lib/commonjs/errors.js +24 -0
  52. package/lib/commonjs/errors.js.map +1 -0
  53. package/lib/commonjs/hooks/index.js +20 -0
  54. package/lib/commonjs/hooks/index.js.map +1 -0
  55. package/lib/commonjs/hooks/useStepStats.js +51 -0
  56. package/lib/commonjs/hooks/useStepStats.js.map +1 -0
  57. package/lib/commonjs/hooks/useStepTracker.js +134 -0
  58. package/lib/commonjs/hooks/useStepTracker.js.map +1 -0
  59. package/lib/commonjs/index.js +64 -0
  60. package/lib/commonjs/index.js.map +1 -0
  61. package/lib/commonjs/package.json +1 -0
  62. package/lib/commonjs/types.js +2 -0
  63. package/lib/commonjs/types.js.map +1 -0
  64. package/lib/module/NativeStepTrackerPro.js +12 -0
  65. package/lib/module/NativeStepTrackerPro.js.map +1 -0
  66. package/lib/module/StepTracker.js +284 -0
  67. package/lib/module/StepTracker.js.map +1 -0
  68. package/lib/module/constants.js +26 -0
  69. package/lib/module/constants.js.map +1 -0
  70. package/lib/module/errors.js +18 -0
  71. package/lib/module/errors.js.map +1 -0
  72. package/lib/module/hooks/index.js +5 -0
  73. package/lib/module/hooks/index.js.map +1 -0
  74. package/lib/module/hooks/useStepStats.js +46 -0
  75. package/lib/module/hooks/useStepStats.js.map +1 -0
  76. package/lib/module/hooks/useStepTracker.js +129 -0
  77. package/lib/module/hooks/useStepTracker.js.map +1 -0
  78. package/lib/module/index.js +7 -0
  79. package/lib/module/index.js.map +1 -0
  80. package/lib/module/package.json +1 -0
  81. package/lib/module/types.js +2 -0
  82. package/lib/module/types.js.map +1 -0
  83. package/lib/typescript/NativeStepTrackerPro.d.ts +50 -0
  84. package/lib/typescript/NativeStepTrackerPro.d.ts.map +1 -0
  85. package/lib/typescript/StepTracker.d.ts +76 -0
  86. package/lib/typescript/StepTracker.d.ts.map +1 -0
  87. package/lib/typescript/constants.d.ts +10 -0
  88. package/lib/typescript/constants.d.ts.map +1 -0
  89. package/lib/typescript/errors.d.ts +8 -0
  90. package/lib/typescript/errors.d.ts.map +1 -0
  91. package/lib/typescript/hooks/index.d.ts +5 -0
  92. package/lib/typescript/hooks/index.d.ts.map +1 -0
  93. package/lib/typescript/hooks/useStepStats.d.ts +11 -0
  94. package/lib/typescript/hooks/useStepStats.d.ts.map +1 -0
  95. package/lib/typescript/hooks/useStepTracker.d.ts +26 -0
  96. package/lib/typescript/hooks/useStepTracker.d.ts.map +1 -0
  97. package/lib/typescript/index.d.ts +7 -0
  98. package/lib/typescript/index.d.ts.map +1 -0
  99. package/lib/typescript/types.d.ts +183 -0
  100. package/lib/typescript/types.d.ts.map +1 -0
  101. package/package.json +79 -0
  102. package/src/NativeStepTrackerPro.ts +66 -0
  103. package/src/StepTracker.ts +410 -0
  104. package/src/constants.ts +43 -0
  105. package/src/errors.ts +31 -0
  106. package/src/hooks/index.ts +4 -0
  107. package/src/hooks/useStepStats.ts +55 -0
  108. package/src/hooks/useStepTracker.ts +167 -0
  109. package/src/index.ts +27 -0
  110. package/src/types.ts +203 -0
@@ -0,0 +1,167 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { AppState } from 'react-native';
3
+ import StepTracker, { isSupported } from '../StepTracker';
4
+ import type {
5
+ GoalReachedEvent,
6
+ StepSnapshot,
7
+ StepTrackerConfig,
8
+ TrackingState,
9
+ } from '../types';
10
+
11
+ export interface UseStepTrackerOptions extends StepTrackerConfig {
12
+ /** Initialise and start tracking as soon as permissions allow. Default false. */
13
+ autoStart?: boolean;
14
+ /** Refetch the snapshot when the app returns to the foreground. Default true. */
15
+ refreshOnForeground?: boolean;
16
+ onGoalReached?: (event: GoalReachedEvent) => void;
17
+ }
18
+
19
+ export interface UseStepTrackerResult {
20
+ snapshot: StepSnapshot | null;
21
+ state: TrackingState;
22
+ ready: boolean;
23
+ error: Error | null;
24
+ start: () => Promise<void>;
25
+ pause: () => Promise<void>;
26
+ resume: () => Promise<void>;
27
+ stop: () => Promise<void>;
28
+ refresh: () => Promise<void>;
29
+ requestPermissions: () => Promise<boolean>;
30
+ }
31
+
32
+ /**
33
+ * Wires the tracker into a component: initialises once, subscribes to
34
+ * `stepsChanged`, and re-reads the snapshot when the app is foregrounded.
35
+ */
36
+ export function useStepTracker(
37
+ options: UseStepTrackerOptions = {}
38
+ ): UseStepTrackerResult {
39
+ const {
40
+ autoStart = false,
41
+ refreshOnForeground = true,
42
+ onGoalReached,
43
+ ...config
44
+ } = options;
45
+
46
+ const [snapshot, setSnapshot] = useState<StepSnapshot | null>(null);
47
+ const [state, setState] = useState<TrackingState>('idle');
48
+ const [ready, setReady] = useState(false);
49
+ const [error, setError] = useState<Error | null>(null);
50
+
51
+ const configRef = useRef(config);
52
+ configRef.current = config;
53
+ const goalRef = useRef(onGoalReached);
54
+ goalRef.current = onGoalReached;
55
+
56
+ const refresh = useCallback(async () => {
57
+ if (!isSupported()) return;
58
+ try {
59
+ setSnapshot(await StepTracker.getTodaySteps());
60
+ setState(await StepTracker.getTrackingState());
61
+ } catch (e) {
62
+ setError(e as Error);
63
+ }
64
+ }, []);
65
+
66
+ useEffect(() => {
67
+ let cancelled = false;
68
+ if (!isSupported()) {
69
+ setState('unsupported');
70
+ setReady(true);
71
+ return;
72
+ }
73
+
74
+ (async () => {
75
+ try {
76
+ const initial = await StepTracker.initialize(configRef.current);
77
+ if (cancelled) return;
78
+ setSnapshot(initial);
79
+ setState(initial.state);
80
+ if (autoStart) {
81
+ const perms = await StepTracker.checkPermissions();
82
+ if (perms.allGranted) {
83
+ const started = await StepTracker.startTracking();
84
+ if (!cancelled) {
85
+ setSnapshot(started);
86
+ setState(started.state);
87
+ }
88
+ }
89
+ }
90
+ } catch (e) {
91
+ if (!cancelled) setError(e as Error);
92
+ } finally {
93
+ if (!cancelled) setReady(true);
94
+ }
95
+ })();
96
+
97
+ const stepsSub = StepTracker.addListener('stepsChanged', (payload) => {
98
+ setSnapshot(payload);
99
+ });
100
+ const stateSub = StepTracker.addListener('trackingStateChanged', (payload) => {
101
+ setState(payload.state);
102
+ });
103
+ const goalSub = StepTracker.addListener('goalReached', (payload) => {
104
+ goalRef.current?.(payload);
105
+ });
106
+ const daySub = StepTracker.addListener('dayChanged', () => {
107
+ void refresh();
108
+ });
109
+
110
+ return () => {
111
+ cancelled = true;
112
+ stepsSub.remove();
113
+ stateSub.remove();
114
+ goalSub.remove();
115
+ daySub.remove();
116
+ };
117
+ }, [autoStart, refresh]);
118
+
119
+ useEffect(() => {
120
+ if (!refreshOnForeground) return;
121
+ const sub = AppState.addEventListener('change', (next) => {
122
+ if (next === 'active') void refresh();
123
+ });
124
+ return () => sub.remove();
125
+ }, [refresh, refreshOnForeground]);
126
+
127
+ // Each action is memoised separately. Building them inline in the returned
128
+ // object handed callers a new function identity on every render, which loops
129
+ // any `useEffect` that lists one in its dependency array.
130
+ const run = useCallback(async (fn: () => Promise<StepSnapshot>) => {
131
+ try {
132
+ const result = await fn();
133
+ setSnapshot(result);
134
+ setState(result.state);
135
+ } catch (e) {
136
+ setError(e as Error);
137
+ }
138
+ }, []);
139
+
140
+ const start = useCallback(() => run(() => StepTracker.startTracking()), [run]);
141
+ const pause = useCallback(() => run(() => StepTracker.pauseTracking()), [run]);
142
+ const resume = useCallback(() => run(() => StepTracker.resumeTracking()), [run]);
143
+ const stop = useCallback(() => run(() => StepTracker.stopTracking()), [run]);
144
+
145
+ const requestPermissions = useCallback(async () => {
146
+ try {
147
+ const result = await StepTracker.requestPermissions();
148
+ return result.allGranted;
149
+ } catch (e) {
150
+ setError(e as Error);
151
+ return false;
152
+ }
153
+ }, []);
154
+
155
+ return {
156
+ snapshot,
157
+ state,
158
+ ready,
159
+ error,
160
+ refresh,
161
+ start,
162
+ pause,
163
+ resume,
164
+ stop,
165
+ requestPermissions,
166
+ };
167
+ }
package/src/index.ts ADDED
@@ -0,0 +1,27 @@
1
+ export { StepTracker, StepTracker as default, isSupported } from './StepTracker';
2
+ export { StepTrackerError } from './errors';
3
+ export type { StepTrackerErrorCode } from './errors';
4
+ export { DEFAULT_CONFIG, STRIDE_COEFFICIENT } from './constants';
5
+ export * from './hooks';
6
+ export type {
7
+ DayChangedEvent,
8
+ DayRecord,
9
+ DeviceCapabilities,
10
+ EventSubscription,
11
+ GoalProgressEvent,
12
+ GoalReachedEvent,
13
+ HealthConnectStatus,
14
+ PermissionStatus,
15
+ RangeOptions,
16
+ RangeStats,
17
+ SensorSource,
18
+ Sex,
19
+ StepSnapshot,
20
+ StepTrackerConfig,
21
+ StepTrackerEvent,
22
+ StepTrackerEventMap,
23
+ StepsChangedEvent,
24
+ SyncEvent,
25
+ TrackingState,
26
+ TrackingStateEvent,
27
+ } from './types';
package/src/types.ts ADDED
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Public type definitions for react-native-step-tracker-pro.
3
+ */
4
+
5
+ /** Biological sex is only used to pick a stride-length coefficient. */
6
+ export type Sex = 'male' | 'female' | 'unspecified';
7
+
8
+ /** Which hardware sensor the service ended up using. */
9
+ export type SensorSource = 'step_counter' | 'step_detector' | 'none';
10
+
11
+ export type TrackingState =
12
+ | 'idle'
13
+ | 'running'
14
+ | 'paused'
15
+ | 'stopped'
16
+ | 'unsupported';
17
+
18
+ export interface StepTrackerConfig {
19
+ /** Height in centimetres. Used for stride length. Default 170. */
20
+ height?: number;
21
+ /** Weight in kilograms. Used for calorie estimation. Default 70. */
22
+ weight?: number;
23
+ /**
24
+ * Overrides the derived stride length, in metres. Leave unset to derive it
25
+ * from `height` and `sex`.
26
+ */
27
+ strideLength?: number;
28
+ sex?: Sex;
29
+ /** Steps. Default 10000. */
30
+ dailyGoal?: number;
31
+ /** Steps. Defaults to dailyGoal * 7. */
32
+ weeklyGoal?: number;
33
+ /** Steps. Defaults to dailyGoal * 30. */
34
+ monthlyGoal?: number;
35
+ /** kcal per kg per km of walking. Default 0.57. */
36
+ calorieCoefficient?: number;
37
+ /** How many days of history to keep on device. Default 35. */
38
+ historyRetentionDays?: number;
39
+ /** Notification title. Supports {steps}, {goal}, {percent}, {distance}, {calories}. */
40
+ notificationTitle?: string;
41
+ notificationText?: string;
42
+ /** Drawable name in the host app, e.g. 'ic_stat_steps'. Falls back to a bundled icon. */
43
+ notificationIcon?: string;
44
+ /** Notification channel name shown in Android settings. */
45
+ notificationChannelName?: string;
46
+ /** Show Pause/Resume actions on the notification. Default true. */
47
+ notificationActions?: boolean;
48
+ /** Minimum ms between notification redraws. Default 1000. */
49
+ notificationThrottleMs?: number;
50
+ /** Minimum ms between JS `stepsChanged` events. Default 500. */
51
+ eventThrottleMs?: number;
52
+ /** Persist to SQLite after this many steps. Default 10. */
53
+ persistEveryNSteps?: number;
54
+ /** Mirror data into Health Connect when permissions are granted. Default true. */
55
+ healthConnectEnabled?: boolean;
56
+ /** Auto-write today's totals to Health Connect every N minutes. Default 30. 0 disables. */
57
+ healthConnectSyncIntervalMinutes?: number;
58
+ /** Optional HTTPS endpoint that unsynced day records get POSTed to. */
59
+ remoteSyncUrl?: string;
60
+ remoteSyncHeaders?: Record<string, string>;
61
+ /** Restart tracking automatically after device reboot. Default true. */
62
+ autoStartOnBoot?: boolean;
63
+ }
64
+
65
+ export interface StepSnapshot {
66
+ /** yyyy-MM-dd in the device timezone. */
67
+ date: string;
68
+ steps: number;
69
+ /** Metres. */
70
+ distance: number;
71
+ /** Kilocalories. */
72
+ calories: number;
73
+ dailyGoal: number;
74
+ /** 0..1, clamped. */
75
+ goalProgress: number;
76
+ goalReached: boolean;
77
+ state: TrackingState;
78
+ source: SensorSource;
79
+ /** Epoch ms of the last sensor sample. */
80
+ timestamp: number;
81
+ }
82
+
83
+ export interface DayRecord {
84
+ date: string;
85
+ steps: number;
86
+ distance: number;
87
+ calories: number;
88
+ /** Mirrored into Health Connect. */
89
+ synced: boolean;
90
+ /** Uploaded to `remoteSyncUrl`. Always false when no endpoint is configured. */
91
+ syncedRemote: boolean;
92
+ }
93
+
94
+ export interface RangeStats {
95
+ /** Inclusive yyyy-MM-dd. */
96
+ startDate: string;
97
+ /** Inclusive yyyy-MM-dd. */
98
+ endDate: string;
99
+ totalSteps: number;
100
+ totalDistance: number;
101
+ totalCalories: number;
102
+ averageSteps: number;
103
+ /** Days with at least one recorded step. */
104
+ activeDays: number;
105
+ bestDay: DayRecord | null;
106
+ /** Every day in the range, zero-filled, ascending. */
107
+ days: DayRecord[];
108
+ /** Present for weekly/monthly windows when a goal is configured. */
109
+ goal?: number;
110
+ goalProgress?: number;
111
+ }
112
+
113
+ export interface RangeOptions {
114
+ /**
115
+ * 'calendar' snaps to the current week (Mon–Sun), month or year.
116
+ * 'rolling' uses the last 7 / 30 / 365 days ending today.
117
+ * Default 'calendar'.
118
+ */
119
+ mode?: 'calendar' | 'rolling';
120
+ /** 0 = current period, 1 = previous period, and so on. Only for 'calendar'. */
121
+ offset?: number;
122
+ }
123
+
124
+ export interface PermissionStatus {
125
+ activityRecognition: boolean;
126
+ postNotifications: boolean;
127
+ /** True on API < 34 where the permission does not exist. */
128
+ foregroundServiceHealth: boolean;
129
+ /** All permissions required to start tracking are granted. */
130
+ allGranted: boolean;
131
+ }
132
+
133
+ export interface HealthConnectStatus {
134
+ /** Health Connect SDK is available on the device. */
135
+ available: boolean;
136
+ /** The user must install or update the Health Connect provider. */
137
+ requiresUpdate: boolean;
138
+ granted: boolean;
139
+ grantedPermissions: string[];
140
+ }
141
+
142
+ export interface DeviceCapabilities {
143
+ hasStepCounter: boolean;
144
+ hasStepDetector: boolean;
145
+ supported: boolean;
146
+ sdkInt: number;
147
+ manufacturer: string;
148
+ model: string;
149
+ }
150
+
151
+ export interface StepsChangedEvent extends StepSnapshot {}
152
+
153
+ export interface GoalReachedEvent {
154
+ type: 'daily' | 'weekly' | 'monthly';
155
+ goal: number;
156
+ steps: number;
157
+ date: string;
158
+ timestamp: number;
159
+ }
160
+
161
+ export interface GoalProgressEvent {
162
+ type: 'daily' | 'weekly' | 'monthly';
163
+ goal: number;
164
+ steps: number;
165
+ progress: number;
166
+ date: string;
167
+ }
168
+
169
+ export interface TrackingStateEvent {
170
+ state: TrackingState;
171
+ source: SensorSource;
172
+ reason?: string;
173
+ }
174
+
175
+ export interface SyncEvent {
176
+ target: 'health_connect' | 'remote';
177
+ syncedRecords: number;
178
+ failedRecords: number;
179
+ success: boolean;
180
+ error?: string;
181
+ }
182
+
183
+ export interface DayChangedEvent {
184
+ previousDate: string;
185
+ currentDate: string;
186
+ previousDaySteps: number;
187
+ }
188
+
189
+ export interface StepTrackerEventMap {
190
+ stepsChanged: StepsChangedEvent;
191
+ goalReached: GoalReachedEvent;
192
+ goalProgressChanged: GoalProgressEvent;
193
+ trackingStateChanged: TrackingStateEvent;
194
+ dayChanged: DayChangedEvent;
195
+ syncCompleted: SyncEvent;
196
+ error: { code: string; message: string };
197
+ }
198
+
199
+ export type StepTrackerEvent = keyof StepTrackerEventMap;
200
+
201
+ export interface EventSubscription {
202
+ remove(): void;
203
+ }