/* * Copyright (C) 2021 Cameron Katri * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #import #import #import #import "Tweak.h" void openApplication(NSString *bundleID) { FBSOpenApplicationOptions *opts = [%c(FBSOpenApplicationOptions) optionsWithDictionary:@{ @"__LaunchOrigin" : @"BulletinDestinationCoverSheet", @"__PromptUnlockDevice" : @YES, @"__UnlockDevice" : @YES, @"__LaunchImage" : @"", @"__Actions" : @[] }]; FBSOpenApplicationService *openService = [[%c(FBSOpenApplicationService) alloc] init]; [openService openApplication:bundleID withOptions:opts completion:nil]; } %hook CSQuickActionsView %property (nonatomic, retain) NSMutableArray * leftButtons; %property (nonatomic, retain) NSMutableArray * rightButtons; %property (nonatomic) BOOL leftOpen; %property (nonatomic) BOOL rightOpen; %property (nonatomic) BOOL collapseLeft; %property (nonatomic) BOOL collapseRight; %property (nonatomic, retain) DNDStateService *stateService; -(id)initWithFrame:(CGRect)arg1 delegate:(id)arg2 { id o = %orig; NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"com.cameronkatri.quickactions"]; NSArray *leftButtons = [defaults arrayForKey:@"leftButtons"]; if (leftButtons == nil) leftButtons = @[@"com.apple.flashlight"]; NSArray *rightButtons = [defaults arrayForKey:@"rightButtons"]; if (rightButtons == nil) rightButtons = @[@"com.apple.camera"]; self.leftButtons = [[NSMutableArray alloc] init]; self.rightButtons = [[NSMutableArray alloc] init]; self.collapseLeft = [leftButtons count] > 1 ? true : false; self.leftOpen = !self.collapseLeft; if ([leftButtons count] == 0) { [self.flashlightButton setHidden:1]; } else if ([leftButtons count] == 1) { [self.flashlightButton setBundleID:leftButtons[0]]; } else if ([leftButtons count] > 1) { [self.flashlightButton setImage:nil]; for (int i = 0; i < [leftButtons count]; i++) { CSQuickActionsButton *button = [[CSQuickActionsButton alloc] initWithType:(i + 2)]; [button setBundleID:leftButtons[i]]; [button setBackgroundEffectViewGroupName:[self _buttonGroupName]]; [button setLegibilitySettings:[self legibilitySettings]]; [button setPermitted:1]; [self insertSubview:button belowSubview:self.flashlightButton]; [self _addTargetsToButton:button]; [self.leftButtons addObject:button]; } } self.collapseRight = [rightButtons count] > 1 ? true : false; self.rightOpen = !self.collapseRight; if ([rightButtons count] == 0) { [self.cameraButton setHidden:1]; } else if ([rightButtons count] == 1) { [self.cameraButton setBundleID:rightButtons[0]]; } else if ([rightButtons count] > 1) { [self.cameraButton setImage:nil]; for (int i = 0; i < [rightButtons count]; i++) { CSQuickActionsButton *button = [[CSQuickActionsButton alloc] initWithType:(i + 2)]; [button setBundleID:rightButtons[i]]; [button setBackgroundEffectViewGroupName:[self _buttonGroupName]]; [button setLegibilitySettings:[self legibilitySettings]]; [button setPermitted:1]; [self insertSubview:button belowSubview:self.cameraButton]; [self _addTargetsToButton:button]; [self.rightButtons addObject:button]; } } self.stateService = (DNDStateService *)[objc_getClass("DNDStateService") serviceForClientIdentifier:@"com.apple.donotdisturb.control-center.module"]; [self.stateService addStateUpdateListener:self withCompletionHandler:nil]; return o; } -(void)refreshFlashlightAvailability { %orig; [UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^(void){ for (CSQuickActionsButton *button in [self leftButtons]) button.frame = [self leftFrameForButton:button open:NO]; self.leftOpen = NO; for (CSQuickActionsButton *button in [self rightButtons]) button.frame = [self rightFrameForButton:button open:NO]; self.rightOpen = NO; } completion:^(BOOL finished) { for (CSQuickActionsButton *button in [self leftButtons]) [button setHidden:YES]; for (CSQuickActionsButton *button in [self rightButtons]) [button setHidden:YES]; }]; } %new -(CGRect)rightFrameForButton:(CSQuickActionsButton*)button open:(BOOL)open { CGRect cameraFrame = [[self cameraButton] frame]; if (open) { return CGRectMake(cameraFrame.origin.x, cameraFrame.origin.y - ((cameraFrame.size.height * 3/4) * (button.type - 1)), cameraFrame.size.width, cameraFrame.size.height); } else { return cameraFrame; } } %new -(CGRect)leftFrameForButton:(CSQuickActionsButton*)button open:(BOOL)open { CGRect flashlightFrame = [[self flashlightButton] frame]; if (open) { return CGRectMake(flashlightFrame.origin.x, flashlightFrame.origin.y - ((flashlightFrame.size.height * 3/4) * (button.type - 1)), flashlightFrame.size.width, flashlightFrame.size.height); } else { return flashlightFrame; } } -(void)setLegibilitySettings:(id)legibilitySettings { %orig; for (CSQuickActionsButton *button in [self leftButtons]) [button setLegibilitySettings:legibilitySettings]; for (CSQuickActionsButton *button in [self rightButtons]) [button setLegibilitySettings:legibilitySettings]; } -(void)_layoutQuickActionButtons { %orig; UIEdgeInsets insets = [self _buttonOutsets]; if (SBFEffectiveHomeButtonType() != 2) { CGRect bounds = [[UIScreen mainScreen] _referenceBounds]; // Detect if we are on an iPhone SE and adjust the insets // accordingly to not overlap with the text if ([[UIScreen mainScreen] nativeBounds].size.height == 1136) insets.bottom += 10; CGFloat buttonWidth = 50 + insets.right + insets.left; CGFloat buttonHeight = 50 + insets.top + insets.bottom; [[self flashlightButton] setEdgeInsets:insets]; self.flashlightButton.frame = CGRectMake(insets.left, bounds.size.height - buttonHeight - insets.bottom, buttonWidth, buttonHeight); [[self cameraButton] setEdgeInsets:insets]; self.cameraButton.frame = CGRectMake(bounds.size.width - insets.left - buttonWidth, bounds.size.height - buttonHeight - insets.bottom, buttonWidth, buttonHeight); } NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"com.cameronkatri.quickactions"]; CGRect frame = self.flashlightButton.frame; frame.origin.x += [defaults floatForKey:@"LeftOffsetX"]; frame.origin.y -= [defaults floatForKey:@"LeftOffsetY"]; self.flashlightButton.frame = frame; frame = self.cameraButton.frame; frame.origin.x -= [defaults floatForKey:@"RightOffsetX"]; frame.origin.y -= [defaults floatForKey:@"RightOffsetY"]; self.cameraButton.frame = frame; UIImageSymbolConfiguration *imageConfig = [UIImageSymbolConfiguration configurationWithTextStyle:UIFontTextStyleTitle1]; UIImage *image = [UIImage systemImageNamed:@"ellipsis" withConfiguration:imageConfig]; if ([self.leftButtons count] > 1) { [self.flashlightButton setImage:image]; ((UIImageView*)[self.flashlightButton valueForKey:@"_contentView"]).contentMode = UIViewContentModeScaleAspectFit; } if ([self.rightButtons count] > 1) { [self.cameraButton setImage:image]; ((UIImageView*)[self.cameraButton valueForKey:@"_contentView"]).contentMode = UIViewContentModeScaleAspectFit; } for (CSQuickActionsButton *button in [self leftButtons]) { [button setEdgeInsets:insets]; button.frame = [self leftFrameForButton:button open:NO]; [button setHidden:YES]; } for (CSQuickActionsButton *button in [self rightButtons]) { [button setEdgeInsets:insets]; button.frame = [self rightFrameForButton:button open:NO]; [button setHidden:YES]; } [self updateDND:nil]; } -(void)handleButtonPress:(CSQuickActionsButton *)button { [button setSelected:false]; if (button.type == 0 && self.collapseRight) { self.rightOpen = !self.rightOpen; [UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^(void){ for (CSQuickActionsButton *button in [self rightButtons]) { button.frame = [self rightFrameForButton:button open:self.rightOpen]; if (self.rightOpen) [button setHidden:0]; } } completion:^(BOOL finished) { for (CSQuickActionsButton *button in [self rightButtons]) [button setHidden:!self.rightOpen]; }]; } else if (button.type == 1 && self.collapseLeft) { self.leftOpen = !self.leftOpen; [UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^(void) { for (CSQuickActionsButton *button in [self leftButtons]) { button.frame = [self leftFrameForButton:button open:self.leftOpen]; if (self.leftOpen) [button setHidden:0]; } } completion:^(BOOL finished) { for (CSQuickActionsButton *button in [self leftButtons]) [button setHidden:!self.leftOpen]; }]; } else if ([button.bundleID isEqualToString:@"com.apple.flashlight"]) { [self.delegate _toggleFlashlight]; } else if ([button.bundleID isEqualToString:@"com.apple.camera"]) { [self.delegate _launchCamera]; } else if ([button.bundleID isEqualToString:@"com.apple.donotdisturb"]) { [self setDoNotDisturb:!self.isDNDActive]; } else if (button.bundleID) openApplication(button.bundleID); else { %orig; return; } /* This will make the flashlight module be correct */ [self.delegate _resetIdleTimer]; [self.delegate sendAction:[CSAction actionWithType:5]]; return; } -(void)handleButtonTouchBegan:(CSQuickActionsButton *)button { if (button.bundleID != nil || (button.type == 0 && self.collapseRight) || (button.type == 1 && self.collapseLeft)) return; %orig; } -(void)handleButtonTouchEnded:(CSQuickActionsButton *)button { if (button.bundleID != nil || (button.type == 0 && self.collapseRight) || (button.type == 1 && self.collapseLeft)) return; %orig; } -(void)setFlashlightOn:(BOOL)arg { if ([self.flashlightButton.bundleID isEqualToString:@"com.apple.flashlight"]) [self.flashlightButton setSelected:arg]; if ([self.cameraButton.bundleID isEqualToString:@"com.apple.flashlight"]) [self.cameraButton setSelected:arg]; for (CSQuickActionsButton *button in [self leftButtons]) if ([button.bundleID isEqualToString:@"com.apple.flashlight"]) [button setSelected:arg]; for (CSQuickActionsButton *button in [self rightButtons]) if ([button.bundleID isEqualToString:@"com.apple.flashlight"]) [button setSelected:arg]; } %new -(void)setDoNotDisturb:(BOOL)state { DNDModeAssertionService *assertionService = (DNDModeAssertionService *)[objc_getClass("DNDModeAssertionService") serviceForClientIdentifier:@"com.apple.donotdisturb.control-center.module"]; if (state) { DNDModeAssertionDetails *newAssertion = [objc_getClass("DNDModeAssertionDetails") userRequestedAssertionDetailsWithIdentifier:@"com.apple.control-center.manual-toggle" modeIdentifier:@"com.apple.donotdisturb.mode.default" lifetime:nil]; [assertionService takeModeAssertionWithDetails:newAssertion error:NULL]; } else { [assertionService invalidateAllActiveModeAssertionsWithError:NULL]; } [[NSNotificationCenter defaultCenter] postNotificationName:@"SBQuietModeStatusChangedNotification" object:nil]; } %new -(BOOL)isDNDActive { // DNDStateService *stateService = (DNDStateService *)[objc_getClass("DNDStateService") serviceForClientIdentifier:@"com.apple.donotdisturb.control-center.module"]; return [[self.stateService queryCurrentStateWithError:nil] isActive]; } %new -(void)updateDND:(NSNotification *)notif { BOOL active = [self isDNDActive]; if ([self.flashlightButton.bundleID isEqualToString:@"com.apple.donotdisturb"]) [self.flashlightButton setSelected:active]; if ([self.cameraButton.bundleID isEqualToString:@"com.apple.donotdisturb"]) [self.cameraButton setSelected:active]; for (CSQuickActionsButton *button in [self leftButtons]) if ([button.bundleID isEqualToString:@"com.apple.donotdisturb"]) [button setSelected:active]; for (CSQuickActionsButton *button in [self rightButtons]) if ([button.bundleID isEqualToString:@"com.apple.donotdisturb"]) [button setSelected:active]; } %new -(void)stateService:(id)arg1 didReceiveDoNotDisturbStateUpdate:(id)arg2 { dispatch_async(dispatch_get_main_queue(), ^{ [self updateDND:nil]; }); } %end %hook CSQuickActionsButton %property (nonatomic, retain) NSString *bundleID; -(void)setImage:(UIImage *)img { %orig; [[self valueForKey:@"_contentView"] setImage:img]; } -(void)setBundleID:(NSString*)bundleID { %orig; if ([bundleID isEqualToString:@"com.apple.camera"]) { [self setImage:[self _imageWithName:@"OrbCamera"]]; [self setLatching:FALSE]; } else if ([bundleID isEqualToString:@"com.apple.flashlight"]) { [self setImage:[self _imageWithName:@"OrbFlashlightOff"]]; [self setSelectedImage:[self _imageWithName:@"OrbFlashlightOff"]]; [self setLatching:TRUE]; } else if (([bundleID isEqualToString:@"com.apple.donotdisturb"])) { UIImageSymbolConfiguration *imageConfig = [UIImageSymbolConfiguration configurationWithTextStyle:UIFontTextStyleTitle2]; [self setImage:[UIImage systemImageNamed:@"moon.fill" withConfiguration:imageConfig]]; [self setSelectedImage:[UIImage systemImageNamed:@"moon.fill" withConfiguration:imageConfig]]; ((UIImageView *)[self valueForKey:@"_contentView"]).contentMode = UIViewContentModeScaleAspectFit; [self setLatching:TRUE]; } else { [self setImage:[UIImage _applicationIconImageForBundleIdentifier:bundleID format:0 scale:[UIScreen mainScreen].scale]]; [self setLatching:FALSE]; } } -(void)setPermitted:(BOOL)permitted { %orig(YES); } %end %hook CSQuickActionsViewController -(BOOL)hasCamera { return true; } -(BOOL)hasFlashlight { return true; } +(BOOL)deviceSupportsButtons { return true; } %end // vim: filetype=logos