@jesushr0013/native-timer 8.1.0 → 8.2.1
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.
- package/Jesushr0013NativeTimer.podspec +3 -14
- package/NativeTimerKit.podspec +3 -5
- package/Package.swift +11 -2
- package/README.md +453 -81
- package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +2 -2
- package/android/.gradle/checksums/checksums.lock +0 -0
- package/android/.gradle/checksums/md5-checksums.bin +0 -0
- package/android/.gradle/checksums/sha1-checksums.bin +0 -0
- package/ios/{LiveActivitiesKit → Core}/NativeTimerManager.swift +0 -25
- package/ios/Core/WorkSessionTimerAttributes.swift +32 -0
- package/ios/{LiveActivitiesKit → LiveActivities}/NativeTimerWidget.swift +8 -2
- package/package.json +1 -1
|
@@ -11,23 +11,12 @@ Pod::Spec.new do |s|
|
|
|
11
11
|
s.homepage = package['repository']['url'] rescue 'https://github.com/jesherram/native-timer'
|
|
12
12
|
s.author = package['author']
|
|
13
13
|
s.source = { :git => 'https://github.com/jesherram/native-timer.git', :tag => s.version.to_s }
|
|
14
|
-
s.source_files = 'ios/
|
|
15
|
-
s.public_header_files = 'ios/LiveActivitiesKit/*.h'
|
|
14
|
+
s.source_files = 'ios/Core/**/*.{swift,h,m,c,cc,mm,cpp}', 'ios/Plugin/**/*.{swift,h,m,c,cc,mm,cpp}'
|
|
16
15
|
|
|
17
16
|
s.ios.deployment_target = '15.0'
|
|
18
17
|
s.dependency 'Capacitor'
|
|
19
18
|
s.swift_version = '5.1'
|
|
20
19
|
|
|
21
|
-
#
|
|
22
|
-
|
|
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
|
-
}
|
|
20
|
+
# Solo ActivityKit como weak_framework - SwiftUI ya NO se linkea en este target
|
|
21
|
+
s.weak_frameworks = 'ActivityKit'
|
|
33
22
|
end
|
package/NativeTimerKit.podspec
CHANGED
|
@@ -6,15 +6,13 @@ Pod::Spec.new do |s|
|
|
|
6
6
|
s.homepage = 'https://github.com/jesherram/native-timer'
|
|
7
7
|
s.author = { 'Meycagesal' => 'info@meycagesal.com' }
|
|
8
8
|
s.source = { :git => 'https://github.com/jesherram/native-timer.git', :tag => s.version.to_s }
|
|
9
|
-
s.source_files = 'ios/
|
|
10
|
-
s.public_header_files = 'ios/LiveActivitiesKit/*.h'
|
|
9
|
+
s.source_files = 'ios/LiveActivities/**/*.{swift,h,m,c,cc,mm,cpp}'
|
|
11
10
|
|
|
12
11
|
s.ios.deployment_target = '15.0'
|
|
12
|
+
s.dependency 'Jesushr0013NativeTimer'
|
|
13
13
|
s.swift_version = '5.1'
|
|
14
14
|
|
|
15
|
-
#
|
|
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
|
|
15
|
+
# Live Activities requiere SwiftUI - weak linking para compatibilidad con iOS < 16.1
|
|
18
16
|
s.weak_frameworks = 'WidgetKit', 'SwiftUI', 'ActivityKit', 'SwiftUICore'
|
|
19
17
|
|
|
20
18
|
# Linker flags para asegurar weak linking de SwiftUICore
|
package/Package.swift
CHANGED
|
@@ -7,7 +7,10 @@ let package = Package(
|
|
|
7
7
|
products: [
|
|
8
8
|
.library(
|
|
9
9
|
name: "Jesushr0013NativeTimer",
|
|
10
|
-
targets: ["Jesushr0013NativeTimer"])
|
|
10
|
+
targets: ["Jesushr0013NativeTimer"]),
|
|
11
|
+
.library(
|
|
12
|
+
name: "Jesushr0013NativeTimerLiveActivities",
|
|
13
|
+
targets: ["Jesushr0013NativeTimerLiveActivities"])
|
|
11
14
|
],
|
|
12
15
|
dependencies: [
|
|
13
16
|
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0")
|
|
@@ -20,6 +23,12 @@ let package = Package(
|
|
|
20
23
|
.product(name: "Cordova", package: "capacitor-swift-pm")
|
|
21
24
|
],
|
|
22
25
|
path: "ios",
|
|
23
|
-
sources: ["
|
|
26
|
+
sources: ["Core", "Plugin"]),
|
|
27
|
+
.target(
|
|
28
|
+
name: "Jesushr0013NativeTimerLiveActivities",
|
|
29
|
+
dependencies: [
|
|
30
|
+
"Jesushr0013NativeTimer"
|
|
31
|
+
],
|
|
32
|
+
path: "ios/LiveActivities")
|
|
24
33
|
]
|
|
25
34
|
)
|
package/README.md
CHANGED
|
@@ -4,166 +4,437 @@ A Capacitor 8+ plugin for **work shift time tracking** (jornada laboral) with **
|
|
|
4
4
|
|
|
5
5
|
The widget displays a live "Jornada Activa" (Active Shift) timer designed specifically for tracking work hours.
|
|
6
6
|
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [Features](#features)
|
|
12
|
+
- [Architecture (v8.2.0+)](#architecture-v820)
|
|
13
|
+
- [Requirements](#requirements)
|
|
14
|
+
- [Installation (New Project)](#installation-new-project)
|
|
15
|
+
- [iOS Setup](#ios-setup)
|
|
16
|
+
- [Android Setup](#android-setup)
|
|
17
|
+
- [Usage](#usage)
|
|
18
|
+
- [API Reference](#api-reference)
|
|
19
|
+
- [Upgrading from v8.1.x to v8.2.0](#upgrading-from-v81x-to-v820)
|
|
20
|
+
- [Troubleshooting](#troubleshooting)
|
|
21
|
+
- [License](#license)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
7
25
|
## Features
|
|
8
26
|
|
|
9
27
|
- **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
|
|
28
|
+
- **iOS**: Live Activities with Dynamic Island and Lock Screen widget showing "⚡ Jornada Activa" (iOS 16.2+)
|
|
29
|
+
- **iOS**: Compatible with iOS 15+ — SwiftUI is **completely isolated** in a separate target, so there is **no crash on iOS 16**
|
|
12
30
|
- Background-safe timer that survives app suspension — ideal for long work shifts
|
|
13
31
|
- Customizable primary color for notifications and widgets
|
|
14
32
|
- Smart notification management (foreground/background aware)
|
|
15
33
|
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Architecture (v8.2.0+)
|
|
37
|
+
|
|
38
|
+
Starting from v8.2.0, the iOS code is split into **3 independent targets** to prevent the main app from linking SwiftUI/SwiftUICore:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
ios/
|
|
42
|
+
├── Core/ ← NativeTimerCore target
|
|
43
|
+
│ ├── NativeTimerManager.swift (timer logic, ActivityKit management)
|
|
44
|
+
│ └── WorkSessionTimerAttributes.swift (ActivityAttributes model)
|
|
45
|
+
├── Plugin/ ← Jesushr0013NativeTimer target
|
|
46
|
+
│ └── NativeTimerPlugin.swift (Capacitor bridge)
|
|
47
|
+
└── LiveActivities/ ← NativeTimerLiveActivities target
|
|
48
|
+
└── NativeTimerWidget.swift (SwiftUI widget for Dynamic Island)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
| Target | SwiftUI? | What it does |
|
|
52
|
+
|--------|----------|--------------|
|
|
53
|
+
| **NativeTimerCore** | **No** | Timer logic, notification management, `WorkSessionTimerAttributes` model, `NativeTimerManager` class. Safe on iOS 15+. |
|
|
54
|
+
| **Jesushr0013NativeTimer** | **No** | Capacitor plugin bridge (`NativeTimerPlugin`). Depends on Core. Auto-discovered by Capacitor. Safe on iOS 15+. |
|
|
55
|
+
| **NativeTimerLiveActivities** | **Yes** | SwiftUI widget for Dynamic Island and Lock Screen. Depends on Core. Only loaded in Widget Extension target. |
|
|
56
|
+
|
|
57
|
+
> **Why this matters:** In previous versions, the widget's `import SwiftUI` lived in the same target as the plugin, causing `SwiftUICore` to be linked into the main app binary. On iOS 16.0–16.0.x, `SwiftUICore.framework` doesn't exist as a standalone framework, causing an immediate crash at launch. This architecture completely eliminates that problem.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
16
61
|
## Requirements
|
|
17
62
|
|
|
18
63
|
| Platform | Minimum Version |
|
|
19
64
|
|----------|----------------|
|
|
20
65
|
| Capacitor | 8.0.0+ |
|
|
21
|
-
| iOS |
|
|
66
|
+
| iOS | 15.0+ (Live Activities require 16.2+) |
|
|
22
67
|
| Android | API 26+ (Android 8.0) |
|
|
23
68
|
| Xcode | 16+ |
|
|
69
|
+
| Node.js | 18+ |
|
|
24
70
|
|
|
25
|
-
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Installation (New Project)
|
|
74
|
+
|
|
75
|
+
### Step 1: Install the npm package
|
|
26
76
|
|
|
27
77
|
```bash
|
|
28
78
|
npm install @jesushr0013/native-timer
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Step 2: Sync with native platforms
|
|
82
|
+
|
|
83
|
+
```bash
|
|
29
84
|
npx cap sync
|
|
30
85
|
```
|
|
31
86
|
|
|
32
|
-
|
|
87
|
+
This will:
|
|
88
|
+
- Copy the plugin to `node_modules/`
|
|
89
|
+
- Update `ios/App/Podfile` with the plugin's pod dependency
|
|
90
|
+
- Run `pod install` in the `ios/` folder
|
|
91
|
+
- Copy plugin files to the Android project
|
|
33
92
|
|
|
34
|
-
|
|
93
|
+
---
|
|
35
94
|
|
|
36
|
-
|
|
95
|
+
## iOS Setup
|
|
37
96
|
|
|
38
|
-
###
|
|
97
|
+
### Step 1: Verify pod installation
|
|
39
98
|
|
|
40
|
-
|
|
99
|
+
After `npx cap sync`, verify that the plugin was installed correctly:
|
|
41
100
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
101
|
+
```bash
|
|
102
|
+
cd ios
|
|
103
|
+
pod install
|
|
104
|
+
cd ..
|
|
45
105
|
```
|
|
46
106
|
|
|
47
|
-
|
|
107
|
+
You should see `Jesushr0013NativeTimer` in the pod list. This pod includes **only** `Core/` + `Plugin/` — **no SwiftUI**.
|
|
48
108
|
|
|
49
|
-
###
|
|
109
|
+
### Step 2: Enable Live Activities in Info.plist
|
|
50
110
|
|
|
51
|
-
|
|
111
|
+
Open `ios/App/App/Info.plist` and add:
|
|
52
112
|
|
|
53
113
|
```xml
|
|
54
114
|
<key>NSSupportsLiveActivities</key>
|
|
55
115
|
<true/>
|
|
56
116
|
```
|
|
57
117
|
|
|
118
|
+
Or in Xcode: open Info.plist → add row → key `NSSupportsLiveActivities` → type `Boolean` → value `YES`.
|
|
119
|
+
|
|
120
|
+
### Step 3: Add Widget Extension (required for Live Activities)
|
|
121
|
+
|
|
122
|
+
Live Activities need a **Widget Extension** target in your Xcode project:
|
|
123
|
+
|
|
124
|
+
1. Open `ios/App/App.xcworkspace` in Xcode
|
|
125
|
+
2. File → New → Target → **Widget Extension**
|
|
126
|
+
3. Name it (e.g. `TimerWidgetExtension`)
|
|
127
|
+
4. Uncheck "Include Configuration App Intent" (not needed)
|
|
128
|
+
5. Click Finish
|
|
129
|
+
|
|
130
|
+
### Step 4: Add NativeTimerKit to Widget Extension
|
|
131
|
+
|
|
132
|
+
#### Option A: CocoaPods (recommended for Capacitor projects)
|
|
133
|
+
|
|
134
|
+
Edit your `ios/App/Podfile` and add a target block for the Widget Extension:
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
target 'App' do
|
|
138
|
+
capacitor_pods
|
|
139
|
+
# ... your existing pods
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Add this new block:
|
|
143
|
+
target 'TimerWidgetExtension' do
|
|
144
|
+
pod 'NativeTimerKit'
|
|
145
|
+
end
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Then reinstall pods:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
cd ios
|
|
152
|
+
pod install
|
|
153
|
+
cd ..
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
#### Option B: Swift Package Manager
|
|
157
|
+
|
|
158
|
+
1. In Xcode: File → Add Package Dependencies
|
|
159
|
+
2. Enter: `https://github.com/jesherram/native-timer.git`
|
|
160
|
+
3. Select version `8.2.0` or higher
|
|
161
|
+
4. Add `NativeTimerLiveActivities` library to your **Widget Extension** target
|
|
162
|
+
|
|
163
|
+
### Step 5: Write the Widget Extension code
|
|
164
|
+
|
|
165
|
+
Replace the auto-generated Widget Extension code with:
|
|
166
|
+
|
|
167
|
+
```swift
|
|
168
|
+
import WidgetKit
|
|
169
|
+
import SwiftUI
|
|
170
|
+
import ActivityKit
|
|
171
|
+
|
|
172
|
+
// CocoaPods imports:
|
|
173
|
+
import MeycagesalNativeTimer // provides WorkSessionTimerAttributes + NativeTimerManager
|
|
174
|
+
import NativeTimerKit // provides NativeTimerWidget (pre-built UI)
|
|
175
|
+
|
|
176
|
+
// If using SPM instead, replace the imports above with:
|
|
177
|
+
// import NativeTimerCore
|
|
178
|
+
// import NativeTimerLiveActivities
|
|
179
|
+
|
|
180
|
+
@main
|
|
181
|
+
struct TimerWidgetBundle: WidgetBundle {
|
|
182
|
+
var body: some Widget {
|
|
183
|
+
if #available(iOS 16.1, *) {
|
|
184
|
+
NativeTimerWidget()
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
That's it! The `NativeTimerWidget` comes pre-built with:
|
|
191
|
+
- Lock Screen banner with gradient background
|
|
192
|
+
- Dynamic Island (expanded, compact, and minimal views)
|
|
193
|
+
- Live timer counter using SwiftUI `.timer` style
|
|
194
|
+
- Customizable accent color via `primaryColor`
|
|
195
|
+
|
|
196
|
+
#### Optional: Build your own custom widget
|
|
197
|
+
|
|
198
|
+
If you want to customize the widget UI, use `WorkSessionTimerAttributes` directly:
|
|
199
|
+
|
|
200
|
+
```swift
|
|
201
|
+
import ActivityKit
|
|
202
|
+
import SwiftUI
|
|
203
|
+
import WidgetKit
|
|
204
|
+
import MeycagesalNativeTimer // or NativeTimerCore for SPM
|
|
205
|
+
|
|
206
|
+
@available(iOS 16.1, *)
|
|
207
|
+
struct MyCustomTimerWidget: Widget {
|
|
208
|
+
var body: some WidgetConfiguration {
|
|
209
|
+
ActivityConfiguration(for: WorkSessionTimerAttributes.self) { context in
|
|
210
|
+
// Lock Screen UI
|
|
211
|
+
VStack {
|
|
212
|
+
Text(context.state.title)
|
|
213
|
+
Text(context.state.elapsedTime)
|
|
214
|
+
.font(.title.bold())
|
|
215
|
+
}
|
|
216
|
+
.padding()
|
|
217
|
+
} dynamicIsland: { context in
|
|
218
|
+
DynamicIsland {
|
|
219
|
+
DynamicIslandExpandedRegion(.bottom) {
|
|
220
|
+
Text(context.state.elapsedTime)
|
|
221
|
+
.font(.title2.bold())
|
|
222
|
+
}
|
|
223
|
+
} compactLeading: {
|
|
224
|
+
Image(systemName: "timer")
|
|
225
|
+
} compactTrailing: {
|
|
226
|
+
Text(context.state.elapsedTime)
|
|
227
|
+
.font(.caption2)
|
|
228
|
+
} minimal: {
|
|
229
|
+
Image(systemName: "timer")
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Available fields in `context.state`:**
|
|
237
|
+
|
|
238
|
+
| Field | Type | Description |
|
|
239
|
+
|-------|------|-------------|
|
|
240
|
+
| `title` | `String` | Session name |
|
|
241
|
+
| `elapsedTime` | `String` | Formatted elapsed time (e.g. `"1 h 30 min"`) |
|
|
242
|
+
| `status` | `String` | Session status (e.g. `"active"`) |
|
|
243
|
+
| `startTime` | `String` | ISO 8601 start date |
|
|
244
|
+
| `primaryColor` | `String` | Hex color (e.g. `"#0045a5"`) |
|
|
245
|
+
|
|
246
|
+
### Step 6: No SwiftUICore workaround needed
|
|
247
|
+
|
|
248
|
+
> **v8.2.0+:** Previous versions required adding `-weak_framework SwiftUICore` to the App target's linker flags. **This is no longer necessary.** The plugin target does not link SwiftUI at all.
|
|
249
|
+
>
|
|
250
|
+
> If you have this flag from a previous version, you can safely **remove it** from the App target. Keep it **only** in the Widget Extension target (the `NativeTimerKit` pod handles this automatically).
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
58
254
|
## Android Setup
|
|
59
255
|
|
|
60
|
-
|
|
256
|
+
### Step 1: Permissions
|
|
257
|
+
|
|
258
|
+
Make sure your `android/app/src/main/AndroidManifest.xml` includes:
|
|
61
259
|
|
|
62
260
|
```xml
|
|
63
261
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
|
262
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
|
64
263
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
|
65
264
|
```
|
|
66
265
|
|
|
266
|
+
### Step 2: That's it
|
|
267
|
+
|
|
268
|
+
The plugin automatically registers the foreground service and notification channel. No additional native code is needed.
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
67
272
|
## Usage
|
|
68
273
|
|
|
274
|
+
### Import
|
|
275
|
+
|
|
69
276
|
```typescript
|
|
70
277
|
import { NativeTimer } from '@jesushr0013/native-timer';
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Basic Timer
|
|
71
281
|
|
|
72
|
-
|
|
73
|
-
//
|
|
74
|
-
// The iOS widget always shows "⚡ Jornada Activa" as the title.
|
|
282
|
+
```typescript
|
|
283
|
+
// Start a work shift timer
|
|
75
284
|
await NativeTimer.startTimer({
|
|
76
|
-
startTime: Date.now(),
|
|
77
|
-
title: 'Jornada Activa',
|
|
78
|
-
body: 'Timer en marcha...',
|
|
79
|
-
primaryColor: '#0045a5',
|
|
285
|
+
startTime: Date.now(), // timestamp in ms
|
|
286
|
+
title: 'Jornada Activa', // Android notification title
|
|
287
|
+
body: 'Timer en marcha...', // Android notification body
|
|
288
|
+
primaryColor: '#0045a5', // optional: accent color
|
|
80
289
|
});
|
|
81
290
|
|
|
82
|
-
// Check if
|
|
291
|
+
// Check if running
|
|
83
292
|
const { isRunning } = await NativeTimer.isTimerRunning();
|
|
84
293
|
|
|
85
|
-
// Get elapsed time
|
|
294
|
+
// Get elapsed time in milliseconds
|
|
86
295
|
const { elapsedTime } = await NativeTimer.getElapsedTime();
|
|
87
296
|
|
|
88
|
-
// Update notification text
|
|
297
|
+
// Update the Android notification text
|
|
89
298
|
await NativeTimer.updateNotification({
|
|
90
|
-
title: '
|
|
91
|
-
body: '2h 30min
|
|
299
|
+
title: 'Jornada Activa',
|
|
300
|
+
body: '2h 30min transcurridos',
|
|
92
301
|
});
|
|
93
302
|
|
|
94
|
-
// Stop
|
|
303
|
+
// Stop everything
|
|
95
304
|
await NativeTimer.stopTimer();
|
|
96
305
|
```
|
|
97
306
|
|
|
98
|
-
### Live Activities (iOS only)
|
|
307
|
+
### Live Activities (iOS 16.2+ only)
|
|
99
308
|
|
|
100
309
|
```typescript
|
|
101
|
-
// Check
|
|
310
|
+
// 1. Check if Live Activities are available
|
|
102
311
|
const { available } = await NativeTimer.areLiveActivitiesAvailable();
|
|
103
312
|
|
|
104
313
|
if (available) {
|
|
105
|
-
// Start Live Activity
|
|
314
|
+
// 2. Start a Live Activity (shows in Dynamic Island + Lock Screen)
|
|
106
315
|
const { activityId } = await NativeTimer.startLiveActivity({
|
|
107
|
-
title: '
|
|
108
|
-
startTime: new Date().toISOString(),
|
|
109
|
-
elapsedTime: '0 h 0 min',
|
|
316
|
+
title: 'Jornada Laboral',
|
|
317
|
+
startTime: new Date().toISOString(), // ISO 8601 format
|
|
318
|
+
elapsedTime: '0 h 0 min', // formatted string
|
|
110
319
|
status: 'active',
|
|
111
|
-
primaryColor: '#0045a5',
|
|
320
|
+
primaryColor: '#0045a5', // optional
|
|
112
321
|
});
|
|
113
322
|
|
|
114
|
-
// Update Live Activity
|
|
323
|
+
// 3. Update the Live Activity periodically
|
|
115
324
|
await NativeTimer.updateLiveActivity({
|
|
116
325
|
activityId: activityId!,
|
|
117
326
|
elapsedTime: '1 h 30 min',
|
|
118
327
|
status: 'active',
|
|
119
328
|
});
|
|
120
329
|
|
|
121
|
-
// Stop Live Activity
|
|
330
|
+
// 4. Stop the Live Activity when the shift ends
|
|
122
331
|
await NativeTimer.stopLiveActivity({ activityId: activityId! });
|
|
123
|
-
|
|
124
|
-
// Or stop all at once
|
|
125
|
-
await NativeTimer.stopAllLiveActivities();
|
|
126
332
|
}
|
|
333
|
+
|
|
334
|
+
// Or stop ALL Live Activities at once (useful for cleanup)
|
|
335
|
+
await NativeTimer.stopAllLiveActivities();
|
|
127
336
|
```
|
|
128
337
|
|
|
129
|
-
### Foreground
|
|
338
|
+
### Smart Foreground/Background Management
|
|
130
339
|
|
|
131
340
|
```typescript
|
|
132
|
-
|
|
133
|
-
|
|
341
|
+
import { App } from '@capacitor/app';
|
|
342
|
+
|
|
343
|
+
// Tell the plugin when the app goes to foreground/background
|
|
344
|
+
// This controls whether local notifications are shown
|
|
345
|
+
// (only shown in background to avoid bothering the user)
|
|
134
346
|
App.addListener('appStateChange', ({ isActive }) => {
|
|
135
347
|
NativeTimer.setAppForegroundState({ inForeground: isActive });
|
|
136
348
|
});
|
|
137
349
|
|
|
138
|
-
// Reset notification dismissed state
|
|
350
|
+
// Reset the "notification dismissed" state when reopening the app
|
|
139
351
|
await NativeTimer.resetNotificationState();
|
|
140
352
|
```
|
|
141
353
|
|
|
142
|
-
###
|
|
354
|
+
### Timer Update Listener
|
|
143
355
|
|
|
144
356
|
```typescript
|
|
145
|
-
// Listen for periodic
|
|
357
|
+
// Listen for periodic updates (every ~30 seconds)
|
|
146
358
|
const listener = await NativeTimer.addListener('timerUpdate', (data) => {
|
|
147
359
|
console.log('Elapsed:', data.elapsedTime, 'ms');
|
|
148
360
|
console.log('Formatted:', data.formattedTime);
|
|
149
361
|
});
|
|
150
362
|
|
|
151
|
-
//
|
|
363
|
+
// Clean up when done
|
|
152
364
|
await NativeTimer.removeAllListeners();
|
|
153
365
|
```
|
|
154
366
|
|
|
155
|
-
|
|
367
|
+
### Complete Example
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
import { NativeTimer } from '@jesushr0013/native-timer';
|
|
371
|
+
import { App } from '@capacitor/app';
|
|
372
|
+
import { Capacitor } from '@capacitor/core';
|
|
373
|
+
|
|
374
|
+
let currentActivityId: string | null = null;
|
|
375
|
+
|
|
376
|
+
// --- Start shift ---
|
|
377
|
+
async function startShift() {
|
|
378
|
+
const now = Date.now();
|
|
379
|
+
const color = '#0045a5';
|
|
380
|
+
|
|
381
|
+
// Start the native timer (Android foreground service + iOS timer)
|
|
382
|
+
await NativeTimer.startTimer({
|
|
383
|
+
startTime: now,
|
|
384
|
+
title: 'Jornada Activa',
|
|
385
|
+
body: 'Registrando tu jornada laboral...',
|
|
386
|
+
primaryColor: color,
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
// On iOS, also start a Live Activity
|
|
390
|
+
if (Capacitor.getPlatform() === 'ios') {
|
|
391
|
+
const { available } = await NativeTimer.areLiveActivitiesAvailable();
|
|
392
|
+
if (available) {
|
|
393
|
+
const { activityId } = await NativeTimer.startLiveActivity({
|
|
394
|
+
title: 'Jornada Laboral',
|
|
395
|
+
startTime: new Date(now).toISOString(),
|
|
396
|
+
elapsedTime: '0 h 0 min',
|
|
397
|
+
status: 'active',
|
|
398
|
+
primaryColor: color,
|
|
399
|
+
});
|
|
400
|
+
currentActivityId = activityId ?? null;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// --- Stop shift ---
|
|
406
|
+
async function stopShift() {
|
|
407
|
+
await NativeTimer.stopTimer();
|
|
408
|
+
|
|
409
|
+
if (currentActivityId) {
|
|
410
|
+
await NativeTimer.stopLiveActivity({ activityId: currentActivityId });
|
|
411
|
+
currentActivityId = null;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// --- Foreground/background awareness ---
|
|
416
|
+
App.addListener('appStateChange', ({ isActive }) => {
|
|
417
|
+
NativeTimer.setAppForegroundState({ inForeground: isActive });
|
|
418
|
+
if (isActive) {
|
|
419
|
+
NativeTimer.resetNotificationState();
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
---
|
|
425
|
+
|
|
426
|
+
## API Reference
|
|
156
427
|
|
|
157
428
|
### `startTimer(options)`
|
|
158
429
|
|
|
159
|
-
|
|
430
|
+
Starts the native timer. On Android, creates a Foreground Service with a persistent notification. On iOS, prepares the internal timer for Live Activities.
|
|
160
431
|
|
|
161
432
|
| Param | Type | Required | Description |
|
|
162
433
|
|-------|------|----------|-------------|
|
|
163
|
-
| `startTime` | `number` | Yes | Timestamp
|
|
164
|
-
| `title` | `string` | Yes |
|
|
165
|
-
| `body` | `string` | Yes |
|
|
166
|
-
| `primaryColor` | `string` | No |
|
|
434
|
+
| `startTime` | `number` | Yes | Timestamp in milliseconds (e.g. `Date.now()`) |
|
|
435
|
+
| `title` | `string` | Yes | Notification title (Android only) |
|
|
436
|
+
| `body` | `string` | Yes | Notification body (Android only) |
|
|
437
|
+
| `primaryColor` | `string` | No | Hex color (e.g. `"#0045a5"`) for notification and widget accent |
|
|
167
438
|
|
|
168
439
|
**Returns:** `Promise<{ success: boolean }>`
|
|
169
440
|
|
|
@@ -171,7 +442,7 @@ Inicia el timer nativo. En Android crea un Foreground Service con notificación
|
|
|
171
442
|
|
|
172
443
|
### `stopTimer()`
|
|
173
444
|
|
|
174
|
-
|
|
445
|
+
Stops the timer, cancels the Foreground Service (Android), and removes all pending notifications.
|
|
175
446
|
|
|
176
447
|
**Returns:** `Promise<{ success: boolean }>`
|
|
177
448
|
|
|
@@ -179,12 +450,12 @@ Detiene el timer, cancela el Foreground Service (Android) y elimina todas las no
|
|
|
179
450
|
|
|
180
451
|
### `updateNotification(options)`
|
|
181
452
|
|
|
182
|
-
|
|
453
|
+
Updates the Android notification text. On iOS, use `updateLiveActivity()` instead.
|
|
183
454
|
|
|
184
455
|
| Param | Type | Required | Description |
|
|
185
456
|
|-------|------|----------|-------------|
|
|
186
|
-
| `title` | `string` | Yes |
|
|
187
|
-
| `body` | `string` | Yes |
|
|
457
|
+
| `title` | `string` | Yes | New notification title |
|
|
458
|
+
| `body` | `string` | Yes | New notification body |
|
|
188
459
|
|
|
189
460
|
**Returns:** `Promise<{ success: boolean }>`
|
|
190
461
|
|
|
@@ -192,7 +463,7 @@ Actualiza el texto de la notificación del timer en Android. En iOS no tiene efe
|
|
|
192
463
|
|
|
193
464
|
### `isTimerRunning()`
|
|
194
465
|
|
|
195
|
-
|
|
466
|
+
Checks if a timer is currently active.
|
|
196
467
|
|
|
197
468
|
**Returns:** `Promise<{ isRunning: boolean }>`
|
|
198
469
|
|
|
@@ -200,19 +471,19 @@ Comprueba si hay un timer activo en ejecución.
|
|
|
200
471
|
|
|
201
472
|
### `getElapsedTime()`
|
|
202
473
|
|
|
203
|
-
|
|
474
|
+
Gets the elapsed time since the timer was started.
|
|
204
475
|
|
|
205
|
-
**Returns:** `Promise<{ elapsedTime: number }>` —
|
|
476
|
+
**Returns:** `Promise<{ elapsedTime: number }>` — time in milliseconds
|
|
206
477
|
|
|
207
478
|
---
|
|
208
479
|
|
|
209
480
|
### `setAppForegroundState(options)`
|
|
210
481
|
|
|
211
|
-
|
|
482
|
+
Tells the plugin whether the app is in the foreground or background. Controls whether local notifications are displayed (only shown in background).
|
|
212
483
|
|
|
213
484
|
| Param | Type | Required | Description |
|
|
214
485
|
|-------|------|----------|-------------|
|
|
215
|
-
| `inForeground` | `boolean` | Yes | `true`
|
|
486
|
+
| `inForeground` | `boolean` | Yes | `true` if the app is visible, `false` if backgrounded |
|
|
216
487
|
|
|
217
488
|
**Returns:** `Promise<{ success: boolean }>`
|
|
218
489
|
|
|
@@ -220,7 +491,7 @@ Indica al plugin si la app está en primer o segundo plano. Esto controla si las
|
|
|
220
491
|
|
|
221
492
|
### `resetNotificationState()`
|
|
222
493
|
|
|
223
|
-
|
|
494
|
+
Resets the internal "notification dismissed" state. Call this when reopening the app so notifications can be shown again in background.
|
|
224
495
|
|
|
225
496
|
**Returns:** `Promise<{ success: boolean }>`
|
|
226
497
|
|
|
@@ -228,7 +499,7 @@ Resetea el estado interno de "notificación descartada". Útil al volver a abrir
|
|
|
228
499
|
|
|
229
500
|
### `areLiveActivitiesAvailable()` *(iOS only)*
|
|
230
501
|
|
|
231
|
-
|
|
502
|
+
Checks if the device supports Live Activities (requires iOS 16.2+ and user permission).
|
|
232
503
|
|
|
233
504
|
**Returns:** `Promise<{ available: boolean }>`
|
|
234
505
|
|
|
@@ -236,29 +507,29 @@ Verifica si el dispositivo soporta Live Activities (requiere iOS 16.2+ y que el
|
|
|
236
507
|
|
|
237
508
|
### `startLiveActivity(options)` *(iOS only)*
|
|
238
509
|
|
|
239
|
-
|
|
510
|
+
Starts a Live Activity showing the shift timer on Dynamic Island (iPhone 14 Pro+) and Lock Screen.
|
|
240
511
|
|
|
241
512
|
| Param | Type | Required | Description |
|
|
242
513
|
|-------|------|----------|-------------|
|
|
243
|
-
| `title` | `string` | Yes |
|
|
244
|
-
| `startTime` | `string` | Yes |
|
|
245
|
-
| `elapsedTime` | `string` | Yes |
|
|
246
|
-
| `status` | `string` | Yes |
|
|
247
|
-
| `primaryColor` | `string` | No |
|
|
514
|
+
| `title` | `string` | Yes | Session name |
|
|
515
|
+
| `startTime` | `string` | Yes | Start date/time in ISO 8601 format |
|
|
516
|
+
| `elapsedTime` | `string` | Yes | Formatted elapsed time (e.g. `"1 h 30 min"`) |
|
|
517
|
+
| `status` | `string` | Yes | Session status (e.g. `"active"`, `"paused"`) |
|
|
518
|
+
| `primaryColor` | `string` | No | Hex color for the widget (default: `"#0045a5"`) |
|
|
248
519
|
|
|
249
|
-
**Returns:** `Promise<{ success: boolean; activityId?: string }>`
|
|
520
|
+
**Returns:** `Promise<{ success: boolean; activityId?: string }>`
|
|
250
521
|
|
|
251
522
|
---
|
|
252
523
|
|
|
253
524
|
### `updateLiveActivity(options)` *(iOS only)*
|
|
254
525
|
|
|
255
|
-
|
|
526
|
+
Updates an existing Live Activity with new elapsed time and status.
|
|
256
527
|
|
|
257
528
|
| Param | Type | Required | Description |
|
|
258
529
|
|-------|------|----------|-------------|
|
|
259
|
-
| `activityId` | `string` | Yes | ID
|
|
260
|
-
| `elapsedTime` | `string` | Yes |
|
|
261
|
-
| `status` | `string` | Yes |
|
|
530
|
+
| `activityId` | `string` | Yes | ID returned by `startLiveActivity` |
|
|
531
|
+
| `elapsedTime` | `string` | Yes | Updated elapsed time (e.g. `"2 h 15 min"`) |
|
|
532
|
+
| `status` | `string` | Yes | Current status (e.g. `"active"`) |
|
|
262
533
|
|
|
263
534
|
**Returns:** `Promise<{ success: boolean }>`
|
|
264
535
|
|
|
@@ -266,11 +537,11 @@ Actualiza una Live Activity existente con nuevo tiempo transcurrido y estado.
|
|
|
266
537
|
|
|
267
538
|
### `stopLiveActivity(options)` *(iOS only)*
|
|
268
539
|
|
|
269
|
-
|
|
540
|
+
Stops a specific Live Activity and removes it from Dynamic Island and Lock Screen.
|
|
270
541
|
|
|
271
542
|
| Param | Type | Required | Description |
|
|
272
543
|
|-------|------|----------|-------------|
|
|
273
|
-
| `activityId` | `string` | Yes | ID
|
|
544
|
+
| `activityId` | `string` | Yes | ID returned by `startLiveActivity` |
|
|
274
545
|
|
|
275
546
|
**Returns:** `Promise<{ success: boolean }>`
|
|
276
547
|
|
|
@@ -278,7 +549,7 @@ Detiene una Live Activity específica y la elimina de la Dynamic Island y pantal
|
|
|
278
549
|
|
|
279
550
|
### `stopAllLiveActivities()` *(iOS only)*
|
|
280
551
|
|
|
281
|
-
|
|
552
|
+
Stops **all** active Live Activities from this plugin. Useful for cleanup on logout or shift end.
|
|
282
553
|
|
|
283
554
|
**Returns:** `Promise<{ success: boolean }>`
|
|
284
555
|
|
|
@@ -286,7 +557,7 @@ Detiene **todas** las Live Activities activas del plugin. Útil para limpieza al
|
|
|
286
557
|
|
|
287
558
|
### `addListener('timerUpdate', callback)`
|
|
288
559
|
|
|
289
|
-
|
|
560
|
+
Listens for periodic timer updates (~30 seconds).
|
|
290
561
|
|
|
291
562
|
```typescript
|
|
292
563
|
const listener = await NativeTimer.addListener('timerUpdate', (data) => {
|
|
@@ -299,10 +570,111 @@ const listener = await NativeTimer.addListener('timerUpdate', (data) => {
|
|
|
299
570
|
|
|
300
571
|
### `removeAllListeners()`
|
|
301
572
|
|
|
302
|
-
|
|
573
|
+
Removes all registered listeners.
|
|
303
574
|
|
|
304
575
|
**Returns:** `Promise<void>`
|
|
305
576
|
|
|
577
|
+
---
|
|
578
|
+
|
|
579
|
+
## Upgrading from v8.1.x to v8.2.0
|
|
580
|
+
|
|
581
|
+
### What changed
|
|
582
|
+
|
|
583
|
+
The iOS code was split from 1 target into 3 separate targets. The main plugin **no longer links SwiftUI**, completely fixing the `SwiftUICore` crash on iOS 16.
|
|
584
|
+
|
|
585
|
+
### Step-by-step upgrade
|
|
586
|
+
|
|
587
|
+
```bash
|
|
588
|
+
# 1. Update the plugin
|
|
589
|
+
npm install @jesushr0013/native-timer@8.2.0
|
|
590
|
+
|
|
591
|
+
# 2. Remove local patches (if you had modified the plugin's Swift files locally)
|
|
592
|
+
# Only needed if you used patch-package:
|
|
593
|
+
rm patches/@jesushr0013+native-timer+*.patch # skip if you don't have patches
|
|
594
|
+
|
|
595
|
+
# 3. Sync Capacitor
|
|
596
|
+
npx cap sync ios
|
|
597
|
+
|
|
598
|
+
# 4. Reinstall pods from scratch (important: pod paths changed)
|
|
599
|
+
cd ios
|
|
600
|
+
pod deintegrate
|
|
601
|
+
pod install --repo-update
|
|
602
|
+
cd ..
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
Then in **Xcode**:
|
|
606
|
+
|
|
607
|
+
5. **Remove the old linker flag** (if you had it):
|
|
608
|
+
- Select your **App** target → Build Settings → Other Linker Flags
|
|
609
|
+
- **Remove** `-weak_framework SwiftUICore`
|
|
610
|
+
- This flag is no longer needed in the App target
|
|
611
|
+
|
|
612
|
+
6. **Clean and rebuild**:
|
|
613
|
+
- Product → Clean Build Folder (`Cmd+Shift+K`)
|
|
614
|
+
- Build (`Cmd+B`)
|
|
615
|
+
|
|
616
|
+
7. **Test on iOS 16 simulator** — the app should launch without any `SwiftUICore` crash.
|
|
617
|
+
|
|
618
|
+
### Breaking changes for Widget Extension users
|
|
619
|
+
|
|
620
|
+
If your Widget Extension was importing files from the old `LiveActivitiesKit` directory:
|
|
621
|
+
|
|
622
|
+
| Before (v8.1.x) | After (v8.2.0) |
|
|
623
|
+
|------------------|----------------|
|
|
624
|
+
| Files in `ios/LiveActivitiesKit/` | Files split into `ios/Core/` + `ios/LiveActivities/` |
|
|
625
|
+
| Single target compiled everything together | 3 separate targets |
|
|
626
|
+
| `import MeycagesalNativeTimer` (CocoaPods) | `import MeycagesalNativeTimer` + `import NativeTimerKit` (CocoaPods) |
|
|
627
|
+
| N/A (SPM) | `import NativeTimerCore` + `import NativeTimerLiveActivities` (SPM) |
|
|
628
|
+
|
|
629
|
+
---
|
|
630
|
+
|
|
631
|
+
## Troubleshooting
|
|
632
|
+
|
|
633
|
+
### `SwiftUICore not available` crash on iOS 16
|
|
634
|
+
|
|
635
|
+
**Cause:** You are using a version < 8.2.0, or pods were not reinstalled after upgrading.
|
|
636
|
+
|
|
637
|
+
**Fix:**
|
|
638
|
+
```bash
|
|
639
|
+
npm install @jesushr0013/native-timer@8.2.0
|
|
640
|
+
npx cap sync ios
|
|
641
|
+
cd ios && pod deintegrate && pod install --repo-update && cd ..
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
### Live Activities not appearing
|
|
645
|
+
|
|
646
|
+
1. Check that `NSSupportsLiveActivities` is `true` in `Info.plist`
|
|
647
|
+
2. Check that the Widget Extension target has `NativeTimerKit` (CocoaPods) or `NativeTimerLiveActivities` (SPM)
|
|
648
|
+
3. Check that the device is running iOS 16.2+
|
|
649
|
+
4. Check that Live Activities are enabled in Settings → Your App → Live Activities
|
|
650
|
+
|
|
651
|
+
### Pod install fails with "Unable to find a specification for NativeTimerKit"
|
|
652
|
+
|
|
653
|
+
Run with `--repo-update` to refresh the pod specs cache:
|
|
654
|
+
|
|
655
|
+
```bash
|
|
656
|
+
cd ios
|
|
657
|
+
pod install --repo-update
|
|
658
|
+
cd ..
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
### Widget Extension shows blank / doesn't load
|
|
662
|
+
|
|
663
|
+
Make sure your Widget Extension's `@main` bundle includes the widget:
|
|
664
|
+
|
|
665
|
+
```swift
|
|
666
|
+
@main
|
|
667
|
+
struct TimerWidgetBundle: WidgetBundle {
|
|
668
|
+
var body: some Widget {
|
|
669
|
+
if #available(iOS 16.1, *) {
|
|
670
|
+
NativeTimerWidget()
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
---
|
|
677
|
+
|
|
306
678
|
## License
|
|
307
679
|
|
|
308
680
|
MIT
|
|
File without changes
|
|
Binary file
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
#
|
|
2
|
-
gradle.version=
|
|
1
|
+
#Thu Apr 16 07:13:06 CEST 2026
|
|
2
|
+
gradle.version=8.9
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -6,31 +6,6 @@ import UIKit
|
|
|
6
6
|
import ActivityKit
|
|
7
7
|
#endif
|
|
8
8
|
|
|
9
|
-
@available(iOS 16.2, *)
|
|
10
|
-
public struct WorkSessionTimerAttributes: ActivityAttributes {
|
|
11
|
-
public struct ContentState: Codable, Hashable {
|
|
12
|
-
public let title: String
|
|
13
|
-
public let elapsedTime: String
|
|
14
|
-
public let status: String
|
|
15
|
-
public let startTime: String
|
|
16
|
-
public let primaryColor: String
|
|
17
|
-
|
|
18
|
-
public init(title: String, elapsedTime: String, status: String, startTime: String, primaryColor: String = "#0045a5") {
|
|
19
|
-
self.title = title
|
|
20
|
-
self.elapsedTime = elapsedTime
|
|
21
|
-
self.status = status
|
|
22
|
-
self.startTime = startTime
|
|
23
|
-
self.primaryColor = primaryColor
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
public let sessionName: String
|
|
28
|
-
|
|
29
|
-
public init(sessionName: String) {
|
|
30
|
-
self.sessionName = sessionName
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
9
|
public class NativeTimerManager {
|
|
35
10
|
private var startTime: Date?
|
|
36
11
|
private var timer: Timer?
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
#if canImport(ActivityKit)
|
|
4
|
+
import ActivityKit
|
|
5
|
+
#endif
|
|
6
|
+
|
|
7
|
+
#if canImport(ActivityKit)
|
|
8
|
+
@available(iOS 16.1, *)
|
|
9
|
+
public struct WorkSessionTimerAttributes: ActivityAttributes {
|
|
10
|
+
public struct ContentState: Codable, Hashable {
|
|
11
|
+
public let title: String
|
|
12
|
+
public let elapsedTime: String
|
|
13
|
+
public let status: String
|
|
14
|
+
public let startTime: String
|
|
15
|
+
public let primaryColor: String
|
|
16
|
+
|
|
17
|
+
public init(title: String, elapsedTime: String, status: String, startTime: String, primaryColor: String = "#0045a5") {
|
|
18
|
+
self.title = title
|
|
19
|
+
self.elapsedTime = elapsedTime
|
|
20
|
+
self.status = status
|
|
21
|
+
self.startTime = startTime
|
|
22
|
+
self.primaryColor = primaryColor
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public let sessionName: String
|
|
27
|
+
|
|
28
|
+
public init(sessionName: String) {
|
|
29
|
+
self.sessionName = sessionName
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
#endif
|
|
@@ -3,7 +3,13 @@ import SwiftUI
|
|
|
3
3
|
import WidgetKit
|
|
4
4
|
import ActivityKit
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
#if canImport(Jesushr0013NativeTimer)
|
|
7
|
+
import Jesushr0013NativeTimer
|
|
8
|
+
#elseif canImport(MeycagesalNativeTimer)
|
|
9
|
+
import MeycagesalNativeTimer
|
|
10
|
+
#endif
|
|
11
|
+
|
|
12
|
+
@available(iOS 16.1, *)
|
|
7
13
|
public struct NativeTimerWidget: Widget {
|
|
8
14
|
public var body: some WidgetConfiguration {
|
|
9
15
|
ActivityConfiguration(for: WorkSessionTimerAttributes.self) { context in
|
|
@@ -467,7 +473,7 @@ extension Color {
|
|
|
467
473
|
}
|
|
468
474
|
}
|
|
469
475
|
|
|
470
|
-
@available(iOS 16.
|
|
476
|
+
@available(iOS 16.1, *)
|
|
471
477
|
public struct NativeTimerWidgetBundle: WidgetBundle {
|
|
472
478
|
public var body: some Widget {
|
|
473
479
|
NativeTimerWidget()
|