1
0
mirror of https://github.com/danog/Telegram.git synced 2024-12-04 10:27:46 +01:00
Telegram/legacy/TelegraphKit/TGHacks.m
2015-10-01 19:19:52 +03:00

591 lines
20 KiB
Objective-C

#import "TGHacks.h"
#import "FreedomUIKit.h"
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#import <objc/runtime.h>
#import <objc/message.h>
#import "TGViewController.h"
#import "TGNavigationBar.h"
#import "TGRTL.h"
#import "TGAnimationBlockDelegate.h"
static float animationDurationFactor = 1.0f;
static float secondaryAnimationDurationFactor = 1.0f;
static bool forceMovieAnimatedScaleMode = false;
static bool forcePerformWithAnimationFlag = false;
void SwizzleClassMethod(Class c, SEL orig, SEL new)
{
Method origMethod = class_getClassMethod(c, orig);
Method newMethod = class_getClassMethod(c, new);
c = object_getClass((id)c);
if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
else
method_exchangeImplementations(origMethod, newMethod);
}
void SwizzleInstanceMethod(Class c, SEL orig, SEL new)
{
Method origMethod = nil, newMethod = nil;
origMethod = class_getInstanceMethod(c, orig);
newMethod = class_getInstanceMethod(c, new);
if ((origMethod != nil) && (newMethod != nil))
{
if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
else
method_exchangeImplementations(origMethod, newMethod);
}
else
NSLog(@"Attempt to swizzle nonexistent methods!");
}
void SwizzleInstanceMethodWithAnotherClass(Class c1, SEL orig, Class c2, SEL new)
{
Method origMethod = nil, newMethod = nil;
origMethod = class_getInstanceMethod(c1, orig);
newMethod = class_getInstanceMethod(c2, new);
if ((origMethod != nil) && (newMethod != nil))
{
if(class_addMethod(c1, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
class_replaceMethod(c1, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
else
method_exchangeImplementations(origMethod, newMethod);
}
else
NSLog(@"Attempt to swizzle nonexistent methods!");
}
void InjectClassMethodFromAnotherClass(Class toClass, Class fromClass, SEL fromSelector, SEL toSeletor)
{
Method method = class_getClassMethod(fromClass, fromSelector);
if (method != nil)
{
if (!class_addMethod(toClass, toSeletor, method_getImplementation(method), method_getTypeEncoding(method)))
NSLog(@"Attempt to add method failed");
}
else
NSLog(@"Attempt to add nonexistent method");
}
void InjectInstanceMethodFromAnotherClass(Class toClass, Class fromClass, SEL fromSelector, SEL toSeletor)
{
Method method = class_getInstanceMethod(fromClass, fromSelector);
if (method != nil)
{
if (!class_addMethod(toClass, toSeletor, method_getImplementation(method), method_getTypeEncoding(method)))
NSLog(@"Attempt to add method failed");
}
else
NSLog(@"Attempt to add nonexistent method");
}
@interface UIView (TGHacks)
+ (void)telegraph_setAnimationDuration:(NSTimeInterval)duration;
+ (void)TG_performWithoutAnimation:(void (^)(void))actionsWithoutAnimation;
- (UIView *)TG_snapshotViewAfterScreenUpdates:(BOOL)afterUpdates;
@end
@implementation UIView (TGHacks)
+ (void)telegraph_setAnimationDuration:(NSTimeInterval)duration
{
[self telegraph_setAnimationDuration:(duration * animationDurationFactor)];
}
+ (void)telegraph_animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion
{
[self telegraph_animateWithDuration:duration * secondaryAnimationDurationFactor delay:delay options:options animations:animations completion:completion];
}
+ (void)TG_performWithoutAnimation:(void (^)(void))actionsWithoutAnimation
{
float lastDurationFactor = animationDurationFactor;
animationDurationFactor = 0.0f;
bool animationsWereEnabled = [UIView areAnimationsEnabled];
[UIView setAnimationsEnabled:false];
if (actionsWithoutAnimation)
actionsWithoutAnimation();
[UIView setAnimationsEnabled:animationsWereEnabled];
animationDurationFactor = lastDurationFactor;
}
+ (void)TG_performWithoutAnimation_maybeNot:(void (^)(void))actionsWithoutAnimation
{
if (actionsWithoutAnimation)
{
if (forcePerformWithAnimationFlag)
actionsWithoutAnimation();
else
[self TG_performWithoutAnimation_maybeNot:actionsWithoutAnimation];
}
}
- (UIView *)TG_snapshotViewAfterScreenUpdates:(BOOL)__unused afterUpdates
{
UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.opaque, 0.0f);
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
if (image != nil)
return [[UIImageView alloc] initWithImage:image];
return nil;
}
@end
#pragma mark -
@implementation TGHacks
+ (void)hackSetAnimationDuration
{
SwizzleClassMethod([UIView class], @selector(setAnimationDuration:), @selector(telegraph_setAnimationDuration:));
SwizzleClassMethod([UIView class], @selector(animateWithDuration:delay:options:animations:completion:), @selector(telegraph_animateWithDuration:delay:options:animations:completion:));
if (iosMajorVersion() >= 7)
{
if (iosMajorVersion() >= 8)
{
SwizzleClassMethod([UIView class], @selector(performWithoutAnimation:), @selector(TG_performWithoutAnimation_maybeNot:));
}
}
else
{
InjectClassMethodFromAnotherClass(object_getClass([UIView class]), object_getClass([UIView class]), @selector(TG_performWithoutAnimation:), @selector(performWithoutAnimation:));
InjectInstanceMethodFromAnotherClass([UIView class], [UIView class], @selector(TG_snapshotViewAfterScreenUpdates:), @selector(snapshotViewAfterScreenUpdates:));
}
[TGRTL doMagic];
}
+ (void)setAnimationDurationFactor:(float)factor
{
animationDurationFactor = factor;
}
+ (void)setSecondaryAnimationDurationFactor:(float)factor
{
secondaryAnimationDurationFactor = factor;
}
+ (void)setApplicationStatusBarAlpha:(float)alpha
{
static SEL selector = NULL;
if (selector == NULL)
{
NSString *str1 = @"rs`str";
NSString *str2 = @"A`qVhmcnv";
selector = NSSelectorFromString([[NSString alloc] initWithFormat:@"%@%@", TGEncodeText(str1, 1), TGEncodeText(str2, 1)]);
}
if ([[UIApplication sharedApplication] respondsToSelector:selector])
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
UIWindow *window = [[UIApplication sharedApplication] performSelector:selector];
#pragma clang diagnostic pop
window.alpha = alpha;
}
}
static UIView *findStatusBarView()
{
static Class viewClass = nil;
static SEL selector = NULL;
if (selector == NULL)
{
NSString *str1 = @"rs`str";
NSString *str2 = @"A`qVhmcnv";
selector = NSSelectorFromString([[NSString alloc] initWithFormat:@"%@%@", TGEncodeText(str1, 1), TGEncodeText(str2, 1)]);
viewClass = NSClassFromString(TGEncodeText(@"VJTubuvtCbs", -1));
}
if ([[UIApplication sharedApplication] respondsToSelector:selector])
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
UIWindow *window = [[UIApplication sharedApplication] performSelector:selector];
#pragma clang diagnostic pop
for (UIView *subview in window.subviews)
{
if ([subview isKindOfClass:viewClass])
{
return subview;
}
}
}
return nil;
}
+ (void)animateApplicationStatusBarAppearance:(int)statusBarAnimation duration:(NSTimeInterval)duration completion:(void (^)())completion
{
[self animateApplicationStatusBarAppearance:statusBarAnimation delay:0.0 duration:duration completion:completion];
}
+ (void)animateApplicationStatusBarAppearance:(int)statusBarAnimation delay:(NSTimeInterval)delay duration:(NSTimeInterval)duration completion:(void (^)())completion
{
UIView *view = findStatusBarView();
if (view != nil)
{
if ((statusBarAnimation & TGStatusBarAppearanceAnimationSlideDown) || (statusBarAnimation & TGStatusBarAppearanceAnimationSlideUp))
{
CGPoint startPosition = view.layer.position;
CGPoint position = view.layer.position;
CGPoint normalPosition = CGPointMake(CGFloor(view.frame.size.width / 2), CGFloor(view.frame.size.height / 2));
CGFloat viewHeight = view.frame.size.height;
if (statusBarAnimation & TGStatusBarAppearanceAnimationSlideDown)
{
startPosition = CGPointMake(CGFloor(view.frame.size.width / 2), CGFloor(view.frame.size.height / 2) - viewHeight);
position = CGPointMake(CGFloor(view.frame.size.width / 2), CGFloor(view.frame.size.height / 2));
}
else if (statusBarAnimation & TGStatusBarAppearanceAnimationSlideUp)
{
startPosition = CGPointMake(CGFloor(view.frame.size.width / 2), CGFloor(view.frame.size.height / 2));
position = CGPointMake(CGFloor(view.frame.size.width / 2), CGFloor(view.frame.size.height / 2) - viewHeight);
}
CABasicAnimation *animation = [[CABasicAnimation alloc] init];
animation.duration = duration;
animation.fromValue = [NSValue valueWithCGPoint:startPosition];
animation.toValue = [NSValue valueWithCGPoint:position];
animation.removedOnCompletion = true;
animation.fillMode = kCAFillModeForwards;
animation.beginTime = delay;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
TGAnimationBlockDelegate *delegate = [[TGAnimationBlockDelegate alloc] initWithLayer:view.layer];
delegate.completion = ^(BOOL finished)
{
if (finished)
view.layer.position = normalPosition;
if (completion)
completion();
};
animation.delegate = delegate;
[view.layer addAnimation:animation forKey:@"position"];
view.layer.position = position;
}
else if ((statusBarAnimation & TGStatusBarAppearanceAnimationFadeIn) || (statusBarAnimation & TGStatusBarAppearanceAnimationFadeOut))
{
float startOpacity = view.layer.opacity;
float opacity = view.layer.opacity;
if (statusBarAnimation & TGStatusBarAppearanceAnimationFadeIn)
{
startOpacity = 0.0f;
opacity = 1.0f;
}
else if (statusBarAnimation & TGStatusBarAppearanceAnimationFadeOut)
{
startOpacity = 1.0f;
opacity = 0.0f;
}
CABasicAnimation *animation = [[CABasicAnimation alloc] init];
animation.duration = duration;
animation.fromValue = @(startOpacity);
animation.toValue = @(opacity);
animation.removedOnCompletion = true;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
TGAnimationBlockDelegate *delegate = [[TGAnimationBlockDelegate alloc] initWithLayer:view.layer];
delegate.completion = ^(__unused BOOL finished)
{
if (completion)
completion();
};
animation.delegate = delegate;
[view.layer addAnimation:animation forKey:@"opacity"];
}
}
else
{
if (completion)
completion();
}
}
+ (void)animateApplicationStatusBarStyleTransitionWithDuration:(NSTimeInterval)duration
{
UIView *view = findStatusBarView();
if (view != nil)
{
UIView *snapshotView = [view snapshotViewAfterScreenUpdates:false];
[view addSubview:snapshotView];
[UIView animateWithDuration:duration animations:^
{
snapshotView.alpha = 0.0f;
} completion:^(__unused BOOL finished)
{
[snapshotView removeFromSuperview];
}];
}
}
+ (CGFloat)statusBarHeightForOrientation:(UIInterfaceOrientation)orientation
{
static SEL selector = NULL;
if (selector == NULL)
{
NSString *str1 = @"rs`str";
NSString *str2 = @"A`qVhmcnv";
selector = NSSelectorFromString([[NSString alloc] initWithFormat:@"%@%@", TGEncodeText(str1, 1), TGEncodeText(str2, 1)]);
}
if ([[UIApplication sharedApplication] respondsToSelector:selector])
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
UIWindow *window = [[UIApplication sharedApplication] performSelector:selector];
#pragma clang diagnostic pop
Class statusBarClass = NSClassFromString(TGEncodeText(@"VJTubuvtCbs", -1));
for (UIView *view in window.subviews)
{
if ([view isKindOfClass:statusBarClass])
{
SEL selector = NSSelectorFromString(TGEncodeText(@"dvssfouTuzmf", -1));
NSMethodSignature *signature = [statusBarClass instanceMethodSignatureForSelector:selector];
if (signature == nil)
{
TGLog(@"***** Method not found");
return 20.0f;
}
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:signature];
[inv setSelector:selector];
[inv setTarget:view];
[inv invoke];
NSInteger result = 0;
[inv getReturnValue:&result];
SEL selector2 = NSSelectorFromString(TGEncodeText(@"ifjhiuGpsTuzmf;psjfoubujpo;", -1));
NSMethodSignature *signature2 = [statusBarClass methodSignatureForSelector:selector2];
if (signature2 == nil)
{
TGLog(@"***** Method not found");
return 20.0f;
}
NSInvocation *inv2 = [NSInvocation invocationWithMethodSignature:signature2];
[inv2 setSelector:selector2];
[inv2 setTarget:[view class]];
[inv2 setArgument:&result atIndex:2];
NSInteger argOrientation = orientation;
[inv2 setArgument:&argOrientation atIndex:3];
[inv2 invoke];
CGFloat result2 = 0;
[inv2 getReturnValue:&result2];
return result2;
}
}
}
return 20.0f;
}
+ (bool)isKeyboardVisible
{
return [self isKeyboardVisibleAlt];
}
static bool keyboardHidden = true;
+ (bool)isKeyboardVisibleAlt
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
[[NSNotificationCenter defaultCenter] addObserverForName:UIKeyboardWillHideNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(__unused NSNotification *notification)
{
if (!freedomUIKitTest3())
keyboardHidden = true;
}];
[[NSNotificationCenter defaultCenter] addObserverForName:UIKeyboardWillShowNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(__unused NSNotification *notification)
{
keyboardHidden = false;
}];
});
return !keyboardHidden;
}
+ (CGFloat)keyboardHeightForOrientation:(UIInterfaceOrientation)orientation
{
static NSInvocation *invocation = nil;
static Class keyboardClass = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
keyboardClass = NSClassFromString(TGEncodeText(@"VJLfzcpbse", -1));
SEL selector = NSSelectorFromString(TGEncodeText(@"tj{fGpsJoufsgbdfPsjfoubujpo;", -1));
NSMethodSignature *signature = [keyboardClass methodSignatureForSelector:selector];
if (signature == nil)
TGLog(@"***** Method not found");
else
{
invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setSelector:selector];
}
});
if (invocation != nil)
{
[invocation setTarget:[keyboardClass class]];
[invocation setArgument:&orientation atIndex:2];
[invocation invoke];
CGSize result = CGSizeZero;
[invocation getReturnValue:&result];
return MIN(result.width, result.height);
}
return 0.0f;
}
+ (void)applyCurrentKeyboardAutocorrectionVariant
{
static Class keyboardClass = NULL;
static SEL currentInstanceSelector = NULL;
static SEL applyVariantSelector = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
keyboardClass = NSClassFromString(TGEncodeText(@"VJLfzcpbse", -1));
currentInstanceSelector = NSSelectorFromString(TGEncodeText(@"bdujwfLfzcpbse", -1));
applyVariantSelector = NSSelectorFromString(TGEncodeText(@"bddfquBvupdpssfdujpo", -1));
});
if ([keyboardClass respondsToSelector:currentInstanceSelector])
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
id currentInstance = [keyboardClass performSelector:currentInstanceSelector];
if ([currentInstance respondsToSelector:applyVariantSelector])
[currentInstance performSelector:applyVariantSelector];
#pragma clang diagnostic pop
}
}
+ (UIWindow *)applicationKeyboardWindow
{
static Class keyboardWindowClass = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
keyboardWindowClass = NSClassFromString(TGEncodeText(@"VJUfyuFggfdutXjoepx", -1));
});
for (UIWindow *window in [[UIApplication sharedApplication] windows])
{
if ([[window class] isEqual:keyboardWindowClass])
return window;
}
return nil;
}
+ (UIView *)applicationKeyboardView
{
static Class keyboardViewClass = Nil;
static Class keyboardViewContainerClass = Nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
keyboardViewClass = NSClassFromString(TGEncodeText(@"VJJoqvuTfuIptuWjfx", -1));
keyboardViewContainerClass = NSClassFromString(TGEncodeText(@"VJJoqvuTfuDpoubjofsWjfx", -1));
});
for (UIView *view in [self applicationKeyboardWindow].subviews)
{
if ([view isKindOfClass:keyboardViewContainerClass])
{
for (UIView *subview in view.subviews)
{
if ([subview isKindOfClass:keyboardViewClass])
return subview;
}
}
}
return nil;
}
+ (void)setForceMovieAnimatedScaleMode:(bool)force
{
forceMovieAnimatedScaleMode = force;
}
+ (void)forcePerformWithAnimation:(dispatch_block_t)block
{
if (block)
{
bool flag = forcePerformWithAnimationFlag;
forcePerformWithAnimationFlag = true;
block();
forcePerformWithAnimationFlag = flag;
}
}
@end
#if TARGET_IPHONE_SIMULATOR
extern float UIAnimationDragCoefficient(void);
#endif
CGFloat TGAnimationSpeedFactor()
{
#if TARGET_IPHONE_SIMULATOR
return UIAnimationDragCoefficient();
#endif
return 1.0f;
}