1 #import <Foundation/Foundation.h>
2 #include <objc/NSObject.h>
3 #import <UIKit/UIKit.h>
4 #import <Preferences/PSSpecifier.h>
5 #import "QASAppSelectorController.h"
7 @interface LSApplicationRecord
8 @property (readonly) NSArray * appTags;
9 @property (getter=isLaunchProhibited,readonly) BOOL launchProhibited;
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;
23 @interface LSApplicationProxy (StolenFromAltList)
27 @interface LSApplicationWorkspace : NSObject
28 +(id)defaultWorkspace;
29 -(NSArray<LSApplicationProxy *> *)allApplications;
32 @interface NSMutableArray (Custom)
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;
44 @implementation QASAppSelectorController
49 PSSpecifier *specifier = [self specifier];
51 self.defaults = [specifier propertyForKey:@"defaults"];
52 self.key = [specifier propertyForKey:@"key"];
54 self.disabled = [NSMutableArray new];
55 self.enabled = [NSMutableArray new];
57 NSArray *defaults = [[[NSUserDefaults alloc] initWithSuiteName:self.defaults] arrayForKey:self.key];
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"];
65 [self.enabled addObjectsFromArray:defaults];
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];
72 if ([self.enabled indexOfObject:@"com.apple.flashlight"] == NSNotFound)
73 [self.disabled addObject:@"com.apple.flashlight"];
75 if ([self.enabled indexOfObject:@"com.apple.donotdisturb"] == NSNotFound)
76 [self.disabled addObject:@"com.apple.donotdisturb"];
78 [self.disabled sortApps];
81 -(void)viewWillAppear:(BOOL)animated
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;
88 [self.view addSubview:self.tableView];
91 -(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
95 else if (section == 1)
101 -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
106 -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
109 return self.enabled.count;
111 return self.disabled.count;
114 -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
116 NSString *bundleid = indexPath.section == 0 ? self.enabled[indexPath.row] : self.disabled[indexPath.row];
118 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"com.cameronkatri.quickactions"];
121 cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"com.cameronkatri.quickactions"];
123 cell.selectionStyle = UITableViewCellSelectionStyleNone;
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];
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;
146 cell.detailTextLabel.text = bundleid;
148 cell.detailTextLabel.textColor = [UIColor secondaryLabelColor];
149 cell.imageView.image = [UIImage _applicationIconImageForBundleIdentifier:bundleid format:0 scale:[UIScreen mainScreen].scale];
152 cell.showsReorderControl = indexPath.section == 0 ? YES : FALSE;
157 -(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
159 return indexPath.section == 0 ? UITableViewCellEditingStyleDelete : UITableViewCellEditingStyleInsert;
162 -(BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
164 return indexPath.section == 0 ? YES : FALSE;
167 -(void)tableView:(UITableView *)tableView moveRowAtIndexPath:(nonnull NSIndexPath *)sourceIndexPath toIndexPath:(nonnull NSIndexPath *)destinationIndexPath
169 if (sourceIndexPath.section == 0) {
170 if (destinationIndexPath.section == 0)
171 [self.enabled exchangeObjectAtIndex:sourceIndexPath.row withObjectAtIndex:destinationIndexPath.row];
173 [self.disabled addObject:self.enabled[sourceIndexPath.row]];
174 [self.enabled removeObjectAtIndex:sourceIndexPath.row];
177 [self.enabled insertObject:self.disabled[sourceIndexPath.row] atIndex:destinationIndexPath.row];
178 [self.disabled removeObjectAtIndex:sourceIndexPath.row];
181 [self.disabled sortApps];
182 [tableView reloadData];
184 [[[NSUserDefaults alloc] initWithSuiteName:self.defaults] setObject:self.enabled forKey:self.key];
187 -(NSIndexPath *)tableView:(UITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath
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]);
203 return [NSIndexPath indexPathForRow:insPoint inSection:1];
205 return proposedDestinationIndexPath;
208 -(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(nonnull NSIndexPath *)indexPath
210 NSString *item = indexPath.section == 0 ? self.enabled[indexPath.row] : self.disabled[indexPath.row];
211 NSIndexPath *insertPath;
213 if (editingStyle == UITableViewCellEditingStyleDelete) {
214 [self.enabled removeObject:item];
215 [self.disabled addObject:item];
216 [self.disabled sortApps];
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];
223 insertPath = [NSIndexPath indexPathForRow:([self.enabled count] - 1) inSection:0];
226 [tableView beginUpdates];
227 [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:insertPath] withRowAnimation:UITableViewRowAnimationFade];
228 [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
229 [tableView endUpdates];
231 [[[NSUserDefaults alloc] initWithSuiteName:self.defaults] setObject:self.enabled forKey:self.key];
235 @implementation NSMutableArray (Custom)
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;
243 NSString *obj1Name = [[LSApplicationProxy applicationProxyForIdentifier:obj1] localizedNameForContext:nil];
244 NSString *obj2Name = [[LSApplicationProxy applicationProxyForIdentifier:obj2] localizedNameForContext:nil];
246 return ([obj1Name localizedCaseInsensitiveCompare:obj2Name]);
251 @implementation LSApplicationProxy (StolenFromAltList)
253 BOOL tagArrayContainsTag(NSArray* tagArr, NSString* tag)
255 if(!tagArr || !tag) return NO;
257 __block BOOL found = NO;
259 [tagArr enumerateObjectsUsingBlock:^(NSString* tagToCheck, NSUInteger idx, BOOL* stop)
261 if(![tagToCheck isKindOfClass:[NSString class]])
266 if([tagToCheck rangeOfString:tag options:0].location != NSNotFound)
279 NSArray* recordAppTags;
282 BOOL launchProhibited = NO;
284 if([self respondsToSelector:@selector(correspondingApplicationRecord)])
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;
291 if([self respondsToSelector:@selector(appTags)])
293 appTags = self.appTags;
295 if(!launchProhibited && [self respondsToSelector:@selector(isLaunchProhibited)])
297 launchProhibited = self.launchProhibited;
300 NSURL* bundleURL = self.bundleURL;
301 if(bundleURL && [bundleURL checkResourceIsReachableAndReturnError:nil])
303 NSBundle* bundle = [NSBundle bundleWithURL:bundleURL];
304 sbAppTags = [bundle objectForInfoDictionaryKey:@"SBAppTags"];
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;