@umituz/react-native-design-system 2.3.15 → 2.3.17
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/package.json +1 -3
- package/src/index.ts +14 -0
- package/src/molecules/calendar/domain/entities/CalendarEvent.entity.ts +0 -43
- package/src/molecules/calendar/index.ts +1 -25
- package/src/molecules/calendar/infrastructure/services/CalendarService.ts +1 -47
- package/src/molecules/calendar/presentation/hooks/useCalendar.ts +2 -178
- package/src/molecules/calendar/infrastructure/services/CalendarEvents.ts +0 -196
- package/src/molecules/calendar/infrastructure/services/CalendarPermissions.ts +0 -92
- package/src/molecules/calendar/infrastructure/services/CalendarSync.ts +0 -205
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-design-system",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.17",
|
|
4
4
|
"description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive and safe area utilities",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -60,7 +60,6 @@
|
|
|
60
60
|
"@umituz/react-native-localization": "latest",
|
|
61
61
|
"@umituz/react-native-uuid": "latest",
|
|
62
62
|
"expo-application": ">=5.0.0",
|
|
63
|
-
"expo-calendar": ">=13.0.0",
|
|
64
63
|
"expo-clipboard": ">=8.0.0",
|
|
65
64
|
"expo-crypto": ">=13.0.0",
|
|
66
65
|
"expo-device": ">=5.0.0",
|
|
@@ -114,7 +113,6 @@
|
|
|
114
113
|
"eslint-plugin-react-hooks": "^7.0.1",
|
|
115
114
|
"eslint-plugin-react-native": "^5.0.0",
|
|
116
115
|
"expo-application": "~5.9.1",
|
|
117
|
-
"expo-calendar": "~14.0.0",
|
|
118
116
|
"expo-clipboard": "~8.0.7",
|
|
119
117
|
"expo-crypto": "~14.0.0",
|
|
120
118
|
"expo-device": "~7.0.2",
|
package/src/index.ts
CHANGED
|
@@ -411,6 +411,20 @@ export {
|
|
|
411
411
|
type CountdownTarget,
|
|
412
412
|
type CountdownFormatOptions,
|
|
413
413
|
type CountdownDisplayConfig,
|
|
414
|
+
// Animation
|
|
415
|
+
Animated,
|
|
416
|
+
useSharedValue,
|
|
417
|
+
useAnimatedStyle,
|
|
418
|
+
withTiming,
|
|
419
|
+
withSpring,
|
|
420
|
+
withSequence,
|
|
421
|
+
withRepeat,
|
|
422
|
+
runOnJS,
|
|
423
|
+
cancelAnimation,
|
|
424
|
+
Easing,
|
|
425
|
+
type SharedValue,
|
|
426
|
+
type WithTimingConfig,
|
|
427
|
+
type WithSpringConfig,
|
|
414
428
|
} from './molecules';
|
|
415
429
|
|
|
416
430
|
// =============================================================================
|
|
@@ -74,26 +74,6 @@ export interface CalendarEvent {
|
|
|
74
74
|
*/
|
|
75
75
|
reminders?: number[];
|
|
76
76
|
|
|
77
|
-
/**
|
|
78
|
-
* System calendar sync (expo-calendar integration)
|
|
79
|
-
*/
|
|
80
|
-
systemCalendar?: {
|
|
81
|
-
/**
|
|
82
|
-
* System calendar ID where event is synced
|
|
83
|
-
*/
|
|
84
|
-
calendarId: string;
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* System event ID from device calendar
|
|
88
|
-
*/
|
|
89
|
-
eventId: string;
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Last sync timestamp
|
|
93
|
-
*/
|
|
94
|
-
lastSyncedAt: Date;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
77
|
/**
|
|
98
78
|
* App-specific metadata
|
|
99
79
|
* Use this for domain-specific properties
|
|
@@ -158,7 +138,6 @@ export interface CreateCalendarEventRequest {
|
|
|
158
138
|
reminders?: number[];
|
|
159
139
|
metadata?: unknown;
|
|
160
140
|
recurring?: CalendarEvent['recurring'];
|
|
161
|
-
syncToSystemCalendar?: boolean; // Auto-sync to device calendar
|
|
162
141
|
}
|
|
163
142
|
|
|
164
143
|
/**
|
|
@@ -177,26 +156,4 @@ export interface UpdateCalendarEventRequest {
|
|
|
177
156
|
reminders?: number[];
|
|
178
157
|
metadata?: unknown;
|
|
179
158
|
recurring?: CalendarEvent['recurring'];
|
|
180
|
-
systemCalendar?: CalendarEvent['systemCalendar'];
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* System calendar list item
|
|
185
|
-
*/
|
|
186
|
-
export interface SystemCalendar {
|
|
187
|
-
id: string;
|
|
188
|
-
title: string;
|
|
189
|
-
color: string;
|
|
190
|
-
source: string;
|
|
191
|
-
isPrimary: boolean;
|
|
192
|
-
allowsModifications: boolean;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Calendar permission result
|
|
197
|
-
*/
|
|
198
|
-
export interface CalendarPermissionResult {
|
|
199
|
-
granted: boolean;
|
|
200
|
-
canAskAgain: boolean;
|
|
201
|
-
status: string;
|
|
202
159
|
}
|
|
@@ -11,31 +11,13 @@
|
|
|
11
11
|
* - Event CRUD operations with persistence
|
|
12
12
|
* - Completion tracking
|
|
13
13
|
* - Recurring events support
|
|
14
|
-
* - **NEW:** System calendar sync (expo-calendar) - iOS/Android only
|
|
15
14
|
*
|
|
16
15
|
* Usage:
|
|
17
16
|
* ```tsx
|
|
18
|
-
* import { useCalendar, AtomicCalendar
|
|
17
|
+
* import { useCalendar, AtomicCalendar } from '@umituz/react-native-design-system';
|
|
19
18
|
*
|
|
20
19
|
* const MyScreen = () => {
|
|
21
20
|
* const { days, selectedDate, actions } = useCalendar();
|
|
22
|
-
* const {
|
|
23
|
-
* systemCalendars,
|
|
24
|
-
* permission,
|
|
25
|
-
* requestPermission,
|
|
26
|
-
* syncEventToCalendar,
|
|
27
|
-
* } = useSystemCalendar();
|
|
28
|
-
*
|
|
29
|
-
* // Request permission to access device calendar
|
|
30
|
-
* useEffect(() => {
|
|
31
|
-
* requestPermission();
|
|
32
|
-
* }, []);
|
|
33
|
-
*
|
|
34
|
-
* // Sync event to device calendar
|
|
35
|
-
* const handleCreateEvent = async (eventData) => {
|
|
36
|
-
* const event = await actions.addEvent(eventData);
|
|
37
|
-
* await syncEventToCalendar(event); // Sync to iOS/Android calendar
|
|
38
|
-
* };
|
|
39
21
|
*
|
|
40
22
|
* return (
|
|
41
23
|
* <AtomicCalendar
|
|
@@ -53,8 +35,6 @@ export type {
|
|
|
53
35
|
CalendarEvent,
|
|
54
36
|
CreateCalendarEventRequest,
|
|
55
37
|
UpdateCalendarEventRequest,
|
|
56
|
-
SystemCalendar,
|
|
57
|
-
CalendarPermissionResult,
|
|
58
38
|
} from './domain/entities/CalendarEvent.entity';
|
|
59
39
|
|
|
60
40
|
export type {
|
|
@@ -68,10 +48,7 @@ export type { ICalendarRepository } from './domain/repositories/ICalendarReposit
|
|
|
68
48
|
|
|
69
49
|
// Infrastructure Services
|
|
70
50
|
export { CalendarService } from './infrastructure/services/CalendarService';
|
|
71
|
-
export { CalendarPermissions } from './infrastructure/services/CalendarPermissions';
|
|
72
|
-
export { CalendarEvents } from './infrastructure/services/CalendarEvents';
|
|
73
51
|
export { CalendarGeneration } from './infrastructure/services/CalendarGeneration';
|
|
74
|
-
export { CalendarSync } from './infrastructure/services/CalendarSync';
|
|
75
52
|
|
|
76
53
|
// Infrastructure Utils
|
|
77
54
|
export { DateUtilities } from './infrastructure/utils/DateUtilities';
|
|
@@ -87,7 +64,6 @@ export {
|
|
|
87
64
|
useCalendar,
|
|
88
65
|
useCalendarNavigation,
|
|
89
66
|
useCalendarEvents,
|
|
90
|
-
useSystemCalendar,
|
|
91
67
|
type UseCalendarReturn,
|
|
92
68
|
} from './presentation/hooks/useCalendar';
|
|
93
69
|
|
|
@@ -10,12 +10,8 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type { CalendarDay, CalendarWeek } from '../../domain/entities/CalendarDay.entity';
|
|
13
|
-
import type { CalendarEvent
|
|
13
|
+
import type { CalendarEvent } from '../../domain/entities/CalendarEvent.entity';
|
|
14
14
|
import { CalendarGeneration } from './CalendarGeneration';
|
|
15
|
-
import { CalendarPermissions } from './CalendarPermissions';
|
|
16
|
-
// CalendarEvents is not used in this facade
|
|
17
|
-
// import { CalendarEvents } from './CalendarEvents';
|
|
18
|
-
import { CalendarSync } from './CalendarSync';
|
|
19
15
|
import { DateUtilities } from '../utils/DateUtilities';
|
|
20
16
|
|
|
21
17
|
/**
|
|
@@ -79,48 +75,6 @@ export class CalendarService {
|
|
|
79
75
|
return CalendarGeneration.getNextWeek(currentDate);
|
|
80
76
|
}
|
|
81
77
|
|
|
82
|
-
/**
|
|
83
|
-
* Request calendar permissions
|
|
84
|
-
*/
|
|
85
|
-
static async requestPermissions() {
|
|
86
|
-
return CalendarPermissions.requestPermissions();
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Check if permissions are granted
|
|
91
|
-
*/
|
|
92
|
-
static async hasPermissions(): Promise<boolean> {
|
|
93
|
-
return CalendarPermissions.hasPermissions();
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Sync event to system calendar
|
|
98
|
-
*/
|
|
99
|
-
static async syncToSystemCalendar(event: CalendarEvent) {
|
|
100
|
-
return CalendarSync.syncToSystemCalendar(event);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Update system calendar event
|
|
105
|
-
*/
|
|
106
|
-
static async updateSystemCalendarEvent(event: CalendarEvent) {
|
|
107
|
-
return CalendarSync.updateSystemCalendarEvent(event);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Remove event from system calendar
|
|
112
|
-
*/
|
|
113
|
-
static async removeFromSystemCalendar(eventId: string) {
|
|
114
|
-
return CalendarSync.removeFromSystemCalendar(eventId);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Get system calendars
|
|
119
|
-
*/
|
|
120
|
-
static async getSystemCalendars(): Promise<SystemCalendar[]> {
|
|
121
|
-
return CalendarSync.getSystemCalendars();
|
|
122
|
-
}
|
|
123
|
-
|
|
124
78
|
/**
|
|
125
79
|
* Get events for a specific date
|
|
126
80
|
*/
|
|
@@ -26,15 +26,11 @@
|
|
|
26
26
|
* ```
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
|
-
import { useMemo, useEffect
|
|
29
|
+
import { useMemo, useEffect } from 'react';
|
|
30
30
|
import { useCalendarStore, type CalendarViewMode } from '../../infrastructure/storage/CalendarStore';
|
|
31
31
|
import { CalendarService } from '../../infrastructure/services/CalendarService';
|
|
32
32
|
import type { CalendarDay } from '../../domain/entities/CalendarDay.entity';
|
|
33
|
-
import type {
|
|
34
|
-
CalendarEvent,
|
|
35
|
-
SystemCalendar,
|
|
36
|
-
CalendarPermissionResult,
|
|
37
|
-
} from '../../domain/entities/CalendarEvent.entity';
|
|
33
|
+
import type { CalendarEvent } from '../../domain/entities/CalendarEvent.entity';
|
|
38
34
|
|
|
39
35
|
/**
|
|
40
36
|
* Calendar hook return type
|
|
@@ -182,175 +178,3 @@ export const useCalendarEvents = () => {
|
|
|
182
178
|
clearError,
|
|
183
179
|
};
|
|
184
180
|
};
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Hook for system calendar integration (expo-calendar)
|
|
188
|
-
*
|
|
189
|
-
* USAGE:
|
|
190
|
-
* ```tsx
|
|
191
|
-
* const {
|
|
192
|
-
* systemCalendars,
|
|
193
|
-
* permission,
|
|
194
|
-
* requestPermission,
|
|
195
|
-
* syncEventToCalendar,
|
|
196
|
-
* updateSyncedEvent,
|
|
197
|
-
* deleteSyncedEvent,
|
|
198
|
-
* } = useSystemCalendar();
|
|
199
|
-
*
|
|
200
|
-
* // Request permission
|
|
201
|
-
* const granted = await requestPermission();
|
|
202
|
-
*
|
|
203
|
-
* // Sync event to device calendar
|
|
204
|
-
* await syncEventToCalendar(event);
|
|
205
|
-
* ```
|
|
206
|
-
*/
|
|
207
|
-
export const useSystemCalendar = () => {
|
|
208
|
-
const [systemCalendars, setSystemCalendars] = useState<SystemCalendar[]>([]);
|
|
209
|
-
const [permission, setPermission] = useState<CalendarPermissionResult | null>(null);
|
|
210
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
211
|
-
|
|
212
|
-
const { actions } = useCalendarStore((state) => state);
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Request calendar permissions
|
|
216
|
-
*/
|
|
217
|
-
const requestPermission = useCallback(async (): Promise<boolean> => {
|
|
218
|
-
setIsLoading(true);
|
|
219
|
-
try {
|
|
220
|
-
const result = await CalendarService.requestPermissions();
|
|
221
|
-
setPermission(result);
|
|
222
|
-
return result.granted;
|
|
223
|
-
} catch {
|
|
224
|
-
return false;
|
|
225
|
-
} finally {
|
|
226
|
-
setIsLoading(false);
|
|
227
|
-
}
|
|
228
|
-
}, []);
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Load system calendars
|
|
232
|
-
*/
|
|
233
|
-
const loadSystemCalendars = useCallback(async () => {
|
|
234
|
-
setIsLoading(true);
|
|
235
|
-
try {
|
|
236
|
-
const calendars = await CalendarService.getSystemCalendars();
|
|
237
|
-
setSystemCalendars(calendars);
|
|
238
|
-
} catch {
|
|
239
|
-
setSystemCalendars([]);
|
|
240
|
-
} finally {
|
|
241
|
-
setIsLoading(false);
|
|
242
|
-
}
|
|
243
|
-
}, []);
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Sync event to system calendar
|
|
247
|
-
*/
|
|
248
|
-
const syncEventToCalendar = useCallback(
|
|
249
|
-
async (event: CalendarEvent): Promise<boolean> => {
|
|
250
|
-
setIsLoading(true);
|
|
251
|
-
try {
|
|
252
|
-
const result = await CalendarService.syncToSystemCalendar(event);
|
|
253
|
-
|
|
254
|
-
if (result.success && result.eventId && result.calendarId) {
|
|
255
|
-
// Update event with system calendar info
|
|
256
|
-
await actions.updateEvent({
|
|
257
|
-
id: event.id,
|
|
258
|
-
systemCalendar: {
|
|
259
|
-
eventId: result.eventId,
|
|
260
|
-
calendarId: result.calendarId,
|
|
261
|
-
lastSyncedAt: new Date(),
|
|
262
|
-
},
|
|
263
|
-
});
|
|
264
|
-
return true;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
return false;
|
|
268
|
-
} catch {
|
|
269
|
-
return false;
|
|
270
|
-
} finally {
|
|
271
|
-
setIsLoading(false);
|
|
272
|
-
}
|
|
273
|
-
},
|
|
274
|
-
[actions]
|
|
275
|
-
);
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Update synced event in system calendar
|
|
279
|
-
*/
|
|
280
|
-
const updateSyncedEvent = useCallback(async (event: CalendarEvent): Promise<boolean> => {
|
|
281
|
-
if (!event.systemCalendar) return false;
|
|
282
|
-
|
|
283
|
-
setIsLoading(true);
|
|
284
|
-
try {
|
|
285
|
-
const result = await CalendarService.updateSystemCalendarEvent(event);
|
|
286
|
-
|
|
287
|
-
if (result.success) {
|
|
288
|
-
// Update last synced timestamp
|
|
289
|
-
await actions.updateEvent({
|
|
290
|
-
id: event.id,
|
|
291
|
-
systemCalendar: {
|
|
292
|
-
...event.systemCalendar,
|
|
293
|
-
lastSyncedAt: new Date(),
|
|
294
|
-
},
|
|
295
|
-
});
|
|
296
|
-
return true;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
return false;
|
|
300
|
-
} catch {
|
|
301
|
-
return false;
|
|
302
|
-
} finally {
|
|
303
|
-
setIsLoading(false);
|
|
304
|
-
}
|
|
305
|
-
}, [actions]);
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* Delete synced event from system calendar
|
|
309
|
-
*/
|
|
310
|
-
const deleteSyncedEvent = useCallback(
|
|
311
|
-
async (event: CalendarEvent): Promise<boolean> => {
|
|
312
|
-
if (!event.systemCalendar) return false;
|
|
313
|
-
|
|
314
|
-
setIsLoading(true);
|
|
315
|
-
try {
|
|
316
|
-
const result = await CalendarService.removeFromSystemCalendar(
|
|
317
|
-
event.systemCalendar.eventId
|
|
318
|
-
);
|
|
319
|
-
|
|
320
|
-
if (result.success) {
|
|
321
|
-
// Remove system calendar info from event
|
|
322
|
-
await actions.updateEvent({
|
|
323
|
-
id: event.id,
|
|
324
|
-
systemCalendar: undefined,
|
|
325
|
-
});
|
|
326
|
-
return true;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
return false;
|
|
330
|
-
} catch {
|
|
331
|
-
return false;
|
|
332
|
-
} finally {
|
|
333
|
-
setIsLoading(false);
|
|
334
|
-
}
|
|
335
|
-
},
|
|
336
|
-
[actions]
|
|
337
|
-
);
|
|
338
|
-
|
|
339
|
-
// Load calendars when permission is granted
|
|
340
|
-
useEffect(() => {
|
|
341
|
-
if (permission?.granted) {
|
|
342
|
-
loadSystemCalendars();
|
|
343
|
-
}
|
|
344
|
-
}, [permission, loadSystemCalendars]);
|
|
345
|
-
|
|
346
|
-
return {
|
|
347
|
-
systemCalendars,
|
|
348
|
-
permission,
|
|
349
|
-
isLoading,
|
|
350
|
-
requestPermission,
|
|
351
|
-
loadSystemCalendars,
|
|
352
|
-
syncEventToCalendar,
|
|
353
|
-
updateSyncedEvent,
|
|
354
|
-
deleteSyncedEvent,
|
|
355
|
-
};
|
|
356
|
-
};
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Calendar Events Service
|
|
3
|
-
*
|
|
4
|
-
* Handles CRUD operations for calendar events.
|
|
5
|
-
*
|
|
6
|
-
* SOLID: Single Responsibility - Only event operations
|
|
7
|
-
* DRY: Centralized event management
|
|
8
|
-
* KISS: Simple event interface
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import * as Calendar from 'expo-calendar';
|
|
12
|
-
import { Platform } from 'react-native';
|
|
13
|
-
import { DateUtilities } from '../utils/DateUtilities';
|
|
14
|
-
import { CalendarPermissions } from './CalendarPermissions';
|
|
15
|
-
import type { CalendarEvent, SystemCalendar } from '../../domain/entities/CalendarEvent.entity';
|
|
16
|
-
|
|
17
|
-
export class CalendarEvents {
|
|
18
|
-
/**
|
|
19
|
-
* Create event in system calendar
|
|
20
|
-
*/
|
|
21
|
-
static async createEvent(
|
|
22
|
-
event: CalendarEvent,
|
|
23
|
-
calendar: SystemCalendar
|
|
24
|
-
): Promise<{ success: boolean; eventId?: string; error?: string }> {
|
|
25
|
-
try {
|
|
26
|
-
if (Platform.OS === 'web') {
|
|
27
|
-
return { success: false, error: 'Calendar sync not supported on web' };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const permission = await CalendarPermissions.requestPermissions();
|
|
31
|
-
if (!permission.granted) {
|
|
32
|
-
return { success: false, error: 'Calendar permission not granted' };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const eventData = this.buildEventData(event);
|
|
36
|
-
const systemEventId = await Calendar.createEventAsync(calendar.id, eventData);
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
success: true,
|
|
40
|
-
eventId: systemEventId
|
|
41
|
-
};
|
|
42
|
-
} catch (error) {
|
|
43
|
-
return {
|
|
44
|
-
success: false,
|
|
45
|
-
error: error instanceof Error ? error.message : 'Failed to create event'
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Update event in system calendar
|
|
52
|
-
*/
|
|
53
|
-
static async updateEvent(
|
|
54
|
-
event: CalendarEvent
|
|
55
|
-
): Promise<{ success: boolean; error?: string }> {
|
|
56
|
-
try {
|
|
57
|
-
if (Platform.OS === 'web' || !event.systemCalendar?.eventId) {
|
|
58
|
-
return { success: false, error: 'No system calendar data' };
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const permission = await CalendarPermissions.requestPermissions();
|
|
62
|
-
if (!permission.granted) {
|
|
63
|
-
return { success: false, error: 'Calendar permission not granted' };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const eventData = this.buildEventData(event);
|
|
67
|
-
await Calendar.updateEventAsync(event.systemCalendar.eventId, eventData);
|
|
68
|
-
|
|
69
|
-
return { success: true };
|
|
70
|
-
} catch (error) {
|
|
71
|
-
return {
|
|
72
|
-
success: false,
|
|
73
|
-
error: error instanceof Error ? error.message : 'Failed to update event'
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Delete event from system calendar
|
|
80
|
-
*/
|
|
81
|
-
static async deleteEvent(
|
|
82
|
-
eventId: string
|
|
83
|
-
): Promise<{ success: boolean; error?: string }> {
|
|
84
|
-
try {
|
|
85
|
-
if (Platform.OS === 'web') {
|
|
86
|
-
return { success: false, error: 'Calendar sync not supported on web' };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const permission = await CalendarPermissions.requestPermissions();
|
|
90
|
-
if (!permission.granted) {
|
|
91
|
-
return { success: false, error: 'Calendar permission not granted' };
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
await Calendar.deleteEventAsync(eventId);
|
|
95
|
-
return { success: true };
|
|
96
|
-
} catch (error) {
|
|
97
|
-
return {
|
|
98
|
-
success: false,
|
|
99
|
-
error: error instanceof Error ? error.message : 'Failed to delete event'
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Get events from system calendar
|
|
106
|
-
*/
|
|
107
|
-
static async getSystemEvents(
|
|
108
|
-
calendarId: string,
|
|
109
|
-
startDate: Date,
|
|
110
|
-
endDate: Date
|
|
111
|
-
): Promise<CalendarEvent[]> {
|
|
112
|
-
try {
|
|
113
|
-
if (Platform.OS === 'web') {
|
|
114
|
-
return [];
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const permission = await CalendarPermissions.hasPermissions();
|
|
118
|
-
if (!permission) {
|
|
119
|
-
return [];
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const systemEvents = await Calendar.getEventsAsync(
|
|
123
|
-
[calendarId],
|
|
124
|
-
startDate,
|
|
125
|
-
endDate
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
return systemEvents.map((event: Calendar.Event) => this.mapSystemEventToCalendarEvent(event));
|
|
129
|
-
} catch {
|
|
130
|
-
return [];
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Build event data for system calendar
|
|
136
|
-
*/
|
|
137
|
-
private static buildEventData(event: CalendarEvent) {
|
|
138
|
-
const [year, month, day] = event.date.split('-').map(Number);
|
|
139
|
-
let startDate = new Date(year, month - 1, day);
|
|
140
|
-
let endDate = new Date(startDate);
|
|
141
|
-
|
|
142
|
-
// Set time if provided
|
|
143
|
-
if (event.time) {
|
|
144
|
-
const [hours, minutes] = event.time.split(':').map(Number);
|
|
145
|
-
startDate.setHours(hours, minutes, 0, 0);
|
|
146
|
-
endDate.setHours(hours, minutes, 0, 0);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Set duration
|
|
150
|
-
if (event.duration) {
|
|
151
|
-
endDate.setMinutes(endDate.getMinutes() + event.duration);
|
|
152
|
-
} else {
|
|
153
|
-
endDate.setHours(endDate.getHours() + 1); // Default 1 hour
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Create reminders
|
|
157
|
-
const alarms = event.reminders?.map(minutesBefore => ({
|
|
158
|
-
relativeOffset: -minutesBefore
|
|
159
|
-
}));
|
|
160
|
-
|
|
161
|
-
return {
|
|
162
|
-
title: event.title,
|
|
163
|
-
startDate,
|
|
164
|
-
endDate,
|
|
165
|
-
notes: event.description,
|
|
166
|
-
location: event.location,
|
|
167
|
-
alarms,
|
|
168
|
-
timeZone: DateUtilities.getCurrentTimezone()
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Map system calendar event to domain event
|
|
174
|
-
*/
|
|
175
|
-
private static mapSystemEventToCalendarEvent(systemEvent: any): CalendarEvent {
|
|
176
|
-
return {
|
|
177
|
-
id: systemEvent.id,
|
|
178
|
-
title: systemEvent.title || '',
|
|
179
|
-
description: systemEvent.notes || '',
|
|
180
|
-
date: DateUtilities.formatDateToString(systemEvent.startDate),
|
|
181
|
-
time: DateUtilities.formatTimeToString(systemEvent.startDate),
|
|
182
|
-
duration: systemEvent.endDate && systemEvent.startDate
|
|
183
|
-
? (systemEvent.endDate.getTime() - systemEvent.startDate.getTime()) / (1000 * 60)
|
|
184
|
-
: undefined,
|
|
185
|
-
location: systemEvent.location || '',
|
|
186
|
-
reminders: systemEvent.alarms?.map((alarm: any) => -alarm.relativeOffset) || [],
|
|
187
|
-
createdAt: new Date(),
|
|
188
|
-
updatedAt: new Date(),
|
|
189
|
-
systemCalendar: {
|
|
190
|
-
calendarId: systemEvent.calendarId,
|
|
191
|
-
eventId: systemEvent.id,
|
|
192
|
-
lastSyncedAt: new Date()
|
|
193
|
-
}
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Calendar Permissions Service
|
|
3
|
-
*
|
|
4
|
-
* Handles calendar permission requests and status checks.
|
|
5
|
-
*
|
|
6
|
-
* SOLID: Single Responsibility - Only permission operations
|
|
7
|
-
* DRY: Centralized permission logic
|
|
8
|
-
* KISS: Simple permission interface
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import * as Calendar from 'expo-calendar';
|
|
12
|
-
import { Platform } from 'react-native';
|
|
13
|
-
import type { CalendarPermissionResult } from '../../domain/entities/CalendarEvent.entity';
|
|
14
|
-
|
|
15
|
-
export class CalendarPermissions {
|
|
16
|
-
/**
|
|
17
|
-
* Request calendar permissions
|
|
18
|
-
*
|
|
19
|
-
* @returns Promise with permission result
|
|
20
|
-
*/
|
|
21
|
-
static async requestPermissions(): Promise<CalendarPermissionResult> {
|
|
22
|
-
try {
|
|
23
|
-
if (Platform.OS === 'web') {
|
|
24
|
-
return {
|
|
25
|
-
granted: false,
|
|
26
|
-
canAskAgain: false,
|
|
27
|
-
status: 'denied'
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const { status, canAskAgain } = await Calendar.requestCalendarPermissionsAsync();
|
|
32
|
-
|
|
33
|
-
return {
|
|
34
|
-
granted: status === 'granted',
|
|
35
|
-
canAskAgain,
|
|
36
|
-
status
|
|
37
|
-
};
|
|
38
|
-
} catch {
|
|
39
|
-
return {
|
|
40
|
-
granted: false,
|
|
41
|
-
canAskAgain: false,
|
|
42
|
-
status: 'error'
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Check if calendar permissions are granted
|
|
49
|
-
*
|
|
50
|
-
* @returns Promise with permission status
|
|
51
|
-
*/
|
|
52
|
-
static async hasPermissions(): Promise<boolean> {
|
|
53
|
-
try {
|
|
54
|
-
if (Platform.OS === 'web') {
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const { status } = await Calendar.getCalendarPermissionsAsync();
|
|
59
|
-
return status === 'granted';
|
|
60
|
-
} catch {
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Get current permission status
|
|
67
|
-
*
|
|
68
|
-
* @returns Promise with current status
|
|
69
|
-
*/
|
|
70
|
-
static async getPermissionStatus(): Promise<CalendarPermissionResult['status']> {
|
|
71
|
-
try {
|
|
72
|
-
if (Platform.OS === 'web') {
|
|
73
|
-
return 'denied';
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const { status } = await Calendar.getCalendarPermissionsAsync();
|
|
77
|
-
return status;
|
|
78
|
-
} catch {
|
|
79
|
-
return 'error';
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Calendar Sync Service
|
|
3
|
-
*
|
|
4
|
-
* Handles synchronization with system calendar.
|
|
5
|
-
*
|
|
6
|
-
* SOLID: Single Responsibility - Only sync operations
|
|
7
|
-
* DRY: Centralized sync logic
|
|
8
|
-
* KISS: Simple sync interface
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import * as Calendar from 'expo-calendar';
|
|
12
|
-
import { Platform } from 'react-native';
|
|
13
|
-
import { CalendarPermissions } from './CalendarPermissions';
|
|
14
|
-
import type { CalendarEvent, SystemCalendar } from '../../domain/entities/CalendarEvent.entity';
|
|
15
|
-
|
|
16
|
-
export class CalendarSync {
|
|
17
|
-
/**
|
|
18
|
-
* Sync event to system calendar
|
|
19
|
-
*/
|
|
20
|
-
static async syncToSystemCalendar(
|
|
21
|
-
event: CalendarEvent
|
|
22
|
-
): Promise<{
|
|
23
|
-
success: boolean;
|
|
24
|
-
eventId?: string;
|
|
25
|
-
calendarId?: string;
|
|
26
|
-
error?: string;
|
|
27
|
-
}> {
|
|
28
|
-
try {
|
|
29
|
-
if (Platform.OS === 'web') {
|
|
30
|
-
return { success: false, error: 'Calendar sync not supported on web' };
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const permission = await CalendarPermissions.requestPermissions();
|
|
34
|
-
if (!permission.granted) {
|
|
35
|
-
return { success: false, error: 'Calendar permission not granted' };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const primaryCal = await this.getPrimaryCalendar();
|
|
39
|
-
if (!primaryCal) {
|
|
40
|
-
return { success: false, error: 'No writable calendar found' };
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const eventData = this.buildSystemEventData(event);
|
|
44
|
-
const systemEventId = await Calendar.createEventAsync(primaryCal.id, eventData);
|
|
45
|
-
|
|
46
|
-
return {
|
|
47
|
-
success: true,
|
|
48
|
-
eventId: systemEventId,
|
|
49
|
-
calendarId: primaryCal.id
|
|
50
|
-
};
|
|
51
|
-
} catch (error) {
|
|
52
|
-
return {
|
|
53
|
-
success: false,
|
|
54
|
-
error: error instanceof Error ? error.message : 'Failed to sync to calendar'
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Update system calendar event
|
|
61
|
-
*/
|
|
62
|
-
static async updateSystemCalendarEvent(
|
|
63
|
-
event: CalendarEvent
|
|
64
|
-
): Promise<{ success: boolean; error?: string }> {
|
|
65
|
-
try {
|
|
66
|
-
if (Platform.OS === 'web' || !event.systemCalendar) {
|
|
67
|
-
return { success: false, error: 'No system calendar data' };
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const permission = await CalendarPermissions.requestPermissions();
|
|
71
|
-
if (!permission.granted) {
|
|
72
|
-
return { success: false, error: 'Calendar permission not granted' };
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const eventData = this.buildSystemEventData(event);
|
|
76
|
-
await Calendar.updateEventAsync(event.systemCalendar.eventId, eventData);
|
|
77
|
-
|
|
78
|
-
return { success: true };
|
|
79
|
-
} catch (error) {
|
|
80
|
-
return {
|
|
81
|
-
success: false,
|
|
82
|
-
error: error instanceof Error ? error.message : 'Failed to update event'
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Remove event from system calendar
|
|
89
|
-
*/
|
|
90
|
-
static async removeFromSystemCalendar(
|
|
91
|
-
eventId: string
|
|
92
|
-
): Promise<{ success: boolean; error?: string }> {
|
|
93
|
-
try {
|
|
94
|
-
if (Platform.OS === 'web') {
|
|
95
|
-
return { success: false, error: 'Calendar sync not supported on web' };
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const permission = await CalendarPermissions.requestPermissions();
|
|
99
|
-
if (!permission.granted) {
|
|
100
|
-
return { success: false, error: 'Calendar permission not granted' };
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
await Calendar.deleteEventAsync(eventId);
|
|
104
|
-
return { success: true };
|
|
105
|
-
} catch (error) {
|
|
106
|
-
return {
|
|
107
|
-
success: false,
|
|
108
|
-
error: error instanceof Error ? error.message : 'Failed to remove from calendar'
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Get primary writable calendar
|
|
115
|
-
*/
|
|
116
|
-
static async getPrimaryCalendar(): Promise<SystemCalendar | null> {
|
|
117
|
-
try {
|
|
118
|
-
if (Platform.OS === 'web') {
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const calendars = await Calendar.getCalendarsAsync();
|
|
123
|
-
const writableCalendars = calendars.filter((cal: Calendar.Calendar) =>
|
|
124
|
-
cal.allowsModifications &&
|
|
125
|
-
(cal.source.type === 'local' || cal.source.type === 'caldav')
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
// Prefer default calendar, fallback to first writable
|
|
129
|
-
const primaryCal = writableCalendars.find((cal: Calendar.Calendar) => cal.isPrimary) || writableCalendars[0];
|
|
130
|
-
if (!primaryCal) return null;
|
|
131
|
-
|
|
132
|
-
return {
|
|
133
|
-
id: primaryCal.id,
|
|
134
|
-
title: primaryCal.title,
|
|
135
|
-
color: primaryCal.color,
|
|
136
|
-
allowsModifications: primaryCal.allowsModifications,
|
|
137
|
-
source: primaryCal.source.name,
|
|
138
|
-
isPrimary: primaryCal.isPrimary || false
|
|
139
|
-
};
|
|
140
|
-
} catch {
|
|
141
|
-
return null;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Get all system calendars
|
|
147
|
-
*/
|
|
148
|
-
static async getSystemCalendars(): Promise<SystemCalendar[]> {
|
|
149
|
-
try {
|
|
150
|
-
if (Platform.OS === 'web') {
|
|
151
|
-
return [];
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const calendars = await Calendar.getCalendarsAsync();
|
|
155
|
-
return calendars.map((cal: Calendar.Calendar) => ({
|
|
156
|
-
id: cal.id,
|
|
157
|
-
title: cal.title,
|
|
158
|
-
color: cal.color,
|
|
159
|
-
allowsModifications: cal.allowsModifications,
|
|
160
|
-
source: cal.source.name,
|
|
161
|
-
isPrimary: cal.isPrimary || false
|
|
162
|
-
}));
|
|
163
|
-
} catch {
|
|
164
|
-
return [];
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Build system calendar event data
|
|
170
|
-
*/
|
|
171
|
-
private static buildSystemEventData(event: CalendarEvent) {
|
|
172
|
-
const [year, month, day] = event.date.split('-').map(Number);
|
|
173
|
-
let startDate = new Date(year, month - 1, day);
|
|
174
|
-
let endDate = new Date(startDate);
|
|
175
|
-
|
|
176
|
-
// Set time if provided
|
|
177
|
-
if (event.time) {
|
|
178
|
-
const [hours, minutes] = event.time.split(':').map(Number);
|
|
179
|
-
startDate.setHours(hours, minutes, 0, 0);
|
|
180
|
-
endDate.setHours(hours, minutes, 0, 0);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Set duration
|
|
184
|
-
if (event.duration) {
|
|
185
|
-
endDate.setMinutes(endDate.getMinutes() + event.duration);
|
|
186
|
-
} else {
|
|
187
|
-
endDate.setHours(endDate.getHours() + 1); // Default 1 hour
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Create reminders
|
|
191
|
-
const alarms = event.reminders?.map(minutesBefore => ({
|
|
192
|
-
relativeOffset: -minutesBefore
|
|
193
|
-
}));
|
|
194
|
-
|
|
195
|
-
return {
|
|
196
|
-
title: event.title,
|
|
197
|
-
startDate,
|
|
198
|
-
endDate,
|
|
199
|
-
notes: event.description,
|
|
200
|
-
location: event.location,
|
|
201
|
-
alarms,
|
|
202
|
-
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
}
|