]> git.cameronkatri.com Git - tweaks.git/blob - QuickActions/Tweak.x
Fixes requested by Chariz
[tweaks.git] / QuickActions / Tweak.x
1 /*
2 * Copyright (C) 2021 Cameron Katri
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU Affero General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Affero General Public License for more details.
13 *
14 * You should have received a copy of the GNU Affero General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17 #import <Foundation/NSUserDefaults.h>
18 #import <CoreGraphics/CoreGraphics.h>
19 #import <UIKit/UIKit.h>
20
21 #import "Tweak.h"
22
23 void openApplication(NSString *bundleID)
24 {
25 FBSOpenApplicationOptions* opts = [%c(FBSOpenApplicationOptions) optionsWithDictionary:@{
26 @"__LaunchOrigin" : @"BulletinDestinationCoverSheet",
27 @"__PromptUnlockDevice" : @YES,
28 @"__UnlockDevice" : @YES,
29 @"__LaunchImage" : @"",
30 @"__Actions" : @[]
31 }];
32 FBSystemServiceOpenApplicationRequest* request = [%c(FBSystemServiceOpenApplicationRequest) request];
33 request.options = opts;
34 request.bundleIdentifier = bundleID;
35 request.trusted = YES;
36 request.clientProcess = [[%c(FBProcessManager) sharedInstance] systemApplicationProcess];
37
38 [[%c(SBMainWorkspace) sharedInstance] systemService:[%c(FBSystemService) sharedInstance] handleOpenApplicationRequest:request withCompletion:^{}];
39 }
40
41 %hook CSQuickActionsView
42
43 %property (nonatomic, retain) NSMutableArray * leftButtons;
44 %property (nonatomic, retain) NSMutableArray * rightButtons;
45 %property (nonatomic) BOOL leftOpen;
46 %property (nonatomic) BOOL rightOpen;
47 %property (nonatomic) BOOL collapseLeft;
48 %property (nonatomic) BOOL collapseRight;
49 %property (nonatomic, retain) DNDStateService *stateService;
50
51 -(id)initWithFrame:(CGRect)arg1 delegate:(id)arg2
52 {
53 id o = %orig;
54
55 NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"com.cameronkatri.quickactions"];
56
57 NSArray *leftButtons = [defaults arrayForKey:@"leftButtons"];
58 if (leftButtons == nil)
59 leftButtons = @[@"com.apple.flashlight"];
60 NSArray *rightButtons = [defaults arrayForKey:@"rightButtons"];
61 if (rightButtons == nil)
62 rightButtons = @[@"com.apple.camera"];
63
64 self.leftButtons = [[NSMutableArray alloc] init];
65 self.rightButtons = [[NSMutableArray alloc] init];
66
67 self.collapseLeft = [leftButtons count] > 1 ? true : false;
68 self.leftOpen = !self.collapseLeft;
69
70 if ([leftButtons count] == 0) {
71 [self.flashlightButton setHidden:1];
72 } else if ([leftButtons count] == 1) {
73 [self.flashlightButton setBundleID:leftButtons[0]];
74 } else if ([leftButtons count] > 1) {
75 [self.flashlightButton setImage:nil];
76 for (int i = 0; i < [leftButtons count]; i++) {
77 CSQuickActionsButton *button = [[CSQuickActionsButton alloc] initWithType:(i + 2)];
78 [button setBundleID:leftButtons[i]];
79
80 [button setBackgroundEffectViewGroupName:[self _buttonGroupName]];
81 [button setLegibilitySettings:[self legibilitySettings]];
82 [button setPermitted:1];
83
84 [self insertSubview:button belowSubview:self.flashlightButton];
85 [self _addTargetsToButton:button];
86 [self.leftButtons addObject:button];
87 }
88 }
89
90 self.collapseRight = [rightButtons count] > 1 ? true : false;
91 self.rightOpen = !self.collapseRight;
92
93 if ([rightButtons count] == 0) {
94 [self.cameraButton setHidden:1];
95 } else if ([rightButtons count] == 1) {
96 [self.cameraButton setBundleID:rightButtons[0]];
97 } else if ([rightButtons count] > 1) {
98 [self.cameraButton setImage:nil];
99 for (int i = 0; i < [rightButtons count]; i++) {
100 CSQuickActionsButton *button = [[CSQuickActionsButton alloc] initWithType:(i + 2)];
101 [button setBundleID:rightButtons[i]];
102
103 [button setBackgroundEffectViewGroupName:[self _buttonGroupName]];
104 [button setLegibilitySettings:[self legibilitySettings]];
105 [button setPermitted:1];
106
107 [self insertSubview:button belowSubview:self.cameraButton];
108 [self _addTargetsToButton:button];
109 [self.rightButtons addObject:button];
110 }
111 }
112
113 self.stateService = (DNDStateService *)[objc_getClass("DNDStateService") serviceForClientIdentifier:@"com.apple.donotdisturb.control-center.module"];
114 [self.stateService addStateUpdateListener:self withCompletionHandler:nil];
115
116 return o;
117 }
118
119 -(void)refreshFlashlightAvailability
120 {
121 %orig;
122
123 if (self.leftOpen) {
124 self.leftOpen = !self.leftOpen;
125 for (CSQuickActionsButton *button in [self leftButtons]) {
126 button.frame = [self leftFrameForButton:button];
127 [button setHidden:!self.leftOpen];
128 }
129 }
130 if (self.rightOpen) {
131 self.rightOpen = !self.rightOpen;
132 for (CSQuickActionsButton *button in [self rightButtons]) {
133 button.frame = [self rightFrameForButton:button];
134 [button setHidden:!self.rightOpen];
135 }
136 }
137 }
138
139 %new
140 -(CGRect)rightFrameForButton:(CSQuickActionsButton*)button
141 {
142 CGRect cameraFrame = [[self cameraButton] frame];
143 if (self.rightOpen) {
144 return CGRectMake(cameraFrame.origin.x,
145 cameraFrame.origin.y - ((cameraFrame.size.height * 3/4) * (button.type - 1)),
146 cameraFrame.size.width, cameraFrame.size.height);
147 } else {
148 return cameraFrame;
149 }
150 }
151
152 %new
153 -(CGRect)leftFrameForButton:(CSQuickActionsButton*)button
154 {
155 CGRect flashlightFrame = [[self flashlightButton] frame];
156 if (self.leftOpen) {
157 return CGRectMake(flashlightFrame.origin.x,
158 flashlightFrame.origin.y - ((flashlightFrame.size.height * 3/4) * (button.type - 1)),
159 flashlightFrame.size.width, flashlightFrame.size.height);
160 } else {
161 return flashlightFrame;
162 }
163 }
164
165 -(void)setLegibilitySettings:(id)legibilitySettings
166 {
167 %orig;
168 for (CSQuickActionsButton *button in [self leftButtons])
169 [button setLegibilitySettings:legibilitySettings];
170 for (CSQuickActionsButton *button in [self rightButtons])
171 [button setLegibilitySettings:legibilitySettings];
172 }
173
174 -(void)_layoutQuickActionButtons
175 {
176 %orig;
177
178 UIEdgeInsets insets = [self _buttonOutsets];
179 if (SBFEffectiveHomeButtonType() != 2) {
180 CGRect bounds = [[UIScreen mainScreen] _referenceBounds];
181
182 // Detect if we are on an iPhone SE and adjust the insets
183 // accordingly to not overlap with the text
184 if ([[UIScreen mainScreen] nativeBounds].size.height == 1136)
185 insets.bottom += 10;
186
187 CGFloat buttonWidth = 50 + insets.right + insets.left;
188 CGFloat buttonHeight = 50 + insets.top + insets.bottom;
189
190 [[self flashlightButton] setEdgeInsets:insets];
191
192 self.flashlightButton.frame = CGRectMake(insets.left,
193 bounds.size.height - buttonHeight - insets.bottom,
194 buttonWidth, buttonHeight);
195
196 [[self cameraButton] setEdgeInsets:insets];
197
198 self.cameraButton.frame = CGRectMake(bounds.size.width - insets.left - buttonWidth,
199 bounds.size.height - buttonHeight - insets.bottom,
200 buttonWidth, buttonHeight);
201 }
202
203 NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"com.cameronkatri.quickactions"];
204
205 CGRect frame = self.flashlightButton.frame;
206 frame.origin.x += [defaults floatForKey:@"LeftOffsetX"];
207 frame.origin.y -= [defaults floatForKey:@"LeftOffsetY"];
208 self.flashlightButton.frame = frame;
209
210 frame = self.cameraButton.frame;
211 frame.origin.x -= [defaults floatForKey:@"RightOffsetX"];
212 frame.origin.y -= [defaults floatForKey:@"RightOffsetY"];
213 self.cameraButton.frame = frame;
214
215 UIImageSymbolConfiguration *imageConfig = [UIImageSymbolConfiguration configurationWithTextStyle:UIFontTextStyleTitle1];
216 UIImage *image = [UIImage systemImageNamed:@"ellipsis" withConfiguration:imageConfig];
217 if ([self.leftButtons count] > 1) {
218 [self.flashlightButton setImage:image];
219 ((UIImageView*)[self.flashlightButton valueForKey:@"_contentView"]).contentMode = UIViewContentModeScaleAspectFit;
220 }
221 if ([self.rightButtons count] > 1) {
222 [self.cameraButton setImage:image];
223 ((UIImageView*)[self.cameraButton valueForKey:@"_contentView"]).contentMode = UIViewContentModeScaleAspectFit;
224 }
225
226 for (CSQuickActionsButton *button in [self leftButtons]) {
227 [button setEdgeInsets:insets];
228 button.frame = [self leftFrameForButton:button];
229 [button setHidden:!self.leftOpen];
230 }
231 for (CSQuickActionsButton *button in [self rightButtons]) {
232 [button setEdgeInsets:insets];
233 button.frame = [self rightFrameForButton:button];
234 [button setHidden:!self.rightOpen];
235 }
236
237 [self updateDND:nil];
238 }
239
240 -(void)handleButtonPress:(CSQuickActionsButton *)button
241 {
242 [button setSelected:false];
243
244 if (button.type == 0 && self.collapseRight) {
245 self.rightOpen = !self.rightOpen;
246 [UIView animateWithDuration:0.25
247 delay:0
248 options:UIViewAnimationOptionCurveEaseOut
249 animations:^(void){
250 for (CSQuickActionsButton *button in [self rightButtons]) {
251 button.frame = [self rightFrameForButton:button];
252 if (self.rightOpen)
253 [button setHidden:0];
254 }
255 }
256 completion:^(BOOL finished) {
257 for (CSQuickActionsButton *button in [self rightButtons])
258 [button setHidden:!self.rightOpen];
259 }];
260 } else if (button.type == 1 && self.collapseLeft) {
261 self.leftOpen = !self.leftOpen;
262 [UIView animateWithDuration:0.25
263 delay:0
264 options:UIViewAnimationOptionCurveEaseOut
265 animations:^(void) {
266 for (CSQuickActionsButton *button in [self leftButtons]) {
267 button.frame = [self leftFrameForButton:button];
268 if (self.leftOpen)
269 [button setHidden:0];
270 }
271 }
272 completion:^(BOOL finished) {
273 for (CSQuickActionsButton *button in [self leftButtons])
274 [button setHidden:!self.leftOpen];
275 }];
276 } else if ([button.bundleID isEqualToString:@"com.apple.flashlight"]) {
277 [self.delegate _toggleFlashlight];
278 } else if ([button.bundleID isEqualToString:@"com.apple.camera"]) {
279 [self.delegate _launchCamera];
280 } else if ([button.bundleID isEqualToString:@"com.apple.donotdisturb"]) {
281 [self setDoNotDisturb:!self.isDNDActive];
282 } else if (button.bundleID)
283 openApplication(button.bundleID);
284 else {
285 %orig;
286 return;
287 }
288
289 /* This will make the CC module be correct */
290 [self.delegate _resetIdleTimer];
291 [self.delegate sendAction:[CSAction actionWithType:5]];
292
293 return;
294 }
295
296 -(void)handleButtonTouchBegan:(CSQuickActionsButton *)button
297 {
298 if (button.bundleID != nil ||
299 (button.type == 0 && self.collapseRight) ||
300 (button.type == 1 && self.collapseLeft))
301 return;
302 %orig;
303 }
304
305 -(void)handleButtonTouchEnded:(CSQuickActionsButton *)button
306 {
307 if (button.bundleID != nil ||
308 (button.type == 0 && self.collapseRight) ||
309 (button.type == 1 && self.collapseLeft))
310 return;
311 %orig;
312 }
313
314 -(void)setFlashlightOn:(BOOL)arg
315 {
316 if ([self.flashlightButton.bundleID isEqualToString:@"com.apple.flashlight"])
317 [self.flashlightButton setSelected:arg];
318 if ([self.cameraButton.bundleID isEqualToString:@"com.apple.flashlight"])
319 [self.cameraButton setSelected:arg];
320 for (CSQuickActionsButton *button in [self leftButtons])
321 if ([button.bundleID isEqualToString:@"com.apple.flashlight"])
322 [button setSelected:arg];
323 for (CSQuickActionsButton *button in [self rightButtons])
324 if ([button.bundleID isEqualToString:@"com.apple.flashlight"])
325 [button setSelected:arg];
326 }
327
328 %new
329 -(void)setDoNotDisturb:(BOOL)state
330 {
331 DNDModeAssertionService *assertionService = (DNDModeAssertionService *)[objc_getClass("DNDModeAssertionService") serviceForClientIdentifier:@"com.apple.donotdisturb.control-center.module"];
332
333 if (state) {
334 DNDModeAssertionDetails *newAssertion = [objc_getClass("DNDModeAssertionDetails") userRequestedAssertionDetailsWithIdentifier:@"com.apple.control-center.manual-toggle" modeIdentifier:@"com.apple.donotdisturb.mode.default" lifetime:nil];
335 [assertionService takeModeAssertionWithDetails:newAssertion error:NULL];
336 } else {
337 [assertionService invalidateAllActiveModeAssertionsWithError:NULL];
338 }
339
340 [[NSNotificationCenter defaultCenter] postNotificationName:@"SBQuietModeStatusChangedNotification" object:nil];
341 }
342
343 %new
344 -(BOOL)isDNDActive
345 {
346
347 // DNDStateService *stateService = (DNDStateService *)[objc_getClass("DNDStateService") serviceForClientIdentifier:@"com.apple.donotdisturb.control-center.module"];
348 return [[self.stateService queryCurrentStateWithError:nil] isActive];
349 }
350
351 %new
352 -(void)updateDND:(NSNotification *)notif
353 {
354 BOOL active = [self isDNDActive];
355
356 if ([self.flashlightButton.bundleID isEqualToString:@"com.apple.donotdisturb"])
357 [self.flashlightButton setSelected:active];
358 if ([self.cameraButton.bundleID isEqualToString:@"com.apple.donotdisturb"])
359 [self.cameraButton setSelected:active];
360 for (CSQuickActionsButton *button in [self leftButtons])
361 if ([button.bundleID isEqualToString:@"com.apple.donotdisturb"])
362 [button setSelected:active];
363 for (CSQuickActionsButton *button in [self rightButtons])
364 if ([button.bundleID isEqualToString:@"com.apple.donotdisturb"])
365 [button setSelected:active];
366 }
367
368 %new
369 -(void)stateService:(id)arg1 didReceiveDoNotDisturbStateUpdate:(id)arg2
370 {
371 dispatch_async(dispatch_get_main_queue(), ^{
372 [self updateDND:nil];
373 });
374 }
375
376 %end
377
378 %hook CSQuickActionsButton
379
380 %property (nonatomic, retain) NSString *bundleID;
381
382 -(void)setImage:(UIImage *)img
383 {
384 %orig;
385 [[self valueForKey:@"_contentView"] setImage:img];
386 }
387
388 -(void)setBundleID:(NSString*)bundleID
389 {
390 %orig;
391 if ([bundleID isEqualToString:@"com.apple.camera"]) {
392 [self setImage:[self _imageWithName:@"OrbCamera"]];
393 [self setLatching:FALSE];
394 } else if ([bundleID isEqualToString:@"com.apple.flashlight"]) {
395 [self setImage:[self _imageWithName:@"OrbFlashlightOff"]];
396 [self setSelectedImage:[self _imageWithName:@"OrbFlashlightOff"]];
397 [self setLatching:TRUE];
398 } else if (([bundleID isEqualToString:@"com.apple.donotdisturb"])) {
399 UIImageSymbolConfiguration *imageConfig = [UIImageSymbolConfiguration configurationWithTextStyle:UIFontTextStyleTitle2];
400 [self setImage:[UIImage systemImageNamed:@"moon.fill" withConfiguration:imageConfig]];
401 [self setSelectedImage:[UIImage systemImageNamed:@"moon.fill" withConfiguration:imageConfig]];
402 ((UIImageView *)[self valueForKey:@"_contentView"]).contentMode = UIViewContentModeScaleAspectFit;
403 [self setLatching:TRUE];
404 } else {
405 [self setImage:[UIImage _applicationIconImageForBundleIdentifier:bundleID format:0 scale:[UIScreen mainScreen].scale]];
406 [self setLatching:FALSE];
407 }
408 }
409
410 -(void)setPermitted:(BOOL)permitted
411 {
412 %orig(YES);
413 }
414
415 %end
416
417 %hook CSQuickActionsViewController
418
419 -(BOOL)hasCamera
420 {
421 return true;
422 }
423
424 -(BOOL)hasFlashlight
425 {
426 return true;
427 }
428
429 +(BOOL)deviceSupportsButtons
430 {
431 return true;
432 }
433
434 %end
435
436 // vim: filetype=logos