@greatdayhr/capacitor-datetime-setting 1.2.0 → 2.0.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.
@@ -4,10 +4,11 @@ import Capacitor
4
4
  /**
5
5
  * DateTimeSettingPlugin
6
6
  *
7
- * Capacitor plugin to check auto time/timezone settings and open device settings.
7
+ * Capacitor plugin for comprehensive date/time change detection and management.
8
+ * Cloned from date_change_checker Flutter plugin.
8
9
  *
9
10
  * iOS implementation uses network time comparison with AutoDateTimeDetector
10
- * for reliable detection of automatic date/time settings.
11
+ * for reliable detection of automatic date/time settings and changes.
11
12
  */
12
13
  @objc(DateTimeSettingPlugin)
13
14
  public class DateTimeSettingPlugin: CAPPlugin {
@@ -24,60 +25,210 @@ public class DateTimeSettingPlugin: CAPPlugin {
24
25
  }
25
26
 
26
27
  /**
27
- * Check if automatic time is enabled on the device.
28
+ * Check if date/time has been manually changed (inverse of auto time enabled).
29
+ * This is a simple wrapper that returns !isAutoDateTimeEnabled.
28
30
  *
29
- * iOS implementation uses network time comparison for reliable detection.
30
- * Results are cached for 30 seconds to minimize network calls.
31
+ * From date_change_checker source:
32
+ * Returns true if auto date/time is disabled, indicating possible manual changes.
31
33
  */
32
- @objc func timeIsAuto(_ call: CAPPluginCall) {
34
+ @objc func isDateTimeChanged(_ call: CAPPluginCall) {
33
35
  AutoDateTimeDetector.isAutoDateTimeEnabled { isEnabled in
34
36
  DispatchQueue.main.async {
37
+ // Return !isEnabled to indicate if date/time has been changed
35
38
  call.resolve([
36
- "value": isEnabled
39
+ "changed": !isEnabled
37
40
  ])
38
41
  }
39
42
  }
40
43
  }
41
44
 
45
+ // MARK: - Date/Time Change Detection
46
+
42
47
  /**
43
- * Check if automatic timezone is enabled on the device.
44
- *
45
- * iOS implementation uses the same detection as timeIsAuto since
46
- * auto timezone and auto date/time are typically linked on iOS.
48
+ * Detects if the device's date/time has been manually changed
47
49
  */
48
- @objc func timeZoneIsAuto(_ call: CAPPluginCall) {
49
- AutoDateTimeDetector.isAutoDateTimeEnabled { isEnabled in
50
+ @objc func detectDateTimeChange(_ call: CAPPluginCall) {
51
+ AutoDateTimeDetector.detectDateTimeChange { changeDetected in
50
52
  DispatchQueue.main.async {
51
53
  call.resolve([
52
- "value": isEnabled
54
+ "changed": changeDetected
53
55
  ])
54
56
  }
55
57
  }
56
58
  }
57
59
 
58
60
  /**
59
- * Open the device's Settings app.
60
- *
61
- * On iOS, this opens the main Settings app as there's no direct way
62
- * to open the Date & Time settings page.
61
+ * Comprehensive date and time change detection with detailed analysis
63
62
  */
64
- @objc func openSetting(_ call: CAPPluginCall) {
65
- DispatchQueue.main.async {
66
- if let settingsUrl = URL(string: UIApplication.openSettingsURLString) {
67
- if UIApplication.shared.canOpenURL(settingsUrl) {
68
- UIApplication.shared.open(settingsUrl, options: [:]) { success in
69
- if success {
70
- call.resolve()
71
- } else {
72
- call.reject("Failed to open settings")
73
- }
74
- }
75
- } else {
76
- call.reject("Cannot open settings URL")
63
+ @objc func detectComprehensiveDateTimeChange(_ call: CAPPluginCall) {
64
+ AutoDateTimeDetector.detectComprehensiveDateTimeChange { result in
65
+ DispatchQueue.main.async {
66
+ let changeTypeString: String
67
+ switch result.changeType {
68
+ case .noChange:
69
+ changeTypeString = "noChange"
70
+ case .timeOnly:
71
+ changeTypeString = "timeOnly"
72
+ case .dateOnly:
73
+ changeTypeString = "dateOnly"
74
+ case .dateAndTime:
75
+ changeTypeString = "dateAndTime"
76
+ }
77
+
78
+ var resultDict: [String: Any] = [
79
+ "changeType": changeTypeString,
80
+ "timeDifference": result.timeDifference,
81
+ "dateChanged": result.dateChanged,
82
+ "timeChanged": result.timeChanged,
83
+ "isAutoDateTimeEnabled": result.isAutoDateTimeEnabled,
84
+ "currentDate": result.currentDate.timeIntervalSince1970
85
+ ]
86
+
87
+ if let previousDate = result.previousDate {
88
+ resultDict["previousDate"] = previousDate.timeIntervalSince1970
77
89
  }
78
- } else {
79
- call.reject("Invalid settings URL")
90
+
91
+ call.resolve(resultDict)
92
+ }
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Detects specifically if only the date has been changed
98
+ */
99
+ @objc func detectDateOnlyChange(_ call: CAPPluginCall) {
100
+ AutoDateTimeDetector.detectDateOnlyChange { changeDetected in
101
+ DispatchQueue.main.async {
102
+ call.resolve([
103
+ "changed": changeDetected
104
+ ])
80
105
  }
81
106
  }
82
107
  }
108
+
109
+ /**
110
+ * Comprehensive date and time change detection with automatic notifications
111
+ */
112
+ @objc func detectAndNotifyDateTimeChanges(_ call: CAPPluginCall) {
113
+ AutoDateTimeDetector.detectAndNotifyDateTimeChanges { result in
114
+ DispatchQueue.main.async {
115
+ let changeTypeString: String
116
+ switch result.changeType {
117
+ case .noChange:
118
+ changeTypeString = "noChange"
119
+ case .timeOnly:
120
+ changeTypeString = "timeOnly"
121
+ case .dateOnly:
122
+ changeTypeString = "dateOnly"
123
+ case .dateAndTime:
124
+ changeTypeString = "dateAndTime"
125
+ }
126
+
127
+ var resultDict: [String: Any] = [
128
+ "changeType": changeTypeString,
129
+ "timeDifference": result.timeDifference,
130
+ "dateChanged": result.dateChanged,
131
+ "timeChanged": result.timeChanged,
132
+ "isAutoDateTimeEnabled": result.isAutoDateTimeEnabled,
133
+ "currentDate": result.currentDate.timeIntervalSince1970
134
+ ]
135
+
136
+ if let previousDate = result.previousDate {
137
+ resultDict["previousDate"] = previousDate.timeIntervalSince1970
138
+ }
139
+
140
+ call.resolve(resultDict)
141
+ }
142
+ }
143
+ }
144
+
145
+ // MARK: - Time Utilities
146
+
147
+ /**
148
+ * Get the device's current local time
149
+ */
150
+ @objc func getLocalTime(_ call: CAPPluginCall) {
151
+ let currentTime = AutoDateTimeDetector.getCurrentLocalTime()
152
+ let timestamp = currentTime.timeIntervalSince1970
153
+ call.resolve([
154
+ "timestamp": timestamp
155
+ ])
156
+ }
157
+
158
+ /**
159
+ * Fetch accurate UTC time from internet time server
160
+ */
161
+ @objc func getInternetUTCTime(_ call: CAPPluginCall) {
162
+ AutoDateTimeDetector.fetchInternetUTCTime { result in
163
+ DispatchQueue.main.async {
164
+ switch result {
165
+ case .success(let utcTime):
166
+ let timestamp = utcTime.timeIntervalSince1970
167
+ call.resolve([
168
+ "timestamp": timestamp
169
+ ])
170
+ case .failure(let error):
171
+ call.reject("Failed to fetch internet time", nil, error)
172
+ }
173
+ }
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Convert local time to UTC
179
+ */
180
+ @objc func convertToLocalTime(_ call: CAPPluginCall) {
181
+ guard let timestamp = call.getDouble("timestamp") else {
182
+ call.reject("Missing timestamp parameter")
183
+ return
184
+ }
185
+
186
+ let localTime = Date(timeIntervalSince1970: timestamp)
187
+ let utcTime = AutoDateTimeDetector.convertLocalTimeToUTC(localTime)
188
+ let utcTimestamp = utcTime.timeIntervalSince1970
189
+
190
+ call.resolve([
191
+ "timestamp": utcTimestamp
192
+ ])
193
+ }
194
+
195
+ // MARK: - Timestamp Management
196
+
197
+ /**
198
+ * Set the stored timestamp for future change detection
199
+ */
200
+ @objc func setStoredTimestamp(_ call: CAPPluginCall) {
201
+ guard let timestamp = call.getDouble("timestamp") else {
202
+ call.reject("Missing timestamp parameter")
203
+ return
204
+ }
205
+
206
+ let date = Date(timeIntervalSince1970: timestamp)
207
+ AutoDateTimeDetector.setStoredTimestamp(date)
208
+ call.resolve()
209
+ }
210
+
211
+ /**
212
+ * Get the currently stored timestamp
213
+ */
214
+ @objc func getStoredTimestamp(_ call: CAPPluginCall) {
215
+ if let storedTime = AutoDateTimeDetector.getStoredTimestamp() {
216
+ let timestamp = storedTime.timeIntervalSince1970
217
+ call.resolve([
218
+ "timestamp": timestamp
219
+ ])
220
+ } else {
221
+ call.resolve([
222
+ "timestamp": NSNull()
223
+ ])
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Reset the detector (clears all stored data and cache)
229
+ */
230
+ @objc func resetDetector(_ call: CAPPluginCall) {
231
+ AutoDateTimeDetector.reset()
232
+ call.resolve()
233
+ }
83
234
  }
@@ -0,0 +1,259 @@
1
+ import Foundation
2
+ import UserNotifications
3
+ import UIKit
4
+
5
+ /**
6
+ * NotificationManager handles displaying notifications for date/time change detection
7
+ * Supports iOS 10+ with UserNotifications framework and fallback for older versions
8
+ */
9
+ class NotificationManager: NSObject {
10
+
11
+ static let shared = NotificationManager()
12
+
13
+ private override init() {
14
+ super.init()
15
+ }
16
+
17
+ /**
18
+ * Requests notification permission from the user
19
+ * Should be called during app initialization
20
+ */
21
+ func requestNotificationPermission(completion: @escaping (Bool) -> Void) {
22
+ if #available(iOS 10.0, *) {
23
+ let center = UNUserNotificationCenter.current()
24
+ center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
25
+ DispatchQueue.main.async {
26
+ if let error = error {
27
+ print("Notification permission error: \(error.localizedDescription)")
28
+ completion(false)
29
+ } else {
30
+ completion(granted)
31
+ }
32
+ }
33
+ }
34
+ } else {
35
+ // Fallback for iOS 9 and earlier
36
+ let settings = UIUserNotificationSettings(types: [.alert, .sound, .badge], categories: nil)
37
+ UIApplication.shared.registerUserNotificationSettings(settings)
38
+ completion(true)
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Displays a notification when automatic date/time is disabled
44
+ */
45
+ func showAutoDateTimeDisabledNotification() {
46
+ let title = "Automatic Date & Time Disabled"
47
+ let body = "Your device's automatic date and time setting appears to be disabled. Please enable it in Settings > General > Date & Time for accurate time synchronization."
48
+
49
+ showNotification(title: title, body: body, identifier: "auto_datetime_disabled")
50
+ }
51
+
52
+ /**
53
+ * Displays a notification when date-only change is detected
54
+ */
55
+ func showDateOnlyChangeNotification() {
56
+ let title = "Date Change Detected"
57
+ let body = "The date has been manually changed while the time remained unchanged. This may indicate that automatic date and time is disabled."
58
+
59
+ showNotification(title: title, body: body, identifier: "date_only_change")
60
+ }
61
+
62
+ /**
63
+ * Displays a notification when time-only change is detected
64
+ */
65
+ func showTimeOnlyChangeNotification() {
66
+ let title = "Time Change Detected"
67
+ let body = "The time has been manually changed. This may indicate that automatic date and time is disabled."
68
+
69
+ showNotification(title: title, body: body, identifier: "time_only_change")
70
+ }
71
+
72
+ /**
73
+ * Displays a notification when both date and time changes are detected
74
+ */
75
+ func showDateTimeChangeNotification() {
76
+ let title = "Date & Time Change Detected"
77
+ let body = "Both date and time have been manually changed. Please ensure automatic date and time is enabled for accurate synchronization."
78
+
79
+ showNotification(title: title, body: body, identifier: "datetime_change")
80
+ }
81
+
82
+ /**
83
+ * Displays a custom notification with specified parameters
84
+ */
85
+ func showCustomNotification(title: String, body: String, identifier: String? = nil) {
86
+ let notificationId = identifier ?? "custom_notification_\(Date().timeIntervalSince1970)"
87
+ showNotification(title: title, body: body, identifier: notificationId)
88
+ }
89
+
90
+ /**
91
+ * Core notification display method that handles iOS version compatibility
92
+ */
93
+ private func showNotification(title: String, body: String, identifier: String) {
94
+ if #available(iOS 10.0, *) {
95
+ showModernNotification(title: title, body: body, identifier: identifier)
96
+ } else {
97
+ showLegacyNotification(title: title, body: body)
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Shows notification using UserNotifications framework (iOS 10+)
103
+ */
104
+ @available(iOS 10.0, *)
105
+ private func showModernNotification(title: String, body: String, identifier: String) {
106
+ let center = UNUserNotificationCenter.current()
107
+
108
+ // Check if notifications are authorized
109
+ center.getNotificationSettings { settings in
110
+ guard settings.authorizationStatus == .authorized else {
111
+ print("Notifications not authorized")
112
+ return
113
+ }
114
+
115
+ // Create notification content
116
+ let content = UNMutableNotificationContent()
117
+ content.title = title
118
+ content.body = body
119
+ content.sound = .default
120
+ content.badge = NSNumber(value: UIApplication.shared.applicationIconBadgeNumber + 1)
121
+
122
+ // Create trigger (immediate delivery)
123
+ let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.1, repeats: false)
124
+
125
+ // Create request
126
+ let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
127
+
128
+ // Schedule notification
129
+ center.add(request) { error in
130
+ if let error = error {
131
+ print("Failed to schedule notification: \(error.localizedDescription)")
132
+ } else {
133
+ print("Notification scheduled successfully: \(title)")
134
+ }
135
+ }
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Shows notification using legacy UILocalNotification (iOS 9 and earlier)
141
+ */
142
+ private func showLegacyNotification(title: String, body: String) {
143
+ DispatchQueue.main.async {
144
+ if #available(iOS 8.0, *) {
145
+ let notification = UILocalNotification()
146
+ notification.alertTitle = title
147
+ notification.alertBody = body
148
+ notification.soundName = UILocalNotificationDefaultSoundName
149
+ notification.applicationIconBadgeNumber = UIApplication.shared.applicationIconBadgeNumber + 1
150
+ notification.fireDate = Date(timeIntervalSinceNow: 0.1)
151
+
152
+ UIApplication.shared.scheduleLocalNotification(notification)
153
+ print("Legacy notification scheduled: \(title)")
154
+ } else {
155
+ // For iOS 7 and earlier, show an alert
156
+ let alert = UIAlertView(title: title, message: body, delegate: nil, cancelButtonTitle: "OK")
157
+ alert.show()
158
+ print("Alert shown for iOS 7: \(title)")
159
+ }
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Cancels all pending notifications
165
+ */
166
+ func cancelAllNotifications() {
167
+ if #available(iOS 10.0, *) {
168
+ UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
169
+ UNUserNotificationCenter.current().removeAllDeliveredNotifications()
170
+ } else {
171
+ UIApplication.shared.cancelAllLocalNotifications()
172
+ }
173
+
174
+ // Reset badge count
175
+ DispatchQueue.main.async {
176
+ UIApplication.shared.applicationIconBadgeNumber = 0
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Cancels notifications with specific identifier
182
+ */
183
+ func cancelNotification(withIdentifier identifier: String) {
184
+ if #available(iOS 10.0, *) {
185
+ UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [identifier])
186
+ UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [identifier])
187
+ } else {
188
+ // For legacy notifications, we need to cancel all and reschedule others
189
+ // This is a limitation of the older notification system
190
+ let scheduledNotifications = UIApplication.shared.scheduledLocalNotifications ?? []
191
+ for notification in scheduledNotifications {
192
+ if notification.userInfo?["identifier"] as? String == identifier {
193
+ UIApplication.shared.cancelLocalNotification(notification)
194
+ }
195
+ }
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Checks current notification authorization status
201
+ */
202
+ func checkNotificationPermission(completion: @escaping (Bool) -> Void) {
203
+ if #available(iOS 10.0, *) {
204
+ UNUserNotificationCenter.current().getNotificationSettings { settings in
205
+ DispatchQueue.main.async {
206
+ completion(settings.authorizationStatus == .authorized)
207
+ }
208
+ }
209
+ } else {
210
+ // For older iOS versions, check if notification types are enabled
211
+ let settings = UIApplication.shared.currentUserNotificationSettings
212
+ completion(settings?.types != [])
213
+ }
214
+ }
215
+ }
216
+
217
+ // MARK: - UNUserNotificationCenterDelegate
218
+
219
+ @available(iOS 10.0, *)
220
+ extension NotificationManager: UNUserNotificationCenterDelegate {
221
+
222
+ /**
223
+ * Handle notification when app is in foreground
224
+ */
225
+ func userNotificationCenter(_ center: UNUserNotificationCenter,
226
+ willPresent notification: UNNotification,
227
+ withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
228
+ // Show notification even when app is in foreground
229
+ if #available(iOS 14.0, *) {
230
+ completionHandler([.banner, .sound, .badge])
231
+ } else {
232
+ completionHandler([.alert, .sound, .badge])
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Handle notification tap
238
+ */
239
+ func userNotificationCenter(_ center: UNUserNotificationCenter,
240
+ didReceive response: UNNotificationResponse,
241
+ withCompletionHandler completionHandler: @escaping () -> Void) {
242
+ let identifier = response.notification.request.identifier
243
+ print("Notification tapped: \(identifier)")
244
+
245
+ // Handle specific notification actions here if needed
246
+ switch identifier {
247
+ case "auto_datetime_disabled":
248
+ // Could open settings or show more information
249
+ break
250
+ case "date_only_change", "time_only_change", "datetime_change":
251
+ // Could show detailed change information
252
+ break
253
+ default:
254
+ break
255
+ }
256
+
257
+ completionHandler()
258
+ }
259
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@greatdayhr/capacitor-datetime-setting",
3
- "version": "1.2.0",
4
- "description": "Capacitor plugin to get information about auto time and auto timezone, open setting if not set to auto",
3
+ "version": "2.0.1",
4
+ "description": "Capacitor plugin for comprehensive iOS date/time change detection. Cloned from date_change_checker Flutter plugin.",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",
7
7
  "types": "dist/esm/index.d.ts",
@@ -27,9 +27,12 @@
27
27
  "plugin",
28
28
  "native",
29
29
  "datetime",
30
- "timezone",
31
- "auto-time",
32
- "settings"
30
+ "time-detection",
31
+ "date-change",
32
+ "ios",
33
+ "date-change-checker",
34
+ "automatic-time",
35
+ "network-time"
33
36
  ],
34
37
  "scripts": {
35
38
  "verify": "npm run verify:ios && npm run verify:android && npm run verify:web",