-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTSCategoryStore.m
More file actions
557 lines (492 loc) · 19.9 KB
/
Copy pathTSCategoryStore.m
File metadata and controls
557 lines (492 loc) · 19.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
//
// TSCategoryList.m
// TSCategories
//
// Created by Awais Hussain on 7/21/13.
// Copyright (c) 2013 TimeStamp. All rights reserved.
//
#import "TSCategoryStore.h"
#import <EventKit/EventKit.h>
#import <dispatch/dispatch.h>
#import "TSCalendarStore.h"
#import "TSCategory.h"
#import "UIColor+MLPFlatColors.h"
#import "UIColor+CalendarPalette.h"
@interface TSCategoryStore () {
BOOL saveToFile;
dispatch_queue_t backgroundQueue;
}
// Store of all the categories and subcategories ever created (hidden and unhidden).
@property (nonatomic, strong) NSMutableArray *allCategories;
// This is the curated list of only unhidden categories. Use active calendars to derive this list.
@property (nonatomic, strong) NSMutableArray *categoryArray;
@property (nonatomic, strong) EKEventStore *store;
@end
@implementation TSCategoryStore
@synthesize allCategories = _allCategories;
#pragma mark - Singleton methods
- (id) initSingleton
{
backgroundQueue = dispatch_queue_create("com.timestamp.bgqueue", NULL);
NSLog(@"FUNC called: initSingleton");
saveToFile = YES;
[self loadData];
[self syncStoredDataWithGCalData];
// [self importDefaultCalendars];
[self saveData];
return self;
}
- (EKEventStore *)store {
if (_store == nil) {
_store = [[TSCalendarStore instance] store];
}
return _store;
}
- (NSMutableArray *)allCategories {
if(!_allCategories) {
_allCategories = [[NSMutableArray alloc] init];
}
return _allCategories;
}
- (void)setActiveCalendars:(NSSet *)activeCalendars {
for (TSCategory *cat in self.allCategories) {
cat.active = NO;
for (EKCalendar *cal in activeCalendars) {
if ([cal.calendarIdentifier isEqualToString:cat.calendar.calendarIdentifier] || [cal.title isEqualToString:cat.calendar.title]) {
cat.active = YES;
break;
}
}
}
[self saveData];
}
- (NSSet *)activeCalendars {
NSMutableSet *active = [[NSMutableSet alloc] init];
for (TSCategory *cat in self.allCategories) {
if (cat.active) {
[active addObject:cat.calendar];
}
}
return active.copy;
}
- (NSMutableArray *)categoryArray {
// Check to ensure that each category has a corresponding entry in the active calendars list.
NSLog(@"BUILDING CATEGORY ARRAY FROM: allCategories:");
for (EKCalendar *cal in self.allCategories) {
NSLog(@"%@", cal.title);
}
NSMutableArray *tempArray = [[NSMutableArray alloc] initWithCapacity:self.allCategories.count];
for (TSCategory *cat in self.allCategories) {
if (cat.active) {
[tempArray addObject:cat];
}
}
NSLog(@"BUILDING CATEGORY ARRAY FROM: returned array: %@", tempArray);
return tempArray;
}
+ (TSCategoryStore *) instance
{
// Persistent instance.
static TSCategoryStore *_default = nil;
// Small optimization to avoid wasting time after the
// singleton being initialized.
if (_default != nil)
{
return _default;
}
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
// Allocates once with Grand Central Dispatch (GCD) routine.
// It's thread safe.
static dispatch_once_t safer;
dispatch_once(&safer, ^(void)
{
_default = [[TSCategoryStore alloc] initSingleton];
});
#else
// Allocates once using the old approach, it's slower.
// It's thread safe.
@synchronized([MySingleton class])
{
// The synchronized instruction will make sure,
// that only one thread will access this point at a time.
if (_default == nil)
{
_default = [[TSCategoryList alloc] initSingleton];
}
}
#endif
return _default;
}
#pragma mark setters and getters
- (void)importCalendarData {
}
- (void)makeAllCategoriesActive {
for (TSCategory *cat in self.allCategories) {
cat.active = YES;
}
}
- (void)syncStoredDataWithGCalData {
NSLog(@"FUNC called: syncStoredDataWithGCalData");
// dispatch_async(backgroundQueue, ^{
// get all calendars
NSArray *calendars = [self.store calendarsForEntityType:EKEntityTypeEvent];
NSMutableArray *existingCategories = [[NSMutableArray alloc] initWithCapacity:calendars.count];
NSLog(@"Calendars from EKCal are:");
for (EKCalendar *cal in calendars) {
NSLog(@"%@", cal.title);
}
NSLog(@"Categories stored in phone are:");
for (TSCategory *cat in self.allCategories) {
NSLog(@"%@", cat.title);
}
BOOL catExists;
for (EKCalendar *cal in calendars) {
if (cal.allowsContentModifications) {
catExists = FALSE;
for (TSCategory *cat in self.allCategories) {
if ([cal.title isEqualToString:cat.title] || [cal.title isEqualToString:cat.calendar.title] || [cal.calendarIdentifier isEqualToString:cat.calendar.calendarIdentifier]) {
NSLog(@"The calendar %@ ALREADY EXISTS!", cal.title);
NSLog(@"compare titles %@ vs %@", cal.title, cat.title);
NSLog(@"compare ids %@ vs %@", cal.calendarIdentifier, cat.calendar.calendarIdentifier);
NSLog(@"compare other titles %@ vs %@", cal.title, cat.calendar.title);
catExists = TRUE;
cat.calendar = cal;
cat.title = cal.title;
cat.color = [self prettyColorFromUIColor:[UIColor colorWithCGColor:cal.CGColor]];
[existingCategories addObject:cat];
break;
}
}
// We have a new gCal calendar that doesn't have a TS equivalent.
if (!catExists) {
NSLog(@"New gCal cal that doesn't have TS equivalent: %@", cal.title);
TSCategory *newCategory = [self TSCategoryWithEKCalendar:cal];
newCategory.level = 0;
newCategory.path = ROOT_CATEGORY_PATH;
[existingCategories addObject:newCategory];
[self.allCategories addObject:newCategory];
}
}
}
NSLog(@"Existing categories (which are also in GCal are:");
for (TSCategory *cat in existingCategories) {
NSLog(@"%@", cat.title);
}
// Remove excess categories from allCategories.
NSMutableArray *remove = [[NSMutableArray alloc] init];
for (TSCategory *cat in self.allCategories) {
if (![existingCategories containsObject:cat]) {
NSLog(@"Removed object: %@", cat.title);
[remove addObject:cat];
}
}
[self.allCategories removeObjectsInArray:remove];
NSLog(@"Finished syncing calendars");
NSLog(@"UPDATED: Calendars from EKCal are:");
for (EKCalendar *cal in calendars) {
NSLog(@"%@", cal.title);
}
NSLog(@"UPDATED: Categories stored in phone are:");
for (TSCategory *cat in self.allCategories) {
NSLog(@"%@", cat.title);
}
NSLog(@"UPDATED: Active categories are:");
for (TSCategory *cat in self.activeCalendars) {
NSLog(@"%@", cat.title);
}
}
- (UIColor *)prettyColorFromUIColor:(UIColor *)color {
// Hacky solution. For some reason UIColor+ColorPalette category not being recognized.
float h, s, b, a;
if ([color getHue:&h saturation:&s brightness:&b alpha:&a]) {
return [UIColor colorWithHue:h saturation:MIN(s, 0.6) brightness:b alpha:a];
}
return nil;
}
- (void)importDefaultCategories {
[self importDefaultCalendars];
[self syncStoredDataWithGCalData];
[self saveData];
}
- (void)importDefaultCalendars {
NSArray *defaultCats = [[self getDefaultCalendars] copy];
TSCategory *tempCat;
BOOL catExistsAlready = FALSE;
// Check that the category doesn't already exist in our application.
for (TSCategory *defCat in defaultCats) {
for (TSCategory *stoCat in self.allCategories) {
catExistsAlready = FALSE;
if ([defCat.title isEqualToString:stoCat.title] || [defCat.calendar.title isEqualToString:stoCat.calendar.title] || [defCat.calendar.calendarIdentifier isEqualToString:stoCat.calendar.calendarIdentifier]) {
// The calendar already exists in our store
catExistsAlready = TRUE;
defCat.calendar = stoCat.calendar;
// Make stoCat an active calendar.
stoCat.active = YES;
break;
}
}
if (!catExistsAlready) {
// The addNewCalendar: function takes care of adding newCat to self.allCategories.
tempCat = [self addNewCalendar:defCat];
}
}
}
- (NSMutableArray *)getDefaultCalendars {
NSMutableArray *catArray = [[NSMutableArray alloc] init];
TSCategory *sleep = [[TSCategory alloc] init];
sleep.title = @"Sleep";
sleep.color = [self prettyColorFromUIColor:[UIColor colorFromHexString:@"414750"]];
sleep.path = ROOT_CATEGORY_PATH;
[sleep addSubcategory:@"Sleep"];
[sleep addSubcategory:@"Nap"];
TSCategory * class = [[TSCategory alloc]init];
class.title = @"Classes";
class.color = [self prettyColorFromUIColor:[UIColor flatBlueColor]];
class.path = ROOT_CATEGORY_PATH;
[class addSubcategory:@"Physics"];
TSCategory * school = [[TSCategory alloc]init];
school.title = @"Study";
school.color = [self prettyColorFromUIColor:[UIColor flatYellowColor]];
school.path = ROOT_CATEGORY_PATH;
[school addSubcategory:@"Class"];
[school addSubcategory:@"Studying"];
[school addSubcategory:@"Extra Curriculars"];
TSCategory *social = [[TSCategory alloc] init];
social.title = @"Social";
social.color = [self prettyColorFromUIColor:[UIColor flatOrangeColor]];
social.path = ROOT_CATEGORY_PATH;
[social addSubcategory:@"Hanging Out"];
[social addSubcategory:@"Partying"];
TSCategory *food = [[TSCategory alloc] init];
food.title = @"Food";
food.color = [self prettyColorFromUIColor:[UIColor colorFromHexString:@"a78143"]];
food.path = ROOT_CATEGORY_PATH;
[food addSubcategory:@"Breakfast"];
[food addSubcategory:@"Lunch"];
[food addSubcategory:@"Dinner"];
[food addSubcategory:@"Snacks"];
TSCategory *travel = [[TSCategory alloc] init];
travel.title = @"Travel";
travel.color = [self prettyColorFromUIColor:[UIColor colorFromHexString:@"2f3876"]];
travel.path = ROOT_CATEGORY_PATH;
[travel addSubcategory:@"Walking"];
[travel addSubcategory:@"To Class"];
[travel addSubcategory:@"Biking"];
[travel addSubcategory:@"Back Home :)"];
TSCategory * sport = [[TSCategory alloc]init];
sport.title = @"Fitness";
sport.color = [self prettyColorFromUIColor:[UIColor colorFromHexString:@"6ba743"]];
sport.path = ROOT_CATEGORY_PATH;
[sport addSubcategory:@"Gym"];
[sport addSubcategory:@"Sport"];
[sport addSubcategory:@"Running"];
TSCategory * procras = [[TSCategory alloc]init];
procras.title = @"Wasted Time";
procras.color = [self prettyColorFromUIColor:[UIColor colorFromHexString:@"722f76"]];
procras.path = ROOT_CATEGORY_PATH;
[procras addSubcategory:@"Internet"];
[procras addSubcategory:@"Tired"];
TSCategory *misc = [[TSCategory alloc] init];
misc.path = ROOT_CATEGORY_PATH;
misc.title = @"Miscellaneous";
misc.color = [self prettyColorFromUIColor:[UIColor colorFromHexString:@"4a80b9"]];
[procras addSubcategory:@"Internet"];
catArray = [NSMutableArray arrayWithObjects:procras, travel,misc, sport, food, social, school, class, sleep, nil];
// catArray = [NSMutableArray arrayWithObjects:sport,procras, misc, nil];
return catArray;
}
#pragma mark - database methods
- (NSArray *)dataForPath:(NSString *)path {
TSCategory *category = [self categoryForPath:path andCategory:nil];
NSArray *array = [[NSArray alloc] init];;
if (category == nil) {
array = self.categoryArray;
} else {
array = category.subCategories;
}
return array;
}
- (TSCategory *)categoryForPath:(NSString *)path {
TSCategory *category = [[TSCategory alloc] init];
if (![path isEqualToString:ROOT_CATEGORY_PATH]) {
category = [self categoryForPath:path andCategory:nil];
return category;
}
return nil;
}
// Recursively goes in and locates the category specified by the path.
// If it returns nil, then we are at the ROOT level.
- (TSCategory *)categoryForPath:(NSString *)path andCategory:(TSCategory *)category {
if (category == nil) {
category = [[TSCategory alloc] init];
category.subCategories = self.allCategories;
}
if ([path isEqualToString:ROOT_CATEGORY_PATH]) {
NSLog(@"This is the ROOT path");
return nil;
}
NSMutableArray *pathElements = [[path componentsSeparatedByString:@":"] mutableCopy];
[pathElements removeObjectAtIndex:0];
NSString *newPath = [pathElements componentsJoinedByString:@":"];
if ([newPath length] == 0) {
return category;
}
NSString *element = [pathElements objectAtIndex:0];
for (TSCategory *cat in category.subCategories) {
if ([cat.title isEqualToString:element]) {
return [self categoryForPath:newPath andCategory:cat];
}
}
NSLog(@"Error: The specified path (%@) doesn't exist", path);
return nil;
}
- (void)addSubcategory:(NSString *)name AtPathLevel:(NSString *)path {
NSLog(@"Searching for path: %@", path);
if ([name isEqualToString:@""]) return;
TSCategory *category = [self categoryForPath:path andCategory:nil];
if ([path isEqualToString:ROOT_CATEGORY_PATH]) {
NSLog(@"Need to add a 'calendar' level category");
TSCategory *newCat = [[TSCategory alloc] init];
newCat.title = name;
newCat.color = [UIColor randomFlatColor];
// For some reason it's not seeing that I have a UIColor category, so the below is done manually.
float h, s, b, a;
[newCat.color getHue:&h saturation:&s brightness:&b alpha:&a];
newCat.color = [UIColor colorWithHue:h saturation:MIN(s, 0.6) brightness:b alpha:a];
[self addNewCalendar:newCat];
} else {
// Do some validation checks. Ensure that the category doesn't already exist.
[category addSubcategory:name];
}
[self saveData];
}
- (void)exchangeCategoryAtIndex:(NSInteger)ind1 withIndex:(NSInteger)ind2 forPath:(NSString *)path {
dispatch_async(backgroundQueue, ^{
TSCategory *category = [self categoryForPath:path andCategory:nil];
if (category == nil) {
TSCategory *cat1 = [self.categoryArray objectAtIndex:ind1];
TSCategory *cat2 = [self.categoryArray objectAtIndex:ind2];
// [self.categoryArray exchangeObjectAtIndex:ind1 withObjectAtIndex:ind2];
NSInteger newInd1 = [self.allCategories indexOfObject:cat1];
NSInteger newInd2 = [self.allCategories indexOfObject:cat2];
[self.allCategories exchangeObjectAtIndex:newInd1 withObjectAtIndex:newInd2];
} else {
[category.subCategories exchangeObjectAtIndex:ind1 withObjectAtIndex:ind2];
}
[self saveData];
});
}
- (void)deleteCategory:(TSCategory *)category atPath:(NSString *)path {
dispatch_async(backgroundQueue, ^{
if ([path isEqualToString:ROOT_CATEGORY_PATH]) {
// Have to 'hide' an entire calendar. We don't yet have this functionality.
category.active = NO;
} else {
TSCategory *storedCat = [self categoryForPath:path andCategory:nil];
for (TSCategory *cat in storedCat.subCategories) {
if ([cat isEqual:category]) {
// remove category.
[storedCat.subCategories removeObject:cat];
break;
}
}
}
[self saveData];
});
}
- (TSCategory *)addNewCalendar:(TSCategory *)category {
// This should be changed to match user's preferences.
BOOL useICloudStorage = YES;
// Find " source for new calendar
EKSource* localSource=nil;
for (EKSource* source in self.store.sources) {
// if (useICloudStorage) {
if(source.sourceType == EKSourceTypeCalDAV && [source.title isEqualToString:@"iCloud"]) {
localSource = source;
break;
}
else if (source.sourceType == EKSourceTypeCalDAV) {
localSource = source;
break;
}
else {
if (source.sourceType == EKSourceTypeLocal) {
localSource = source;
}
}
}
// Check that the calendar doesn't already exist.
for (EKCalendar *cal in [self.store calendarsForEntityType:EKEntityTypeEvent]) {
if ([cal.title isEqualToString:category.title]) {
return nil;
NSLog(@"FAILED: Trying to create a calendar with a name that already exists");
}
}
// Create new calendar
EKCalendar *calendar = [EKCalendar calendarForEntityType:EKEntityTypeEvent eventStore:self.store];
calendar.source = localSource;
calendar.title = category.title;
calendar.CGColor = category.color.CGColor;
NSError *err;
BOOL success = [self.store saveCalendar:calendar commit:YES error:&err];
if (success == NO) {
NSLog(@"Error creating new calendar: %@", err);
return nil;
} else {
NSLog(@"Successfully created new calendar:%@", calendar);
}
// Update level of calendar.
TSCategory *cat = [self TSCategoryWithEKCalendar:calendar];
cat.level = 0;
cat.path = ROOT_CATEGORY_PATH;
cat.active = YES;
// Let the database know.
[self.allCategories insertObject:cat atIndex:0];
return cat;
}
#pragma mark Persistence Methods
-(void)saveData
{
dispatch_async(backgroundQueue, ^{
if (saveToFile) {
NSLog(@"Save data method called");
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES) objectAtIndex:0];
NSString *savePath = [rootPath stringByAppendingPathComponent:@"TSCategoryData"];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSMutableData *saveData = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:saveData];
[archiver encodeObject:self.allCategories forKey:@"categories"];
[archiver finishEncoding];
[fileManager createFileAtPath:savePath contents:saveData attributes:nil];
}
});
}
-(void)loadData
{
// dispatch_async(backgroundQueue, ^{
NSLog(@"Load data method called");
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES) objectAtIndex:0];
NSString *savePath = [rootPath stringByAppendingPathComponent:@"TSCategoryData"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:savePath])
{
NSData *data = [fileManager contentsAtPath:savePath];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
self.allCategories = [unarchiver decodeObjectForKey:@"categories"];
}
// });
}
#pragma mark Utility Methods
- (TSCategory *)TSCategoryWithEKCalendar:(EKCalendar *)calendar {
TSCategory *category = [[TSCategory alloc] init];
category.title = calendar.title;
category.color = [UIColor colorWithCGColor:calendar.CGColor];
float h, s, b, a;
[category.color getHue:&h saturation:&s brightness:&b alpha:&a];
category.color = [UIColor colorWithHue:h saturation:MIN(s, 0.6) brightness:b alpha:a];
category.calendar = calendar;
return category;
}
@end