├── .gitignore ├── ampere.png ├── banner.png ├── amperesettings ├── Resources │ ├── dark.png │ ├── icon.png │ ├── light.png │ ├── charging.png │ ├── critical.png │ ├── icon@2x.png │ ├── icon@3x.png │ ├── lowpower.png │ ├── Info.plist │ └── Root.plist ├── Makefile ├── layout │ └── Library │ │ └── PreferenceLoader │ │ └── Preferences │ │ └── AmpereSettings.plist ├── AmpSwitch.h ├── AMPRootListController.h ├── AmpSwitch.m └── AMPRootListController.m ├── entitlements.plist ├── control ├── .github └── ISSUE_TEMPLATE │ ├── feature-request.md │ └── bug-report.md ├── README.md ├── Makefile ├── Ampere.plist ├── LICENSE ├── Ampere.h └── Ampere.xm /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | .theos/ 3 | .DS_Store -------------------------------------------------------------------------------- /ampere.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MTACS/Ampere/HEAD/ampere.png -------------------------------------------------------------------------------- /banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MTACS/Ampere/HEAD/banner.png -------------------------------------------------------------------------------- /amperesettings/Resources/dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MTACS/Ampere/HEAD/amperesettings/Resources/dark.png -------------------------------------------------------------------------------- /amperesettings/Resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MTACS/Ampere/HEAD/amperesettings/Resources/icon.png -------------------------------------------------------------------------------- /amperesettings/Resources/light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MTACS/Ampere/HEAD/amperesettings/Resources/light.png -------------------------------------------------------------------------------- /amperesettings/Resources/charging.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MTACS/Ampere/HEAD/amperesettings/Resources/charging.png -------------------------------------------------------------------------------- /amperesettings/Resources/critical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MTACS/Ampere/HEAD/amperesettings/Resources/critical.png -------------------------------------------------------------------------------- /amperesettings/Resources/icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MTACS/Ampere/HEAD/amperesettings/Resources/icon@2x.png -------------------------------------------------------------------------------- /amperesettings/Resources/icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MTACS/Ampere/HEAD/amperesettings/Resources/icon@3x.png -------------------------------------------------------------------------------- /amperesettings/Resources/lowpower.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MTACS/Ampere/HEAD/amperesettings/Resources/lowpower.png -------------------------------------------------------------------------------- /entitlements.plist: -------------------------------------------------------------------------------- 1 | platform-application 2 | 3 | com.apple.private.security.no-sandbox 4 | 5 | com.apple.private.security.storage.AppBundles 6 | 7 | com.apple.private.security.storage.AppDataContainers 8 | -------------------------------------------------------------------------------- /control: -------------------------------------------------------------------------------- 1 | Package: com.mtac.ampere 2 | Name: Ampere 3 | Version: 1.4 4 | Architecture: iphoneos-arm 5 | Description: iOS 16 Style Battery Indicator 6 | Maintainer: MTAC 7 | Author: MTAC 8 | Section: Tweaks 9 | Depends: mobilesubstrate, firmware (>= 14.0), preferenceloader 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea to be added to this project 4 | title: "[FEATURE]" 5 | labels: '' 6 | assignees: MTACS 7 | 8 | --- 9 | 10 | # Please add a detailed description of the feature you would like added 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ampere 2 | Source for Ampere tweak for iOS 14, 15 & 16 3 | 4 | Available for rootful & rootless on [Havoc Repo](https://havoc.app/package/ampere). RootHide package available [here](https://mtac.app/repo/debs/com.mtac.ampere_1.4_iphoneos-arm64e.deb) 5 | 6 | ![banner](https://github.com/MTACS/Ampere/blob/1.1/banner.png) 7 | 8 | ## Building 9 | 10 | `git clone https://github.com/MTACS/Ampere.git` 11 | 12 | `cd Ampere` 13 | 14 | `make do` 15 | 16 | Make sure to set `PREFIX` and `THEOS_DEVICE_IP` to your setup and current device 17 | -------------------------------------------------------------------------------- /amperesettings/Makefile: -------------------------------------------------------------------------------- 1 | TARGET := iphone:clang:latest:14.0 2 | SYSROOT = $(THEOS)/sdks/iPhoneOS14.2.sdk 3 | 4 | include $(THEOS)/makefiles/common.mk 5 | 6 | BUNDLE_NAME = AmpereSettings 7 | 8 | AmpereSettings_FILES = AMPRootListController.m AmpSwitch.m 9 | AmpereSettings_FRAMEWORKS = UIKit 10 | AmpereSettings_PRIVATE_FRAMEWORKS = Preferences 11 | AmpereSettings_INSTALL_PATH = /Library/PreferenceBundles 12 | AmpereSettings_CFLAGS = -fobjc-arc -Wdeprecated-declarations -Wno-deprecated-declarations 13 | ifeq ($(THEOS_PACKAGE_SCHEME),roothide) 14 | AmpereSettings_LDFLAGS += -lroothide 15 | endif 16 | 17 | include $(THEOS_MAKE_PATH)/bundle.mk 18 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET := iphone:clang:latest:14.0 2 | INSTALL_TARGET_PROCESSES = SpringBoard Preferences 3 | ARCHS = arm64 arm64e 4 | SYSROOT = $(THEOS)/sdks/iPhoneOS14.2.sdk 5 | DEBUG = 1 6 | FINALPACKAGE = 0 7 | 8 | include $(THEOS)/makefiles/common.mk 9 | 10 | TWEAK_NAME = Ampere 11 | 12 | Ampere_CFLAGS = -fobjc-arc -Wdeprecated-declarations -Wno-deprecated-declarations 13 | Ampere_FILES = Ampere.xm 14 | Ampere_FRAMEWORKS = IOKit 15 | ifeq ($(THEOS_PACKAGE_SCHEME),roothide) 16 | Ampere_LDFLAGS += -lroothide 17 | endif 18 | 19 | include $(THEOS_MAKE_PATH)/tweak.mk 20 | SUBPROJECTS += amperesettings 21 | include $(THEOS_MAKE_PATH)/aggregate.mk 22 | -------------------------------------------------------------------------------- /amperesettings/layout/Library/PreferenceLoader/Preferences/AmpereSettings.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | entry 6 | 7 | bundle 8 | AmpereSettings 9 | cell 10 | PSLinkCell 11 | detail 12 | AMPRootListController 13 | icon 14 | icon.png 15 | isController 16 | 17 | label 18 | Ampere 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Ampere.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Filter 6 | 7 | Bundles 8 | 9 | com.apple.CarPlayApp 10 | com.apple.springboard 11 | com.apple.DashBoard 12 | 13 | Executables 14 | 15 | DashBoard 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report unexpected behavior 4 | title: "[BUG]" 5 | labels: '' 6 | assignees: MTACS 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Device Information (please complete the following information):** 27 | - Device: [e.g. iPhone 7+] 28 | - iOS Version: [e.g. iOS 15.6] 29 | - Ampere version [e.g. 1.0.3] 30 | -------------------------------------------------------------------------------- /amperesettings/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | AmpereSettings 9 | CFBundleIdentifier 10 | com.mtac.ampere.settings 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1.0 21 | NSPrincipalClass 22 | AMPRootListController 23 | 24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 D.F. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /amperesettings/AmpSwitch.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | #define LIGHT_TINT [UIColor colorWithRed: 0.20 green: 0.60 blue: 0.86 alpha: 1.00] 5 | #define DARK_TINT [UIColor colorWithRed: 0.36 green: 0.68 blue: 0.89 alpha: 1.00] 6 | 7 | typedef enum { 8 | AmpSwitchStyleLight, 9 | AmpSwitchStyleDark, 10 | AmpSwitchStyleDefault 11 | } AmpSwitchStyle; 12 | 13 | typedef enum { 14 | AmpSwitchStateOn, 15 | AmpSwitchStateOff 16 | } AmpSwitchState; 17 | 18 | typedef enum { 19 | AmpSwitchSizeBig, 20 | AmpSwitchSizeNormal, 21 | AmpSwitchSizeSmall 22 | } AmpSwitchSize; 23 | 24 | @protocol AmpSwitchDelegate 25 | - (void)switchStateChanged:(AmpSwitchState)currentState; 26 | @end 27 | 28 | @interface AmpSwitch : UIControl 29 | @property (nonatomic, assign) id delegate; 30 | @property (nonatomic, assign) BOOL isOn; 31 | @property (nonatomic, assign) BOOL isEnabled; 32 | @property (nonatomic, strong) UIColor *thumbOnTintColor; 33 | @property (nonatomic, strong) UIColor *thumbOffTintColor; 34 | @property (nonatomic, strong) UIColor *trackOnTintColor; 35 | @property (nonatomic, strong) UIColor *trackOffTintColor; 36 | @property (nonatomic, strong) UIColor *thumbDisabledTintColor; 37 | @property (nonatomic, strong) UIColor *trackDisabledTintColor; 38 | @property (nonatomic, strong) UIButton *switchThumb; 39 | @property (nonatomic, strong) UIView *track; 40 | - (id)init; 41 | - (id)initWithSize:(AmpSwitchSize)size state:(AmpSwitchState)state; 42 | - (id)initWithSize:(AmpSwitchSize)size style:(AmpSwitchStyle)style state:(AmpSwitchState)state; 43 | - (BOOL)getSwitchState; 44 | - (void)setOn:(BOOL)on; 45 | - (void)setOn:(BOOL)on animated:(BOOL)animated; 46 | - (void)setThumbState:(BOOL)state animated:(BOOL)animated; 47 | @end -------------------------------------------------------------------------------- /amperesettings/AMPRootListController.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | #import 5 | #import "AmpSwitch.h" 6 | #import "spawn.h" 7 | #import 8 | 9 | #define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending) 10 | 11 | static NSString *domain = @"com.mtac.amp"; 12 | 13 | @interface NSUserDefaults (Ampere) 14 | - (id)objectForKey:(NSString *)key inDomain:(NSString *)domain; 15 | - (void)setObject:(id)value forKey:(NSString *)key inDomain:(NSString *)domain; 16 | @end 17 | 18 | @interface UIView (Ampere) 19 | - (id)_viewControllerForAncestor; 20 | @end 21 | 22 | @interface UIColor (Ampere) 23 | + (id)tableCellGroupedBackgroundColor; 24 | @end 25 | 26 | @interface BSPlatform : NSObject 27 | + (id)sharedInstance; 28 | - (NSInteger)homeButtonType; 29 | @end 30 | 31 | @interface UINavigationItem (Ampere) 32 | @property (assign, nonatomic) UINavigationBar *navigationBar; 33 | @end 34 | 35 | @interface PSTableCell (Ampere) 36 | - (void)setValue:(id)arg0; 37 | @end 38 | 39 | @interface FBSSystemService : NSObject 40 | + (id)sharedService; 41 | - (void)sendActions:(id)arg1 withResult:(id)arg2; 42 | @end 43 | 44 | @interface BSAction : NSObject 45 | @end 46 | 47 | @interface SBSRelaunchAction : BSAction 48 | + (id)actionWithReason:(id)arg1 options:(unsigned long long)arg2 targetURL:(id)arg3; 49 | @end 50 | 51 | @interface AmpColorCell : PSControlTableCell 52 | @property (nonatomic, retain) UIButton *control; 53 | - (NSDictionary *)dictionaryForColor:(UIColor *)color; 54 | - (void)selectColor; 55 | @end 56 | 57 | @interface AmpStepperCell : PSControlTableCell 58 | @property (nonatomic, retain) UIStepper *control; 59 | @end 60 | 61 | @interface AmpSwitchCell : PSSwitchTableCell 62 | - (void)selectColor; 63 | - (UIColor *)selectedColor; 64 | @end 65 | 66 | @interface AmpSelectorCell : PSControlTableCell 67 | @property (nonatomic, retain) UIButton *control; 68 | @end 69 | 70 | @interface AmpCodeCell : PSTableCell 71 | @end 72 | 73 | @interface AMPRootListController : PSListController { 74 | UITableView *_table; 75 | } 76 | @property (nonatomic, strong) UIView *headerView; 77 | @property (nonatomic, strong) UIImageView *batteryView; 78 | @property (nonatomic, retain) UILabel *percentageLabel; 79 | @property (nonatomic, retain) UILabel *sectionSegmentLabel; 80 | @property (nonatomic, retain) UISegmentedControl *segmentedControl; 81 | @property (nonatomic, strong) AmpSwitch *enableSwitch; 82 | - (void)setEnableSwitchState; 83 | @end 84 | -------------------------------------------------------------------------------- /Ampere.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "spawn.h" 3 | #import 4 | #include 5 | 6 | #ifndef kCFCoreFoundationVersionNumber_iOS_16_0 7 | #define kCFCoreFoundationVersionNumber_iOS_16_0 1946.10 8 | #endif 9 | #define kSLSystemVersioniOS16 kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_16_0 10 | 11 | #define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending) 12 | #define SYSTEM_VERSION_GREATER_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending) 13 | 14 | #define WIDTH [UIScreen mainScreen].bounds.size.width 15 | #define HEIGHT [UIScreen mainScreen].bounds.size.height 16 | 17 | extern NSString *const kCAFilterDestOut; 18 | 19 | static NSString *domain = @"com.mtac.amp"; 20 | static NSString *preferencesNotification = @"com.mtac.amp/preferences.changed"; 21 | static NSString *statusBarNotification = @"com.mtac.amp/statusbar.changed"; 22 | static BOOL enabled; 23 | static BOOL showBolt; 24 | static BOOL useGesture; 25 | static BOOL useStatsModule; 26 | static BOOL overrideColorStandard; 27 | static BOOL overrideColorCharging; 28 | static BOOL overrideColorLowPower; 29 | static BOOL overrideColorCritical; 30 | static NSInteger textStyle; 31 | static NSInteger fontSize; 32 | static NSInteger batterySizing; 33 | 34 | @interface CALayer (Ampere) 35 | @property (nonatomic, retain) NSString *compositingFilter; 36 | @property (nonatomic, assign) BOOL allowsGroupOpacity; 37 | @property (nonatomic, assign) BOOL allowsGroupBlending; 38 | @property (copy) NSString *cornerCurve; 39 | @end 40 | 41 | @interface _CDBatterySaver : NSObject 42 | + (id)batterySaver; 43 | - (NSInteger)getPowerMode; 44 | - (BOOL)setPowerMode:(NSInteger)arg0 error:(id)arg1; 45 | @end 46 | 47 | @interface _PMLowPowerMode : NSObject 48 | + (id)sharedInstance; 49 | - (NSInteger)getPowerMode; 50 | - (void)setPowerMode:(NSInteger)arg0 fromSource:(id)arg1; 51 | - (void)setPowerMode:(NSInteger)arg0 fromSource:(id)arg1 withCompletion:(id)arg2; 52 | @end 53 | 54 | @interface NSUserDefaults (Ampere) 55 | - (id)objectForKey:(NSString *)key inDomain:(NSString *)domain; 56 | - (void)setObject:(id)value forKey:(NSString *)key inDomain:(NSString *)domain; 57 | @end 58 | 59 | @interface _UIBatteryView : UIView 60 | @property (readonly, nonatomic, getter=isLowBattery) BOOL lowBattery; 61 | @property (retain, nonatomic) UIImageView *ampereImageView; 62 | @property (retain, nonatomic) UILabel *percentageLabel; 63 | @property (retain, nonatomic) CALayer *fillLayer; 64 | @property (retain, nonatomic) CALayer *pinLayer; 65 | @property (retain, nonatomic) CALayer *bodyLayer; 66 | @property (readonly, nonatomic) CAShapeLayer *bodyShapeLayer; 67 | @property (copy, nonatomic) UIColor *pinColor; 68 | @property (copy, nonatomic) UIColor *bodyColor; 69 | @property (nonatomic) CGFloat pinColorAlpha; 70 | @property (nonatomic) CGFloat bodyColorAlpha; 71 | @property (nonatomic) CGFloat chargePercent; 72 | @property (nonatomic) BOOL saverModeActive; 73 | @property (nonatomic) NSInteger chargingState; 74 | @property (nonatomic) NSInteger iconSize; 75 | - (CGRect)_bodyRectForTraitCollection:(id)arg0; 76 | - (struct CGRect )_updateBodyLayers; 77 | - (id)_batteryFillColor; 78 | - (id)_batteryTextColor; 79 | - (void)_updateBatteryFillColor; 80 | - (void)_updatePercentage; 81 | - (void)_updatePercentageFont; 82 | - (id)initWithSizeCategory:(NSInteger)arg0; 83 | - (UIColor *)ampereFillColor; 84 | @end 85 | 86 | @interface BCUIRowView : UIView 87 | @property (nonatomic, strong) _UIBatteryView *ampereBatteryView; 88 | @property (nonatomic) NSInteger percentCharge; 89 | @end 90 | 91 | @interface BCUIBatteryView : _UIBatteryView 92 | @end 93 | 94 | @interface _UIStaticBatteryView : _UIBatteryView 95 | - (void)setShowsPercentage:(BOOL)arg0; 96 | @end 97 | 98 | @interface UIStatusBarItem : NSObject 99 | @end 100 | 101 | @interface _UIStatusBarDisplayItem : NSObject 102 | @property (nonatomic, getter=isEnabled) BOOL enabled; 103 | @end 104 | 105 | @interface _UIStatusBarStringView : UIView 106 | - (void)setText:(id)arg0; 107 | @end 108 | 109 | @interface _UIStatusBarBatteryItem : UIStatusBarItem 110 | @property (retain, nonatomic) _UIStatusBarStringView *percentView; 111 | + (id)staticIconDisplayIdentifier; 112 | + (id)iconDisplayIdentifier; 113 | + (id)percentDisplayIdentifier; 114 | + (id)chargingDisplayIdentifier; 115 | - (void)toggleLowPower:(id)sender; 116 | @end 117 | 118 | @interface _UIStatusBarDataBatteryEntry : NSObject 119 | @property (nonatomic) NSInteger state; 120 | @end 121 | 122 | @interface MTMaterialView : UIView 123 | @end 124 | 125 | @protocol CCUIContentModuleContentViewController 126 | @end 127 | 128 | @protocol CCUIContentModuleBackgroundViewController 129 | @end 130 | 131 | @interface CCUIContentModuleContentContainerView : UIView { 132 | MTMaterialView *_moduleMaterialView; 133 | } 134 | @end 135 | 136 | @interface CCUIContentModuleContainerViewController : UIViewController 137 | @property (retain, nonatomic) UIViewController *contentViewController; 138 | @property (retain, nonatomic) UIViewController *backgroundViewController; 139 | @property (retain, nonatomic) CCUIContentModuleContentContainerView *contentContainerView; 140 | @property (copy, nonatomic) NSString *moduleIdentifier; 141 | @end 142 | 143 | @interface CCUIToggleModule : NSObject 144 | @end 145 | 146 | @interface CCUIButtonModuleView : UIControl 147 | @end 148 | 149 | @interface CCUIButtonModuleViewController : UIViewController 150 | @property (readonly, nonatomic) CCUIButtonModuleView *buttonView; 151 | @end 152 | 153 | @interface CCUIToggleViewController : CCUIButtonModuleViewController 154 | @property (weak, nonatomic) CCUIToggleModule *module; 155 | @property (readonly, nonatomic) CGFloat preferredExpandedContentHeight; 156 | @property (readonly, nonatomic) CGFloat preferredExpandedContentWidth; 157 | @property (readonly, nonatomic) BOOL providesOwnPlatter; 158 | @property (readonly, nonatomic) BOOL shouldPerformClickInteraction; 159 | @property (readonly, nonatomic) BOOL shouldPerformHoverInteraction; 160 | @end 161 | 162 | @interface CCUIContentModuleContext : NSObject 163 | @property (readonly, copy, nonatomic) NSString *moduleIdentifier; 164 | @end 165 | 166 | @interface CCUIMenuModuleViewController : CCUIButtonModuleViewController 167 | @property (weak, nonatomic) CCUIContentModuleContext *contentModuleContext; 168 | @end 169 | 170 | @interface CCUILowPowerModuleViewController : CCUIMenuModuleViewController 171 | @end 172 | 173 | @interface CCUILowPowerModule : NSObject 174 | @end 175 | 176 | @interface AMPStatsController : UIViewController 177 | @property (nonatomic, strong) UITableView *tableView; 178 | @end -------------------------------------------------------------------------------- /amperesettings/Resources/Root.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | items 6 | 7 | 8 | cell 9 | PSGroupCell 10 | label 11 | Appearance 12 | 13 | 14 | cellClass 15 | AmpSelectorCell 16 | default 17 | 0 18 | defaults 19 | com.mtac.amp 20 | label 21 | Percent Color 22 | subtitle 23 | Swipe for more info 24 | key 25 | textStyle 26 | values 27 | 28 | 0 29 | 1 30 | 2 31 | 32 | titles 33 | 34 | Default 35 | Transparent 36 | Custom 37 | 38 | images 39 | 40 | applelogo 41 | shadow 42 | paintpalette.fill 43 | 44 | PostNotification 45 | com.mtac.amp/preferences.changed 46 | iconImageSystem 47 | 48 | name 49 | percent 50 | 51 | 52 | 53 | cellClass 54 | AmpColorCell 55 | defaults 56 | com.mtac.amp 57 | key 58 | textColor 59 | label 60 | Text Color 61 | subtitle 62 | Set percentage label color 63 | height 64 | 70 65 | id 66 | textColor 67 | PostNotification 68 | com.mtac.amp/preferences.changed 69 | iconImageSystem 70 | 71 | name 72 | paintbrush.pointed.fill 73 | 74 | 75 | 76 | cell 77 | PSGroupCell 78 | label 79 | Colors 80 | 81 | 82 | cell 83 | PSSwitchCell 84 | cellClass 85 | AmpSwitchCell 86 | default 87 | 88 | defaults 89 | com.mtac.amp 90 | label 91 | Fill Color 92 | subtitle 93 | Override normal color. Swipe cell to set color 94 | key 95 | overrideColorStandard 96 | id 97 | overrideColorStandard 98 | height 99 | 85 100 | PostNotification 101 | com.mtac.amp/preferences.changed 102 | iconImageSystem 103 | 104 | name 105 | battery.100 106 | 107 | 108 | 109 | cell 110 | PSSwitchCell 111 | cellClass 112 | AmpSwitchCell 113 | default 114 | 115 | defaults 116 | com.mtac.amp 117 | label 118 | Fill Color 119 | subtitle 120 | Override charging color. Swipe cell to set color 121 | key 122 | overrideColorCharging 123 | id 124 | overrideColorCharging 125 | height 126 | 85 127 | PostNotification 128 | com.mtac.amp/preferences.changed 129 | iconImageSystem 130 | 131 | name 132 | battery.100 133 | 134 | 135 | 136 | cell 137 | PSSwitchCell 138 | cellClass 139 | AmpSwitchCell 140 | default 141 | 142 | defaults 143 | com.mtac.amp 144 | label 145 | Fill Color 146 | subtitle 147 | Override low power color. Swipe cell to set color 148 | key 149 | overrideColorLowPower 150 | id 151 | overrideColorLowPower 152 | height 153 | 85 154 | PostNotification 155 | com.mtac.amp/preferences.changed 156 | iconImageSystem 157 | 158 | name 159 | battery.100 160 | 161 | 162 | 163 | cell 164 | PSSwitchCell 165 | cellClass 166 | AmpSwitchCell 167 | default 168 | 169 | defaults 170 | com.mtac.amp 171 | label 172 | Fill Color 173 | subtitle 174 | Override critical color. Swipe cell to set color 175 | key 176 | overrideColorCritical 177 | id 178 | overrideColorCritical 179 | height 180 | 85 181 | PostNotification 182 | com.mtac.amp/preferences.changed 183 | iconImageSystem 184 | 185 | name 186 | battery.100 187 | 188 | 189 | 190 | cell 191 | PSGroupCell 192 | label 193 | Font 194 | 195 | 196 | cellClass 197 | AmpStepperCell 198 | default 199 | 10 200 | defaults 201 | com.mtac.amp 202 | key 203 | fontSize 204 | id 205 | fontSize 206 | label 207 | Font Size: 208 | subtitle 209 | Swipe left to reset 210 | max 211 | 12 212 | min 213 | 4 214 | height 215 | 65 216 | PostNotification 217 | com.mtac.amp/preferences.changed 218 | iconImageSystem 219 | 220 | name 221 | textformat 222 | 223 | 224 | 225 | cell 226 | PSGroupCell 227 | label 228 | Bolt 229 | 230 | 231 | cell 232 | PSSwitchCell 233 | cellClass 234 | AmpSwitchCell 235 | default 236 | 237 | defaults 238 | com.mtac.amp 239 | label 240 | Show Charging Bolt 241 | subtitle 242 | Moves charging bolt next to battery percentage 243 | key 244 | showBolt 245 | id 246 | showBolt 247 | height 248 | 85 249 | PostNotification 250 | com.mtac.amp/preferences.changed 251 | iconImageSystem 252 | 253 | name 254 | bolt.fill 255 | 256 | 257 | 258 | cell 259 | PSGroupCell 260 | label 261 | Gestures 262 | 263 | 264 | cell 265 | PSSwitchCell 266 | cellClass 267 | AmpSwitchCell 268 | default 269 | 270 | defaults 271 | com.mtac.amp 272 | label 273 | Use Low Power Gesture 274 | subtitle 275 | Tap battery icon to toggle Low Power mode 276 | key 277 | useGesture 278 | height 279 | 85 280 | id 281 | lowPower 282 | PostNotification 283 | com.mtac.amp/preferences.changed 284 | iconImageSystem 285 | 286 | name 287 | hand.point.up.fill 288 | 289 | 290 | 291 | cell 292 | PSGroupCell 293 | label 294 | Control Center 295 | 296 | 297 | cell 298 | PSSwitchCell 299 | cellClass 300 | AmpSwitchCell 301 | default 302 | 303 | defaults 304 | com.mtac.amp 305 | label 306 | Enable Battery Info 307 | subtitle 308 | Force Touch on Low Power Mode module in Control Center to view 309 | key 310 | useStatsModule 311 | height 312 | 85 313 | id 314 | statsModule 315 | PostNotification 316 | com.mtac.amp/preferences.changed 317 | iconImageSystem 318 | 319 | name 320 | switch.2 321 | 322 | 323 | 324 | cell 325 | PSGroupCell 326 | label 327 | Source 328 | 329 | 330 | cellClass 331 | AmpCodeCell 332 | defaults 333 | com.mtac.amp 334 | key 335 | lowPowerColor 336 | label 337 | View Source Code 338 | subtitle 339 | https://github.com/MTACS/Ampere 340 | height 341 | 70 342 | iconImageSystem 343 | 344 | name 345 | link 346 | 347 | 348 | 349 | cell 350 | PSGroupCell 351 | label 352 | 353 | 354 | 355 | cell 356 | PSGroupCell 357 | label 358 | 359 | 360 | 361 | title 362 | Ampere 363 | 364 | 365 | -------------------------------------------------------------------------------- /amperesettings/AmpSwitch.m: -------------------------------------------------------------------------------- 1 | #import "AmpSwitch.h" 2 | 3 | @interface AmpSwitch () 4 | @property (nonatomic) CGFloat trackThickness; 5 | @property (nonatomic) CGFloat thumbSize; 6 | @end 7 | 8 | @implementation AmpSwitch { 9 | float thumbOnPosition; 10 | float thumbOffPosition; 11 | AmpSwitchStyle thumbStyle; 12 | } 13 | - (id)init { 14 | self = [self initWithSize:AmpSwitchSizeNormal style:AmpSwitchStyleDefault state:AmpSwitchStateOff]; 15 | return self; 16 | } 17 | - (id)initWithSize:(AmpSwitchSize)size state:(AmpSwitchState)state { 18 | self.thumbOnTintColor = [UIColor colorWithRed:52./255. green:109./255. blue:241./255. alpha:1.0]; 19 | self.thumbOffTintColor = [UIColor colorWithRed:249./255. green:249./255. blue:249./255. alpha:1.0]; 20 | self.trackOnTintColor = [UIColor colorWithRed:143./255. green:179./255. blue:247./255. alpha:1.0]; 21 | self.trackOffTintColor = [UIColor colorWithRed:193./255. green:193./255. blue:193./255. alpha:1.0]; 22 | self.thumbDisabledTintColor = [UIColor colorWithRed:174./255. green:174./255. blue:174./255. alpha:1.0]; 23 | self.trackDisabledTintColor = [UIColor colorWithRed:203./255. green:203./255. blue:203./255. alpha:1.0]; 24 | self.isEnabled = YES; 25 | 26 | CGRect frame; 27 | CGRect trackFrame = CGRectZero; 28 | CGRect thumbFrame = CGRectZero; 29 | switch (size) { 30 | case AmpSwitchSizeBig: 31 | frame = CGRectMake(0, 0, 50, 40); 32 | self.trackThickness = 23.0; 33 | self.thumbSize = 31.0; 34 | break; 35 | 36 | case AmpSwitchSizeNormal: 37 | frame = CGRectMake(0, 0, 40, 30); 38 | self.trackThickness = 17.0; 39 | self.thumbSize = 24.0; 40 | break; 41 | 42 | case AmpSwitchSizeSmall: 43 | frame = CGRectMake(0, 0, 30, 25); 44 | self.trackThickness = 13.0; 45 | self.thumbSize = 18.0; 46 | break; 47 | 48 | default: 49 | frame = CGRectMake(0, 0, 40, 30); 50 | self.trackThickness = 13.0; 51 | self.thumbSize = 20.0; 52 | break; 53 | } 54 | 55 | trackFrame.size.height = self.trackThickness; 56 | trackFrame.size.width = frame.size.width; 57 | trackFrame.origin.x = 0.0; 58 | trackFrame.origin.y = (frame.size.height-trackFrame.size.height)/2; 59 | thumbFrame.size.height = self.thumbSize; 60 | thumbFrame.size.width = thumbFrame.size.height; 61 | thumbFrame.origin.x = 0.0; 62 | thumbFrame.origin.y = (frame.size.height-thumbFrame.size.height)/2; 63 | 64 | self = [super initWithFrame:frame]; 65 | 66 | self.track = [[UIView alloc] initWithFrame:trackFrame]; 67 | self.track.backgroundColor = [UIColor grayColor]; 68 | self.track.layer.cornerRadius = MIN(self.track.frame.size.height, self.track.frame.size.width)/2; 69 | [self addSubview:self.track]; 70 | 71 | self.switchThumb = [[UIButton alloc] initWithFrame:thumbFrame]; 72 | self.switchThumb.backgroundColor = [UIColor whiteColor]; 73 | self.switchThumb.layer.cornerRadius = self.switchThumb.frame.size.height/2; 74 | self.switchThumb.layer.shadowOpacity = 0.5; 75 | self.switchThumb.layer.shadowOffset = CGSizeMake(0.0, 1.0); 76 | self.switchThumb.layer.shadowColor = [UIColor blackColor].CGColor; 77 | self.switchThumb.layer.shadowRadius = 2.0f; 78 | [self.switchThumb addTarget:self action:@selector(onTouchDown:withEvent:) forControlEvents:UIControlEventTouchDown]; 79 | [self.switchThumb addTarget:self action:@selector(onTouchUpOutsideOrCanceled:withEvent:) forControlEvents:UIControlEventTouchUpOutside]; 80 | [self.switchThumb addTarget:self action:@selector(switchThumbTapped:) forControlEvents:UIControlEventTouchUpInside]; 81 | [self.switchThumb addTarget:self action:@selector(onTouchDragInside:withEvent:) forControlEvents:UIControlEventTouchDragInside]; 82 | [self.switchThumb addTarget:self action:@selector(onTouchUpOutsideOrCanceled:withEvent:) forControlEvents:UIControlEventTouchCancel]; 83 | 84 | [self addSubview:self.switchThumb]; 85 | 86 | thumbOnPosition = self.frame.size.width - self.switchThumb.frame.size.width; 87 | thumbOffPosition = self.switchThumb.frame.origin.x; 88 | 89 | switch (state) { 90 | case AmpSwitchStateOn: 91 | self.isOn = YES; 92 | self.switchThumb.backgroundColor = self.thumbOnTintColor; 93 | CGRect thumbFrame = self.switchThumb.frame; 94 | thumbFrame.origin.x = thumbOnPosition; 95 | self.switchThumb.frame = thumbFrame; 96 | break; 97 | 98 | case AmpSwitchStateOff: 99 | self.isOn = NO; 100 | self.switchThumb.backgroundColor = self.thumbOffTintColor; 101 | break; 102 | 103 | default: 104 | self.isOn = NO; 105 | self.switchThumb.backgroundColor = self.thumbOffTintColor; 106 | break; 107 | } 108 | 109 | UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(switchAreaTapped:)]; 110 | [self addGestureRecognizer:singleTap]; 111 | return self; 112 | } 113 | - (id)initWithSize:(AmpSwitchSize)size style:(AmpSwitchStyle)style state:(AmpSwitchState)state { 114 | self = [self initWithSize:size state:state]; 115 | if (self) { 116 | self.thumbOnTintColor = LIGHT_TINT; 117 | self.thumbOffTintColor = [UIColor systemGray3Color]; 118 | self.trackOnTintColor = DARK_TINT; 119 | self.trackOffTintColor = [UIColor systemGrayColor]; 120 | self.thumbDisabledTintColor = [UIColor systemGray2Color]; 121 | self.trackDisabledTintColor = [UIColor systemGray2Color]; 122 | } 123 | return self; 124 | } 125 | - (void)willMoveToSuperview:(UIView *)newSuperview { 126 | [super willMoveToSuperview:newSuperview]; 127 | if (self.isOn == YES) { 128 | self.switchThumb.backgroundColor = self.thumbOnTintColor; 129 | self.track.backgroundColor = self.trackOnTintColor; 130 | } else { 131 | self.switchThumb.backgroundColor = self.thumbOffTintColor; 132 | self.track.backgroundColor = self.trackOffTintColor; 133 | [self setThumbState:NO animated:NO]; 134 | } 135 | if (self.isEnabled == NO) { 136 | self.switchThumb.backgroundColor = self.thumbDisabledTintColor; 137 | self.track.backgroundColor = self.trackDisabledTintColor; 138 | } 139 | } 140 | - (BOOL)getSwitchState { 141 | return self.isOn; 142 | } 143 | - (void)setOn:(BOOL)on { 144 | [self setOn:on animated:NO]; 145 | } 146 | - (void)setOn:(BOOL)on animated:(BOOL)animated { 147 | [self setThumbState:on animated:animated]; 148 | } 149 | - (void)setEnabled:(BOOL)enabled { 150 | [super setEnabled:enabled]; 151 | 152 | [UIView animateWithDuration:0.1 animations:^{ 153 | if (enabled == YES) { 154 | if (self.isOn == YES) { 155 | self.switchThumb.backgroundColor = self.thumbOnTintColor; 156 | self.track.backgroundColor = self.trackOnTintColor; 157 | } else { 158 | self.switchThumb.backgroundColor = self.thumbOffTintColor; 159 | self.track.backgroundColor = self.trackOffTintColor; 160 | } 161 | self.isEnabled = YES; 162 | } else { 163 | self.switchThumb.backgroundColor = self.thumbDisabledTintColor; 164 | self.track.backgroundColor = self.trackDisabledTintColor; 165 | self.isEnabled = NO; 166 | } 167 | }]; 168 | } 169 | - (void)switchAreaTapped:(UITapGestureRecognizer *)recognizer { 170 | if ([self.delegate respondsToSelector:@selector(switchStateChanged:)]) { 171 | if (self.isOn == YES) { 172 | [self.delegate switchStateChanged:AmpSwitchStateOff]; 173 | } else{ 174 | [self.delegate switchStateChanged:AmpSwitchStateOn]; 175 | } 176 | } 177 | [self changeThumbState]; 178 | } 179 | - (void)changeThumbState { 180 | [self setThumbState:!self.isOn animated:YES]; 181 | } 182 | - (void)setThumbState:(BOOL)state animated:(BOOL)animated { 183 | if (state) { 184 | if (animated) { 185 | [UIView animateWithDuration:0.15f delay:0.05f options:UIViewAnimationOptionCurveEaseInOut animations:^{ 186 | CGRect thumbFrame = self.switchThumb.frame; 187 | thumbFrame.origin.x = thumbOnPosition + 3.0; 188 | self.switchThumb.frame = thumbFrame; 189 | if (self.isEnabled == YES) { 190 | self.switchThumb.backgroundColor = self.thumbOnTintColor; 191 | self.track.backgroundColor = self.trackOnTintColor; 192 | } else { 193 | self.switchThumb.backgroundColor = self.thumbDisabledTintColor; 194 | self.track.backgroundColor = self.trackDisabledTintColor; 195 | } 196 | self.userInteractionEnabled = NO; 197 | } completion:^(BOOL finished) { 198 | if (self.isOn == NO) { 199 | self.isOn = YES; 200 | [self sendActionsForControlEvents:UIControlEventValueChanged]; 201 | } 202 | self.isOn = YES; 203 | [UIView animateWithDuration:0.15f animations:^{ 204 | CGRect thumbFrame = self.switchThumb.frame; 205 | thumbFrame.origin.x = thumbOnPosition; 206 | self.switchThumb.frame = thumbFrame; 207 | } completion:^(BOOL finished) { 208 | self.userInteractionEnabled = YES; 209 | }]; 210 | }]; 211 | } else { 212 | CGRect thumbFrame = self.switchThumb.frame; 213 | thumbFrame.origin.x = thumbOnPosition; 214 | self.switchThumb.frame = thumbFrame; 215 | if (self.isEnabled == YES) { 216 | self.switchThumb.backgroundColor = self.thumbOnTintColor; 217 | self.track.backgroundColor = self.trackOnTintColor; 218 | } else { 219 | self.switchThumb.backgroundColor = self.thumbDisabledTintColor; 220 | self.track.backgroundColor = self.trackDisabledTintColor; 221 | } 222 | 223 | if (self.isOn == NO) { 224 | self.isOn = YES; 225 | [self sendActionsForControlEvents:UIControlEventValueChanged]; 226 | } 227 | self.isOn = YES; 228 | } 229 | } else { 230 | if (animated) { 231 | [UIView animateWithDuration:0.15f delay:0.05f options:UIViewAnimationOptionCurveEaseInOut animations:^{ 232 | CGRect thumbFrame = self.switchThumb.frame; 233 | thumbFrame.origin.x = thumbOffPosition - 3.0; 234 | self.switchThumb.frame = thumbFrame; 235 | if (self.isEnabled == YES) { 236 | self.switchThumb.backgroundColor = self.thumbOffTintColor; 237 | self.track.backgroundColor = self.trackOffTintColor; 238 | } else { 239 | self.switchThumb.backgroundColor = self.thumbDisabledTintColor; 240 | self.track.backgroundColor = self.trackDisabledTintColor; 241 | } 242 | self.userInteractionEnabled = NO; 243 | } completion:^(BOOL finished) { 244 | if (self.isOn == YES) { 245 | self.isOn = NO; 246 | [self sendActionsForControlEvents:UIControlEventValueChanged]; 247 | } 248 | self.isOn = NO; 249 | [UIView animateWithDuration:0.15f animations:^{ 250 | CGRect thumbFrame = self.switchThumb.frame; 251 | thumbFrame.origin.x = thumbOffPosition; 252 | self.switchThumb.frame = thumbFrame; 253 | } completion:^(BOOL finished) { 254 | self.userInteractionEnabled = YES; 255 | }]; 256 | }]; 257 | } else { 258 | CGRect thumbFrame = self.switchThumb.frame; 259 | thumbFrame.origin.x = thumbOffPosition; 260 | self.switchThumb.frame = thumbFrame; 261 | if (self.isEnabled == YES) { 262 | self.switchThumb.backgroundColor = self.thumbOffTintColor; 263 | self.track.backgroundColor = self.trackOffTintColor; 264 | } else { 265 | self.switchThumb.backgroundColor = self.thumbDisabledTintColor; 266 | self.track.backgroundColor = self.trackDisabledTintColor; 267 | } 268 | 269 | if (self.isOn == YES) { 270 | self.isOn = NO; 271 | [self sendActionsForControlEvents:UIControlEventValueChanged]; 272 | } 273 | self.isOn = NO; 274 | } 275 | } 276 | } 277 | - (void)onTouchDown:(UIButton *)button withEvent:(UIEvent *)event { 278 | 279 | } 280 | - (void)switchThumbTapped: (id)sender { 281 | if ([self.delegate respondsToSelector:@selector(switchStateChanged:)]) { 282 | if (self.isOn == YES) { 283 | [self.delegate switchStateChanged:AmpSwitchStateOff]; 284 | } else{ 285 | [self.delegate switchStateChanged:AmpSwitchStateOn]; 286 | } 287 | } 288 | [self changeThumbState]; 289 | } 290 | - (void)onTouchUpOutsideOrCanceled:(UIButton *)button withEvent:(UIEvent *)event { 291 | UITouch *touch = [[event touchesForView:button] anyObject]; 292 | CGPoint previous = [touch previousLocationInView:button]; 293 | CGPoint position = [touch locationInView:button]; 294 | float dX = position.x - previous.x; 295 | float origin = button.frame.origin.x + dX; 296 | 297 | if (origin > (self.frame.size.width - self.switchThumb.frame.size.width) / 2) { 298 | [self setThumbState:YES animated:YES]; 299 | } else { 300 | [self setThumbState:NO animated:YES]; 301 | } 302 | } 303 | - (void)onTouchDragInside:(UIButton *)button withEvent:(UIEvent *)event { 304 | UITouch *touch = [[event touchesForView:button] anyObject]; 305 | CGPoint previous = [touch previousLocationInView:button]; 306 | CGPoint position = [touch locationInView:button]; 307 | float dX = position.x - previous.x; 308 | 309 | CGRect thumbFrame = button.frame; 310 | 311 | thumbFrame.origin.x += dX; 312 | thumbFrame.origin.x = MIN(thumbFrame.origin.x, thumbOnPosition); 313 | thumbFrame.origin.x = MAX(thumbFrame.origin.x, thumbOffPosition); 314 | 315 | if (thumbFrame.origin.x != button.frame.origin.x) { 316 | button.frame = thumbFrame; 317 | } 318 | } 319 | @end -------------------------------------------------------------------------------- /Ampere.xm: -------------------------------------------------------------------------------- 1 | #import "Ampere.h" 2 | 3 | NSString *batteryCharging() { 4 | UIDevice *device = [UIDevice currentDevice]; 5 | device.batteryMonitoringEnabled = YES; 6 | switch ([device batteryState]) { 7 | case UIDeviceBatteryStateCharging: 8 | return @"Yes"; 9 | case UIDeviceBatteryStateFull: 10 | case UIDeviceBatteryStateUnplugged: 11 | case UIDeviceBatteryStateUnknown: 12 | default: 13 | return @"No"; 14 | } 15 | } 16 | 17 | NSString *celsiusTemperature() { 18 | io_service_t powerSource = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPMPowerSource")); 19 | if (powerSource) { 20 | CFMutableDictionaryRef batteryDictionaryRef = NULL; 21 | if (IORegistryEntryCreateCFProperties(powerSource, &batteryDictionaryRef, 0, 0) == KERN_SUCCESS) { 22 | float temperature = -1; 23 | CFNumberRef temperatureRef = (CFNumberRef)IORegistryEntryCreateCFProperty(powerSource, CFSTR("Temperature"), kCFAllocatorDefault, 0); 24 | CFNumberGetValue(temperatureRef, kCFNumberFloatType, &temperature); 25 | CFRelease(temperatureRef); 26 | return [NSString stringWithFormat:@"%.1f°C", temperature / 100]; 27 | } 28 | } 29 | return @"--°C"; 30 | } 31 | 32 | NSString *fahrenheitTemperature() { 33 | NSString *celsiusMethod = celsiusTemperature(); 34 | NSString *celsius = [celsiusMethod substringToIndex:celsiusMethod.length - 2]; 35 | if ([celsius isEqualToString:@"--"]) 36 | return [celsius stringByAppendingString:@"°F"]; 37 | return [NSString stringWithFormat:@"%.1f°F", [celsius floatValue] * 1.8 + 32.0]; 38 | } 39 | 40 | NSString *cycles() { 41 | io_service_t powerSource = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPMPowerSource")); 42 | if (powerSource) { 43 | CFMutableDictionaryRef batteryDictionaryRef = NULL; 44 | if (IORegistryEntryCreateCFProperties(powerSource, &batteryDictionaryRef, 0, 0) == KERN_SUCCESS) { 45 | int cycles = -1; 46 | CFNumberRef cyclesRef = (CFNumberRef)IORegistryEntryCreateCFProperty(powerSource, CFSTR("CycleCount"), kCFAllocatorDefault, 0); 47 | CFNumberGetValue(cyclesRef, kCFNumberIntType, &cycles); 48 | CFRelease(cyclesRef); 49 | return [NSString stringWithFormat:@"%d", cycles]; 50 | } 51 | } 52 | return @"-"; 53 | } 54 | 55 | int maxCapacity() { 56 | io_service_t powerSource = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPMPowerSource")); 57 | if (powerSource) { 58 | CFMutableDictionaryRef batteryDictionaryRef = NULL; 59 | if (IORegistryEntryCreateCFProperties(powerSource, &batteryDictionaryRef, 0, 0) == KERN_SUCCESS) { 60 | int maxCapacity = -1; 61 | CFNumberRef maxCapRef = (CFNumberRef)IORegistryEntryCreateCFProperty(powerSource, CFSTR("AppleRawMaxCapacity"), kCFAllocatorDefault, 0); 62 | CFNumberGetValue(maxCapRef, kCFNumberIntType, &maxCapacity); 63 | CFRelease(maxCapRef); 64 | return maxCapacity; 65 | } 66 | } 67 | return -1; 68 | } 69 | 70 | int designCapacity() { 71 | io_service_t powerSource = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPMPowerSource")); 72 | if (powerSource) { 73 | CFMutableDictionaryRef batteryDictionaryRef = NULL; 74 | if (IORegistryEntryCreateCFProperties(powerSource, &batteryDictionaryRef, 0, 0) == KERN_SUCCESS) { 75 | int designCapacity = -1; 76 | CFNumberRef designCapRef = (CFNumberRef)IORegistryEntryCreateCFProperty(powerSource, CFSTR("DesignCapacity"), kCFAllocatorDefault, 0); 77 | CFNumberGetValue(designCapRef, kCFNumberIntType, &designCapacity); 78 | CFRelease(designCapRef); 79 | return designCapacity; 80 | } 81 | } 82 | return -1; 83 | } 84 | 85 | NSString *amperage() { 86 | io_service_t powerSource = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPMPowerSource")); 87 | if (powerSource) { 88 | CFMutableDictionaryRef batteryDictionaryRef = NULL; 89 | if (IORegistryEntryCreateCFProperties(powerSource, &batteryDictionaryRef, 0, 0) == KERN_SUCCESS) { 90 | int amperage = -1; 91 | CFNumberRef amperageRef = (CFNumberRef)IORegistryEntryCreateCFProperty(powerSource, CFSTR("InstantAmperage"), kCFAllocatorDefault, 0); 92 | CFNumberGetValue(amperageRef, kCFNumberIntType, &erage); 93 | CFRelease(amperageRef); 94 | return [NSString stringWithFormat:@"%d mA", amperage]; 95 | } 96 | } 97 | return @"- mA"; 98 | } 99 | 100 | NSString *voltage() { 101 | io_service_t powerSource = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPMPowerSource")); 102 | if (powerSource) { 103 | CFMutableDictionaryRef batteryDictionaryRef = NULL; 104 | if (IORegistryEntryCreateCFProperties(powerSource, &batteryDictionaryRef, 0, 0) == KERN_SUCCESS) { 105 | float voltage = -1; 106 | CFNumberRef voltageRef = (CFNumberRef)IORegistryEntryCreateCFProperty(powerSource, CFSTR("Voltage"), kCFAllocatorDefault, 0); 107 | CFNumberGetValue(voltageRef, kCFNumberFloatType, &voltage); 108 | CFRelease(voltageRef); 109 | return [NSString stringWithFormat:@"%.1f V", voltage / 1000]; 110 | } 111 | } 112 | return @"- V"; 113 | } 114 | 115 | @implementation AMPStatsController 116 | - (id)init { 117 | self = [super init]; 118 | if (self) { 119 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reloadData) name:@"AMPReloadStatsController" object:nil]; 120 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reloadData) name:@"SBUIACStatusChangedNotification" object:nil]; 121 | } 122 | return self; 123 | } 124 | - (void)viewDidLoad { 125 | [super viewDidLoad]; 126 | 127 | self.tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleInsetGrouped]; 128 | self.tableView.translatesAutoresizingMaskIntoConstraints = NO; 129 | self.tableView.delegate = self; 130 | self.tableView.dataSource = self; 131 | self.tableView.separatorColor = [UIColor clearColor]; 132 | self.tableView.backgroundColor = [UIColor clearColor]; 133 | self.tableView.userInteractionEnabled = NO; 134 | [self.view addSubview:self.tableView]; 135 | 136 | [NSLayoutConstraint activateConstraints:@[ 137 | [self.tableView.topAnchor constraintEqualToAnchor:self.view.topAnchor], 138 | [self.tableView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor], 139 | [self.tableView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], 140 | [self.tableView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor], 141 | ]]; 142 | } 143 | - (double)tableView:(id)arg1 heightForRowAtIndexPath:(id)arg2 { 144 | return 44.0; 145 | } 146 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 147 | return 1; 148 | } 149 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 150 | return 8; 151 | } 152 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 153 | static NSString *identifier = @"Cell"; 154 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; 155 | if (!cell) { 156 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]; 157 | } 158 | cell.backgroundColor = [UIColor clearColor]; 159 | UIListContentConfiguration *content = [cell defaultContentConfiguration]; 160 | [content setText:[self titleForRow:indexPath.row]]; 161 | [content.textProperties setColor:[UIColor whiteColor]]; 162 | [content setSecondaryText:[self detailForRow:indexPath.row]]; 163 | [content.secondaryTextProperties setColor:[UIColor colorWithWhite:1.0 alpha:0.75]]; 164 | [content.secondaryTextProperties setFont:[UIFont systemFontOfSize:18]]; 165 | [content setPrefersSideBySideTextAndSecondaryText:YES]; 166 | [cell setContentConfiguration:content]; 167 | 168 | return cell; 169 | } 170 | - (NSString *)titleForRow:(NSInteger)row { 171 | NSString *title; 172 | switch (row) { 173 | case 0: 174 | title = @"Charging"; 175 | break; 176 | case 1: 177 | title = @"Cycles"; 178 | break; 179 | case 2: 180 | title = @"Temperature"; 181 | break; 182 | case 3: 183 | title = @"Health"; 184 | break; 185 | case 4: 186 | title = @"Max Capacity"; 187 | break; 188 | case 5: 189 | title = @"Design Capacity"; 190 | break; 191 | case 6: 192 | title = @"Amperage"; 193 | break; 194 | case 7: 195 | title = @"Voltage"; 196 | } 197 | return title; 198 | } 199 | - (NSString *)detailForRow:(NSInteger)row { 200 | NSString *detail; 201 | switch (row) { 202 | case 0: 203 | detail = batteryCharging(); 204 | break; 205 | case 1: 206 | detail = cycles(); 207 | break; 208 | case 2: 209 | detail = [NSString stringWithFormat:@"%@/%@", celsiusTemperature(), fahrenheitTemperature()]; 210 | break; 211 | case 3: 212 | detail = [NSString stringWithFormat:@"%0.f%%", (CGFloat)maxCapacity() / (CGFloat)designCapacity() * 100]; 213 | break; 214 | case 4: 215 | detail = [NSString stringWithFormat:@"%d mAh", maxCapacity()]; 216 | break; 217 | case 5: 218 | detail = [NSString stringWithFormat:@"%d mAh", designCapacity()]; 219 | break; 220 | case 6: 221 | detail = amperage(); 222 | break; 223 | case 7: 224 | detail = voltage(); 225 | break; 226 | } 227 | return detail; 228 | } 229 | - (void)reloadData { 230 | [self.tableView reloadData]; 231 | } 232 | - (BOOL)_canShowWhileLocked { 233 | return YES; 234 | } 235 | @end 236 | 237 | %group Ampere 238 | %hook _UIStatusBarBatteryItem 239 | - (_UIBatteryView *)batteryView { 240 | _UIBatteryView *batteryView = %orig; 241 | if (useGesture) { 242 | [batteryView setUserInteractionEnabled:YES]; 243 | 244 | UITapGestureRecognizer *toggleLowPower = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleLowPower:)]; 245 | toggleLowPower.numberOfTapsRequired = 1; 246 | if (batteryView.gestureRecognizers.count == 0) { 247 | [batteryView addGestureRecognizer:toggleLowPower]; 248 | } 249 | } 250 | return %orig; 251 | } 252 | %new 253 | - (void)toggleLowPower:(id)sender { 254 | if (objc_getClass("_PMLowPowerMode")) { 255 | _PMLowPowerMode *lowPowerMode = [objc_getClass("_PMLowPowerMode") sharedInstance]; 256 | BOOL active = [lowPowerMode getPowerMode] == 1; 257 | [lowPowerMode setPowerMode:!active fromSource:@"SpringBoard"]; 258 | } else { 259 | long long state = [[objc_getClass("_CDBatterySaver") batterySaver] getPowerMode]; 260 | if (state == 0) { 261 | [[objc_getClass("_CDBatterySaver") batterySaver] setPowerMode:1 error:nil]; 262 | } 263 | if (state == 1) { 264 | [[objc_getClass("_CDBatterySaver") batterySaver] setPowerMode:0 error:nil]; 265 | } 266 | } 267 | } 268 | %end 269 | 270 | %hook _UIBatteryView 271 | - (id)initWithFrame:(CGRect)arg1 { 272 | self = %orig; 273 | if (self) { 274 | // Notifications for automatic prefs updates 275 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_updateBatteryFillColor) name:@"AmpereUpdate" object:nil]; 276 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_updatePercentage) name:@"AmpereUpdate" object:nil]; 277 | } 278 | return self; 279 | } 280 | - (id)_batteryTextColor { 281 | if (textStyle == 0) { 282 | if (self.saverModeActive) { 283 | return [UIColor blackColor]; 284 | } 285 | } else if (textStyle == 2) { // Use custom percentage text color 286 | NSDictionary *textColorDict = [[NSUserDefaults standardUserDefaults] objectForKey:@"textColorDict" inDomain:domain]; 287 | UIColor *textColor = [UIColor colorWithRed:[textColorDict[@"red"] floatValue] green:[textColorDict[@"green"] floatValue] blue:[textColorDict[@"blue"] floatValue] alpha:1.0]; 288 | return textColor; 289 | } 290 | return %orig; 291 | } 292 | %new 293 | - (UIColor *)ampereFillColor { 294 | NSDictionary *standardColorDict = [[NSUserDefaults standardUserDefaults] objectForKey:@"overrideColorStandardDict" inDomain:domain]; 295 | NSDictionary *lowPowerColorDict = [[NSUserDefaults standardUserDefaults] objectForKey:@"overrideColorLowPowerDict" inDomain:domain]; 296 | NSDictionary *chargingColorDict = [[NSUserDefaults standardUserDefaults] objectForKey:@"overrideColorChargingDict" inDomain:domain]; 297 | NSDictionary *criticalColorDict = [[NSUserDefaults standardUserDefaults] objectForKey:@"overrideColorCriticalDict" inDomain:domain]; 298 | UIColor *standardColor = overrideColorStandard ? [UIColor colorWithRed:[standardColorDict[@"red"] floatValue] green:[standardColorDict[@"green"] floatValue] blue:[standardColorDict[@"blue"] floatValue] alpha:1.0] : [UIColor labelColor]; 299 | UIColor *lowPowerColor = overrideColorLowPower ? [UIColor colorWithRed:[lowPowerColorDict[@"red"] floatValue] green:[lowPowerColorDict[@"green"] floatValue] blue:[lowPowerColorDict[@"blue"] floatValue] alpha:1.0] : [UIColor systemYellowColor]; 300 | UIColor *chargingColor = overrideColorCharging ? [UIColor colorWithRed:[chargingColorDict[@"red"] floatValue] green:[chargingColorDict[@"green"] floatValue] blue:[chargingColorDict[@"blue"] floatValue] alpha:1.0] : [UIColor systemGreenColor]; 301 | UIColor *criticalColor = overrideColorCritical ? [UIColor colorWithRed:[criticalColorDict[@"red"] floatValue] green:[criticalColorDict[@"green"] floatValue] blue:[criticalColorDict[@"blue"] floatValue] alpha:1.0] : [UIColor systemRedColor]; 302 | 303 | if (!self.saverModeActive) { // Normal use 304 | if (self.chargingState == 1) { // Charging 305 | return chargingColor; 306 | } else if (self.lowBattery) { 307 | return criticalColor; 308 | } else return standardColor; // Color of normal use (not charging & not in Low Power mode) 309 | } else { // Low Power, overrides custom charging color 310 | return lowPowerColor; 311 | } 312 | } 313 | - (UIColor *)_batteryFillColor { // Return default or custom fill colors based on charging state 314 | return [self ampereFillColor]; 315 | } 316 | %end 317 | 318 | %hook _UIStatusBarDataBatteryEntry 319 | - (NSString *)detailString { 320 | return @""; // Return empty string to keep automatic sizing 321 | } 322 | %end 323 | 324 | %hook BCUIRowView 325 | %property (nonatomic, strong) _UIBatteryView *ampereBatteryView; 326 | - (void)_configureBatteryViewIfNecessary { 327 | %orig; 328 | BCUIBatteryView *batteryView = MSHookIvar(self, "_batteryView"); 329 | 330 | if (!(kSLSystemVersioniOS16)) { 331 | if (!self.ampereBatteryView) self.ampereBatteryView = [[%c(_UIBatteryView) alloc] initWithSizeCategory:0]; 332 | self.ampereBatteryView.translatesAutoresizingMaskIntoConstraints = NO; 333 | 334 | [self addSubview:self.ampereBatteryView]; 335 | [NSLayoutConstraint activateConstraints:@[ 336 | [self.ampereBatteryView.widthAnchor constraintEqualToAnchor:batteryView.widthAnchor constant:-1], 337 | [self.ampereBatteryView.heightAnchor constraintEqualToAnchor:batteryView.heightAnchor], 338 | [self.ampereBatteryView.centerXAnchor constraintEqualToAnchor:batteryView.centerXAnchor], 339 | [self.ampereBatteryView.centerYAnchor constraintEqualToAnchor:batteryView.centerYAnchor], 340 | ]]; 341 | batteryView.hidden = YES; 342 | 343 | [self.ampereBatteryView setChargePercent:batteryView.chargePercent]; 344 | [self.ampereBatteryView setChargingState:batteryView.chargingState]; 345 | } 346 | } 347 | - (void)_configurePercentChargeLabelIfNecessary { 348 | 349 | } 350 | %end 351 | %end 352 | 353 | %group XVI 354 | %hook _UIStaticBatteryView 355 | - (void)setShowsPercentage:(BOOL)arg0 { 356 | %orig(YES); 357 | } 358 | - (void)_createFillLayer { 359 | %orig; 360 | [self setShowsPercentage:YES]; 361 | } 362 | %end 363 | 364 | %hook _UIBatteryView 365 | - (BOOL)_batteryTextIsCutout { 366 | if (textStyle == 1 || [[NSBundle mainBundle].bundleIdentifier isEqualToString:@"com.apple.CarPlayApp"]) { 367 | return YES; 368 | } 369 | return %orig; 370 | } 371 | %end 372 | %end 373 | 374 | %group XV 375 | %hook _UIBatteryView 376 | - (void)setSaverModeActive:(BOOL)arg1 { 377 | %orig; 378 | [self _updatePercentage]; 379 | } 380 | - (UIColor *)pinColor { 381 | return (self.chargePercent > 0.97) ? [self _batteryFillColor] : %orig; // Set pin color to fill color, but only when charge exceeds frame of regular battery body 382 | } 383 | + (id)_pinBezierPathForSize:(struct CGSize )arg0 complex:(BOOL)arg1 { 384 | UIBezierPath *path = %orig; 385 | if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) { 386 | [path applyTransform:CGAffineTransformMakeTranslation(1, 0)]; // Shift pin 1 px, done because setting line interspace width to fill body adds border 387 | } 388 | return path; 389 | } 390 | - (void)_updateFillLayer { 391 | %orig; 392 | [self.fillLayer setCornerRadius:([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) ? 4.0 : 3.5]; // Set fill corner radius whenever layer updates 393 | [self.fillLayer setCornerCurve:@"circular"]; 394 | } 395 | - (void)_updatePercentage { 396 | %orig; 397 | self.percentageLabel.font = [UIFont systemFontOfSize:((self.chargingState == 1 || self.chargePercent == 1.0) && fontSize > 10) ? 10 : fontSize weight:((batterySizing == 1) ? UIFontWeightHeavy : UIFontWeightBold)]; // Set custom percentage font size 398 | if (showBolt && self.chargingState == 1 && self.chargePercent != 1.0) { // Show bolt next to percentage label text 399 | NSTextAttachment *attachment = [[NSTextAttachment alloc] init]; 400 | [attachment setBounds:CGRectMake(0, 0, roundf(self.percentageLabel.font.capHeight * 0.6), roundf(self.percentageLabel.font.capHeight))]; 401 | [attachment setImage:[[UIImage systemImageNamed:@"bolt.fill"] imageWithTintColor:(self.saverModeActive) ? [UIColor blackColor] : [self _batteryTextColor]]]; 402 | 403 | NSMutableAttributedString *atr = [[NSMutableAttributedString alloc] initWithString:self.percentageLabel.text]; 404 | [atr appendAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]]; 405 | self.percentageLabel.attributedText = atr; 406 | } 407 | self.percentageLabel.layer.allowsGroupBlending = YES; 408 | self.percentageLabel.layer.allowsGroupOpacity = YES; 409 | self.percentageLabel.layer.compositingFilter = (textStyle == 1) || (textStyle == 0 && (self.chargingState != 1) && !self.saverModeActive) ? kCAFilterDestOut : nil; // Enable cutout effect on text when in transparent mode or default (when not charging) 410 | 411 | [self.percentageLabel sizeToFit]; 412 | if ([UIApplication sharedApplication].userInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) { // Support RTL languages 413 | self.percentageLabel.transform = CGAffineTransformMakeScale(-1.0, 1.0); 414 | } 415 | } 416 | - (CGFloat)_outsideCornerRadiusForTraitCollection:(id)arg0 { 417 | return ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) ? 4.0 : 3.5; // Slightly adjust corner radius for expanded height 418 | } 419 | + (CGFloat)_lineWidthAndInterspaceForIconSize:(NSInteger)arg0 { 420 | return 0; 421 | } 422 | - (CGFloat)bodyColorAlpha { 423 | return 1.0; // Overrides default fill color alpha (normally 0.4) 424 | } 425 | - (CAShapeLayer *)bodyShapeLayer { 426 | CAShapeLayer *bodyLayer = %orig; 427 | bodyLayer.fillColor = [[UIColor labelColor] colorWithAlphaComponent:0.4].CGColor; // Fill exisiting battery view completely 428 | return bodyLayer; 429 | } 430 | - (CALayer *)fillLayer { 431 | CALayer *fill = %orig; 432 | fill.maskedCorners = (self.chargePercent > 0.82) ? (kCALayerMaxXMaxYCorner | kCALayerMaxXMinYCorner | kCALayerMinXMaxYCorner | kCALayerMinXMinYCorner) : (kCALayerMinXMaxYCorner | kCALayerMinXMinYCorner); // Rounded corners always on leading edge, flat on trailing until above 92% to match stock radius 433 | fill.bounds = CGRectMake(fill.bounds.origin.x - 1, fill.bounds.origin.y, fill.bounds.size.width, self.bounds.size.height); 434 | return fill; 435 | } 436 | - (CGRect)_bodyRectForTraitCollection:(id)arg0 { 437 | CGRect bodyRect = %orig; 438 | return CGRectMake(bodyRect.origin.x, bodyRect.origin.y, bodyRect.size.width - 1, bodyRect.size.height); // Resize view height to better replicate iOS 16 439 | } 440 | - (CGFloat)_lineWidthAndInterspaceForTraitCollection:(id)arg0 { 441 | return 0; // Disable space between fill layer and border of body layer 442 | } 443 | - (BOOL)_shouldShowBolt { 444 | return NO; // Disable interior bolt when charging 445 | } 446 | - (BOOL)_currentlyShowsPercentage { 447 | return YES; // Always display battery percentage label 448 | } 449 | %end 450 | 451 | %hook _UIStatusBarBatteryItem 452 | + (id)staticIconDisplayIdentifier { 453 | return [self iconDisplayIdentifier]; // Override static identifier, used in iPhone Control Center & everywhere on iPad 454 | } 455 | %end 456 | %end 457 | 458 | %group AmpereCarPlay 459 | %hook _UIBatteryView 460 | - (void)setSaverModeActive:(BOOL)arg1 { 461 | %orig; 462 | [self _updatePercentage]; 463 | } 464 | - (void)_createBodyLayers { 465 | %orig; 466 | [self _updatePercentageFont]; 467 | } 468 | - (BOOL)showsPercentage { 469 | return YES; 470 | } 471 | - (BOOL)_shouldShowBolt { 472 | return NO; 473 | } 474 | - (BOOL)showsInlineChargingIndicator { 475 | return NO; 476 | } 477 | - (CGFloat)_lineWidthAndInterspaceForTraitCollection:(id)arg0 { 478 | return 0; 479 | } 480 | - (void)_updatePercentage { 481 | %orig; 482 | [self.percentageLabel setFrame:CGRectMake(-1, self.bounds.origin.y, self.bounds.size.width -0.5, self.bounds.size.height)]; 483 | [self.percentageLabel setTextAlignment:(NSTextAlignment)1]; 484 | [self.percentageLabel setText:[NSString stringWithFormat:@"%.f", [self chargePercent] * 100]]; 485 | self.percentageLabel.alpha = 1.0; 486 | // UIFontDescriptor *fontDescriptor = [UIFontDescriptor fontDescriptorWithName:@".SFUI-SemiCondensedBold" size:11]; 487 | self.percentageLabel.font = [UIFont monospacedDigitSystemFontOfSize:10 weight:UIFontWeightBold]; // Set custom percentage font size 488 | self.percentageLabel.textColor = [self _batteryTextColor]; 489 | // self.percentageLabel.font = [UIFont fontWithDescriptor:fontDescriptor size:11]; 490 | if (self.chargePercent != 1.0) { // Show bolt next to percentage label text 491 | NSTextAttachment *attachment = [[NSTextAttachment alloc] init]; 492 | [attachment setBounds:CGRectMake(0, 0, roundf(self.percentageLabel.font.capHeight * 0.6), roundf(self.percentageLabel.font.capHeight))]; 493 | [attachment setImage:[[UIImage systemImageNamed:@"bolt.fill"] imageWithTintColor:(self.saverModeActive) ? [UIColor blackColor] : [self _batteryTextColor]]]; 494 | 495 | NSMutableAttributedString *atr = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%.f", [self chargePercent] * 100]]; 496 | [atr appendAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]]; 497 | self.percentageLabel.attributedText = atr; 498 | } 499 | self.percentageLabel.layer.allowsGroupBlending = YES; 500 | self.percentageLabel.layer.allowsGroupOpacity = YES; 501 | self.percentageLabel.layer.compositingFilter = (textStyle == 1) || (textStyle == 0 && (self.chargingState != 1) && !self.saverModeActive) ? kCAFilterDestOut : nil; // Enable cutout effect on text when in transparent mode or default (when not charging) 502 | 503 | self.pinColor = (self.chargePercent > 0.97) ? [self _batteryFillColor] : [self bodyColor]; 504 | } 505 | - (CALayer *)fillLayer { 506 | CALayer *fill = %orig; 507 | fill.maskedCorners = (self.chargePercent > 0.82) ? (kCALayerMaxXMaxYCorner | kCALayerMaxXMinYCorner | kCALayerMinXMaxYCorner | kCALayerMinXMinYCorner) : (kCALayerMinXMaxYCorner | kCALayerMinXMinYCorner); // Rounded corners always on leading edge, flat on trailing until above 92% to match stock radius 508 | fill.frame = CGRectMake(fill.frame.origin.x, fill.bounds.origin.y, fill.bounds.size.width - 0.5, self.bounds.size.height); 509 | return fill; 510 | } 511 | - (CAShapeLayer *)bodyShapeLayer { 512 | CAShapeLayer *bodyLayer = %orig; 513 | bodyLayer.fillColor = [[UIColor labelColor] colorWithAlphaComponent:0.4].CGColor; // Fill exisiting battery view completely 514 | return bodyLayer; 515 | } 516 | - (void)_updateFillLayer { 517 | %orig; 518 | [self.fillLayer setCornerRadius:3]; // Set fill corner radius whenever layer updates 519 | [self.fillLayer setCornerCurve:@"circular"]; 520 | } 521 | - (UIColor *)pinColor { 522 | return (self.chargePercent > 0.97) ? [self _batteryFillColor] : [self bodyColor]; // Set pin color to fill color, but only when charge exceeds frame of regular battery body 523 | } 524 | - (CGRect)_bodyRectForTraitCollection:(id)arg0 { 525 | CGRect bodyRect = %orig; 526 | return CGRectMake(bodyRect.origin.x, bodyRect.origin.y, bodyRect.size.width - 1, bodyRect.size.height); // Resize view height to better replicate iOS 16 527 | } 528 | + (id)_pinBezierPathForSize:(struct CGSize )arg0 complex:(BOOL)arg1 { 529 | UIBezierPath *path = %orig; 530 | [path applyTransform:CGAffineTransformMakeTranslation(2, 0)]; // Shift pin 1 px, done because setting line interspace width to fill body adds borderr 531 | return path; 532 | } 533 | - (id)_batteryTextColor { 534 | if (textStyle == 0) { 535 | if (self.saverModeActive) { 536 | return [UIColor blackColor]; 537 | } 538 | } else if (textStyle == 2) { // Use custom percentage text color 539 | NSDictionary *textColorDict = [[NSUserDefaults standardUserDefaults] objectForKey:@"textColorDict" inDomain:domain]; 540 | UIColor *textColor = [UIColor colorWithRed:[textColorDict[@"red"] floatValue] green:[textColorDict[@"green"] floatValue] blue:[textColorDict[@"blue"] floatValue] alpha:1.0]; 541 | return textColor; 542 | } 543 | return %orig; 544 | } 545 | %end 546 | %end 547 | 548 | %group BatteryInfo 549 | %hook CCUIToggleViewController 550 | - (void)viewDidLoad { 551 | %orig; 552 | if ([self.module isKindOfClass:%c(CCUILowPowerModule)]) { 553 | AMPStatsController *statsController = [[AMPStatsController alloc] init]; 554 | if (![self.view.subviews containsObject:statsController.view]) { 555 | statsController.view.translatesAutoresizingMaskIntoConstraints = NO; 556 | statsController.view.tag = 9999; 557 | statsController.view.hidden = YES; 558 | [self addChildViewController:statsController]; 559 | [self.view addSubview:statsController.view]; 560 | 561 | [NSLayoutConstraint activateConstraints:@[ 562 | [statsController.view.topAnchor constraintEqualToAnchor:self.view.topAnchor], 563 | [statsController.view.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor], 564 | [statsController.view.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], 565 | [statsController.view.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor], 566 | ]]; 567 | } 568 | } 569 | } 570 | - (BOOL)shouldFinishTransitionToExpandedContentModule { 571 | if ([self.module isKindOfClass:%c(CCUILowPowerModule)]) { 572 | return YES; 573 | } 574 | return %orig; 575 | } 576 | - (CGFloat)preferredExpandedContentHeight { 577 | if ([self.module isKindOfClass:%c(CCUILowPowerModule)]) { 578 | return HEIGHT * 0.5; 579 | } 580 | return %orig; 581 | } 582 | - (CGFloat)preferredExpandedContentWidth { 583 | if ([self.module isKindOfClass:%c(CCUILowPowerModule)]) { 584 | return WIDTH * 0.6; 585 | } 586 | return %orig; 587 | } 588 | - (void)willTransitionToExpandedContentMode:(BOOL)arg0 { 589 | %orig; 590 | if ([self.module isKindOfClass:%c(CCUILowPowerModule)]) { 591 | UIView *statsView = (UIView *)[self.view viewWithTag:9999]; 592 | statsView.hidden = !arg0; 593 | 594 | self.buttonView.hidden = arg0; 595 | 596 | if (arg0) { 597 | [[NSNotificationCenter defaultCenter] postNotificationName:@"AMPReloadStatsController" object:nil]; 598 | } 599 | } 600 | } 601 | %end 602 | 603 | %hook CCUIContentModuleContainerViewController 604 | - (BOOL)clickPresentationInteractionShouldPresent:(id)arg0 { 605 | if ([self.moduleIdentifier isEqualToString:@"com.apple.control-center.LowPowerModule"] && SYSTEM_VERSION_GREATER_THAN(@"15.0")) { 606 | return YES; 607 | } 608 | return %orig; 609 | } 610 | - (void)viewDidLoad { 611 | %orig; 612 | if ([self.moduleIdentifier isEqualToString:@"com.apple.control-center.LowPowerModule"] && SYSTEM_VERSION_GREATER_THAN(@"15.0")) { 613 | AMPStatsController *statsController = [[AMPStatsController alloc] init]; 614 | if (![self.view.subviews containsObject:statsController.view]) { 615 | statsController.view.translatesAutoresizingMaskIntoConstraints = NO; 616 | statsController.view.tag = 9999; 617 | statsController.view.hidden = YES; 618 | [self addChildViewController:statsController]; 619 | [self.view addSubview:statsController.view]; 620 | 621 | [NSLayoutConstraint activateConstraints:@[ 622 | [statsController.view.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor], 623 | [statsController.view.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor], 624 | [statsController.view.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:16], 625 | [statsController.view.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-16], 626 | [statsController.view.heightAnchor constraintEqualToConstant:HEIGHT * 0.6], 627 | ]]; 628 | } 629 | } 630 | } 631 | - (void)transitionToExpandedMode:(BOOL)arg0 { 632 | %orig; 633 | if ([self.moduleIdentifier isEqualToString:@"com.apple.control-center.LowPowerModule"] && SYSTEM_VERSION_GREATER_THAN(@"15.0")) { 634 | UIView *statsView = (UIView *)[self.view viewWithTag:9999]; 635 | statsView.hidden = !arg0; 636 | 637 | self.contentViewController.view.hidden = arg0; 638 | CCUIContentModuleContentContainerView *containerView = self.contentContainerView; 639 | MTMaterialView *materialView = MSHookIvar(containerView, "_moduleMaterialView"); 640 | materialView.hidden = arg0; 641 | 642 | if (arg0) { 643 | [[NSNotificationCenter defaultCenter] postNotificationName:@"AMPReloadStatsController" object:nil]; 644 | } 645 | } 646 | } 647 | %end 648 | %end 649 | 650 | static void reloadStatusBar(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) { 651 | [[NSNotificationCenter defaultCenter] postNotificationName:@"AmpereUpdate" object:nil]; // Post local update notification 652 | } 653 | 654 | static void loadPreferences(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) { 655 | NSNumber *enabledValue = (NSNumber *)[[NSUserDefaults standardUserDefaults] objectForKey:@"enabled" inDomain:domain]; 656 | enabled = (enabledValue) ? [enabledValue boolValue] : NO; 657 | NSNumber *showBoltValue = (NSNumber *)[[NSUserDefaults standardUserDefaults] objectForKey:@"showBolt" inDomain: domain]; 658 | showBolt = (showBoltValue) ? [showBoltValue boolValue] : YES; 659 | NSNumber *overrideColorStandardValue = (NSNumber *)[[NSUserDefaults standardUserDefaults] objectForKey:@"overrideColorStandard" inDomain:domain]; 660 | overrideColorStandard = (overrideColorStandardValue) ? [overrideColorStandardValue boolValue] : NO; 661 | NSNumber *overrideColorChargingValue = (NSNumber *)[[NSUserDefaults standardUserDefaults] objectForKey:@"overrideColorCharging" inDomain:domain]; 662 | overrideColorCharging = (overrideColorChargingValue) ? [overrideColorChargingValue boolValue] : NO; 663 | NSNumber *overrideColorLowPowerValue = (NSNumber *)[[NSUserDefaults standardUserDefaults] objectForKey:@"overrideColorLowPower" inDomain:domain]; 664 | overrideColorLowPower = (overrideColorLowPowerValue) ? [overrideColorLowPowerValue boolValue] : NO; 665 | NSNumber *overrideColorCriticalValue = (NSNumber *)[[NSUserDefaults standardUserDefaults] objectForKey:@"overrideColorCritical" inDomain:domain]; 666 | overrideColorCritical = (overrideColorCriticalValue) ? [overrideColorCriticalValue boolValue] : NO; 667 | NSNumber *useGestureValue = (NSNumber *)[[NSUserDefaults standardUserDefaults] objectForKey:@"useGesture" inDomain:domain]; 668 | useGesture = (useGestureValue) ? [useGestureValue boolValue] : NO; 669 | NSNumber *useStatsModuleValue = (NSNumber *)[[NSUserDefaults standardUserDefaults] objectForKey:@"useStatsModule" inDomain:domain]; 670 | useStatsModule = (useStatsModuleValue) ? [useStatsModuleValue boolValue] : YES; 671 | 672 | NSNumber *textStyleValue = (NSNumber *)[[NSUserDefaults standardUserDefaults] objectForKey:@"textStyle" inDomain:domain]; 673 | textStyle = (textStyleValue) ? [textStyleValue integerValue] : 0; 674 | NSNumber *fontSizeValue = (NSNumber *)[[NSUserDefaults standardUserDefaults] objectForKey:@"fontSize" inDomain:domain]; 675 | fontSize = (fontSizeValue) ? [fontSizeValue integerValue] : 8; 676 | } 677 | 678 | %ctor { 679 | loadPreferences(NULL, NULL, NULL, NULL, NULL); // Load prefs 680 | CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, loadPreferences, (CFStringRef)preferencesNotification, NULL, CFNotificationSuspensionBehaviorCoalesce); // Preferences changed 681 | CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, reloadStatusBar, (CFStringRef)statusBarNotification, NULL, CFNotificationSuspensionBehaviorCoalesce); // Update status bar 682 | if (enabled) { 683 | %init(Ampere); 684 | if (!(kSLSystemVersioniOS16)) { 685 | %init(XV); 686 | } else { 687 | %init(XVI); 688 | if ([[NSBundle mainBundle].bundleIdentifier isEqualToString:@"com.apple.CarPlayApp"]) { 689 | %init(AmpereCarPlay); 690 | } 691 | } 692 | if (useStatsModule) { 693 | %init(BatteryInfo); 694 | } 695 | } 696 | } -------------------------------------------------------------------------------- /amperesettings/AMPRootListController.m: -------------------------------------------------------------------------------- 1 | #include "AMPRootListController.h" 2 | 3 | @import SafariServices; 4 | 5 | @implementation AMPRootListController 6 | - (NSArray *)specifiers { 7 | if (!_specifiers) { 8 | _specifiers = [self loadSpecifiersFromPlistName:@"Root" target:self]; 9 | } 10 | 11 | for (PSSpecifier *specifier in _specifiers) { 12 | if (specifier.properties[@"iconImageSystem"] != nil) { 13 | NSDictionary *systemIconDict = specifier.properties[@"iconImageSystem"]; 14 | UIImageSymbolConfiguration *configuration = [UIImageSymbolConfiguration configurationWithPointSize:18 weight:UIImageSymbolWeightSemibold scale:UIImageSymbolScaleMedium]; 15 | UIImage *systemIcon = [[UIImage systemImageNamed:systemIconDict[@"name"] withConfiguration:configuration] imageWithTintColor:[UIColor systemGreenColor]]; 16 | [specifier setProperty:systemIcon forKey:@"iconImage"]; 17 | } 18 | } 19 | return _specifiers; 20 | } 21 | - (UITableViewStyle)tableViewStyle { 22 | return UITableViewStyleInsetGrouped; 23 | } 24 | - (instancetype)init { 25 | self = [super init]; 26 | if (self) { 27 | self.enableSwitch = [[AmpSwitch alloc] initWithSize:AmpSwitchSizeBig style:AmpSwitchStyleDefault state:AmpSwitchStateOff]; 28 | self.enableSwitch.delegate = self; 29 | self.enableSwitch.thumbOnTintColor = [UIColor systemGreenColor]; 30 | self.enableSwitch.thumbOffTintColor = [UIColor labelColor]; 31 | self.enableSwitch.trackOnTintColor = [UIColor secondaryLabelColor]; 32 | self.enableSwitch.trackOffTintColor = [UIColor secondaryLabelColor]; 33 | [self setupButtonMenu]; 34 | 35 | self.navigationController.navigationBar.prefersLargeTitles = YES; 36 | self.navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeAlways; 37 | } 38 | return self; 39 | } 40 | - (void)setupButtonMenu { 41 | [self.enableSwitch addTarget:self action:@selector(toggleState:) forControlEvents:UIControlEventTouchUpInside]; 42 | 43 | UIAction *respring = [UIAction actionWithTitle:@"Respring" image:[UIImage systemImageNamed:@"gearshape.fill"] identifier:nil handler:^(__kindof UIAction *_Nonnull action) { 44 | [self respring]; 45 | }]; 46 | 47 | UIAction *reset = [UIAction actionWithTitle:@"Reset Settings" image:[UIImage systemImageNamed:@"gearshape.fill"] identifier:nil handler:^(__kindof UIAction *_Nonnull action) { 48 | [self reset]; 49 | }]; 50 | reset.attributes = UIMenuElementAttributesDestructive; 51 | 52 | UIMenu *menuActions = [UIMenu menuWithTitle:@"" children:@[respring, reset]]; 53 | UIBarButtonItem *optionsItem = [[UIBarButtonItem alloc] initWithImage:[UIImage systemImageNamed:@"gearshape.fill"] menu:menuActions]; 54 | optionsItem.tintColor = [UIColor systemGreenColor]; 55 | 56 | self.navigationItem.rightBarButtonItems = @[optionsItem]; 57 | } 58 | - (void)viewWillAppear:(BOOL)animated { 59 | [super viewWillAppear:animated]; 60 | 61 | [self setEnableSwitchState]; 62 | [self reloadSpecifiers]; 63 | 64 | self.view.tintColor = [UIColor systemGreenColor]; 65 | [[UIApplication sharedApplication] keyWindow].tintColor = [UIColor systemGreenColor]; 66 | [self.navigationController.navigationItem.navigationBar sizeToFit]; 67 | _table.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; 68 | } 69 | - (void)viewWillDisappear:(BOOL)animated { 70 | [[UIApplication sharedApplication] keyWindow].tintColor = nil; 71 | [super viewWillDisappear:animated]; 72 | } 73 | - (void)viewDidLoad { 74 | [super viewDidLoad]; 75 | 76 | self.navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeAlways; 77 | 78 | self.headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 280)]; 79 | self.enableSwitch.translatesAutoresizingMaskIntoConstraints = NO; 80 | 81 | self.batteryView = [[UIImageView alloc] initWithFrame:CGRectZero]; 82 | self.batteryView.contentMode = UIViewContentModeScaleAspectFit; 83 | self.batteryView.translatesAutoresizingMaskIntoConstraints = NO; 84 | self.batteryView.image = [[UIImage systemImageNamed:@"bolt.fill"] imageWithTintColor:[UIColor systemGreenColor]]; 85 | self.batteryView.tintColor = [UIColor systemGreenColor]; 86 | self.batteryView.layer.shadowOpacity = 1.0f; 87 | self.batteryView.layer.shadowOffset = CGSizeZero; 88 | self.batteryView.layer.shadowColor = [UIColor systemGreenColor].CGColor; 89 | 90 | CABasicAnimation *glowanimation = [CABasicAnimation animation]; 91 | glowanimation.keyPath = @"shadowRadius"; 92 | glowanimation.fromValue = @0; 93 | glowanimation.toValue = @10; 94 | glowanimation.autoreverses = YES; 95 | glowanimation.duration = 1.0; 96 | glowanimation.repeatCount = HUGE_VALF; 97 | [self.batteryView.layer addAnimation:glowanimation forKey:@"glow"]; 98 | 99 | self.segmentedControl = [[UISegmentedControl alloc] initWithItems:@[@"Charging", @"Normal", @"Low Power", @"Critical"]]; 100 | self.segmentedControl.translatesAutoresizingMaskIntoConstraints = NO; 101 | self.segmentedControl.frame = CGRectZero; 102 | self.segmentedControl.selectedSegmentIndex = [[[NSUserDefaults standardUserDefaults] objectForKey:@"preferenceSection" inDomain:domain] integerValue] ?: 0; 103 | self.segmentedControl.backgroundColor = [UIColor systemBackgroundColor]; 104 | self.segmentedControl.selectedSegmentTintColor = [UIColor tableCellGroupedBackgroundColor]; 105 | [self.segmentedControl addTarget:self action:@selector(segmentChanged:) forControlEvents: UIControlEventValueChanged]; 106 | [self.segmentedControl setDividerImage:[UIImage new] forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault]; 107 | 108 | [[UISegmentedControl appearance] setTitleTextAttributes:@{NSForegroundColorAttributeName: [UIColor labelColor]} forState:UIControlStateSelected]; 109 | [[UISegmentedControl appearance] setTitleTextAttributes:@{NSForegroundColorAttributeName: [UIColor secondaryLabelColor]} forState:UIControlStateNormal]; 110 | 111 | self.sectionSegmentLabel = [[UILabel alloc] initWithFrame:CGRectZero]; 112 | self.sectionSegmentLabel.translatesAutoresizingMaskIntoConstraints = NO; 113 | self.sectionSegmentLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightSemibold]; 114 | self.sectionSegmentLabel.textColor = [UIColor secondaryLabelColor]; 115 | self.sectionSegmentLabel.textAlignment = NSTextAlignmentCenter; 116 | self.sectionSegmentLabel.text = @"ꜜ Select Battery Mode to Configure ꜜ"; 117 | 118 | [self.headerView addSubview:self.enableSwitch]; 119 | [self.headerView addSubview:self.batteryView]; 120 | [self.headerView addSubview:self.sectionSegmentLabel]; 121 | [self.headerView addSubview:self.segmentedControl]; 122 | 123 | [NSLayoutConstraint activateConstraints:@[ 124 | [self.batteryView.widthAnchor constraintEqualToConstant:50], 125 | [self.batteryView.heightAnchor constraintEqualToConstant:75], 126 | [self.batteryView.centerXAnchor constraintEqualToAnchor:self.headerView.centerXAnchor], 127 | [self.batteryView.topAnchor constraintEqualToAnchor:self.headerView.topAnchor constant:8], 128 | [self.enableSwitch.topAnchor constraintEqualToAnchor:self.batteryView.bottomAnchor constant:8], 129 | [self.enableSwitch.centerXAnchor constraintEqualToAnchor:self.headerView.centerXAnchor], 130 | [self.enableSwitch.widthAnchor constraintEqualToConstant:50], 131 | [self.enableSwitch.heightAnchor constraintEqualToConstant:40], 132 | [self.segmentedControl.bottomAnchor constraintEqualToAnchor:self.headerView.bottomAnchor constant:-30], 133 | [self.segmentedControl.leadingAnchor constraintEqualToAnchor:self.headerView.leadingAnchor constant:16], 134 | [self.segmentedControl.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor constant:-16], 135 | [self.segmentedControl.heightAnchor constraintEqualToConstant:40], 136 | [self.sectionSegmentLabel.bottomAnchor constraintEqualToAnchor:self.segmentedControl.topAnchor constant:-5], 137 | [self.sectionSegmentLabel.heightAnchor constraintEqualToConstant:40], 138 | [self.sectionSegmentLabel.centerXAnchor constraintEqualToAnchor:self.headerView.centerXAnchor], 139 | [self.sectionSegmentLabel.widthAnchor constraintEqualToAnchor:self.segmentedControl.widthAnchor], 140 | ]]; 141 | _table.tableHeaderView = self.headerView; 142 | } 143 | - (void)segmentChanged:(UISegmentedControl *)segmentControl { 144 | [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithInt:segmentControl.selectedSegmentIndex] forKey:@"preferenceSection" inDomain:domain]; 145 | [self reloadSpecifiers]; 146 | } 147 | - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { 148 | PSTableCell *tableCell = (PSTableCell *)cell; 149 | if (([tableCell.specifier.properties[@"id"] isEqualToString:@"fontSize"] || [tableCell.specifier.properties[@"id"] isEqualToString:@"showBolt"]) && SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"16.0") && [[objc_getClass("BSPlatform") sharedInstance] homeButtonType] == 2) { 150 | [tableCell setCellEnabled:NO]; 151 | } 152 | } 153 | - (void)reloadSpecifiers { 154 | [super reloadSpecifiers]; 155 | switch (self.segmentedControl.selectedSegmentIndex) { 156 | case 0: 157 | [self removeContiguousSpecifiers:@[[self specifierForID:@"overrideColorStandard"]] animated:NO]; 158 | [self removeContiguousSpecifiers:@[[self specifierForID:@"overrideColorLowPower"]] animated:NO]; 159 | [self removeContiguousSpecifiers:@[[self specifierForID:@"overrideColorCritical"]] animated:NO]; 160 | break; 161 | case 1: 162 | [self removeContiguousSpecifiers:@[[self specifierForID:@"overrideColorCharging"]] animated:NO]; 163 | [self removeContiguousSpecifiers:@[[self specifierForID:@"overrideColorLowPower"]] animated:NO]; 164 | [self removeContiguousSpecifiers:@[[self specifierForID:@"overrideColorCritical"]] animated:NO]; 165 | break; 166 | case 2: 167 | [self removeContiguousSpecifiers:@[[self specifierForID:@"overrideColorStandard"]] animated:NO]; 168 | [self removeContiguousSpecifiers:@[[self specifierForID:@"overrideColorCharging"]] animated:NO]; 169 | [self removeContiguousSpecifiers:@[[self specifierForID:@"overrideColorCritical"]] animated:NO]; 170 | break; 171 | case 3: 172 | [self removeContiguousSpecifiers:@[[self specifierForID:@"overrideColorStandard"]] animated:NO]; 173 | [self removeContiguousSpecifiers:@[[self specifierForID:@"overrideColorLowPower"]] animated:NO]; 174 | [self removeContiguousSpecifiers:@[[self specifierForID:@"overrideColorCharging"]] animated:NO]; 175 | break; 176 | } 177 | } 178 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 179 | tableView.tableHeaderView = self.headerView; 180 | return [super tableView:tableView cellForRowAtIndexPath:indexPath]; 181 | } 182 | - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { 183 | UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 200, 30)]; 184 | NSString *title = [self tableView:tableView titleForHeaderInSection:section]; 185 | if (title != nil) { 186 | titleLabel.textColor = [UIColor secondaryLabelColor]; 187 | titleLabel.font = [UIFont systemFontOfSize:20 weight:UIFontWeightSemibold]; 188 | titleLabel.text = [NSString stringWithFormat:@" %@", title]; 189 | } 190 | return titleLabel; 191 | } 192 | - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { 193 | if ([self tableView:tableView titleForHeaderInSection:section] != nil) { 194 | return 40; 195 | } 196 | return 10; 197 | } 198 | - (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section { 199 | if (section == [self numberOfGroups] - 1) { 200 | UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(([UIScreen mainScreen].bounds.size.width / 2) - 100, 0, 200, 100)]; 201 | titleLabel.numberOfLines = 2; 202 | titleLabel.textColor = [UIColor secondaryLabelColor]; 203 | titleLabel.textAlignment = NSTextAlignmentCenter; 204 | 205 | NSString *primary = @"Ampere"; 206 | NSString *secondary = @"v1.4 © MTAC"; 207 | 208 | NSMutableAttributedString *final = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@\n%@", primary, secondary]]; 209 | [final addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:18 weight:UIFontWeightSemibold] range:[final.string rangeOfString:primary]]; 210 | [final addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:12 weight:UIFontWeightRegular] range:[final.string rangeOfString:secondary]]; 211 | 212 | titleLabel.attributedText = final; 213 | return titleLabel; 214 | } 215 | return nil; 216 | } 217 | - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { 218 | if (section == [self numberOfGroups] - 1) { 219 | return 50; 220 | } 221 | return 0; 222 | } 223 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 224 | if (indexPath.section == 6) { 225 | if (indexPath.row == 0) { 226 | [[NSBundle bundleWithPath:@"/System/Library/Frameworks/SafariServices.framework"] load]; 227 | if ([SFSafariViewController class] != nil) { 228 | SFSafariViewController *safariView = [[SFSafariViewController alloc] initWithURL:[NSURL URLWithString:@"https://github.com/MTACS/Ampere"]]; 229 | if ([safariView respondsToSelector:@selector(setPreferredControlTintColor:)]) { 230 | safariView.preferredControlTintColor = [UIColor systemGreenColor]; 231 | } 232 | [self.navigationController presentViewController:safariView animated:YES completion:nil]; 233 | } 234 | } 235 | } 236 | } 237 | - (UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath { 238 | UISwipeActionsConfiguration *swipeActions; 239 | PSTableCell *cell = (PSTableCell *)[tableView cellForRowAtIndexPath:indexPath]; 240 | NSMutableArray *actions = [NSMutableArray new]; 241 | if ([cell isKindOfClass:NSClassFromString(@"AmpSwitchCell")] && indexPath.section == 1) { 242 | UIContextualAction *colorPickerAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleNormal title:nil handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) { 243 | [(AmpSwitchCell *)cell selectColor]; 244 | completionHandler(YES); 245 | }]; 246 | colorPickerAction.backgroundColor = [(AmpSwitchCell *)cell selectedColor]; // [UIColor tableCellGroupedBackgroundColor]; 247 | colorPickerAction.image = [UIImage systemImageNamed:@"paintbrush.pointed.fill"]; 248 | 249 | if ([[[NSUserDefaults standardUserDefaults] objectForKey:cell.specifier.properties[@"key"] inDomain:domain] boolValue]) { 250 | [actions addObject:colorPickerAction]; 251 | } 252 | } else { 253 | if (indexPath.section == 0) { 254 | if (indexPath.row == 0) { 255 | UIContextualAction *info = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleNormal title:nil handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) { 256 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Transparent Text" message:@"\nEnabling transparent text allows the background of the current app to show through the battery's percentage text. This is done to replicate iOS 16's styling, and works best on Homescreen & Lockscreen" preferredStyle:UIAlertControllerStyleAlert]; 257 | [alertController addAction:[UIAlertAction actionWithTitle:@"Dismiss" style:UIAlertActionStyleDefault handler:nil]]; 258 | [self presentViewController:alertController animated:YES completion:nil]; 259 | completionHandler(YES); 260 | }]; 261 | 262 | info.backgroundColor = [UIColor tableCellGroupedBackgroundColor]; 263 | info.image = [UIImage systemImageNamed:@"info.circle.fill"]; 264 | 265 | [actions addObject:info]; 266 | } 267 | } else if (indexPath.section == 2) { 268 | if (indexPath.row == 0) { 269 | UIContextualAction *reset = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleNormal title:nil handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) { 270 | [[NSUserDefaults standardUserDefaults] setObject:@10 forKey:@"fontSize" inDomain:domain]; 271 | [[NSUserDefaults standardUserDefaults] synchronize]; 272 | [self reloadSpecifier:cell.specifier]; 273 | completionHandler(YES); 274 | }]; 275 | 276 | reset.backgroundColor = [UIColor tableCellGroupedBackgroundColor]; 277 | reset.image = [UIImage systemImageNamed:@"arrow.counterclockwise.circle"]; 278 | 279 | [actions addObject:reset]; 280 | } 281 | } 282 | } 283 | swipeActions = [UISwipeActionsConfiguration configurationWithActions:actions]; 284 | swipeActions.performsFirstActionWithFullSwipe = YES; 285 | return swipeActions; 286 | } 287 | - (void)setEnableSwitchState { 288 | if (![[[NSUserDefaults standardUserDefaults] objectForKey:@"enabled" inDomain:domain] boolValue]) { 289 | [[self enableSwitch] setOn:NO animated:NO]; 290 | } else { 291 | [[self enableSwitch] setOn:YES animated:NO]; 292 | } 293 | CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), (CFStringRef)@"com.mtac.amp/statusbar.changed", nil, nil, true); 294 | } 295 | - (void)switchStateChanged:(AmpSwitchState)switchState { 296 | AudioServicesPlaySystemSound(1519); 297 | if (!self.enableSwitch.isOn) { 298 | [[NSUserDefaults standardUserDefaults] setObject:@YES forKey:@"enabled" inDomain:domain]; 299 | [self.enableSwitch setOn:YES animated:YES]; 300 | } else { 301 | [[NSUserDefaults standardUserDefaults] setObject:@NO forKey:@"enabled" inDomain:domain]; 302 | [self.enableSwitch setOn:NO animated:YES]; 303 | } 304 | [[NSUserDefaults standardUserDefaults] synchronize]; 305 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^(void){ 306 | [self respring]; 307 | }); 308 | } 309 | - (void)setPreferenceValue:(id)value specifier:(PSSpecifier *)specifier { 310 | [super setPreferenceValue:value specifier:specifier]; 311 | // [self reloadSpecifiers]; 312 | CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), (CFStringRef)@"com.mtac.amp/statusbar.changed", nil, nil, true); 313 | } 314 | - (void)respring { 315 | UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleLarge]; 316 | indicator.translatesAutoresizingMaskIntoConstraints = NO; 317 | 318 | UIAlertController *applyAlert = [UIAlertController alertControllerWithTitle:@"\n\n" message:@"\n" preferredStyle:UIAlertControllerStyleAlert]; 319 | [applyAlert.view addSubview:indicator]; 320 | 321 | [NSLayoutConstraint activateConstraints:@[ 322 | [indicator.centerXAnchor constraintEqualToAnchor:applyAlert.view.centerXAnchor], 323 | [indicator.centerYAnchor constraintEqualToAnchor:applyAlert.view.centerYAnchor], 324 | ]]; 325 | 326 | [indicator startAnimating]; 327 | [self presentViewController:applyAlert animated:true completion:nil]; 328 | 329 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.75 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^(void){ 330 | SBSRelaunchAction *respringAction = [NSClassFromString(@"SBSRelaunchAction") actionWithReason:@"RestartRenderServer" options:4 targetURL:[NSURL URLWithString:@"prefs:root=Bridge"]]; 331 | FBSSystemService *frontBoardService = [NSClassFromString(@"FBSSystemService") sharedService]; 332 | NSSet *actions = [NSSet setWithObject:respringAction]; 333 | [frontBoardService sendActions:actions withResult:nil]; 334 | }); 335 | } 336 | - (void)reset { 337 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Reset Settings" message:@"\n All settings will be restored to default and device will respring. Continue?" preferredStyle:UIAlertControllerStyleAlert]; 338 | [alertController addAction:[UIAlertAction actionWithTitle:@"No" style:UIAlertActionStyleDefault handler:nil]]; 339 | [alertController addAction:[UIAlertAction actionWithTitle:@"Yes" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { 340 | [[NSUserDefaults standardUserDefaults] removePersistentDomainForName:domain]; 341 | [self reloadSpecifiers]; 342 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^(void){ 343 | [self respring]; 344 | }); 345 | }]]; 346 | [self presentViewController:alertController animated:YES completion:nil]; 347 | } 348 | @end 349 | 350 | @implementation AmpColorCell 351 | @dynamic control; 352 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier specifier:(PSSpecifier *)specifier { 353 | self = [super initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseIdentifier specifier:specifier]; 354 | if (self) { 355 | self.accessoryView = self.control; 356 | self.detailTextLabel.text = [specifier.properties objectForKey:@"subtitle"]; 357 | self.detailTextLabel.numberOfLines = 2; 358 | [self setCellEnabled:[[[NSUserDefaults standardUserDefaults] objectForKey:@"textStyle" inDomain:domain] integerValue] == 2]; 359 | } 360 | return self; 361 | } 362 | - (void)setCellEnabled:(BOOL)cellEnabled { 363 | [super setCellEnabled:cellEnabled]; 364 | self.control.backgroundColor = cellEnabled ? [self selectedColor] : [UIColor secondaryLabelColor]; 365 | // self.control.hidden = !cellEnabled; 366 | } 367 | - (BOOL)cellEnabled { 368 | return [[[NSUserDefaults standardUserDefaults] objectForKey:@"textStyle" inDomain:domain] integerValue] == 2; 369 | } 370 | - (void)refreshCellContentsWithSpecifier:(PSSpecifier *)specifier { 371 | [super refreshCellContentsWithSpecifier:specifier]; 372 | self.control.backgroundColor = [self cellEnabled] ? [self selectedColor] : [UIColor secondaryLabelColor]; 373 | } 374 | - (UIButton *)newControl { 375 | UIButton *colorButton = [UIButton buttonWithType:UIButtonTypeCustom]; 376 | colorButton.frame = CGRectMake(0, 0, 30, 30); 377 | colorButton.backgroundColor = [self selectedColor]; 378 | colorButton.layer.masksToBounds = NO; 379 | colorButton.layer.cornerRadius = colorButton.frame.size.width / 2; 380 | [colorButton addTarget:self action:@selector(selectColor) forControlEvents:UIControlEventTouchUpInside]; 381 | return colorButton; 382 | } 383 | - (void)selectColor { 384 | UIColorPickerViewController *colorPickerController = [[UIColorPickerViewController alloc] init]; 385 | colorPickerController.delegate = self; 386 | colorPickerController.supportsAlpha = NO; 387 | colorPickerController.modalPresentationStyle = UIModalPresentationPageSheet; 388 | colorPickerController.modalInPresentation = YES; 389 | colorPickerController.selectedColor = [self selectedColor]; 390 | [[self _viewControllerForAncestor] presentViewController:colorPickerController animated:YES completion:nil]; 391 | } 392 | - (UIColor *)selectedColor { 393 | NSDictionary *colorDict = [[NSUserDefaults standardUserDefaults] objectForKey:[self.specifier.properties[@"key"] stringByAppendingString:@"Dict"] inDomain:domain]; 394 | return colorDict ? [UIColor colorWithRed:[colorDict[@"red"] floatValue] green:[colorDict[@"green"] floatValue] blue:[colorDict[@"blue"] floatValue] alpha:1.0] : [UIColor secondaryLabelColor]; 395 | } 396 | - (void)colorPickerViewControllerDidSelectColor:(UIColorPickerViewController *)viewController { 397 | [[NSUserDefaults standardUserDefaults] setObject:[self dictionaryForColor:viewController.selectedColor] forKey:[self.specifier.properties[@"key"] stringByAppendingString:@"Dict"] inDomain:domain]; 398 | [[NSUserDefaults standardUserDefaults] synchronize]; 399 | self.control.backgroundColor = [self selectedColor]; 400 | CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), (CFStringRef)@"com.mtac.amp/statusbar.changed", nil, nil, true); 401 | } 402 | - (NSDictionary *)dictionaryForColor:(UIColor *)color { 403 | const CGFloat *components = CGColorGetComponents(color.CGColor); 404 | NSMutableDictionary *colorDict = [NSMutableDictionary new]; 405 | [colorDict setObject:[NSNumber numberWithFloat:components[0]] forKey:@"red"]; 406 | [colorDict setObject:[NSNumber numberWithFloat:components[1]] forKey:@"green"]; 407 | [colorDict setObject:[NSNumber numberWithFloat:components[2]] forKey:@"blue"]; 408 | return colorDict; 409 | } 410 | @end 411 | 412 | @implementation AmpStepperCell 413 | @dynamic control; 414 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier specifier:(PSSpecifier *)specifier { 415 | self = [super initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseIdentifier specifier:specifier]; 416 | if (self) { 417 | self.accessoryView = self.control; 418 | self.detailTextLabel.text = specifier.properties[@"subtitle"] ?: @""; 419 | self.detailTextLabel.numberOfLines = 2; 420 | } 421 | return self; 422 | } 423 | - (void)refreshCellContentsWithSpecifier:(PSSpecifier *)specifier { 424 | [super refreshCellContentsWithSpecifier:specifier]; 425 | self.control.minimumValue = [specifier.properties[@"min"] doubleValue]; 426 | self.control.maximumValue = [specifier.properties[@"max"] doubleValue]; 427 | [self _updateLabel]; 428 | } 429 | - (void)setCellEnabled:(BOOL)cellEnabled { 430 | [super setCellEnabled:cellEnabled]; 431 | self.control.enabled = cellEnabled; 432 | } 433 | - (UIStepper *)newControl { 434 | UIStepper *stepper = [[UIStepper alloc] initWithFrame:CGRectZero]; 435 | stepper.continuous = NO; 436 | return stepper; 437 | } 438 | - (NSNumber *)controlValue { 439 | return @(self.control.value); 440 | } 441 | - (void)setValue:(NSNumber *)value { 442 | [super setValue:value]; 443 | self.control.value = value.doubleValue; 444 | } 445 | - (void)controlChanged:(UIStepper *)stepper { 446 | [super controlChanged:stepper]; 447 | [self _updateLabel]; 448 | } 449 | - (void)_updateLabel { 450 | if (!self.control) { 451 | return; 452 | } 453 | NSString *labelText = [NSString stringWithFormat:@"%@ %d", self.specifier.name, (int)self.control.value]; 454 | if (labelText != nil) { 455 | self.textLabel.text = labelText; 456 | [self setNeedsLayout]; 457 | } 458 | } 459 | - (void)prepareForReuse { 460 | [super prepareForReuse]; 461 | self.control.value = 0; 462 | self.control.minimumValue = 0; 463 | self.control.maximumValue = 100; 464 | } 465 | @end 466 | 467 | @implementation AmpSwitchCell 468 | - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(id)identifier specifier:(PSSpecifier *)specifier { 469 | self = [super initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:identifier specifier:specifier]; 470 | if (self) { 471 | self.accessoryView = self.control; 472 | self.detailTextLabel.text = specifier.properties[@"subtitle"] ?: @""; 473 | self.detailTextLabel.numberOfLines = [specifier.properties[@"subtitleLines"] intValue] ?: 2; 474 | } 475 | return self; 476 | } 477 | - (id)newControl { 478 | AmpSwitch *switchControl = [[AmpSwitch alloc] initWithSize:AmpSwitchSizeNormal state:AmpSwitchStateOff]; 479 | switchControl.delegate = self; 480 | switchControl.thumbOnTintColor = [UIColor systemGreenColor]; 481 | switchControl.thumbOffTintColor = [UIColor labelColor]; 482 | switchControl.trackOnTintColor = [UIColor secondaryLabelColor]; 483 | switchControl.trackOffTintColor = [UIColor secondaryLabelColor]; 484 | return switchControl; 485 | } 486 | - (void)switchStateChanged:(AmpSwitchState)currentState { 487 | AudioServicesPlaySystemSound(1519); 488 | } 489 | - (void)selectColor { 490 | UIColorPickerViewController *colorPickerController = [[UIColorPickerViewController alloc] init]; 491 | colorPickerController.delegate = self; 492 | colorPickerController.supportsAlpha = NO; 493 | colorPickerController.modalPresentationStyle = UIModalPresentationPageSheet; 494 | colorPickerController.modalInPresentation = YES; 495 | colorPickerController.selectedColor = [self selectedColor]; 496 | [[self _viewControllerForAncestor] presentViewController:colorPickerController animated:YES completion:nil]; 497 | } 498 | - (UIColor *)selectedColor { 499 | NSDictionary *colorDict = [[NSUserDefaults standardUserDefaults] objectForKey:[self.specifier.properties[@"key"] stringByAppendingString:@"Dict"] inDomain:domain]; 500 | return colorDict ? [UIColor colorWithRed:[colorDict[@"red"] floatValue] green:[colorDict[@"green"] floatValue] blue:[colorDict[@"blue"] floatValue] alpha:1.0] : [UIColor secondaryLabelColor]; 501 | } 502 | - (void)colorPickerViewControllerDidSelectColor:(UIColorPickerViewController *)viewController { 503 | [[NSUserDefaults standardUserDefaults] setObject:[self dictionaryForColor:viewController.selectedColor] forKey:[self.specifier.properties[@"key"] stringByAppendingString:@"Dict"] inDomain:domain]; 504 | [[NSUserDefaults standardUserDefaults] synchronize]; 505 | 506 | CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), (CFStringRef)@"com.mtac.amp/statusbar.changed", nil, nil, true); 507 | } 508 | - (NSDictionary *)dictionaryForColor:(UIColor *)color { 509 | const CGFloat *components = CGColorGetComponents(color.CGColor); 510 | NSMutableDictionary *colorDict = [NSMutableDictionary new]; 511 | [colorDict setObject:[NSNumber numberWithFloat:components[0]] forKey:@"red"]; 512 | [colorDict setObject:[NSNumber numberWithFloat:components[1]] forKey:@"green"]; 513 | [colorDict setObject:[NSNumber numberWithFloat:components[2]] forKey:@"blue"]; 514 | return colorDict; 515 | } 516 | @end 517 | 518 | @implementation AmpSelectorCell 519 | @dynamic control; 520 | - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(id)identifier specifier:(PSSpecifier *)specifier { 521 | self = [super initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:identifier specifier:specifier]; 522 | if (self) { 523 | self.accessoryView = self.control; 524 | self.detailTextLabel.text = specifier.properties[@"subtitle"] ?: @""; 525 | self.detailTextLabel.numberOfLines = 2; 526 | 527 | [self _updateControl]; 528 | } 529 | return self; 530 | } 531 | - (id)newControl { 532 | UIButton *selectorButton = [UIButton buttonWithType:UIButtonTypeCustom]; 533 | selectorButton.frame = CGRectMake(0, 0, 120, 32); 534 | selectorButton.menu = [self menu]; 535 | selectorButton.showsMenuAsPrimaryAction = YES; 536 | selectorButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentRight; 537 | [selectorButton setTitleColor:[UIColor secondaryLabelColor] forState:UIControlStateNormal]; 538 | 539 | [self _updateControl]; 540 | return selectorButton; 541 | } 542 | - (UIMenu *)menu { 543 | NSMutableArray *menuItems = [NSMutableArray new]; 544 | NSArray *values = self.specifier.properties[@"values"]; 545 | NSArray *titles = self.specifier.properties[@"titles"]; 546 | NSArray *images = self.specifier.properties[@"images"]; 547 | // NSString *key = [self.specifier.properties objectForKey:@"key"]; 548 | for (NSInteger i = 0; i < values.count; i++) { 549 | UIAction *action = [UIAction actionWithTitle:titles[i] image:[UIImage systemImageNamed:images[i]] identifier:nil handler:^(__kindof UIAction *_Nonnull action) { 550 | NSInteger valueIndex = [[values objectAtIndex:i] integerValue]; 551 | [self setValue:[NSNumber numberWithInteger:valueIndex]]; 552 | [self _updateControl]; 553 | }]; 554 | if ([[self value] integerValue] == i) { 555 | action.state = UIMenuElementStateOn; 556 | } else { 557 | action.state = UIMenuElementStateOff; 558 | } 559 | [menuItems addObject:action]; 560 | } 561 | 562 | UIMenu *menuActions = [UIMenu menuWithTitle:@"" children:menuItems]; 563 | return menuActions; 564 | } 565 | - (void)refreshCellContentsWithSpecifier:(PSSpecifier *)specifier { 566 | [super refreshCellContentsWithSpecifier:specifier]; 567 | [self _updateControl]; 568 | } 569 | - (void)_updateControl { 570 | NSArray *titles = self.specifier.properties[@"titles"]; 571 | NSInteger value = [[self value] integerValue]; 572 | CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), (CFStringRef)@"com.mtac.amp/preferences.changed", nil, nil, true); 573 | CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), (CFStringRef)@"com.mtac.amp/statusbar.changed", nil, nil, true); 574 | 575 | [(UIButton *)self.control setMenu:[self menu]]; 576 | [(UIButton *)self.control setTitle:[titles[value] stringByAppendingString:@" ›"] forState:UIControlStateNormal]; 577 | } 578 | - (void)setValue:(id)arg0 { 579 | [super setValue:arg0]; 580 | NSString *key = [self.specifier.properties objectForKey:@"key"]; 581 | [[NSUserDefaults standardUserDefaults] setObject:arg0 forKey:key inDomain:domain]; 582 | } 583 | @end 584 | 585 | @implementation AmpCodeCell 586 | - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(id)identifier specifier:(PSSpecifier *)specifier { 587 | self = [super initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:identifier specifier:specifier]; 588 | if (self) { 589 | self.accessoryView = [[UIImageView alloc] initWithImage:[[UIImage systemImageNamed:@"network"] imageWithTintColor:[UIColor secondaryLabelColor]]]; 590 | self.detailTextLabel.text = specifier.properties[@"subtitle"] ?: @""; 591 | self.detailTextLabel.numberOfLines = 1; 592 | } 593 | return self; 594 | } 595 | @end --------------------------------------------------------------------------------