@jesushr0013/native-timer 8.0.9 → 8.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.
@@ -19,5 +19,15 @@ Pod::Spec.new do |s|
19
19
  s.swift_version = '5.1'
20
20
 
21
21
  # Frameworks condicionais - ActivityKit apenas para iOS 16.2+
22
- s.weak_frameworks = 'WidgetKit', 'SwiftUI', 'ActivityKit'
22
+ # SwiftUICore es necesario como weak_framework porque Xcode 16+ lo separa de SwiftUI
23
+ # y en iOS 16.x no existe como framework independiente
24
+ s.weak_frameworks = 'WidgetKit', 'SwiftUI', 'ActivityKit', 'SwiftUICore'
25
+
26
+ # Linker flags para asegurar weak linking de SwiftUICore
27
+ s.pod_target_xcconfig = {
28
+ 'OTHER_LDFLAGS' => '$(inherited) -weak_framework SwiftUICore'
29
+ }
30
+ s.user_target_xcconfig = {
31
+ 'OTHER_LDFLAGS' => '$(inherited) -weak_framework SwiftUICore'
32
+ }
23
33
  end
@@ -13,5 +13,15 @@ Pod::Spec.new do |s|
13
13
  s.swift_version = '5.1'
14
14
 
15
15
  # Frameworks condicionais - ActivityKit apenas para iOS 16.2+
16
- s.weak_frameworks = 'WidgetKit', 'SwiftUI', 'ActivityKit'
16
+ # SwiftUICore es necesario como weak_framework porque Xcode 16+ lo separa de SwiftUI
17
+ # y en iOS 16.x no existe como framework independiente
18
+ s.weak_frameworks = 'WidgetKit', 'SwiftUI', 'ActivityKit', 'SwiftUICore'
19
+
20
+ # Linker flags para asegurar weak linking de SwiftUICore
21
+ s.pod_target_xcconfig = {
22
+ 'OTHER_LDFLAGS' => '$(inherited) -weak_framework SwiftUICore'
23
+ }
24
+ s.user_target_xcconfig = {
25
+ 'OTHER_LDFLAGS' => '$(inherited) -weak_framework SwiftUICore'
26
+ }
17
27
  end
package/README.md ADDED
@@ -0,0 +1,308 @@
1
+ # @jesushr0013/native-timer
2
+
3
+ A Capacitor 8+ plugin for **work shift time tracking** (jornada laboral) with **Android Foreground Service** and **iOS Live Activities** (Dynamic Island + Lock Screen).
4
+
5
+ The widget displays a live "Jornada Activa" (Active Shift) timer designed specifically for tracking work hours.
6
+
7
+ ## Features
8
+
9
+ - **Android**: Persistent foreground service with notification for shift tracking
10
+ - **iOS**: Live Activities with Dynamic Island and Lock Screen widget showing "Jornada Activa" (iOS 16.2+)
11
+ - **iOS**: Compatible with iOS 16.x and 17+ (SwiftUICore weak-linked)
12
+ - Background-safe timer that survives app suspension — ideal for long work shifts
13
+ - Customizable primary color for notifications and widgets
14
+ - Smart notification management (foreground/background aware)
15
+
16
+ ## Requirements
17
+
18
+ | Platform | Minimum Version |
19
+ |----------|----------------|
20
+ | Capacitor | 8.0.0+ |
21
+ | iOS | 16.0+ (Live Activities require 16.2+) |
22
+ | Android | API 26+ (Android 8.0) |
23
+ | Xcode | 16+ |
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ npm install @jesushr0013/native-timer
29
+ npx cap sync
30
+ ```
31
+
32
+ ## iOS Setup
33
+
34
+ ### 1. Add Widget Extension
35
+
36
+ In Xcode, add a **Widget Extension** target to your project for Live Activities support.
37
+
38
+ ### 2. Fix SwiftUICore for iOS 16.x
39
+
40
+ If you compile with Xcode 16+, you **must** add this linker flag to both your App target and Widget Extension target in Xcode:
41
+
42
+ **Build Settings → Other Linker Flags:**
43
+ ```
44
+ -weak_framework SwiftUICore
45
+ ```
46
+
47
+ This prevents a crash on iOS 16.x where `SwiftUICore.framework` doesn't exist as a standalone framework.
48
+
49
+ ### 3. Entitlements
50
+
51
+ Add the `Push Notifications` capability and enable `Supports Live Activities` in your `Info.plist`:
52
+
53
+ ```xml
54
+ <key>NSSupportsLiveActivities</key>
55
+ <true/>
56
+ ```
57
+
58
+ ## Android Setup
59
+
60
+ The plugin automatically manages a Foreground Service. Make sure your `AndroidManifest.xml` includes:
61
+
62
+ ```xml
63
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
64
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
65
+ ```
66
+
67
+ ## Usage
68
+
69
+ ```typescript
70
+ import { NativeTimer } from '@jesushr0013/native-timer';
71
+
72
+ // Start a timer
73
+ // Note: title/body are for the Android notification only.
74
+ // The iOS widget always shows "⚡ Jornada Activa" as the title.
75
+ await NativeTimer.startTimer({
76
+ startTime: Date.now(),
77
+ title: 'Jornada Activa',
78
+ body: 'Timer en marcha...',
79
+ primaryColor: '#0045a5', // optional — widget & notification accent color
80
+ });
81
+
82
+ // Check if timer is running
83
+ const { isRunning } = await NativeTimer.isTimerRunning();
84
+
85
+ // Get elapsed time (ms)
86
+ const { elapsedTime } = await NativeTimer.getElapsedTime();
87
+
88
+ // Update notification text
89
+ await NativeTimer.updateNotification({
90
+ title: 'Still working',
91
+ body: '2h 30min elapsed',
92
+ });
93
+
94
+ // Stop the timer
95
+ await NativeTimer.stopTimer();
96
+ ```
97
+
98
+ ### Live Activities (iOS only)
99
+
100
+ ```typescript
101
+ // Check availability
102
+ const { available } = await NativeTimer.areLiveActivitiesAvailable();
103
+
104
+ if (available) {
105
+ // Start Live Activity
106
+ const { activityId } = await NativeTimer.startLiveActivity({
107
+ title: 'Work Session',
108
+ startTime: new Date().toISOString(),
109
+ elapsedTime: '0 h 0 min',
110
+ status: 'active',
111
+ primaryColor: '#0045a5',
112
+ });
113
+
114
+ // Update Live Activity
115
+ await NativeTimer.updateLiveActivity({
116
+ activityId: activityId!,
117
+ elapsedTime: '1 h 30 min',
118
+ status: 'active',
119
+ });
120
+
121
+ // Stop Live Activity
122
+ await NativeTimer.stopLiveActivity({ activityId: activityId! });
123
+
124
+ // Or stop all at once
125
+ await NativeTimer.stopAllLiveActivities();
126
+ }
127
+ ```
128
+
129
+ ### Foreground State Management
130
+
131
+ ```typescript
132
+ // Tell the plugin when your app goes to background/foreground
133
+ // to manage notifications intelligently
134
+ App.addListener('appStateChange', ({ isActive }) => {
135
+ NativeTimer.setAppForegroundState({ inForeground: isActive });
136
+ });
137
+
138
+ // Reset notification dismissed state
139
+ await NativeTimer.resetNotificationState();
140
+ ```
141
+
142
+ ### Event Listener
143
+
144
+ ```typescript
145
+ // Listen for periodic timer updates
146
+ const listener = await NativeTimer.addListener('timerUpdate', (data) => {
147
+ console.log('Elapsed:', data.elapsedTime, 'ms');
148
+ console.log('Formatted:', data.formattedTime);
149
+ });
150
+
151
+ // Remove all listeners
152
+ await NativeTimer.removeAllListeners();
153
+ ```
154
+
155
+ ## API
156
+
157
+ ### `startTimer(options)`
158
+
159
+ Inicia el timer nativo. En Android crea un Foreground Service con notificación persistente. En iOS prepara el timer interno para Live Activities.
160
+
161
+ | Param | Type | Required | Description |
162
+ |-------|------|----------|-------------|
163
+ | `startTime` | `number` | Yes | Timestamp en milisegundos (ej: `Date.now()`) del inicio de la jornada |
164
+ | `title` | `string` | Yes | Título de la notificación (solo Android) |
165
+ | `body` | `string` | Yes | Cuerpo de la notificación (solo Android) |
166
+ | `primaryColor` | `string` | No | Color hex (ej: `#0045a5`) para la notificación Android y el widget iOS |
167
+
168
+ **Returns:** `Promise<{ success: boolean }>`
169
+
170
+ ---
171
+
172
+ ### `stopTimer()`
173
+
174
+ Detiene el timer, cancela el Foreground Service (Android) y elimina todas las notificaciones pendientes.
175
+
176
+ **Returns:** `Promise<{ success: boolean }>`
177
+
178
+ ---
179
+
180
+ ### `updateNotification(options)`
181
+
182
+ Actualiza el texto de la notificación del timer en Android. En iOS no tiene efecto visible (el widget se actualiza via `updateLiveActivity`).
183
+
184
+ | Param | Type | Required | Description |
185
+ |-------|------|----------|-------------|
186
+ | `title` | `string` | Yes | Nuevo título de la notificación |
187
+ | `body` | `string` | Yes | Nuevo cuerpo de la notificación |
188
+
189
+ **Returns:** `Promise<{ success: boolean }>`
190
+
191
+ ---
192
+
193
+ ### `isTimerRunning()`
194
+
195
+ Comprueba si hay un timer activo en ejecución.
196
+
197
+ **Returns:** `Promise<{ isRunning: boolean }>`
198
+
199
+ ---
200
+
201
+ ### `getElapsedTime()`
202
+
203
+ Obtiene el tiempo transcurrido desde que se inició el timer.
204
+
205
+ **Returns:** `Promise<{ elapsedTime: number }>` — tiempo en milisegundos
206
+
207
+ ---
208
+
209
+ ### `setAppForegroundState(options)`
210
+
211
+ Indica al plugin si la app está en primer o segundo plano. Esto controla si las notificaciones locales se muestran (solo en segundo plano) para no molestar al usuario mientras usa la app.
212
+
213
+ | Param | Type | Required | Description |
214
+ |-------|------|----------|-------------|
215
+ | `inForeground` | `boolean` | Yes | `true` si la app está visible, `false` si está en segundo plano |
216
+
217
+ **Returns:** `Promise<{ success: boolean }>`
218
+
219
+ ---
220
+
221
+ ### `resetNotificationState()`
222
+
223
+ Resetea el estado interno de "notificación descartada". Útil al volver a abrir la app para que las notificaciones puedan mostrarse de nuevo en segundo plano.
224
+
225
+ **Returns:** `Promise<{ success: boolean }>`
226
+
227
+ ---
228
+
229
+ ### `areLiveActivitiesAvailable()` *(iOS only)*
230
+
231
+ Verifica si el dispositivo soporta Live Activities (requiere iOS 16.2+ y que el usuario las tenga habilitadas).
232
+
233
+ **Returns:** `Promise<{ available: boolean }>`
234
+
235
+ ---
236
+
237
+ ### `startLiveActivity(options)` *(iOS only)*
238
+
239
+ Inicia una Live Activity que muestra el timer de jornada en la Dynamic Island (iPhone 14 Pro+) y en la pantalla de bloqueo. El widget muestra "⚡ Jornada Activa" con un contador en tiempo real.
240
+
241
+ | Param | Type | Required | Description |
242
+ |-------|------|----------|-------------|
243
+ | `title` | `string` | Yes | Nombre de la sesión (usado internamente) |
244
+ | `startTime` | `string` | Yes | Fecha/hora de inicio en formato ISO 8601 |
245
+ | `elapsedTime` | `string` | Yes | Tiempo transcurrido formateado (ej: `"1 h 30 min"`) |
246
+ | `status` | `string` | Yes | Estado de la jornada (ej: `"active"`, `"paused"`) |
247
+ | `primaryColor` | `string` | No | Color hex para el widget (default: `#0045a5`) |
248
+
249
+ **Returns:** `Promise<{ success: boolean; activityId?: string }>` — el `activityId` se usa para actualizar o detener la actividad
250
+
251
+ ---
252
+
253
+ ### `updateLiveActivity(options)` *(iOS only)*
254
+
255
+ Actualiza una Live Activity existente con nuevo tiempo transcurrido y estado.
256
+
257
+ | Param | Type | Required | Description |
258
+ |-------|------|----------|-------------|
259
+ | `activityId` | `string` | Yes | ID devuelto por `startLiveActivity` |
260
+ | `elapsedTime` | `string` | Yes | Tiempo transcurrido actualizado (ej: `"2 h 15 min"`) |
261
+ | `status` | `string` | Yes | Estado actual (ej: `"active"`) |
262
+
263
+ **Returns:** `Promise<{ success: boolean }>`
264
+
265
+ ---
266
+
267
+ ### `stopLiveActivity(options)` *(iOS only)*
268
+
269
+ Detiene una Live Activity específica y la elimina de la Dynamic Island y pantalla de bloqueo.
270
+
271
+ | Param | Type | Required | Description |
272
+ |-------|------|----------|-------------|
273
+ | `activityId` | `string` | Yes | ID devuelto por `startLiveActivity` |
274
+
275
+ **Returns:** `Promise<{ success: boolean }>`
276
+
277
+ ---
278
+
279
+ ### `stopAllLiveActivities()` *(iOS only)*
280
+
281
+ Detiene **todas** las Live Activities activas del plugin. Útil para limpieza al cerrar sesión o al detener la jornada.
282
+
283
+ **Returns:** `Promise<{ success: boolean }>`
284
+
285
+ ---
286
+
287
+ ### `addListener('timerUpdate', callback)`
288
+
289
+ Escucha actualizaciones periódicas del timer (cada ~30 segundos).
290
+
291
+ ```typescript
292
+ const listener = await NativeTimer.addListener('timerUpdate', (data) => {
293
+ console.log(data.elapsedTime); // number (ms)
294
+ console.log(data.formattedTime); // string
295
+ });
296
+ ```
297
+
298
+ ---
299
+
300
+ ### `removeAllListeners()`
301
+
302
+ Elimina todos los listeners registrados.
303
+
304
+ **Returns:** `Promise<void>`
305
+
306
+ ## License
307
+
308
+ MIT
@@ -1,7 +1,6 @@
1
+ #if canImport(ActivityKit)
1
2
  import SwiftUI
2
3
  import WidgetKit
3
-
4
- #if canImport(ActivityKit)
5
4
  import ActivityKit
6
5
 
7
6
  @available(iOS 16.2, *)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jesushr0013/native-timer",
3
- "version": "8.0.9",
3
+ "version": "8.1.0",
4
4
  "description": "Plugin nativo para timer con foreground service",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",