├── .gitignore ├── .images ├── 1.PNG ├── 2.PNG ├── 3.PNG ├── 4.PNG └── 5.PNG ├── ATLApplicationListControllerBase.h ├── ATLApplicationListControllerBase.m ├── ATLApplicationListMultiSelectionController.h ├── ATLApplicationListMultiSelectionController.m ├── ATLApplicationListSelectionController.h ├── ATLApplicationListSelectionController.m ├── ATLApplicationListSubcontroller.h ├── ATLApplicationListSubcontroller.m ├── ATLApplicationListSubcontrollerController.h ├── ATLApplicationListSubcontrollerController.m ├── ATLApplicationSection.h ├── ATLApplicationSection.m ├── ATLApplicationSelectionCell.h ├── ATLApplicationSelectionCell.m ├── ATLApplicationSubtitleCell.h ├── ATLApplicationSubtitleCell.m ├── ATLApplicationSubtitleSwitchCell.h ├── ATLApplicationSubtitleSwitchCell.m ├── AltList.h ├── AltList.x ├── AltListTestBundlelessPreferences ├── Makefile └── layout │ └── Library │ └── PreferenceLoader │ └── Preferences │ └── AltListTestBundlelessPreferences.plist ├── AltListTestPreferences ├── APPRootListController.h ├── APPRootListController.m ├── Makefile ├── Resources │ ├── Info.plist │ └── Root.plist └── layout │ └── Library │ ├── Application Support │ └── AltListTest.bundle │ │ ├── de.lproj │ │ └── Localizable.strings │ │ └── en.lproj │ │ └── Localizable.strings │ └── PreferenceLoader │ └── Preferences │ └── AppPrefsTestBundle.plist ├── CoreServices.h ├── LICENSE.md ├── LSApplicationProxy+AltList.h ├── LSApplicationProxy+AltList.m ├── Makefile ├── PSSpecifier+AltList.h ├── PSSpecifier+AltList.m ├── README.md ├── Resources ├── Info.plist ├── ar.lproj │ └── Localizable.strings ├── de.lproj │ └── Localizable.strings ├── en.lproj │ └── Localizable.strings ├── fr.lproj │ └── Localizable.strings ├── it.lproj │ └── Localizable.strings ├── ja.lproj │ └── Localizable.strings ├── ko.lproj │ └── Localizable.strings ├── nl.lproj │ └── Localizable.strings ├── pl.lproj │ └── Localizable.strings ├── pt.lproj │ └── Localizable.strings ├── ru.lproj │ └── Localizable.strings ├── sk.lproj │ └── Localizable.strings ├── tr.lproj │ └── Localizable.strings ├── zh-Hant.lproj │ └── Localizable.strings └── zh.lproj │ └── Localizable.strings ├── control ├── install_to_theos.sh └── release_build.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .theos/ 2 | .DS_Store 3 | packages/ -------------------------------------------------------------------------------- /.images/1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opa334/AltList/9db09f92eff0404ae7fa9c2fe6c25ba13d5e02d7/.images/1.PNG -------------------------------------------------------------------------------- /.images/2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opa334/AltList/9db09f92eff0404ae7fa9c2fe6c25ba13d5e02d7/.images/2.PNG -------------------------------------------------------------------------------- /.images/3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opa334/AltList/9db09f92eff0404ae7fa9c2fe6c25ba13d5e02d7/.images/3.PNG -------------------------------------------------------------------------------- /.images/4.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opa334/AltList/9db09f92eff0404ae7fa9c2fe6c25ba13d5e02d7/.images/4.PNG -------------------------------------------------------------------------------- /.images/5.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opa334/AltList/9db09f92eff0404ae7fa9c2fe6c25ba13d5e02d7/.images/5.PNG -------------------------------------------------------------------------------- /ATLApplicationListControllerBase.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "ATLApplicationSection.h" 4 | 5 | @class LSApplicationProxy; 6 | 7 | @interface PSListController() 8 | - (BOOL)containsSpecifier:(PSSpecifier*)specifier; 9 | @end 10 | 11 | @protocol LSApplicationWorkspaceObserverProtocol 12 | @optional 13 | -(void)applicationsDidInstall:(id)arg1; 14 | -(void)applicationsDidUninstall:(id)arg1; 15 | @end 16 | 17 | @interface ATLApplicationListControllerBase : PSListController 18 | { 19 | dispatch_queue_t _iconLoadQueue; 20 | NSMutableArray* _allSpecifiers; 21 | NSMutableDictionary* _specifiersByLetter; 22 | NSArray* _applicationSections; 23 | UISearchController* _searchController; 24 | NSString* _searchKey; 25 | BOOL _isPopulated; 26 | BOOL _isReloadingSpecifiers; 27 | NSBundle* _altListBundle; 28 | UIImage* _placeholderAppIcon; 29 | } 30 | 31 | @property (nonatomic) BOOL useSearchBar; 32 | @property (nonatomic) BOOL hideSearchBarWhileScrolling; 33 | @property (nonatomic) BOOL includeIdentifiersInSearch; 34 | @property (nonatomic) BOOL showIdentifiersAsSubtitle; 35 | @property (nonatomic) BOOL alphabeticIndexingEnabled; 36 | @property (nonatomic) BOOL hideAlphabeticSectionHeaders; 37 | @property (nonatomic) NSBundle* localizationBundle; 38 | 39 | - (instancetype)initWithSections:(NSArray*)applicationSections; 40 | 41 | - (void)_setUpSearchBar; 42 | - (void)_loadSectionsFromSpecifier; 43 | - (void)_populateSections; 44 | 45 | - (void)loadPreferences; 46 | - (void)prepareForPopulatingSections; 47 | - (NSString*)localizedStringForString:(NSString*)string; 48 | - (void)reloadApplications; 49 | 50 | - (BOOL)shouldHideApplicationSpecifiers; 51 | - (BOOL)shouldHideApplicationSpecifier:(PSSpecifier*)specifier; 52 | 53 | - (BOOL)shouldShowSubtitles; 54 | - (NSString*)subtitleForApplicationWithIdentifier:(NSString*)applicationID; 55 | - (NSString*)_subtitleForSpecifier:(PSSpecifier*)specifier; 56 | 57 | - (PSCellType)cellTypeForApplicationCells; 58 | - (Class)customCellClassForCellType:(PSCellType)cellType; 59 | - (Class)detailControllerClassForSpecifierOfApplicationProxy:(LSApplicationProxy*)applicationProxy; 60 | - (SEL)getterForSpecifierOfApplicationProxy:(LSApplicationProxy*)applicationProxy; 61 | - (SEL)setterForSpecifierOfApplicationProxy:(LSApplicationProxy*)applicationProxy; 62 | 63 | - (PSSpecifier*)createSpecifierForApplicationProxy:(LSApplicationProxy*)applicationProxy; 64 | - (NSArray*)createSpecifiersForApplicationSection:(ATLApplicationSection*)section; 65 | - (PSSpecifier*)createGroupSpecifierForApplicationSection:(ATLApplicationSection*)section; 66 | 67 | - (NSMutableArray*)specifiersGroupedByLetters; 68 | - (void)populateSpecifiersByLetter; 69 | 70 | - (PSSpecifier*)specifierForApplicationWithIdentifier:(NSString*)applicationID; 71 | - (NSIndexPath*)indexPathForApplicationWithIdentifier:(NSString*)applicationID; 72 | 73 | @end -------------------------------------------------------------------------------- /ATLApplicationListControllerBase.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "ATLApplicationListControllerBase.h" 3 | #import "CoreServices.h" 4 | #import "LSApplicationProxy+AltList.h" 5 | #import "ATLApplicationSubtitleSwitchCell.h" 6 | #import "ATLApplicationSubtitleCell.h" 7 | 8 | @interface UIImage (Private) 9 | + (instancetype)_applicationIconImageForBundleIdentifier:(NSString*)bundleIdentifier format:(int)format scale:(CGFloat)scale; 10 | @end 11 | 12 | @implementation ATLApplicationListControllerBase 13 | 14 | - (instancetype)init 15 | { 16 | self = [super init]; 17 | _placeholderAppIcon = [UIImage _applicationIconImageForBundleIdentifier:@"com.apple.WebSheet" format:0 scale:[UIScreen mainScreen].scale]; 18 | if (dispatch_queue_attr_make_with_qos_class != NULL) 19 | { 20 | dispatch_queue_attr_t qos = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, DISPATCH_QUEUE_PRIORITY_BACKGROUND, -1); 21 | _iconLoadQueue = dispatch_queue_create("com.opa334.AltList.IconLoadQueue", qos); 22 | } 23 | 24 | _altListBundle = [NSBundle bundleForClass:[ATLApplicationListControllerBase class]]; 25 | [[LSApplicationWorkspace defaultWorkspace] addObserver:self]; 26 | return self; 27 | } 28 | 29 | - (instancetype)initWithSections:(NSArray*)applicationSections 30 | { 31 | self = [self init]; 32 | _applicationSections = applicationSections; 33 | return self; 34 | } 35 | 36 | - (void)dealloc 37 | { 38 | [[LSApplicationWorkspace defaultWorkspace] removeObserver:self]; 39 | } 40 | 41 | - (void)viewDidLoad 42 | { 43 | [super viewDidLoad]; 44 | 45 | PSSpecifier* specifier = [self specifier]; 46 | 47 | NSNumber* useSearchBarNum = [specifier propertyForKey:@"useSearchBar"]; 48 | if(useSearchBarNum) 49 | { 50 | self.useSearchBar = [useSearchBarNum boolValue]; 51 | } 52 | if(self.useSearchBar) 53 | { 54 | NSNumber* hideSearchBarWhileScrollingNum = [specifier propertyForKey:@"hideSearchBarWhileScrolling"]; // only on ios 11 and up 55 | if(hideSearchBarWhileScrollingNum) 56 | { 57 | self.hideSearchBarWhileScrolling = [hideSearchBarWhileScrollingNum boolValue]; 58 | } 59 | 60 | NSNumber* includeIdentifiersInSearchNum = [specifier propertyForKey:@"includeIdentifiersInSearch"]; 61 | if(includeIdentifiersInSearchNum) 62 | { 63 | self.includeIdentifiersInSearch = [includeIdentifiersInSearchNum boolValue]; 64 | } 65 | } 66 | 67 | [self _setUpSearchBar]; 68 | } 69 | 70 | // UITableViewDelegate 71 | 72 | - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section 73 | { 74 | if(self.hideAlphabeticSectionHeaders) 75 | { 76 | PSSpecifier* specifier = [self specifierAtIndex:[self indexOfGroup:section]]; 77 | if([[specifier propertyForKey:@"isLetterSection"] boolValue]) 78 | { 79 | return 0.00000000001; 80 | } 81 | } 82 | 83 | return [super tableView:tableView heightForHeaderInSection:section]; 84 | } 85 | 86 | - (NSString*)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section 87 | { 88 | if(self.hideAlphabeticSectionHeaders) 89 | { 90 | PSSpecifier* specifier = [self specifierAtIndex:[self indexOfGroup:section]]; 91 | if([[specifier propertyForKey:@"isLetterSection"] boolValue]) 92 | { 93 | return nil; 94 | } 95 | } 96 | 97 | return [super tableView:tableView titleForHeaderInSection:section]; 98 | } 99 | 100 | - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section 101 | { 102 | if(self.alphabeticIndexingEnabled) 103 | { 104 | PSSpecifier* specifier = [self specifierAtIndex:[self indexOfGroup:section]]; 105 | if([[specifier propertyForKey:@"isLetterSection"] boolValue] || [[specifier propertyForKey:@"isFirstLetterSection"] boolValue]) 106 | { 107 | return 0.00000000001; 108 | } 109 | } 110 | 111 | return [super tableView:tableView heightForFooterInSection:section]; 112 | } 113 | 114 | - (NSString*)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section 115 | { 116 | if(self.hideAlphabeticSectionHeaders) 117 | { 118 | PSSpecifier* specifier = [self specifierAtIndex:[self indexOfGroup:section]]; 119 | if([[specifier propertyForKey:@"isLetterSection"] boolValue]) 120 | { 121 | return nil; 122 | } 123 | } 124 | 125 | return [super tableView:tableView titleForFooterInSection:section]; 126 | } 127 | 128 | - (NSArray*)sectionIndexTitlesForTableView:(UITableView *)tableView 129 | { 130 | if(self.alphabeticIndexingEnabled) 131 | { 132 | NSMutableArray* firstLetters = [_specifiersByLetter allKeys].mutableCopy; 133 | [firstLetters sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)]; 134 | 135 | if([firstLetters.firstObject isEqualToString:@"#"]) 136 | { 137 | // if there is a section for applications that don't start with a letter, move it to the bottom 138 | [firstLetters removeObjectAtIndex:0]; 139 | [firstLetters addObject:@"#"]; 140 | } 141 | 142 | return firstLetters; 143 | } 144 | return nil; 145 | } 146 | 147 | // UITableViewDelegate end 148 | 149 | // LSApplicationWorkspaceObserverProtocol 150 | 151 | - (void)applicationsDidInstall:(id)arg1 152 | { 153 | dispatch_async(dispatch_get_main_queue(), ^(void){ 154 | [self reloadApplications]; 155 | }); 156 | } 157 | 158 | - (void)applicationsDidUninstall:(id)arg1 159 | { 160 | dispatch_async(dispatch_get_main_queue(), ^(void){ 161 | [self reloadApplications]; 162 | }); 163 | } 164 | 165 | // LSApplicationWorkspaceObserverProtocol end 166 | 167 | - (void)setAlphabeticIndexingEnabled:(BOOL)enabled 168 | { 169 | if(_applicationSections.count == 1) 170 | { 171 | _alphabeticIndexingEnabled = enabled; 172 | return; 173 | } 174 | 175 | _alphabeticIndexingEnabled = NO; 176 | } 177 | 178 | - (void)_setUpSearchBar 179 | { 180 | if(self.useSearchBar) 181 | { 182 | _searchController = [[UISearchController alloc] initWithSearchResultsController:nil]; 183 | _searchController.searchResultsUpdater = self; 184 | if (@available(iOS 9.1, *)) _searchController.obscuresBackgroundDuringPresentation = NO; 185 | if (@available(iOS 11.0, *)) 186 | { 187 | if(@available(iOS 13.0, *)) 188 | { 189 | _searchController.hidesNavigationBarDuringPresentation = YES; 190 | } 191 | else 192 | { 193 | _searchController.hidesNavigationBarDuringPresentation = NO; 194 | } 195 | 196 | self.navigationItem.searchController = _searchController; 197 | self.navigationItem.hidesSearchBarWhenScrolling = self.hideSearchBarWhileScrolling; 198 | } 199 | else 200 | { 201 | self.table.tableHeaderView = _searchController.searchBar; 202 | [self.table setContentOffset:CGPointMake(0,44) animated:NO]; 203 | } 204 | } 205 | } 206 | 207 | - (void)updateSearchResultsForSearchController:(UISearchController *)searchController 208 | { 209 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 210 | _searchKey = searchController.searchBar.text; 211 | dispatch_async(dispatch_get_main_queue(), ^(void){ 212 | [self reloadSpecifiers]; 213 | }); 214 | }); 215 | } 216 | 217 | - (void)_loadSectionsFromSpecifier 218 | { 219 | NSArray* plistSections = [[self specifier] propertyForKey:@"sections"]; 220 | if(plistSections) 221 | { 222 | NSMutableArray* applicationSectionsM = [NSMutableArray new]; 223 | [plistSections enumerateObjectsUsingBlock:^(NSDictionary* dict, NSUInteger idx, BOOL *stop) 224 | { 225 | if(![dict isKindOfClass:[NSDictionary class]]) return; 226 | ATLApplicationSection* section = [ATLApplicationSection applicationSectionWithDictionary:dict]; 227 | [applicationSectionsM addObject:section]; 228 | }]; 229 | _applicationSections = applicationSectionsM.copy; 230 | } 231 | else 232 | { 233 | _applicationSections = @[[[ATLApplicationSection alloc] initNonCustomSectionWithType:SECTION_TYPE_VISIBLE]]; 234 | } 235 | } 236 | 237 | - (void)_populateSections 238 | { 239 | NSArray* allInstalledApplications = [[LSApplicationWorkspace defaultWorkspace] atl_allInstalledApplications]; 240 | [_applicationSections enumerateObjectsUsingBlock:^(ATLApplicationSection* section, NSUInteger idx, BOOL *stop) 241 | { 242 | [section populateFromAllApplications:allInstalledApplications]; 243 | }]; 244 | } 245 | 246 | - (void)loadPreferences { } 247 | - (void)savePreferences { } 248 | 249 | - (void)prepareForPopulatingSections 250 | { 251 | NSNumber* showIdentifiersAsSubtitleNum = [[self specifier] propertyForKey:@"showIdentifiersAsSubtitle"]; 252 | if(showIdentifiersAsSubtitleNum) 253 | { 254 | self.showIdentifiersAsSubtitle = [showIdentifiersAsSubtitleNum boolValue]; 255 | } 256 | 257 | NSNumber* alphabeticIndexingEnabledNum = [[self specifier] propertyForKey:@"alphabeticIndexingEnabled"]; 258 | self.alphabeticIndexingEnabled = [alphabeticIndexingEnabledNum boolValue]; 259 | if(self.alphabeticIndexingEnabled) 260 | { 261 | NSNumber* hideAlphabeticSectionHeadersNum = [[self specifier] propertyForKey:@"hideAlphabeticSectionHeaders"]; 262 | self.hideAlphabeticSectionHeaders = [hideAlphabeticSectionHeadersNum boolValue]; 263 | } 264 | 265 | NSString* localizationBundlePathString = [[self specifier] propertyForKey:@"localizationBundlePath"]; 266 | if(localizationBundlePathString) 267 | { 268 | self.localizationBundle = [NSBundle bundleWithPath:localizationBundlePathString]; 269 | } 270 | } 271 | 272 | - (NSString*)localizedStringForString:(NSString*)string 273 | { 274 | if(self.localizationBundle) 275 | { 276 | NSString* localizedString = [self.localizationBundle localizedStringForKey:string value:nil table:nil]; 277 | if(localizedString) 278 | { 279 | return localizedString; 280 | } 281 | } 282 | 283 | if(!_altListBundle) 284 | { 285 | return string; 286 | } 287 | 288 | return [_altListBundle localizedStringForKey:string value:string table:nil]; 289 | } 290 | 291 | - (PSCellType)cellTypeForApplicationCells 292 | { 293 | return PSStaticTextCell; 294 | } 295 | 296 | - (Class)customCellClassForCellType:(PSCellType)cellType 297 | { 298 | if([self shouldShowSubtitles]) 299 | { 300 | if(cellType == PSSwitchCell) 301 | { 302 | return [ATLApplicationSubtitleSwitchCell class]; 303 | } 304 | else 305 | { 306 | return [ATLApplicationSubtitleCell class]; 307 | } 308 | } 309 | 310 | return nil; 311 | } 312 | 313 | - (Class)detailControllerClassForSpecifierOfApplicationProxy:(LSApplicationProxy*)applicationProxy 314 | { 315 | return nil; 316 | } 317 | 318 | - (SEL)getterForSpecifierOfApplicationProxy:(LSApplicationProxy*)applicationProxy 319 | { 320 | return nil; 321 | } 322 | 323 | - (SEL)setterForSpecifierOfApplicationProxy:(LSApplicationProxy*)applicationProxy 324 | { 325 | return nil; 326 | } 327 | 328 | - (PSSpecifier*)createSpecifierForApplicationProxy:(LSApplicationProxy*)applicationProxy 329 | { 330 | SEL setter = [self setterForSpecifierOfApplicationProxy:applicationProxy]; 331 | SEL getter = [self getterForSpecifierOfApplicationProxy:applicationProxy]; 332 | PSCellType cellType = [self cellTypeForApplicationCells]; 333 | 334 | PSSpecifier* specifier = [PSSpecifier preferenceSpecifierNamed:[applicationProxy atl_nameToDisplay] 335 | target:self 336 | set:setter 337 | get:getter 338 | detail:nil 339 | cell:cellType 340 | edit:nil]; 341 | 342 | NSString* bundleIdentifier = applicationProxy.atl_bundleIdentifier; 343 | specifier.identifier = bundleIdentifier; 344 | [specifier setProperty:bundleIdentifier forKey:@"applicationIdentifier"]; 345 | 346 | [specifier setProperty:_placeholderAppIcon forKey:@"iconImage"]; 347 | 348 | Class customCellClass = [self customCellClassForCellType:cellType]; 349 | if(customCellClass) 350 | { 351 | [specifier setProperty:customCellClass forKey:@"cellClass"]; 352 | } 353 | 354 | Class detailControllerClass = [self detailControllerClassForSpecifierOfApplicationProxy:applicationProxy]; 355 | if(detailControllerClass) 356 | { 357 | specifier.detailControllerClass = detailControllerClass; 358 | } 359 | 360 | if(_iconLoadQueue) 361 | { 362 | UITableView* tableView = [self valueForKey:@"_table"]; 363 | dispatch_async(_iconLoadQueue, ^{ 364 | //usleep(1000 * 500); // (test delay for debugging) 365 | UIImage* iconImage = [UIImage _applicationIconImageForBundleIdentifier:applicationProxy.atl_bundleIdentifier format:0 scale:[UIScreen mainScreen].scale]; 366 | dispatch_async(dispatch_get_main_queue(), ^{ 367 | [specifier setProperty:iconImage forKey:@"iconImage"]; 368 | if([self containsSpecifier:specifier]) 369 | { 370 | NSIndexPath* specifierIndexPath = [self indexPathForIndex:[self indexOfSpecifier:specifier]]; 371 | if([[tableView indexPathsForVisibleRows] containsObject:specifierIndexPath]) 372 | { 373 | dispatch_async(dispatch_get_main_queue(), ^{ 374 | if(!_isReloadingSpecifiers) 375 | { 376 | [self reloadSpecifier:specifier]; 377 | } 378 | }); 379 | } 380 | } 381 | }); 382 | }); 383 | } 384 | else 385 | { 386 | UIImage* iconImage = [UIImage _applicationIconImageForBundleIdentifier:applicationProxy.atl_bundleIdentifier format:0 scale:[UIScreen mainScreen].scale]; 387 | [specifier setProperty:iconImage forKey:@"iconImage"]; 388 | } 389 | 390 | [specifier setProperty:@YES forKey:@"enabled"]; 391 | 392 | return specifier; 393 | } 394 | 395 | - (BOOL)shouldHideApplicationSpecifiers 396 | { 397 | if(_searchKey) 398 | { 399 | return ![_searchKey isEqualToString:@""]; 400 | } 401 | return NO; 402 | } 403 | 404 | - (BOOL)shouldHideApplicationSpecifier:(PSSpecifier*)specifier 405 | { 406 | BOOL nameMatch = [specifier.name rangeOfString:_searchKey options:NSCaseInsensitiveSearch range:NSMakeRange(0, [specifier.name length]) locale:[NSLocale currentLocale]].location != NSNotFound; 407 | 408 | BOOL identifierMatch = NO; 409 | if(self.includeIdentifiersInSearch) 410 | { 411 | NSString* applicationID = [specifier propertyForKey:@"applicationIdentifier"]; 412 | identifierMatch = [applicationID rangeOfString:_searchKey options:NSCaseInsensitiveSearch].location != NSNotFound; 413 | } 414 | 415 | return !identifierMatch && !nameMatch; 416 | } 417 | 418 | - (BOOL)shouldShowSubtitles 419 | { 420 | if(self.showIdentifiersAsSubtitle) 421 | { 422 | return YES; 423 | } 424 | return NO; 425 | } 426 | 427 | - (NSString*)subtitleForApplicationWithIdentifier:(NSString*)applicationID 428 | { 429 | if(self.showIdentifiersAsSubtitle) 430 | { 431 | return applicationID; 432 | } 433 | return nil; 434 | } 435 | 436 | - (NSString*)_subtitleForSpecifier:(PSSpecifier*)specifier 437 | { 438 | return [self subtitleForApplicationWithIdentifier:[specifier propertyForKey:@"applicationIdentifier"]]; 439 | } 440 | 441 | - (NSArray*)createSpecifiersForApplicationSection:(ATLApplicationSection*)section 442 | { 443 | NSMutableArray* sectionSpecifiers = [NSMutableArray new]; 444 | 445 | [section.applicationsInSection enumerateObjectsUsingBlock:^(LSApplicationProxy* appProxy, NSUInteger idx, BOOL *stop) 446 | { 447 | PSSpecifier* appSpecifier = [self createSpecifierForApplicationProxy:appProxy]; 448 | if(appSpecifier) 449 | { 450 | [sectionSpecifiers addObject:appSpecifier]; 451 | } 452 | }]; 453 | 454 | return sectionSpecifiers; 455 | } 456 | 457 | - (void)populateSpecifiersByLetter 458 | { 459 | _specifiersByLetter = [NSMutableDictionary new]; 460 | 461 | [_specifiers enumerateObjectsUsingBlock:^(PSSpecifier* specifier, NSUInteger idx, BOOL *stop) 462 | { 463 | NSString* trimmedName = [specifier.name stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 464 | // RTL/LTR characters, WhatsApp has an LTR character in front of it's name 465 | trimmedName = [trimmedName stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"\u200E\u200F"]]; 466 | 467 | NSString* firstLetter = @"#"; 468 | if(trimmedName.length > 0) 469 | { 470 | unichar firstLetterChar = [trimmedName.uppercaseString characterAtIndex:0]; 471 | if(firstLetterChar >= 'A' && firstLetterChar <= 'Z') 472 | { 473 | firstLetter = [NSString stringWithFormat:@"%c", firstLetterChar]; 474 | } 475 | } 476 | 477 | NSMutableArray* letterSpecifiers = [_specifiersByLetter objectForKey:firstLetter]; 478 | if(!letterSpecifiers) 479 | { 480 | letterSpecifiers = [NSMutableArray new]; 481 | [_specifiersByLetter setObject:letterSpecifiers forKey:firstLetter]; 482 | } 483 | [letterSpecifiers addObject:specifier]; 484 | }]; 485 | } 486 | 487 | - (NSMutableArray*)specifiersGroupedByLetters 488 | { 489 | BOOL firstSpecifier = YES; 490 | NSMutableArray* letterGroupedSpecifiers = [NSMutableArray new]; 491 | for(char c = 'A'; c <= 'Z'+1; c++) 492 | { 493 | NSString* cString; 494 | if(c == ('Z'+1)) 495 | { 496 | // Anything that doesn't start with a letter should be at the bottom 497 | cString = @"#"; 498 | } 499 | else 500 | { 501 | cString = [NSString stringWithFormat:@"%c", c]; 502 | } 503 | NSMutableArray* letterSpecifiers = [_specifiersByLetter objectForKey:cString]; 504 | if(letterSpecifiers) 505 | { 506 | PSSpecifier* groupSpecifier = [PSSpecifier emptyGroupSpecifier]; 507 | if(firstSpecifier && self.hideAlphabeticSectionHeaders) 508 | { 509 | groupSpecifier.name = [self localizedStringForString:@"Applications"]; 510 | [groupSpecifier setProperty:@YES forKey:@"isFirstLetterSection"]; 511 | } 512 | else 513 | { 514 | groupSpecifier.name = cString; 515 | [groupSpecifier setProperty:@YES forKey:@"isLetterSection"]; 516 | } 517 | [letterGroupedSpecifiers addObject:groupSpecifier]; 518 | [letterGroupedSpecifiers addObjectsFromArray:letterSpecifiers]; 519 | firstSpecifier = NO; 520 | } 521 | } 522 | 523 | return letterGroupedSpecifiers; 524 | } 525 | 526 | - (PSSpecifier*)createGroupSpecifierForApplicationSection:(ATLApplicationSection*)section 527 | { 528 | PSSpecifier* groupSpecifier = [PSSpecifier emptyGroupSpecifier]; 529 | groupSpecifier.name = [self localizedStringForString:section.sectionName]; 530 | return groupSpecifier; 531 | } 532 | 533 | - (void)reloadApplications 534 | { 535 | _allSpecifiers = nil; 536 | [self reloadSpecifiers]; 537 | } 538 | 539 | - (void)reloadSpecifiers 540 | { 541 | _isReloadingSpecifiers = YES; 542 | [super reloadSpecifiers]; 543 | _isReloadingSpecifiers = NO; 544 | } 545 | 546 | - (NSMutableArray*)specifiers 547 | { 548 | if(!_specifiers) 549 | { 550 | [self loadPreferences]; 551 | 552 | if(!_applicationSections) 553 | { 554 | [self _loadSectionsFromSpecifier]; 555 | } 556 | 557 | if(!_allSpecifiers) 558 | { 559 | [self prepareForPopulatingSections]; 560 | [self _populateSections]; 561 | 562 | _allSpecifiers = [NSMutableArray new]; 563 | 564 | [_applicationSections enumerateObjectsUsingBlock:^(ATLApplicationSection* section, NSUInteger idx, BOOL *stop) 565 | { 566 | PSSpecifier* groupSpecifier = [self createGroupSpecifierForApplicationSection:section]; 567 | NSArray* specifiersForSection = [self createSpecifiersForApplicationSection:section]; 568 | if(specifiersForSection && specifiersForSection.count > 0) 569 | { 570 | if(!self.alphabeticIndexingEnabled) 571 | { 572 | [_allSpecifiers addObject:groupSpecifier]; 573 | } 574 | [_allSpecifiers addObjectsFromArray:specifiersForSection]; 575 | } 576 | }]; 577 | } 578 | 579 | if(![self shouldHideApplicationSpecifiers]) 580 | { 581 | _specifiers = _allSpecifiers; 582 | } 583 | else 584 | { 585 | _specifiers = [NSMutableArray new]; 586 | [_allSpecifiers enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(PSSpecifier* specifier, NSUInteger idx, BOOL *stop) 587 | { 588 | if(specifier.cellType != PSGroupCell) 589 | { 590 | // hide specifiers that should be hidden 591 | if([self shouldHideApplicationSpecifier:specifier]) 592 | { 593 | return; 594 | } 595 | } 596 | else 597 | { 598 | // hide empty sections 599 | if(_specifiers.count == 0) 600 | { 601 | return; 602 | } 603 | PSSpecifier* firstSpecifier = _specifiers.firstObject; 604 | if(firstSpecifier.cellType == PSGroupCell) 605 | { 606 | return; 607 | } 608 | } 609 | 610 | [_specifiers insertObject:specifier atIndex:0]; 611 | }]; 612 | } 613 | 614 | if(self.alphabeticIndexingEnabled) 615 | { 616 | [self populateSpecifiersByLetter]; 617 | _specifiers = [self specifiersGroupedByLetters]; 618 | } 619 | } 620 | 621 | return _specifiers; 622 | } 623 | 624 | - (PSSpecifier*)specifierForApplicationWithIdentifier:(NSString*)applicationID 625 | { 626 | __block PSSpecifier* specifierToReturn; 627 | [_specifiers enumerateObjectsUsingBlock:^(PSSpecifier* specifier, NSUInteger idx, BOOL *stop) 628 | { 629 | NSString* specifierApplicationID = [specifier propertyForKey:@"applicationIdentifier"]; 630 | if([applicationID isEqualToString:specifierApplicationID]) 631 | { 632 | specifierToReturn = specifier; 633 | *stop = YES; 634 | } 635 | }]; 636 | return specifierToReturn; 637 | } 638 | 639 | - (NSIndexPath*)indexPathForApplicationWithIdentifier:(NSString*)applicationID 640 | { 641 | PSSpecifier* specifier = [self specifierForApplicationWithIdentifier:applicationID]; 642 | return [self indexPathForIndex:[self indexOfSpecifier:specifier]]; 643 | } 644 | 645 | @end 646 | -------------------------------------------------------------------------------- /ATLApplicationListMultiSelectionController.h: -------------------------------------------------------------------------------- 1 | #import "ATLApplicationListControllerBase.h" 2 | 3 | @interface ATLApplicationListMultiSelectionController : ATLApplicationListControllerBase 4 | { 5 | NSMutableSet* _selectedApplications; 6 | BOOL _defaultApplicationSwitchValue; 7 | } 8 | 9 | - (void)setApplicationEnabled:(NSNumber*)enabledNum specifier:(PSSpecifier*)specifier; 10 | - (id)readApplicationEnabled:(PSSpecifier*)specifier; 11 | 12 | @end -------------------------------------------------------------------------------- /ATLApplicationListMultiSelectionController.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "ATLApplicationListMultiSelectionController.h" 3 | #import "PSSpecifier+AltList.h" 4 | 5 | @implementation ATLApplicationListMultiSelectionController 6 | 7 | - (void)loadPreferences 8 | { 9 | PSSpecifier* specifier = [self specifier]; 10 | if([specifier atl_hasValidGetter]) 11 | { 12 | _selectedApplications = [NSMutableSet setWithArray:[specifier atl_performGetter]]; 13 | } 14 | 15 | if(!_selectedApplications) 16 | { 17 | NSArray* defaultValue = [specifier propertyForKey:@"default"]; 18 | if(defaultValue && [defaultValue isKindOfClass:[NSArray class]]) 19 | { 20 | _selectedApplications = [NSMutableSet setWithArray:defaultValue]; 21 | } 22 | else 23 | { 24 | _selectedApplications = [NSMutableSet new]; 25 | } 26 | } 27 | } 28 | 29 | - (void)savePreferences 30 | { 31 | PSSpecifier* specifier = [self specifier]; 32 | if([specifier atl_hasValidSetter]) 33 | { 34 | [specifier atl_performSetterWithValue:[_selectedApplications allObjects]]; 35 | } 36 | } 37 | 38 | - (void)prepareForPopulatingSections 39 | { 40 | [super prepareForPopulatingSections]; 41 | NSNumber* defaultApplicationValueNum = [[self specifier] propertyForKey:@"defaultApplicationSwitchValue"]; 42 | _defaultApplicationSwitchValue = [defaultApplicationValueNum boolValue]; 43 | } 44 | 45 | - (void)setApplicationEnabled:(NSNumber*)enabledNum specifier:(PSSpecifier*)specifier 46 | { 47 | NSString* applicationID = [specifier propertyForKey:@"applicationIdentifier"]; 48 | if([enabledNum boolValue] != _defaultApplicationSwitchValue) 49 | { 50 | [_selectedApplications addObject:applicationID]; 51 | } 52 | else 53 | { 54 | [_selectedApplications removeObject:applicationID]; 55 | } 56 | 57 | [self savePreferences]; 58 | } 59 | 60 | - (id)readApplicationEnabled:(PSSpecifier*)specifier 61 | { 62 | NSString* applicationID = [specifier propertyForKey:@"applicationIdentifier"]; 63 | BOOL applicationSelected = [_selectedApplications containsObject:applicationID]; 64 | 65 | if(applicationSelected) 66 | { 67 | return @(!_defaultApplicationSwitchValue); 68 | } 69 | 70 | return @(_defaultApplicationSwitchValue); 71 | } 72 | 73 | - (PSCellType)cellTypeForApplicationCells 74 | { 75 | return PSSwitchCell; 76 | } 77 | 78 | - (SEL)getterForSpecifierOfApplicationProxy:(LSApplicationProxy*)applicationProxy 79 | { 80 | return @selector(readApplicationEnabled:); 81 | } 82 | 83 | - (SEL)setterForSpecifierOfApplicationProxy:(LSApplicationProxy*)applicationProxy 84 | { 85 | return @selector(setApplicationEnabled:specifier:); 86 | } 87 | 88 | @end -------------------------------------------------------------------------------- /ATLApplicationListSelectionController.h: -------------------------------------------------------------------------------- 1 | #import "ATLApplicationListControllerBase.h" 2 | 3 | @interface ATLApplicationListSelectionController : ATLApplicationListControllerBase 4 | { 5 | NSString* _selectedApplicationID; 6 | } 7 | @end -------------------------------------------------------------------------------- /ATLApplicationListSelectionController.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "ATLApplicationListSelectionController.h" 3 | #import "PSSpecifier+AltList.h" 4 | 5 | @interface PSTableCell() 6 | - (void)setChecked:(BOOL)checked; 7 | @end 8 | 9 | @implementation ATLApplicationListSelectionController 10 | 11 | - (void)loadPreferences 12 | { 13 | PSSpecifier* specifier = [self specifier]; 14 | if([specifier atl_hasValidGetter]) 15 | { 16 | _selectedApplicationID = [specifier atl_performGetter]; 17 | } 18 | if(!_selectedApplicationID) 19 | { 20 | NSString* defaultValue = [specifier propertyForKey:@"default"]; 21 | if(defaultValue && [defaultValue isKindOfClass:[NSString class]]) 22 | { 23 | _selectedApplicationID = defaultValue; 24 | } 25 | } 26 | } 27 | 28 | - (void)savePreferences 29 | { 30 | PSSpecifier* specifier = [self specifier]; 31 | if([specifier atl_hasValidSetter]) 32 | { 33 | [specifier atl_performSetterWithValue:_selectedApplicationID]; 34 | } 35 | } 36 | 37 | - (PSTableCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath 38 | { 39 | PSTableCell* tableCell = (PSTableCell*)[super tableView:tableView cellForRowAtIndexPath:indexPath]; 40 | 41 | PSSpecifier* specifier = [tableCell specifier]; 42 | NSString* applicationID = [specifier propertyForKey:@"applicationIdentifier"]; 43 | 44 | [tableCell setChecked:[_selectedApplicationID isEqualToString:applicationID]]; 45 | 46 | return tableCell; 47 | } 48 | 49 | - (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath 50 | { 51 | if(_selectedApplicationID) 52 | { 53 | //deselect previously selected application if visible 54 | NSIndexPath* previousIndexPath = [self indexPathForApplicationWithIdentifier:_selectedApplicationID]; 55 | if([[tableView indexPathsForVisibleRows] containsObject:previousIndexPath]) 56 | { 57 | [tableView cellForRowAtIndexPath:previousIndexPath].accessoryType = UITableViewCellAccessoryNone; 58 | } 59 | } 60 | 61 | PSSpecifier* specifierOfCell = [self specifierAtIndex:[self indexForIndexPath:indexPath]]; 62 | _selectedApplicationID = [specifierOfCell propertyForKey:@"applicationIdentifier"]; 63 | 64 | [tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryCheckmark; 65 | 66 | [self savePreferences]; 67 | } 68 | 69 | - (void)viewWillDisappear:(BOOL)animated 70 | { 71 | PSListController* topVC = (PSListController*)self.navigationController.topViewController; 72 | if([topVC respondsToSelector:@selector(reloadSpecifier:)]) 73 | { 74 | [topVC reloadSpecifier:[self specifier]]; 75 | } 76 | } 77 | 78 | @end -------------------------------------------------------------------------------- /ATLApplicationListSubcontroller.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface ATLApplicationListSubcontroller : PSListController 4 | @property (nonatomic) NSString* applicationID; 5 | @end -------------------------------------------------------------------------------- /ATLApplicationListSubcontroller.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "ATLApplicationListSubcontroller.h" 3 | #import 4 | 5 | @implementation ATLApplicationListSubcontroller 6 | 7 | - (void)setSpecifier:(PSSpecifier*)specifier 8 | { 9 | [super setSpecifier:specifier]; 10 | self.applicationID = [specifier propertyForKey:@"applicationIdentifier"]; 11 | [self setTitle:specifier.name]; 12 | } 13 | 14 | - (NSMutableArray*)loadSpecifiersFromPlistName:(NSString*)plistName target:(id)target 15 | { 16 | NSMutableArray* specifiers = [super loadSpecifiersFromPlistName:plistName target:target]; 17 | if([self.title isEqualToString:@""] || !self.title) 18 | { 19 | [self setTitle:[self specifier].name]; 20 | } 21 | return specifiers; 22 | } 23 | 24 | - (void)viewWillDisappear:(BOOL)animated 25 | { 26 | // auto reload preview string in previous page 27 | PSListController* topVC = (PSListController*)self.navigationController.topViewController; 28 | if([topVC respondsToSelector:@selector(reloadSpecifier:)]) 29 | { 30 | [topVC reloadSpecifier:[self specifier]]; 31 | } 32 | } 33 | 34 | @end -------------------------------------------------------------------------------- /ATLApplicationListSubcontrollerController.h: -------------------------------------------------------------------------------- 1 | #import "ATLApplicationListControllerBase.h" 2 | 3 | @interface ATLApplicationListSubcontrollerController : ATLApplicationListControllerBase 4 | @property (nonatomic) Class subcontrollerClass; 5 | 6 | - (NSString*)previewStringForApplicationWithIdentifier:(NSString*)applicationID; 7 | 8 | @end -------------------------------------------------------------------------------- /ATLApplicationListSubcontrollerController.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "ATLApplicationListSubcontrollerController.h" 3 | #import "CoreServices.h" 4 | #import "LSApplicationProxy+AltList.h" 5 | 6 | @implementation ATLApplicationListSubcontrollerController 7 | 8 | - (NSString*)previewStringForApplicationWithIdentifier:(NSString*)applicationID 9 | { 10 | return nil; 11 | } 12 | 13 | - (NSString*)_previewStringForSpecifier:(PSSpecifier*)specifier 14 | { 15 | NSString* previewString = [self previewStringForApplicationWithIdentifier:[specifier propertyForKey:@"applicationIdentifier"]]; 16 | return previewString; 17 | } 18 | 19 | - (void)prepareForPopulatingSections 20 | { 21 | [super prepareForPopulatingSections]; 22 | NSString* subcontrollerClassString = [[self specifier] propertyForKey:@"subcontrollerClass"]; 23 | if(subcontrollerClassString) 24 | { 25 | self.subcontrollerClass = NSClassFromString(subcontrollerClassString); 26 | } 27 | } 28 | 29 | - (SEL)getterForSpecifierOfApplicationProxy:(LSApplicationProxy*)applicationProxy 30 | { 31 | return @selector(_previewStringForSpecifier:); 32 | } 33 | 34 | - (PSCellType)cellTypeForApplicationCells 35 | { 36 | return PSLinkListCell; 37 | } 38 | 39 | - (Class)detailControllerClassForSpecifierOfApplicationProxy:(LSApplicationProxy*)applicationProxy 40 | { 41 | return self.subcontrollerClass; 42 | } 43 | 44 | @end -------------------------------------------------------------------------------- /ATLApplicationSection.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | typedef NS_ENUM(NSInteger, ApplicationSectionType) { 4 | SECTION_TYPE_ALL, 5 | SECTION_TYPE_SYSTEM, 6 | SECTION_TYPE_USER, 7 | SECTION_TYPE_HIDDEN, 8 | SECTION_TYPE_VISIBLE, 9 | SECTION_TYPE_CUSTOM 10 | }; 11 | 12 | #define kApplicationSectionTypeAll @"All" 13 | #define kApplicationSectionTypeSystem @"System" 14 | #define kApplicationSectionTypeUser @"User" 15 | #define kApplicationSectionTypeHidden @"Hidden" 16 | #define kApplicationSectionTypeVisible @"Visible" 17 | #define kApplicationSectionTypeCustom @"Custom" 18 | 19 | @interface ATLApplicationSection : NSObject 20 | @property (nonatomic) ApplicationSectionType sectionType; 21 | @property (nonatomic) NSPredicate* customPredicate; 22 | @property (nonatomic) NSString* sectionName; 23 | 24 | @property (nonatomic) NSArray* applicationsInSection; 25 | 26 | + (ApplicationSectionType)sectionTypeFromString:(NSString*)typeString; 27 | + (NSString*)stringFromSectionType:(ApplicationSectionType)sectionType; 28 | 29 | + (__kindof ATLApplicationSection*)applicationSectionWithDictionary:(NSDictionary*)sectionDictionary; 30 | - (instancetype)_initWithDictionary:(NSDictionary*)sectionDictionary; 31 | - (instancetype)initNonCustomSectionWithType:(ApplicationSectionType)sectionType; 32 | - (instancetype)initCustomSectionWithPredicate:(NSPredicate*)predicate sectionName:(NSString*)sectionName; 33 | - (NSArray*)sortDescriptorsForApplications; 34 | - (void)populateFromAllApplications:(NSArray*)allApplications; 35 | 36 | @end -------------------------------------------------------------------------------- /ATLApplicationSection.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "ATLApplicationSection.h" 3 | 4 | @implementation ATLApplicationSection 5 | 6 | + (ApplicationSectionType)sectionTypeFromString:(NSString*)typeString 7 | { 8 | if([typeString isEqualToString:kApplicationSectionTypeAll]) 9 | { 10 | return SECTION_TYPE_ALL; 11 | } 12 | else if([typeString isEqualToString:kApplicationSectionTypeSystem]) 13 | { 14 | return SECTION_TYPE_SYSTEM; 15 | } 16 | else if([typeString isEqualToString:kApplicationSectionTypeUser]) 17 | { 18 | return SECTION_TYPE_USER; 19 | } 20 | else if([typeString isEqualToString:kApplicationSectionTypeHidden]) 21 | { 22 | return SECTION_TYPE_HIDDEN; 23 | } 24 | else if([typeString isEqualToString:kApplicationSectionTypeVisible]) 25 | { 26 | return SECTION_TYPE_VISIBLE; 27 | } 28 | 29 | return SECTION_TYPE_CUSTOM; 30 | } 31 | 32 | + (NSString*)stringFromSectionType:(ApplicationSectionType)sectionType 33 | { 34 | switch(sectionType) 35 | { 36 | case SECTION_TYPE_ALL: 37 | return kApplicationSectionTypeAll; 38 | case SECTION_TYPE_SYSTEM: 39 | return kApplicationSectionTypeSystem; 40 | case SECTION_TYPE_USER: 41 | return kApplicationSectionTypeUser; 42 | case SECTION_TYPE_HIDDEN: 43 | return kApplicationSectionTypeHidden; 44 | case SECTION_TYPE_VISIBLE: 45 | return kApplicationSectionTypeVisible; 46 | default: 47 | return kApplicationSectionTypeCustom; 48 | } 49 | } 50 | 51 | + (NSString*)sectionTitleForNonCustomSectionType:(ApplicationSectionType)sectionType 52 | { 53 | switch(sectionType) 54 | { 55 | case SECTION_TYPE_ALL: 56 | case SECTION_TYPE_VISIBLE: 57 | return @"Applications"; 58 | case SECTION_TYPE_SYSTEM: 59 | return @"System Applications"; 60 | case SECTION_TYPE_USER: 61 | return @"User Applications"; 62 | case SECTION_TYPE_HIDDEN: 63 | return @"Hidden Applications"; 64 | default: 65 | return nil; 66 | } 67 | } 68 | 69 | + (__kindof ATLApplicationSection*)applicationSectionWithDictionary:(NSDictionary*)sectionDictionary 70 | { 71 | NSString* customClassString = sectionDictionary[@"customClass"]; 72 | if(customClassString) 73 | { 74 | Class customClass = NSClassFromString(customClassString); 75 | return [[customClass alloc] _initWithDictionary:sectionDictionary]; 76 | } 77 | else 78 | { 79 | return [[ATLApplicationSection alloc] _initWithDictionary:sectionDictionary]; 80 | } 81 | } 82 | 83 | - (instancetype)_initWithDictionary:(NSDictionary*)sectionDictionary 84 | { 85 | NSString* sectionTypeString = sectionDictionary[@"sectionType"]; 86 | if(!sectionTypeString) return nil; 87 | 88 | ApplicationSectionType sectionType = [[self class] sectionTypeFromString:sectionTypeString]; 89 | 90 | if(sectionType == SECTION_TYPE_CUSTOM) 91 | { 92 | NSString* predicateString = sectionDictionary[@"sectionPredicate"]; 93 | NSPredicate* predicate = [NSPredicate predicateWithFormat:predicateString]; 94 | NSString* sectionName = sectionDictionary[@"sectionName"]; 95 | self = [self initCustomSectionWithPredicate:predicate sectionName:sectionName]; 96 | } 97 | else 98 | { 99 | self = [self initNonCustomSectionWithType:sectionType]; 100 | } 101 | 102 | return self; 103 | } 104 | 105 | - (instancetype)initNonCustomSectionWithType:(ApplicationSectionType)sectionType 106 | { 107 | self = [super init]; 108 | 109 | self.sectionType = sectionType; 110 | 111 | return self; 112 | } 113 | 114 | - (instancetype)initCustomSectionWithPredicate:(NSPredicate*)predicate sectionName:(NSString*)sectionName 115 | { 116 | self = [super init]; 117 | 118 | self.sectionType = SECTION_TYPE_CUSTOM; 119 | _customPredicate = predicate; 120 | _sectionName = sectionName; 121 | 122 | return self; 123 | } 124 | 125 | - (void)setSectionType:(ApplicationSectionType)sectionType 126 | { 127 | _sectionType = sectionType; 128 | if(_sectionType != SECTION_TYPE_CUSTOM) 129 | { 130 | _sectionName = [[self class] sectionTitleForNonCustomSectionType:sectionType]; 131 | } 132 | } 133 | 134 | - (NSArray*)sortDescriptorsForApplications 135 | { 136 | return @[[NSSortDescriptor sortDescriptorWithKey:@"atl_fastDisplayName" ascending:YES selector:@selector(localizedCaseInsensitiveCompare:)]]; 137 | } 138 | 139 | - (void)populateFromAllApplications:(NSArray*)allApplications 140 | { 141 | NSPredicate* predicateToUse; 142 | 143 | switch(_sectionType) 144 | { 145 | case SECTION_TYPE_ALL: 146 | break; 147 | 148 | case SECTION_TYPE_SYSTEM: 149 | predicateToUse = [NSPredicate predicateWithFormat:@"atl_isSystemApplication == YES"]; 150 | break; 151 | 152 | case SECTION_TYPE_USER: 153 | predicateToUse = [NSPredicate predicateWithFormat:@"atl_isUserApplication == YES"]; 154 | break; 155 | 156 | case SECTION_TYPE_HIDDEN: 157 | predicateToUse = [NSPredicate predicateWithFormat:@"atl_isHidden == YES"]; 158 | break; 159 | 160 | case SECTION_TYPE_VISIBLE: 161 | predicateToUse = [NSPredicate predicateWithFormat:@"atl_isHidden == NO"]; 162 | break; 163 | 164 | default: 165 | predicateToUse = _customPredicate; 166 | break; 167 | } 168 | 169 | NSArray* filteredApplications; 170 | if(predicateToUse) 171 | { 172 | filteredApplications = [allApplications filteredArrayUsingPredicate:predicateToUse]; 173 | } 174 | else 175 | { 176 | filteredApplications = allApplications; 177 | } 178 | 179 | NSArray* filteredAndSortedApplications = [filteredApplications sortedArrayUsingDescriptors:[self sortDescriptorsForApplications]]; 180 | _applicationsInSection = filteredAndSortedApplications; 181 | } 182 | 183 | @end -------------------------------------------------------------------------------- /ATLApplicationSelectionCell.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface ATLApplicationSelectionCell : PSTableCell 4 | 5 | @end -------------------------------------------------------------------------------- /ATLApplicationSelectionCell.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "ATLApplicationSelectionCell.h" 3 | #import "CoreServices.h" 4 | #import "LSApplicationProxy+AltList.h" 5 | 6 | @interface PSTableCell() 7 | - (void)setValue:(id)value; 8 | @end 9 | 10 | @implementation ATLApplicationSelectionCell 11 | 12 | - (void)setValue:(id)value 13 | { 14 | if([value isKindOfClass:[NSString class]]) 15 | { 16 | NSString* strValue = value; 17 | LSApplicationProxy* appProxy = [LSApplicationProxy applicationProxyForIdentifier:strValue]; 18 | if(appProxy) 19 | { 20 | [super setValue:[appProxy atl_nameToDisplay]]; 21 | return; 22 | } 23 | } 24 | 25 | [super setValue:value]; 26 | } 27 | 28 | @end -------------------------------------------------------------------------------- /ATLApplicationSubtitleCell.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface ATLApplicationSubtitleCell : PSTableCell 5 | { 6 | UILabel* _customValueLabel; 7 | } 8 | @end -------------------------------------------------------------------------------- /ATLApplicationSubtitleCell.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "ATLApplicationSubtitleCell.h" 3 | 4 | #import "ATLApplicationListControllerBase.h" 5 | 6 | @implementation ATLApplicationSubtitleCell 7 | 8 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString*)reuseIdentifier specifier:(PSSpecifier*)specifier 9 | { 10 | self = [super initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseIdentifier specifier:specifier]; 11 | 12 | if(self) 13 | { 14 | _customValueLabel = [UILabel new]; 15 | _customValueLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody]; 16 | 17 | BOOL isRTL = [UIApplication sharedApplication].userInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft; 18 | if(isRTL) 19 | { 20 | _customValueLabel.textAlignment = NSTextAlignmentLeft; 21 | } 22 | else 23 | { 24 | _customValueLabel.textAlignment = NSTextAlignmentRight; 25 | } 26 | 27 | _customValueLabel.numberOfLines = 1; 28 | 29 | if(@available(iOS 13, *)) 30 | { 31 | _customValueLabel.textColor = [UIColor secondaryLabelColor]; 32 | } 33 | else 34 | { 35 | _customValueLabel.textColor = [UIColor colorWithRed:0.5568 green:0.5568 blue:0.5764 alpha:1.0]; 36 | } 37 | 38 | [self.contentView addSubview:_customValueLabel]; 39 | _customValueLabel.hidden = YES; 40 | } 41 | 42 | return self; 43 | } 44 | 45 | - (void)layoutSubviews 46 | { 47 | [super layoutSubviews]; 48 | 49 | BOOL isRTL = [UIApplication sharedApplication].userInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft; 50 | 51 | if(isRTL) 52 | { 53 | CGFloat detailTextLeftPos = self.detailTextLabel.frame.origin.x; 54 | CGFloat textLeftPos = self.textLabel.frame.origin.x; 55 | CGFloat width = 0; 56 | if(detailTextLeftPos < textLeftPos) 57 | { 58 | width = detailTextLeftPos; 59 | } 60 | else 61 | { 62 | width = textLeftPos; 63 | } 64 | 65 | _customValueLabel.frame = CGRectMake(0, 0, width, self.contentView.bounds.size.height); 66 | } 67 | else 68 | { 69 | CGFloat detailTextRightPos = self.detailTextLabel.frame.origin.x + self.detailTextLabel.bounds.size.width; 70 | CGFloat textRightPos = self.textLabel.frame.origin.x + self.textLabel.bounds.size.width; 71 | CGFloat x = 0; 72 | if(detailTextRightPos > textRightPos) 73 | { 74 | x = detailTextRightPos; 75 | } 76 | else 77 | { 78 | x = textRightPos; 79 | } 80 | CGFloat width = self.contentView.bounds.size.width - x; 81 | _customValueLabel.frame = CGRectMake(x, 0, width, self.contentView.bounds.size.height); 82 | } 83 | } 84 | 85 | - (void)refreshCellContentsWithSpecifier:(PSSpecifier*)specifier 86 | { 87 | [super refreshCellContentsWithSpecifier:specifier]; 88 | id target = specifier.target; 89 | if([target respondsToSelector:@selector(_subtitleForSpecifier:)]) 90 | { 91 | self.detailTextLabel.text = [(ATLApplicationListControllerBase*)target _subtitleForSpecifier:specifier]; 92 | } 93 | } 94 | 95 | - (void)setValue:(id)value 96 | { 97 | if([value isKindOfClass:[NSString class]]) 98 | { 99 | NSString* valueStr = value; 100 | if(![valueStr isEqualToString:@""]) 101 | { 102 | _customValueLabel.hidden = NO; 103 | _customValueLabel.text = valueStr; 104 | return; 105 | } 106 | } 107 | 108 | _customValueLabel.hidden = YES; 109 | } 110 | 111 | - (void)prepareForReuse 112 | { 113 | [super prepareForReuse]; 114 | _customValueLabel.text = nil; 115 | } 116 | 117 | @end -------------------------------------------------------------------------------- /ATLApplicationSubtitleSwitchCell.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface ATLApplicationSubtitleSwitchCell : PSSwitchTableCell 5 | @end -------------------------------------------------------------------------------- /ATLApplicationSubtitleSwitchCell.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "ATLApplicationSubtitleSwitchCell.h" 3 | 4 | #import "ATLApplicationListControllerBase.h" 5 | 6 | @implementation ATLApplicationSubtitleSwitchCell 7 | 8 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString*)reuseIdentifier specifier:(PSSpecifier*)specifier 9 | { 10 | return [super initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseIdentifier specifier:specifier]; 11 | } 12 | 13 | - (void)refreshCellContentsWithSpecifier:(PSSpecifier*)specifier 14 | { 15 | [super refreshCellContentsWithSpecifier:specifier]; 16 | id target = specifier.target; 17 | if([target respondsToSelector:@selector(_subtitleForSpecifier:)]) 18 | { 19 | self.detailTextLabel.text = [(ATLApplicationListControllerBase*)target _subtitleForSpecifier:specifier]; 20 | } 21 | } 22 | 23 | @end -------------------------------------------------------------------------------- /AltList.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | #import 5 | #import 6 | #import 7 | #import 8 | #import 9 | #import -------------------------------------------------------------------------------- /AltList.x: -------------------------------------------------------------------------------- 1 | #import 2 | #import "CoreServices.h" 3 | #import "LSApplicationProxy+AltList.h" 4 | #import 5 | 6 | NSString *safe_getExecutablePath(void) 7 | { 8 | char executablePathC[PATH_MAX]; 9 | uint32_t executablePathCSize = sizeof(executablePathC); 10 | _NSGetExecutablePath(&executablePathC[0], &executablePathCSize); 11 | return [NSString stringWithUTF8String:executablePathC]; 12 | } 13 | 14 | //Pre heat display names 15 | %ctor 16 | { 17 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 18 | [[[LSApplicationWorkspace defaultWorkspace] atl_allInstalledApplications] enumerateObjectsUsingBlock:^(LSApplicationProxy* proxy, NSUInteger idx, BOOL *stop) 19 | { 20 | [proxy atl_fastDisplayName]; 21 | }]; 22 | }); 23 | } -------------------------------------------------------------------------------- /AltListTestBundlelessPreferences/Makefile: -------------------------------------------------------------------------------- 1 | ifeq ($(THEOS_PACKAGE_SCHEME),rootless) 2 | TARGET := iphone:clang:16.2:15.0 3 | else 4 | TARGET := iphone:clang:14.5:7.0 5 | endif 6 | 7 | include $(THEOS)/makefiles/common.mk 8 | 9 | include $(THEOS_MAKE_PATH)/bundle.mk 10 | -------------------------------------------------------------------------------- /AltListTestBundlelessPreferences/layout/Library/PreferenceLoader/Preferences/AltListTestBundlelessPreferences.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | entry 6 | 7 | bundle 8 | AltList 9 | cell 10 | PSLinkListCell 11 | detail 12 | ATLApplicationListMultiSelectionController 13 | key 14 | selectedApplications 15 | defaults 16 | com.opa334.altlisttestbundlelesspreferences 17 | sections 18 | 19 | 20 | sectionType 21 | Visible 22 | 23 | 24 | isController 25 | 26 | overridePrincipalClass 27 | 28 | label 29 | AltListTestBundlelessPreferences 30 | useSearchBar 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /AltListTestPreferences/APPRootListController.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface APPRootListController : PSListController 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /AltListTestPreferences/APPRootListController.m: -------------------------------------------------------------------------------- 1 | #include "APPRootListController.h" 2 | 3 | @implementation APPRootListController 4 | 5 | - (NSArray *)specifiers { 6 | if (!_specifiers) { 7 | _specifiers = [self loadSpecifiersFromPlistName:@"Root" target:self]; 8 | } 9 | 10 | return _specifiers; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /AltListTestPreferences/Makefile: -------------------------------------------------------------------------------- 1 | ifeq ($(THEOS_PACKAGE_SCHEME),rootless) 2 | TARGET := iphone:clang:16.2:15.0 3 | else 4 | TARGET := iphone:clang:14.5:7.0 5 | endif 6 | 7 | include $(THEOS)/makefiles/common.mk 8 | 9 | BUNDLE_NAME = AltListTestPreferences 10 | 11 | AltListTestPreferences_FILES = APPRootListController.m 12 | AltListTestPreferences_FRAMEWORKS = UIKit 13 | AltListTestPreferences_PRIVATE_FRAMEWORKS = Preferences 14 | AltListTestPreferences_EXTRA_FRAMEWORKS = AltList 15 | AltListTestPreferences_INSTALL_PATH = /Library/PreferenceBundles 16 | AltListTestPreferences_CFLAGS = -fobjc-arc 17 | 18 | include $(THEOS_MAKE_PATH)/bundle.mk -------------------------------------------------------------------------------- /AltListTestPreferences/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | AltListTestPreferences 9 | CFBundleIdentifier 10 | com.opa334.altlisttestpreferences 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1.0 21 | NSPrincipalClass 22 | APPRootListController 23 | 24 | 25 | -------------------------------------------------------------------------------- /AltListTestPreferences/Resources/Root.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | items 6 | 7 | 8 | cell 9 | PSGroupCell 10 | label 11 | AltListTestPreferences First Page 12 | 13 | 14 | cell 15 | PSLinkListCell 16 | defaults 17 | com.opa334.altlisttestpreferences 18 | label 19 | Test 1 20 | detail 21 | ATLApplicationListControllerBase 22 | sections 23 | 24 | 25 | sectionType 26 | System 27 | 28 | 29 | sectionType 30 | User 31 | 32 | 33 | sectionType 34 | Hidden 35 | 36 | 37 | 38 | 39 | cell 40 | PSLinkListCell 41 | defaults 42 | com.opa334.altlisttestpreferences 43 | label 44 | Test 2 45 | detail 46 | ATLApplicationListSelectionController 47 | key 48 | selectedApplicationFromTest2 49 | cellClass 50 | ATLApplicationSelectionCell 51 | sections 52 | 53 | 54 | sectionType 55 | System 56 | 57 | 58 | sectionType 59 | User 60 | 61 | 62 | sectionType 63 | Hidden 64 | 65 | 66 | useSearchBar 67 | 68 | hideSearchBarWhileScrolling 69 | 70 | showIdentifiersAsSubtitle 71 | 72 | includeIdentifiersInSearch 73 | 74 | 75 | 76 | cell 77 | PSLinkListCell 78 | defaults 79 | com.opa334.altlisttestpreferences 80 | label 81 | Test 3 82 | detail 83 | ATLApplicationListMultiSelectionController 84 | key 85 | selectedApplicationsFromTest3 86 | sections 87 | 88 | 89 | sectionType 90 | System 91 | 92 | 93 | sectionType 94 | User 95 | 96 | 97 | sectionType 98 | Hidden 99 | 100 | 101 | showIdentifiersAsSubtitle 102 | 103 | defaultApplicationSwitchValue 104 | 105 | useSearchBar 106 | 107 | 108 | 109 | cell 110 | PSLinkListCell 111 | defaults 112 | com.opa334.altlisttestpreferences 113 | label 114 | Test 4 115 | detail 116 | ATLApplicationListMultiSelectionController 117 | key 118 | selectedApplicationsFromTest3 119 | sections 120 | 121 | 122 | sectionType 123 | System 124 | 125 | 126 | sectionType 127 | User 128 | 129 | 130 | sectionType 131 | Hidden 132 | 133 | 134 | showIdentifiersAsSubtitle 135 | 136 | defaultApplicationSwitchValue 137 | 138 | useSearchBar 139 | 140 | 141 | 142 | cell 143 | PSLinkListCell 144 | defaults 145 | com.opa334.altlisttestpreferences 146 | label 147 | Test 5 148 | detail 149 | ATLApplicationListMultiSelectionController 150 | key 151 | selectedApplicationsFromTest3 152 | sections 153 | 154 | 155 | sectionType 156 | All 157 | 158 | 159 | showIdentifiersAsSubtitle 160 | 161 | defaultApplicationSwitchValue 162 | 163 | useSearchBar 164 | 165 | 166 | 167 | cell 168 | PSLinkListCell 169 | defaults 170 | com.opa334.altlisttestpreferences 171 | label 172 | Test 6 173 | detail 174 | ATLApplicationListMultiSelectionController 175 | key 176 | selectedApplicationsFromTest3 177 | sections 178 | 179 | 180 | sectionType 181 | Visible 182 | 183 | 184 | showIdentifiersAsSubtitle 185 | 186 | defaultApplicationSwitchValue 187 | 188 | useSearchBar 189 | 190 | 191 | 192 | cell 193 | PSLinkListCell 194 | defaults 195 | com.opa334.altlisttestpreferences 196 | label 197 | Test 7 198 | detail 199 | ATLApplicationListMultiSelectionController 200 | key 201 | selectedApplicationsFromTest3 202 | sections 203 | 204 | 205 | sectionType 206 | Visible 207 | 208 | 209 | showIdentifiersAsSubtitle 210 | 211 | defaultApplicationSwitchValue 212 | 213 | useSearchBar 214 | 215 | alphabeticIndexingEnabled 216 | 217 | hideAlphabeticSectionHeaders 218 | 219 | 220 | 221 | cell 222 | PSLinkListCell 223 | defaults 224 | com.opa334.altlisttestpreferences 225 | label 226 | Test 7.5 227 | detail 228 | ATLApplicationListMultiSelectionController 229 | key 230 | selectedApplicationsFromTest3 231 | sections 232 | 233 | 234 | sectionType 235 | Visible 236 | 237 | 238 | sectionType 239 | Hidden 240 | 241 | 242 | showIdentifiersAsSubtitle 243 | 244 | defaultApplicationSwitchValue 245 | 246 | useSearchBar 247 | 248 | alphabeticIndexingEnabled 249 | 250 | hideAlphabeticSectionHeaders 251 | 252 | 253 | 254 | cell 255 | PSLinkListCell 256 | defaults 257 | com.opa334.altlisttestpreferences 258 | label 259 | Custom Type + Localization 260 | detail 261 | ATLApplicationListMultiSelectionController 262 | key 263 | selectedApplicationsFromTest3 264 | sections 265 | 266 | 267 | sectionType 268 | Custom 269 | sectionName 270 | Supports Multiple Windows 271 | sectionPredicate 272 | supportsMultiwindow == YES 273 | 274 | 275 | sectionType 276 | Custom 277 | sectionName 278 | Supports Multiple Windows 279 | sectionPredicate 280 | supportsMultiwindow == NO 281 | 282 | 283 | showIdentifiersAsSubtitle 284 | 285 | defaultApplicationSwitchValue 286 | 287 | useSearchBar 288 | 289 | localizationBundlePath 290 | /Library/Application Support/AltListTest.bundle 291 | 292 | 293 | cell 294 | PSLinkListCell 295 | label 296 | Test 8 297 | detail 298 | ATLApplicationListSubcontrollerController 299 | sections 300 | 301 | 302 | sectionType 303 | Visible 304 | 305 | 306 | showIdentifiersAsSubtitle 307 | 308 | useSearchBar 309 | 310 | alphabeticIndexingEnabled 311 | 312 | hideAlphabeticSectionHeaders 313 | 314 | 315 | 316 | title 317 | AltListTestPreferences 318 | 319 | 320 | -------------------------------------------------------------------------------- /AltListTestPreferences/layout/Library/Application Support/AltListTest.bundle/de.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "Supports Multiple Windows" = "Unterstützt mehrere Fenster"; -------------------------------------------------------------------------------- /AltListTestPreferences/layout/Library/Application Support/AltListTest.bundle/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "Supports Multiple Windows" = "Supports Multiple Windows"; 2 | -------------------------------------------------------------------------------- /AltListTestPreferences/layout/Library/PreferenceLoader/Preferences/AppPrefsTestBundle.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | entry 6 | 7 | bundle 8 | AltListTestPreferences 9 | cell 10 | PSLinkCell 11 | detail 12 | APPRootListController 13 | icon 14 | icon.png 15 | isController 16 | 17 | label 18 | AltListTestPreferences 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /CoreServices.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface LSApplicationRecord : NSObject 5 | @property (nonatomic,readonly) NSArray* appTags; // 'hidden' 6 | @property (getter=isLaunchProhibited,readonly) BOOL launchProhibited; 7 | @end 8 | 9 | @interface LSApplicationProxy (Additions) 10 | @property (nonatomic,readonly) NSString* localizedName; 11 | @property (nonatomic,readonly) NSString* applicationType; // (User/System) 12 | @property (nonatomic,readonly) NSArray* appTags; // 'hidden' 13 | @property (getter=isLaunchProhibited,nonatomic,readonly) BOOL launchProhibited; 14 | + (instancetype)applicationProxyForIdentifier:(NSString*)identifier; 15 | - (LSApplicationRecord*)correspondingApplicationRecord; 16 | @end 17 | 18 | @interface LSApplicationWorkspace (Additions) 19 | - (void)addObserver:(id)arg1; 20 | - (void)removeObserver:(id)arg1; 21 | - (void)enumerateApplicationsOfType:(NSUInteger)type block:(void (^)(LSApplicationProxy*))block; 22 | @end -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Lars Fröder 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /LSApplicationProxy+AltList.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface LSApplicationProxy (AltList) 5 | - (BOOL)atl_isSystemApplication; 6 | - (BOOL)atl_isUserApplication; 7 | - (BOOL)atl_isHidden; 8 | - (NSString*)atl_fastDisplayName; 9 | - (NSString*)atl_nameToDisplay; 10 | @property (nonatomic,readonly) NSString* atl_bundleIdentifier; 11 | @end 12 | 13 | @interface LSApplicationWorkspace (AltList) 14 | - (NSArray*)atl_allInstalledApplications; 15 | @end -------------------------------------------------------------------------------- /LSApplicationProxy+AltList.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "CoreServices.h" 3 | #import "LSApplicationProxy+AltList.h" 4 | 5 | extern NSString *safe_getExecutablePath(void); 6 | 7 | @implementation LSApplicationProxy (AltList) 8 | 9 | - (BOOL)atl_isSystemApplication 10 | { 11 | return [self.applicationType isEqualToString:@"System"] && ![self atl_isHidden]; 12 | } 13 | 14 | - (BOOL)atl_isUserApplication 15 | { 16 | return [self.applicationType isEqualToString:@"User"] && ![self atl_isHidden]; 17 | } 18 | 19 | // the tag " hidden " is also valid, so we need to check if any strings contain "hidden" instead 20 | BOOL tagArrayContainsTag(NSArray* tagArr, NSString* tag) 21 | { 22 | if(!tagArr || !tag) return NO; 23 | 24 | __block BOOL found = NO; 25 | 26 | [tagArr enumerateObjectsUsingBlock:^(NSString* tagToCheck, NSUInteger idx, BOOL* stop) 27 | { 28 | if(![tagToCheck isKindOfClass:[NSString class]]) 29 | { 30 | return; 31 | } 32 | 33 | if([tagToCheck rangeOfString:tag options:0].location != NSNotFound) 34 | { 35 | found = YES; 36 | *stop = YES; 37 | } 38 | }]; 39 | 40 | return found; 41 | } 42 | 43 | // always returns NO on iOS 7 44 | - (BOOL)atl_isHidden 45 | { 46 | NSArray* appTags; 47 | NSArray* recordAppTags; 48 | NSArray* sbAppTags; 49 | 50 | BOOL launchProhibited = NO; 51 | 52 | if([self respondsToSelector:@selector(correspondingApplicationRecord)]) 53 | { 54 | // On iOS 14, self.appTags is always empty but the application record still has the correct ones 55 | LSApplicationRecord* record = [self correspondingApplicationRecord]; 56 | recordAppTags = record.appTags; 57 | launchProhibited = record.launchProhibited; 58 | } 59 | if([self respondsToSelector:@selector(appTags)]) 60 | { 61 | appTags = self.appTags; 62 | } 63 | if(!launchProhibited && [self respondsToSelector:@selector(isLaunchProhibited)]) 64 | { 65 | launchProhibited = self.launchProhibited; 66 | } 67 | 68 | NSURL* bundleURL = self.bundleURL; 69 | if(bundleURL && [bundleURL checkResourceIsReachableAndReturnError:nil]) 70 | { 71 | NSBundle* bundle = [NSBundle bundleWithURL:bundleURL]; 72 | sbAppTags = [bundle objectForInfoDictionaryKey:@"SBAppTags"]; 73 | } 74 | 75 | BOOL isWebApplication = ([self.atl_bundleIdentifier rangeOfString:@"com.apple.webapp" options:NSCaseInsensitiveSearch].location != NSNotFound); 76 | return tagArrayContainsTag(appTags, @"hidden") || tagArrayContainsTag(recordAppTags, @"hidden") || tagArrayContainsTag(sbAppTags, @"hidden") || isWebApplication || launchProhibited; 77 | } 78 | 79 | // Getting the display name is slow (up to 2ms) because it uses an IPC call 80 | // this stacks up if you do it for every single application 81 | // This method provides a faster way (around 0.5ms) to get the display name 82 | // This reduces the overall time needed to sort the applications from ~230 to ~120ms on my test device 83 | - (NSString*)atl_fastDisplayName 84 | { 85 | NSString* cachedDisplayName = [self valueForKey:@"_localizedName"]; 86 | if(cachedDisplayName && ![cachedDisplayName isEqualToString:@""]) 87 | { 88 | return cachedDisplayName; 89 | } 90 | 91 | NSString* localizedName; 92 | 93 | NSURL* bundleURL = self.bundleURL; 94 | if(!bundleURL || ![bundleURL checkResourceIsReachableAndReturnError:nil]) 95 | { 96 | localizedName = self.localizedName; 97 | } 98 | else 99 | { 100 | NSBundle* bundle = [NSBundle bundleWithURL:bundleURL]; 101 | 102 | localizedName = [bundle objectForInfoDictionaryKey:@"CFBundleDisplayName"]; 103 | if(![localizedName isKindOfClass:[NSString class]]) localizedName = nil; 104 | if(!localizedName || [localizedName isEqualToString:@""]) 105 | { 106 | localizedName = [bundle objectForInfoDictionaryKey:@"CFBundleName"]; 107 | if(![localizedName isKindOfClass:[NSString class]]) localizedName = nil; 108 | if(!localizedName || [localizedName isEqualToString:@""]) 109 | { 110 | localizedName = [bundle objectForInfoDictionaryKey:@"CFBundleExecutable"]; 111 | if(![localizedName isKindOfClass:[NSString class]]) localizedName = nil; 112 | if(!localizedName || [localizedName isEqualToString:@""]) 113 | { 114 | //last possible fallback: use slow IPC call 115 | localizedName = self.localizedName; 116 | } 117 | } 118 | } 119 | } 120 | 121 | [self setValue:localizedName forKey:@"_localizedName"]; 122 | return localizedName; 123 | } 124 | 125 | - (NSString*)atl_nameToDisplay 126 | { 127 | NSString* localizedName = [self atl_fastDisplayName]; 128 | 129 | if([self.atl_bundleIdentifier rangeOfString:@"carplay" options:NSCaseInsensitiveSearch].location != NSNotFound) 130 | { 131 | if([localizedName rangeOfString:@"carplay" options:NSCaseInsensitiveSearch range:NSMakeRange(0, localizedName.length) locale:[NSLocale currentLocale]].location == NSNotFound) 132 | { 133 | return [localizedName stringByAppendingString:@" (CarPlay)"]; 134 | } 135 | } 136 | 137 | return localizedName; 138 | } 139 | 140 | -(id)atl_bundleIdentifier 141 | { 142 | // iOS 8-14 143 | if([self respondsToSelector:@selector(bundleIdentifier)]) 144 | { 145 | return [self bundleIdentifier]; 146 | } 147 | // iOS 7 148 | else 149 | { 150 | return [self applicationIdentifier]; 151 | } 152 | } 153 | 154 | @end 155 | 156 | @implementation LSApplicationWorkspace (AltList) 157 | 158 | - (NSArray*)atl_allInstalledApplications 159 | { 160 | NSString *selfExecutable = safe_getExecutablePath(); 161 | if ([selfExecutable isEqualToString:@"/usr/libexec/lsd"]) { 162 | // Prevent this from ever getting called inside lsd itself 163 | // Otherwise it could cause a crash 164 | return nil; 165 | } 166 | 167 | if(![self respondsToSelector:@selector(enumerateApplicationsOfType:block:)]) 168 | { 169 | return [self allInstalledApplications]; 170 | } 171 | 172 | NSMutableArray* installedApplications = [NSMutableArray new]; 173 | [self enumerateApplicationsOfType:0 block:^(LSApplicationProxy* appProxy) 174 | { 175 | [installedApplications addObject:appProxy]; 176 | }]; 177 | [self enumerateApplicationsOfType:1 block:^(LSApplicationProxy* appProxy) 178 | { 179 | [installedApplications addObject:appProxy]; 180 | }]; 181 | return installedApplications; 182 | } 183 | 184 | @end -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifeq ($(THEOS_PACKAGE_SCHEME),rootless) 2 | TARGET := iphone:clang:16.5:15.0 3 | else 4 | TARGET := iphone:clang:14.5:7.0 5 | endif 6 | 7 | include $(THEOS)/makefiles/common.mk 8 | 9 | FRAMEWORK_NAME = AltList 10 | 11 | AltList_FILES = AltList.x $(wildcard *.m) 12 | AltList_PUBLIC_HEADERS = ATLApplicationListControllerBase.h ATLApplicationListMultiSelectionController.h ATLApplicationListSelectionController.h ATLApplicationListSubcontroller.h ATLApplicationListSubcontrollerController.h ATLApplicationSection.h ATLApplicationSelectionCell.h ATLApplicationSubtitleCell.h ATLApplicationSubtitleSwitchCell.h LSApplicationProxy+AltList.h 13 | AltList_INSTALL_PATH = /Library/Frameworks 14 | AltList_CFLAGS = -fobjc-arc -Wno-tautological-pointer-compare 15 | ifeq ($(THEOS_PACKAGE_SCHEME),rootless) 16 | AltList_LDFLAGS += -install_name @rpath/AltList.framework/AltList 17 | endif 18 | AltList_FRAMEWORKS = MobileCoreServices 19 | AltList_PRIVATE_FRAMEWORKS = Preferences 20 | 21 | after-AltList-stage:: 22 | @ln -s $(THEOS_PACKAGE_INSTALL_PREFIX)/Library/Frameworks/AltList.framework $(THEOS_STAGING_DIR)/Library/PreferenceBundles/AltList.bundle 23 | 24 | include $(THEOS_MAKE_PATH)/framework.mk 25 | ifeq ($(PACKAGE_BUILDNAME),debug) 26 | SUBPROJECTS += AltListTestPreferences 27 | SUBPROJECTS += AltListTestBundlelessPreferences 28 | endif 29 | include $(THEOS_MAKE_PATH)/aggregate.mk 30 | -------------------------------------------------------------------------------- /PSSpecifier+AltList.h: -------------------------------------------------------------------------------- 1 | @interface PSSpecifier (AltList) 2 | - (BOOL)atl_hasValidGetter; 3 | - (id)atl_performGetter; 4 | - (BOOL)atl_hasValidSetter; 5 | - (void)atl_performSetterWithValue:(id)value; 6 | @end -------------------------------------------------------------------------------- /PSSpecifier+AltList.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PSSpecifier : NSObject 3 | { 4 | @public 5 | id target; 6 | SEL getter; 7 | SEL setter; 8 | } 9 | - (BOOL)hasValidGetter; 10 | - (id)performGetter; 11 | - (BOOL)hasValidSetter; 12 | - (void)performSetterWithValue:(id)value; 13 | @end 14 | #import "PSSpecifier+AltList.h" 15 | 16 | @implementation PSSpecifier (AltList) 17 | 18 | - (BOOL)atl_hasValidGetter 19 | { 20 | if([self respondsToSelector:@selector(hasValidGetter)]) 21 | { 22 | return [self hasValidGetter]; 23 | } 24 | else 25 | { 26 | if(getter && target) 27 | { 28 | return [target respondsToSelector:getter]; 29 | } 30 | else 31 | { 32 | return NO; 33 | } 34 | } 35 | } 36 | 37 | - (id)atl_performGetter 38 | { 39 | if([self respondsToSelector:@selector(performGetter)]) 40 | { 41 | return [self performGetter]; 42 | } 43 | else 44 | { 45 | if([self atl_hasValidGetter]) 46 | { 47 | return [((id (*)(id, SEL, id))[target methodForSelector:getter])(target, getter, self) mutableCopy]; 48 | } 49 | return nil; 50 | } 51 | } 52 | 53 | - (BOOL)atl_hasValidSetter 54 | { 55 | if([self respondsToSelector:@selector(hasValidSetter)]) 56 | { 57 | return [self hasValidSetter]; 58 | } 59 | else 60 | { 61 | if(setter && target) 62 | { 63 | return [target respondsToSelector:setter]; 64 | } 65 | else 66 | { 67 | return NO; 68 | } 69 | } 70 | } 71 | 72 | - (void)atl_performSetterWithValue:(id)value 73 | { 74 | if([self respondsToSelector:@selector(performSetterWithValue:)]) 75 | { 76 | [self performSetterWithValue:value]; 77 | } 78 | else 79 | { 80 | if([self atl_hasValidSetter]) 81 | { 82 | ((void (*)(id, SEL, id, id))[target methodForSelector:setter])(target, setter, value, self); 83 | } 84 | } 85 | } 86 | @end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AltList 2 | A modern AppList alternative 3 | 4 | The main focus of this dependency is to be an easy to use and easy to customize framework to handle per app preferences. 5 | Unlike AppLists `ALApplicationList` class, AltList does not have a way to get installed applications yourself, as stock iOS classes like [`LSApplicationWorkspace`](https://developer.limneos.net/?ios=13.1.3&framework=CoreServices.framework&header=LSApplicationWorkspace.h) and [`LSApplicationProxy`](https://developer.limneos.net/?ios=13.1.3&framework=CoreServices.framework&header=LSApplicationProxy.h) (MobileCoreService / CoreServices framework) can already do that. Communication to SpringBoard is not needed for this. 6 | 7 | Example uses: 8 | ```objc 9 | // get LSApplicationProxy of all installed applications 10 | NSArray* allInstalledApplications = [[LSApplicationWorkspace defaultWorkspace] atl_allInstalledApplications]; 11 | 12 | // get LSApplicationProxy for one application 13 | LSApplicationProxy* appProxy = [LSApplicationProxy applicationProxyForIdentifier:@"com.yourapp.yourapp"]; 14 | ``` 15 | 16 | ## Features 17 | * Uses `LSApplicationWorkspace`, not dependent on RocketBootstrap / SpringBoard injection 18 | * Supports search bars 19 | * Supports application sections (similar to AppList) 20 | * Supports alphabetic indexing if only one section is specified 21 | * Doesn't reinvent the wheel 22 | * Supports iOS 7 and up 23 | 24 | ## Installation 25 | Run [install_to_theos.sh](install_to_theos.sh) and add it to the makefile of your project: 26 | ``` 27 | _EXTRA_FRAMEWORKS = AltList 28 | ``` 29 | Then you can import it using `#import ` or just use the classes below in your preferences plist. 30 | 31 | ## Documentation 32 | 33 | AltList features three `PSListController` subclasses that can be used from your preference bundle or application. 34 | Unlike AppList it is highly customizable, so if you want to adapt something you can just make a subclass and specify that instead. 35 | 36 | All classes can be configured via values in your plist. 37 | 38 | ### Plist-Only approach 39 | 40 | For very simple preferences, it is possible to use AltList straight from your entry.plist without implementing any class. For an example check out [AltListTestBundlelessPreferences](AltListTestBundlelessPreferences/layout/Library/PreferenceLoader/Preferences/AltListTestBundlelessPreferences.plist). 41 | 42 | ### ATLApplicationSection 43 | 44 | Just like the original AppList, AltList allows you to specify the sections of applications it should display. 45 | 46 | AltList ships with a few stock section types: 47 | * All: All Applications, including hidden ones 48 | * System: System Applications (e.g. App Store, Safari) 49 | * User: User installed applications (e.g. Reddit, Instagram) 50 | * Hidden: Hidden applications (e.g. InCallService, Carplay Settings) 51 | * Visible: All applications that are not hidden (e.g. System and User applications) 52 | 53 | The stock section types already have predicates and localized names. 54 | Custom sections are also supported and allow you to specify your own section name and predicate. 55 | If you want your custom section name to be localized: the value you set will be passed to the `localizedStringForString` of your ListController (see below). 56 | 57 | | Key | Type | Fallback | Usage | 58 | | ------------------ | ------ | -------- | ------------ | 59 | | `sectionType` | String (All/System/User/Hidden/Visible/Custom) | Visible | Type of the section, see above | 60 | | `sectionName` | String | @"" | For custom sections, name of the section | 61 | | `sectionPredicate` | String | @"" | For custom sections, predicate to filter the applications, check out the [`LSApplicationProxy` headers](https://developer.limneos.net/?ios=14.4&framework=CoreServices.framework&header=LSApplicationProxy.h) for possible values to use | 62 | | `customClass` | String | @"" | Custom subclass of `ATLApplicationSection` to use, in case you want to implement even more custom behaviour | 63 | 64 | ### ATLApplicationListControllerBase 65 | 66 | ATLApplicationListControllerBase is the base class inherited by the other classes, it has several features that apply to all classes below. 67 | 68 | #### Keys 69 | 70 | | Key | Type | Fallback | Usage | 71 | | ------------------------------ | ------- | ------------------- | ------------ | 72 | | `sections` | Array | One Visible section | Array of dictionaries that represent the sections in which the applications are shown | 73 | | `useSearchBar` | Boolean | false | Whether there should be a search bar at the top that allows to search for applications [(Example)](.images/1.PNG?raw=true) | 74 | | `hideSearchBarWhileScrolling` | Boolean | false | When `useSearchBar` is enabled, whether the search bar should be hidden while scrolling (Always true on iOS 10 and below) [(Example)](.images/2.PNG?raw=true) | 75 | | `includeIdentifiersInSearch` | Boolean | false | When `useSearchBar` is enabled, whether it should be possible to search for apps by their identifier. When this is false, it is only possible to search for apps by their name. 76 | | `showIdentifiersAsSubtitle` | Boolean | false | Whether the application identifiers should be shown in the subtitle [(Example)](.images/3.PNG?raw=true) | 77 | | `alphabeticIndexingEnabled` | Boolean | false | When there is only one section, whether to section and index it by the starting letters [(Example)](.images/4.PNG?raw=true) | 78 | | `hideAlphabeticSectionHeaders` | Boolean | false | When `alphabeticIndexingEnabled` is true, whether to hide the sections that contain the first letters [(Example)](.images/5.PNG?raw=true) | 79 | | `localizationBundlePath` | String | @"" | Path to the bundle that should be used for localizing custom section titles | 80 | 81 | #### Methods (Can be overwritten by subclasses for customization) 82 | 83 | | Method | Purpose | 84 | | ------------------------------------------------------------------------------------------ | ------------ | 85 | | `- (void)loadPreferences` | Load the preference value that the list controller will display | 86 | | `- (void)savePreferences` | Save preferences when they have changed | 87 | | `- (void)prepareForPopulatingSections` | Initialize stuff that needs to be done before the populating starts | 88 | | `- (NSString*)localizedStringForString:(NSString*)string` | Localize string if possible from internal AltList bundle or the localization bundle specified by localizationBundlePath | 89 | | `- (void)reloadApplications` | Reload applications and specifiers | 90 | | `- (BOOL)shouldHideApplicationSpecifiers` | Whether any specifier at all should be hidden (internally used for search bar) | 91 | | `- (BOOL)shouldHideApplicationSpecifier:(PSSpecifier*)specifier` | Whether the specifier `specifier` should be hidden (internally used for search bar) | 92 | | `- (BOOL)shouldShowSubtitles` | Whether subtitles should be shown on the application cells | 93 | | `- (NSString*)subtitleForApplicationWithIdentifier:(NSString*)applicationID` | The subtitle that should be displayed in the cell for the specific application that's passed as `applicationID` | 94 | | `- (PSCellType)cellTypeForApplicationCells` | Cell type for the application cells | 95 | | `- (Class)customCellClassForCellType:(PSCellType)cellType` | Custom cell class for the application cells | 96 | | `- (Class)detailControllerClassForSpecifierOfApplicationProxy:(LSApplicationProxy*)applicationProxy` | detailControllerClass to be used by the application specifiers | 97 | | `- (SEL)getterForSpecifierOfApplicationProxy:(LSApplicationProxy*)applicationProxy` | getter to be used by the application specifiers | 98 | | `- (SEL)setterForSpecifierOfApplicationProxy:(LSApplicationProxy*)applicationProxy` | setter to be used by the application specifiers | 99 | | `- (PSSpecifier*)createSpecifierForApplicationProxy:(LSApplicationProxy*)applicationProxy` | Create a specifier for the application represented by `applicationProxy` | 100 | | `- (NSArray*)createSpecifiersForApplicationSection:(ATLApplicationSection*)section` | Create the specicifers for a whole application section represented by `section` (Calls the method above) | 101 | 102 | ### ATLApplicationListSelectionController 103 | 104 | ATLApplicationListSelectionController can be used as the detail class of a PSLinkListCell to have a list of applications of which one can be selected. 105 | The selected applications bundle identifier will be saved in the preference domain specified via `defaults` under the specified `key`. 106 | It respects the `get` / `set` attributes so by default it will use the `readPreferenceValue:` and `setPreferenceValue:specifier:` methods of the PSListController that contains the cell for reading / writing the value. 107 | Setting the cellClass to `ATLApplicationSelectionCell` is recommended so the value preview shows the application name instead of the application bundle identifier. 108 | 109 | #### Example 110 | ```xml 111 | 112 | cell 113 | PSLinkListCell 114 | detail 115 | ATLApplicationListSelectionController 116 | cellClass 117 | ATLApplicationSelectionCell 118 | defaults 119 | com.yourcompany.yourtweakprefs 120 | key 121 | (...) 122 | label 123 | (...) 124 | sections 125 | 126 | 127 | sectionType 128 | System 129 | 130 | 131 | sectionType 132 | User 133 | 134 | 135 | useSearchBar 136 | 137 | hideSearchBarWhileScrolling 138 | 139 | 140 | ``` 141 | 142 | ### ATLApplicationListMultiSelectionController 143 | 144 | ATLApplicationListSelectionController can be used as the detail class of a PSLinkListCell to have a list of applications where each has a switch next to it. 145 | The key `defaultApplicationSwitchValue` can be set to a boolean and will be used as the default value of the application switches. 146 | An array with the application identifiers of the enabled (or disabled when `defaultApplicationSwitchValue` is true) applications will be saved in the preference domain specified via `defaults` under the specified `key`. 147 | It respects the `get` / `set` attributes so by default it will use the `readPreferenceValue:` and `setPreferenceValue:specifier:` methods of the PSListController that contains the cell for reading / writing the value. 148 | 149 | #### Keys 150 | 151 | | Key | Type | Fallback | Usage | 152 | | ------------------------------ | ------- | --------- | ----------------------------------------- | 153 | | `defaultApplicationSwitchValue` | Boolean | false | Default value of the application switches | 154 | 155 | #### Example 156 | 157 | ```xml 158 | 159 | cell 160 | PSLinkListCell 161 | detail 162 | ATLApplicationListMultiSelectionController 163 | defaults 164 | (...) 165 | key 166 | (...) 167 | label 168 | (...) 169 | sections 170 | 171 | 172 | sectionType 173 | Visible 174 | 175 | 176 | sectionType 177 | Hidden 178 | 179 | 180 | showIdentifiersAsSubtitle 181 | 182 | defaultApplicationSwitchValue 183 | 184 | useSearchBar 185 | 186 | 187 | ``` 188 | 189 | ### ATLApplicationListSubcontrollerController 190 | 191 | ATLApplicationListSubcontrollerController can be used as the detail class of a PSLinkListCell to have one PSListController per application. 192 | The key `subcontrollerClass` is used to specify the PSListController subclass, although you can also subclass `ATLApplicationListSubcontroller` instead which has a convienience method to get the application identifier and also automatically reloads the preview string shown in the application list. 193 | Preview strings are supported but require you to subclass ATLApplicationListSubcontrollerController and use your subclass instead. In your subclass you need to overwrite the `previewStringForApplicationWithIdentifier:` method and return the preview string there. 194 | Preferences also need to be handled in your `PSListController`/`ATLApplicationListSubcontrollerController` subclass, if you want an example for this, check out the [Choicy preferences](https://github.com/opa334/Choicy/blob/master/Preferences/CHPApplicationDaemonConfigurationListController.m). 195 | 196 | #### Keys 197 | 198 | | Key | Type | Fallback | Usage | 199 | | -------------------- | ------- | --------------- | ------------------------------------------------------------------ | 200 | | `subcontrollerClass` | String | None (Required) | Name of the class that should be used for the application subpages | 201 | 202 | #### Example 203 | 204 | ```xml 205 | 206 | cell 207 | PSLinkListCell 208 | detail 209 | ATLApplicationListSubcontrollerController 210 | subcontrollerClass 211 | (...) 212 | label 213 | (...) 214 | sections 215 | 216 | 217 | sectionType 218 | Visible 219 | 220 | 221 | sectionType 222 | Hidden 223 | 224 | 225 | showIdentifiersAsSubtitle 226 | 227 | useSearchBar 228 | 229 | 230 | ``` 231 | -------------------------------------------------------------------------------- /Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | AltList 9 | CFBundleIdentifier 10 | com.opa334.altlist 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | FMWK 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | NSPrincipalClass 22 | ATLApplicationListControllerBase 23 | 24 | 25 | -------------------------------------------------------------------------------- /Resources/ar.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "Applications" = "التطبيقات"; 2 | "System Applications" = "تطبيقات النظام"; 3 | "User Applications" = "تطبيقات المستخدم"; 4 | "Hidden Applications" = "التطبيقات المخفية"; 5 | -------------------------------------------------------------------------------- /Resources/de.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "Applications" = "Applikationen"; 2 | "System Applications" = "Systemapplikationen"; 3 | "User Applications" = "Benutzerapplikationen"; 4 | "Hidden Applications" = "Versteckte Applikationen"; -------------------------------------------------------------------------------- /Resources/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "Applications" = "Applications"; 2 | "System Applications" = "System Applications"; 3 | "User Applications" = "User Applications"; 4 | "Hidden Applications" = "Hidden Applications"; 5 | -------------------------------------------------------------------------------- /Resources/fr.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "Applications" = "Applications"; 2 | "System Applications" = "Applications Système"; 3 | "User Applications" = "Applications Utilisateur"; 4 | "Hidden Applications" = "Applications masquées"; 5 | -------------------------------------------------------------------------------- /Resources/it.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "Applications" = "App"; 2 | "System Applications" = "App di Sistema"; 3 | "User Applications" = "App Personali"; 4 | "Hidden Applications" = "App Nascoste"; -------------------------------------------------------------------------------- /Resources/ja.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "Applications" = "アプリケーション"; 2 | "System Applications" = "システムアプリケーション"; 3 | "User Applications" = "ユーザーアプリケーション"; 4 | "Hidden Applications" = "非表示のアプリケーション"; 5 | -------------------------------------------------------------------------------- /Resources/ko.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "Applications" = "앱"; 2 | "System Applications" = "시스템 앱"; 3 | "User Applications" = "사용자 앱"; 4 | "Hidden Applications" = "숨겨진 앱"; 5 | -------------------------------------------------------------------------------- /Resources/nl.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "Applications" = "Applicaties"; 2 | "System Applications" = "Systeem Applicaties"; 3 | "User Applications" = "Gebruiker Applicaties"; 4 | "Hidden Applications" = "Verborgen Applicaties"; 5 | -------------------------------------------------------------------------------- /Resources/pl.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "Applications" = "Aplikacje"; 2 | "System Applications" = "Aplikacje Systemowe"; 3 | "User Applications" = "Aplikacje Użytkownika"; 4 | "Hidden Applications" = "Ukryte Aplikacje"; 5 | -------------------------------------------------------------------------------- /Resources/pt.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "Applications" = "Aplicativos"; 2 | "System Applications" = "Aplicativos do Sistema"; 3 | "User Applications" = "Aplicativos do Usuário"; 4 | "Hidden Applications" = "Aplicativos Ocultos"; 5 | -------------------------------------------------------------------------------- /Resources/ru.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "Applications" = "Приложения"; 2 | "System Applications" = "Системные приложения"; 3 | "User Applications" = "Пользовательские приложения"; 4 | "Hidden Applications" = "Скрытые приложения"; 5 | -------------------------------------------------------------------------------- /Resources/sk.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "Applications" = "Aplikácie"; 2 | "System Applications" = "Systémové aplikácie"; 3 | "User Applications" = "Používateľské aplikácie"; 4 | "Hidden Applications" = "Skryté aplikácie"; 5 | -------------------------------------------------------------------------------- /Resources/tr.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "Applications" = "Uygulamalar"; 2 | "System Applications" = "Sistem Uygulamaları"; 3 | "User Applications" = "Kullanıcı Uygulamaları"; 4 | "Hidden Applications" = "Gizlenmiş Uygulamalar"; -------------------------------------------------------------------------------- /Resources/zh-Hant.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "Applications" = "應用程式"; 2 | "System Applications" = "系統應用"; 3 | "User Applications" = "用戶應用"; 4 | "Hidden Applications" = "隱藏應用"; 5 | -------------------------------------------------------------------------------- /Resources/zh.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "Applications" = "应用程序"; 2 | "System Applications" = "系统应用"; 3 | "User Applications" = "用户应用"; 4 | "Hidden Applications" = "隐藏的应用"; 5 | -------------------------------------------------------------------------------- /control: -------------------------------------------------------------------------------- 1 | Package: com.opa334.altlist 2 | Name: AltList 3 | Depiction: https://moreinfo.thebigboss.org/moreinfo/depiction.php?file=altlistDp 4 | Version: 1.0.11 5 | Architecture: iphoneos-arm 6 | Depends: firmware (>=7.0) 7 | Description: A modern AppList alternative 8 | Maintainer: opa334 9 | Author: opa334 10 | Section: System 11 | Tag: role::developer 12 | -------------------------------------------------------------------------------- /install_to_theos.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | make clean 3 | make FINALPACKAGE=1 4 | cp -Rv "./.theos/obj/AltList.framework" "$THEOS/lib" 5 | 6 | make clean 7 | make FINALPACKAGE=1 THEOS_PACKAGE_SCHEME=rootless 8 | mkdir -p "$THEOS/lib/iphone/rootless/lib" 9 | cp -Rv "./.theos/obj/AltList.framework" "$THEOS/lib/iphone/rootless" 10 | 11 | echo "Successfully installed AltList" 12 | -------------------------------------------------------------------------------- /release_build.sh: -------------------------------------------------------------------------------- 1 | #/bin/sh 2 | 3 | export PREFIX=$THEOS/toolchain/Xcode11.xctoolchain/usr/bin/ 4 | 5 | make clean 6 | make package FINALPACKAGE=1 7 | 8 | export -n PREFIX 9 | 10 | make clean 11 | make package FINALPACKAGE=1 THEOS_PACKAGE_SCHEME=rootless --------------------------------------------------------------------------------