diff --git a/Frameworks/Preferences.framework/Headers/PSListItemsController.h b/Frameworks/Preferences.framework/Headers/PSListItemsController.h index b2936bc..172ab29 100644 --- a/Frameworks/Preferences.framework/Headers/PSListItemsController.h +++ b/Frameworks/Preferences.framework/Headers/PSListItemsController.h @@ -1,3 +1,4 @@ +#import #import @class PSSpecifier; diff --git a/Frameworks/Preferences.framework/Headers/PSRootController.h b/Frameworks/Preferences.framework/Headers/PSRootController.h index 5430aa8..1bf671e 100644 --- a/Frameworks/Preferences.framework/Headers/PSRootController.h +++ b/Frameworks/Preferences.framework/Headers/PSRootController.h @@ -1,13 +1,15 @@ #import @class PSListController; +@class PSViewController; @interface PSRootController : UINavigationController - (instancetype)initWithTitle:(NSString *)title identifier:(NSString *)identifier; - (void)handleURL:(id)url ; -- (void)pushController:(PSListController *)controller; // < 3.2 +- (void)pushController:(PSViewController *)controller; // < 3.2 - (void)suspend; +- (void)setSupportedInterfaceOrientations:(NSUInteger)orientations; @end diff --git a/Frameworks/Preferences.framework/Headers/PSSpecifier.h b/Frameworks/Preferences.framework/Headers/PSSpecifier.h index 67d94f0..d9f5d8f 100644 --- a/Frameworks/Preferences.framework/Headers/PSSpecifier.h +++ b/Frameworks/Preferences.framework/Headers/PSSpecifier.h @@ -118,14 +118,23 @@ __END_DECLS @interface PSSpecifier : NSObject { @public - SEL action; Class detailControllerClass; + id target; + SEL getter; + SEL setter; + SEL action; + SEL cancel; + SEL _confirmationAction; + SEL _confirmationCancelAction; + SEL _buttonAction; + SEL _controllerLoadAction; } + (instancetype)preferenceSpecifierNamed:(NSString *)identifier target:(id)target set:(SEL)set get:(SEL)get detail:(Class)detail cell:(PSCellType)cellType edit:(Class)edit; + (instancetype)emptyGroupSpecifier; + (instancetype)groupSpecifierWithName:(NSString *)name; +@property (nonatomic,retain) NSArray * values; @property (nonatomic, retain) id target; @property (nonatomic, retain) NSString *name; @property (nonatomic, retain) NSString *identifier; @@ -147,4 +156,7 @@ __END_DECLS -(SEL)controllerLoadAction; -(void)performControllerLoadAction; +- (void)setValues:(NSArray *)values titles:(NSArray *)titles; +- (void)setValues:(NSArray *)values titles:(NSArray *)titles shortTitles:(NSArray *)shortTitles; + @end diff --git a/Makefile b/Makefile index fd6cf83..d3dda1e 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -export TARGET = iphone:clang:13.0:10.0 +export TARGET = iphone:clang:14.4:10.0 export ARCHS = armv7 arm64 DEBUG = 1 diff --git a/Releases/com.creaturecoding.tweaksettings_1.0.6_iphoneos-arm.deb b/Releases/com.creaturecoding.tweaksettings_1.0.6_iphoneos-arm.deb new file mode 100644 index 0000000..a49bf48 Binary files /dev/null and b/Releases/com.creaturecoding.tweaksettings_1.0.6_iphoneos-arm.deb differ diff --git a/TweakSettings-App/Info.plist b/TweakSettings-App/Info.plist index fca63d9..5adf299 100644 --- a/TweakSettings-App/Info.plist +++ b/TweakSettings-App/Info.plist @@ -49,9 +49,9 @@ UISupportedInterfaceOrientations - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationPortrait UISupportedInterfaceOrientations~ipad diff --git a/TweakSettings-App/Localizable.h b/TweakSettings-App/Localizable.h index c802bd7..904dc74 100644 --- a/TweakSettings-App/Localizable.h +++ b/TweakSettings-App/Localizable.h @@ -26,8 +26,27 @@ #define ROOT_NAVIGATION_TITLE_KEY @"ROOT_NAVIGATION_TITLE_KEY" #define ROOT_NAVIGATION_RIGHT_TITLE_KEY @"ROOT_NAVIGATION_RIGHT_TITLE_KEY" +#define ROOT_NAVIGATION_LEFT_TITLE_KEY @"ROOT_NAVIGATION_LEFT_TITLE_KEY" #define ALERT_CANCEL_TITLE_KEY @"ALERT_CANCEL_TITLE_KEY" +#define ALERT_DISMISS_TITLE_KEY @"ALERT_DISMISS_TITLE_KEY" #define ALERT_ACTION_MESSAGE_KEY @"ALERT_ACTION_MESSAGE_KEY" +#define ALWAYS_SHOW_SEARCH_BAR_NAME_KEY @"ALWAYS_SHOW_SEARCH_BAR_NAME_KEY" +#define ALWAYS_SHOW_SEARCH_BAR_GROUP_KEY @"ALWAYS_SHOW_SEARCH_BAR_GROUP_KEY" +#define REQUIRE_ACTION_CONFIRMATION_NAME_KEY @"REQUIRE_ACTION_CONFIRMATION_NAME_KEY" +#define REQUIRE_ACTION_CONFIRMATION_GROUP_KEY @"REQUIRE_ACTION_CONFIRMATION_GROUP_KEY" +#define LONG_PRESS_OPENS_SETTINGS_NAME_KEY @"LONG_PRESS_OPENS_SETTINGS_NAME_KEY" +#define LONG_PRESS_OPENS_SETTINGS_GROUP_KEY @"LONG_PRESS_OPENS_SETTINGS_GROUP_KEY" +#define USE_LARGE_TITLES_ON_ROOT_LIST_NAME_KEY @"USE_LARGE_TITLES_ON_ROOT_LIST_NAME_KEY" +#define USE_LARGE_TITLES_ON_ROOT_LIST_GROUP_KEY @"USE_LARGE_TITLES_ON_ROOT_LIST_GROUP_KEY" +#define APP_SETTINGS_TITLE_NAME_KEY @"APP_SETTINGS_TITLE_NAME_KEY" +#define SOURCE_CODE_NAME_KEY @"SOURCE_CODE_NAME_KEY" +#define APP_VERSION_NAME_KEY @"APP_VERSION_NAME_KEY" + +#define CHECK_FOR_UPDATES_TITLE_KEY @"CHECK_FOR_UPDATES_TITLE_KEY" +#define CHECK_FOR_UPDATES_ERROR_MESSAGE_KEY @"CHECK_FOR_UPDATES_ERROR_MESSAGE_KEY" +#define CHECK_FOR_UPDATES_PASS_MESSAGE_KEY @"CHECK_FOR_UPDATES_PASS_MESSAGE_KEY" +#define CHECK_FOR_UPDATES_FAIL_MESSAGE_KEY @"CHECK_FOR_UPDATES_FAIL_MESSAGE_KEY" + #endif //LOCALIZABLE_H diff --git a/TweakSettings-App/TS-Prefix.pch b/TweakSettings-App/TS-Prefix.pch index 6a343ec..a4b7169 100644 --- a/TweakSettings-App/TS-Prefix.pch +++ b/TweakSettings-App/TS-Prefix.pch @@ -50,9 +50,9 @@ NS_INLINE __unused NSObject *OBJECT_WITH_PLIST_OPTIONS(NSString *path, NSPropert NS_INLINE __unused NSDictionary *DICTIONARY_WITH_PLIST_OPTIONS(NSString *path, NSPropertyListReadOptions options) { NSObject *object = OBJECT_WITH_PLIST_OPTIONS(path, options); if (object && [object isKindOfClass:NSDictionary.class]) { - NSLog(@"ERROR (property-list file at %@ is not a dictionary)", path); return (NSDictionary *) object; } + NSLog(@"ERROR (property-list file at %@ is not a dictionary)", path); return nil; } @@ -63,9 +63,9 @@ NS_INLINE __unused NSDictionary *DICTIONARY_WITH_PLIST(NSString *path) { NS_INLINE __unused NSArray *ARRAY_WITH_PLIST_OPTIONS(NSString *path, NSPropertyListReadOptions options) { NSObject *object = OBJECT_WITH_PLIST_OPTIONS(path, options); if (object && [object isKindOfClass:NSArray.class]) { - NSLog(@"ERROR (property-list file at %@ is not an array)", path); return (NSArray *) object; } + NSLog(@"ERROR (property-list file at %@ is not an array)", path); return nil; } diff --git a/TweakSettings-App/TSAppDelegate.h b/TweakSettings-App/TSAppDelegate.h index d8b349b..54b91f8 100644 --- a/TweakSettings-App/TSAppDelegate.h +++ b/TweakSettings-App/TSAppDelegate.h @@ -9,11 +9,22 @@ #import #import "TSUtilityActionManager.h" +@class TSRootNavigationManager; + +#define APP_DELEGATE ((TSAppDelegate *) UIApplication.sharedApplication.delegate) + @interface TSAppDelegate : UIApplication @property(strong, nonatomic) UIWindow *window; +@property(nonatomic, weak) id popoverSender; +@property(nonatomic, strong, readonly) TSRootNavigationManager *navigationManager; -- (void)handleActionForType:(NSString *)actionType withConfirmationSender:(id)sender; +- (void)presentAsPopover:(UIViewController *)controller withSender:(id)sender; +- (void)presentViewController:(UIViewController *)controller; + +- (void)handleActionForType:(NSString *)actionType; - (void)openApplicationURL:(NSURL *)url; +- (void)generateURL; + @end diff --git a/TweakSettings-App/TSAppDelegate.m b/TweakSettings-App/TSAppDelegate.m index e13f5c7..b88c696 100644 --- a/TweakSettings-App/TSAppDelegate.m +++ b/TweakSettings-App/TSAppDelegate.m @@ -6,29 +6,24 @@ // // -#import +#import "TSAppDelegate.h" #import #import -#import "TSAppDelegate.h" #import "TSRootListController.h" #import "Localizable.h" +#import "TSRootNavigationManager.h" +#import "TSUserDefaults.h" -void HandleExceptions(NSException *exception) { - NSLog(@"unhandled exception: %@", [exception debugDescription]); -} - -@interface TSAppDelegate () - -@property(nonatomic, strong) PSRootController *rootController; -@property(nonatomic, strong) TSRootListController *rootListController; -@property(nonatomic, strong) NSString *launchIdentifier; -@end +static void HandleExceptions(NSException *exception) { + NSLog(@"TweakSettings unhandled exception: %@", exception.debugDescription); +} @implementation TSAppDelegate -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - // Override point for customization after application launch. +#pragma mark - UIApplicationDelegate + +- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSSetUncaughtExceptionHandler(&HandleExceptions); @@ -39,58 +34,40 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( [[UIApplicationShortcutItem alloc] initWithType:TSActionTypeRespring localizedTitle:NSLocalizedString(RESPRING_TITLE_KEY, nil) localizedSubtitle:NSLocalizedString(RESPRING_SUBTITLE_KEY, nil) icon:nil userInfo:nil] ]; - _rootListController = [TSRootListController new]; - _rootController = [[PSRootController alloc] initWithRootViewController:_rootListController]; - _rootListController.rootController = _rootController; - _rootListController.launchIdentifier = _launchIdentifier; + _navigationManager = [TSRootNavigationManager new]; self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; - self.window.rootViewController = _rootController; + self.window.rootViewController = (id)_navigationManager.splitController; [self.window makeKeyAndVisible]; - if (launchOptions[UIApplicationLaunchOptionsShortcutItemKey]) { - - [self handleActionForType:[(UIApplicationShortcutItem *)launchOptions[UIApplicationLaunchOptionsShortcutItemKey] type] withConfirmationSender:nil]; - return NO; - } - return YES; } -- (void)applicationWillResignActive:(UIApplication *)application { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. -} - - -- (void)applicationDidEnterBackground:(UIApplication *)application { - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. -} - +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { -- (void)applicationWillEnterForeground:(UIApplication *)application { - // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. -} + if (launchOptions[UIApplicationLaunchOptionsShortcutItemKey]) { + [self handleActionForType:[(UIApplicationShortcutItem *)launchOptions[UIApplicationLaunchOptionsShortcutItemKey] type]]; + return NO; + } -- (void)applicationDidBecomeActive:(UIApplication *)application { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. -} + if (launchOptions[UIApplicationLaunchOptionsURLKey]) { + [self.navigationManager.rootListController setShowOnLoad:NO]; + [self.navigationManager setDeferredLoadURL:launchOptions[UIApplicationLaunchOptionsURLKey]]; + return NO; + } -- (void)applicationWillTerminate:(UIApplication *)application { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. + return YES; } - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *)options { if ([url.scheme isEqualToString:@"tweaks"]) { - NSString *urlString = url.absoluteString; + if (self.navigationManager.rootListController.rootListLoaded) { - if ([urlString containsString:@"root="]) { - self.launchIdentifier = [urlString substringFromIndex:[urlString rangeOfString:@"root="].location + 5]; + [self.navigationManager processURL:url animated:YES]; } return YES; @@ -101,50 +78,55 @@ - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(N - (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler { - [self handleActionForType:shortcutItem.type withConfirmationSender:nil]; + [self handleActionForType:shortcutItem.type]; completionHandler(YES); } -- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray *))restorationHandler { +- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray> *))restorationHandler { if ([userActivity.activityType isEqualToString:CSSearchableItemActionType]) { - self.launchIdentifier = [userActivity.userInfo[CSSearchableItemActivityIdentifier] stringByReplacingOccurrencesOfString:@"tweaks:root=" withString:@""]; + NSURL *launchURL = [NSURL URLWithString:userActivity.userInfo[CSSearchableItemActivityIdentifier]]; + [self.navigationManager processURL:launchURL animated:NO]; } return YES; } -- (void)handleActionForType:(NSString *)actionType withConfirmationSender:(id)sender { +#pragma mark - Public Methods + +- (void)presentAsPopover:(UIViewController *)controller withSender:(id)sender { + + if (sender == nil) sender = _navigationManager.rootListController.navigationItem.rightBarButtonItem; + self.popoverSender = sender; - UIAlertController *controller = ActionAlertForType(actionType); controller.modalPresentationStyle = UIModalPresentationPopover; if (sender && [sender isKindOfClass:UIBarButtonItem.class]) { - controller.popoverPresentationController.barButtonItem = sender; + } + else if (sender && [sender isKindOfClass:UIView.class]) { + controller.popoverPresentationController.sourceRect = [sender bounds]; + controller.popoverPresentationController.sourceView = sender; + controller.popoverPresentationController.permittedArrowDirections = (UIPopoverArrowDirection)0; + } - } else if (sender && [sender isKindOfClass:UIView.class]) { - - controller.popoverPresentationController.sourceView = (UIView *)sender; - controller.popoverPresentationController.sourceRect = ((UIView *)sender).bounds; + [_navigationManager.rootListController presentViewController:controller animated:YES completion:nil]; +} - } else { +- (void)presentViewController:(UIViewController *)controller { + [_navigationManager.topNavigationController presentViewController:controller animated:YES completion:nil]; +} - controller.popoverPresentationController.sourceView = self.rootController.view; - controller.popoverPresentationController.sourceRect = self.rootController.view.bounds; - } +- (void)handleActionForType:(NSString *)actionType { - [self.rootController presentViewController:controller animated:YES completion:nil]; -} + if (CanRunWithoutConfirmation(actionType) && !TSUserDefaults.sharedDefaults.requireActionConfirmation) { -- (void)setLaunchIdentifier:(NSString *)launchIdentifier { + HandleActionForType(actionType); - _launchIdentifier = [launchIdentifier stringByReplacingOccurrencesOfString:@"tweaks:root=" withString:@""]; - _launchIdentifier = launchIdentifier; - _rootListController.launchIdentifier = launchIdentifier; + } else { - [_rootController popToRootViewControllerAnimated:NO]; - [_rootListController pushToLaunchIdentifier]; + [self presentAsPopover:ActionAlertForType(actionType) withSender:_popoverSender]; + } } - (void)openApplicationURL:(NSURL *)url { @@ -157,4 +139,9 @@ - (void)openApplicationURL:(NSURL *)url { } } +- (void)generateURL { + NSURL *url = [self.navigationManager urlForCurrentNavStack]; + [NSUserDefaults.standardUserDefaults setObject:url.absoluteString forKey:@"kPreferencePositionKey"]; +} + @end diff --git a/TweakSettings-App/TSChangelogController.h b/TweakSettings-App/TSChangelogController.h new file mode 100644 index 0000000..8403497 --- /dev/null +++ b/TweakSettings-App/TSChangelogController.h @@ -0,0 +1,11 @@ +// +// Created by Dana Buehre on 11/21/21. +// + +#import +#import + + +@interface TSChangelogController : PSListController + +@end \ No newline at end of file diff --git a/TweakSettings-App/TSChangelogController.m b/TweakSettings-App/TSChangelogController.m new file mode 100644 index 0000000..8c00bc5 --- /dev/null +++ b/TweakSettings-App/TSChangelogController.m @@ -0,0 +1,85 @@ +// +// Created by Dana Buehre on 11/21/21. +// + +#import +#import "TSChangelogController.h" +#import + + +@implementation TSChangelogController { + + NSArray *_releases; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + [self _loadChangelog]; +} + +#pragma mark - PSListController + +- (NSMutableArray *)specifiers { + + if (!_specifiers) { + + if (_releases) { + + NSMutableArray *specifiers = [NSMutableArray new]; + + for (NSDictionary *release in _releases) { + + [specifiers addObject:[PSSpecifier groupSpecifierWithName:release[@"version"]]]; + + for (NSString *change in release[@"changes"]) { + + [specifiers addObject:[PSSpecifier preferenceSpecifierNamed:change target:nil set:nil get:nil detail:nil cell:PSStaticTextCell edit:nil]]; + } + } + + _specifiers = specifiers; + } + + else { + + _specifiers = @[[PSSpecifier preferenceSpecifierNamed:nil target:nil set:nil get:nil detail:nil cell:PSSpinnerCell edit:nil]].mutableCopy; + } + } + + return _specifiers; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath]; + cell.textLabel.numberOfLines = 0; + cell.detailTextLabel.numberOfLines = 0; + return cell; +} + +#pragma mark - Private Methods + +- (void)_loadChangelog { + + NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; + NSURL *latestReleaseURL = [NSURL URLWithString:@"https://api.creaturecoding.com/info/package?id=tweaksettings&key=changelog"]; + + [[session dataTaskWithURL:latestReleaseURL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + + if (!data || error) { + return; + } + + NSArray *releases = [NSJSONSerialization JSONObjectWithData:data options:(NSJSONReadingOptions)0 error:nil]; + if (!releases || !releases.count) { + return; + } + + self->_releases = releases; + + MAIN_QUEUE(^{ [self reloadSpecifiers]; }); + + }] resume]; +} + +@end \ No newline at end of file diff --git a/TweakSettings-App/TSOptionsController.h b/TweakSettings-App/TSOptionsController.h new file mode 100644 index 0000000..bf2426d --- /dev/null +++ b/TweakSettings-App/TSOptionsController.h @@ -0,0 +1,19 @@ +// +// TSOptionsController.h +// TweakSettings +// +// Created by Dana Buehre on 11/14/21. +// +// + + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface TSOptionsController : PSListController + +@end + +NS_ASSUME_NONNULL_END diff --git a/TweakSettings-App/TSOptionsController.m b/TweakSettings-App/TSOptionsController.m new file mode 100644 index 0000000..59f17f4 --- /dev/null +++ b/TweakSettings-App/TSOptionsController.m @@ -0,0 +1,160 @@ +// +// TSOptionsController.m +// TweakSettings +// +// Created by Dana Buehre on 11/14/21. +// +// + +#import +#import +#import "TSOptionsController.h" +#import "TSUserDefaults.h" +#import "Localizable.h" +#import "TSChangelogController.h" + +@implementation TSOptionsController + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.navigationItem.title = self.title; + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(_dismiss)]; +} + +#pragma mark - PSListController + +- (NSString *)title { + return NSLocalizedString(APP_SETTINGS_TITLE_NAME_KEY, nil); +} + +- (NSMutableArray *)specifiers { + + if (!_specifiers) { + + NSMutableArray *specifiers = @[ + [self groupSpecifierNamed:nil footer:NSLocalizedString(REQUIRE_ACTION_CONFIRMATION_GROUP_KEY, nil)], + [self specifierNamed:NSLocalizedString(REQUIRE_ACTION_CONFIRMATION_NAME_KEY, nil) key:RequireActionConfirmationKey default:@(YES) cellType:PSSwitchCell], + [self groupSpecifierNamed:nil footer:NSLocalizedString(LONG_PRESS_OPENS_SETTINGS_GROUP_KEY, nil)], + [self specifierNamed:NSLocalizedString(LONG_PRESS_OPENS_SETTINGS_NAME_KEY, nil) key:LongPressOpensSettingsKey default:@(YES) cellType:PSSwitchCell], + ].mutableCopy; + + if (@available(iOS 11, *)) { + [specifiers insertObject:[self groupSpecifierNamed:nil footer:NSLocalizedString(USE_LARGE_TITLES_ON_ROOT_LIST_GROUP_KEY, nil)] atIndex:0]; + [specifiers insertObject:[self specifierNamed:NSLocalizedString(USE_LARGE_TITLES_ON_ROOT_LIST_NAME_KEY, nil) key:UseLargeTitlesOnRootListKey default:@(YES) cellType:PSSwitchCell] atIndex:1]; + [specifiers insertObject:[self groupSpecifierNamed:nil footer:NSLocalizedString(ALWAYS_SHOW_SEARCH_BAR_GROUP_KEY, nil)] atIndex:2]; + [specifiers insertObject:[self specifierNamed:NSLocalizedString(ALWAYS_SHOW_SEARCH_BAR_NAME_KEY, nil) key:AlwaysShowSearchBarKey default:@(NO) cellType:PSSwitchCell] atIndex:3]; + } + + [specifiers addObjectsFromArray:[self _footerSpecifiers]]; + + _specifiers = specifiers; + } + + return _specifiers; +} + +#pragma mark - Public Methods + +- (PSSpecifier *)specifierNamed:(NSString *)name key:(NSString *)key default:(id)defaultValue cellType:(PSCellType)cellType { + + PSSpecifier *specifier = [PSSpecifier preferenceSpecifierNamed:name target:self set:@selector(setPreferenceValue:specifier:) get:@selector(readPreferenceValue:) detail:nil cell:cellType edit:nil]; + [specifier setProperty:key forKey:PSKeyNameKey]; + [specifier setProperty:defaultValue forKey:PSDefaultValueKey]; + [specifier setProperty:@"com.creaturecoding.tweaksettings" forKey:PSDefaultsKey]; + [specifier setProperty:@"com.creaturecoding.tweaksettings/changed" forKey:PSValueChangedNotificationKey]; + + return specifier; +} + +- (PSSpecifier *)groupSpecifierNamed:(NSString *)name footer:(NSString *)footer { + + PSSpecifier *specifier = [PSSpecifier groupSpecifierWithName:name]; + [specifier setProperty:footer forKey:PSFooterTextGroupKey]; + return specifier; +} + +#pragma mark - Private Methods + +- (void)_dismiss { + + [self.presentingViewController dismissViewControllerAnimated:YES completion:nil]; +} + +- (void)_openLink:(PSSpecifier *)specifier { + + NSURL *url = [specifier propertyForKey:@"openURL"]; + [UIApplication.sharedApplication openURL:url options:@{} completionHandler:nil]; +} + +- (NSArray *)_footerSpecifiers { + + PSSpecifier *developerGroupSpecifier = [PSSpecifier emptyGroupSpecifier]; + [developerGroupSpecifier setProperty:@"CreatureCoding © 2021" forKey:PSFooterTextGroupKey]; + [developerGroupSpecifier setProperty:@1 forKey:PSFooterAlignmentGroupKey]; + + PSSpecifier *changelogSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Changelog" target:self set:nil get:nil detail:TSChangelogController.class cell:PSLinkCell edit:nil]; + + PSSpecifier *sourceSpecifier = [PSSpecifier preferenceSpecifierNamed:NSLocalizedString(SOURCE_CODE_NAME_KEY, nil) target:self set:nil get:nil detail:nil cell:PSLinkCell edit:nil]; + [sourceSpecifier setProperty:[NSURL URLWithString:@"https://github.com/CreatureSurvive/TweakSettings"] forKey:@"openURL"]; + sourceSpecifier->action = @selector(_openLink:); + + PSSpecifier *versionSpecifier = [PSSpecifier preferenceSpecifierNamed:NSLocalizedString(APP_VERSION_NAME_KEY, nil) target:self set:nil get:@selector(_valueForSpecifier:) detail:nil cell:PSTitleValueCell edit:nil]; + [versionSpecifier setProperty:[NSBundle.mainBundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"] forKey:PSValueKey]; + versionSpecifier->action = @selector(_checkForUpdates); + + return @[developerGroupSpecifier, changelogSpecifier, sourceSpecifier, versionSpecifier]; +} + +- (NSString *)_valueForSpecifier:(PSSpecifier *)specifier { + + return [specifier propertyForKey:PSValueKey]; +} + +- (void)_checkForUpdates { + NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration]; + [configuration setRequestCachePolicy:NSURLRequestReloadIgnoringCacheData]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration]; + NSURL *latestReleaseURL = [NSURL URLWithString:@"https://api.creaturecoding.com/info/package?id=tweaksettings&key=package.version"]; + + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(CHECK_FOR_UPDATES_TITLE_KEY, nil) message:nil preferredStyle:UIAlertControllerStyleAlert]; + MAIN_QUEUE(^{ [self.navigationController presentViewController:alertController animated:NO completion:nil]; }); + + [[session dataTaskWithURL:latestReleaseURL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + + void (^updateAlert)(NSString *, NSString *, BOOL ) = ^(NSString *title, NSString *message, BOOL error) { + MAIN_QUEUE(^{ + if (title) alertController.title = title; + if (message) alertController.message = message; + if (error) alertController.message = NSLocalizedString(CHECK_FOR_UPDATES_ERROR_MESSAGE_KEY, nil); + [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(ALERT_DISMISS_TITLE_KEY, nil) style:UIAlertActionStyleCancel handler:nil]]; + }); + }; + + if (!data || error) { + updateAlert(nil, nil, YES); + return; + } + + NSString *key = @"version"; + NSDictionary *release = [NSJSONSerialization JSONObjectWithData:data options:(NSJSONReadingOptions)0 error:nil]; + if (!release || !release[key] || ![release[key] length]) { + updateAlert(nil, nil, YES); + return; + } + + NSString *message; + NSString *releaseVersion = release[key]; + NSString *localVersion = [NSBundle.mainBundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; + BOOL updateAvailable = ![releaseVersion isEqualToString:localVersion]; + + message = [NSString stringWithFormat:updateAvailable + ? NSLocalizedString(CHECK_FOR_UPDATES_FAIL_MESSAGE_KEY, nil) + : NSLocalizedString(CHECK_FOR_UPDATES_PASS_MESSAGE_KEY, nil), releaseVersion]; + + updateAlert(nil, message, NO); + + }] resume]; +} + +@end diff --git a/TweakSettings-App/TSPackageUtility.h b/TweakSettings-App/TSPackageUtility.h new file mode 100644 index 0000000..d46dcd7 --- /dev/null +++ b/TweakSettings-App/TSPackageUtility.h @@ -0,0 +1,17 @@ +// +// Created by Dana Buehre on 6/20/21. +// + +#import + +@class PSListController; + +BOOL PREFERENCE_FILTER_PASSES_ENVIRONMENT_CHECKS(NSDictionary *filter); +NSArray *SPECIFIERS_FROM_ENTRY(NSDictionary *entry, NSString *sourceBundlePath, NSString *title, PSListController *listController); + +@interface TSPackageUtility : NSObject + ++ (NSArray *)loadTweakSpecifiersInController:(PSListController *)controller; ++ (PSViewController *)controllerForSpecifier:(PSSpecifier *)specifier inController:(PSListController *)parentController; + +@end \ No newline at end of file diff --git a/TweakSettings-App/TSPackageUtility.m b/TweakSettings-App/TSPackageUtility.m new file mode 100644 index 0000000..8cde3d9 --- /dev/null +++ b/TweakSettings-App/TSPackageUtility.m @@ -0,0 +1,190 @@ +// +// Created by Dana Buehre on 6/20/21. +// + +#import +#import +#import +#import +#import +#import + +#import "TSPackageUtility.h" +#import "libprefs.h" + +#pragma mark - LIBPREFS Shim + +BOOL PREFERENCE_FILTER_PASSES_ENVIRONMENT_CHECKS(NSDictionary *filter) { + BOOL valid = YES; + NSArray *coreFoundationVersion; + + if (filter && (coreFoundationVersion = filter[@"CoreFoundationVersion"])) { + if (coreFoundationVersion.count > 0) valid = valid && (kCFCoreFoundationVersionNumber >= [coreFoundationVersion[0] floatValue]); // lower + if (coreFoundationVersion.count > 1) valid = valid && (kCFCoreFoundationVersionNumber < [coreFoundationVersion[1] floatValue]); // upper + } + + return valid; +} + +NSArray *SPECIFIERS_FROM_ENTRY(NSDictionary *entry, NSString *sourceBundlePath, NSString *title, PSListController *listController) { + + NSString *bundleName = entry[@"bundle"]; + NSString *bundlePath = entry[@"bundlePath"]; + NSDictionary *specifierPlist = @{ @"items" : @[entry] }; + BOOL isBundle = bundleName != nil; + + if (isBundle) { + NSFileManager *fileManger = NSFileManager.defaultManager; + if (![fileManger fileExistsAtPath:bundlePath]) + bundlePath = [NSString stringWithFormat:@"/Library/PreferenceBundles/%@.bundle", bundleName]; + if (![fileManger fileExistsAtPath:bundlePath]) + bundlePath = [NSString stringWithFormat:@"/System/Library/PreferenceBundles/%@.bundle", bundleName]; + if (![fileManger fileExistsAtPath:bundlePath]) { + return nil; + } + } + + NSBundle *prefBundle = [NSBundle bundleWithPath:(isBundle ? bundlePath : sourceBundlePath)]; + NSMutableArray *bundleControllers = [listController valueForKey:@"_bundleControllers"]; + + void *handle = dlopen("/System/Library/PrivateFrameworks/Preferences.framework/Preferences", RTLD_LAZY); + NSArray *(*_SpecifiersFromPlist)(NSDictionary *,PSSpecifier *,id ,NSString *,NSBundle *,NSString **,NSString **,PSListController *,NSMutableArray **) = dlsym(handle, "SpecifiersFromPlist"); + NSArray *specs = _SpecifiersFromPlist(specifierPlist, nil, listController, title, prefBundle, NULL, NULL, listController, &bundleControllers); + + if (!specs.count) return nil; + + if (isBundle) { + if ([entry[PSBundleIsControllerKey] boolValue]) { + for (PSSpecifier *specifier in specs) { + [specifier setProperty:bundlePath forKey:PSLazilyLoadedBundleKey]; + [specifier setProperty:[NSBundle bundleWithPath:sourceBundlePath] forKey:@"pl_bundle"]; + if (!specifier.name) specifier.name = title; + } + } + } else { + Class customClass = NSClassFromString(@"PLCustomListController"); + Class localizedClass = NSClassFromString(@"PLLocalizedListController"); + BOOL isLocalizedBundle = ![sourceBundlePath.lastPathComponent isEqualToString:@"Preferences"]; + + if ((isLocalizedBundle && localizedClass) || customClass) { + + PSSpecifier *specifier = specs.firstObject; + [specifier setValue:(isLocalizedBundle ? localizedClass : customClass) forKey:@"detailControllerClass"]; + [specifier setProperty:prefBundle forKey:@"pl_bundle"]; + + if (![specifier.properties[PSTitleKey] isEqualToString:title]) { + [specifier setProperty:title forKey:@"pl_alt_plist_name"]; + if (!specifier.name) specifier.name = title; + } + } + } + + return specs; +} + + +@implementation TSPackageUtility { + +} + +#pragma mark - Member Methods + ++ (NSArray *)loadTweakSpecifiersInController:(PSListController *)controller { + NSMutableArray *preferenceSpecifiers = [NSMutableArray new]; + NSArray *preferenceBundlePaths = [NSFileManager.defaultManager subpathsOfDirectoryAtPath:@"/Library/PreferenceLoader/Preferences" error:nil]; + NSMutableArray *searchableItems = [NSMutableArray new]; + + for (NSString *item in preferenceBundlePaths) + { + if (![item.pathExtension isEqualToString:@"plist"]) continue; + + NSString *plistPath = [NSString stringWithFormat:@"/Library/PreferenceLoader/Preferences/%@", item]; + NSDictionary *plist = DICTIONARY_WITH_PLIST(plistPath); + + if (!plist[@"entry"]) continue; + if (!PREFERENCE_FILTER_PASSES_ENVIRONMENT_CHECKS(plist[@"filter"] ?: plist[@"pl_filter"])) continue; + if (!PREFERENCE_FILTER_PASSES_ENVIRONMENT_CHECKS(plist[@"entry"][@"pl_filter"])) continue; + + NSString *bundlePath = [plistPath stringByDeletingLastPathComponent]; + NSString *title = [item.lastPathComponent stringByDeletingPathExtension]; + BOOL customInstall = access("/var/lib/dpkg/info/com.artikus.preferenceloader.list", F_OK) == 0 + || access("/var/lib/dpkg/info/com.creaturecoding.preferred.list", F_OK) == 0; + + NSArray *itemSpecifiers = !customInstall + ? SPECIFIERS_FROM_ENTRY(plist[@"entry"], bundlePath, title, controller) + : [controller specifiersFromEntry:plist[@"entry"] sourcePreferenceLoaderBundlePath:bundlePath title:title]; + + if (itemSpecifiers && itemSpecifiers.count) { + + for (PSSpecifier *specifier in itemSpecifiers) + { + if (![specifier propertyForKey:PSIconImageKey]) { + + [specifier setProperty:[UIImage imageNamed:@"tweak"] forKey:PSIconImageKey]; + } + + CSSearchableItemAttributeSet *attributeSet = [[CSSearchableItemAttributeSet alloc] initWithItemContentType:(NSString *) kUTTypeImage]; + attributeSet.title = specifier.name; + attributeSet.contentDescription = [NSString stringWithFormat:@"Tweak Settings \u2192 %@", specifier.name]; + attributeSet.thumbnailData = UIImagePNGRepresentation([specifier propertyForKey:PSIconImageKey]); + attributeSet.keywords = @[@"tweaks", @"packages", @"jailbreak", specifier.name]; + + NSString *uniqueIdentifier = [NSString stringWithFormat:@"%@", specifier.identifier]; + CSSearchableItem *searchItem = [[CSSearchableItem alloc] initWithUniqueIdentifier:uniqueIdentifier domainIdentifier:@"com.creaturecoding.tweaksettings" attributeSet:attributeSet]; + [searchableItems addObject:searchItem]; + } + + [preferenceSpecifiers addObjectsFromArray:itemSpecifiers]; + } + } + + if (preferenceSpecifiers.count) { + + [preferenceSpecifiers sortUsingDescriptors:@[ + [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES selector:@selector(localizedCaseInsensitiveCompare:)]] + ]; + } + + [CSSearchableIndex.defaultSearchableIndex deleteAllSearchableItemsWithCompletionHandler:^(NSError *error) { + [CSSearchableIndex.defaultSearchableIndex indexSearchableItems:searchableItems completionHandler:nil]; + }]; + + return preferenceSpecifiers; +} + ++ (PSViewController *)controllerForSpecifier:(PSSpecifier *)specifier inController:(PSListController *)parentController { + + if ([parentController respondsToSelector:@selector(controllerForSpecifier:)]) { + + return [parentController performSelector:@selector(controllerForSpecifier:) withObject:specifier]; + } + + [specifier performControllerLoadAction]; + + Class detailClass = [specifier respondsToSelector:@selector(detailControllerClass)] + ? [specifier detailControllerClass] + : [specifier valueForKey:@"detailControllerClass"] + ? : NSClassFromString(@"PLCustomListController") + ? : PSListController.class; + + if ([detailClass isSubclassOfClass:PSViewController.class]) { + + id controller = [detailClass alloc]; + controller = ([controller respondsToSelector:@selector(initForContentSize:)]) + ? [controller initForContentSize:UIScreen.mainScreen.bounds.size] + : [controller init]; + + if (controller && [controller isKindOfClass:PSViewController.class]) { + + [controller setRootController:parentController.rootController]; + [controller setParentController:parentController]; + [controller setSpecifier:specifier]; + } + + return controller; + } + + return nil; +} + +@end \ No newline at end of file diff --git a/TweakSettings-App/TSPrefsRootController.h b/TweakSettings-App/TSPrefsRootController.h new file mode 100644 index 0000000..c988066 --- /dev/null +++ b/TweakSettings-App/TSPrefsRootController.h @@ -0,0 +1,17 @@ +// +// Created by Dana Buehre on 11/6/21. +// + +#import + +@class TSRootListController; + +@interface TSPrefsRootController : PSRootController + +@property (nonatomic, strong, readonly) TSRootListController *rootListController; + +- (instancetype)initWithRootViewController:(UIViewController *)rootViewController rootListController:(TSRootListController *)rootListController; + +- (instancetype)initWithRootListController:(TSRootListController *)rootListController; + +@end \ No newline at end of file diff --git a/TweakSettings-App/TSPrefsRootController.m b/TweakSettings-App/TSPrefsRootController.m new file mode 100644 index 0000000..9c360a7 --- /dev/null +++ b/TweakSettings-App/TSPrefsRootController.m @@ -0,0 +1,29 @@ +// +// Created by Dana Buehre on 11/6/21. +// + +#import "TSPrefsRootController.h" +#import "TSRootListController.h" + + +@implementation TSPrefsRootController + +- (instancetype)initWithRootViewController:(UIViewController *)rootViewController rootListController:(TSRootListController *)rootListController { + if (self = [super initWithRootViewController:rootViewController]) { + + _rootListController = rootListController; + } + + return self; +} + +- (instancetype)initWithRootListController:(TSRootListController *)rootListController { + if (self = [super init]) { + + _rootListController = rootListController; + } + + return self; +} + +@end \ No newline at end of file diff --git a/TweakSettings-App/TSRootListController.h b/TweakSettings-App/TSRootListController.h index c9211ba..60ea5e5 100644 --- a/TweakSettings-App/TSRootListController.h +++ b/TweakSettings-App/TSRootListController.h @@ -10,7 +10,6 @@ @interface TSRootListController : TSSearchableListController -@property (nonatomic, strong) NSString *launchIdentifier; +@property (nonatomic, assign) BOOL rootListLoaded; -- (void)pushToLaunchIdentifier; @end diff --git a/TweakSettings-App/TSRootListController.m b/TweakSettings-App/TSRootListController.m index bcaa891..fd1c5be 100644 --- a/TweakSettings-App/TSRootListController.m +++ b/TweakSettings-App/TSRootListController.m @@ -6,63 +6,75 @@ // // -#import -#import #import -#import +#import #import "TSRootListController.h" +#import "TSPackageUtility.h" #import "TSAppDelegate.h" #import "Localizable.h" -#import "libprefs.h" +#import "TSRootNavigationManager.h" +#import "TSUserDefaults.h" +#import "TSOptionsController.h" -@interface UIBarButtonItem (iOS14) +@interface UIBarButtonItem (iOS13) - (id)initWithTitle:(NSString *)table menu:(UIMenu *)menu API_AVAILABLE(ios(13.0)); @end -@interface TSRootListController () - -@end @implementation TSRootListController { + UIRefreshControl *_refreshControl; } +- (instancetype)init { + + if (self = [super init]) { + + PSSpecifier *specifier = [PSSpecifier groupSpecifierWithName:@"Tweaks"]; + specifier.identifier = @"tweaks"; + self.specifier = specifier; + } + + return self; +} + - (void)viewDidLoad { [super viewDidLoad]; self.navigationItem.title = NSLocalizedString(ROOT_NAVIGATION_TITLE_KEY, nil); if (@available(iOS 14, *)) { - self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(ROOT_NAVIGATION_RIGHT_TITLE_KEY, nil) menu:ActionListMenu(self)]; + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(ROOT_NAVIGATION_RIGHT_TITLE_KEY, nil) menu:ActionListMenu()]; + [APP_DELEGATE setPopoverSender:self.navigationItem.rightBarButtonItem]; } else { - self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(ROOT_NAVIGATION_RIGHT_TITLE_KEY, nil) style:UIBarButtonItemStylePlain target:self action:@selector(handleActionButtonTapped:)]; + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(ROOT_NAVIGATION_RIGHT_TITLE_KEY, nil) style:UIBarButtonItemStylePlain target:self action:@selector(_handleActionButtonTapped:)]; } + self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(ROOT_NAVIGATION_LEFT_TITLE_KEY, nil) style:UIBarButtonItemStylePlain target:self action:@selector(_handleOpenSettings:)]; + _refreshControl = [UIRefreshControl new]; - [_refreshControl addTarget:self action:@selector(handleRefresh) forControlEvents:UIControlEventValueChanged]; + [_refreshControl addTarget:self action:@selector(_handleRefresh) forControlEvents:UIControlEventValueChanged]; self.table.refreshControl = _refreshControl; -} -- (void)viewWillAppear:(BOOL)animated { - [super viewWillAppear:animated]; + [self _preferencesChanged]; - [self pushToLaunchIdentifier]; + [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(_preferencesChanged) name:TSUserDefaultsChangedKey object:nil]; + + _rootListLoaded = YES; } +#pragma mark - PSListController + - (NSMutableArray *)specifiers { + if (!_specifiers) { - NSMutableArray *specifiers = [self loadTweakSpecifiers].mutableCopy; - NSString *appVersion = [NSBundle.mainBundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; - NSString *footerText = [NSString stringWithFormat:@"Tweak Settings — v%@\nCreatureCoding © 2021", appVersion]; - PSSpecifier *groupSpecifier = [PSSpecifier emptyGroupSpecifier]; - [groupSpecifier setProperty:footerText forKey:PSFooterTextGroupKey]; - [groupSpecifier setProperty:@1 forKey:PSFooterAlignmentGroupKey]; - [specifiers insertObject:groupSpecifier atIndex:0]; + NSMutableArray *specifiers = [TSPackageUtility loadTweakSpecifiersInController:self].mutableCopy; self.specifiers = specifiers; self.unfilteredSpecifiers = specifiers; if (_refreshControl && _refreshControl.isRefreshing) { + [_refreshControl endRefreshing]; } } @@ -71,201 +83,75 @@ - (NSMutableArray *)specifiers { } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath]; - if (!cell.gestureRecognizers.count) { - [cell addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleCellLongPress:)]]; - } - return cell; -} - -- (void)handleCellLongPress:(UILongPressGestureRecognizer *)sender { - if (sender.state == UIGestureRecognizerStateBegan) { + UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath]; - CGPoint point = [sender locationInView:self.table]; - NSIndexPath *indexPath = [self.table indexPathForRowAtPoint:point]; - PSSpecifier *specifier = [self specifierAtIndexPath:indexPath]; - NSString *urlString = [NSString stringWithFormat:@"prefs:root=%@", specifier.identifier]; - NSURL *url = [NSURL URLWithString:[urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]]; + if (!cell.gestureRecognizers.count) { - [(TSAppDelegate *)UIApplication.sharedApplication openApplicationURL:url]; + [cell addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(_handleCellLongPress:)]]; } -} - -- (NSArray *)loadTweakSpecifiers { - NSMutableArray *preferenceSpecifiers = [NSMutableArray new]; - BOOL (^PREFERENCE_FILTER_PASSES_ENVIRONMENT_CHECKS)(NSDictionary *) = ^BOOL (NSDictionary *filter) { - BOOL valid = YES; - NSArray *coreFoundationVersion; - - if (filter && (coreFoundationVersion = filter[@"CoreFoundationVersion"])) { - if (coreFoundationVersion.count > 0) valid = valid && (kCFCoreFoundationVersionNumber >= [coreFoundationVersion[0] floatValue]); // lower - if (coreFoundationVersion.count > 1) valid = valid && (kCFCoreFoundationVersionNumber < [coreFoundationVersion[1] floatValue]); // upper - } - - return valid; - }; - - NSArray *(^SPECIFIERS_FROM_ENTRY)(NSDictionary *, NSString *, NSString *, PSListController *) = ^NSArray * (NSDictionary *entry, NSString *sourceBundlePath, NSString *title, PSListController *listController) { - - NSString *bundleName = entry[@"bundle"]; - NSString *bundlePath = entry[@"bundlePath"]; - NSDictionary *specifierPlist = @{ @"items" : @[entry] }; - BOOL isBundle = bundleName != nil; - - if (isBundle) { - NSFileManager *fileManger = NSFileManager.defaultManager; - if (![fileManger fileExistsAtPath:bundlePath]) - bundlePath = [NSString stringWithFormat:@"/Library/PreferenceBundles/%@.bundle", bundleName]; - if (![fileManger fileExistsAtPath:bundlePath]) - bundlePath = [NSString stringWithFormat:@"/System/Library/PreferenceBundles/%@.bundle", bundleName]; - if (![fileManger fileExistsAtPath:bundlePath]) { - return nil; - } - } - - NSBundle *prefBundle = [NSBundle bundleWithPath:(isBundle ? bundlePath : sourceBundlePath)]; - NSMutableArray *bundleControllers = [listController valueForKey:@"_bundleControllers"]; - - void *handle = dlopen("/System/Library/PrivateFrameworks/Preferences.framework/Preferences", RTLD_LAZY); - NSArray *(*_SpecifiersFromPlist)(NSDictionary *,PSSpecifier *,id ,NSString *,NSBundle *,NSString **,NSString **,PSListController *,NSMutableArray **) = dlsym(handle, "SpecifiersFromPlist"); - NSArray *specs = _SpecifiersFromPlist(specifierPlist, nil, listController, title, prefBundle, NULL, NULL, listController, &bundleControllers); - - if (!specs.count) return nil; - - if (isBundle) { - if ([entry[PSBundleIsControllerKey] boolValue]) { - for (PSSpecifier *specifier in specs) { - [specifier setProperty:bundlePath forKey:PSLazilyLoadedBundleKey]; - [specifier setProperty:[NSBundle bundleWithPath:sourceBundlePath] forKey:@"pl_bundle"]; - if (!specifier.name) specifier.name = title; - } - } - } else { - Class customClass = NSClassFromString(@"PLCustomListController"); - Class localizedClass = NSClassFromString(@"PLLocalizedListController"); - BOOL isLocalizedBundle = ![sourceBundlePath.lastPathComponent isEqualToString:@"Preferences"]; - - if ((isLocalizedBundle && localizedClass) || customClass) { - - PSSpecifier *specifier = specs.firstObject; - [specifier setValue:(isLocalizedBundle ? localizedClass : customClass) forKey:@"detailControllerClass"]; - [specifier setProperty:prefBundle forKey:@"pl_bundle"]; - - if (![specifier.properties[PSTitleKey] isEqualToString:title]) { - [specifier setProperty:title forKey:@"pl_alt_plist_name"]; - if (!specifier.name) specifier.name = title; - } - } - } - - return specs; - }; - - NSArray *preferenceBundlePaths = [NSFileManager.defaultManager subpathsOfDirectoryAtPath:@"/Library/PreferenceLoader/Preferences" error:nil]; - NSMutableArray *searchableItems = [NSMutableArray new]; - - for (NSString *item in preferenceBundlePaths) - { - if (![item.pathExtension isEqualToString:@"plist"]) continue; - - NSString *plistPath = [NSString stringWithFormat:@"/Library/PreferenceLoader/Preferences/%@", item]; - NSDictionary *plist = DICTIONARY_WITH_PLIST(plistPath); - - if (!plist[@"entry"]) continue; - if (!PREFERENCE_FILTER_PASSES_ENVIRONMENT_CHECKS(plist[@"filter"] ?: plist[@"pl_filter"])) continue; - if (!PREFERENCE_FILTER_PASSES_ENVIRONMENT_CHECKS(plist[@"entry"][@"pl_filter"])) continue; - - NSString *bundlePath = [plistPath stringByDeletingLastPathComponent]; - NSString *title = [item.lastPathComponent stringByDeletingPathExtension]; - BOOL customInstall = access("/var/lib/dpkg/info/com.artikus.preferenceloader.list", F_OK) == 0 - || access("/var/lib/dpkg/info/com.creaturecoding.preferred.list", F_OK) == 0; - - NSArray *itemSpecifiers = !customInstall - ? SPECIFIERS_FROM_ENTRY(plist[@"entry"], bundlePath, title, self) - : [self specifiersFromEntry:plist[@"entry"] sourcePreferenceLoaderBundlePath:bundlePath title:title]; + return cell; +} - if (itemSpecifiers && itemSpecifiers.count) - { - for (PSSpecifier *specifier in itemSpecifiers) - { - if (![specifier propertyForKey:PSIconImageKey]) { - [specifier setProperty:[UIImage imageNamed:@"tweak"] forKey:PSIconImageKey]; - } +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - CSSearchableItemAttributeSet *attributeSet = [[CSSearchableItemAttributeSet alloc] initWithItemContentType:(NSString *) kUTTypeImage]; - attributeSet.title = specifier.name; - attributeSet.contentDescription = [NSString stringWithFormat:@"Tweak Settings \u2192 %@", specifier.name]; - attributeSet.thumbnailData = UIImagePNGRepresentation([specifier propertyForKey:PSIconImageKey]); - attributeSet.keywords = @[@"tweaks", @"packages", @"jailbreak", specifier.name]; + if (self.splitViewController.collapsed) { - NSString *uniqueIdentifier = [NSString stringWithFormat:@"%@", specifier.identifier]; - CSSearchableItem *searchItem = [[CSSearchableItem alloc] initWithUniqueIdentifier:uniqueIdentifier domainIdentifier:@"com.creaturecoding.tweaksettings" attributeSet:attributeSet]; - [searchableItems addObject:searchItem]; - } + [super tableView:tableView didSelectRowAtIndexPath:indexPath]; - [preferenceSpecifiers addObjectsFromArray:itemSpecifiers]; - } - } + } else { - if (preferenceSpecifiers.count) { - [preferenceSpecifiers sortUsingDescriptors:@[ - [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES selector:@selector(localizedCaseInsensitiveCompare:)]] - ]; + PSSpecifier *specifier = [self specifierAtIndexPath:indexPath]; + [APP_DELEGATE.navigationManager pushDetailControllerForSpecifier:specifier]; } +} - [CSSearchableIndex.defaultSearchableIndex deleteAllSearchableItemsWithCompletionHandler:^(NSError *error) { - [CSSearchableIndex.defaultSearchableIndex indexSearchableItems:searchableItems completionHandler:nil]; - }]; +#pragma mark - Private Methods - return preferenceSpecifiers; -} +- (void)_handleRefresh { -- (void)handleRefresh { _specifiers = nil; [self reloadSpecifiers]; } -- (void)handleActionButtonTapped:(UIBarButtonItem *)sender { +- (void)_handleCellLongPress:(UILongPressGestureRecognizer *)sender { - [self.navigationController presentViewController:ActionListAlert(sender) animated:YES completion:nil]; -} + if (sender.state == UIGestureRecognizerStateBegan) { -- (void)pushToLaunchIdentifier { + if (!TSUserDefaults.sharedDefaults.longPressOpensSettings) return; - if (_launchIdentifier) { - PSSpecifier *specifier = [self specifierForID:_launchIdentifier]; + CGPoint point = [sender locationInView:self.table]; + NSIndexPath *indexPath = [self.table indexPathForRowAtPoint:point]; + PSSpecifier *specifier = [self specifierAtIndexPath:indexPath]; + NSString *urlString = [NSString stringWithFormat:@"prefs:root=%@", specifier.identifier]; + NSURL *url = [NSURL URLWithString:[urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]]; - if (specifier) { + [APP_DELEGATE openApplicationURL:url]; + } +} - [specifier performControllerLoadAction]; +- (void)_handleActionButtonTapped:(UIBarButtonItem *)sender { - Class detailClass = [specifier respondsToSelector:@selector(detailControllerClass)] - ? [specifier detailControllerClass] - : [specifier valueForKey:@"detailControllerClass"] - ? : NSClassFromString(@"PLCustomListController"); + [APP_DELEGATE presentAsPopover:ActionListAlert() withSender:sender]; +} - if ([detailClass isSubclassOfClass:PSViewController.class]) { +- (void)_handleOpenSettings:(UIBarButtonItem *)sender { - id controller = [detailClass alloc]; - controller = ([controller respondsToSelector:@selector(initForContentSize:)]) - ? [controller initForContentSize:UIScreen.mainScreen.bounds.size] - : [controller init]; + [APP_DELEGATE presentAsPopover:[[UINavigationController alloc] initWithRootViewController:TSOptionsController.new] withSender:sender]; +} - if (controller && [controller isKindOfClass:PSViewController.class]) { +- (void)_preferencesChanged { - [controller setRootController:self.rootController]; - [controller setParentController:self]; - [controller setSpecifier:specifier]; - } + if (@available(iOS 11, *)) { + TSUserDefaults *defaults = TSUserDefaults.sharedDefaults; - [self.navigationController pushViewController:controller animated:NO]; - } - } + self.navigationItem.hidesSearchBarWhenScrolling = !defaults.alwaysShowSearchBar; + self.navigationController.navigationBar.prefersLargeTitles = defaults.useLargeTitlesOnRootList; + self.navigationItem.largeTitleDisplayMode = defaults.useLargeTitlesOnRootList + ? UINavigationItemLargeTitleDisplayModeAlways + : UINavigationItemLargeTitleDisplayModeNever; } - - _launchIdentifier = nil; } @end diff --git a/TweakSettings-App/TSRootNavigationManager.h b/TweakSettings-App/TSRootNavigationManager.h new file mode 100644 index 0000000..db594b4 --- /dev/null +++ b/TweakSettings-App/TSRootNavigationManager.h @@ -0,0 +1,42 @@ +// +// TSRootNavigationManager.h +// TweakSettings +// +// Created by Dana Buehre on 11/9/21. +// +// + + + +@class TSPrefsRootController; +@class TSRootListController; +@class TSSplitViewController; +@class UINavigationController; +@class PSListController; + +NS_ASSUME_NONNULL_BEGIN + +#define NAVIGATION_MANAGER APP_DELEGATE.navigationController + +@interface TSRootNavigationManager : NSObject + +@property(nonatomic, strong) NSURL *deferredLoadURL; + +@property(nonatomic, strong, readonly) TSPrefsRootController *rootController; +@property(nonatomic, strong, readonly) TSRootListController *rootListController; +@property(nonatomic, strong, readonly) TSSplitViewController *splitController; +@property(nonatomic, strong, readonly) UINavigationController *navigationController; + + +- (__kindof UIViewController *)topViewController; +- (__kindof UINavigationController *)topNavigationController; +- (BOOL)isCollapsed; + +- (NSURL *)urlForCurrentNavStack; +- (void)processDeferredURL:(BOOL)animated; +- (void)processURL:(NSURL *)url animated:(BOOL)animated; +- (void)pushDetailControllerForSpecifier:(PSSpecifier *)specifier; + +@end + +NS_ASSUME_NONNULL_END diff --git a/TweakSettings-App/TSRootNavigationManager.m b/TweakSettings-App/TSRootNavigationManager.m new file mode 100644 index 0000000..dc77179 --- /dev/null +++ b/TweakSettings-App/TSRootNavigationManager.m @@ -0,0 +1,192 @@ +// +// TSRootNavigationManager.m +// TweakSettings +// +// Created by Dana Buehre on 11/9/21. +// +// + +#import +#import +#import +#import +#import "TSRootNavigationManager.h" +#import "TSPrefsRootController.h" +#import "TSRootListController.h" +#import "TSPackageUtility.h" +#import "TSSplitViewController.h" + +@interface TSRootNavigationManager () + +@property(nonatomic, strong, readonly) NSArray *navigationStack; +@property(nonatomic, strong, readonly) PSListController *blankListController; + +@end + +@implementation TSRootNavigationManager + +- (instancetype)init +{ + if (self = [super init]) { + + _blankListController = PSListController.new; + _splitController = TSSplitViewController.new; + _rootListController = TSRootListController.new; + _navigationController = [[UINavigationController alloc] initWithRootViewController:_rootListController]; + _rootController = [[TSPrefsRootController alloc] initWithRootViewController:_blankListController rootListController:_rootListController]; + _rootListController.rootController = _rootController; + _splitController.delegate = self; + _splitController.navigationDelegate = self; + _splitController.preferredDisplayMode = UISplitViewControllerDisplayModeAllVisible; + _splitController.containerNavigationController = _rootController; + [_splitController setViewControllers:@[_navigationController, _rootController]]; + [_rootController setSupportedInterfaceOrientations:_splitController.supportedInterfaceOrientations]; + _navigationController.delegate = self; + _rootController.delegate = self; + } + + return self; +} + +#pragma mark - UISplitViewControllerDelegate + +- (BOOL)splitViewController:(UISplitViewController *)splitViewController collapseSecondaryViewController:(UIViewController *)secondaryViewController ontoPrimaryViewController:(UIViewController *)primaryViewController +{ + [self _getCurrentNavigationStack]; + [_rootController setViewControllers:_navigationStack animated:NO]; + return NO; +} + +- (UIViewController *)splitViewController:(UISplitViewController *)splitViewController separateSecondaryViewControllerFromPrimaryViewController:(UIViewController *)primaryViewController +{ + + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT (SELF isKindOfClass: %@) && SELF != %@", UINavigationController.class, _rootListController]; + NSMutableArray *viewControllers = [_rootListController.navigationController.viewControllers filteredArrayUsingPredicate:predicate].mutableCopy; + + if (!viewControllers || !viewControllers.count) { + + viewControllers = (_navigationStack && _navigationStack.count ? _navigationStack : @[_blankListController]).mutableCopy; + } + + [_navigationController popToRootViewControllerAnimated:NO]; + [_rootController setViewControllers:viewControllers animated:NO]; + + [self _resetNavigationAppearance:_navigationController]; + [self _resetNavigationAppearance:_rootController]; + + return _rootController; +} + +#pragma mark - PSSplitViewControllerNavigationDelegate + +- (void)splitViewControllerDidPopToRootController:(id)splitViewController +{ + [self _resetNavigationAppearance:_navigationController]; + [self _resetNavigationAppearance:_rootController]; +} + +#pragma mark - UINavigationControllerDelegate + +- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated { + [self _getCurrentNavigationStack]; + + if (_deferredLoadURL) { + + [self processDeferredURL:NO]; + } +} + +#pragma mark - Public Methods + +- (UIViewController *)topViewController +{ + return self.isCollapsed ? _navigationController.topViewController : _rootController.topViewController; +} + +- (UINavigationController *)topNavigationController +{ + return self.isCollapsed ? _navigationController : (typeof(_navigationController))_rootController; +} + +- (BOOL)isCollapsed +{ + return _splitController.collapsed; +} + +- (NSURL *)urlForCurrentNavStack { + NSMutableString *path = @"tweaks:root=".mutableCopy; + NSArray *viewControllers = self.topNavigationController.viewControllers; + + for (int i = 1; i < viewControllers.count; ++i) { + PSViewController *controller = viewControllers[(NSUInteger) i]; + if (!controller.specifier) continue; + [path appendFormat:(i > 1 ? @"/%@" : @"%@"), controller.specifier.identifier]; + } + + return [NSURL URLWithString:[path stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLFragmentAllowedCharacterSet]]; +} + +- (void)processDeferredURL:(BOOL)animated { + [self processURL:_deferredLoadURL animated:animated]; + _deferredLoadURL = nil; +} + +- (void)processURL:(NSURL *)url animated:(BOOL)animated { + if (!url) return; + + NSString * pathString = [url.absoluteString.stringByRemovingPercentEncoding stringByReplacingOccurrencesOfString:@"tweaks:root=" withString:@""]; + NSArray *components = [pathString componentsSeparatedByString:@"/"]; + NSMutableArray *controllers = [NSMutableArray new]; + PSListController *parentController = _rootListController; + + for (NSString *identifier in components) + { + if (!parentController) break; + PSSpecifier *specifier = [parentController specifierForID:identifier]; + if (!specifier) break; + PSViewController *controller = [TSPackageUtility controllerForSpecifier:specifier inController:parentController]; + if (!controller) break; + [controllers addObject:controller]; + + parentController = ([controller isKindOfClass:PSListController.class]) ? (PSListController *)controller : nil; + } + + if (self.isCollapsed) { + + [controllers insertObject:_rootListController atIndex:0]; + } + + [self.topNavigationController setViewControllers:controllers animated:animated]; +} + +- (void)pushDetailControllerForSpecifier:(PSSpecifier *)specifier { + PSViewController *controller = [TSPackageUtility controllerForSpecifier:specifier inController:_rootListController]; + + if (controller) { + + [self.topNavigationController setViewControllers:@[controller] animated:NO]; + [self _resetNavigationAppearance:self.rootController]; + } +} + +#pragma mark - Private Methods + +- (void)_resetNavigationAppearance:(UINavigationController *)controller { + [controller.navigationBar setBackgroundImage:nil forBarMetrics:UIBarMetricsDefault]; + [controller.navigationBar setShadowImage:nil]; + [controller.navigationBar setTintColor:nil]; + [controller.navigationBar setBarTintColor:nil]; + [controller.navigationBar setTitleTextAttributes:nil]; + if (@available(iOS 11, *)) { + + [controller.navigationBar setLargeTitleTextAttributes:nil]; + } +} + +- (void)_getCurrentNavigationStack { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF != %@", _blankListController]; + NSArray *viewControllers = [_rootController.viewControllers filteredArrayUsingPredicate:predicate]; + _navigationStack = viewControllers; +} + +@end diff --git a/TweakSettings-App/TSSearchableListController.h b/TweakSettings-App/TSSearchableListController.h index 817d9ae..7009863 100644 --- a/TweakSettings-App/TSSearchableListController.h +++ b/TweakSettings-App/TSSearchableListController.h @@ -13,6 +13,8 @@ @interface TSSearchableListController : PSListController -@property(nonatomic, strong) NSMutableArray *unfilteredSpecifiers; +@property (nonatomic, strong) NSMutableArray *unfilteredSpecifiers; +@property (nonatomic, assign) BOOL searchOnLoad; +@property (nonatomic, assign) BOOL showOnLoad; @end diff --git a/TweakSettings-App/TSSearchableListController.m b/TweakSettings-App/TSSearchableListController.m index 73786c3..3c09ed3 100644 --- a/TweakSettings-App/TSSearchableListController.m +++ b/TweakSettings-App/TSSearchableListController.m @@ -8,13 +8,36 @@ #import #import "TSSearchableListController.h" +#import "TSUserDefaults.h" @interface TSSearchableListController () @end @implementation TSSearchableListController { + UISearchController *_searchController; + BOOL _firstLoadComplete; +} + +- (instancetype)init { + + if (self = [super init]) { + + _showOnLoad = YES; + } + + return self; +} + +- (instancetype)initForContentSize:(CGSize)contentSize { + + if (self = [super init]) { + + _showOnLoad = YES; + } + + return self; } - (void)viewDidLoad { @@ -28,6 +51,7 @@ - (void)viewDidLoad { if (@available(iOS 11.0, *)) { self.navigationItem.searchController = _searchController; + self.navigationController.navigationBar.prefersLargeTitles = TSUserDefaults.sharedDefaults.useLargeTitlesOnRootList; } else { self.table.tableHeaderView = _searchController.searchBar; } @@ -35,11 +59,39 @@ - (void)viewDidLoad { self.unfilteredSpecifiers = self.specifiers; } +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + if (!_firstLoadComplete) { + + if (@available(iOS 11, *)) { + self.navigationItem.hidesSearchBarWhenScrolling = NO; + [self.navigationController.navigationBar sizeToFit]; + } + } +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + + + if (!_firstLoadComplete) { + + if (@available(iOS 11, *)) { + self.navigationItem.hidesSearchBarWhenScrolling = !TSUserDefaults.sharedDefaults.alwaysShowSearchBar; + } + + self->_firstLoadComplete = YES; + } +} + #pragma mark - UISearchResultsUpdating - (void)updateSearchResultsForSearchController:(nonnull UISearchController *)searchController { + __block NSString *searchText = searchController.searchBar.text; if (searchText && searchText.length > 0) { + HIGH_QUEUE(^{ NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.name contains[cd] %@", searchText]; __block NSMutableArray *filteredSpecifiers = [self.unfilteredSpecifiers filteredArrayUsingPredicate:predicate].mutableCopy; @@ -50,6 +102,7 @@ - (void)updateSearchResultsForSearchController:(nonnull UISearchController *)sea }); }); } else { + MAIN_QUEUE_UNSAFE(^{ self.specifiers = self.unfilteredSpecifiers; [self.table reloadData]; @@ -60,6 +113,7 @@ - (void)updateSearchResultsForSearchController:(nonnull UISearchController *)sea #pragma mark - UISearchControllerDelegate - (void)didDismissSearchController:(UISearchController *)searchController { + MAIN_QUEUE_UNSAFE(^{ self.specifiers = self.unfilteredSpecifiers; [self.table reloadData]; @@ -67,6 +121,7 @@ - (void)didDismissSearchController:(UISearchController *)searchController { } - (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar { + MAIN_QUEUE_UNSAFE(^{ self.specifiers = self.unfilteredSpecifiers; [self.table reloadData]; diff --git a/TweakSettings-App/TSSplitViewController.h b/TweakSettings-App/TSSplitViewController.h new file mode 100644 index 0000000..f4c3d20 --- /dev/null +++ b/TweakSettings-App/TSSplitViewController.h @@ -0,0 +1,17 @@ +// +// TSSplitViewController.h +// TweakSettings +// +// Created by Dana Buehre on 11/13/21. +// +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface TSSplitViewController : PSSplitViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/TweakSettings-App/TSSplitViewController.m b/TweakSettings-App/TSSplitViewController.m new file mode 100644 index 0000000..67ad396 --- /dev/null +++ b/TweakSettings-App/TSSplitViewController.m @@ -0,0 +1,21 @@ +// +// TSSplitViewController.m +// TweakSettings +// +// Created by Dana Buehre on 11/13/21. +// +// + +#import +#import "TSSplitViewController.h" + +@implementation TSSplitViewController + +//- (NSUInteger)supportedInterfaceOrientations { +// +// return UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad +// ? UIInterfaceOrientationMaskAllButUpsideDown +// : UIInterfaceOrientationMaskPortrait; +//} + +@end diff --git a/TweakSettings-App/TSUserDefaults.h b/TweakSettings-App/TSUserDefaults.h new file mode 100644 index 0000000..2af8d4a --- /dev/null +++ b/TweakSettings-App/TSUserDefaults.h @@ -0,0 +1,24 @@ +// +// Created by Dana Buehre on 11/13/21. +// + +#import + + +static NSString *const TSUserDefaultsChangedKey = @"@kTSUserDefaultsChanged"; +static NSString *const UseLargeTitlesOnRootListKey = @"kUseLargeTitlesOnRootList"; +static NSString *const AlwaysShowSearchBarKey = @"kAlwaysShowSearchBar"; +static NSString *const RequireActionConfirmationKey = @"kRequireActionConfirmation"; +static NSString *const LongPressOpensSettingsKey = @"kLongPressOpensSettings"; + +@interface TSUserDefaults : NSObject + +@property (nonatomic, strong) NSUserDefaults *defaults; + +@property (nonatomic, readwrite) BOOL useLargeTitlesOnRootList; +@property (nonatomic, readwrite) BOOL alwaysShowSearchBar; +@property (nonatomic, readwrite) BOOL requireActionConfirmation; +@property (nonatomic, readwrite) BOOL longPressOpensSettings; + ++ (instancetype)sharedDefaults; +@end \ No newline at end of file diff --git a/TweakSettings-App/TSUserDefaults.m b/TweakSettings-App/TSUserDefaults.m new file mode 100644 index 0000000..bac244f --- /dev/null +++ b/TweakSettings-App/TSUserDefaults.m @@ -0,0 +1,124 @@ +// +// Created by Dana Buehre on 11/13/21. +// + +#import "TSUserDefaults.h" + +@interface TSUserDefaults () +- (void)_preferenceNotificationReceived; +@end + +static void ReceivedNotification(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) { + if (observer) [(__bridge TSUserDefaults *) observer _preferenceNotificationReceived]; +} + +@implementation TSUserDefaults { + NSString *_bundleIdentifier; + NSString *_preferencesChangedIdentifier; + NSString *_preferencePath; +} + ++ (instancetype)sharedDefaults { + static TSUserDefaults *userDefaults; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + userDefaults = [TSUserDefaults new]; + }); + + return userDefaults; +} + +- (instancetype)init { + if (self = [super init]) { + + _bundleIdentifier = @"com.creaturecoding.tweaksettings"; + _preferencesChangedIdentifier = @"com.creaturecoding.tweaksettings/changed"; + _preferencePath = [NSHomeDirectory() stringByAppendingFormat:@"/Library/Preferences/%@.plist", _bundleIdentifier]; + + _defaults = NSUserDefaults.standardUserDefaults; + + CFNotificationCenterAddObserver( + CFNotificationCenterGetDarwinNotifyCenter(), + (__bridge void *) self, + (CFNotificationCallback) ReceivedNotification, + (__bridge CFStringRef) _preferencesChangedIdentifier, + NULL, + CFNotificationSuspensionBehaviorCoalesce + ); + + [self _setDefaults]; + } + + return self; +} + +#pragma mark - Properties + +- (BOOL)useLargeTitlesOnRootList { + + return [self.defaults boolForKey:UseLargeTitlesOnRootListKey]; +} + +- (void)setUseLargeTitlesOnRootList:(BOOL)useLargeTitlesOnRootList { + + [self.defaults setBool:useLargeTitlesOnRootList forKey:UseLargeTitlesOnRootListKey]; +} + +- (BOOL)alwaysShowSearchBar { + + return [self.defaults boolForKey:AlwaysShowSearchBarKey]; +} + +- (void)setAlwaysShowSearchBar:(BOOL)alwaysShowSearchBar { + + [self.defaults setBool:alwaysShowSearchBar forKey:AlwaysShowSearchBarKey]; +} + +- (BOOL)requireActionConfirmation { + + return [self.defaults boolForKey:RequireActionConfirmationKey]; +} + +- (void)setRequireActionConfirmation:(BOOL)requireActionConfirmation { + + [self.defaults setBool:requireActionConfirmation forKey:RequireActionConfirmationKey]; +} + +- (BOOL)longPressOpensSettings { + + return [self.defaults boolForKey:LongPressOpensSettingsKey]; +} + +- (void)setLongPressOpensSettings:(BOOL)longPressOpensSettings { + + [self.defaults setBool:longPressOpensSettings forKey:LongPressOpensSettingsKey]; +} + +- (void)synchronize { + + CFPreferencesSynchronize( + (__bridge CFStringRef) _bundleIdentifier, + kCFPreferencesCurrentUser, + kCFPreferencesAnyHost + ); +} + +#pragma mark - Private Methods + +- (void)_preferenceNotificationReceived { + + [self synchronize]; + [NSNotificationCenter.defaultCenter postNotificationName:TSUserDefaultsChangedKey object:nil]; +} + +- (void)_setDefaults { + + NSUserDefaults *defaults = self.defaults; + + if (![defaults objectForKey:UseLargeTitlesOnRootListKey]) [defaults setBool:YES forKey:UseLargeTitlesOnRootListKey]; + if (![defaults objectForKey:AlwaysShowSearchBarKey]) [defaults setBool:NO forKey:AlwaysShowSearchBarKey]; + if (![defaults objectForKey:RequireActionConfirmationKey]) [defaults setBool:YES forKey:RequireActionConfirmationKey]; + if (![defaults objectForKey:LongPressOpensSettingsKey]) [defaults setBool:YES forKey:LongPressOpensSettingsKey]; +} + +@end \ No newline at end of file diff --git a/TweakSettings-App/TSUtilityActionManager.h b/TweakSettings-App/TSUtilityActionManager.h index 17bc495..54c4014 100644 --- a/TweakSettings-App/TSUtilityActionManager.h +++ b/TweakSettings-App/TSUtilityActionManager.h @@ -20,11 +20,12 @@ extern NSString *const TSActionTypeReboot; extern NSString *const TSActionTypeUserspaceReboot; extern NSString *const TSActionTypeTweakInject; -extern inline NSString *TitleForActionType(NSString *type); -extern inline NSString *SubtitleForActionType(NSString *type); -extern inline int HandleActionForType(NSString *actionType); -extern inline UIAlertController *ActionAlertForType(NSString *actionType); -extern inline UIAlertController *ActionListAlert(id sender); -extern inline UIMenu *ActionListMenu(id sender) API_AVAILABLE(ios(13.0)); +extern NSString *TitleForActionType(NSString *type); +extern NSString *SubtitleForActionType(NSString *type); +extern BOOL CanRunWithoutConfirmation(NSString *actionType); +extern int HandleActionForType(NSString *actionType); +extern UIAlertController *ActionAlertForType(NSString *actionType); +extern UIAlertController *ActionListAlert(void); +extern UIMenu *ActionListMenu(void) API_AVAILABLE(ios(13.0)); #endif /* TSActionType_h */ diff --git a/TweakSettings-App/TSUtilityActionManager.m b/TweakSettings-App/TSUtilityActionManager.m index 64ebdae..7ba3c5d 100644 --- a/TweakSettings-App/TSUtilityActionManager.m +++ b/TweakSettings-App/TSUtilityActionManager.m @@ -41,6 +41,14 @@ return nil; } +BOOL CanRunWithoutConfirmation(NSString *actionType) { + + if (!actionType || !actionType.length) return NO; + return !([actionType isEqualToString:TSActionTypeReboot] + || [actionType isEqualToString:TSActionTypeLDRestart] + || [actionType isEqualToString:TSActionTypeUserspaceReboot]); +}; + int HandleActionForType(NSString *actionType) { if (!actionType || !actionType.length) return EXIT_FAILURE; @@ -63,7 +71,7 @@ int HandleActionForType(NSString *actionType) { NSString *title = TitleForActionType(actionType); NSString *message = [NSString stringWithFormat:NSLocalizedString(ALERT_ACTION_MESSAGE_KEY, nil), SubtitleForActionType(actionType)]; - UIAlertController *controller = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleActionSheet]; + UIAlertController *controller = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; [controller addAction:[UIAlertAction actionWithTitle:title style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { HandleActionForType(actionType); @@ -73,68 +81,66 @@ int HandleActionForType(NSString *actionType) { return controller; } -UIAlertController *ActionListAlert(id sender) { +UIAlertController *ActionListAlert(void) { BOOL userspace_supported = access("/odyssey/jailbreakd.plist", F_OK) == 0 || access("/taurine/jailbreakd.plist", F_OK) == 0; UIAlertController *controller = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; - controller.modalPresentationStyle = UIModalPresentationPopover; - controller.popoverPresentationController.barButtonItem = sender; [controller addAction:[UIAlertAction actionWithTitle:NSLocalizedString(RESPRING_TITLE_KEY, nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - [((TSAppDelegate *)UIApplication.sharedApplication) handleActionForType:TSActionTypeRespring withConfirmationSender:sender]; + [APP_DELEGATE handleActionForType:TSActionTypeRespring]; }]]; [controller addAction:[UIAlertAction actionWithTitle:NSLocalizedString(SAFEMODE_TITLE_KEY, nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - [((TSAppDelegate *)UIApplication.sharedApplication) handleActionForType:TSActionTypeSafemode withConfirmationSender:sender]; + [APP_DELEGATE handleActionForType:TSActionTypeSafemode]; }]]; [controller addAction:[UIAlertAction actionWithTitle:NSLocalizedString(UICACHE_TITLE_KEY, nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - [((TSAppDelegate *)UIApplication.sharedApplication) handleActionForType:TSActionTypeUICache withConfirmationSender:sender]; + [APP_DELEGATE handleActionForType:TSActionTypeUICache]; }]]; [controller addAction:[UIAlertAction actionWithTitle:NSLocalizedString(LDRESTART_TITLE_KEY, nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - [((TSAppDelegate *)UIApplication.sharedApplication) handleActionForType:TSActionTypeLDRestart withConfirmationSender:sender]; + [APP_DELEGATE handleActionForType:TSActionTypeLDRestart]; }]]; [controller addAction:[UIAlertAction actionWithTitle:NSLocalizedString(REBOOT_TITLE_KEY, nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - [((TSAppDelegate *)UIApplication.sharedApplication) handleActionForType:TSActionTypeReboot withConfirmationSender:sender]; + [APP_DELEGATE handleActionForType:TSActionTypeReboot]; }]]; if (userspace_supported) { [controller addAction:[UIAlertAction actionWithTitle:NSLocalizedString(USREBOOT_TITLE_KEY, nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - [((TSAppDelegate *)UIApplication.sharedApplication) handleActionForType:TSActionTypeUserspaceReboot withConfirmationSender:sender]; + [APP_DELEGATE handleActionForType:TSActionTypeUserspaceReboot]; }]]; } [controller addAction:[UIAlertAction actionWithTitle:NSLocalizedString(TWEAKINJECT_TITLE_KEY, nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - [((TSAppDelegate *)UIApplication.sharedApplication) handleActionForType:TSActionTypeTweakInject withConfirmationSender:sender]; + [APP_DELEGATE handleActionForType:TSActionTypeTweakInject]; }]]; [controller addAction:[UIAlertAction actionWithTitle:NSLocalizedString(ALERT_CANCEL_TITLE_KEY, nil) style:UIAlertActionStyleCancel handler:nil]]; return controller; } -UIMenu *ActionListMenu(id sender) API_AVAILABLE(ios(13.0)) { +UIMenu *ActionListMenu(void) API_AVAILABLE(ios(13.0)) { NSMutableArray *menuActions = [NSMutableArray new]; BOOL userspace_supported = access("/odyssey/jailbreakd.plist", F_OK) == 0 || access("/taurine/jailbreakd.plist", F_OK) == 0; [menuActions addObject:[UIAction actionWithTitle:NSLocalizedString(RESPRING_TITLE_KEY, nil) image:nil identifier:nil handler:^(__kindof UIAction *action) { - [((TSAppDelegate *)UIApplication.sharedApplication) handleActionForType:TSActionTypeRespring withConfirmationSender:sender]; + [APP_DELEGATE handleActionForType:TSActionTypeRespring]; }]]; [menuActions addObject:[UIAction actionWithTitle:NSLocalizedString(SAFEMODE_TITLE_KEY, nil) image:nil identifier:nil handler:^(__kindof UIAction *action) { - [((TSAppDelegate *)UIApplication.sharedApplication) handleActionForType:TSActionTypeSafemode withConfirmationSender:sender]; + [APP_DELEGATE handleActionForType:TSActionTypeSafemode]; }]]; [menuActions addObject:[UIAction actionWithTitle:NSLocalizedString(UICACHE_TITLE_KEY, nil) image:nil identifier:nil handler:^(__kindof UIAction *action) { - [((TSAppDelegate *)UIApplication.sharedApplication) handleActionForType:TSActionTypeUICache withConfirmationSender:sender]; + [APP_DELEGATE handleActionForType:TSActionTypeUICache]; }]]; [menuActions addObject:[UIAction actionWithTitle:NSLocalizedString(LDRESTART_TITLE_KEY, nil) image:nil identifier:nil handler:^(__kindof UIAction *action) { - [((TSAppDelegate *)UIApplication.sharedApplication) handleActionForType:TSActionTypeLDRestart withConfirmationSender:sender]; + [APP_DELEGATE handleActionForType:TSActionTypeLDRestart]; }]]; [menuActions addObject:[UIAction actionWithTitle:NSLocalizedString(REBOOT_TITLE_KEY, nil) image:nil identifier:nil handler:^(__kindof UIAction *action) { - [((TSAppDelegate *)UIApplication.sharedApplication) handleActionForType:TSActionTypeReboot withConfirmationSender:sender]; + [APP_DELEGATE handleActionForType:TSActionTypeReboot]; }]]; if (userspace_supported) { [menuActions addObject:[UIAction actionWithTitle:NSLocalizedString(USREBOOT_TITLE_KEY, nil) image:nil identifier:nil handler:^(__kindof UIAction *action) { - [((TSAppDelegate *)UIApplication.sharedApplication) handleActionForType:TSActionTypeUserspaceReboot withConfirmationSender:sender]; + [APP_DELEGATE handleActionForType:TSActionTypeUserspaceReboot]; }]]; } [menuActions addObject:[UIAction actionWithTitle:NSLocalizedString(TWEAKINJECT_TITLE_KEY, nil) image:nil identifier:nil handler:^(__kindof UIAction *action) { - [((TSAppDelegate *)UIApplication.sharedApplication) handleActionForType:TSActionTypeTweakInject withConfirmationSender:sender]; + [APP_DELEGATE handleActionForType:TSActionTypeTweakInject]; }]]; return [UIMenu menuWithTitle:@"" children:menuActions]; diff --git a/TweakSettings-App/en.lproj/Localizable.strings b/TweakSettings-App/en.lproj/Localizable.strings index b937c0b..b338431 100644 --- a/TweakSettings-App/en.lproj/Localizable.strings +++ b/TweakSettings-App/en.lproj/Localizable.strings @@ -22,7 +22,28 @@ /* Application Root Navigation Title */ "ROOT_NAVIGATION_TITLE_KEY" = "Tweaks"; "ROOT_NAVIGATION_RIGHT_TITLE_KEY" = "Actions"; +"ROOT_NAVIGATION_LEFT_TITLE_KEY" = "Settings"; /* Alert Strings */ "ALERT_CANCEL_TITLE_KEY" = "Cancel"; -"ALERT_ACTION_MESSAGE_KEY" = "This action will: %@\n\nAre you sure you want to proceed?"; \ No newline at end of file +"ALERT_DISMISS_TITLE_KEY" = "Dismiss"; +"ALERT_ACTION_MESSAGE_KEY" = "This action will: %@\n\nAre you sure you want to proceed?"; + +/* App Settings Strings */ +"ALWAYS_SHOW_SEARCH_BAR_NAME_KEY" = "Always Show SearchBar"; +"ALWAYS_SHOW_SEARCH_BAR_GROUP_KEY" = "When enabled, the search bar on the root page will always be visible, even when scrolling"; +"REQUIRE_ACTION_CONFIRMATION_NAME_KEY" = "Require Action Confirmation"; +"REQUIRE_ACTION_CONFIRMATION_GROUP_KEY" = "When enabled, actions will show a confirmation alert before executing. Note that Reboot, LDRestart, and Userspace Reboot actions will always show a confirmation alert before executing"; +"LONG_PRESS_OPENS_SETTINGS_NAME_KEY" = "Long Press Opens Settings"; +"LONG_PRESS_OPENS_SETTINGS_GROUP_KEY" = "When enabled, long pressing a cell on the root page will open the respective tweak in the Settings app"; +"USE_LARGE_TITLES_ON_ROOT_LIST_NAME_KEY" = "Use Large Titles"; +"USE_LARGE_TITLES_ON_ROOT_LIST_GROUP_KEY" = "When enabled, the root page will use a large navigation title"; +"APP_SETTINGS_TITLE_NAME_KEY" = "App Settings"; +"SOURCE_CODE_NAME_KEY" = "Source Code"; +"APP_VERSION_NAME_KEY" = "App Version"; + +/* Check For Update Strings */ +"CHECK_FOR_UPDATES_TITLE_KEY" = "Checking For Updates"; +"CHECK_FOR_UPDATES_ERROR_MESSAGE_KEY" = "There was an error checking for updates, please try again later, or check your network."; +"CHECK_FOR_UPDATES_PASS_MESSAGE_KEY" = "No updates available, (v%@) is the latest version"; +"CHECK_FOR_UPDATES_FAIL_MESSAGE_KEY" = "Update available, (v%@) has been released"; diff --git a/TweakSettings-App/libprefs.h b/TweakSettings-App/libprefs.h index 652317f..3e4d296 100644 --- a/TweakSettings-App/libprefs.h +++ b/TweakSettings-App/libprefs.h @@ -10,6 +10,7 @@ @interface PSListController (libprefs) - (NSArray *)specifiersFromEntry:(NSDictionary *)entry sourcePreferenceLoaderBundlePath:(NSString *)sourceBundlePath title:(NSString *)title; +- (PSViewController *)controllerForSpecifier:(PSSpecifier *)specifier; @end @interface PSSpecifier (libprefs) diff --git a/TweakSettings.xcodeproj/project.pbxproj b/TweakSettings.xcodeproj/project.pbxproj index d911d6a..03c0368 100644 --- a/TweakSettings.xcodeproj/project.pbxproj +++ b/TweakSettings.xcodeproj/project.pbxproj @@ -7,13 +7,20 @@ objects = { /* Begin PBXBuildFile section */ + 9B2410C17764CAD6DDF9697F /* TSPrefsRootController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B2415C8F4F658811D457B2F /* TSPrefsRootController.m */; }; 9B2413AFE1BFF43795ACA65A /* TSAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B241E3CD3D1F6D3B55609D3 /* TSAppDelegate.m */; }; 9B24144D3CF66D6F611A33BE /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B241B6A5EF71185ECED8986 /* main.m */; }; + 9B2414620AAAA9E6F0EFA9C5 /* TSRootNavigationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B24160C2F81CDF8D1985651 /* TSRootNavigationManager.m */; }; + 9B24150C2229EB2912D6B972 /* TSSplitViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B2419707513EEC97C39BA0C /* TSSplitViewController.m */; }; 9B2417125AF333AF7433AE3F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9B241145674E6322664B2C03 /* Assets.xcassets */; }; 9B2417E56F6BC6F4E56A7C8A /* libraries.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9B241CBB0D66BA984E5ACE30 /* libraries.plist */; }; + 9B2418031071B898F6F6C4C3 /* TSChangelogController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B241C07A10B2F57EE3E6BDE /* TSChangelogController.m */; }; + 9B2418FA5B1C717A218F1CB0 /* TSPackageUtility.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B241D1EF08E980D0C398043 /* TSPackageUtility.m */; }; + 9B2419DE2F448CE206B96E8D /* TSUserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B24192FDF28BB2496BBEA7B /* TSUserDefaults.m */; }; + 9B241A7A916D4D6CAE569F3E /* TSUtilityActionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B241649FFFF894DC1194290 /* TSUtilityActionManager.m */; }; 9B241AD7EE27F4BB6C24537F /* TSRootListController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B2413E3DE3004D22E25AAFB /* TSRootListController.m */; }; - 9B241E08CA97D4D566147CE2 /* TSUtilityActionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B2415290925BE7BE1FAC79B /* TSUtilityActionManager.m */; }; 9B241E2F90DD7CBD64A43B51 /* TSSearchableListController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B241E51979762427B0DEF19 /* TSSearchableListController.m */; }; + 9B241FBC80A9F03FB421F1D3 /* TSOptionsController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B241D3DD023C428A7AD45EF /* TSOptionsController.m */; }; 9B241FDA50F976918DC53162 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9B241DE01047656B78369A94 /* LaunchScreen.storyboard */; }; B86FC5D626645DE60011E4AF /* Preferences.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B871CD462651D3680058F1C2 /* Preferences.framework */; }; B871CDA0265601110058F1C2 /* icon_large.png in Resources */ = {isa = PBXBuildFile; fileRef = B871CD9F265601110058F1C2 /* icon_large.png */; }; @@ -23,22 +30,36 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 9B2410F33CBF4A245569CC80 /* TSSplitViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSSplitViewController.h; sourceTree = ""; }; 9B241145674E6322664B2C03 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 9B2411C5CDD06E84A912DF3A /* libprefs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = libprefs.h; sourceTree = ""; }; - 9B2413D1010D1C1F36578888 /* TSUtilityActionManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSUtilityActionManager.h; sourceTree = ""; }; + 9B24127C70ECB51EAE77F9EE /* TSPackageUtility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSPackageUtility.h; sourceTree = ""; }; 9B2413E3DE3004D22E25AAFB /* TSRootListController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSRootListController.m; sourceTree = ""; }; 9B2413E6B518A0A99C895507 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 9B2415290925BE7BE1FAC79B /* TSUtilityActionManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSUtilityActionManager.m; sourceTree = ""; }; + 9B24142E08BDFAEE4F8373EF /* TSChangelogController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSChangelogController.h; sourceTree = ""; }; 9B2415BA8335FBD99BD1A482 /* TweakSettings.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TweakSettings.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 9B2415C8F4F658811D457B2F /* TSPrefsRootController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSPrefsRootController.m; sourceTree = ""; }; + 9B24160C2F81CDF8D1985651 /* TSRootNavigationManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSRootNavigationManager.m; sourceTree = ""; }; + 9B241649FFFF894DC1194290 /* TSUtilityActionManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSUtilityActionManager.m; sourceTree = ""; }; 9B24169943CD9A7E1AA8E2FE /* TSAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TSAppDelegate.h; sourceTree = ""; }; 9B2416D9FECD758BFA318498 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.info; path = Info.plist; sourceTree = ""; }; + 9B24181BB83D9773FE8446BB /* TSPrefsRootController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSPrefsRootController.h; sourceTree = ""; }; + 9B24192FDF28BB2496BBEA7B /* TSUserDefaults.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSUserDefaults.m; sourceTree = ""; }; + 9B2419707513EEC97C39BA0C /* TSSplitViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSSplitViewController.m; sourceTree = ""; }; 9B2419CD47CC813402F66384 /* Localizable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Localizable.h; sourceTree = ""; }; 9B241B6A5EF71185ECED8986 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 9B241B6FA0ACC48629D31472 /* TSUtilityActionManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSUtilityActionManager.h; sourceTree = ""; }; 9B241BDCA3396202FEB00F19 /* TSRootListController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSRootListController.h; sourceTree = ""; }; + 9B241C07A10B2F57EE3E6BDE /* TSChangelogController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSChangelogController.m; sourceTree = ""; }; 9B241CA30D8199267740AE3F /* TSSearchableListController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TSSearchableListController.h; sourceTree = ""; }; 9B241CBB0D66BA984E5ACE30 /* libraries.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = libraries.plist; sourceTree = ""; }; + 9B241D1EF08E980D0C398043 /* TSPackageUtility.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSPackageUtility.m; sourceTree = ""; }; + 9B241D3DD023C428A7AD45EF /* TSOptionsController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSOptionsController.m; sourceTree = ""; }; + 9B241DE6A7BF5B084C8A0BA5 /* TSRootNavigationManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSRootNavigationManager.h; sourceTree = ""; }; 9B241E3CD3D1F6D3B55609D3 /* TSAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TSAppDelegate.m; sourceTree = ""; }; 9B241E51979762427B0DEF19 /* TSSearchableListController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TSSearchableListController.m; sourceTree = ""; }; + 9B241F0ED97E09EE8CF883DB /* TSOptionsController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSOptionsController.h; sourceTree = ""; }; + 9B241F39E85CCB4B2115C9D7 /* TSUserDefaults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSUserDefaults.h; sourceTree = ""; }; B86FC5C72663E8D80011E4AF /* entitlements.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = entitlements.plist; sourceTree = ""; }; B86FC5C82663E8D80011E4AF /* Makefile */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; B86FC5C92663E8D80011E4AF /* main.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = main.c; sourceTree = ""; }; @@ -99,8 +120,22 @@ 9B2411C5CDD06E84A912DF3A /* libprefs.h */, 9B2419CD47CC813402F66384 /* Localizable.h */, B871CDB5265991710058F1C2 /* Localizable.strings */, - 9B2413D1010D1C1F36578888 /* TSUtilityActionManager.h */, - 9B2415290925BE7BE1FAC79B /* TSUtilityActionManager.m */, + 9B2415C8F4F658811D457B2F /* TSPrefsRootController.m */, + 9B24181BB83D9773FE8446BB /* TSPrefsRootController.h */, + 9B241DE6A7BF5B084C8A0BA5 /* TSRootNavigationManager.h */, + 9B24160C2F81CDF8D1985651 /* TSRootNavigationManager.m */, + 9B2410F33CBF4A245569CC80 /* TSSplitViewController.h */, + 9B2419707513EEC97C39BA0C /* TSSplitViewController.m */, + 9B241F0ED97E09EE8CF883DB /* TSOptionsController.h */, + 9B241D3DD023C428A7AD45EF /* TSOptionsController.m */, + 9B24127C70ECB51EAE77F9EE /* TSPackageUtility.h */, + 9B241D1EF08E980D0C398043 /* TSPackageUtility.m */, + 9B241F39E85CCB4B2115C9D7 /* TSUserDefaults.h */, + 9B24192FDF28BB2496BBEA7B /* TSUserDefaults.m */, + 9B241B6FA0ACC48629D31472 /* TSUtilityActionManager.h */, + 9B241649FFFF894DC1194290 /* TSUtilityActionManager.m */, + 9B241C07A10B2F57EE3E6BDE /* TSChangelogController.m */, + 9B24142E08BDFAEE4F8373EF /* TSChangelogController.h */, ); path = "TweakSettings-App"; sourceTree = ""; @@ -243,7 +278,14 @@ 9B2413AFE1BFF43795ACA65A /* TSAppDelegate.m in Sources */, 9B241E2F90DD7CBD64A43B51 /* TSSearchableListController.m in Sources */, 9B241AD7EE27F4BB6C24537F /* TSRootListController.m in Sources */, - 9B241E08CA97D4D566147CE2 /* TSUtilityActionManager.m in Sources */, + 9B2410C17764CAD6DDF9697F /* TSPrefsRootController.m in Sources */, + 9B2414620AAAA9E6F0EFA9C5 /* TSRootNavigationManager.m in Sources */, + 9B24150C2229EB2912D6B972 /* TSSplitViewController.m in Sources */, + 9B241FBC80A9F03FB421F1D3 /* TSOptionsController.m in Sources */, + 9B2418FA5B1C717A218F1CB0 /* TSPackageUtility.m in Sources */, + 9B2419DE2F448CE206B96E8D /* TSUserDefaults.m in Sources */, + 9B241A7A916D4D6CAE569F3E /* TSUtilityActionManager.m in Sources */, + 9B2418031071B898F6F6C4C3 /* TSChangelogController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/layout/DEBIAN/control b/layout/DEBIAN/control index 12c0929..cf9cc6f 100644 --- a/layout/DEBIAN/control +++ b/layout/DEBIAN/control @@ -1,7 +1,7 @@ Package: com.creaturecoding.tweaksettings Name: TweakSettings Depends: firmware (>= 10.0), preferenceloader | com.creaturecoding.preferred -Version: 1.0.5 +Version: 1.0.6 Priority: optional Architecture: iphoneos-arm Description: Dedicated settings app for tweaks