rn-persistent-timer 1.1.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/README.md +607 -0
  2. package/android/.gradle/7.4.2/checksums/checksums.lock +0 -0
  3. package/android/.gradle/7.4.2/fileChanges/last-build.bin +0 -0
  4. package/android/.gradle/7.4.2/fileHashes/fileHashes.lock +0 -0
  5. package/android/.gradle/7.4.2/gc.properties +0 -0
  6. package/android/.gradle/vcs-1/gc.properties +0 -0
  7. package/android/build.gradle +15 -0
  8. package/android/src/main/java/com/rnpersistenttimer/RNPersistentTimerModule.java +164 -0
  9. package/android/src/main/java/com/rnpersistenttimer/RNPersistentTimerPackage.java +27 -0
  10. package/android/src/main/java/com/rnpersistenttimer/TimerForegroundService.java +280 -0
  11. package/ios/RNPersistentTimer.h +10 -0
  12. package/ios/RNPersistentTimer.m +221 -0
  13. package/lib/commonjs/NativeTimerModule.js +46 -0
  14. package/lib/commonjs/NativeTimerModule.js.map +1 -0
  15. package/lib/commonjs/PersistentTimerManager.js +337 -0
  16. package/lib/commonjs/PersistentTimerManager.js.map +1 -0
  17. package/lib/commonjs/index.js +76 -0
  18. package/lib/commonjs/index.js.map +1 -0
  19. package/lib/commonjs/types.js +2 -0
  20. package/lib/commonjs/types.js.map +1 -0
  21. package/lib/commonjs/usePersistentTimer.js +159 -0
  22. package/lib/commonjs/usePersistentTimer.js.map +1 -0
  23. package/lib/commonjs/utils.js +112 -0
  24. package/lib/commonjs/utils.js.map +1 -0
  25. package/lib/module/NativeTimerModule.js +40 -0
  26. package/lib/module/NativeTimerModule.js.map +1 -0
  27. package/lib/module/PersistentTimerManager.js +329 -0
  28. package/lib/module/PersistentTimerManager.js.map +1 -0
  29. package/lib/module/index.js +17 -0
  30. package/lib/module/index.js.map +1 -0
  31. package/lib/module/types.js +2 -0
  32. package/lib/module/types.js.map +1 -0
  33. package/lib/module/usePersistentTimer.js +153 -0
  34. package/lib/module/usePersistentTimer.js.map +1 -0
  35. package/lib/module/utils.js +100 -0
  36. package/lib/module/utils.js.map +1 -0
  37. package/lib/typescript/NativeTimerModule.d.ts +31 -0
  38. package/lib/typescript/NativeTimerModule.d.ts.map +1 -0
  39. package/lib/typescript/PersistentTimerManager.d.ts +37 -0
  40. package/lib/typescript/PersistentTimerManager.d.ts.map +1 -0
  41. package/lib/typescript/index.d.ts +7 -0
  42. package/lib/typescript/index.d.ts.map +1 -0
  43. package/lib/typescript/types.d.ts +167 -0
  44. package/lib/typescript/types.d.ts.map +1 -0
  45. package/lib/typescript/usePersistentTimer.d.ts +16 -0
  46. package/lib/typescript/usePersistentTimer.d.ts.map +1 -0
  47. package/lib/typescript/utils.d.ts +36 -0
  48. package/lib/typescript/utils.d.ts.map +1 -0
  49. package/package.json +98 -0
  50. package/src/NativeTimerModule.ts +73 -0
  51. package/src/PersistentTimerManager.ts +410 -0
  52. package/src/index.ts +41 -0
  53. package/src/types.ts +198 -0
  54. package/src/usePersistentTimer.tsx +173 -0
  55. package/src/utils.ts +91 -0
package/README.md ADDED
@@ -0,0 +1,607 @@
1
+ # rn-persistent-timer
2
+
3
+ > **React Native timers that keep running — in the foreground, background, and even after the app is killed.**
4
+ > Full TypeScript support. Zero bundled native dependencies.
5
+
6
+ [![npm version](https://img.shields.io/npm/v/rn-persistent-timer.svg?logo=npm)](https://www.npmjs.com/package/rn-persistent-timer)
7
+ [![npm downloads](https://img.shields.io/npm/dm/rn-persistent-timer.svg)](https://www.npmjs.com/package/rn-persistent-timer)
8
+ [![license](https://img.shields.io/npm/l/rn-persistent-timer.svg)](LICENSE)
9
+ [![platform](https://img.shields.io/badge/platform-iOS%20%7C%20Android-orange.svg)](#)
10
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue.svg?logo=typescript)](#)
11
+ [![React Native](https://img.shields.io/badge/React%20Native-≥%200.71-61dafb.svg?logo=react)](#)
12
+
13
+ ---
14
+
15
+ ## ✨ Features
16
+
17
+ | Feature | Foreground | Background | Killed App |
18
+ |---|:---:|:---:|:---:|
19
+ | JS-only timer | ✅ | ❌ | ❌ |
20
+ | Background timer | ✅ | ✅ | ❌ |
21
+ | Kill-proof timer | ✅ | ✅ | ✅ |
22
+
23
+ - 🕐 **Stopwatch** (count up) and **Countdown** (count down to zero)
24
+ - 🔔 **Persistent Android notification** with pause/resume actions (Foreground Service)
25
+ - 📱 **iOS background task** via `UIBackgroundTaskIdentifier` + `BGTaskScheduler`
26
+ - 💾 **Killed-state persistence** via `AsyncStorage` — restores elapsed time on next open
27
+ - 🎣 **React Hook** (`usePersistentTimer`) + **Imperative class** (`PersistentTimerManager`)
28
+ - 📦 **Full TypeScript** — types included, no `@types/` package needed
29
+ - 🔄 **Multiple simultaneous timers** with independent configs
30
+ - ⚡ **Zero external JS dependencies** beyond `@react-native-async-storage/async-storage`
31
+
32
+ ---
33
+
34
+ ## 📋 Table of Contents
35
+
36
+ 1. [Installation](#-installation)
37
+ 2. [Android Setup](#-android-setup)
38
+ 3. [iOS Setup](#-ios-setup)
39
+ 4. [Quick Start](#-quick-start)
40
+ 5. [Usage Examples](#-usage-examples)
41
+ 6. [API Reference](#-api-reference)
42
+ 7. [TypeScript Types](#-typescript-types)
43
+ 8. [Troubleshooting](#-troubleshooting)
44
+
45
+ ---
46
+
47
+ ## 📦 Installation
48
+
49
+ ```bash
50
+ # npm
51
+ npm install rn-persistent-timer @react-native-async-storage/async-storage
52
+
53
+ # yarn
54
+ yarn add rn-persistent-timer @react-native-async-storage/async-storage
55
+ ```
56
+
57
+ > **Note:** `@react-native-async-storage/async-storage` is required for killed-state persistence.
58
+
59
+ ---
60
+
61
+ ## 🤖 Android Setup
62
+
63
+ ### 1. Add the Package to your `MainApplication`
64
+
65
+ **`MainApplication.kt`** (Kotlin — React Native 0.71+):
66
+ ```kotlin
67
+ import com.rnpersistenttimer.RNPersistentTimerPackage
68
+
69
+ override fun getPackages(): List<ReactPackage> = listOf(
70
+ MainReactPackage(),
71
+ RNPersistentTimerPackage(), // ← add this
72
+ )
73
+ ```
74
+
75
+ **`MainApplication.java`** (Java):
76
+ ```java
77
+ import com.rnpersistenttimer.RNPersistentTimerPackage;
78
+
79
+ @Override
80
+ protected List<ReactPackage> getPackages() {
81
+ return Arrays.asList(
82
+ new MainReactPackage(),
83
+ new RNPersistentTimerPackage() // ← add this
84
+ );
85
+ }
86
+ ```
87
+
88
+ ### 2. Register the Foreground Service in `AndroidManifest.xml`
89
+
90
+ ```xml
91
+ <!-- Inside <application> tag -->
92
+ <service
93
+ android:name="com.rnpersistenttimer.TimerForegroundService"
94
+ android:foregroundServiceType="dataSync"
95
+ android:exported="false" />
96
+
97
+ <!-- Inside <manifest> tag (before <application>) -->
98
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
99
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
100
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
101
+ ```
102
+
103
+ > **Android 13+ (API 33+):** You must request `POST_NOTIFICATIONS` permission at runtime before starting a timer with notifications.
104
+
105
+ ### 3. Request Notification Permission (Android 13+)
106
+
107
+ ```tsx
108
+ import { PermissionsAndroid, Platform } from 'react-native';
109
+
110
+ async function requestNotificationPermission(): Promise<void> {
111
+ if (Platform.OS === 'android' && Platform.Version >= 33) {
112
+ await PermissionsAndroid.request(
113
+ PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS,
114
+ );
115
+ }
116
+ }
117
+ ```
118
+
119
+ ---
120
+
121
+ ## 🍎 iOS Setup
122
+
123
+ ### 1. Add Capability: Background Modes
124
+
125
+ In Xcode:
126
+ 1. Select your project target → **Signing & Capabilities**
127
+ 2. Click **+ Capability** → add **Background Modes**
128
+ 3. Enable ✅ **Background fetch** and ✅ **Background processing**
129
+
130
+ ### 2. Register BGTask Identifier in `Info.plist`
131
+
132
+ ```xml
133
+ <key>BGTaskSchedulerPermittedIdentifiers</key>
134
+ <array>
135
+ <string>com.yourapp.timer</string>
136
+ </array>
137
+ ```
138
+
139
+ > Replace `com.yourapp.timer` with your actual bundle ID prefix.
140
+
141
+ ### 3. Update `RNPersistentTimer.m`
142
+
143
+ Open `ios/RNPersistentTimer.m` and replace the identifier string:
144
+
145
+ ```objc
146
+ // Find this line in scheduleBGTask:
147
+ [[BGAppRefreshTaskRequest alloc] initWithIdentifier:@"com.yourapp.timer"];
148
+
149
+ // Replace with your actual bundle ID:
150
+ [[BGAppRefreshTaskRequest alloc] initWithIdentifier:@"com.YourApp.timer"];
151
+ ```
152
+
153
+ ### 4. Install Pods
154
+
155
+ ```bash
156
+ cd ios && pod install && cd ..
157
+ ```
158
+
159
+ ---
160
+
161
+ ## ⚡ Quick Start
162
+
163
+ ```tsx
164
+ import React from 'react';
165
+ import { View, Text, TouchableOpacity } from 'react-native';
166
+ import { usePersistentTimer } from 'rn-persistent-timer';
167
+
168
+ export default function StopwatchScreen() {
169
+ const { snapshot, start, pause, resume, reset, isRunning, isPaused } =
170
+ usePersistentTimer({
171
+ timerId: 'my-stopwatch', // unique ID — required
172
+ mode: 'stopwatch',
173
+ runInBackground: true, // keeps ticking when app is backgrounded
174
+ });
175
+
176
+ return (
177
+ <View>
178
+ <Text>{snapshot.formattedElapsed}</Text> {/* "HH:MM:SS" */}
179
+ <Text>State: {snapshot.state}</Text>
180
+
181
+ {!isRunning && !isPaused && (
182
+ <TouchableOpacity onPress={start}>
183
+ <Text>▶ Start</Text>
184
+ </TouchableOpacity>
185
+ )}
186
+ {isRunning && (
187
+ <TouchableOpacity onPress={pause}>
188
+ <Text>⏸ Pause</Text>
189
+ </TouchableOpacity>
190
+ )}
191
+ {isPaused && (
192
+ <TouchableOpacity onPress={resume}>
193
+ <Text>▶ Resume</Text>
194
+ </TouchableOpacity>
195
+ )}
196
+ <TouchableOpacity onPress={reset}>
197
+ <Text>⟳ Reset</Text>
198
+ </TouchableOpacity>
199
+ </View>
200
+ );
201
+ }
202
+ ```
203
+
204
+ ---
205
+
206
+ ## 📖 Usage Examples
207
+
208
+ ### Stopwatch — Foreground Only
209
+
210
+ ```tsx
211
+ const timer = usePersistentTimer({
212
+ timerId: 'foreground-sw',
213
+ mode: 'stopwatch',
214
+ runInBackground: false,
215
+ runInKilledState: false,
216
+ });
217
+ ```
218
+
219
+ ### Stopwatch — Keeps Running in Background
220
+
221
+ ```tsx
222
+ const timer = usePersistentTimer({
223
+ timerId: 'bg-stopwatch',
224
+ mode: 'stopwatch',
225
+ runInBackground: true,
226
+ showNotification: true,
227
+ notification: {
228
+ title: '⏱ Stopwatch Running',
229
+ body: 'Elapsed: {time}', // {time} is replaced automatically
230
+ color: '#4CAF50',
231
+ showActions: true, // adds Pause/Resume buttons to notification
232
+ },
233
+ onBackground: (snap) => console.log('Backgrounded at', snap.formattedElapsed),
234
+ onForeground: (snap) => console.log('Foregrounded at', snap.formattedElapsed),
235
+ });
236
+ ```
237
+
238
+ ### Kill-Proof Timer (Survives App Kill)
239
+
240
+ ```tsx
241
+ import { Alert } from 'react-native';
242
+
243
+ const timer = usePersistentTimer({
244
+ timerId: 'kill-proof',
245
+ mode: 'stopwatch',
246
+ runInBackground: true,
247
+ runInKilledState: true, // ← enables AsyncStorage persistence
248
+ onRestore: (snap) => {
249
+ Alert.alert('Welcome back!', `Timer restored to ${snap.formattedElapsed}`);
250
+ },
251
+ });
252
+ ```
253
+
254
+ ### Countdown Timer
255
+
256
+ ```tsx
257
+ const timer = usePersistentTimer({
258
+ timerId: 'workout-countdown',
259
+ mode: 'countdown',
260
+ duration: 5 * 60, // 5 minutes in seconds
261
+ runInBackground: true,
262
+ runInKilledState: true,
263
+ notification: {
264
+ title: '⏳ Workout Timer',
265
+ body: '{time} remaining',
266
+ color: '#F44336',
267
+ },
268
+ onComplete: () => Alert.alert('Time\'s up! 🎉'),
269
+ });
270
+
271
+ // Use snapshot.formattedRemaining for countdown display
272
+ // Use snapshot.progress (0→1) for a progress bar
273
+ console.log(timer.snapshot.formattedRemaining); // "04:59"
274
+ console.log(timer.snapshot.progress); // 0.003...
275
+ ```
276
+
277
+ ### Auto-Pause on Background (Game Timer)
278
+
279
+ ```tsx
280
+ const timer = usePersistentTimer({
281
+ timerId: 'game-timer',
282
+ mode: 'stopwatch',
283
+ pauseOnBackground: true, // auto-pauses when app is backgrounded
284
+ runInKilledState: false,
285
+ });
286
+ ```
287
+
288
+ ### Multiple Concurrent Timers
289
+
290
+ ```tsx
291
+ const workTimer = usePersistentTimer({ timerId: 'work', mode: 'countdown', duration: 25 * 60, runInBackground: true });
292
+ const breakTimer = usePersistentTimer({ timerId: 'break', mode: 'countdown', duration: 5 * 60, runInBackground: true });
293
+ const sessionTimer = usePersistentTimer({ timerId: 'session', mode: 'stopwatch', runInBackground: true });
294
+ ```
295
+
296
+ ### Imperative API — Outside React (Redux, MobX, Services)
297
+
298
+ ```tsx
299
+ import { PersistentTimerManager } from 'rn-persistent-timer';
300
+
301
+ const manager = new PersistentTimerManager({
302
+ timerId: 'service-timer',
303
+ mode: 'stopwatch',
304
+ runInBackground: true,
305
+ runInKilledState: false,
306
+ });
307
+
308
+ manager.on('onTick', (snap) => {
309
+ console.log('Tick:', snap.formattedElapsed);
310
+ reduxStore.dispatch(updateTimer(snap));
311
+ });
312
+
313
+ manager.on('onComplete', (snap) => {
314
+ console.log('Done!');
315
+ });
316
+
317
+ manager.start();
318
+ // Later...
319
+ manager.pause();
320
+ manager.resume();
321
+ manager.reset();
322
+ manager.destroy(); // Always call when done to prevent memory leaks
323
+ ```
324
+
325
+ ### Platform Support Check
326
+
327
+ ```tsx
328
+ import {
329
+ isBackgroundTimerSupported,
330
+ isKilledStateTimerSupported,
331
+ } from 'rn-persistent-timer';
332
+
333
+ useEffect(() => {
334
+ isBackgroundTimerSupported().then((supported) => {
335
+ console.log('Background timer supported:', supported);
336
+ });
337
+ isKilledStateTimerSupported().then((supported) => {
338
+ console.log('Kill-state timer supported:', supported);
339
+ });
340
+ }, []);
341
+ ```
342
+
343
+ ### Utility Functions
344
+
345
+ ```tsx
346
+ import { formatTime, parseTime } from 'rn-persistent-timer';
347
+
348
+ formatTime(3661); // → "01:01:01"
349
+ formatTime(59); // → "00:00:59"
350
+ parseTime('01:30:00'); // → 5400
351
+ parseTime('05:30'); // → 330
352
+ ```
353
+
354
+ ---
355
+
356
+ ## 📚 API Reference
357
+
358
+ ### `usePersistentTimer(config)`
359
+
360
+ React hook. Returns `UsePersistentTimerReturn`.
361
+
362
+ | Prop | Type | Default | Description |
363
+ |---|---|---|---|
364
+ | `timerId` | `string` | **required** | Unique timer ID |
365
+ | `mode` | `'stopwatch' \| 'countdown'` | `'stopwatch'` | Timer direction |
366
+ | `duration` | `number` | `0` | Seconds for countdown |
367
+ | `runInBackground` | `boolean` | `true` | Continue when app is backgrounded |
368
+ | `runInKilledState` | `boolean` | `false` | Restore after app kill |
369
+ | `interval` | `number` | `1000` | Tick interval (ms) |
370
+ | `showNotification` | `boolean` | `true` | Show Android notification |
371
+ | `notification` | `AndroidNotificationConfig` | `{}` | Notification options |
372
+ | `pauseOnBackground` | `boolean` | `false` | Auto-pause on background |
373
+ | `resetOnForeground` | `boolean` | `false` | Auto-reset when returned from kill |
374
+
375
+ **Callbacks** (all optional):
376
+
377
+ | Callback | Signature | Description |
378
+ |---|---|---|
379
+ | `onTick` | `(snap: TimerSnapshot) => void` | Every tick |
380
+ | `onStart` | `(snap: TimerSnapshot) => void` | Timer started |
381
+ | `onPause` | `(snap: TimerSnapshot) => void` | Timer paused |
382
+ | `onResume` | `(snap: TimerSnapshot) => void` | Timer resumed |
383
+ | `onReset` | `(snap: TimerSnapshot) => void` | Timer reset |
384
+ | `onComplete` | `(snap: TimerSnapshot) => void` | Countdown finished |
385
+ | `onBackground` | `(snap: TimerSnapshot) => void` | App went to background |
386
+ | `onForeground` | `(snap: TimerSnapshot) => void` | App returned to foreground |
387
+ | `onRestore` | `(snap: TimerSnapshot) => void` | State restored after kill |
388
+ | `onError` | `(error: Error) => void` | Internal error |
389
+
390
+ **Return value:**
391
+
392
+ | Field | Type | Description |
393
+ |---|---|---|
394
+ | `snapshot` | `TimerSnapshot` | Reactive timer state |
395
+ | `isRunning` | `boolean` | `state === 'running'` |
396
+ | `isPaused` | `boolean` | `state === 'paused'` |
397
+ | `isCompleted` | `boolean` | `state === 'completed'` |
398
+ | `appState` | `AppState` | Current app state |
399
+ | `start()` | `() => void` | Start the timer |
400
+ | `pause()` | `() => void` | Pause the timer |
401
+ | `resume()` | `() => void` | Resume from pause |
402
+ | `reset()` | `() => void` | Stop and reset to 0 |
403
+ | `stop()` | `() => void` | Stop without reset |
404
+ | `getSnapshot()` | `() => TimerSnapshot` | Synchronous snapshot |
405
+ | `destroy()` | `() => void` | Clean up all listeners |
406
+
407
+ ---
408
+
409
+ ### `PersistentTimerManager`
410
+
411
+ Class-based API for use outside of React components.
412
+
413
+ ```ts
414
+ const manager = new PersistentTimerManager(config: TimerConfig);
415
+
416
+ manager.start();
417
+ manager.pause();
418
+ manager.resume();
419
+ manager.reset();
420
+ manager.stop();
421
+ manager.getSnapshot(): TimerSnapshot;
422
+ manager.destroy();
423
+
424
+ manager.on(event, handler);
425
+ manager.off(event, handler);
426
+
427
+ // Static: restore from killed state
428
+ const restored = await PersistentTimerManager.restore(timerId);
429
+ ```
430
+
431
+ ---
432
+
433
+ ### `TimerSnapshot`
434
+
435
+ ```ts
436
+ interface TimerSnapshot {
437
+ timerId: string;
438
+ elapsed: number; // seconds since start
439
+ remaining: number | null; // seconds left (countdown only)
440
+ state: TimerState; // 'idle' | 'running' | 'paused' | 'completed'
441
+ appState: AppState; // 'foreground' | 'background' | 'killed'
442
+ startedAt: number | null; // unix ms when started
443
+ pausedAt: number | null; // unix ms when paused
444
+ formattedElapsed: string; // "HH:MM:SS"
445
+ formattedRemaining: string | null; // "HH:MM:SS" (countdown only)
446
+ progress: number | null; // 0→1 (countdown only)
447
+ }
448
+ ```
449
+
450
+ ---
451
+
452
+ ### `AndroidNotificationConfig`
453
+
454
+ ```ts
455
+ interface AndroidNotificationConfig {
456
+ title?: string; // Notification title
457
+ body?: string; // Body text; use {time} for the current timer value
458
+ channelId?: string; // Notification channel ID
459
+ channelName?: string; // Channel display name
460
+ icon?: string; // Drawable resource name
461
+ color?: string; // Hex accent color, e.g. '#FF5733'
462
+ showTime?: boolean; // Show time in notification
463
+ showActions?: boolean;// Show Pause/Resume action buttons
464
+ }
465
+ ```
466
+
467
+ ---
468
+
469
+ ## 🔧 Troubleshooting
470
+
471
+ ### Native module not found warning
472
+
473
+ ```
474
+ [rn-persistent-timer] Native module not found.
475
+ ```
476
+
477
+ **iOS:** Run `npx pod-install` (or `cd ios && pod install`) then rebuild.
478
+ **Android:** Clean and rebuild the Android project: `cd android && ./gradlew clean && cd ..`.
479
+
480
+ Foreground (JS-only) timers still work without the native module.
481
+
482
+ ---
483
+
484
+ ### Timer stops in background on Android
485
+
486
+ 1. Ensure `TimerForegroundService` is registered in `AndroidManifest.xml`.
487
+ 2. Check that `POST_NOTIFICATIONS` permission is granted on Android 13+.
488
+ 3. Set `showNotification: true` — Android 8+ **requires** a visible notification for foreground services.
489
+ 4. Some OEM ROMs (Xiaomi, OPPO, etc.) aggressively kill background processes. Guide users to whitelist your app in battery settings.
490
+
491
+ ---
492
+
493
+ ### Timer stops in background on iOS
494
+
495
+ 1. Confirm **Background Modes** capability is added in Xcode.
496
+ 2. Verify `BGTaskSchedulerPermittedIdentifiers` is set in `Info.plist`.
497
+ 3. iOS limits background execution to ~30 seconds unless using `BGTaskScheduler`. The module handles this automatically via `UIBackgroundTaskIdentifier`.
498
+
499
+ ---
500
+
501
+ ### Timer not restoring after app kill
502
+
503
+ 1. Ensure `runInKilledState: true` in your config.
504
+ 2. Confirm `@react-native-async-storage/async-storage` is installed and linked.
505
+ 3. Rebuild the project after installing `async-storage`.
506
+
507
+ ---
508
+
509
+ ### TypeScript errors
510
+
511
+ Ensure your `tsconfig.json` has:
512
+ ```json
513
+ {
514
+ "compilerOptions": {
515
+ "moduleResolution": "bundler",
516
+ "strict": true
517
+ }
518
+ }
519
+ ```
520
+
521
+ ---
522
+
523
+ ## 📁 Project Structure
524
+
525
+ ```
526
+ rn-persistent-timer/
527
+ ├── src/
528
+ │ ├── index.ts # Public API barrel
529
+ │ ├── types.ts # All TypeScript types
530
+ │ ├── NativeTimerModule.ts # JS ↔ Native bridge
531
+ │ ├── PersistentTimerManager.ts # Core timer class
532
+ │ ├── usePersistentTimer.tsx # React hook
533
+ │ └── utils.ts # Utility functions
534
+ ├── android/
535
+ │ ├── build.gradle
536
+ │ └── src/main/java/com/rnpersistenttimer/
537
+ │ ├── RNPersistentTimerModule.java
538
+ │ ├── RNPersistentTimerPackage.java
539
+ │ └── TimerForegroundService.java
540
+ ├── ios/
541
+ │ ├── RNPersistentTimer.h
542
+ │ └── RNPersistentTimer.m
543
+ ├── example/
544
+ │ └── App.tsx # 8-scenario demo app
545
+ └── lib/ # Built output (generated)
546
+ ├── commonjs/
547
+ ├── module/
548
+ └── typescript/
549
+ ```
550
+
551
+ ---
552
+
553
+ ## 🚀 Running the Example App
554
+
555
+ ```bash
556
+ # 1. Clone or navigate to the package
557
+ cd /path/to/rn-persistent-timer
558
+
559
+ # 2. Install dependencies
560
+ yarn install
561
+
562
+ # 3. Install example dependencies
563
+ cd example && yarn install && cd ..
564
+
565
+ # 4. iOS — install pods
566
+ cd example/ios && pod install && cd ../..
567
+ npx react-native run-ios
568
+
569
+ # 5. Android
570
+ npx react-native run-android
571
+ ```
572
+
573
+ ---
574
+
575
+ ## 📤 Building & Publishing to npm
576
+
577
+ ```bash
578
+ # 1. Install dependencies
579
+ yarn install
580
+
581
+ # 2. Build the TypeScript output
582
+ yarn build # runs react-native-builder-bob
583
+
584
+ # 3. Verify types compile
585
+ yarn typecheck # runs tsc --noEmit
586
+
587
+ # 4. Publish
588
+ npm publish --access public
589
+ ```
590
+
591
+ ---
592
+
593
+ ## 📄 License
594
+
595
+ MIT © Vipin Jaiswal
596
+
597
+ ---
598
+
599
+ ## 🙏 Contributing
600
+
601
+ PRs and issues welcome! Please open an issue before starting a large change.
602
+
603
+ 1. Fork the repo
604
+ 2. Create your feature branch: `git checkout -b feat/my-feature`
605
+ 3. Commit changes: `git commit -m 'feat: add my feature'`
606
+ 4. Push: `git push origin feat/my-feature`
607
+ 5. Open a Pull Request
File without changes
File without changes
@@ -0,0 +1,15 @@
1
+ apply plugin: 'com.android.library'
2
+
3
+ android {
4
+ compileSdkVersion 34
5
+ defaultConfig {
6
+ minSdkVersion 21
7
+ targetSdkVersion 34
8
+ }
9
+ }
10
+
11
+ dependencies {
12
+ implementation 'com.facebook.react:react-native:+'
13
+ implementation 'androidx.work:work-runtime:2.9.0'
14
+ implementation 'androidx.core:core:1.12.0'
15
+ }