]> git.cameronkatri.com Git - tweaks.git/blob - QuickActions/QuickActionsPrefs/QASAppSelectorController.m
I lost track of all the changes
[tweaks.git] / QuickActions / QuickActionsPrefs / QASAppSelectorController.m
1 #import <Foundation/Foundation.h>
2 #include <objc/NSObject.h>
3 #import <UIKit/UIKit.h>
4 #import <Preferences/PSSpecifier.h>
5 #import "QASAppSelectorController.h"
6
7 @interface LSApplicationRecord
8 @property (readonly) NSArray * appTags;
9 @property (getter=isLaunchProhibited,readonly) BOOL launchProhibited;
10 @end
11
12 @interface LSApplicationProxy : NSObject
13 @property (getter=isLaunchProhibited, nonatomic, readonly) BOOL launchProhibited;
14 @property (nonatomic, readonly) NSArray *appTags;
15 @property (nonatomic,readonly) LSApplicationRecord * correspondingApplicationRecord;
16 + (id)applicationProxyForIdentifier:(id)arg1;
17 - (id)localizedNameForContext:(id)arg1;
18 - (NSString *)bundleIdentifier;
19 - (NSString *)applicationType;
20 - (NSURL *)bundleURL;
21 @end
22
23 @interface LSApplicationProxy (StolenFromAltList)
24 - (BOOL)atl_isHidden;
25 @end
26
27 @interface LSApplicationWorkspace : NSObject
28 +(id)defaultWorkspace;
29 -(NSArray<LSApplicationProxy *> *)allApplications;
30 @end
31
32 @interface NSMutableArray (Custom)
33 -(void)sortApps;
34 @end
35
36 // @interface ListItem : NSObject
37 // @property (nonatomic, retain) NSString *name;
38 // @property (nonatomic, retain) NSString *bundleID;
39 // @property (nonatomic, retain) NSString *type;
40 // @property (nonatomic, retain) UIImage *icon;
41 // -(ListItem *)initWithName:(NSString *)name bundleID:(NSString *)bundleID type:(NSString *)type icon:(UIImage *)icon;
42 // @end
43
44 @implementation QASAppSelectorController
45 -(void)viewDidLoad
46 {
47 [super viewDidLoad];
48
49 PSSpecifier *specifier = [self specifier];
50
51 self.defaults = [specifier propertyForKey:@"defaults"];
52 self.key = [specifier propertyForKey:@"key"];
53
54 self.disabled = [NSMutableArray new];
55 self.enabled = [NSMutableArray new];
56
57 NSArray *defaults = [[[NSUserDefaults alloc] initWithSuiteName:self.defaults] arrayForKey:self.key];
58
59 if (defaults == nil) {
60 if ([self.key isEqualToString:@"leftButtons"])
61 [self.enabled addObject:@"com.apple.flashlight"];
62 else if ([self.key isEqualToString:@"rightButtons"])
63 [self.enabled addObject:@"com.apple.camera"];
64 } else
65 [self.enabled addObjectsFromArray:defaults];
66
67 for (LSApplicationProxy *proxy in [[LSApplicationWorkspace defaultWorkspace] allApplications]) {
68 if (![proxy atl_isHidden] && [self.enabled indexOfObject:proxy.bundleIdentifier] == NSNotFound)
69 [self.disabled addObject:proxy.bundleIdentifier];
70 }
71
72 if ([self.enabled indexOfObject:@"com.apple.flashlight"] == NSNotFound)
73 [self.disabled addObject:@"com.apple.flashlight"];
74
75 if ([self.enabled indexOfObject:@"com.apple.donotdisturb"] == NSNotFound)
76 [self.disabled addObject:@"com.apple.donotdisturb"];
77
78 [self.disabled sortApps];
79 }
80
81 -(void)viewWillAppear:(BOOL)animated
82 {
83 self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
84 self.tableView.delegate = self;
85 self.tableView.dataSource = self;
86 self.tableView.editing = TRUE;
87
88 [self.view addSubview:self.tableView];
89 }
90
91 -(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
92 {
93 if (section == 0)
94 return @"Enabled";
95 else if (section == 1)
96 return @"Disabled";
97 else
98 return @"";
99 }
100
101 -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
102 {
103 return 2;
104 }
105
106 -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
107 {
108 if (section == 0)
109 return self.enabled.count;
110 else
111 return self.disabled.count;
112 }
113
114 -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
115 {
116 NSString *bundleid = indexPath.section == 0 ? self.enabled[indexPath.row] : self.disabled[indexPath.row];
117
118 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"com.cameronkatri.quickactions"];
119
120 if (cell == nil) {
121 cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"com.cameronkatri.quickactions"];
122
123 cell.selectionStyle = UITableViewCellSelectionStyleNone;
124 }
125
126 if ([bundleid isEqualToString:@"com.apple.flashlight"]) {
127 cell.textLabel.text = @"Flashlight";
128 cell.detailTextLabel.text = nil;
129 NSBundle *flashlightModule = [NSBundle bundleWithPath:@"/System/Library/ControlCenter/Bundles/FlashlightModule.bundle"];
130 cell.imageView.image = [[UIImage imageNamed:@"SettingsIcon"
131 inBundle:flashlightModule
132 compatibleWithTraitCollection:nil] _applicationIconImageForFormat:0 precomposed:YES scale:[UIScreen mainScreen].scale];
133 } else if ([bundleid isEqualToString:@"com.apple.donotdisturb"]) {
134 cell.textLabel.text = @"Do Not Disturb";
135 cell.detailTextLabel.text = nil;
136 NSBundle *doNotDisturbBundle = [NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/DoNotDisturb.framework/PlugIns/DoNotDisturbIntents.appex"];
137 cell.imageView.image = [[UIImage imageNamed:@"DoNotDisturb"
138 inBundle:doNotDisturbBundle
139 compatibleWithTraitCollection:nil] _applicationIconImageForFormat:0 precomposed:YES scale:[UIScreen mainScreen].scale];
140 } else {
141 cell.textLabel.text = [[LSApplicationProxy applicationProxyForIdentifier:bundleid] localizedNameForContext:nil];
142 if (cell.textLabel.text == nil) {
143 cell.textLabel.text = bundleid;
144 cell.detailTextLabel.text = nil;
145 } else {
146 cell.detailTextLabel.text = bundleid;
147 }
148 cell.detailTextLabel.textColor = [UIColor secondaryLabelColor];
149 cell.imageView.image = [UIImage _applicationIconImageForBundleIdentifier:bundleid format:0 scale:[UIScreen mainScreen].scale];
150 }
151
152 cell.showsReorderControl = indexPath.section == 0 ? YES : FALSE;
153
154 return cell;
155 }
156
157 -(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
158 {
159 return indexPath.section == 0 ? UITableViewCellEditingStyleDelete : UITableViewCellEditingStyleInsert;
160 }
161
162 -(BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
163 {
164 return indexPath.section == 0 ? YES : FALSE;
165 }
166
167 -(void)tableView:(UITableView *)tableView moveRowAtIndexPath:(nonnull NSIndexPath *)sourceIndexPath toIndexPath:(nonnull NSIndexPath *)destinationIndexPath
168 {
169 if (sourceIndexPath.section == 0) {
170 if (destinationIndexPath.section == 0)
171 [self.enabled exchangeObjectAtIndex:sourceIndexPath.row withObjectAtIndex:destinationIndexPath.row];
172 else {
173 [self.disabled addObject:self.enabled[sourceIndexPath.row]];
174 [self.enabled removeObjectAtIndex:sourceIndexPath.row];
175 }
176 } else {
177 [self.enabled insertObject:self.disabled[sourceIndexPath.row] atIndex:destinationIndexPath.row];
178 [self.disabled removeObjectAtIndex:sourceIndexPath.row];
179 }
180
181 [self.disabled sortApps];
182 [tableView reloadData];
183
184 [[[NSUserDefaults alloc] initWithSuiteName:self.defaults] setObject:self.enabled forKey:self.key];
185 }
186
187 -(NSIndexPath *)tableView:(UITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath
188 {
189 if (sourceIndexPath.section == 0 && proposedDestinationIndexPath.section == 1) {
190 NSUInteger insPoint = [self.disabled
191 indexOfObject:self.enabled[sourceIndexPath.row]
192 inSortedRange:NSMakeRange(0, [self.disabled count])
193 options:NSBinarySearchingInsertionIndex
194 usingComparator:^NSComparisonResult(NSString *obj1, NSString *obj2) {
195 if ([obj1 isEqualToString:@"com.apple.flashlight"])
196 return NSOrderedAscending;
197 else if ([obj2 isEqualToString:@"com.apple.flashlight"])
198 return NSOrderedDescending;
199 NSString *obj1Name = [[LSApplicationProxy applicationProxyForIdentifier:obj1] localizedNameForContext:nil];
200 NSString *obj2Name = [[LSApplicationProxy applicationProxyForIdentifier:obj2] localizedNameForContext:nil];
201 return ([obj1Name localizedCaseInsensitiveCompare:obj2Name]);
202 }];
203 return [NSIndexPath indexPathForRow:insPoint inSection:1];
204 }
205 return proposedDestinationIndexPath;
206 }
207
208 -(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(nonnull NSIndexPath *)indexPath
209 {
210 NSString *item = indexPath.section == 0 ? self.enabled[indexPath.row] : self.disabled[indexPath.row];
211 NSIndexPath *insertPath;
212
213 if (editingStyle == UITableViewCellEditingStyleDelete) {
214 [self.enabled removeObject:item];
215 [self.disabled addObject:item];
216 [self.disabled sortApps];
217
218 insertPath = [NSIndexPath indexPathForRow:[self.disabled indexOfObject:item] inSection:1];
219 } else if (editingStyle == UITableViewCellEditingStyleInsert) {
220 [self.disabled removeObject:item];
221 [self.enabled addObject:item];
222
223 insertPath = [NSIndexPath indexPathForRow:([self.enabled count] - 1) inSection:0];
224 }
225
226 [tableView beginUpdates];
227 [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:insertPath] withRowAnimation:UITableViewRowAnimationFade];
228 [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
229 [tableView endUpdates];
230
231 [[[NSUserDefaults alloc] initWithSuiteName:self.defaults] setObject:self.enabled forKey:self.key];
232 }
233 @end
234
235 @implementation NSMutableArray (Custom)
236 -(void)sortApps {
237 [self sortUsingComparator:^NSComparisonResult(NSString *obj1, NSString *obj2) {
238 if ([obj1 isEqualToString:@"com.apple.flashlight"])
239 return NSOrderedAscending;
240 else if ([obj2 isEqualToString:@"com.apple.flashlight"])
241 return NSOrderedDescending;
242
243 NSString *obj1Name = [[LSApplicationProxy applicationProxyForIdentifier:obj1] localizedNameForContext:nil];
244 NSString *obj2Name = [[LSApplicationProxy applicationProxyForIdentifier:obj2] localizedNameForContext:nil];
245
246 return ([obj1Name localizedCaseInsensitiveCompare:obj2Name]);
247 }];
248 }
249 @end
250
251 @implementation LSApplicationProxy (StolenFromAltList)
252
253 BOOL tagArrayContainsTag(NSArray* tagArr, NSString* tag)
254 {
255 if(!tagArr || !tag) return NO;
256
257 __block BOOL found = NO;
258
259 [tagArr enumerateObjectsUsingBlock:^(NSString* tagToCheck, NSUInteger idx, BOOL* stop)
260 {
261 if(![tagToCheck isKindOfClass:[NSString class]])
262 {
263 return;
264 }
265
266 if([tagToCheck rangeOfString:tag options:0].location != NSNotFound)
267 {
268 found = YES;
269 *stop = YES;
270 }
271 }];
272
273 return found;
274 }
275
276 - (BOOL)atl_isHidden
277 {
278 NSArray* appTags;
279 NSArray* recordAppTags;
280 NSArray* sbAppTags;
281
282 BOOL launchProhibited = NO;
283
284 if([self respondsToSelector:@selector(correspondingApplicationRecord)])
285 {
286 // On iOS 14, self.appTags is always empty but the application record still has the correct ones
287 LSApplicationRecord* record = [self correspondingApplicationRecord];
288 recordAppTags = record.appTags;
289 launchProhibited = record.launchProhibited;
290 }
291 if([self respondsToSelector:@selector(appTags)])
292 {
293 appTags = self.appTags;
294 }
295 if(!launchProhibited && [self respondsToSelector:@selector(isLaunchProhibited)])
296 {
297 launchProhibited = self.launchProhibited;
298 }
299
300 NSURL* bundleURL = self.bundleURL;
301 if(bundleURL && [bundleURL checkResourceIsReachableAndReturnError:nil])
302 {
303 NSBundle* bundle = [NSBundle bundleWithURL:bundleURL];
304 sbAppTags = [bundle objectForInfoDictionaryKey:@"SBAppTags"];
305 }
306
307 BOOL isWebApplication = ([self.bundleIdentifier rangeOfString:@"com.apple.webapp" options:NSCaseInsensitiveSearch].location != NSNotFound);
308 return tagArrayContainsTag(appTags, @"hidden") || tagArrayContainsTag(recordAppTags, @"hidden") || tagArrayContainsTag(sbAppTags, @"hidden") || isWebApplication || launchProhibited;
309 }
310 @end