Full Code of raulsaeed/TikTokPlusPlus for AI

main 6add794fc818 cached
48 files
231.5 KB
56.0k tokens
3 symbols
1 requests
Download .txt
Showing preview only (246K chars total). Download the full file or copy to clipboard to get everything.
Repository: raulsaeed/TikTokPlusPlus
Branch: main
Commit: 6add794fc818
Files: 48
Total size: 231.5 KB

Directory structure:
gitextract_wc0kqf1u/

├── .gitignore
├── BHDownload.h
├── BHDownload.m
├── BHIManager.h
├── BHIManager.m
├── BHMultipleDownload.h
├── BHMultipleDownload.m
├── BHTikTok.plist
├── JGProgressHUD/
│   ├── JGProgressHUD-Defines.h
│   ├── JGProgressHUD.h
│   ├── JGProgressHUD.m
│   ├── JGProgressHUDAnimation.h
│   ├── JGProgressHUDAnimation.m
│   ├── JGProgressHUDErrorIndicatorView.h
│   ├── JGProgressHUDErrorIndicatorView.m
│   ├── JGProgressHUDFadeAnimation.h
│   ├── JGProgressHUDFadeAnimation.m
│   ├── JGProgressHUDFadeZoomAnimation.h
│   ├── JGProgressHUDFadeZoomAnimation.m
│   ├── JGProgressHUDImageIndicatorView.h
│   ├── JGProgressHUDImageIndicatorView.m
│   ├── JGProgressHUDIndeterminateIndicatorView.h
│   ├── JGProgressHUDIndeterminateIndicatorView.m
│   ├── JGProgressHUDIndicatorView.h
│   ├── JGProgressHUDIndicatorView.m
│   ├── JGProgressHUDPieIndicatorView.h
│   ├── JGProgressHUDPieIndicatorView.m
│   ├── JGProgressHUDRingIndicatorView.h
│   ├── JGProgressHUDRingIndicatorView.m
│   ├── JGProgressHUDShadow.h
│   ├── JGProgressHUDShadow.m
│   ├── JGProgressHUDSuccessIndicatorView.h
│   └── JGProgressHUDSuccessIndicatorView.m
├── Makefile
├── README.md
├── SecurityViewController.h
├── SecurityViewController.m
├── Settings/
│   ├── CountryTable.h
│   ├── CountryTable.m
│   ├── LiveActions.h
│   ├── LiveActions.m
│   ├── PlaybackSpeed.h
│   ├── PlaybackSpeed.m
│   ├── ViewController.h
│   └── ViewController.m
├── TikTokHeaders.h
├── Tweak.x
└── control

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
.theos/
.vscode/
packages/
.DS_Store


================================================
FILE: BHDownload.h
================================================
//
//  BHDownload.h
//  DIYTableView
//
//  Created by BandarHelal on 12/01/1442 AH.
//  Copyright © 1442 BandarHelal. All rights reserved.
//

#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@protocol BHDownloadDelegate <NSObject>
@optional
- (void)downloadProgress:(float)progress;
- (void)downloadDidFinish:(NSURL *)filePath Filename:(NSString *)fileName;
- (void)downloadDidFailureWithError:(NSError *)error;
@end

@interface BHDownload : NSObject
{
   id delegate;
}
- (void)setDelegate:(id)newDelegate;
- (instancetype)init;
- (void)downloadFileWithURL:(NSURL *)url;
@property (nonatomic, strong) NSString *fileName;
@end

@interface BHDownload () <NSURLSessionDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate, NSURLSessionStreamDelegate>
@property (nonatomic, strong) NSURLSession *Session;
@end

NS_ASSUME_NONNULL_END


================================================
FILE: BHDownload.m
================================================
//
//  BHDownload.m
//  DIYTableView
//
//  Created by BandarHelal on 12/01/1442 AH.
//  Copyright © 1442 BandarHelal. All rights reserved.
//

#import "BHDownload.h"

@implementation BHDownload
- (instancetype)init {
    self = [super init];
    if (self) {
        self.Session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    }
    return self;
}

- (void)downloadFileWithURL:(NSURL *)url {
    if (url) {
        self.fileName = url.absoluteString.lastPathComponent;
        NSURLSessionDownloadTask *downloadTask = [self.Session downloadTaskWithURL:url];
        [downloadTask resume];
    }
}
- (void)setDelegate:(id)newDelegate {
    if (newDelegate) {
        delegate = newDelegate;
    }
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
    float prog = (float)totalBytesWritten / (float)totalBytesExpectedToWrite;
    [delegate downloadProgress:prog];
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(nonnull NSURL *)location {
    [delegate downloadDidFinish:location Filename:self.fileName];
}
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error {
    [delegate downloadDidFailureWithError:error];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    [delegate downloadDidFailureWithError:error];
}
@end


================================================
FILE: BHIManager.h
================================================
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface BHIManager: NSObject
+ (BOOL)hideAds;
+ (BOOL)downloadButton;
+ (BOOL)shareSheet;
+ (BOOL)removeWatermark;
+ (BOOL)hideElementButton;
+ (BOOL)uploadRegion;
+ (BOOL)autoPlay;
+ (BOOL)stopPlay;
+ (BOOL)progressBar;
+ (BOOL)transparentCommnet;
+ (BOOL)showUsername;
+ (BOOL)disablePullToRefresh;
+ (BOOL)disableUnsensitive;
+ (BOOL)disableWarnings;
+ (BOOL)disableLive;
+ (BOOL)skipRecommendations;
+ (BOOL)likeConfirmation;
+ (BOOL)likeCommentConfirmation;
+ (BOOL)dislikeCommentConfirmation;
+ (BOOL)followConfirmation;
+ (BOOL)profileSave;
+ (BOOL)profileCopy;
+ (BOOL)profileVideoCount;
+ (BOOL)videoLikeCount;
+ (BOOL)videoUploadDate;
+ (BOOL)alwaysOpenSafari;
+ (BOOL)regionChangingEnabled;
+ (NSNumber *)selectedRegion;
+ (NSNumber *)selectedLiveAction;
+ (BOOL)liveActionEnabled;
+ (BOOL)speedEnabled;
+ (NSDictionary *)selectedSpeed;
+ (BOOL)fakeChangesEnabled;
+ (BOOL)fakeVerified;
+ (BOOL)uploadHD;
+ (BOOL)extendedBio;
+ (BOOL)extendedComment;
+ (BOOL)appLock;
+ (void)showSaveVC:(id)item;
+ (void)cleanCache;
+ (BOOL)isEmpty:(NSURL *)url;
+ (NSString *)getDownloadingPersent:(float)per;
+ (void)saveMedia:(id)item fileExtension:(id)fileExtension;
@end

================================================
FILE: BHIManager.m
================================================
#import "BHIManager.h"
#import "TikTokHeaders.h"

@implementation BHIManager
+ (BOOL)hideAds {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"hide_ads"];
}
+ (BOOL)downloadButton {
     return [[NSUserDefaults standardUserDefaults] boolForKey:@"download_button"];
}
+ (BOOL)shareSheet {
     return [[NSUserDefaults standardUserDefaults] boolForKey:@"share_sheet"];
}
+ (BOOL)removeWatermark {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"remove_watermark"];
}
+ (BOOL)hideElementButton {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"remove_elements_button"];
}
+ (BOOL)uploadRegion {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"upload_region"];
}
+ (BOOL)autoPlay {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"auto_play"];
}
+ (BOOL)stopPlay {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"stop_play"];
}
+ (BOOL)progressBar {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"show_porgress_bar"];
}
+ (BOOL)transparentCommnet {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"transparent_commnet"];
}
+ (BOOL)showUsername {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"show_username"];
}
+ (BOOL)disablePullToRefresh {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"pull_to_refresh"];
}
+ (BOOL)disableUnsensitive {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"disable_unsensitive"];
}
+ (BOOL)disableWarnings {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"disable_warnings"];
}
+ (BOOL)disableLive {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"disable_live"];
}
+ (BOOL)skipRecommendations {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"skip_recommnedations"];
}
+ (BOOL)likeConfirmation {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"like_confirm"];
}
+ (BOOL)likeCommentConfirmation {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"like_comment_confirm"];
}
+ (BOOL)dislikeCommentConfirmation {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"dislike_comment_confirm"];
}
+ (BOOL)followConfirmation {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"follow_confirm"];
}
+ (BOOL)profileSave {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"save_profile"];
}
+ (BOOL)profileCopy {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"copy_profile_information"];
}
+ (BOOL)profileVideoCount {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"uploaded_videos"];
}
+ (BOOL)alwaysOpenSafari {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"openInBrowser"];
}
+ (BOOL)regionChangingEnabled {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"en_region"];
}
+ (BOOL)speedEnabled {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"playback_en"];
}
+ (BOOL)liveActionEnabled {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"en_livefunc"];
}
+ (NSNumber *)selectedLiveAction {
    return [[NSUserDefaults standardUserDefaults] objectForKey:@"live_action"];
}
+ (NSNumber *)selectedSpeed {
    return [[NSUserDefaults standardUserDefaults] objectForKey:@"playback_speed"];
}
+ (BOOL)videoLikeCount {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"video_like_count"];
}
+ (BOOL)videoUploadDate {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"video_upload_date"];
}
+ (NSDictionary *)selectedRegion {
    return [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"region"];
}
+ (BOOL)fakeChangesEnabled {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"en_fake"];
}
+ (BOOL)fakeVerified {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"fake_verify"];
}
+ (BOOL)extendedBio {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"extended_bio"];
}
+ (BOOL)extendedComment {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"extendedComment"];
}
+ (BOOL)uploadHD {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"upload_hd"];
}
+ (BOOL)appLock {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"padlock"];
}
+ (void)cleanCache {
    NSArray <NSURL *> *DocumentFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:[NSURL fileURLWithPath:NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true).firstObject] includingPropertiesForKeys:@[] options:NSDirectoryEnumerationSkipsHiddenFiles error:nil];
    
    for (NSURL *file in DocumentFiles) {
        if ([file.pathExtension.lowercaseString isEqualToString:@"mp4"]) {
            [[NSFileManager defaultManager] removeItemAtURL:file error:nil];
        }
        if ([file.pathExtension.lowercaseString isEqualToString:@"png"]) {
            [[NSFileManager defaultManager] removeItemAtURL:file error:nil];
        }
        if ([file.pathExtension.lowercaseString isEqualToString:@"jpeg"]) {
            [[NSFileManager defaultManager] removeItemAtURL:file error:nil];
        }
        if ([file.pathExtension.lowercaseString isEqualToString:@"mp3"]) {
            [[NSFileManager defaultManager] removeItemAtURL:file error:nil];
        }
        if ([file.pathExtension.lowercaseString isEqualToString:@"m4a"]) {
            [[NSFileManager defaultManager] removeItemAtURL:file error:nil];
        }
    }
    
    NSArray <NSURL *> *TempFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:[NSURL fileURLWithPath:NSTemporaryDirectory()] includingPropertiesForKeys:@[] options:NSDirectoryEnumerationSkipsHiddenFiles error:nil];
    
    for (NSURL *file in TempFiles) {
        if ([file.pathExtension.lowercaseString isEqualToString:@"mp4"]) {
            [[NSFileManager defaultManager] removeItemAtURL:file error:nil];
        }
        if ([file.pathExtension.lowercaseString isEqualToString:@"mov"]) {
            [[NSFileManager defaultManager] removeItemAtURL:file error:nil];
        }
        if ([file.pathExtension.lowercaseString isEqualToString:@"tmp"]) {
            [[NSFileManager defaultManager] removeItemAtURL:file error:nil];
        }
        if ([file.pathExtension.lowercaseString isEqualToString:@"png"]) {
            [[NSFileManager defaultManager] removeItemAtURL:file error:nil];
        }
        if ([file.pathExtension.lowercaseString isEqualToString:@"jpeg"]) {
            [[NSFileManager defaultManager] removeItemAtURL:file error:nil];
        }
        if ([file.pathExtension.lowercaseString isEqualToString:@"mp3"]) {
            [[NSFileManager defaultManager] removeItemAtURL:file error:nil];
        }
        if ([file.pathExtension.lowercaseString isEqualToString:@"m4a"]) {
            [[NSFileManager defaultManager] removeItemAtURL:file error:nil];
        }
        if ([file hasDirectoryPath]) {
            if ([BHIManager isEmpty:file]) {
                [[NSFileManager defaultManager] removeItemAtURL:file error:nil];
            }
        }
    }
}
+ (BOOL)isEmpty:(NSURL *)url {
    NSArray *FolderFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:url includingPropertiesForKeys:@[] options:NSDirectoryEnumerationSkipsHiddenFiles error:nil];
    if (FolderFiles.count == 0) {
        return true;
    } else {
        return false;
    }
}
+ (void)showSaveVC:(id)item {
    UIActivityViewController *acVC = [[UIActivityViewController alloc] initWithActivityItems:item applicationActivities:nil];
    if (is_iPad()) {
        acVC.popoverPresentationController.sourceView = topMostController().view;
        acVC.popoverPresentationController.sourceRect = CGRectMake(topMostController().view.bounds.size.width / 2.0, topMostController().view.bounds.size.height / 2.0, 1.0, 1.0);
    }
    [topMostController() presentViewController:acVC animated:true completion:nil];
}
+ (void)saveMedia:(id)newFilePath fileExtension:(id)fileextension {
    NSArray *imageExtensions = @[@"png", @"jpg", @"jpeg", @"gif", @"tiff", @"bmp", @"heif", @"heic", @"svg"];
    NSArray *videoExtensions = @[@"mp4", @"mov", @"avi", @"mkv", @"wmv", @"flv", @"webm"];
    [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
        PHAssetResourceCreationOptions *options = [[PHAssetResourceCreationOptions alloc] init];
        if ([videoExtensions containsObject:fileextension]) {
            [[PHAssetCreationRequest creationRequestForAsset] addResourceWithType:PHAssetResourceTypeVideo fileURL:newFilePath options:options];
        } else if ([imageExtensions containsObject:fileextension]) {
            [[PHAssetCreationRequest creationRequestForAsset] addResourceWithType:PHAssetResourceTypePhoto fileURL:newFilePath options:options];
        } else {
            NSLog(@"Unsupported file type: %@", fileextension);
        }
    } completionHandler:^(BOOL success, NSError * _Nullable error) {
        if (success) {
            NSLog(@"Media saved to Camera Roll successfully.");
        } else {
            NSLog(@"Error saving media to Camera Roll: %@", error);
        }
    }];
}

+ (NSString *)getDownloadingPersent:(float)per {
    NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
    [numberFormatter setNumberStyle:NSNumberFormatterPercentStyle];
    NSNumber *number = [NSNumber numberWithFloat:per];
    return [numberFormatter stringFromNumber:number];
}
@end

================================================
FILE: BHMultipleDownload.h
================================================
#import <Foundation/Foundation.h>

@protocol BHMultipleDownloadDelegate <NSObject>
- (void)downloaderProgress:(float)progress;
- (void)downloaderDidFinishDownloadingAllFiles:(NSMutableArray<NSURL *> *)downloadedFilePaths;
- (void)downloaderDidFailureWithError:(NSError *)error;
@end

@interface BHMultipleDownload : NSObject <NSURLSessionDownloadDelegate>
@property (nonatomic, strong) NSURLSession *session;
@property (nonatomic, weak) id<BHMultipleDownloadDelegate> delegate;
- (void)downloadFiles:(NSArray<NSURL *> *)fileURLs;

@end

================================================
FILE: BHMultipleDownload.m
================================================
#import "BHMultipleDownload.h"

@implementation BHMultipleDownload {
    NSMutableArray<NSURL *> *_downloadedFilePaths;
    NSInteger _completedDownloads;
    NSInteger _totalDownloads;
}

- (instancetype)init {
    self = [super init];
    if (self) {
        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
        self.session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    }
    return self;
}

- (void)downloadFiles:(NSArray<NSURL *> *)fileURLs {
    _downloadedFilePaths = [NSMutableArray array];
    _completedDownloads = 0;
    _totalDownloads = fileURLs.count;

    for (NSURL *fileURL in fileURLs) {
        NSURLSessionDownloadTask *downloadTask = [self.session downloadTaskWithURL:fileURL];
        [downloadTask resume];
    }

    if (fileURLs.count == 0) {
        [self URLSession:self.session didBecomeInvalidWithError:[NSError errorWithDomain:NSURLErrorDomain code:99 userInfo:nil]];
    }
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
    float prog = (float)totalBytesWritten / (float)totalBytesExpectedToWrite;
    if ([self.delegate respondsToSelector:@selector(downloaderProgress:)]) {
        [self.delegate downloaderProgress:prog];
    }
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
  didFinishDownloadingToURL:(NSURL *)location {
    NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
    NSString *destinationPath = [documentsPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-%@", NSUUID.UUID.UUIDString, downloadTask.response.suggestedFilename]];
    
    NSError *error;
    [[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:destinationPath] error:&error];
    
    if (error == nil) {
        [_downloadedFilePaths addObject:[NSURL fileURLWithPath:destinationPath]];
    }
    
    _completedDownloads++;
    
    if (_completedDownloads == _totalDownloads) {
        if ([self.delegate respondsToSelector:@selector(downloaderDidFinishDownloadingAllFiles:)]) {
            [self.delegate downloaderDidFinishDownloadingAllFiles:_downloadedFilePaths];
        }
    }
}

- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error {
    if ([self.delegate respondsToSelector:@selector(downloaderDidFailureWithError:)]) {
        [self.delegate downloaderDidFailureWithError:error];
    }
}
@end

================================================
FILE: BHTikTok.plist
================================================
{ Filter = { Bundles = ( 
    "com.zhiliaoapp.musically",
    "com.ss.iphone.ugc.Ame",
         ); }; }


================================================
FILE: JGProgressHUD/JGProgressHUD-Defines.h
================================================
//
//  JGProgressHUD-Defines.h
//  JGProgressHUD
//
//  Created by Jonas Gessner on 28.04.15.
//  Copyright (c) 2015 Jonas Gessner. All rights reserved.
//

#import <Foundation/Foundation.h>

/**
 Positions of the HUD.
 */
typedef NS_ENUM(NSUInteger, JGProgressHUDPosition) {
    /** Center position. */
    JGProgressHUDPositionCenter = 0,
    /** Top left position. */
    JGProgressHUDPositionTopLeft,
    /** Top center position. */
    JGProgressHUDPositionTopCenter,
    /** Top right position. */
    JGProgressHUDPositionTopRight,
    /** Center left position. */
    JGProgressHUDPositionCenterLeft,
    /** Center right position. */
    JGProgressHUDPositionCenterRight,
    /** Bottom left position. */
    JGProgressHUDPositionBottomLeft,
    /** Bottom center position. */
    JGProgressHUDPositionBottomCenter,
    /** Bottom right position. */
    JGProgressHUDPositionBottomRight
};

/**
 Appearance styles of the HUD.
 */
typedef NS_ENUM(NSUInteger, JGProgressHUDStyle) {
    /** Extra light HUD with dark elements. */
    JGProgressHUDStyleExtraLight = 0,
    /** Light HUD with dark elemets. */
    JGProgressHUDStyleLight,
    /** Dark HUD with light elements. */
    JGProgressHUDStyleDark,
};

#if TARGET_OS_IOS
/**
 Interaction types.
 */
typedef NS_ENUM(NSUInteger, JGProgressHUDInteractionType) {
    /** Block all touches. No interaction behin the HUD is possible. */
    JGProgressHUDInteractionTypeBlockAllTouches = 0,
    /** Block touches on the HUD view. */
    JGProgressHUDInteractionTypeBlockTouchesOnHUDView,
    /** Block no touches. */
    JGProgressHUDInteractionTypeBlockNoTouches
};
#endif

/**
 Parallax Modes.
 */
typedef NS_ENUM(NSUInteger, JGProgressHUDParallaxMode) {
    /** Follows the device setting for parallax. If "Reduce Motion" is enabled, no parallax effect is added to the HUD, if "Reduce Motion" is disabled the HUD will have a parallax effect. This behaviour is only supported on iOS 8 and higher. */
    JGProgressHUDParallaxModeDevice = 0,
    /** Always adds a parallax effect to the HUD. Parallax is only supported on iOS 7 and higher. */
    JGProgressHUDParallaxModeAlwaysOn,
    /** Never adds a parallax effect to the HUD. */
    JGProgressHUDParallaxModeAlwaysOff
};

#ifndef fequal
/**
 Macro for safe floating point comparison (for internal use in JGProgressHUD).
 */
#define fequal(a,b) (fabs((a) - (b)) < FLT_EPSILON)
#endif


================================================
FILE: JGProgressHUD/JGProgressHUD.h
================================================
//
//  JGProgressHUD.h
//  JGProgressHUD
//
//  Created by Jonas Gessner on 20.7.14.
//  Copyright (c) 2014 Jonas Gessner. All rights reserved.
//

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wquoted-include-in-framework-header"
#import "JGProgressHUD-Defines.h"
#import "JGProgressHUDShadow.h"
#import "JGProgressHUDAnimation.h"
#import "JGProgressHUDFadeAnimation.h"
#import "JGProgressHUDFadeZoomAnimation.h"
#import "JGProgressHUDIndicatorView.h"
#import "JGProgressHUDErrorIndicatorView.h"
#import "JGProgressHUDSuccessIndicatorView.h"
#import "JGProgressHUDRingIndicatorView.h"
#import "JGProgressHUDPieIndicatorView.h"
#import "JGProgressHUDIndeterminateIndicatorView.h"
#pragma clang diagnostic pop

@protocol JGProgressHUDDelegate;

/**
 A HUD to indicate progress, success, error, warnings or other notifications to the user.
 @discussion @c JGProgressHUD respects its @c layoutMargins when positioning the HUD view. Additionally, on iOS 11 if @c insetsLayoutMarginsFromSafeArea is set to @c YES (default) the @c layoutMargins additionally contain the @c safeAreaInsets.
 @note Remember to call every method from the main thread! UIKit => main thread!
 @attention You may not add JGProgressHUD to a view which has an alpha value < 1.0 or to a view which is a subview of a view with an alpha value < 1.0.
 */
@interface JGProgressHUD : UIView

/**
 Designated initializer.
 @param style The appearance style of the HUD.
 */
- (instancetype __nonnull)initWithStyle:(JGProgressHUDStyle)style;

/**
 Convenience initializer.
 @param style The appearance style of the HUD.
 */
+ (instancetype __nonnull)progressHUDWithStyle:(JGProgressHUDStyle)style;

/**
 Convenience initializer. The HUD will dynamically change its style based on whether dark mode is enabled or not. When dark mode is on, the style will be JGProgressHUDStyleDark, when dark mode is off the style will be JGProgressHUDStyleExtraLight.
 */
- (instancetype __nonnull)initWithAutomaticStyle;

/**
 Convenience initializer. The HUD will dynamically change its style based on whether dark mode is enabled or not. When dark mode is on, the style will be JGProgressHUDStyleDark, when dark mode is off the style will be JGProgressHUDStyleExtraLight.
 */
+ (instancetype __nonnull)progressHUDWithAutomaticStyle;

/**
 The appearance style of the HUD.
 @b Default: JGProgressHUDStyleExtraLight.
 */
@property (nonatomic, assign) JGProgressHUDStyle style;

/** The view in which the HUD is presented. */
@property (nonatomic, weak, readonly, nullable) UIView *targetView;

/**
 The delegate of the HUD.
 @sa JGProgressHUDDelegate.
 */
@property (nonatomic, weak, nullable) id <JGProgressHUDDelegate> delegate;

/** The actual HUD view visible on screen. You may add animations to this view. */
@property (nonatomic, strong, readonly, nonnull) UIView *HUDView;

/**
 The content view inside the @c HUDView. If you want to add additional views to the HUD you should add them as subview to the @c contentView.
 */
@property (nonatomic, strong, readonly, nonnull) UIView *contentView;

/**
 The label used to present text on the HUD. Set the @c text or @c attributedText property of this label to change the displayed text. You may not change the label's @c frame or @c bounds.
 */
@property (nonatomic, strong, readonly, nonnull) UILabel *textLabel;

/**
 The label used to present detail text on the HUD. Set the @c text or @c attributedText property of this label to change the displayed text. You may not change the label's @c frame or @c bounds.
 */
@property (nonatomic, strong, readonly, nonnull) UILabel *detailTextLabel;

/**
 The indicator view. You can assign a custom subclass of @c JGProgressHUDIndicatorView to this property or one of the default indicator views (if you do so, you should assign it before showing the HUD). This value is optional.
 @b Default: JGProgressHUDIndeterminateIndicatorView.
 */
@property (nonatomic, strong, nullable) JGProgressHUDIndicatorView *indicatorView;

/**
 The shadow cast by the @c HUDView. This value is optional. Setting this to @c nil means no shadow is cast by the HUD.
 @b Default: nil.
 */
@property (nonatomic, strong, nullable) JGProgressHUDShadow *shadow;

/**
 The position of the HUD inside the hosting view's frame, or inside the specified frame.
 @b Default: JGProgressHUDPositionCenter
 */
@property (nonatomic, assign) JGProgressHUDPosition position;

/**
 The animation used for showing and dismissing the HUD.
 @b Default: JGProgressHUDFadeAnimation.
 */
@property (nonatomic, strong, nonnull) JGProgressHUDAnimation *animation;

#if TARGET_OS_IOS
/**
 Interaction type of the HUD. Determines whether touches should be let through to the views behind the HUD.
 @sa JGProgressHUDInteractionType.
 @b Default: JGProgressHUDInteractionTypeBlockAllTouches.
 */
@property (nonatomic, assign) JGProgressHUDInteractionType interactionType;
#endif

/**
 Parallax mode for the HUD. This setting determines whether the HUD should have a parallax (@c UIDeviceMotion) effect. This effect is controlled by device motion on iOS and remote touchpad panning gestures on tvOS.
 @sa JGProgressHUDParallaxMode.
 @b Default: JGProgressHUDParallaxModeDevice.
 */
@property (nonatomic, assign) JGProgressHUDParallaxMode parallaxMode;

#if TARGET_OS_TV
/**
 When this property is set to @c YES the HUD will try to become focused, which prevents interactions with the @c targetView. If set to @c NO the HUD will not become focused and interactions with @c targetView remain possible. Default: @c YES.
 */
@property (nonatomic, assign) BOOL wantsFocus;
#endif

/**
 If the HUD should always have the same width and height.
 @b Default: NO.
 */
@property (nonatomic, assign) BOOL square;

/**
 Internally @c JGProgressHUD uses an @c UIVisualEffectView with a @c UIBlurEffect. A second @c UIVisualEffectView can be added on top of that with a @c UIVibrancyEffect which amplifies and adjusts the color of content layered behind the view, allowing content placed inside the contentView to become more vivid. This flag sets whether the @c UIVibrancyEffect should be used. Using the vibrancy effect can sometimes, depending on the contents of the display, result in a weird look (especially on iOS < 9.3).
 @b Default: NO.
 */
@property (nonatomic, assign) BOOL vibrancyEnabled;

/**
 The radius used for rounding the four corners of the HUD view.
 @b Default: 10.0.
 */
@property (nonatomic, assign) CGFloat cornerRadius;

/**
 Insets the contents of the HUD.
 @b Default: (20, 20, 20, 20).
 */
@property (nonatomic, assign) UIEdgeInsets contentInsets;

/**
 @return Whether the HUD is visible on screen.
 */
@property (nonatomic, assign, readonly, getter = isVisible) BOOL visible;

/**
 The progress to display using the @c progressIndicatorView. A change of this property is not animated. Use the @c setProgress:animated: method for an animated progress change.
 @b Default: 0.0.
 */
@property (nonatomic, assign) float progress;

/**
 Adjusts the current progress shown by the receiver, optionally animating the change.
 
 The current progress is represented by a floating-point value between 0.0 and 1.0, inclusive, where 1.0 indicates the completion of the task. The default value is 0.0. Values less than 0.0 and greater than 1.0 are pinned to those limits.
 @param progress The new progress value.
 @param animated YES if the change should be animated, NO if the change should happen immediately.
 */
- (void)setProgress:(float)progress animated:(BOOL)animated;

/**
 Specifies a minimum time that the HUD will be on-screen. Useful to prevent the HUD from flashing quickly on the screen when indeterminate tasks complete more quickly than expected.
 @b Default: 0.0.
 */
@property (nonatomic, assign) NSTimeInterval minimumDisplayTime;

/**
 Determines whether Voice Over announcements should be made upon displaying the HUD (if Voice Over is active).
 @b Default: YES
 */
@property (nonatomic, assign) BOOL voiceOverEnabled;

#if TARGET_OS_IOS
/**
 A block to be invoked when the HUD view is tapped.
 @note The interaction type of the HUD must be @c JGProgressHUDInteractionTypeBlockTouchesOnHUDView or @c JGProgressHUDInteractionTypeBlockAllTouches, otherwise this block won't be fired.
 */
@property (nonatomic, copy, nullable) void (^tapOnHUDViewBlock)(JGProgressHUD *__nonnull HUD);

/**
 A block to be invoked when the area outside of the HUD view is tapped.
 @note The interaction type of the HUD must be @c JGProgressHUDInteractionTypeBlockAllTouches, otherwise this block won't be fired.
 */
@property (nonatomic, copy, nullable) void (^tapOutsideBlock)(JGProgressHUD *__nonnull HUD);
#endif

/**
 Shows the HUD animated. You should preferably show the HUD in a UIViewController's view. The HUD will be repositioned in response to rotation and keyboard show/hide notifications.
 @param view The view to show the HUD in. The frame of the @c view will be used to calculate the position of the HUD.
 */
- (void)showInView:(UIView *__nonnull)view;

/**
 Shows the HUD. You should preferably show the HUD in a UIViewController's view.  The HUD will be repositioned in response to rotation and keyboard show/hide notifications.
 @param view The view to show the HUD in. The frame of the @c view will be used to calculate the position of the HUD.
 @param animated If the HUD should show with an animation.
 */
- (void)showInView:(UIView *__nonnull)view animated:(BOOL)animated;

/**
 Shows the HUD after a delay. You should preferably show the HUD in a UIViewController's view. The HUD will be repositioned in response to rotation and keyboard show/hide notifications.
 You may call @c dismiss to stop the HUD from appearing before the delay has passed.
 @param view The view to show the HUD in. The frame of the @c view will be used to calculate the position of the HUD.
 @param animated If the HUD should show with an animation.
 @param delay The delay until the HUD will be shown.
 */
- (void)showInView:(UIView *__nonnull)view animated:(BOOL)animated afterDelay:(NSTimeInterval)delay;

/** Dismisses the HUD animated. If the HUD is currently not visible this method does nothing. */
- (void)dismiss;

/**
 Dismisses the HUD. If the HUD is currently not visible this method does nothing.
 @param animated If the HUD should dismiss with an animation.
 */
- (void)dismissAnimated:(BOOL)animated;

/**
 Dismisses the HUD animated after a delay. If the HUD is currently not visible this method does nothing.
 @param delay The delay until the HUD will be dismissed.
 */
- (void)dismissAfterDelay:(NSTimeInterval)delay;

/**
 Dismisses the HUD after a delay. If the HUD is currently not visible this method does nothing.
 @param delay The delay until the HUD will be dismissed.
 @param animated If the HUD should dismiss with an animation.
 */
- (void)dismissAfterDelay:(NSTimeInterval)delay animated:(BOOL)animated;

/**
 Dismisses the HUD after a delay and runs a block upon completion. If the HUD is currently not visible this method does nothing.
 @param delay The delay until the HUD will be dismissed.
 @param animated If the HUD should dismiss with an animation.
 @param dismissCompletion The block to execute after the HUD was dismissed.
 */
- (void)dismissAfterDelay:(NSTimeInterval)delay animated:(BOOL)animated completion:(void (^_Nullable)(void))dismissCompletion;

/**
 Schedules the given block to be executed when this HUD disapears. If the HUD is currently not visible this method does nothing.
 @param dismissCompletion The block to execute after the HUD was dismissed. Multiple calls to this method cause the different blocks to be executed in FIFO order.
 */
- (void)performAfterDismiss:(void (^_Nonnull)(void))dismissCompletion;

@end

@interface JGProgressHUD (HUDManagement)

/**
 @param view The view to return all visible progress HUDs for.
 @return All visible progress HUDs in the view.
 */
+ (NSArray<JGProgressHUD *> *__nonnull)allProgressHUDsInView:(UIView *__nonnull)view;

/**
 @param view The view to return all visible progress HUDs for.
 @return All visible progress HUDs in the view and its subviews.
 */
+ (NSArray<JGProgressHUD *> *__nonnull)allProgressHUDsInViewHierarchy:(UIView *__nonnull)view;

@end

@protocol JGProgressHUDDelegate <NSObject>

@optional

/**
 Called before the HUD will appear.
 @param view The view in which the HUD is presented.
 */
- (void)progressHUD:(JGProgressHUD *__nonnull)progressHUD willPresentInView:(UIView *__nonnull)view;

/**
 Called after the HUD appeared.
 @param view The view in which the HUD is presented.
 */
- (void)progressHUD:(JGProgressHUD *__nonnull)progressHUD didPresentInView:(UIView *__nonnull)view;

/**
 Called before the HUD will disappear.
 @param view The view in which the HUD is presented and will be dismissed from.
 */
- (void)progressHUD:(JGProgressHUD *__nonnull)progressHUD willDismissFromView:(UIView *__nonnull)view;

/**
 Called after the HUD has disappeared.
 @param view The view in which the HUD was presented and was be dismissed from.
 */
- (void)progressHUD:(JGProgressHUD *__nonnull)progressHUD didDismissFromView:(UIView *__nonnull)view;

@end


================================================
FILE: JGProgressHUD/JGProgressHUD.m
================================================
//
//  JGProgressHUD.m
//  JGProgressHUD
//
//  Created by Jonas Gessner on 20.7.14.
//  Copyright (c) 2014 Jonas Gessner. All rights reserved.
//

#import "JGProgressHUD.h"
#import <QuartzCore/QuartzCore.h>
#import "JGProgressHUDFadeAnimation.h"
#import "JGProgressHUDIndeterminateIndicatorView.h"

#if !__has_feature(objc_arc)
#error "JGProgressHUD requires ARC!"
#endif

static inline CGRect JGProgressHUD_CGRectIntegral(CGRect rect) {
    CGFloat scale = [[UIScreen mainScreen] scale];
    
    return (CGRect){{((CGFloat)floor(rect.origin.x*scale))/scale, ((CGFloat)floor(rect.origin.y*scale))/scale}, {((CGFloat)ceil(rect.size.width*scale))/scale, ((CGFloat)ceil(rect.size.height*scale))/scale}};
}

API_AVAILABLE(ios(12.0), tvos(10.0))
static inline JGProgressHUDStyle JGProgressHUDStyleFromUIUserInterfaceStyle(UIUserInterfaceStyle uiStyle) {
    if (uiStyle == UIUserInterfaceStyleDark) {
        return JGProgressHUDStyleDark;
    }
    else {
        return JGProgressHUDStyleExtraLight;
    }
}

@interface JGProgressHUD () {
    BOOL _transitioning;
    BOOL _updateAfterAppear;
    
    BOOL _dismissAfterTransitionFinished;
    BOOL _dismissAfterTransitionFinishedWithAnimation;
    
    CFAbsoluteTime _displayTimestamp;
    dispatch_block_t __nullable _displayBlock;
    
    BOOL _effectiveIndicatorViewNeedsUpdate;
    
    UIView *__nonnull _blurViewContainer;
    UIView *__nonnull _shadowView;
    CAShapeLayer *__nonnull _shadowMaskLayer;
    
    NSMutableArray<void (^)(void)> *_dismissActions;
    
    BOOL _automaticStyle;
}

// In 'beta'
/**
 Setting this to @c YES makes the text and indicator views bigger.
 @b Default: NO.
 */
@property (nonatomic, assign) BOOL thick;

@property (nonatomic, strong, readonly, nonnull) UIVisualEffectView *blurView;
@property (nonatomic, strong, readonly, nonnull) UIVisualEffectView *vibrancyView;

@property (nonatomic, strong, readonly, nonnull) JGProgressHUDIndicatorView *effectiveIndicatorView;

@end

@interface JGProgressHUDAnimation (Private)

@property (nonatomic, weak, nullable) JGProgressHUD *progressHUD;

@end

@implementation JGProgressHUD

@synthesize HUDView = _HUDView;
@synthesize blurView = _blurView;
@synthesize vibrancyView = _vibrancyView;
@synthesize textLabel = _textLabel;
@synthesize detailTextLabel = _detailTextLabel;
@synthesize indicatorView = _indicatorView;
@synthesize animation = _animation;
@synthesize contentView = _contentView;

@dynamic visible;

#pragma mark - Keyboard

static CGRect keyboardFrame = (CGRect){{0.0, 0.0}, {0.0, 0.0}};

#if TARGET_OS_IOS
+ (void)keyboardFrameWillChange:(NSNotification *)notification {
    keyboardFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
    if (CGRectIsEmpty(keyboardFrame)) {
        keyboardFrame = [notification.userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue];
    }
}

+ (void)keyboardFrameDidChange:(NSNotification *)notification {
    keyboardFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
}

+ (void)keyboardDidHide {
    keyboardFrame = CGRectZero;
}

+ (void)load {
    @autoreleasepool {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardFrameWillChange:) name:UIKeyboardWillChangeFrameNotification object:nil];
        
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardFrameDidChange:) name:UIKeyboardDidChangeFrameNotification object:nil];
        
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidHide) name:UIKeyboardDidHideNotification object:nil];
    }
}
#endif

+ (CGRect)currentKeyboardFrame {
    return keyboardFrame;
}

#pragma mark - Initializers

- (instancetype)init {
    return [self initWithAutomaticStyle];
}

- (instancetype)initWithFrame:(CGRect __unused)frame {
    return [self initWithAutomaticStyle];
}

/*
 Basic architecture:
 
 * self covers the entire target view.
 * self.HUDView is a subview of self and has the size and position of the visible HUD. The layer has rounded corners set with self.cornerRadius. The layer does not clip to bounds.
 * _shadowView is a subview of self.HUDView and always covers the entire HUDView. The corners are also rounded in the same way as self.HUDView. It draws its shadow only on the outside by using a masking layer, so that the shadow does not interfere with the blur view.
 * _blurViewContainer is a subview of self.HUDView and always covers the entire self.HUDView. The corners are also rounded in the same way as self.HUDView but it additionally clips to bounds.
 * self.blurView is a subview of _blurViewContainer and provides the blur effect. The corners are not rounded and the view does not clip to bounds.
 * self.vibrancyView is a subview of self.blurView.contentView and provides the vibrancy effect. The corners are not rounded and the view does not clip to bounds.
 * self.contentView is a subview of self.vibrancyView.contentView. It does not always have the same frame as it's superview (during transitions).
 * self.contentView contains the labels and the indicator view.
 
 */
- (instancetype)initWithStyle:(JGProgressHUDStyle)style {
    self = [super initWithFrame:CGRectZero];
    
    if (self) {
        _style = style;
        _voiceOverEnabled = YES;
        _automaticStyle = NO;
        
        _HUDView = [[UIView alloc] init];
        self.HUDView.backgroundColor = [UIColor clearColor];
        [self addSubview:self.HUDView];
        
        _blurViewContainer = [[UIView alloc] init];
        _blurViewContainer.backgroundColor = [UIColor clearColor];
        _blurViewContainer.clipsToBounds = YES;
        [self.HUDView addSubview:_blurViewContainer];
        
        _shadowView = [[UIView alloc] init];
        _shadowView.backgroundColor = [UIColor blackColor];
        _shadowView.userInteractionEnabled = NO;
        _shadowView.layer.shadowOpacity = 1.0;
        _shadowView.alpha = 0.0;
        
        _shadowMaskLayer = [CAShapeLayer layer];
        _shadowMaskLayer.fillRule = kCAFillRuleEvenOdd;
        _shadowMaskLayer.fillColor = [UIColor blackColor].CGColor;
        _shadowMaskLayer.opacity = 1.0;
        
        _shadowView.layer.mask = _shadowMaskLayer;
        
        [self.HUDView addSubview:_shadowView];
        
        _indicatorView = [[JGProgressHUDIndeterminateIndicatorView alloc] init];
        _effectiveIndicatorView = _indicatorView;
        [self.effectiveIndicatorView setUpForHUDStyle:self.style vibrancyEnabled:self.vibrancyEnabled];
        
        self.hidden = YES;
        self.backgroundColor = [UIColor clearColor];
        
        self.contentInsets = UIEdgeInsetsMake(20.0, 20.0, 20.0, 20.0);
        self.layoutMargins = UIEdgeInsetsMake(20.0, 20.0, 20.0, 20.0);
        
        self.cornerRadius = 10.0;
        
        _dismissActions = [[NSMutableArray alloc] init];
        
#if TARGET_OS_IOS
        [self addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)]];
#elif TARGET_OS_TV
        _wantsFocus = YES;
#endif
    }
    
    return self;
}

+ (instancetype)progressHUDWithStyle:(JGProgressHUDStyle)style {
    return [(JGProgressHUD *)[self alloc] initWithStyle:style];
}

- (instancetype)initWithAutomaticStyle {
    if (@available(iOS 12.0, tvOS 10.0, *)) {
        JGProgressHUDStyle initialStyle;
        
        if (@available(iOS 13.0, tvOS 13.0, *)) {
            initialStyle = JGProgressHUDStyleFromUIUserInterfaceStyle([[UITraitCollection currentTraitCollection] userInterfaceStyle]);
        }
        else {
            initialStyle = JGProgressHUDStyleExtraLight;
        }
        
        self = [self initWithStyle:initialStyle];
        
        if (self != nil) {
            _automaticStyle = YES;
        }
    }
    else {
        self = [self initWithStyle:JGProgressHUDStyleExtraLight];
    }
    
    return self;
}

+ (instancetype)progressHUDWithAutomaticStyle {
    return [[self alloc] initWithAutomaticStyle];
}

#pragma mark - Layout

- (void)setHUDViewFrameCenterWithSize:(CGSize)size insetViewFrame:(CGRect)viewFrame {
    CGRect frame = (CGRect){CGPointZero, size};
    
    switch (self.position) {
        case JGProgressHUDPositionTopLeft:
            frame.origin.x = CGRectGetMinX(viewFrame);
            frame.origin.y = CGRectGetMinY(viewFrame);
            break;
            
        case JGProgressHUDPositionTopCenter:
            frame.origin.x = CGRectGetMidX(viewFrame) - size.width/2.0;
            frame.origin.y = CGRectGetMinY(viewFrame);
            break;
            
        case JGProgressHUDPositionTopRight:
            frame.origin.x = CGRectGetMaxX(viewFrame) - size.width;
            frame.origin.y = CGRectGetMinY(viewFrame);
            break;
            
        case JGProgressHUDPositionCenterLeft:
            frame.origin.x = CGRectGetMinX(viewFrame);
            frame.origin.y = CGRectGetMidY(viewFrame) - size.height/2.0;
            break;
            
        case JGProgressHUDPositionCenter:
            frame.origin.x = CGRectGetMidX(viewFrame) - size.width/2.0;
            frame.origin.y = CGRectGetMidY(viewFrame) - size.height/2.0;
            break;
            
        case JGProgressHUDPositionCenterRight:
            frame.origin.x = CGRectGetMaxX(viewFrame) - frame.size.width;
            frame.origin.y = CGRectGetMidY(viewFrame) - size.height/2.0;
            break;
            
        case JGProgressHUDPositionBottomLeft:
            frame.origin.x = CGRectGetMinX(viewFrame);
            frame.origin.y = CGRectGetMaxY(viewFrame) - size.height;
            break;
            
        case JGProgressHUDPositionBottomCenter:
            frame.origin.x = CGRectGetMidX(viewFrame) - size.width/2.0;
            frame.origin.y = CGRectGetMaxY(viewFrame) - frame.size.height;
            break;
            
        case JGProgressHUDPositionBottomRight:
            frame.origin.x = CGRectGetMaxX(viewFrame) - size.width;
            frame.origin.y = CGRectGetMaxY(viewFrame) - size.height;
            break;
    }
    
    CGRect oldHUDFrame = self.HUDView.frame;
    CGRect updatedHUDFrame = JGProgressHUD_CGRectIntegral(frame);
    
    self.HUDView.frame = updatedHUDFrame;
    _shadowView.frame = self.HUDView.bounds;
    [self updateShadowViewMask];
    
    _blurViewContainer.frame = self.HUDView.bounds;
    self.blurView.frame = self.HUDView.bounds;
    self.vibrancyView.frame = self.HUDView.bounds;
    
    [UIView performWithoutAnimation:^{
        self.contentView.frame = (CGRect){{(oldHUDFrame.size.width - updatedHUDFrame.size.width)/2.0, (oldHUDFrame.size.height - updatedHUDFrame.size.height)/2.0}, updatedHUDFrame.size};
    }];
    
    self.contentView.frame = self.HUDView.bounds;
}

- (void)updateShadowViewMask {
    if (CGRectIsEmpty(_shadowView.layer.bounds)) {
        return;
    }
    
    CGRect layerBounds = CGRectMake(0.0, 0.0, _shadowView.layer.bounds.size.width + self.shadow.radius*4.0, _shadowView.layer.bounds.size.height + self.shadow.radius*4.0);
    
    UIBezierPath *path = [UIBezierPath bezierPathWithRect:layerBounds];
    
    CGRect maskRect = CGRectInset(layerBounds, self.shadow.radius*2.0, self.shadow.radius*2.0);
    
    UIBezierPath *roundedPath = [UIBezierPath bezierPathWithRoundedRect:maskRect cornerRadius:self.cornerRadius];
    
    [path appendPath:roundedPath];
    
    _shadowMaskLayer.frame = CGRectInset(_shadowView.layer.bounds, -self.shadow.radius*2.0, -self.shadow.radius*2.0);
    
    CAAnimation *currentAnimation = [self.HUDView.layer animationForKey:@"position"];
    if (currentAnimation != nil) {
        [CATransaction begin];
        
        [CATransaction setAnimationDuration:currentAnimation.duration];
        [CATransaction setAnimationTimingFunction:currentAnimation.timingFunction];
        
        CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
        [_shadowMaskLayer addAnimation:pathAnimation forKey:@"path"];
        
        _shadowMaskLayer.path = path.CGPath;
        
        [CATransaction commit];
    }
    else {
        _shadowMaskLayer.path = path.CGPath;
        // Remove implicit CALayer animations:
        [_shadowMaskLayer removeAllAnimations];
    }
}

- (void)layoutHUD {
    if (_transitioning) {
        _updateAfterAppear = YES;
        return;
    }
    
    if (_targetView == nil) {
        return;
    }
    
    CGRect indicatorFrame = self.effectiveIndicatorView.frame;
    indicatorFrame.origin.y = self.contentInsets.top;
    
    CGRect insetFrame = [self insetFrameForView:self];
    
    CGFloat maxContentWidth = insetFrame.size.width - self.contentInsets.left - self.contentInsets.right;
    CGFloat maxContentHeight = insetFrame.size.height - self.contentInsets.top - self.contentInsets.bottom;
    
    CGRect labelFrame = CGRectZero;
    CGRect detailFrame = CGRectZero;
    
    CGFloat currentY = CGRectGetMaxY(indicatorFrame);
    if (!CGRectIsEmpty(indicatorFrame)) {
        currentY += 10.0;
    }
    
    if (_textLabel.text.length > 0) {
        _textLabel.preferredMaxLayoutWidth = maxContentWidth;
        
        CGSize neededSize = _textLabel.intrinsicContentSize;
        neededSize.height = MIN(neededSize.height, maxContentHeight);
        
        labelFrame.size = neededSize;
        labelFrame.origin.y = currentY;
        currentY = CGRectGetMaxY(labelFrame) + 6.0;
    }
    
    if (_detailTextLabel.text.length > 0) {
        _detailTextLabel.preferredMaxLayoutWidth = maxContentWidth;
        
        CGSize neededSize = _detailTextLabel.intrinsicContentSize;
        neededSize.height = MIN(neededSize.height, maxContentHeight);
        
        detailFrame.size = neededSize;
        detailFrame.origin.y = currentY;
    }
    
    CGSize size = CGSizeZero;
    
    CGFloat width = MIN(self.contentInsets.left + MAX(indicatorFrame.size.width, MAX(labelFrame.size.width, detailFrame.size.width)) + self.contentInsets.right, insetFrame.size.width);
    
    CGFloat height = MAX(CGRectGetMaxY(labelFrame), MAX(CGRectGetMaxY(detailFrame), CGRectGetMaxY(indicatorFrame))) + self.contentInsets.bottom;
    
    if (self.square) {
        CGFloat uniSize = MAX(width, height);
        
        size.width = uniSize;
        size.height = uniSize;
        
        CGFloat heightDelta = (uniSize-height)/2.0;
        
        labelFrame.origin.y += heightDelta;
        detailFrame.origin.y += heightDelta;
        indicatorFrame.origin.y += heightDelta;
    }
    else {
        size.width = width;
        size.height = height;
    }
    
    CGPoint center = CGPointMake(size.width/2.0, size.height/2.0);
    
    indicatorFrame.origin.x = center.x - indicatorFrame.size.width/2.0;
    labelFrame.origin.x = center.x - labelFrame.size.width/2.0;
    detailFrame.origin.x = center.x - detailFrame.size.width/2.0;
    
    [UIView performWithoutAnimation:^{
        self.effectiveIndicatorView.frame = indicatorFrame;
        self->_textLabel.frame = JGProgressHUD_CGRectIntegral(labelFrame);
        self->_detailTextLabel.frame = JGProgressHUD_CGRectIntegral(detailFrame);
    }];
    
    [self setHUDViewFrameCenterWithSize:size insetViewFrame:insetFrame];
}

- (CGRect)insetFrameForView:(UIView *)view {
    CGRect localKeyboardFrame = [view convertRect:[[self class] currentKeyboardFrame] fromView:nil];
    CGRect frame = view.bounds;
    
    if (!CGRectIsEmpty(localKeyboardFrame) && CGRectIntersectsRect(frame, localKeyboardFrame)) {
        CGFloat keyboardMinY = CGRectGetMinY(localKeyboardFrame);
        
        if (@available(iOS 11, tvOS 11, *)) {
            if (self.insetsLayoutMarginsFromSafeArea) {
                // This makes sure that the bottom safe area inset is only respected when that area is not covered by the keyboard. When the keyboard covers the bottom area outside of the safe area it is not necessary to keep the bottom safe area insets part of the insets for the HUD.
                keyboardMinY += self.safeAreaInsets.bottom;
            }
        }
        
        frame.size.height = MAX(MIN(frame.size.height, keyboardMinY), 0.0);
    }
    
    return UIEdgeInsetsInsetRect(frame, view.layoutMargins);
}

- (void)applyCornerRadius {
    self.HUDView.layer.cornerRadius = self.cornerRadius;
    _blurViewContainer.layer.cornerRadius = self.cornerRadius;
    _shadowView.layer.cornerRadius = self.cornerRadius;
    
    [self updateShadowViewMask];
}

#pragma mark - Showing

- (void)cleanUpAfterPresentation {
#if TARGET_OS_TV
    if (self.wantsFocus) {
        [self.targetView setNeedsFocusUpdate];
    }
#endif
    
    self.hidden = NO;
    
    _transitioning = NO;
    // Correct timestamp to the current time for animated presentations:
    _displayTimestamp = CFAbsoluteTimeGetCurrent();
    
    if (_effectiveIndicatorViewNeedsUpdate) {
        self.effectiveIndicatorView = self.indicatorView;
        _effectiveIndicatorViewNeedsUpdate = NO;
        _updateAfterAppear = NO;
    }
    else if (_updateAfterAppear) {
        [self layoutHUD];
        _updateAfterAppear = NO;
    }
    
    if ([self.delegate respondsToSelector:@selector(progressHUD:didPresentInView:)]){
        [self.delegate progressHUD:self didPresentInView:self.targetView];
    }
    
    if (_dismissAfterTransitionFinished) {
        [self dismissAnimated:_dismissAfterTransitionFinishedWithAnimation];
        _dismissAfterTransitionFinished = NO;
        _dismissAfterTransitionFinishedWithAnimation = NO;
    }
    else if (self.voiceOverEnabled && UIAccessibilityIsVoiceOverRunning()) {
        UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, self);
    }
}

- (void)showInView:(UIView *)view {
    [self showInView:view animated:YES];
}

- (void)layoutSubviews {
    [super layoutSubviews];
    
    [self layoutHUD];
}

- (void)showInView:(UIView *)view animated:(BOOL)animated {
    if (_transitioning) {
        return;
    }
    else if (self.targetView != nil) {
#if DEBUG
        NSLog(@"[Warning] The HUD is already visible! Ignoring.");
#endif
        return;
    }
    
    if ([self.delegate respondsToSelector:@selector(progressHUD:willPresentInView:)]) {
        [self.delegate progressHUD:self willPresentInView:view];
    }
    
    _targetView = view;
    
    self.frame = _targetView.bounds;
    
    [_targetView addSubview:self];
    
    self.translatesAutoresizingMaskIntoConstraints = NO;
    
    [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:_targetView attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0.0].active = YES;
    [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:_targetView attribute:NSLayoutAttributeHeight multiplier:1.0 constant:0.0].active = YES;
    [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:_targetView attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0].active = YES;
    [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:_targetView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0].active = YES;
    
    [self setNeedsLayout];
    [self layoutIfNeeded];
    
    _transitioning = YES;
    
    _displayTimestamp = CFAbsoluteTimeGetCurrent();
    
    if (animated && self.animation != nil) {
        [self.animation show];
    }
    else {
        [self cleanUpAfterPresentation];
    }
    
#if TARGET_OS_IOS
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardFrameChanged:) name:UIKeyboardWillChangeFrameNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardFrameChanged:) name:UIKeyboardDidChangeFrameNotification object:nil];
#endif
}

- (void)showInView:(UIView *__nonnull)view animated:(BOOL)animated afterDelay:(NSTimeInterval)delay {
    __weak __typeof(self) weakSelf = self;

    if (_displayBlock != NULL) {
        dispatch_cancel(_displayBlock);
    }

    _displayBlock = dispatch_block_create(0, ^{
        if (weakSelf) {
            __strong __typeof(weakSelf) strongSelf = weakSelf;
            strongSelf->_displayBlock = NULL;
            
            if (!strongSelf.visible) {
                [strongSelf showInView:view animated:animated];
            }
        }
    });

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), _displayBlock);
}

#pragma mark - Dismissing

- (void)cleanUpAfterDismissal {
    self.hidden = YES;
    [self removeFromSuperview];
    
    [self removeObservers];
    
    _transitioning = NO;
    _dismissAfterTransitionFinished = NO;
    _dismissAfterTransitionFinishedWithAnimation = NO;
    
    __typeof(self.targetView) targetView = self.targetView;
    _targetView = nil;
    
    for (void (^action)(void) in _dismissActions) {
        action();
    }
    [_dismissActions removeAllObjects];
    
    if ([self.delegate respondsToSelector:@selector(progressHUD:didDismissFromView:)]) {
        [self.delegate progressHUD:self didDismissFromView:targetView];
    }
}

- (void)dismiss {
    [self dismissAnimated:YES];
}

- (void)dismissAnimated:(BOOL)animated {
    if (_displayBlock != NULL) {
        dispatch_cancel(_displayBlock);
        _displayBlock = NULL;
    }

    if (_transitioning) {
        _dismissAfterTransitionFinished = YES;
        _dismissAfterTransitionFinishedWithAnimation = animated;
        return;
    }
    
    if (self.targetView == nil) {
        return;
    }
    
    if (self.minimumDisplayTime > 0.0 && _displayTimestamp > 0.0) {
        CFAbsoluteTime displayedTime = CFAbsoluteTimeGetCurrent()-_displayTimestamp;
        
        if (displayedTime < self.minimumDisplayTime) {
            NSTimeInterval delta = self.minimumDisplayTime-displayedTime;
            
            [self dismissAfterDelay:delta animated:animated];
            
            return;
        }
    }
    
    if ([self.delegate respondsToSelector:@selector(progressHUD:willDismissFromView:)]) {
        [self.delegate progressHUD:self willDismissFromView:self.targetView];
    }
    
    _transitioning = YES;
    
    if (animated && self.animation) {
        [self.animation hide];
    }
    else {
        [self cleanUpAfterDismissal];
    }
}

- (void)dismissAfterDelay:(NSTimeInterval)delay {
    [self dismissAfterDelay:delay animated:YES];
}

- (void)dismissAfterDelay:(NSTimeInterval)delay animated:(BOOL)animated {
    [self dismissAfterDelay:delay animated:animated completion:nil];
}

- (void)dismissAfterDelay:(NSTimeInterval)delay animated:(BOOL)animated completion:(void (^_Nullable)(void))dismissCompletion {
    __weak __typeof(self) weakSelf = self;
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        if (weakSelf) {
            __strong __typeof(weakSelf) strongSelf = weakSelf;
            if (strongSelf.visible) {
                if (dismissCompletion != nil) {
                    [self performAfterDismiss:dismissCompletion];
                }
                [strongSelf dismissAnimated:animated];
            }
        }
    });
}

- (void)performAfterDismiss:(void (^_Nonnull)(void))dismissCompletion {
    if (self.visible) {
        [_dismissActions addObject:dismissCompletion];
    }
}

#pragma mark - Callbacks

#if TARGET_OS_IOS
- (void)tapped:(UITapGestureRecognizer *)t {
    if (CGRectContainsPoint(self.contentView.bounds, [t locationInView:self.contentView])) {
        if (self.tapOnHUDViewBlock != nil) {
            self.tapOnHUDViewBlock(self);
        }
    }
    else if (self.tapOutsideBlock != nil) {
        self.tapOutsideBlock(self);
    }
}

static inline UIViewAnimationOptions UIViewAnimationOptionsFromUIViewAnimationCurve(UIViewAnimationCurve curve) {
    UIViewAnimationOptions testOptions = UIViewAnimationCurveLinear << 16;
    
    if (testOptions != UIViewAnimationOptionCurveLinear) {
        NSLog(@"Unexpected implementation of UIViewAnimationOptionCurveLinear");
    }
    
    return (UIViewAnimationOptions)(curve << 16);
}

- (void)keyboardFrameChanged:(NSNotification *)notification {
    NSTimeInterval duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
    
    UIViewAnimationCurve curve = (UIViewAnimationCurve)[notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] unsignedIntegerValue];
    
    [UIView animateWithDuration:duration delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionsFromUIViewAnimationCurve(curve) animations:^{
        [self layoutHUD];
    } completion:nil];
}
#endif

- (void)updateMotionOnHUDView {
    BOOL reduceMotionEnabled = UIAccessibilityIsReduceMotionEnabled();
    
    BOOL wantsParallax = ((self.parallaxMode == JGProgressHUDParallaxModeDevice && !reduceMotionEnabled) || self.parallaxMode == JGProgressHUDParallaxModeAlwaysOn);
    BOOL hasParallax = (self.HUDView.motionEffects.count > 0);
    
    if (wantsParallax == hasParallax) {
        return;
    }
    
    if (!wantsParallax) {
        self.HUDView.motionEffects = @[];
    }
    else {
        UIInterpolatingMotionEffect *x = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.x" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
        
        CGFloat maxMovement = 20.0;
        
        x.minimumRelativeValue = @(-maxMovement);
        x.maximumRelativeValue = @(maxMovement);
        
        UIInterpolatingMotionEffect *y = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.y" type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
        
        y.minimumRelativeValue = @(-maxMovement);
        y.maximumRelativeValue = @(maxMovement);
        
        self.HUDView.motionEffects = @[x, y];
    }
}

- (void)animationDidFinish:(BOOL)presenting {
    if (presenting) {
        [self cleanUpAfterPresentation];
    }
    else {
        [self cleanUpAfterDismissal];
    }
}

#pragma mark - Getters

- (BOOL)isVisible {
    return (self.superview != nil);
}

- (UIVisualEffectView *)blurView {
    if (!_blurView) {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateMotionOnHUDView) name:UIAccessibilityReduceMotionStatusDidChangeNotification object:nil];
        
        UIBlurEffectStyle effect;
        
        if (self.style == JGProgressHUDStyleDark) {
            effect = UIBlurEffectStyleDark;
        }
        else if (self.style == JGProgressHUDStyleLight) {
            effect = UIBlurEffectStyleLight;
        }
        else {
            effect = UIBlurEffectStyleExtraLight;
        }
        
        UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:effect];
        
        _blurView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
        
        [self updateMotionOnHUDView];
        
        [_blurViewContainer addSubview:_blurView];
        
#if TARGET_OS_IOS
        [self.contentView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)]];
#endif
    }
    
    return _blurView;
}

- (UIVisualEffectView *)vibrancyView {
    if (!_vibrancyView) {
        UIVibrancyEffect *vibrancyEffect = (self.vibrancyEnabled ? [UIVibrancyEffect effectForBlurEffect:(UIBlurEffect *)self.blurView.effect] : nil);
        
        _vibrancyView = [[UIVisualEffectView alloc] initWithEffect:vibrancyEffect];
        
        [self.blurView.contentView addSubview:_vibrancyView];
    }
    
    return _vibrancyView;
}

- (UIView *)contentView {
    if (_contentView == nil) {
        _contentView = [[UIView alloc] init];
        [self.vibrancyView.contentView addSubview:_contentView];
        
        if (self.effectiveIndicatorView != nil) {
            [self.contentView addSubview:self.effectiveIndicatorView];
        }
    }
    
    return _contentView;
}

- (UILabel *)textLabel {
    if (!_textLabel) {
        _textLabel = [[UILabel alloc] init];
        _textLabel.backgroundColor = [UIColor clearColor];
        _textLabel.textColor = (self.style == JGProgressHUDStyleDark ? [UIColor whiteColor] : [UIColor blackColor]);
        _textLabel.textAlignment = NSTextAlignmentCenter;
#if TARGET_OS_TV
        CGFloat fontSize = 20.0;
#else
        CGFloat fontSize = 17.0;
#endif
        if (self.thick) {
            fontSize *= 1.3;
        }
        
        _textLabel.font = [UIFont boldSystemFontOfSize:fontSize];
        _textLabel.numberOfLines = 0;
        [_textLabel addObserver:self forKeyPath:@"attributedText" options:(NSKeyValueObservingOptions)kNilOptions context:NULL];
        [_textLabel addObserver:self forKeyPath:@"text" options:(NSKeyValueObservingOptions)kNilOptions context:NULL];
        [_textLabel addObserver:self forKeyPath:@"font" options:(NSKeyValueObservingOptions)kNilOptions context:NULL];
        _textLabel.isAccessibilityElement = YES;
        
        [self.contentView addSubview:_textLabel];
    }
    
    return _textLabel;
}

- (UILabel *)detailTextLabel {
    if (!_detailTextLabel) {
        _detailTextLabel = [[UILabel alloc] init];
        _detailTextLabel.backgroundColor = [UIColor clearColor];
        _detailTextLabel.textColor = (self.style == JGProgressHUDStyleDark ? [UIColor whiteColor] : [UIColor blackColor]);
        _detailTextLabel.textAlignment = NSTextAlignmentCenter;
#if TARGET_OS_TV
        CGFloat fontSize = 17.0;
#else
        CGFloat fontSize = 15.0;
#endif
        if (self.thick) {
            fontSize *= 1.3;
        }
        
        _detailTextLabel.font = [UIFont systemFontOfSize:fontSize];
        _detailTextLabel.numberOfLines = 0;
        [_detailTextLabel addObserver:self forKeyPath:@"attributedText" options:(NSKeyValueObservingOptions)kNilOptions context:NULL];
        [_detailTextLabel addObserver:self forKeyPath:@"text" options:(NSKeyValueObservingOptions)kNilOptions context:NULL];
        [_detailTextLabel addObserver:self forKeyPath:@"font" options:(NSKeyValueObservingOptions)kNilOptions context:NULL];
        _detailTextLabel.isAccessibilityElement = YES;
        
        [self.contentView addSubview:_detailTextLabel];
    }
    
    return _detailTextLabel;
}

- (JGProgressHUDAnimation *)animation {
    if (!_animation) {
        self.animation = [JGProgressHUDFadeAnimation animation];
    }
    
    return _animation;
}

#pragma mark - Setters

- (void)setStyle:(JGProgressHUDStyle)style {
    if (self.style == style) {
        return;
    }
    
    _style = style;
    
    // Update indicator
    [self.effectiveIndicatorView setUpForHUDStyle:self.style vibrancyEnabled:self.vibrancyEnabled];
    
    // Update labels
    self.textLabel.textColor = (self.style == JGProgressHUDStyleDark ? [UIColor whiteColor] : [UIColor blackColor]);
    self.detailTextLabel.textColor = (self.style == JGProgressHUDStyleDark ? [UIColor whiteColor] : [UIColor blackColor]);
    
    // Update blur effect
    UIBlurEffectStyle effect;
    
    if (self.style == JGProgressHUDStyleDark) {
        effect = UIBlurEffectStyleDark;
    }
    else if (self.style == JGProgressHUDStyleLight) {
        effect = UIBlurEffectStyleLight;
    }
    else {
        effect = UIBlurEffectStyleExtraLight;
    }
    
    UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:effect];
    self.blurView.effect = blurEffect;
    
    // Update vibrancy effect
    UIVibrancyEffect *vibrancyEffect = (self.vibrancyEnabled ? [UIVibrancyEffect effectForBlurEffect:(UIBlurEffect *)self.blurView.effect] : nil);
    self.vibrancyView.effect = vibrancyEffect;
}

#if TARGET_OS_TV
- (void)setWantsFocus:(BOOL)wantsFocus {
    if (self.wantsFocus == wantsFocus) {
        return;
    }
    
    _wantsFocus = wantsFocus;
    
    self.userInteractionEnabled = self.wantsFocus;
    
    [self.targetView setNeedsFocusUpdate];
}
#endif

- (void)setCornerRadius:(CGFloat)cornerRadius {
    if (fequal(self.cornerRadius, cornerRadius)) {
        return;
    }
    
    _cornerRadius = cornerRadius;
    
    [self applyCornerRadius];
}

- (void)setShadow:(JGProgressHUDShadow *)shadow {
    if (shadow == self.shadow) {
        return;
    }
    
    _shadow = shadow;
    
    [self updateShadowViewMask];
    
    if (_shadow != nil) {
        _shadowView.layer.shadowColor = _shadow.color.CGColor;
        _shadowView.layer.shadowOffset = _shadow.offset;
        _shadowView.layer.shadowRadius = _shadow.radius;
        
        _shadowView.alpha = _shadow.opacity;
    }
    else {
        _shadowView.layer.shadowOffset = CGSizeZero;
        _shadowView.layer.shadowRadius = 0.0;
        
        _shadowView.alpha = 0.0;
    }
}

- (void)setAnimation:(JGProgressHUDAnimation *)animation {
    if (_animation == animation) {
        return;
    }
    
    _animation.progressHUD = nil;
    
    _animation = animation;
    
    _animation.progressHUD = self;
}

- (void)setParallaxMode:(JGProgressHUDParallaxMode)parallaxMode {
    if (self.parallaxMode == parallaxMode) {
        return;
    }
    
    _parallaxMode = parallaxMode;
    
    [self updateMotionOnHUDView];
}

- (void)setPosition:(JGProgressHUDPosition)position {
    if (self.position == position) {
        return;
    }
    
    _position = position;
    [self layoutHUD];
}

- (void)setSquare:(BOOL)square {
    if (self.square == square) {
        return;
    }
    
    _square = square;
    
    [self layoutHUD];
}

- (void)setThick:(BOOL)thick {
    if (self.thick == thick) {
        return;
    }
    
    _thick = thick;
    
    if (self.thick) {
        self.indicatorView.transform = CGAffineTransformMakeScale(1.5, 1.5);
    }
    else {
        self.indicatorView.transform = CGAffineTransformIdentity;
    }
    
#if TARGET_OS_TV
    CGFloat fontSize = 20.0;
#else
    CGFloat fontSize = 17.0;
#endif
    if (self.thick) {
        fontSize *= 1.3;
    }
    
    _textLabel.font = [UIFont boldSystemFontOfSize:fontSize];
#if TARGET_OS_TV
    fontSize = 17.0;
#else
    fontSize = 15.0;
#endif
    if (self.thick) {
        fontSize *= 1.3;
    }
    
    _detailTextLabel.font = [UIFont systemFontOfSize:fontSize];
    
    [self layoutHUD];
}

- (void)setVibrancyEnabled:(BOOL)vibrancyEnabled {
    if (vibrancyEnabled == self.vibrancyEnabled) {
        return;
    }
    
    _vibrancyEnabled = vibrancyEnabled;
    
    UIVibrancyEffect *vibrancyEffect = (self.vibrancyEnabled ? [UIVibrancyEffect effectForBlurEffect:(UIBlurEffect *)self.blurView.effect] : nil);
    
    self.vibrancyView.effect = vibrancyEffect;
    
    [self.effectiveIndicatorView setUpForHUDStyle:self.style vibrancyEnabled:self.vibrancyEnabled];
}

- (void)setIndicatorView:(JGProgressHUDIndicatorView *)indicatorView {
    if (self.indicatorView == indicatorView) {
        return;
    }
    
    _indicatorView = indicatorView;
    
    if (_transitioning) {
        _effectiveIndicatorViewNeedsUpdate = YES;
        return;
    }
    else {
        self.effectiveIndicatorView = self.indicatorView;
    }
}

- (void)setEffectiveIndicatorView:(JGProgressHUDIndicatorView * _Nonnull)effectiveIndicatorView {
    if (self.effectiveIndicatorView == effectiveIndicatorView) {
        return;
    }
    
    [UIView performWithoutAnimation:^{
        [self->_effectiveIndicatorView removeFromSuperview];
        self->_effectiveIndicatorView = effectiveIndicatorView;
        
        if (self.indicatorView != nil) {
            [self.effectiveIndicatorView setUpForHUDStyle:self.style vibrancyEnabled:self.vibrancyEnabled];
            
            if (self.thick) {
                self.effectiveIndicatorView.transform = CGAffineTransformMakeScale(1.5, 1.5);
            }
            else {
                self.effectiveIndicatorView.transform = CGAffineTransformIdentity;
            }
            
            [self.contentView addSubview:self.effectiveIndicatorView];
        }
    }];
    
    [self layoutHUD];
}

- (void)layoutMarginsDidChange {
    [super layoutMarginsDidChange];
    
    [self layoutHUD];
}

- (void)setContentInsets:(UIEdgeInsets)contentInsets {
    if (UIEdgeInsetsEqualToEdgeInsets(self.contentInsets, contentInsets)) {
        return;
    }
    
    _contentInsets = contentInsets;
    
    [self layoutHUD];
}

- (void)setProgress:(float)progress {
    [self setProgress:progress animated:NO];
}

- (void)setProgress:(float)progress animated:(BOOL)animated {
    if (fequal(self.progress, progress)) {
        return;
    }
    
    _progress = progress;
    
    [self.effectiveIndicatorView setProgress:progress animated:animated];
}

#pragma mark - Overrides

- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
    [super traitCollectionDidChange:previousTraitCollection];
    
    if (@available(iOS 12.0, tvOS 10.0, *)) {
        if (_automaticStyle) {
            self.style = JGProgressHUDStyleFromUIUserInterfaceStyle(self.traitCollection.userInterfaceStyle);
        }
    }
}

#if TARGET_OS_IOS
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if (self.interactionType == JGProgressHUDInteractionTypeBlockNoTouches) {
        return nil;
    }
    else {
        UIView *view = [super hitTest:point withEvent:event];
        
        if (self.interactionType == JGProgressHUDInteractionTypeBlockAllTouches) {
            return view;
        }
        else if (self.interactionType == JGProgressHUDInteractionTypeBlockTouchesOnHUDView && view != self) {
            return view;
        }
        
        return nil;
    }
}
#elif TARGET_OS_TV
- (NSArray<id<UIFocusEnvironment>> *)preferredFocusEnvironments {
    return @[self];
}

- (UIView *)preferredFocusedView {
    return nil;
}

- (BOOL)canBecomeFocused {
    return self.wantsFocus;
}
#endif

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if (object == _textLabel || object == _detailTextLabel) {
        [self layoutHUD];
    }
    else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

- (void)dealloc {
    [self removeObservers];
    
    [_textLabel removeObserver:self forKeyPath:@"attributedText"];
    [_textLabel removeObserver:self forKeyPath:@"text"];
    [_textLabel removeObserver:self forKeyPath:@"font"];
    
    [_detailTextLabel removeObserver:self forKeyPath:@"attributedText"];
    [_detailTextLabel removeObserver:self forKeyPath:@"text"];
    [_detailTextLabel removeObserver:self forKeyPath:@"font"];
}

- (void)removeObservers {
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilityReduceMotionStatusDidChangeNotification object:nil];
    
#if TARGET_OS_IOS
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillChangeFrameNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardDidChangeFrameNotification object:nil];
#endif
}

@end

@implementation JGProgressHUD (HUDManagement)

+ (NSArray *)allProgressHUDsInView:(UIView *)view {
    NSMutableArray *HUDs = [NSMutableArray array];
    
    for (UIView *v in view.subviews) {
        if ([v isKindOfClass:[JGProgressHUD class]]) {
            [HUDs addObject:v];
        }
    }
    
    return HUDs.copy;
}

+ (NSMutableArray *)_allProgressHUDsInViewHierarchy:(UIView *)view {
    NSMutableArray *HUDs = [NSMutableArray array];
    
    for (UIView *v in view.subviews) {
        if ([v isKindOfClass:[JGProgressHUD class]]) {
            [HUDs addObject:v];
        }
        else {
            [HUDs addObjectsFromArray:[self _allProgressHUDsInViewHierarchy:v]];
        }
    }
    
    return HUDs;
}

+ (NSArray *)allProgressHUDsInViewHierarchy:(UIView *)view {
    return [self _allProgressHUDsInViewHierarchy:view].copy;
}

@end


================================================
FILE: JGProgressHUD/JGProgressHUDAnimation.h
================================================
//
//  JGProgressHUDAnimation.h
//  JGProgressHUD
//
//  Created by Jonas Gessner on 20.7.14.
//  Copyright (c) 2014 Jonas Gessner. All rights reserved.
//  

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@class JGProgressHUD;

/**
 You may subclass this class to create a custom progress indicator view.
 */
@interface JGProgressHUDAnimation : NSObject

/** Convenience initializer. */
+ (instancetype __nonnull)animation;

/** The HUD using this animation. */
@property (nonatomic, weak, readonly, nullable) JGProgressHUD *progressHUD;

/**
 The @c progressHUD is hidden from screen with @c alpha = 1 and @c hidden = @c YES. Ideally, you should prepare the HUD for presentation, then set @c hidden to @c NO on the @c progressHUD and then perform the animation.
  @post Call @c animationFinished.
 */
- (void)show NS_REQUIRES_SUPER;

/**
 The @c progressHUD wis visible on screen with @c alpha = 1 and @c hidden = @c NO. You should only perform the animation in this method, the @c progressHUD itself will take care of hiding itself and removing itself from superview.
 @post Call @c animationFinished.
 */
- (void)hide NS_REQUIRES_SUPER;

/**
 @pre This method should only be called at the end of a @c show or @c hide animaiton.
 @attention ALWAYS call this method after completing a @c show or @c hide animation.
 */
- (void)animationFinished NS_REQUIRES_SUPER;

@end


================================================
FILE: JGProgressHUD/JGProgressHUDAnimation.m
================================================
//
//  JGProgressHUDAnimation.m
//  JGProgressHUD
//
//  Created by Jonas Gessner on 20.7.14.
//  Copyright (c) 2014 Jonas Gessner. All rights reserved.
//  

#import "JGProgressHUDAnimation.h"
#import "JGProgressHUD.h"

@interface JGProgressHUD (Private)

- (void)animationDidFinish:(BOOL)presenting;

@end

@interface JGProgressHUDAnimation () {
    BOOL _presenting;
}

@property (nonatomic, weak) JGProgressHUD *progressHUD;

@end

@implementation JGProgressHUDAnimation

#pragma mark - Initializers

+ (instancetype)animation {
    return [[self alloc] init];
}

#pragma mark - Public methods

- (void)show {
    _presenting = YES;
}

- (void)hide {
    _presenting = NO;
}

- (void)animationFinished {
    [self.progressHUD animationDidFinish:_presenting];
}

@end


================================================
FILE: JGProgressHUD/JGProgressHUDErrorIndicatorView.h
================================================
//
//  JGProgressHUDErrorIndicatorView.h
//  JGProgressHUD
//
//  Created by Jonas Gessner on 19.08.14.
//  Copyright (c) 2014 Jonas Gessner. All rights reserved.
//

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wquoted-include-in-framework-header"
#import "JGProgressHUDImageIndicatorView.h"
#pragma clang diagnostic pop

/**
 An image indicator showing a cross, representing a failed operation.
 */
@interface JGProgressHUDErrorIndicatorView : JGProgressHUDImageIndicatorView

/**
 Default initializer for this class.
 */
- (instancetype __nonnull)init;

@end


================================================
FILE: JGProgressHUD/JGProgressHUDErrorIndicatorView.m
================================================
//
//  JGProgressHUDErrorIndicatorView.m
//  JGProgressHUD
//
//  Created by Jonas Gessner on 19.08.14.
//  Copyright (c) 2014 Jonas Gessner. All rights reserved.
//

#import "JGProgressHUDErrorIndicatorView.h"
#import "JGProgressHUD.h"

static UIBezierPath *errorBezierPath() {
    static UIBezierPath *path;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        path = [UIBezierPath bezierPath];
        [path moveToPoint:CGPointMake(3, 3)];
        [path addLineToPoint:CGPointMake(30, 30)];
        [path moveToPoint:CGPointMake(30, 3)];
        [path addLineToPoint:CGPointMake(3, 30)];
        
        [path setLineWidth:3];
        [path setLineJoinStyle:kCGLineJoinRound];
        [path setLineCapStyle:kCGLineCapRound];
    });
    
    return path;
}

@implementation JGProgressHUDErrorIndicatorView

- (instancetype)initWithContentView:(UIView *__unused)contentView {
    UIBezierPath *path = errorBezierPath();

    UIGraphicsBeginImageContextWithOptions(CGSizeMake(33, 33), NO, 0.0);
    [[UIColor blackColor] setStroke];
    [path stroke];
    
    UIImage *img = [UIGraphicsGetImageFromCurrentImageContext() imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
    
    UIGraphicsEndImageContext();
    
    self = [super initWithImage:img];

    return self;
}

- (instancetype)init {
    return [self initWithContentView:nil];
}

- (void)updateAccessibility {
    self.accessibilityLabel = NSLocalizedString(@"Error",);
}

@end


================================================
FILE: JGProgressHUD/JGProgressHUDFadeAnimation.h
================================================
//
//  JGProgressHUDFadeAnimation.h
//  JGProgressHUD
//
//  Created by Jonas Gessner on 20.7.14.
//  Copyright (c) 2014 Jonas Gessner. All rights reserved.
//

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wquoted-include-in-framework-header"
#import "JGProgressHUDAnimation.h"
#pragma clang diagnostic pop

/**
 A simple fade animation that fades the HUD from alpha @c 0.0 to alpha @c 1.0.
 */
@interface JGProgressHUDFadeAnimation : JGProgressHUDAnimation

/**
 Duration of the animation.
 
 @b Default: 0.4.
 */
@property (nonatomic, assign) NSTimeInterval duration;

/**
 Animation options
 
 @b Default: UIViewAnimationOptionCurveEaseInOut.
 */
@property (nonatomic, assign) UIViewAnimationOptions animationOptions;

@end


================================================
FILE: JGProgressHUD/JGProgressHUDFadeAnimation.m
================================================
//
//  JGProgressHUDFadeAnimation.m
//  JGProgressHUD
//
//  Created by Jonas Gessner on 20.7.14.
//  Copyright (c) 2014 Jonas Gessner. All rights reserved.
//  

#import "JGProgressHUDFadeAnimation.h"
#import "JGProgressHUD.h"

@implementation JGProgressHUDFadeAnimation

#pragma mark - Initializers

- (instancetype)init {
    self = [super init];
    if (self) {
        self.duration = 0.4;
        self.animationOptions = UIViewAnimationOptionCurveEaseInOut;
    }
    return self;
}

- (void)setAnimationOptions:(UIViewAnimationOptions)animationOptions {
    _animationOptions = (animationOptions | UIViewAnimationOptionBeginFromCurrentState);
}

#pragma mark - Showing

- (void)show {
    [super show];
    
    self.progressHUD.alpha = 0.0;
    self.progressHUD.hidden = NO;
    
    [UIView animateWithDuration:self.duration delay:0.0 options:self.animationOptions animations:^{
        self.progressHUD.alpha = 1.0;
    } completion:^(BOOL __unused finished) {
        [self animationFinished];
    }];
}

#pragma mark - Hiding

- (void)hide {
    [super hide];
    
    [UIView animateWithDuration:self.duration delay:0.0 options:self.animationOptions animations:^{
        self.progressHUD.alpha = 0.0;
    } completion:^(BOOL __unused finished) {
        [self animationFinished];
    }];
}

@end


================================================
FILE: JGProgressHUD/JGProgressHUDFadeZoomAnimation.h
================================================
//
//  JGProgressHUDFadeZoomAnimation.h
//  JGProgressHUD
//
//  Created by Jonas Gessner on 20.7.14.
//  Copyright (c) 2014 Jonas Gessner. All rights reserved.
//  

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wquoted-include-in-framework-header"
#import "JGProgressHUDAnimation.h"
#pragma clang diagnostic pop

/**
 An animation that fades in the HUD and expands the HUD from scale @c (0, 0) to a customizable scale, and finally to scale @c (1, 1), creating a bouncing effect.
 */
@interface JGProgressHUDFadeZoomAnimation : JGProgressHUDAnimation

/**
 Duration of the animation from or to the shrinked state.
 
 @b Default: 0.2.
 */
@property (nonatomic, assign) NSTimeInterval shrinkAnimationDuaration;

/**
 Duration of the animation from or to the expanded state.
 
 @b Default: 0.1.
 */
@property (nonatomic, assign) NSTimeInterval expandAnimationDuaration;

/**
 The scale to apply to the HUD when expanding.
 
 @b Default: (1.1, 1.1).
 */
@property (nonatomic, assign) CGSize expandScale;

@end


================================================
FILE: JGProgressHUD/JGProgressHUDFadeZoomAnimation.m
================================================
//
//  JGProgressHUDFadeZoomAnimation.m
//  JGProgressHUD
//
//  Created by Jonas Gessner on 20.7.14.
//  Copyright (c) 2014 Jonas Gessner. All rights reserved.
//

#import "JGProgressHUDFadeZoomAnimation.h"
#import "JGProgressHUD.h"

@implementation JGProgressHUDFadeZoomAnimation

#pragma mark - Initializers

- (instancetype)init {
    self = [super init];
    if (self) {
        self.shrinkAnimationDuaration = 0.2;
        self.expandAnimationDuaration = 0.1;
        self.expandScale = CGSizeMake(1.1, 1.1);
    }
    return self;
}

#pragma mark - Showing

- (void)show {
    [super show];
    
    self.progressHUD.alpha = 0.0;
    self.progressHUD.HUDView.transform = CGAffineTransformMakeScale(0.1, 0.1);
    
    NSTimeInterval totalDuration = self.expandAnimationDuaration+self.shrinkAnimationDuaration;
    
    self.progressHUD.hidden = NO;
    
    [UIView animateWithDuration:totalDuration delay:0.0 options:(UIViewAnimationOptions)(UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut) animations:^{
        self.progressHUD.alpha = 1.0;
    } completion:nil];
    
    [UIView animateWithDuration:self.shrinkAnimationDuaration delay:0.0 options:(UIViewAnimationOptions)(UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionBeginFromCurrentState) animations:^{
        self.progressHUD.HUDView.transform = CGAffineTransformMakeScale(self.expandScale.width, self.expandScale.height);
    } completion:^(BOOL __unused _finished) {
        [UIView animateWithDuration:self.expandAnimationDuaration delay:0.0 options:(UIViewAnimationOptions)(UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState) animations:^{
            self.progressHUD.HUDView.transform = CGAffineTransformIdentity;
        } completion:^(BOOL __unused __finished) {
            [self animationFinished];
        }];
    }];
}

#pragma mark - Hiding

- (void)hide {
    [super hide];
    
    NSTimeInterval totalDuration = self.expandAnimationDuaration+self.shrinkAnimationDuaration;
    
    [UIView animateWithDuration:totalDuration delay:0.0 options:(UIViewAnimationOptions)(UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut) animations:^{
        self.progressHUD.alpha = 0.0;
    } completion:nil];
    
    [UIView animateWithDuration:self.expandAnimationDuaration delay:0.0 options:(UIViewAnimationOptions)(UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionBeginFromCurrentState) animations:^{
        self.progressHUD.HUDView.transform = CGAffineTransformMakeScale(self.expandScale.width, self.expandScale.height);
    } completion:^(BOOL __unused _finished) {
        [UIView animateWithDuration:self.shrinkAnimationDuaration delay:0.0 options:(UIViewAnimationOptions)(UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState) animations:^{
            self.progressHUD.HUDView.transform = CGAffineTransformMakeScale(0.1, 0.1);
        } completion:^(BOOL __unused __finished) {
            self.progressHUD.HUDView.transform = CGAffineTransformIdentity;
            
            [self animationFinished];
        }];
    }];
}

@end


================================================
FILE: JGProgressHUD/JGProgressHUDImageIndicatorView.h
================================================
//
//  JGProgressHUDImageIndicatorView.h
//  JGProgressHUD
//
//  Created by Jonas Gessner on 05.08.15.
//  Copyright (c) 2015 Jonas Gessner. All rights reserved.
//

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wquoted-include-in-framework-header"
#import "JGProgressHUDIndicatorView.h"
#pragma clang diagnostic pop

/**
 An indicator for displaying custom images. Supports animated images.
 
 You may subclass this class to create a custom image indicator view.
 */
@interface JGProgressHUDImageIndicatorView : JGProgressHUDIndicatorView

/**
 Initializes the indicator view with an UIImageView showing the @c image.
 
 @param image The image to show in the indicator view.
 */
- (instancetype __nonnull)initWithImage:(UIImage *__nonnull)image;

@end


================================================
FILE: JGProgressHUD/JGProgressHUDImageIndicatorView.m
================================================
//
//  JGProgressHUDImageIndicatorView.m
//  JGProgressHUD
//
//  Created by Jonas Gessner on 05.08.15.
//  Copyright (c) 2015 Jonas Gessner. All rights reserved.
//

#import "JGProgressHUDImageIndicatorView.h"

@implementation JGProgressHUDImageIndicatorView {
    BOOL _customizedTintColor;
}

- (instancetype)initWithImage:(UIImage *)image {
    UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
    
    self = [super initWithContentView:imageView];
    
    return self;
}

- (void)setUpForHUDStyle:(JGProgressHUDStyle)style vibrancyEnabled:(BOOL)vibrancyEnabled {
    [super setUpForHUDStyle:style vibrancyEnabled:vibrancyEnabled];

    if (!_customizedTintColor) {
        if (style == JGProgressHUDStyleDark) {
            self.tintColor = [UIColor whiteColor];
        }
        else {
            self.tintColor = [UIColor blackColor];
        }
        _customizedTintColor = NO;
    }
}

- (void)setTintColor:(UIColor *)tintColor {
    [super setTintColor:tintColor];
    _customizedTintColor = YES;
}

@end


================================================
FILE: JGProgressHUD/JGProgressHUDIndeterminateIndicatorView.h
================================================
//
//  JGProgressHUDIndeterminateIndicatorView.h
//  JGProgressHUD
//
//  Created by Jonas Gessner on 19.07.14.
//  Copyright (c) 2014 Jonas Gessner. All rights reserved.
//

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wquoted-include-in-framework-header"
#import "JGProgressHUDIndicatorView.h"
#pragma clang diagnostic pop

/**
 An indeterminate progress indicator showing a @c UIActivityIndicatorView.
 */
@interface JGProgressHUDIndeterminateIndicatorView : JGProgressHUDIndicatorView

/**
 Set the color of the activity indicator view.
 @param color The color to apply to the activity indicator view.
 */
- (void)setColor:(UIColor *__nonnull)color;

@end


================================================
FILE: JGProgressHUD/JGProgressHUDIndeterminateIndicatorView.m
================================================
//
//  JGProgressHUDIndeterminateIndicatorView.m
//  JGProgressHUD
//
//  Created by Jonas Gessner on 19.07.14.
//  Copyright (c) 2014 Jonas Gessner. All rights reserved.
//

#import "JGProgressHUDIndeterminateIndicatorView.h"

#ifndef __IPHONE_13_0
#define __IPHONE_13_0    130000
#endif

@implementation JGProgressHUDIndeterminateIndicatorView

- (instancetype)init {
    UIActivityIndicatorView *activityIndicatorView;
    
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0
    if (@available(iOS 13, tvOS 13, *)) {
        activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleLarge];
    }
    else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
        activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
#pragma clang diagnostic pop
    }
#else
    activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
#endif
    
    [activityIndicatorView startAnimating];
    
    self = [super initWithContentView:activityIndicatorView];
    return self;
}

- (instancetype)initWithHUDStyle:(JGProgressHUDStyle)style {
    return [self init];
}

- (void)setUpForHUDStyle:(JGProgressHUDStyle)style vibrancyEnabled:(BOOL)vibrancyEnabled {
    [super setUpForHUDStyle:style vibrancyEnabled:vibrancyEnabled];
    
    if (style != JGProgressHUDStyleDark) {
        self.color = [UIColor blackColor];
    }
    else {
        self.color = [UIColor whiteColor];
    }
}

- (void)setColor:(UIColor *)color {
    [(UIActivityIndicatorView *)self.contentView setColor:color];
}

- (void)updateAccessibility {
    self.accessibilityLabel = NSLocalizedString(@"Indeterminate progress",);
}

@end


================================================
FILE: JGProgressHUD/JGProgressHUDIndicatorView.h
================================================
//
//  JGProgressHUDIndicatorView.h
//  JGProgressHUD
//
//  Created by Jonas Gessner on 20.7.14.
//  Copyright (c) 2014 Jonas Gessner. All rights reserved.
//  

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wquoted-include-in-framework-header"
#import "JGProgressHUD-Defines.h"
#pragma clang diagnostic pop

/** You may subclass this class to create a custom progress indicator view. */
@interface JGProgressHUDIndicatorView : UIView

/**
 Designated initializer for this class.
 
 @param contentView The content view to place on the container view (the container is the JGProgressHUDIndicatorView).
 */
- (instancetype __nonnull)initWithContentView:(UIView *__nullable)contentView;

/** Use this method to set up the indicator view to fit the HUD style and vibrancy setting. This method is called by @c JGProgressHUD when the indicator view is added to the HUD and when the HUD's @c vibrancyEnabled property changes. This method may be called multiple times with different values. The default implementation does nothing. */
- (void)setUpForHUDStyle:(JGProgressHUDStyle)style vibrancyEnabled:(BOOL)vibrancyEnabled;

/** Ranges from 0.0 to 1.0. */
@property (nonatomic, assign) float progress;

/**
 Adjusts the current progress shown by the receiver, optionally animating the change.
 
 The current progress is represented by a floating-point value between 0.0 and 1.0, inclusive, where 1.0 indicates the completion of the task. The default value is 0.0. Values less than 0.0 and greater than 1.0 are pinned to those limits.
 
 @param progress The new progress value.
 @param animated YES if the change should be animated, NO if the change should happen immediately.
 */
- (void)setProgress:(float)progress animated:(BOOL)animated;

/**
 The content view which displays the progress.
 */
@property (nonatomic, strong, readonly, nullable) UIView *contentView;

/** Schedules an accessibility update on the next run loop. */
- (void)setNeedsAccessibilityUpdate;

/**
 Runs @c updateAccessibility immediately if an accessibility update has been scheduled (through @c setNeedsAccessibilityUpdate) but has not executed yet.
 */
- (void)updateAccessibilityIfNeeded;

/**
 Override to set custom accessibility properties. This method gets called once when initializing the view and after calling @c setNeedsAccessibilityUpdate.
 */
- (void)updateAccessibility;

@end


================================================
FILE: JGProgressHUD/JGProgressHUDIndicatorView.m
================================================
//
//  JGProgressHUDIndicatorView.m
//  JGProgressHUD
//
//  Created by Jonas Gessner on 20.7.14.
//  Copyright (c) 2014 Jonas Gessner. All rights reserved.
//  

#import "JGProgressHUDIndicatorView.h"
#import "JGProgressHUD.h"

@interface JGProgressHUDIndicatorView () {
    BOOL _accessibilityUpdateScheduled;
}

+ (void)runBlock:(void (^)(void))block;

@end

static void runOnNextRunLoop(void (^block)(void)) {
    [[NSRunLoop currentRunLoop] performSelector:@selector(runBlock:) target:[JGProgressHUDIndicatorView class] argument:(id)block order:0 modes:@[NSRunLoopCommonModes]];
}

@implementation JGProgressHUDIndicatorView

#pragma mark - Initializers

- (instancetype)initWithFrame:(CGRect __unused)frame {
    return [self init];
}

- (instancetype)init {
    return [self initWithContentView:nil];
}

- (instancetype)initWithContentView:(UIView *)contentView {
    self = [super initWithFrame:(contentView ? contentView.frame : CGRectMake(0.0, 0.0, 50.0, 50.0))];
    if (self) {
        self.opaque = NO;
        self.backgroundColor = [UIColor clearColor];
        
        self.isAccessibilityElement = YES;
        [self setNeedsAccessibilityUpdate];
        
        if (contentView) {
            _contentView = contentView;
            
            [self addSubview:self.contentView];
        }
    }
    return self;
}

#pragma mark - Setup

- (void)setUpForHUDStyle:(JGProgressHUDStyle)style vibrancyEnabled:(BOOL)vibrancyEnabled {}

#pragma mark - Accessibility

+ (void)runBlock:(void (^)(void))block {
    if (block != nil) {
        block();
    }
}

- (void)setNeedsAccessibilityUpdate {
    if (!_accessibilityUpdateScheduled) {
        _accessibilityUpdateScheduled = YES;
        
        runOnNextRunLoop(^{
            [self updateAccessibilityIfNeeded];
        });
    }
}

- (void)updateAccessibilityIfNeeded {
    if (_accessibilityUpdateScheduled) {
        [self updateAccessibility];
        _accessibilityUpdateScheduled = NO;
    }
}

- (void)updateAccessibility {
    self.accessibilityLabel = [NSLocalizedString(@"Loading",) stringByAppendingFormat:@" %.f %%", self.progress];
}

#pragma mark - Getters & Setters

- (void)setProgress:(float)progress {
    [self setProgress:progress animated:NO];
}

- (void)setProgress:(float)progress animated:(__unused BOOL)animated {
    if (fequal(self.progress, progress)) {
        return;
    }
    
    _progress = progress;
    
    [self setNeedsAccessibilityUpdate];
}

@end


================================================
FILE: JGProgressHUD/JGProgressHUDPieIndicatorView.h
================================================
//
//  JGProgressHUDPieIndicatorView.h
//  JGProgressHUD
//
//  Created by Jonas Gessner on 19.07.14.
//  Copyright (c) 2014 Jonas Gessner. All rights reserved.
//

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wquoted-include-in-framework-header"
#import "JGProgressHUDIndicatorView.h"
#pragma clang diagnostic pop

/**
 A pie shaped determinate progress indicator.
 */
@interface JGProgressHUDPieIndicatorView : JGProgressHUDIndicatorView

/**
 Tint color of the Pie.
 @attention Custom values need to be set after assigning the indicator view to @c JGProgressHUD's @c indicatorView property.
 
 @b Default: White for JGProgressHUDStyleDark, otherwise black.
 */
@property (nonatomic, strong, nonnull) UIColor *color;

/**
 The background fill color inside the pie.
 @attention Custom values need to be set after assigning the indicator view to @c JGProgressHUD's @c indicatorView property.
 
 @b Default: Dark gray for JGProgressHUDStyleDark, otherwise light gray.
 */
@property (nonatomic, strong, nonnull) UIColor *fillColor;

@end


================================================
FILE: JGProgressHUD/JGProgressHUDPieIndicatorView.m
================================================
//
//  JGProgressHUDPieIndicatorView.m
//  JGProgressHUD
//
//  Created by Jonas Gessner on 19.07.14.
//  Copyright (c) 2014 Jonas Gessner. All rights reserved.
//

#import "JGProgressHUDPieIndicatorView.h"

@interface JGProgressHUDPieIndicatorLayer : CALayer

@property (nonatomic, assign) float progress;

@property (nonatomic, weak) UIColor *color;

@property (nonatomic, weak) UIColor *fillColor;

@end

@implementation JGProgressHUDPieIndicatorLayer

@dynamic progress, color, fillColor;

+ (BOOL)needsDisplayForKey:(NSString *)key {
    return ([key isEqualToString:@"progress"] || [key isEqualToString:@"color"] || [key isEqualToString:@"fillColor"] || [super needsDisplayForKey:key]);
}

- (id <CAAction>)actionForKey:(NSString *)key {
    if ([key isEqualToString:@"progress"]) {
        CABasicAnimation *progressAnimation = [CABasicAnimation animation];
        progressAnimation.fromValue = [self.presentationLayer valueForKey:key];
        
        progressAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
        
        return progressAnimation;
    }
    
    return [super actionForKey:key];
}

- (void)drawInContext:(CGContextRef)ctx {
    UIGraphicsPushContext(ctx);
    
    CGRect rect = self.bounds;
    
    CGPoint center = CGPointMake(rect.origin.x + (CGFloat)floor(rect.size.width/2.0), rect.origin.y + (CGFloat)floor(rect.size.height/2.0));
    CGFloat lineWidth = 2.0;
    CGFloat radius = (CGFloat)floor(MIN(rect.size.width, rect.size.height)/2.0)-lineWidth;
    
    UIBezierPath *borderPath = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:0.0 endAngle:2.0*(CGFloat)M_PI clockwise:NO];
    
    [borderPath setLineWidth:lineWidth];
    
    if (self.fillColor) {
        [self.fillColor setFill];
        
        [borderPath fill];
    }
    
    [self.color set];
    
    [borderPath stroke];
    
    if (self.progress > 0.0) {
        UIBezierPath *processPath = [UIBezierPath bezierPath];
        
        [processPath setLineWidth:radius];
        
        CGFloat startAngle = -((CGFloat)M_PI/2.0);
        CGFloat endAngle = startAngle + 2.0 * (CGFloat)M_PI * self.progress;
        
        [processPath addArcWithCenter:center radius:radius/2.0 startAngle:startAngle endAngle:endAngle clockwise:YES];
        
        [processPath stroke];
    }
    
    UIGraphicsPopContext();
}

@end


@implementation JGProgressHUDPieIndicatorView

#pragma mark - Initializers

- (instancetype)init {
    self = [super initWithContentView:nil];
    
    if (self) {
        self.layer.contentsScale = [UIScreen mainScreen].scale;
        [self.layer setNeedsDisplay];
        
        self.color = [UIColor clearColor];
        self.fillColor = [UIColor clearColor];
    }
    
    return self;
}

- (instancetype)initWithHUDStyle:(JGProgressHUDStyle)style {
    return [self init];
}

- (instancetype)initWithContentView:(UIView *)contentView {
    return [self init];
}

#pragma mark - Getters & Setters

- (void)setColor:(UIColor *)tintColor {
    if ([tintColor isEqual:self.color]) {
        return;
    }
    
    _color = tintColor;
    
    [(JGProgressHUDPieIndicatorLayer *)self.layer setColor:self.color];
}

- (void)setFillColor:(UIColor *)fillColor {
    if ([fillColor isEqual:self.fillColor]) {
        return;
    }
    
    _fillColor = fillColor;
    
    [(JGProgressHUDPieIndicatorLayer *)self.layer setFillColor:self.fillColor];
}

- (void)setProgress:(float)progress animated:(BOOL)animated {
    if (fequal(self.progress, progress)) {
        return;
    }
    
    [super setProgress:progress animated:animated];
    
    [CATransaction begin];
    [CATransaction setAnimationDuration:(animated ? 0.3 : 0.0)];
    
    [(JGProgressHUDPieIndicatorLayer *)self.layer setProgress:progress];
    
    [CATransaction commit];
}

#pragma mark - Overrides

- (void)setUpForHUDStyle:(JGProgressHUDStyle)style vibrancyEnabled:(BOOL)vibrancyEnabled {
    [super setUpForHUDStyle:style vibrancyEnabled:vibrancyEnabled];
    
    if (style == JGProgressHUDStyleDark) {
        self.color = [UIColor colorWithWhite:1.0 alpha:1.0];
        self.fillColor = [UIColor colorWithWhite:0.2 alpha:1.0];
    }
    else {
        self.color = [UIColor blackColor];
        if (style == JGProgressHUDStyleLight) {
            self.fillColor = [UIColor colorWithWhite:0.85 alpha:1.0];
        }
        else {
            self.fillColor = [UIColor colorWithWhite:0.9 alpha:1.0];
        }
    }
}

+ (Class)layerClass {
    return [JGProgressHUDPieIndicatorLayer class];
}

@end


================================================
FILE: JGProgressHUD/JGProgressHUDRingIndicatorView.h
================================================
//
//  JGProgressHUDRingIndicatorView.h
//  JGProgressHUD
//
//  Created by Jonas Gessner on 20.7.14.
//  Copyright (c) 2014 Jonas Gessner. All rights reserved.
//

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wquoted-include-in-framework-header"
#import "JGProgressHUDIndicatorView.h"
#pragma clang diagnostic pop

/**
 A ring shaped determinate progress indicator.
 */
@interface JGProgressHUDRingIndicatorView : JGProgressHUDIndicatorView

/**
 Background color of the ring.
 @attention Custom values need to be set after assigning the indicator view to @c JGProgressHUD's @c indicatorView property.
 
 @b Default: Black for JGProgressHUDStyleDark, light gray otherwise.
 */
@property (nonatomic, strong, nonnull) UIColor *ringBackgroundColor;

/**
 Progress color of the progress ring.
 @attention Custom values need to be set after assigning the indicator view to @c JGProgressHUD's @c indicatorView property.
 
 @b Default: White for JGProgressHUDStyleDark, otherwise black.
 */
@property (nonatomic, strong, nonnull) UIColor *ringColor;

/**
 Sets if the progress ring should have a rounded line cap.
 
 @b Default: NO.
 */
@property (nonatomic, assign) BOOL roundProgressLine;

/**
 Width of the ring.
 
 @b Default: 3.0.
 */
@property (nonatomic, assign) CGFloat ringWidth;

@end



================================================
FILE: JGProgressHUD/JGProgressHUDRingIndicatorView.m
================================================
//
//  JGProgressHUDRingIndicatorView.m
//  JGProgressHUD
//
//  Created by Jonas Gessner on 20.7.14.
//  Copyright (c) 2014 Jonas Gessner. All rights reserved.
//  

#import "JGProgressHUDRingIndicatorView.h"


@interface JGProgressHUDRingIndicatorLayer : CALayer

@property (nonatomic, assign) float progress;

@property (nonatomic, weak) UIColor *ringColor;
@property (nonatomic, weak) UIColor *ringBackgroundColor;

@property (nonatomic, assign) BOOL roundProgressLine;

@property (nonatomic, assign) CGFloat ringWidth;

@end

@implementation JGProgressHUDRingIndicatorLayer

@dynamic progress, ringBackgroundColor, ringColor, ringWidth, roundProgressLine;

+ (BOOL)needsDisplayForKey:(NSString *)key {
    return ([key isEqualToString:@"progress"] || [key isEqualToString:@"ringColor"] || [key isEqualToString:@"ringBackgroundColor"] || [key isEqualToString:@"roundProgressLine"] || [key isEqualToString:@"ringWidth"] || [super needsDisplayForKey:key]);
}

- (id <CAAction>)actionForKey:(NSString *)key {
    if ([key isEqualToString:@"progress"]) {
        CABasicAnimation *progressAnimation = [CABasicAnimation animation];
        progressAnimation.fromValue = [self.presentationLayer valueForKey:key];
        
        progressAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
        
        return progressAnimation;
    }
    
    return [super actionForKey:key];
}

- (void)drawInContext:(CGContextRef)ctx {
    UIGraphicsPushContext(ctx);
    
    CGRect rect = self.bounds;
    
    CGPoint center = CGPointMake(rect.origin.x + (CGFloat)floor(rect.size.width/2.0), rect.origin.y + (CGFloat)floor(rect.size.height/2.0));
    CGFloat lineWidth = self.ringWidth;
    CGFloat radius = (CGFloat)floor(MIN(rect.size.width, rect.size.height)/2.0) - lineWidth;
    
    //Background
    [self.ringBackgroundColor setStroke];
    
    UIBezierPath *borderPath = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:0.0 endAngle:2.0*(CGFloat)M_PI clockwise:NO];
    
    [borderPath setLineWidth:lineWidth];
    [borderPath stroke];
    
    //Progress
    [self.ringColor setStroke];
    
    if (self.progress > 0.0) {
        UIBezierPath *processPath = [UIBezierPath bezierPath];
        
        [processPath setLineWidth:lineWidth];
        [processPath setLineCapStyle:(self.roundProgressLine ? kCGLineCapRound : kCGLineCapSquare)];
        
        CGFloat startAngle = -((CGFloat)M_PI / 2.0);
        CGFloat endAngle = startAngle + 2.0 * (CGFloat)M_PI * self.progress;
        
        [processPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];
        
        [processPath stroke];
    }
    
    UIGraphicsPopContext();
}

@end


@implementation JGProgressHUDRingIndicatorView

#pragma mark - Initializers

- (instancetype)init {
    self = [super initWithContentView:nil];;
    
    if (self) {
        self.layer.contentsScale = [UIScreen mainScreen].scale;
        [self.layer setNeedsDisplay];
        
        self.ringWidth = 3.0;
        self.ringColor = [UIColor clearColor];
        self.ringBackgroundColor = [UIColor clearColor];
    }
    
    return self;
}

- (instancetype)initWithHUDStyle:(JGProgressHUDStyle)style {
    return [self init];
}

- (instancetype)initWithContentView:(UIView *)contentView {
    return [self init];
}

#pragma mark - Getters & Setters

- (void)setRoundProgressLine:(BOOL)roundProgressLine {
    if (roundProgressLine == self.roundProgressLine) {
        return;
    }
    
    _roundProgressLine = roundProgressLine;
    
    [(JGProgressHUDRingIndicatorLayer *)self.layer setRoundProgressLine:self.roundProgressLine];
}

- (void)setRingColor:(UIColor *)tintColor {
    if ([tintColor isEqual:self.ringColor]) {
        return;
    }
    
    _ringColor = tintColor;
    
    [(JGProgressHUDRingIndicatorLayer *)self.layer setRingColor:self.ringColor];
}

- (void)setRingBackgroundColor:(UIColor *)backgroundTintColor {
    if ([backgroundTintColor isEqual:self.ringBackgroundColor]) {
        return;
    }
    
    _ringBackgroundColor = backgroundTintColor;
    
    [(JGProgressHUDRingIndicatorLayer *)self.layer setRingBackgroundColor:self.ringBackgroundColor];
}

- (void)setRingWidth:(CGFloat)ringWidth {
    if (fequal(ringWidth, self.ringWidth)) {
        return;
    }
    
    _ringWidth = ringWidth;
    
    [(JGProgressHUDRingIndicatorLayer *)self.layer setRingWidth:self.ringWidth];
}

- (void)setProgress:(float)progress animated:(BOOL)animated {
    if (fequal(self.progress, progress)) {
        return;
    }
    
    [super setProgress:progress animated:animated];
    
    [CATransaction begin];
    [CATransaction setAnimationDuration:(animated ? 0.3 : 0.0)];
    
    [(JGProgressHUDRingIndicatorLayer *)self.layer setProgress:self.progress];
    
    [CATransaction commit];
}

#pragma mark - Overrides

- (void)setUpForHUDStyle:(JGProgressHUDStyle)style vibrancyEnabled:(BOOL)vibrancyEnabled {
    [super setUpForHUDStyle:style vibrancyEnabled:vibrancyEnabled];
    
    if (style == JGProgressHUDStyleDark) {
        self.ringColor = [UIColor colorWithWhite:1.0 alpha:1.0];
        self.ringBackgroundColor = [UIColor colorWithWhite:0.0 alpha:1.0];
    }
    else {
        self.ringColor = [UIColor blackColor];
        if (style == JGProgressHUDStyleLight) {
            self.ringBackgroundColor = [UIColor colorWithWhite:0.85 alpha:1.0];
        }
        else {
            self.ringBackgroundColor = [UIColor colorWithWhite:0.9 alpha:1.0];
        }
    }
}

+ (Class)layerClass {
    return [JGProgressHUDRingIndicatorLayer class];
}

@end


================================================
FILE: JGProgressHUD/JGProgressHUDShadow.h
================================================
//
//  JGProgressHUDShadow.h
//  JGProgressHUD
//
//  Created by Jonas Gessner on 25.09.17.
//  Copyright © 2017 Jonas Gessner. All rights reserved.
//

#import <UIKit/UIKit.h>

/**
 A wrapper representing properties of a shadow.
 */
@interface JGProgressHUDShadow : NSObject

- (instancetype __nonnull)initWithColor:(UIColor *__nonnull)color offset:(CGSize)offset radius:(CGFloat)radius opacity:(float)opacity;

/** Convenience initializer. */
+ (instancetype __nonnull)shadowWithColor:(UIColor *__nonnull)color offset:(CGSize)offset radius:(CGFloat)radius opacity:(float)opacity;

/**
 The color of the shadow. Colors created from patterns are currently NOT supported.
 */
@property (nonatomic, strong, readonly, nonnull) UIColor *color;

/** The shadow offset. */
@property (nonatomic, assign, readonly) CGSize offset;

/** The blur radius used to create the shadow. */
@property (nonatomic, assign, readonly) CGFloat radius;

/**
 The opacity of the shadow. Specifying a value outside the  [0,1] range will give undefined results.
 */
@property (nonatomic, assign, readonly) float opacity;

@end


================================================
FILE: JGProgressHUD/JGProgressHUDShadow.m
================================================
//
//  JGProgressHUDShadow.m
//  JGProgressHUD
//
//  Created by Jonas Gessner on 25.09.17.
//  Copyright © 2017 Jonas Gessner. All rights reserved.
//

#import "JGProgressHUDShadow.h"

@implementation JGProgressHUDShadow

+ (instancetype)shadowWithColor:(UIColor *)color offset:(CGSize)offset radius:(CGFloat)radius opacity:(float)opacity {
    return [[self alloc] initWithColor:color offset:offset radius:radius opacity:opacity];
}

- (instancetype)initWithColor:(UIColor *)color offset:(CGSize)offset radius:(CGFloat)radius opacity:(float)opacity {
    self = [super init];
    
    if (self) {
        _color = color;
        _offset = offset;
        _radius = radius;
        _opacity = opacity;
    }
    
    return self;
}

@end


================================================
FILE: JGProgressHUD/JGProgressHUDSuccessIndicatorView.h
================================================
//
//  JGProgressHUDSuccessIndicatorView.h
//  JGProgressHUD
//
//  Created by Jonas Gessner on 19.08.14.
//  Copyright (c) 2014 Jonas Gessner. All rights reserved.
//

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wquoted-include-in-framework-header"
#import "JGProgressHUDImageIndicatorView.h"
#pragma clang diagnostic pop

/**
 An image indicator showing a checkmark, representing a failed operation.
 */
@interface JGProgressHUDSuccessIndicatorView : JGProgressHUDImageIndicatorView

/**
 Default initializer for this class.
 */
- (instancetype __nonnull)init;

@end


================================================
FILE: JGProgressHUD/JGProgressHUDSuccessIndicatorView.m
================================================
//
//  JGProgressHUDSuccessIndicatorView.m
//  JGProgressHUD
//
//  Created by Jonas Gessner on 19.08.14.
//  Copyright (c) 2014 Jonas Gessner. All rights reserved.
//

#import "JGProgressHUDSuccessIndicatorView.h"
#import "JGProgressHUD.h"

static UIBezierPath *successBezierPath() {
    static UIBezierPath *path;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        path = [UIBezierPath bezierPath];
        [path moveToPoint:CGPointMake(1.5, 18)];
        [path addLineToPoint:CGPointMake(11, 28)];
        [path addLineToPoint:CGPointMake(31.5, 5.5)];
        
        [path setLineWidth:3];
        [path setLineJoinStyle:kCGLineJoinRound];
        [path setLineCapStyle:kCGLineCapRound];
    });

    return path;
}

@implementation JGProgressHUDSuccessIndicatorView

- (instancetype)initWithContentView:(UIView *__unused)contentView {
    UIBezierPath *path = successBezierPath();
    
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(33, 33), NO, 0.0);
    [[UIColor blackColor] setStroke];
    [path stroke];
    
    UIImage *img = [UIGraphicsGetImageFromCurrentImageContext() imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
    
    UIGraphicsEndImageContext();
    
    self = [super initWithImage:img];

    return self;
}

- (instancetype)init {
    return [self initWithContentView:nil];
}

- (void)updateAccessibility {
    self.accessibilityLabel = NSLocalizedString(@"Success",);
}

@end


================================================
FILE: Makefile
================================================
TARGET := iphone:clang:16.5
INSTALL_TARGET_PROCESSES = TikTok
THEOS_DEVICE_IP = 192.168.100.246
THEOS_DEVICE_USER = root
include $(THEOS)/makefiles/common.mk

TWEAK_NAME = BHTikTok

BHTikTok_FILES = Tweak.x $(wildcard *.m JGProgressHUD/*.m Settings/*.m)
BHTikTok_FRAMEWORKS = UIKit Foundation CoreGraphics Photos CoreServices SystemConfiguration SafariServices Security QuartzCore
BHTikTok_CFLAGS = -fobjc-arc -Wno-unused-variable -Wno-unused-value -Wno-deprecated-declarations -Wno-nullability-completeness -Wno-unused-function -Wno-incompatible-pointer-types

include $(THEOS_MAKE_PATH)/tweak.mk


================================================
FILE: README.md
================================================
# BHTikTok++
An awesome tweak for TikTok!

# Telegram Channel 
https://t.me/BHTikTokPlusPlus

# Features
- No Ads
- Download Videos
- Download HD Videos
- Download Photos
- Download Musics
- Upload Region
- Show Username
- Disable Pull-to-refresh
- Disable Unsensitive warning
- Disable Warnings
- Disable Live Broadcast
- Skip Recommendations 
- Remove Watermark
- Show/Hide UI button
- Copy video decription
- Copy video link
- Copy Music link
- Auto Play Next Video
- Show progress bar
- Confirm like
- Confirm comment like
- Confirm comment dislike
- Confirm follow
- Save profile image
- Copy profile information
- Profile Video Count
- Video Upload Date
- Video Like Count
- Always Upload In HD
- Extend bio
- Extend comment
- Always open in Safari
- Changing region
- Fake verify blue mark
- Fake Follower count
- Fake Following count
- Padlock

# TODO
-  [ ] Add Localization for the tweak.
-  [x] Add HD Download
-  [ ] Story Download

================================================
FILE: SecurityViewController.h
================================================
#import <UIKit/UIKit.h>
#import <LocalAuthentication/LocalAuthentication.h>

@interface SecurityViewController : UIViewController
@end

================================================
FILE: SecurityViewController.m
================================================
#import "SecurityViewController.h"

@implementation SecurityViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
    UIVisualEffectView *blurView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
    blurView.frame = self.view.bounds;
    [self.view addSubview:blurView];
    
    UIButton *authenticateButton = [[UIButton alloc] initWithFrame:CGRectMake(20, 20, 200, 60)];
    [authenticateButton setTitle:@"Authenticate" forState:UIControlStateNormal];
    authenticateButton.center = self.view.center;
    [authenticateButton addTarget:self action:@selector(authenticateButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:authenticateButton];
    
    [self authenticate];
}

- (void)authenticateButtonTapped:(id)sender {
    [self authenticate];
}

- (void)authenticate {
    LAContext *context = [[LAContext alloc] init];
    NSError *error = nil;
    
    if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&error]) {
        NSString *reason = @"Identify yourself!";
        
        [context evaluatePolicy:LAPolicyDeviceOwnerAuthentication localizedReason:reason reply:^(BOOL success, NSError *authenticationError) {
            dispatch_async(dispatch_get_main_queue(), ^{
                if (success) {
                    [self dismissViewControllerAnimated:YES completion:nil];
                } else {
                    // error
                }
            });
        }];
    } else {
        // no biometry
    }
}

@end


================================================
FILE: Settings/CountryTable.h
================================================
#import <UIKit/UIKit.h>

@interface CountryTable : UIViewController

@end


================================================
FILE: Settings/CountryTable.m
================================================
#import "CountryTable.h"

@interface CountryTable () <UITableViewDelegate, UITableViewDataSource>

@property (nonatomic, strong) NSArray *regionTitles; // Holds the country names
@property (nonatomic, strong) NSArray *regionCodes;  // Holds the dictionaries of country details
@property (nonatomic, strong) UITableView *tableView; // The table view to show the list

@end
@interface AWEStoreRegionChangeManager: NSObject 
- (void)p_showStoreRegionChangedDialog;
+ (id)sharedInstance;
@end
@implementation CountryTable

- (void)viewDidLoad {
    [super viewDidLoad];
    self.title = @"Regions";
    self.regionTitles = @[@"Saudi Arabia 🇸🇦", @"Taiwan 🇹🇼", @"Hong Kong 🇭🇰", @"Macao 🇲🇴", @"Japan 🇯🇵", @"South Korea 🇰🇷", @"United Kingdom 🇬🇧", @"United States 🇺🇸", @"Australia 🇦🇺", @"Canada 🇨🇦", @"Argentina 🇦🇷", @"Philippines 🇵🇭", @"Laos 🇱🇦", @"Malaysia 🇲🇾", @"Thailand 🇹🇭", @"Singapore 🇸🇬", @"Indonesia 🇮🇩", @"Vietnam 🇻🇳", @"Anguilla 🇦🇮", @"Panama 🇵🇦", @"Germany 🇩🇪", @"Russia 🇷🇺", @"France 🇫🇷", @"Finland 🇫🇮", @"Italy 🇮🇹", @"Pakistan 🇵🇰", @"Denmark 🇩🇰", @"Norway 🇳🇴", @"Sudan 🇸🇩", @"Romania 🇷🇴", @"United Arab Emirates 🇦🇪", @"Egypt 🇪🇬", @"Lebanon 🇱🇧", @"Mexico 🇲🇽", @"Brazil 🇧🇷", @"Turkey 🇹🇷", @"Kuwait 🇰🇼", @"Algeria 🇩🇿"];
    
    self.regionCodes = @[
    @{@"area": @"Saudi Arabia 🇸🇦", @"name": @"Saudi Arabia", @"code": @"SA", @"mcc": @"420", @"mnc": @"01"},
    @{@"area": @"Taiwan 🇹🇼", @"name": @"Taiwan", @"code": @"TW", @"mcc": @"466", @"mnc": @"01"},
    @{@"area": @"Hong Kong 🇭🇰", @"name": @"Hong Kong", @"code": @"HK", @"mcc": @"454", @"mnc": @"00"},
    @{@"area": @"Macao 🇲🇴", @"name": @"Macao", @"code": @"MO", @"mcc": @"455", @"mnc": @"00"},
    @{@"area": @"Japan 🇯🇵", @"name": @"Japan", @"code": @"JP", @"mcc": @"440", @"mnc": @"00"},
    @{@"area": @"South Korea 🇰🇷", @"name": @"South Korea", @"code": @"KR", @"mcc": @"450", @"mnc": @"05"},
    @{@"area": @"United Kingdom 🇬🇧", @"name": @"United Kingdom", @"code": @"GB", @"mcc": @"234", @"mnc": @"30"},
    @{@"area": @"United States 🇺🇸", @"name": @"United States", @"code": @"US", @"mcc": @"310", @"mnc": @"00"},
    @{@"area": @"Australia 🇦🇺", @"name": @"Australia", @"code": @"AU", @"mcc": @"505", @"mnc": @"02"},
    @{@"area": @"Canada 🇨🇦", @"name": @"Canada", @"code": @"CA", @"mcc": @"302", @"mnc": @"720"},
    @{@"area": @"Argentina 🇦🇷", @"name": @"Argentina", @"code": @"AR", @"mcc": @"722", @"mnc": @"07"},
    @{@"area": @"Philippines 🇵🇭", @"name": @"Philippines", @"code": @"PH", @"mcc": @"515", @"mnc": @"02"},
    @{@"area": @"Laos 🇱🇦", @"name": @"Laos", @"code": @"LA", @"mcc": @"457", @"mnc": @"01"},
    @{@"area": @"Malaysia 🇲🇾", @"name": @"Malaysia", @"code": @"MY", @"mcc": @"502", @"mnc": @"13"},
    @{@"area": @"Thailand 🇹🇭", @"name": @"Thailand", @"code": @"TH", @"mcc": @"520", @"mnc": @"18"},
    @{@"area": @"Singapore 🇸🇬", @"name": @"Singapore", @"code": @"SG", @"mcc": @"525", @"mnc": @"01"},
    @{@"area": @"Indonesia 🇮🇩", @"name": @"Indonesia", @"code": @"ID", @"mcc": @"510", @"mnc": @"01"},
    @{@"area": @"Vietnam 🇻🇳", @"name": @"Vietnam", @"code": @"VN", @"mcc": @"452", @"mnc": @"01"},
    @{@"area": @"Anguilla 🇦🇮", @"name": @"Anguilla", @"code": @"AI", @"mcc": @"365", @"mnc": @"840"},
    @{@"area": @"Panama 🇵🇦", @"name": @"Panama", @"code": @"PA", @"mcc": @"714", @"mnc": @"04"},
    @{@"area": @"Germany 🇩🇪", @"name": @"Germany", @"code": @"DE", @"mcc": @"262", @"mnc": @"01"},
    @{@"area": @"Russia 🇷🇺", @"name": @"Russia", @"code": @"RU", @"mcc": @"250", @"mnc": @"01"},
    @{@"area": @"France 🇫🇷", @"name": @"France", @"code": @"FR", @"mcc": @"208", @"mnc": @"10"},
    @{@"area": @"Finland 🇫🇮", @"name": @"Finland", @"code": @"FI", @"mcc": @"244", @"mnc": @"91"},
    @{@"area": @"Italy 🇮🇹", @"name": @"Italy", @"code": @"IT", @"mcc": @"222", @"mnc": @"10"},
    @{@"area": @"Pakistan 🇵🇰", @"name": @"Pakistan", @"code": @"PK", @"mcc": @"410", @"mnc": @"01"},
    @{@"area": @"Denmark 🇩🇰", @"name": @"Denmark", @"code": @"DK", @"mcc": @"238", @"mnc": @"01"},
    @{@"area": @"Norway 🇳🇴", @"name": @"Norway", @"code": @"NO", @"mcc": @"242", @"mnc": @"01"},
    @{@"area": @"Sudan 🇸🇩", @"name": @"Sudan", @"code": @"SD", @"mcc": @"634", @"mnc": @"01"},
    @{@"area": @"Romania 🇷🇴", @"name": @"Romania", @"code": @"RO", @"mcc": @"226", @"mnc": @"01"},
    @{@"area": @"United Arab Emirates 🇦🇪", @"name": @"United Arab Emirates", @"code": @"AE", @"mcc": @"424", @"mnc": @"02"},
    @{@"area": @"Egypt 🇪🇬", @"name": @"Egypt", @"code": @"EG", @"mcc": @"602", @"mnc": @"01"},
    @{@"area": @"Lebanon 🇱🇧", @"name": @"Lebanon", @"code": @"LB", @"mcc": @"415", @"mnc": @"01"},
    @{@"area": @"Mexico 🇲🇽", @"name": @"Mexico", @"code": @"MX", @"mcc": @"334", @"mnc": @"030"},
    @{@"area": @"Brazil 🇧🇷", @"name": @"Brazil", @"code": @"BR", @"mcc": @"724", @"mnc": @"06"},
    @{@"area": @"Turkey 🇹🇷", @"name": @"Turkey", @"code": @"TR", @"mcc": @"286", @"mnc": @"01"},
    @{@"area": @"Kuwait 🇰🇼", @"name": @"Kuwait", @"code": @"KW", @"mcc": @"419", @"mnc": @"02"},
    @{@"area": @"Algeria 🇩🇿", @"name": @"Algeria", @"code": @"DZ", @"mcc": @"603", @"mnc": @"01"}
];

    
    
    self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
    self.tableView.delegate = self;
    self.tableView.dataSource = self;
    [self.view addSubview:self.tableView];
}

#pragma mark - UITableView DataSource

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.regionTitles.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *cellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
    }
    
    cell.textLabel.text = self.regionTitles[indexPath.row];
    
    return cell;
}

#pragma mark - UITableView Delegate

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    
    NSDictionary *selectedRegion = self.regionCodes[indexPath.row];
    
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setObject:selectedRegion forKey:@"region"];
    [defaults synchronize];
    
    NSLog(@"Selected region: %@", selectedRegion);
    [[NSClassFromString(@"AWEStoreRegionChangeManager") sharedInstance] p_showStoreRegionChangedDialog];

}

@end


================================================
FILE: Settings/LiveActions.h
================================================
#import <UIKit/UIKit.h>

@interface LiveActions : UIViewController

@end


================================================
FILE: Settings/LiveActions.m
================================================
#import "LiveActions.h"

@interface LiveActions () <UITableViewDelegate, UITableViewDataSource>

@property (nonatomic, strong) NSArray *liveFuncValues;  // Holds the dictionaries of country details
@property (nonatomic, strong) UITableView *tableView; // The table view to show the list
@property (nonatomic, strong) NSArray *liveFuncTitles;

@end

@implementation LiveActions

- (void)viewDidLoad {
    [super viewDidLoad];
    self.title = @"Live Button Actions";
    self.liveFuncTitles = @[@"Default", @"BHTikTok++ Settings", @"Playback Speed"];
    self.liveFuncValues = @[@0, @1, @2];
    self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
    self.tableView.delegate = self;
    self.tableView.dataSource = self;
    [self.view addSubview:self.tableView];
}

#pragma mark - UITableView DataSource

// Number of rows should be equal to the number of countries in the list
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.liveFuncValues.count;
}

// Configure the cell to display the country name
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *cellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
    }
    
    // Set the country name in the cell
    cell.textLabel.text = self.liveFuncTitles[indexPath.row];
    
    return cell;
}

#pragma mark - UITableView Delegate

// Handle selection of a row
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    
    // Get the selected country's dictionary
    NSNumber *selectedLiveOption = self.liveFuncTitles[indexPath.row];
    
    // Save the selected country dictionary to NSUserDefaults under the key "region"
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setValue:self.liveFuncValues[indexPath.row] forKey:@"live_action"];
    [defaults synchronize];
    
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Live Option Selected" message:[NSString stringWithFormat:@"You selected: %@", self.liveFuncTitles[indexPath.row]]
                                                            preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
    [alert addAction:okAction];
    [self presentViewController:alert animated:YES completion:nil];
    [[NSNotificationCenter defaultCenter] postNotificationName:@"RegionSelectedNotification"
                                                        object:nil
                                                      userInfo:@{@"selected action": selectedLiveOption}];
}

@end


================================================
FILE: Settings/PlaybackSpeed.h
================================================
//
//  PlaybackSpeed.h
//  StaticTableView
//
//  Created by raul on 16/10/2024.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface PlaybackSpeed : UIViewController <UITableViewDelegate, UITableViewDataSource>

@end

NS_ASSUME_NONNULL_END


================================================
FILE: Settings/PlaybackSpeed.m
================================================
//
//  PlaybackSpeed.m
//  StaticTableView
//
//  Created by raul on 16/10/2024.
//

#import "PlaybackSpeed.h"

@interface PlaybackSpeed ()
@property (nonatomic, strong) UITableView *staticTable;
@property (nonatomic, strong) NSArray <NSNumber *> *speeds;
@end

@implementation PlaybackSpeed

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor systemBackgroundColor];
    self.speeds = @[@0.5, @1.0, @1.5, @2.0];
    self.title = @"Select Speed";
    self.staticTable = [[UITableView alloc] initWithFrame:CGRectZero];
    self.staticTable.translatesAutoresizingMaskIntoConstraints = NO;
    [self.staticTable registerClass:[UITableViewCell class] forCellReuseIdentifier:@"CellIdentifier"];

    [self.view addSubview:self.staticTable];
    [NSLayoutConstraint activateConstraints:@[
        [self.staticTable.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
        [self.staticTable.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
        [self.staticTable.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
        [self.staticTable.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor]
    ]];
    self.staticTable.dataSource = self;
    self.staticTable.delegate = self;
}


- (nonnull UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CellIdentifier"];
    cell.textLabel.text = [NSString stringWithFormat:@"%@ x", self.speeds[indexPath.row]];
        cell.tag = [self.speeds[indexPath.row] floatValue];
        return cell;
    
}

- (NSInteger)tableView:(nonnull UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 4;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    NSNumber *selectedSpeed = self.speeds[indexPath.row];
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setValue:selectedSpeed forKey:@"playback_speed"];
    [defaults synchronize];
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Live Option Selected" message:[NSString stringWithFormat:@"You selected: %@", selectedSpeed]
                                                            preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
    [alert addAction:okAction];
    [self presentViewController:alert animated:YES completion:nil];
    [[NSNotificationCenter defaultCenter] postNotificationName:@"RegionSelectedNotification"
                                                        object:nil
                                                      userInfo:@{@"selected Speed": selectedSpeed}];
    NSLog(@"%@",selectedSpeed);
}
    @end


================================================
FILE: Settings/ViewController.h
================================================
//
//  ViewController.h
//  StaticTableView
//
//  Created by raul on 08/10/2024.
//

#import <UIKit/UIKit.h>
#import "TikTokHeaders.h"
@interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate>


@end



================================================
FILE: Settings/ViewController.m
================================================
//
//  ViewController.m
//  StaticTableView
//
//  Created by raul on 08/10/2024.
//

#import "ViewController.h"
#import "CountryTable.h"
#import "LiveActions.h"
#import "PlaybackSpeed.h"

@interface ViewController ()
@property (nonatomic, strong) UITableView *staticTable;
@property (nonatomic, assign) BOOL isAdditionalCellVisible;
@property (nonatomic, assign) UIImage *devImage;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor systemBackgroundColor];
    
    self.title = @"BHTikTok++ Settings";
    self.staticTable = [[UITableView alloc] initWithFrame:CGRectZero ];
    self.staticTable.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:self.staticTable];
    [NSLayoutConstraint activateConstraints:@[
        [self.staticTable.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
        [self.staticTable.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
        [self.staticTable.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
        [self.staticTable.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor]
    ]];
    self.staticTable.dataSource = self;
    self.staticTable.delegate = self;
    self.staticTable.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(regionSelected:)
                                                 name:@"RegionSelectedNotification"
                                               object:nil];
}
- (void)regionSelected:(NSNotification *)notification {
    [self.staticTable reloadData];
}


- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 8;
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    switch (section) {
        case 0:
            return @"Feed";
        case 1:
            return @"Profile";
        case 2:
            return @"Confirm";
        case 3:
            return @"Other";
        case 4:
            return @"Region";
            break;
        case 5:
            return @"Live Button Function";
        case 6:
            return @"Playback Speed";
        case 7:
            return @"Developer";
        default:
            break;
    }
    return @"";
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    switch (section) {
        case 0: // Feed section
            return 15;
        case 1: // Profile section
            return 4;
        case 2: // Confirm section
            return 4;
        case 3: // Other section
            return 10;
        case 4:
            return 2; // region section
        case 5:
            return 2; // live action section
        case 6:
            return 2;
        case 7:
            return 3; // developer section
        default:
            return 0; // Fallback for unexpected section
    }
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.section == 0) {
        switch (indexPath.row) {
            case 0:
                return [self createSwitchCellWithTitle:@"Hide Ads"
                                                Detail:@"Hide all ads from the app"
                                                   Key:@"hide_ads"];
            case 1:
                return [self createSwitchCellWithTitle:@"Download Button"
                                                Detail:@"Enable download button for videos"
                                                   Key:@"download_button"];
            case 2:
                return [self createSwitchCellWithTitle:@"Share Sheet"
                                                Detail:@"Enable sharing options in share sheet"
                                                   Key:@"share_sheet"];
            case 3:
                return [self createSwitchCellWithTitle:@"Remove Watermark"
                                                Detail:@"Remove the TikTok watermark from videos"
                                                   Key:@"remove_watermark"];
            case 4:
                return [self createSwitchCellWithTitle:@"Show/Hide UI Button"
                                                Detail:@"Show or hide the UI button"
                                                   Key:@"remove_elements_button"];
            case 5:
                return [self createSwitchCellWithTitle:@"Stop Playback"
                                                Detail:@"Stop video playback automatically"
                                                   Key:@"stop_play"];
            case 6:
                return [self createSwitchCellWithTitle:@"Auto Play Next Video"
                                                Detail:@"Automatically play the next video"
                                                   Key:@"auto_play"];
            case 7:
                return [self createSwitchCellWithTitle:@"Show Progress Bar"
                                                Detail:@"Display progress bar on video playback"
                                                   Key:@"show_porgress_bar"];
            case 8:
                return [self createSwitchCellWithTitle:@"Transparent Comments"
                                                Detail:@"Make comments transparent"
                                                   Key:@"transparent_commnet"];
            case 9:
                return [self createSwitchCellWithTitle:@"Show Usernames"
                                                Detail:@"Display usernames on videos"
                                                   Key:@"show_username"];
            case 10:
                return [self createSwitchCellWithTitle:@"Disable Sensitive Content"
                                                Detail:@"Disable sensitive content filter"
                                                   Key:@"disable_unsensitive"];
            case 11:
                return [self createSwitchCellWithTitle:@"Disable Warnings"
                                                Detail:@"Disable TikTok warnings"
                                                   Key:@"disable_warnings"];
            case 12:
                return [self createSwitchCellWithTitle:@"Disable Live Streaming"
                                                Detail:@"Disable live video streaming"
                                                   Key:@"disable_live"];
            case 13:
                return [self createSwitchCellWithTitle:@"Skip Recommendations"
                                                Detail:@"Skip recommended videos"
                                                   Key:@"skip_recommnedations"];
            case 14:
                return [self createSwitchCellWithTitle:@"Upload Region"
                                                Detail:@"Show Upload Region Flag Next to Username"
                                                   Key:@"upload_region"];
            default:
                break;
        }
    } else if (indexPath.section == 1) {
        switch (indexPath.row) {
            case 0:
                return [self createSwitchCellWithTitle:@"Profile Save"
                                                Detail:@"Save profile details to clipboard"
                                                   Key:@"save_profile"];
            case 1:
                return [self createSwitchCellWithTitle:@"Profile Copy"
                                                Detail:@"Copy profile information"
                                                   Key:@"copy_profile_information"];
            case 2:
                return [self createSwitchCellWithTitle:@"Video Like Count"
                                                Detail:@"Show the number of likes on videos"
                                                   Key:@"video_like_count"];
            case 3:
                return [self createSwitchCellWithTitle:@"Video Upload Date"
                                                Detail:@"Show the date videos were uploaded"
                                                   Key:@"video_upload_date"];
            default:
                break;
        }
    } else if (indexPath.section == 2) {
        switch (indexPath.row) {
            case 0:
                return [self createSwitchCellWithTitle:@"Like Confirmation"
                                                Detail:@"Confirm before liking a video"
                                                   Key:@"like_confirm"];
            case 1:
                return [self createSwitchCellWithTitle:@"Like Comment Confirmation"
                                                Detail:@"Confirm before liking a comment"
                                                   Key:@"like_comment_confirm"];
            case 2:
                return [self createSwitchCellWithTitle:@"Dislike Comment Confirmation"
                                                Detail:@"Confirm before disliking a comment"
                                                   Key:@"dislike_comment_confirm"];
            case 3:
                return [self createSwitchCellWithTitle:@"Follow Confirmation"
                                                Detail:@"Confirm before following a user"
                                                   Key:@"follow_confirm"];
            default:
                break;
        }
    } else if (indexPath.section == 3) {
        switch (indexPath.row) {
            case 0:
                return [self createSwitchCellWithTitle:@"Always Open Safari"
                                                Detail:@"Always open links in Safari"
                                                   Key:@"openInBrowser"];
            case 1:
                return [self createSwitchCellWithTitle:@"Enable Fake Changes"
                                                Detail:@"Enable fake profile changes"
                                                   Key:@"en_fake"];
            case 2: {
                UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
                
                UILabel *followerLabel = [[UILabel alloc] init];
                followerLabel.text = @"Follower:";
                followerLabel.font = [UIFont systemFontOfSize:16];
                followerLabel.translatesAutoresizingMaskIntoConstraints = NO;
                [cell.contentView addSubview:followerLabel];
                
                UITextField *textField = [[UITextField alloc] init];
                textField.placeholder = @"Enter follower count";
                textField.borderStyle = UITextBorderStyleRoundedRect;
                textField.delegate = self;
                textField.tag = 2;
                textField.returnKeyType = UIReturnKeyDone;
                textField.translatesAutoresizingMaskIntoConstraints = NO;
                [cell.contentView addSubview:textField];
                
                [NSLayoutConstraint activateConstraints:@[
                    [followerLabel.leadingAnchor constraintEqualToAnchor:cell.contentView.leadingAnchor constant:15],
                    [followerLabel.centerYAnchor constraintEqualToAnchor:cell.contentView.centerYAnchor],
                    [followerLabel.widthAnchor constraintEqualToConstant:100],
                    
                    [textField.leadingAnchor constraintEqualToAnchor:followerLabel.trailingAnchor constant:10],
                    [textField.trailingAnchor constraintEqualToAnchor:cell.contentView.trailingAnchor constant:-15],
                    [textField.centerYAnchor constraintEqualToAnchor:cell.contentView.centerYAnchor],
                    [textField.heightAnchor constraintEqualToConstant:30]
                ]];
                NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
                NSString *savedText = [defaults stringForKey:@"following_count"];
                if (savedText) {
                    textField.text = savedText;
                }
                
                return cell;
            }
            case 3: {
                UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
                
                UILabel *followingLabel = [[UILabel alloc] init];
                followingLabel.text = @"Following:";
                followingLabel.font = [UIFont systemFontOfSize:16];
                followingLabel.translatesAutoresizingMaskIntoConstraints = NO;
                [cell.contentView addSubview:followingLabel];
                
                UITextField *textField = [[UITextField alloc] init];
                textField.placeholder = @"Enter following count";
                textField.borderStyle = UITextBorderStyleRoundedRect;
                textField.delegate = self;
                textField.tag = 1;
                textField.returnKeyType = UIReturnKeyDone;
                textField.translatesAutoresizingMaskIntoConstraints = NO;
                [cell.contentView addSubview:textField];
                
                [NSLayoutConstraint activateConstraints:@[
                    [followingLabel.leadingAnchor constraintEqualToAnchor:cell.contentView.leadingAnchor constant:15],
                    [followingLabel.centerYAnchor constraintEqualToAnchor:cell.contentView.centerYAnchor],
                    [followingLabel.widthAnchor constraintEqualToConstant:100],
                    
                    [textField.leadingAnchor constraintEqualToAnchor:followingLabel.trailingAnchor constant:10],
                    [textField.trailingAnchor constraintEqualToAnchor:cell.contentView.trailingAnchor constant:-15],
                    [textField.centerYAnchor constraintEqualToAnchor:cell.contentView.centerYAnchor],
                    [textField.heightAnchor constraintEqualToConstant:30]
                ]];
                
                NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
                NSString *savedText = [defaults stringForKey:@"following_count"];
                if (savedText) {
                    textField.text = savedText;
                }
                
                return cell;
            }
            case 4:
                return [self createSwitchCellWithTitle:@"Fake Verified"
                                                Detail:@"Make your account appear verified"
                                                   Key:@"fake_verify"];
            case 5:
                return [self createSwitchCellWithTitle:@"Extended Bio"
                                                Detail:@"Extend bio section of your profile"
                                                   Key:@"extended_bio"];
            case 6:
                return [self createSwitchCellWithTitle:@"Extended Comments"
                                                Detail:@"Extend the length of your comments"
                                                   Key:@"extendedComment"];
            case 7:
                return [self createSwitchCellWithTitle:@"Upload HD"
                                                Detail:@"Upload videos in HD quality"
                                                   Key:@"upload_hd"];
            case 8:
                return [self createSwitchCellWithTitle:@"App Lock"
                                                Detail:@"Lock the app with a passcode"
                                                   Key:@"padlock"];
            case 9:
                return [self createSwitchCellWithTitle:@"Enable Flex"
                                                Detail:@"Developers Only, DON'T touch it if you don't know what you are doing."
                                                   Key:@"flex_enebaled"];
            default:
                break;
        }
    } else if (indexPath.section == 4) {
        switch (indexPath.row) {
            case 0:
                return [self createSwitchCellWithTitle:@"Enable Region Changing"
                                                Detail:@"Enable region changing functionality"
                                                   Key:@"en_region"];
            case 1: {
                NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
                UITableViewCell *regions = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil];
                regions.textLabel.text = @"Regions";
                NSDictionary *selectedRegion = [defaults dictionaryForKey:@"region"];
                regions.detailTextLabel.text = [NSString stringWithFormat:@"%@", selectedRegion[@"area"]];
                return regions;
            }
            default:
                break;
        }
    } else if (indexPath.section == 5) {
        switch (indexPath.row) {
            case 0:
                return [self createSwitchCellWithTitle:@"Live Button Action"
                                                Detail:@"Change The Default Live Button Action"
                                                   Key:@"en_livefunc"];
            case 1: {
                NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
                UITableViewCell *liveAction = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil];
                liveAction.textLabel.text = @"Actions";
                NSString *selectedLiveAction = [defaults valueForKey:@"live_action"];
                NSArray *liveFuncTitles = @[@"Default", @"BHTikTok++ Settings", @"Playback Speed"];
                if (selectedLiveAction != nil) {
                    liveAction.detailTextLabel.text = [NSString stringWithFormat:@"%@", [liveFuncTitles objectAtIndex:[selectedLiveAction integerValue]]];
                }
                
                return liveAction;
            }
                break;
            default:
                break;
        }
    } else if (indexPath.section == 6) {
        switch (indexPath.row) {
            case 0: {
                return [self createSwitchCellWithTitle:@"Playback Speed"
                                                Detail:@"Enable Presistent Playback Speed."
                                                   Key:@"playback_en"];
            }
            case 1: {
                UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil];
                cell.textLabel.text = @"Speeds";
                
                NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
                NSString *selectedSpeed = [defaults valueForKey:@"playback_speed"];
                if (selectedSpeed != nil) {
                    cell.detailTextLabel.text = [NSString stringWithFormat:@"%@ x", selectedSpeed];
                }
                return cell;
            }
        }
    }
    else if (indexPath.section == 7) {
        switch (indexPath.row) {
            case 0: {
                UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil];
                cell.textLabel.text = @"Raul Saeed";
                cell.textLabel.textColor = [UIColor systemBlueColor];
                cell.detailTextLabel.text = @"Github Page";
                cell.imageView.image = [UIImage systemImageNamed:@"link"];
                cell.detailTextLabel.textColor = [UIColor systemGrayColor];
                return cell;
            }
            case 1: {
                UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil];
                cell.textLabel.text = @"Raul Saeed";
                cell.textLabel.textColor = [UIColor systemBlueColor];
                cell.detailTextLabel.text = @"X Page";
                cell.imageView.image = [UIImage systemImageNamed:@"link"];
                cell.detailTextLabel.textColor = [UIColor systemGrayColor];
                return cell;
            }
            case 2: {
                UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil];
                cell.textLabel.text = @"Buy Me A Coffe";
                cell.textLabel.textColor = [UIColor systemBlueColor];
                cell.detailTextLabel.text = @"To keep me Motivated and the Tweak Updated.";
                cell.imageView.tintColor = [UIColor orangeColor];
                cell.detailTextLabel.textColor = [UIColor systemGrayColor];
                cell.imageView.image = [UIImage systemImageNamed:@"mug.fill"];
                return cell;
            }
                break;
            default:
                break;
        }
    }
    return [UITableViewCell new];
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    NSLog(@"Selected row at index: %ld", (long)indexPath.section);
    if (indexPath.section == 4 && indexPath.row == 1){
        CountryTable *countryTable = [[CountryTable alloc] init];
        UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:countryTable];
        [self presentViewController:navController animated:YES completion:nil];
        
    }
    else if (indexPath.section == 5 && indexPath.row == 1){
        LiveActions *liveActions = [[LiveActions alloc] init];
        UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:liveActions];
        [self presentViewController:navController animated:YES completion:nil];
    } else if (indexPath.section == 6 && indexPath.row == 1) {
        PlaybackSpeed *liveActions = [[PlaybackSpeed alloc] init];
        UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:liveActions];
        [self presentViewController:navController animated:YES completion:nil];
    }
    else if (indexPath.section == 7 && indexPath.row == 0){
        NSURL *url = [NSURL URLWithString:@"https://github.com/raulsaeed"];
        if ([[UIApplication sharedApplication] canOpenURL:url]) {
            
            [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
        }
    }
    else if (indexPath.section == 7 && indexPath.row == 1){
        NSURL *url = [NSURL URLWithString:@"https://x.com/Ashad__Saeed"];;
        if ([[UIApplication sharedApplication] canOpenURL:url]) {
            
            [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
        }
    }
    else if (indexPath.section == 7 && indexPath.row == 2){
        NSURL *url = [NSURL URLWithString:@"https://buymeacoffee.com/raulsaeed79"];
        if ([[UIApplication sharedApplication] canOpenURL:url]) {
            [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
        }
    }
}

- (UITableViewCell *)createSwitchCellWithTitle:(NSString *)title Detail:(NSString*)detail Key:(NSString*)key {
    
    UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil];
    
    
    UISwitch *switchView = [[UISwitch alloc] init];
    [cell.contentView addSubview:switchView];
    cell.accessoryView = switchView;
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    switchView.on = [defaults boolForKey:key];
    switchView.accessibilityLabel = key;
    [switchView addTarget:self action:@selector(switchToggled:) forControlEvents:UIControlEventValueChanged];
    
    
    
    cell.textLabel.text = title;
    cell.detailTextLabel.numberOfLines = 0;
    cell.detailTextLabel.text = detail;
    cell.detailTextLabel.textColor = [UIColor grayColor];
    return cell;
    
}

- (void)switchToggled:(UISwitch *)sender {
    
    NSString *key = sender.accessibilityLabel;
    
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setBool:sender.isOn forKey:key];
    [defaults synchronize];
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
    if (textField.tag == 1){
        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
        [defaults setObject:textField.text forKey:@"following_count"];
        [defaults synchronize];
    } else if (textField.tag == 2){
        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
        [defaults setObject:textField.text forKey:@"follower_count"];
        [defaults synchronize];
    }
    
    
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
    [textField resignFirstResponder];
    return YES;
}
@end


================================================
FILE: TikTokHeaders.h
================================================
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <SafariServices/SafariServices.h>
#import "BHIManager.h"
#import "SecurityViewController.h"
#import "BHDownload.h"
#import "BHMultipleDownload.h"
#import "JGProgressHUD/JGProgressHUD.h"
#import <Photos/Photos.h>
#import "Settings/ViewController.h"
#import "Settings/PlaybackSpeed.h"

@interface AppDelegate : NSObject <UIApplicationDelegate>
@end

@interface TTKCommentPanelViewController: UIViewController
@end 

@interface AWEUserNameLabel: UILabel
-(void)addVerifiedIcon:(BOOL)arg1;
@end

@interface TTKProfileRootView: UIView
@end


@interface BDImageView: UIImageView
- (void)handleLongPress:(UILongPressGestureRecognizer *)sender;
- (void)addHandleLongPress;
- (id)bd_baseImage;
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo;
@end

@interface TTTAttributedLabel: UILabel 
- (void)handleLongPress:(UILongPressGestureRecognizer *)sender;
- (void)addHandleLongPress;
@end

@interface AWEPlayInteractionAuthorView: UIView
- (void)addSubview:(id)arg1;
- (NSString *)emojiForCountryCode:(NSString *)countryCode;
@end
@interface SparkViewController: UIViewController
@property(nonatomic, strong, readwrite) NSURL *originURL;
- (void)didTapCloseButton;
@end

@interface AWEAwemeACLItem: NSObject
- (void)setWatermarkType:(NSUInteger)arg1;
- (NSUInteger)watermarkType;
@end

@interface ACCCreationPublishAction: NSObject
- (BOOL)is_open_hd;
- (void)setIs_open_hd:(BOOL)arg1;
- (BOOL)is_have_hd;
- (void)setIs_have_hd:(BOOL)arg1;
@end

@interface AWEMaskInfoModel
- (BOOL)showMask;
- (void)setShowMask:(BOOL)arg1;
@end

@interface UIView (RCTViewUnmounting)
@property(retain, nonatomic) UIViewController *yy_viewController;
@end
@interface AWECommentPanelCell: UITableView
- (void)onLikeAction:(id)arg1;
- (void)onDislikeAction:(id)arg1;
@end
@interface TikTokFeedTabControl: UIView
@end

@interface AWEFeedVideoButton: UIButton
@property(copy, nonatomic, readwrite) NSString *imageNameString;
@end

@interface AWEURLModel : NSObject
@property(retain, nonatomic) NSArray* originURLList;
- (NSURL *)recommendUrl;
- (NSURL *)bestURLtoDownload;
- (NSString *)bestURLtoDownloadFormat;
@end

@interface AWEVideoModel : NSObject
@property(readonly, nonatomic) AWEURLModel *playURL;
@property(readonly, nonatomic) AWEURLModel *downloadURL;
@property(readonly, nonatomic) NSNumber *duration;
@end

@interface AWEMusicModel : NSObject
@property(readonly, nonatomic) AWEURLModel *playURL;
@end

@interface AWEPhotoAlbumPhoto: NSObject
@property(readonly, nonatomic) AWEURLModel *originPhotoURL;
@end

@interface AWEPhotoAlbumModel: NSObject
@property(readonly, nonatomic) NSArray <AWEPhotoAlbumPhoto *> *photos;
@end

@interface AWEAwemeStatisticsModel: NSObject
@property (nonatomic, strong, readwrite) NSNumber *diggCount;
@end

@interface TUXLabel: UILabel 
@end

@interface AWEUserModel: NSObject
@property(nonatomic, copy, readwrite) NSString *bioUrl;
@property(nonatomic, copy, readwrite) NSString *nickname;
@property(nonatomic, copy, readwrite) NSString *signature;
@property(nonatomic, copy, readwrite) NSString *socialName;
@property (nonatomic, strong, readwrite) NSNumber *visibleVideosCount;
@end

@interface AWEAwemeModel : NSObject
@property(nonatomic) BOOL isAds;
@property(retain, nonatomic) AWEVideoModel *video;
@property(retain, nonatomic) id music;
@property (nonatomic, copy, readwrite) NSString *itemID;
@property(retain, nonatomic) AWEPhotoAlbumModel *photoAlbum;
@property(nonatomic) NSString *music_songName;
@property(nonatomic) NSString *music_artistName;
@property(nonatomic, strong, readwrite) AWEAwemeModel *currentPlayingStory;
@property (nonatomic, copy, readwrite) NSString *region;
@property (nonatomic, strong, readwrite) AWEAwemeStatisticsModel *statistics;
@property (nonatomic, strong, readwrite) NSNumber *createTime;
@property (nonatomic, strong, readwrite) AWEUserModel *author;
@property (nonatomic, assign, readwrite) BOOL isUserRecommendBigCard;
+ (id)liveStreamURLJSONTransformer;
+ (id)relatedLiveJSONTransformer;
+ (id)rawModelFromLiveRoomModel:(id)arg1;
+ (id)aweLiveRoom_subModelPropertyKey;
- (void)live_callInitWithDictyCategoryMethod:(id)arg1;
@end

@interface AWEUserWorkCollectionViewCell: UICollectionView
@property (nonatomic, strong, readwrite) AWEAwemeModel *model;
- (NSString *)formattedNumber:(NSInteger)number;
- (NSString *)formattedDateStringFromTimestamp:(NSTimeInterval)timestamp;
- (id)contentView;
@end

@interface TTKProfileOtherViewController :UIViewController
@property (nonatomic, strong, readwrite) AWEUserModel *user;
@end

@interface AWESettingItemModel: NSObject
@property(nonatomic, copy, readwrite) NSString *identifier;
@property(nonatomic, copy, readwrite) NSString *title;
@property(nonatomic, copy, readwrite) NSString *detail;
@property(nonatomic, strong, readwrite) UIImage *iconImage;
@property(nonatomic, assign, readwrite) NSUInteger type;
- (instancetype)initWithIdentifier:(NSString *)identifier;
@end

@interface TTKSettingsBaseCellPlugin: NSObject
@property(nonatomic, weak, readwrite) id context;
@property(nonatomic, strong, readwrite) AWESettingItemModel *itemModel;
- (instancetype)initWithPluginContext:(id)context;
@end

@interface AWEBaseListSectionViewModel: NSObject
@property(nonatomic, copy, readwrite) NSArray *modelsArray;
- (void)insertModel:(id)model atIndex:(NSInteger)index animated:(bool)animated;
@end

@interface AWESettingsNormalSectionViewModel: AWEBaseListSectionViewModel
@property(nonatomic, weak, readwrite) id context;
@property(nonatomic, copy, readwrite) NSString *sectionHeaderTitle;
@property(nonatomic, copy, readwrite) NSString *sectionIdentifier;
@end

@interface AWEBaseListViewModel: NSObject
- (NSArray *)sectionViewModelsArray;
@end

@interface AWEPluginBaseViewModel: AWEBaseListViewModel
@end

@interface AWESettingsBaseViewModel: AWEPluginBaseViewModel
@end

@interface TTKSettingsViewModel: AWESettingsBaseViewModel
@end

@interface TIKTOKProfileHeaderViewController: UIViewController
@property(nonatomic, strong) AWEUserModel *user;
@end

@interface TIKTOKProfileHeaderView: UIView
- (void)addHandleLongPress;
- (void)handleLongPress:(UILongPressGestureRecognizer *)sender;
@end

@interface AWEProfileImagePreviewView: UIView
@property(strong, nonatomic, readwrite) UIImageView *avatar;
- (void)addHandleLongPress;
- (void)handleLongPress:(UILongPressGestureRecognizer *)sender;
@end

@interface AWEAwemeBaseViewController: UIViewController
@property(nonatomic, strong, readwrite) AWEAwemeModel *model;
@property (nonatomic, copy, readwrite) NSString *referString;
@property (nonatomic) id interactionController;
@end


@interface TTKFeedInteractionContainerElement: NSObject
- (void)hideAllElements:(BOOL)hide exceptArray:(id)array;
@end

@interface TTKFeedInteractionMainContainerElement: TTKFeedInteractionContainerElement
@end

@interface TTKFeedInteractionLegacyMainContainerElement: TTKFeedInteractionMainContainerElement
@end

@interface AWEPlayPhotoAlbumViewController: UIViewController
@property(nonatomic, strong, readwrite) AWEAwemeModel *model;
- (NSIndexPath *)currentIndexPath;
@end

@interface TTKPhotoAlbumFeedCellController: AWEAwemeBaseViewController
{
    AWEPlayPhotoAlbumViewController *_photoAlbumController;
}
@end

@interface TTKPhotoAlbumDetailCellController: AWEAwemeBaseViewController
{
    AWEPlayPhotoAlbumViewController *_photoAlbumController;
}
@end

@interface AWEFeedCellViewController: AWEAwemeBaseViewController;
@property (nonatomic, strong, readwrite) AWEAwemeModel *model;
- (NSInteger)indexPath;
@end

@interface AWEAwemeDetailCellViewController: AWEAwemeBaseViewController
@end

@interface TTKStoryContainerViewController: UIViewController
@property(nonatomic, strong, readwrite) AWEAwemeModel *model;
@property (nonatomic) id interactionController;
@end
@interface TTKStoryDetailContainerViewController: TTKStoryContainerViewController
@end


@interface AWEFeedViewTemplateCell: UITableViewCell
@property(nonatomic, strong, readwrite) UIViewController *viewController;
@property(nonatomic, strong, readwrite) UIViewController *parentVC;
@property(nonatomic, assign) BOOL elementsHidden;
@property (nonatomic, strong) JGProgressHUD *hud;
@property (nonatomic, retain) NSString *fileextension;
@property (nonatomic, retain) UIProgressView *progressView;
- (void)addHandleLongPress;
- (void)handleLongPress:(UILongPressGestureRecognizer *)sender;
- (void)addHideElementButton;
- (void)hideElementButtonHandler:(UIButton *)sender;
- (void)addDownloadButton;
- (void) downloadButtonHandler:(UIButton *)sender;
- (void)downloadVideo:(AWEAwemeBaseViewController *)rootVC;
- (void)downloadMusic:(AWEAwemeBaseViewController *)rootVC;
- (void)copyMusic:(AWEAwemeBaseViewController *)rootVC;
- (void)copyVideo:(AWEAwemeBaseViewController *)rootVC;
- (void)downloadHDVideo:(AWEAwemeBaseViewController *)rootVC;
- (void)copyDecription:(AWEAwemeBaseViewController *)rootVC;
- (void)downloadPhotos:(TTKPhotoAlbumDetailCellController *)rootVC photoIndex:(unsigned long)index;
- (void)downloadPhotos:(TTKPhotoAlbumDetailCellController *)rootVC;
- (void)tapDownloadVideo;
@end
@interface AWEFeedViewTemplateCell () <BHDownloadDelegate, BHMultipleDownloadDelegate>
@end

@interface AWEFeedViewCell: AWEFeedViewTemplateCell
@end

@interface AWEAwemeDetailTableViewCell: UITableViewCell
@property(nonatomic, strong, readwrite) UIViewController *viewController;
@property(nonatomic, strong, readwrite) UIViewController *parentVC;
@property(nonatomic, assign) BOOL elementsHidden;
@property (nonatomic, strong) JGProgressHUD *hud;
@property (nonatomic, retain) NSString *fileextension;
@property (nonatomic, retain) UIProgressView *progressView;
- (void)addHandleLongPress;
- (void)handleLongPress:(UILongPressGestureRecognizer *)sender;
- (void)addHideElementButton;
- (void)hideElementButtonHandler:(UIButton *)sender;
- (void)addDownloadButton;
- (void) downloadButtonHandler:(UIButton *)sender;
- (void)downloadVideo:(AWEAwemeBaseViewController *)rootVC;
- (void)downloadHDVideo:(AWEAwemeBaseViewController *)rootVC;
- (void)downloadMusic:(AWEAwemeBaseViewController *)rootVC;
- (void)copyMusic:(AWEAwemeBaseViewController *)rootVC;
- (void)copyVideo:(AWEAwemeBaseViewController *)rootVC;
- (void)copyDecription:(AWEAwemeBaseViewController *)rootVC;
@end
@interface AWEAwemeDetailTableViewCell () <BHDownloadDelegate>
@end

@interface TTKStoryDetailTableViewCell: UITableViewCell
@property(nonatomic, strong, readwrite) UIViewController *viewController;
@property(nonatomic, strong, readwrite) UIViewController *parentVC;
@property(nonatomic, assign) BOOL elementsHidden;
@property (nonatomic, strong) JGProgressHUD *hud;
@property (nonatomic, retain) NSString *fileextension;
- (void)addHideElementButton;
- (void)hideElementButtonHandler:(UIButton *)sender;
- (void)addDownloadButton;
- (void) downloadButtonHandler:(UIButton *)sender;
- (void)downloadVideo:(AWEAwemeBaseViewController *)rootVC;
- (void)downloadMusic:(AWEAwemeBaseViewController *)rootVC;
- (void)copyMusic:(AWEAwemeBaseViewController *)rootVC;
- (void)copyVideo:(AWEAwemeBaseViewController *)rootVC;
- (void)copyDecription:(AWEAwemeBaseViewController *)rootVC;
@end
@interface TTKStoryDetailTableViewCell () <BHDownloadDelegate>
@end

@interface TTKFeedPassthroughStackView: UIStackView
@end

@interface TUXActionSheetAction: NSObject
@property(nonatomic) NSString *title;
@property(nonatomic) NSString *subtitle;
@property(nonatomic) NSString *imageLabel;
@property(nonatomic) UIImage *image;
- (instancetype)initWithStyle:(NSUInteger)style title:(NSString *)title subtitle:(NSString *)subtitle image:(UIImage *)image imageLabel:(NSString *)imageLabel handler:(void (^ __nullable)(TUXActionSheetAction *action))handler;
@end

@interface TUXActionSheetController: UIViewController
@property(nonatomic, assign, readwrite) BOOL dismissOnDraggingDown;
@property(nonatomic, strong, readwrite) UITableView *tableView;
- (instancetype)initWithTitle:(NSString *)title;
- (void)addAction:(TUXActionSheetAction *)action;
@end

@interface AWEUIAlertView: UIView
+ (void)showAlertWithTitle:(NSString *)title description:(NSString *)description image:(UIImage *)image actionButtonTitle:(NSString *)actionButtonTitle cancelButtonTitle:(NSString *)cancelButtonTitle actionBlock:(void (^)(void))actionBlock cancelBlock:(void (^)(void))cancelBlock;
@end

@interface AWEToast: NSObject
+ (void)showSuccess:(NSString *)title;
@end

@interface AWEPlayVideoPlayerController : NSObject
@property(nonatomic) AWEAwemeBaseViewController *container;
- (void)setPlayerSeekTime:(double)arg1 completion:(id)arg2;
@end

@interface TTKSearchEntranceButton: UIButton
@end

@interface AWEPlayInteractionWarningElementView: UIView
- (id)warningImage;
- (id)warningLabel;
@end
@interface AWEFeedContainerViewController: UIViewController
@property(nonatomic, strong, readwrite) TTKSearchEntranceButton *searchEntranceView;
@end

@interface AWENewFeedTableViewController : UIViewController
@property(nonatomic, weak, readwrite) UIViewController *tabContainerController;
@property (nonatomic, assign, readonly) AWEAwemeModel *currentAweme;
- (void)scrollToNextVideo;
- (void)pause;
- (void)stop;
@end

@interface TTKProfileBaseComponentModel : NSObject
@property (nonatomic, strong) NSString *componentID;
@property (nonatomic, copy, readwrite) NSString *name;
@property (nonatomic, copy, readwrite) NSArray *components;
@property (nonatomic, copy, readwrite) NSDictionary *bizData;
- (NSString *)formattedStringFromNumber:(NSNumber *)number; //new 
- (NSNumber *)numberFromUserDefaultsForKey:(NSString *)key; // new
@end

static BOOL is_iPad() {
    if ([(NSString *)[UIDevice currentDevice].model hasPrefix:@"iPad"]) {
        return YES;
    }
    return NO;
}

static UIViewController * _Nullable _topMostController(UIViewController * _Nonnull cont) {
    UIViewController *topController = cont;
    while (topController.presentedViewController) {
        topController = topController.presentedViewController;
    }
    if ([topController isKindOfClass:[UINavigationController class]]) {
        UIViewController *visible = ((UINavigationController *)topController).visibleViewController;
        if (visible) {
            topController = visible;
        }
    }
    return (topController != cont ? topController : nil);
}
static UIViewController * _Nonnull topMostController() {
    UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
    UIViewController *next = nil;
    while ((next = _topMostController(topController)) != nil) {
        topController = next;
    }
    return topController;
}


================================================
FILE: Tweak.x
================================================
#import "TikTokHeaders.h"

NSArray *jailbreakPaths;

static void showConfirmation(void (^okHandler)(void)) {
  [%c(AWEUIAlertView) showAlertWithTitle:@"BHTikTok, Hi" description:@"Are you sure?" image:nil actionButtonTitle:@"Yes" cancelButtonTitle:@"No" actionBlock:^{
    okHandler();
  } cancelBlock:nil];
}

%hook AppDelegate
- (_Bool)application:(UIApplication *)application didFinishLaunchingWithOptions:(id)arg2 {
    %orig;
    if ([[NSUserDefaults standardUserDefaults] boolForKey:@"flex_enebaled"]) {
        [[%c(FLEXManager) performSelector:@selector(sharedManager)] performSelector:@selector(showExplorer)];
    }
    if (![[NSUserDefaults standardUserDefaults] objectForKey:@"BHTikTokFirstRun"]) {
        [[NSUserDefaults standardUserDefaults] setValue:@"BHTikTokFirstRun" forKey:@"BHTikTokFirstRun"];
        [[NSUserDefaults standardUserDefaults] setBool:true forKey:@"hide_ads"];
        [[NSUserDefaults standardUserDefaults] setBool:true forKey:@"download_button"];
        [[NSUserDefaults standardUserDefaults] setBool:true forKey:@"remove_elements_button"];
        [[NSUserDefaults standardUserDefaults] setBool:true forKey:@"show_porgress_bar"];
        [[NSUserDefaults standardUserDefaults] setBool:true forKey:@"save_profile"];
        [[NSUserDefaults standardUserDefaults] setBool:true forKey:@"copy_profile_information"];
        [[NSUserDefaults standardUserDefaults] setBool:true forKey:@"extended_bio"];
        [[NSUserDefaults standardUserDefaults] setBool:true forKey:@"extendedComment"];
    }
    [BHIManager cleanCache];
    return true;
}

static BOOL isAuthenticationShowed = FALSE;
- (void)applicationDidBecomeActive:(id)arg1 { // old app lock TODO: add face-id
  %orig;

  if ([BHIManager appLock] && !isAuthenticationShowed) {
    UIViewController *rootController = [[self window] rootViewController];
    SecurityViewController *securityViewController = [SecurityViewController new];
    securityViewController.modalPresentationStyle = UIModalPresentationOverFullScreen;
    [rootController presentViewController:securityViewController animated:YES completion:nil];
    isAuthenticationShowed = TRUE;
  }
}

- (void)applicationWillEnterForeground:(id)arg1 {
  %orig;
  isAuthenticationShowed = FALSE;
}
%end

%hook TTKMediaSpeedControlService
- (void)setPlaybackRate:(CGFloat)arg1 {
    NSNumber *speed = [BHIManager selectedSpeed];
    if (![BHIManager speedEnabled] || [speed isEqualToNumber:@1]) {
        return %orig;
    }
    if ([BHIManager speedEnabled]) {
        if ([BHIManager selectedSpeed]) {
            return %orig([speed floatValue]);
        }
    } else {
        return %orig;
    }
}
%end

%hook AWEUserWorkCollectionViewCell
- (void)configWithModel:(id)arg1 isMine:(BOOL)arg2 { // Video like count & upload date lables
    %orig;
    if ([BHIManager videoLikeCount] || [BHIManager videoUploadDate]) {
        for (int i = 0; i < [[self.contentView subviews] count]; i ++) {
            UIView *j = [[self.contentView subviews] objectAtIndex:i];
            if (j.tag == 1001) {
                [j removeFromSuperview];
            } 
            else if (j.tag == 1002) {
                [j removeFromSuperview];
            }
        }

        AWEAwemeModel *model = [self model];
        AWEAwemeStatisticsModel *statistics = [model statistics];
        NSNumber *createTime = [model createTime];
        NSNumber *likeCount = [statistics diggCount];
        NSString *likeCountFormatted = [self formattedNumber:[likeCount integerValue]];
        NSString *formattedDate = [self formattedDateStringFromTimestamp:[createTime doubleValue]];

        UILabel *likeCountLabel = [UILabel new];
        likeCountLabel.text = likeCountFormatted;
        likeCountLabel.textColor = [UIColor whiteColor];
        likeCountLabel.font = [UIFont boldSystemFontOfSize:13.0];
        likeCountLabel.tag = 1001;
        [likeCountLabel setTranslatesAutoresizingMaskIntoConstraints:false];
        
        UIImageView *heartImage = [UIImageView new];
        heartImage.image = [UIImage systemImageNamed:@"heart"];
        heartImage.tintColor = [UIColor whiteColor];
        [heartImage setTranslatesAutoresizingMaskIntoConstraints:false];

        UILabel *uploadDateLabel = [UILabel new];
        uploadDateLabel.text = formattedDate;
        uploadDateLabel.textColor = [UIColor whiteColor];
        uploadDateLabel.font = [UIFont boldSystemFontOfSize:13.0];
        uploadDateLabel.tag = 1002;
        [uploadDateLabel setTranslatesAutoresizingMaskIntoConstraints:false];

        UIImageView *clockImage = [UIImageView new];
        clockImage.image = [UIImage systemImageNamed:@"clock"];
        clockImage.tintColor = [UIColor whiteColor];
        [clockImage setTranslatesAutoresizingMaskIntoConstraints:false];
        

        for (int i = 0; i < [[self.contentView subviews] count]; i ++) {
            UIView *j = [[self.contentView subviews] objectAtIndex:i];
            if (j.tag == 1001) {
                [j removeFromSuperview];
            } 
            else if (j.tag == 1002) {
                [j removeFromSuperview];
            }
        }
        if ([BHIManager videoLikeCount]) {
        [self.contentView addSubview:heartImage];
        [NSLayoutConstraint activateConstraints:@[
                [heartImage.topAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.topAnchor constant:110],
                [heartImage.leadingAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.leadingAnchor constant:4],
                [heartImage.widthAnchor constraintEqualToConstant:16],
                [heartImage.heightAnchor constraintEqualToConstant:16],
            ]];
        [self.contentView addSubview:likeCountLabel];
        [NSLayoutConstraint activateConstraints:@[
                [likeCountLabel.topAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.topAnchor constant:109],
                [likeCountLabel.leadingAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.leadingAnchor constant:23],
                [likeCountLabel.widthAnchor constraintEqualToConstant:200],
                [likeCountLabel.heightAnchor constraintEqualToConstant:16],
            ]];
        }
        if ([BHIManager videoUploadDate]) {
        [self.contentView addSubview:clockImage];
        [NSLayoutConstraint activateConstraints:@[
                [clockImage.topAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.topAnchor constant:128],
                [clockImage.leadingAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.leadingAnchor constant:4],
                [clockImage.widthAnchor constraintEqualToConstant:16],
                [clockImage.heightAnchor constraintEqualToConstant:16],
            ]];
        [self.contentView addSubview:uploadDateLabel];
        [NSLayoutConstraint activateConstraints:@[
                [uploadDateLabel.topAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.topAnchor constant:127],
                [uploadDateLabel.leadingAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.leadingAnchor constant:23],
                [uploadDateLabel.widthAnchor constraintEqualToConstant:200],
                [uploadDateLabel.heightAnchor constraintEqualToConstant:16],
            ]];
        }
    }
}
%new - (NSString *)formattedNumber:(NSInteger)number {

    if (number >= 1000000) {
        return [NSString stringWithFormat:@"%.1fm", number / 1000000.0];
    } else if (number >= 1000) {
        return [NSString stringWithFormat:@"%.1fk", number / 1000.0];
    } else {
        return [NSString stringWithFormat:@"%ld", (long)number];
    }

}
%new - (NSString *)formattedDateStringFromTimestamp:(NSTimeInterval)timestamp {

    NSDate *date = [NSDate dateWithTimeIntervalSince1970:timestamp];
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    dateFormatter.dateFormat = @"dd.MM.yy"; 
    return [dateFormatter stringFromDate:date];

}
%end

%hook TTKProfileRootView
- (void)layoutSubviews { // Video count
    %orig;
    if ([BHIManager profileVideoCount]){
        TTKProfileOtherViewController *rootVC = [self yy_viewController];
        AWEUserModel *user = [rootVC user];
        NSNumber *userVideoCount = [user visibleVideosCount];
        if (userVideoCount){
            UILabel *userVideoCountLabel = [[UILabel alloc] initWithFrame:CGRectMake(0,2,100,20.5)];
            userVideoCountLabel.text = [NSString stringWithFormat:@"Video Count: %@", userVideoCount];
            userVideoCountLabel.font = [UIFont systemFontOfSize:9.0];
            [self addSubview:userVideoCountLabel];
        }
    }
}
%end

%hook BDImageView
- (void)layoutSubviews { // Profile save
    %orig;
    if ([BHIManager profileSave]) {
        [self addHandleLongPress];
    }
}
%new - (void)addHandleLongPress {
    UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
    longPress.minimumPressDuration = 0.3;
    [self addGestureRecognizer:longPress];
}
%new - (void)handleLongPress:(UILongPressGestureRecognizer *)sender {
    if (sender.state == UIGestureRecognizerStateBegan) {
        [%c(AWEUIAlertView) showAlertWithTitle:@"Save profile image" description:@"Do you want to save this image" image:nil actionButtonTitle:@"Yes" cancelButtonTitle:@"No" actionBlock:^{
            UIImageWriteToSavedPhotosAlbum([self bd_baseImage], self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
  } cancelBlock:nil];
    }
}
%new - (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {
    if (error) {
        NSLog(@"Error saving image: %@", error.localizedDescription);
    } else {
        NSLog(@"Image successfully saved to Photos app");
    }
}
%end

%hook AWEUserNameLabel // fake verification
- (void)layoutSubviews {
    %orig;
    if ([self.yy_viewController isKindOfClass:(%c(TTKProfileHomeViewController))] && [BHIManager fakeVerified]) {
        [self addVerifiedIcon:true];
    }
}
%end

%hook TTTAttributedLabel // copy profile decription
- (void)layoutSubviews {
    %orig;
    if ([BHIManager profileCopy]){
        [self addHandleLongPress];
    }
}
%new - (void)addHandleLongPress {
    UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
    longPress.minimumPressDuration = 0.3;
    [self addGestureRecognizer:longPress];
}
%new - (void)handleLongPress:(UILongPressGestureRecognizer *)sender {
    if (sender.state == UIGestureRecognizerStateBegan) {
        NSString *profileDescription = [self text];
        [%c(AWEUIAlertView) showAlertWithTitle:@"Copy bio" description:@"Do you want to copy this text to clipboard" image:nil actionButtonTitle:@"Yes" cancelButtonTitle:@"No" actionBlock:^{
             if (profileDescription) {
                                                                    UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
                                                                    pasteboard.string = profileDescription;
                                                                }
  } cancelBlock:nil];
    }
}
%end

%hook TTKSettingsBaseCellPlugin
- (void)didSelectItemAtIndex:(NSInteger)index {
    if ([self.itemModel.identifier isEqualToString:@"bhtiktok_settings"]) {
        UINavigationController *BHTikTokSettings = [[UINavigationController alloc] initWithRootViewController:[[ViewController alloc] init]];
        [topMostController() presentViewController:BHTikTokSettings animated:true completion:nil];
    } else {
        return %orig;
    }
}
%end

%hook AWESettingsNormalSectionViewModel
- (void)viewDidLoad {
    %orig;
    if ([self.sectionIdentifier isEqualToString:@"account"]) {
        TTKSettingsBaseCellPlugin *BHTikTokSettingsPluginCell = [[%c(TTKSettingsBaseCellPlugin) alloc] initWithPluginContext:self.context];

        AWESettingItemModel *BHTikTokSettingsItemModel = [[%c(AWESettingItemModel) alloc] initWithIdentifier:@"bhtiktok_settings"];
        [BHTikTokSettingsItemModel setTitle:@"BHTikTok++ settings"];
        [BHTikTokSettingsItemModel setDetail:@"BHTikTok++ settings"];
        [BHTikTokSettingsItemModel setIconImage:[UIImage systemImageNamed:@"gear"]];
        [BHTikTokSettingsItemModel setType:99];

        [BHTikTokSettingsPluginCell setItemModel:BHTikTokSettingsItemModel];

        [self insertModel:BHTikTokSettingsPluginCell atIndex:0 animated:true];
    }
}
%end

%hook SparkViewController // alwaysOpenSafari
- (void)viewWillAppear:(BOOL)animated {
    if (![BHIManager alwaysOpenSafari]) {
        return %orig;
    }
    
    // NSURL *url = self.originURL;
    NSURLComponents *components = [NSURLComponents componentsWithURL:self.originURL resolvingAgainstBaseURL:NO];
    NSString *searchParameter = @"url";
    NSString *searchValue = nil;
    
    for (NSURLQueryItem *queryItem in components.queryItems) {
        if ([queryItem.name isEqualToString:searchParameter]) {
            searchValue = queryItem.value;
            break;
        }
    }
    
    // In-app browser is used for two-factor authentication with security key,
    // login will not complete successfully if it's redirected to Safari
    // if ([urlStr containsString:@"twitter.com/account/"] || [urlStr containsString:@"twitter.com/i/flow/"]) {
    //     return %orig;
    // }

    if (searchValue) {
        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:searchValue] options:@{} completionHandler:nil];
        [self didTapCloseButton];
    } else {
        return %orig;
    }
}
%end

%hook CTCarrier // changes country 
- (NSString *)mobileCountryCode {
    if ([BHIManager regionChangingEnabled]) {
        if ([BHIManager selectedRegion]) {
            NSDictionary *selectedRegion = [BHIManager selectedRegion];
            return selectedRegion[@"mcc"];
        }
        return %orig;
    }
    return %orig;
}

- (void)setIsoCountryCode:(NSString *)arg1 {
    if ([BHIManager regionChangingEnabled]) {
        if ([BHIManager selectedRegion]) {
            NSDictionary *selectedRegion = [BHIManager selectedRegion];
            return %orig(selectedRegion[@"code"]);
        }
        return %orig(arg1);
    }
    return %orig(arg1);
}

- (NSString *)isoCountryCode {
    if ([BHIManager regionChangingEnabled]) {
        if ([BHIManager selectedRegion]) {
            NSDictionary *selectedRegion = [BHIManager selectedRegion];
            return selectedRegion[@"code"];
        }
        return %orig;
    }
    return %orig;
}

- (NSString *)mobileNetworkCode {
    if ([BHIManager regionChangingEnabled]) {
        if ([BHIManager selectedRegion]) {
            NSDictionary *selectedRegion = [BHIManager selectedRegion];
            return selectedRegion[@"mnc"];
        }
        return %orig;
    }
    return %orig;
}
%end
%hook TTKStoreRegionService
- (id)storeRegion {
    if ([BHIManager regionChangingEnabled]) {
        if ([BHIManager selectedRegion]) {
            NSDictionary *selectedRegion = [BHIManager selectedRegion];
            return [selectedRegion[@"code"] lowercaseString];
        }
        return %orig;
    }
    return %orig;
}
- (id)getStoreRegion {
    if ([BHIManager regionChangingEnabled]) {
        if ([BHIManager selectedRegion]) {
            NSDictionary *selectedRegion = [BHIManager selectedRegion];
            return [selectedRegion[@"code"] lowercaseString];
        }
        return %orig;
    }
    return %orig;
}
- (void)setStoreRegion:(id)arg1 {
    if ([BHIManager regionChangingEnabled]) {
        if ([BHIManager selectedRegion]) {
            NSDictionary *selectedRegion = [BHIManager selectedRegion];
            return %orig([selectedRegion[@"code"] lowercaseString]);
        }
        return %orig(arg1);
    }
    return %orig(arg1);
}
%end
%hook TIKTOKRegionManager
+ (NSString *)systemRegion {
    if ([BHIManager regionChangingEnabled]) {
        if ([BHIManager selectedRegion]) {
            NSDictionary *selectedRegion = [BHIManager selectedRegion];
            return selectedRegion[@"code"];
        }
        return %orig;
    }
    return %orig;
}
+ (id)region {
    if ([BHIManager regionChangingEnabled]) {
        if ([BHIManager selectedRegion]) {
            NSDictionary *selectedRegion = [BHIManager selectedRegion];
            return selectedRegion[@"code"];
        }
        return %orig;
    }
    return %orig;
}
+ (id)mccmnc {
    if ([BHIManager regionChangingEnabled]) {
        if ([BHIManager selectedRegion]) {
            NSDictionary *selectedRegion = [BHIManager selectedRegion];
            return [NSString stringWithFormat:@"%@%@", selectedRegion[@"mcc"], selectedRegion[@"mnc"]];
        }
        return %orig;
    }
    return %orig;
}
+ (id)storeRegion {
    if ([BHIManager regionChangingEnabled]) {
        if ([BHIManager selectedRegion]) {
            NSDictionary *selectedRegion = [BHIManager selectedRegion];
            return selectedRegion[@"code"];
        }
        return %orig;
    }
    return %orig;
}
+ (id)currentRegionV2 {
    if ([BHIManager regionChangingEnabled]) {
        if ([BHIManager selectedRegion]) {
            NSDictionary *selectedRegion = [BHIManager selectedRegion];
            return selectedRegion[@"code"];
        }
        return %orig;
    }
    return %orig;
}
+ (id)localRegion {
        if ([BHIManager regionChangingEnabled]) {
        if ([BHIManager selectedRegion]) {
            NSDictionary *selectedRegion = [BHIManager selectedRegion];
            return selectedRegion[@"code"];
        }
        return %orig;
    }
    return %orig;
}

%end

%hook TTKPassportAppStoreRegionModel
- (id)storeRegion {
    if ([BHIManager regionChangingEnabled]) {
        if ([BHIManager selectedRegion]) {
            NSDictionary *selectedRegion = [BHIManager selectedRegion];
            return [selectedRegion[@"code"] lowercaseString];
        }
        return %orig;
    }
    return %orig;
}
- (void)setStoreRegion:(id)arg1 {
    if ([BHIManager regionChangingEnabled]) {
        if ([BHIManager selectedRegion]) {
            NSDictionary *selectedRegion = [BHIManager selectedRegion];
            return %orig([selectedRegion[@"code"] lowercaseString]);
        }
        return %orig(arg1);
    }
    return %orig(arg1);
}
- (void)setLocalizedCountryName:(id)arg1 {
    if ([BHIManager regionChangingEnabled]) {
        if ([BHIManager selectedRegion]) {
            NSDictionary *selectedRegion = [BHIManager selectedRegion];
            return %orig(selectedRegion[@"name"]);
        }
        return %orig(arg1);
    }
    return %orig(arg1);
}
- (id)localizedCountryName {
    if ([BHIManager regionChangingEnabled]) {
        if ([BHIManager selectedRegion]) {
            NSDictionary *selectedRegion = [BHIManager selectedRegion];
            return selectedRegion[@"name"];
        }
        return %orig;
    }
    return %orig;
}
%end

%hook ATSRegionCacheManager
- (id)getRegion {
 if ([BHIManager regionChangingEnabled]) {
        if ([BHIManager selectedRegion]) {
            NSDictionary *selectedRegion = [BHIManager selectedRegion];
            return [selectedRegion[@"code"] lowercaseString];
        }
        return %orig;
    }
    return %orig;
}
- (id)storeRegionFromCache {
 if ([BHIManager regionChangingEnabled]) {
        if ([BHIManager selectedRegion]) {
            NSDictionary *selectedRegion = [BHIManager selectedRegion];
            return [selectedRegion[@"code"] lowercaseString];
        }
        return %orig;
    }
    return %orig;
}
- (id)storeRegionFromTTNetNotification:(id)arg1 {
 if ([BHIManager regionChangingEnabled]) {
        if ([BHIManager selectedRegion]) {
            NSDictionary *selectedRegion = [BHIManager selectedRegion];
            return [selectedRegion[@"code"] lowercaseString];
        }
        return %orig;
    }
    return %orig;
}
- (void)setRegion:(id)arg1 {
    if ([BHIManager regionChangingEnabled]) {
        if ([BHIManager selectedRegion]) {
            NSDictionary *selectedRegion = [BHIManager selectedRegion];
            return %orig([selectedRegion[@"code"] lowercaseString]);
        }
        return %orig(arg1);
    }
    return %orig(arg1);
}
- (id)region {
 if ([BHIManager regionChangingEnabled]) {
        if ([BHIManager selectedRegion]) {
            NSDictionary *selectedRegion = [BHIManager selectedRegion];
            return [selectedRegion[@"code"] lowercaseString];
        }
        return %orig;
    }
    return %orig;
}
%end

%hook TTKStoreRegionModel
- (id)storeRegion {
 if ([BHIManager regionChangingEnabled]) {
        if ([BHIManager selectedRegion]) {
            NSDictionary *selectedRegion = [BHIManager selectedRegion];
            return selectedRegion[@"code"];
        }
        return %orig;
    }
    return %orig;
}
- (void)setStoreRegion:(id)arg1 {
    if ([BHIManager regionChangingEnabled]) {
        if ([BHIManager selectedRegion]) {
            NSDictionary *selectedRegion = [BHIManager selectedRegion];
            return %orig(selectedRegion[@"code"]);
        }
        return %orig(arg1);
    }
    return %orig(arg1);
}
%end

%hook TTInstallIDManager
- (id)currentAppRegion {
 if ([BHIManager regionChangingEnabled]) {
        if ([BHIManager selectedRegion]) {
            NSDictionary *selectedRegion = [BHIManager selectedRegion];
            return selectedRegion[@"code"];
        }
        return %orig;
    }
    return %orig;
}
- (void)setCurrentAppRegion:(id)arg1 {
    if ([BHIManager regionChangingEnabled]) {
        if ([BHIManager selectedRegion]) {
            NSDictionary *selectedRegion = [BHIManager selectedRegion];
            return %orig(selectedRegion[@"code"]);
        }
        return %orig(arg1);
    }
    return %orig(arg1);
}
%end

%hook BDInstallGlobalConfig
- (id)currentAppRegion {
 if ([BHIManager regionChangingEnabled]) {
        if ([BHIManager selectedRegion]) {
            NSDictionary *selectedRegion = [BHIManager selectedRegion];
            return selectedRegion[@"code"];
        }
        return %orig;
    }
    return %orig;
}
- (void)setCurrentAppRegion:(id)arg1 {
    if ([BHIManager regionChangingEnabled]) {
        if ([BHIManager selectedRegion]) {
            NSDictionary *selectedRegion = [BHIManager selectedRegion];
            return %orig(selectedRegion[@"code"]);
        }
        return %orig(arg1);
    }
    return %orig(arg1);
}
%end

%hook ACCCreationPublishAction
- (BOOL)is_open_hd {
    if ([BHIManager uploadHD]) {
        return 1;
    }
    return %orig;
}
- (void)setIs_open_hd:(BOOL)arg1 {
    if ([BHIManager uploadHD]) {
        %orig(1);
    }
    else {
        return %orig;
    }
}
- (BOOL)is_have_hd {
    if ([BHIManager uploadHD]) {
        return 1;
    }
    return %orig;
}
- (void)setIs_have_hd:(BOOL)arg1 {
    if ([BHIManager uploadHD]) {
        %orig(1);
    }
    else {
        return %orig;
    }
}

%end

%hook TTKCommentPanelViewController
- (void)viewDidLoad {
    %orig;
    if ([BHIManager transparentCommnet]){
        UIView *commnetView = [self view];
        [commnetView setAlpha:0.90];
    }
}
%end

%hook AWEAwemeModel // no ads, show porgress bar
- (id)initWithDictionary:(id)arg1 error:(id *)arg2 {
    id orig = %orig;
    return [BHIManager hideAds] && self.isAds ? nil : orig;
}
- (id)init {
    id orig = %orig;
    return [BHIManager hideAds] && self.isAds ? nil : orig;
}

- (BOOL)progressBarDraggable {
    return [BHIManager progressBar] || %orig;
}
- (BOOL)progressBarVisible {
    return [BHIManager progressBar] || %orig;
}
- (void)live_callInitWithDictyCategoryMethod:(id)arg1 {
    if (![BHIManager disableLive]) {
        %orig;
    }
}
+ (id)liveStreamURLJSONTransformer {
    if ([BHIManager disableLive]) {
        return nil;
    }
    return %orig;
}
+ (id)relatedLiveJSONTransformer {
    if ([BHIManager disableLive]) {
        return nil;
    }
    return %orig;
}
+ (id)rawModelFromLiveRoomModel:(id)arg1 {
    if ([BHIManager disableLive]) {
        return nil;
    }
    return %orig;
}
+ (id)aweLiveRoom_subModelPropertyKey {
    if ([BHIManager disableLive]) {
        return nil;
    }
    return %orig;
}
%end

%hook AWEPlayInteractionWarningElementView
- (id)warningImage {
    if ([BHIManager disableWarnings]) {
        return nil;
    }
    return %orig;
}
- (id)warningLabel {
    if ([BHIManager disableWarnings]) {
        return nil;
    }
    return %orig;
}
%end

%hook TUXLabel
- (void)setText:(NSString*)arg1 {
    if ([BHIManager showUsername]) {
        if ([[[self superview] superview] isKindOfClass:%c(AWEPlayInteractionAuthorUserNameButton)]){
            AWEFeedCellViewController *rootVC = [[[self superview] superview] yy_viewController];
            AWEAwemeModel *model = rootVC.model;
            AWEUserModel *authorModel = model.author;
            NSString *nickname = authorModel.nickname;
            NSString *username = authorModel.socialName;
            %orig(username);
        }else {
            %orig;
        }
    }else {
        %orig;
    }
}
%end

%hook AWENewFeedTableViewController
- (BOOL)disablePullToRefreshGestureRecognizer {
    if ([BHIManager disablePullToRefresh]){
        return 1;
    }
    return %orig;
}

%end

%hook AWEPlayVideoPlayerController // auto play next video and stop looping video
- (void)playerWillLoopPlaying:(id)arg1 {
    if ([BHIManager autoPlay]) {
        if ([self.container.parentViewController isKindOfClass:%c(AWENewFeedTableViewController)]) {
            [((AWENewFeedTableViewController *)self.container.parentViewController) scrollToNextVideo];
            return;
        }
    }
    %orig;
}
- (BOOL)loop {
    if ([BHIManager stopPlay]) {
        return 0;
    }
    return %orig; 
}
- (void)setLoop:(BOOL)arg1 {
    if ([BHIManager stopPlay]) {
        %orig(0);
    }else {
        %orig;
    }
}
%end

%hook AWEMaskInfoModel // Disable Unsensitive Content
- (BOOL)showMask {
    if ([BHIManager disableUnsensitive]) {
        return 0;
    }
    return %orig;
}
- (void)setShowMask:(BOOL)arg1 {
    if ([BHIManager disableUnsensitive]) {
        %orig(0);
    }
    else {
        %orig;
    }
}
%end

%hook AWEAwemeACLItem // remove default watermark
- (void)setWatermarkType:(NSUInteger)arg1 {
    if ([BHIManager removeWatermark]){
        %orig(1);
    }
    else { 
        %orig;
    }
    
}
- (NSUInteger)watermarkType {
    if ([BHIManager removeWatermark]){
        return 1;
    }
    return %orig;
}
%end

%hook UIButton // follow confirmation broken 
- (void)_onTouchUpInside {
    if ([BHIManager followConfirmation] && [self.currentTitle isEqualToString:@"Follow"]) {
        showConfirmation(^(void) { %orig; });
    } else {
        %orig;
    }
}
%end
%hook AWEPlayInteractionUserAvatarElement
- (void)onFollowViewClicked:(id)sender {
    if ([BHIManager followConfirmation]) {
        showConfirmation(^(void) { %orig; });
    } else {
        return %orig;
    }
}
%end

%hook TTKProfileBaseComponentModel // Fake Followers, Fake Following and FakeVerified.

- (NSDictionary *)bizData {
	if ([BHIManager fakeChangesEnabled]) {
		NSDictionary *originalData = %orig;
		NSMutableDictionary *modifiedData = [originalData mutableCopy];
		
		NSNumber *fakeFollowingCount = [self numberFromUserDefaultsForKey:@"following_count"];
		NSNumber *fakeFollowersCount = [self numberFromUserDefaultsForKey:@"follower_count"];
		
		if ([self.componentID isEqualToString:@"relation_info_follower"]) {
			modifiedData[@"follower_count"] = fakeFollowersCount ?: @0; 
		} else if ([self.componentID isEqualToString:@"relation_info_following"]) {
			modifiedData[@"following_count"] = fakeFollowingCount ?: @0; 
			modifiedData[@"formatted_number"] = [self formattedStringFromNumber:fakeFollowingCount ?: @0];
		} 
		return [modifiedData copy];
	}
	return %orig;
}

- (NSArray *)components {
	if ([BHIManager fakeVerified]) {
		NSArray *originalComponents = %orig;
		if ([self.componentID isEqualToString:@"user_account_base_info"] && originalComponents.count == 1) {
			NSMutableArray *modifiedComponents = [originalComponents mutableCopy];
			TTKProfileBaseComponentModel *fakeVerify = [%c(TTKProfileBaseComponentModel) new];
			fakeVerify.componentID = @"user_account_verify";
			fakeVerify.name = @"user_account_verify";
			[modifiedComponents addObject:fakeVerify];
			return [modifiedComponents copy];
		}
	}
	return %orig;
}

%new - (NSNumber *)numberFromUserDefaultsForKey:(NSString *)key {
    NSString *stringValue = [[NSUserDefaults standardUserDefaults] stringForKey:key];
    return (stringValue.length > 0) ? @([stringValue doubleValue]) : @0; 
}

%new - (NSString *)formattedStringFromNumber:(NSNumber *)number {
    if (!number) return @"0"; 

    double value = [number doubleValue];
    if (value == 0) return @"0"; 

    NSString *formattedString;
    if (value >= 1e9) {
        formattedString = [NSString stringWithFormat:@"%.1fB", value / 1e9];
    } else if (value >= 1e6) {
        formattedString = [NSString stringWithFormat:@"%.1fM", value / 1e6];
    } else if (value >= 1e3) {
        formattedString = [NSString stringWithFormat:@"%.1fk", value / 1e3];
    } else {
        formattedString = [NSString stringWithFormat:@"%.0f", value];
    }

    return formattedString;
}

%end

%hook AWEFeedVideoButton // like 
Download .txt
gitextract_wc0kqf1u/

├── .gitignore
├── BHDownload.h
├── BHDownload.m
├── BHIManager.h
├── BHIManager.m
├── BHMultipleDownload.h
├── BHMultipleDownload.m
├── BHTikTok.plist
├── JGProgressHUD/
│   ├── JGProgressHUD-Defines.h
│   ├── JGProgressHUD.h
│   ├── JGProgressHUD.m
│   ├── JGProgressHUDAnimation.h
│   ├── JGProgressHUDAnimation.m
│   ├── JGProgressHUDErrorIndicatorView.h
│   ├── JGProgressHUDErrorIndicatorView.m
│   ├── JGProgressHUDFadeAnimation.h
│   ├── JGProgressHUDFadeAnimation.m
│   ├── JGProgressHUDFadeZoomAnimation.h
│   ├── JGProgressHUDFadeZoomAnimation.m
│   ├── JGProgressHUDImageIndicatorView.h
│   ├── JGProgressHUDImageIndicatorView.m
│   ├── JGProgressHUDIndeterminateIndicatorView.h
│   ├── JGProgressHUDIndeterminateIndicatorView.m
│   ├── JGProgressHUDIndicatorView.h
│   ├── JGProgressHUDIndicatorView.m
│   ├── JGProgressHUDPieIndicatorView.h
│   ├── JGProgressHUDPieIndicatorView.m
│   ├── JGProgressHUDRingIndicatorView.h
│   ├── JGProgressHUDRingIndicatorView.m
│   ├── JGProgressHUDShadow.h
│   ├── JGProgressHUDShadow.m
│   ├── JGProgressHUDSuccessIndicatorView.h
│   └── JGProgressHUDSuccessIndicatorView.m
├── Makefile
├── README.md
├── SecurityViewController.h
├── SecurityViewController.m
├── Settings/
│   ├── CountryTable.h
│   ├── CountryTable.m
│   ├── LiveActions.h
│   ├── LiveActions.m
│   ├── PlaybackSpeed.h
│   ├── PlaybackSpeed.m
│   ├── ViewController.h
│   └── ViewController.m
├── TikTokHeaders.h
├── Tweak.x
└── control
Download .txt
SYMBOL INDEX (3 symbols across 1 files)

FILE: TikTokHeaders.h
  function end (line 383) | end
  function UIViewController (line 392) | static UIViewController * _Nullable _topMostController(UIViewController ...
  function UIViewController (line 405) | static UIViewController * _Nonnull topMostController() {
Condensed preview — 48 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (249K chars).
[
  {
    "path": ".gitignore",
    "chars": 37,
    "preview": ".theos/\n.vscode/\npackages/\n.DS_Store\n"
  },
  {
    "path": "BHDownload.h",
    "chars": 878,
    "preview": "//\n//  BHDownload.h\n//  DIYTableView\n//\n//  Created by BandarHelal on 12/01/1442 AH.\n//  Copyright © 1442 BandarHelal. A"
  },
  {
    "path": "BHDownload.m",
    "chars": 1698,
    "preview": "//\n//  BHDownload.m\n//  DIYTableView\n//\n//  Created by BandarHelal on 12/01/1442 AH.\n//  Copyright © 1442 BandarHelal. A"
  },
  {
    "path": "BHIManager.h",
    "chars": 1232,
    "preview": "#import <Foundation/Foundation.h>\n#import <UIKit/UIKit.h>\n\n@interface BHIManager: NSObject\n+ (BOOL)hideAds;\n+ (BOOL)down"
  },
  {
    "path": "BHIManager.m",
    "chars": 9359,
    "preview": "#import \"BHIManager.h\"\n#import \"TikTokHeaders.h\"\n\n@implementation BHIManager\n+ (BOOL)hideAds {\n    return [[NSUserDefaul"
  },
  {
    "path": "BHMultipleDownload.h",
    "chars": 535,
    "preview": "#import <Foundation/Foundation.h>\n\n@protocol BHMultipleDownloadDelegate <NSObject>\n- (void)downloaderProgress:(float)pro"
  },
  {
    "path": "BHMultipleDownload.m",
    "chars": 2734,
    "preview": "#import \"BHMultipleDownload.h\"\n\n@implementation BHMultipleDownload {\n    NSMutableArray<NSURL *> *_downloadedFilePaths;\n"
  },
  {
    "path": "BHTikTok.plist",
    "chars": 104,
    "preview": "{ Filter = { Bundles = ( \n    \"com.zhiliaoapp.musically\",\n    \"com.ss.iphone.ugc.Ame\",\n         ); }; }\n"
  },
  {
    "path": "JGProgressHUD/JGProgressHUD-Defines.h",
    "chars": 2395,
    "preview": "//\n//  JGProgressHUD-Defines.h\n//  JGProgressHUD\n//\n//  Created by Jonas Gessner on 28.04.15.\n//  Copyright (c) 2015 Jon"
  },
  {
    "path": "JGProgressHUD/JGProgressHUD.h",
    "chars": 13083,
    "preview": "//\n//  JGProgressHUD.h\n//  JGProgressHUD\n//\n//  Created by Jonas Gessner on 20.7.14.\n//  Copyright (c) 2014 Jonas Gessne"
  },
  {
    "path": "JGProgressHUD/JGProgressHUD.m",
    "chars": 39943,
    "preview": "//\n//  JGProgressHUD.m\n//  JGProgressHUD\n//\n//  Created by Jonas Gessner on 20.7.14.\n//  Copyright (c) 2014 Jonas Gessne"
  },
  {
    "path": "JGProgressHUD/JGProgressHUDAnimation.h",
    "chars": 1384,
    "preview": "//\n//  JGProgressHUDAnimation.h\n//  JGProgressHUD\n//\n//  Created by Jonas Gessner on 20.7.14.\n//  Copyright (c) 2014 Jon"
  },
  {
    "path": "JGProgressHUD/JGProgressHUDAnimation.m",
    "chars": 771,
    "preview": "//\n//  JGProgressHUDAnimation.m\n//  JGProgressHUD\n//\n//  Created by Jonas Gessner on 20.7.14.\n//  Copyright (c) 2014 Jon"
  },
  {
    "path": "JGProgressHUD/JGProgressHUDErrorIndicatorView.h",
    "chars": 582,
    "preview": "//\n//  JGProgressHUDErrorIndicatorView.h\n//  JGProgressHUD\n//\n//  Created by Jonas Gessner on 19.08.14.\n//  Copyright (c"
  },
  {
    "path": "JGProgressHUD/JGProgressHUDErrorIndicatorView.m",
    "chars": 1483,
    "preview": "//\n//  JGProgressHUDErrorIndicatorView.m\n//  JGProgressHUD\n//\n//  Created by Jonas Gessner on 19.08.14.\n//  Copyright (c"
  },
  {
    "path": "JGProgressHUD/JGProgressHUDFadeAnimation.h",
    "chars": 747,
    "preview": "//\n//  JGProgressHUDFadeAnimation.h\n//  JGProgressHUD\n//\n//  Created by Jonas Gessner on 20.7.14.\n//  Copyright (c) 2014"
  },
  {
    "path": "JGProgressHUD/JGProgressHUDFadeAnimation.m",
    "chars": 1310,
    "preview": "//\n//  JGProgressHUDFadeAnimation.m\n//  JGProgressHUD\n//\n//  Created by Jonas Gessner on 20.7.14.\n//  Copyright (c) 2014"
  },
  {
    "path": "JGProgressHUD/JGProgressHUDFadeZoomAnimation.h",
    "chars": 1026,
    "preview": "//\n//  JGProgressHUDFadeZoomAnimation.h\n//  JGProgressHUD\n//\n//  Created by Jonas Gessner on 20.7.14.\n//  Copyright (c) "
  },
  {
    "path": "JGProgressHUD/JGProgressHUDFadeZoomAnimation.m",
    "chars": 3144,
    "preview": "//\n//  JGProgressHUDFadeZoomAnimation.m\n//  JGProgressHUD\n//\n//  Created by Jonas Gessner on 20.7.14.\n//  Copyright (c) "
  },
  {
    "path": "JGProgressHUD/JGProgressHUDImageIndicatorView.h",
    "chars": 773,
    "preview": "//\n//  JGProgressHUDImageIndicatorView.h\n//  JGProgressHUD\n//\n//  Created by Jonas Gessner on 05.08.15.\n//  Copyright (c"
  },
  {
    "path": "JGProgressHUD/JGProgressHUDImageIndicatorView.m",
    "chars": 1038,
    "preview": "//\n//  JGProgressHUDImageIndicatorView.m\n//  JGProgressHUD\n//\n//  Created by Jonas Gessner on 05.08.15.\n//  Copyright (c"
  },
  {
    "path": "JGProgressHUD/JGProgressHUDIndeterminateIndicatorView.h",
    "chars": 680,
    "preview": "//\n//  JGProgressHUDIndeterminateIndicatorView.h\n//  JGProgressHUD\n//\n//  Created by Jonas Gessner on 19.07.14.\n//  Copy"
  },
  {
    "path": "JGProgressHUD/JGProgressHUDIndeterminateIndicatorView.m",
    "chars": 1843,
    "preview": "//\n//  JGProgressHUDIndeterminateIndicatorView.m\n//  JGProgressHUD\n//\n//  Created by Jonas Gessner on 19.07.14.\n//  Copy"
  },
  {
    "path": "JGProgressHUD/JGProgressHUDIndicatorView.h",
    "chars": 2441,
    "preview": "//\n//  JGProgressHUDIndicatorView.h\n//  JGProgressHUD\n//\n//  Created by Jonas Gessner on 20.7.14.\n//  Copyright (c) 2014"
  },
  {
    "path": "JGProgressHUD/JGProgressHUDIndicatorView.m",
    "chars": 2460,
    "preview": "//\n//  JGProgressHUDIndicatorView.m\n//  JGProgressHUD\n//\n//  Created by Jonas Gessner on 20.7.14.\n//  Copyright (c) 2014"
  },
  {
    "path": "JGProgressHUD/JGProgressHUDPieIndicatorView.h",
    "chars": 1056,
    "preview": "//\n//  JGProgressHUDPieIndicatorView.h\n//  JGProgressHUD\n//\n//  Created by Jonas Gessner on 19.07.14.\n//  Copyright (c) "
  },
  {
    "path": "JGProgressHUD/JGProgressHUDPieIndicatorView.m",
    "chars": 4588,
    "preview": "//\n//  JGProgressHUDPieIndicatorView.m\n//  JGProgressHUD\n//\n//  Created by Jonas Gessner on 19.07.14.\n//  Copyright (c) "
  },
  {
    "path": "JGProgressHUD/JGProgressHUDRingIndicatorView.h",
    "chars": 1310,
    "preview": "//\n//  JGProgressHUDRingIndicatorView.h\n//  JGProgressHUD\n//\n//  Created by Jonas Gessner on 20.7.14.\n//  Copyright (c) "
  },
  {
    "path": "JGProgressHUD/JGProgressHUDRingIndicatorView.m",
    "chars": 5656,
    "preview": "//\n//  JGProgressHUDRingIndicatorView.m\n//  JGProgressHUD\n//\n//  Created by Jonas Gessner on 20.7.14.\n//  Copyright (c) "
  },
  {
    "path": "JGProgressHUD/JGProgressHUDShadow.h",
    "chars": 1100,
    "preview": "//\n//  JGProgressHUDShadow.h\n//  JGProgressHUD\n//\n//  Created by Jonas Gessner on 25.09.17.\n//  Copyright © 2017 Jonas G"
  },
  {
    "path": "JGProgressHUD/JGProgressHUDShadow.m",
    "chars": 739,
    "preview": "//\n//  JGProgressHUDShadow.m\n//  JGProgressHUD\n//\n//  Created by Jonas Gessner on 25.09.17.\n//  Copyright © 2017 Jonas G"
  },
  {
    "path": "JGProgressHUD/JGProgressHUDSuccessIndicatorView.h",
    "chars": 590,
    "preview": "//\n//  JGProgressHUDSuccessIndicatorView.h\n//  JGProgressHUD\n//\n//  Created by Jonas Gessner on 19.08.14.\n//  Copyright "
  },
  {
    "path": "JGProgressHUD/JGProgressHUDSuccessIndicatorView.m",
    "chars": 1455,
    "preview": "//\n//  JGProgressHUDSuccessIndicatorView.m\n//  JGProgressHUD\n//\n//  Created by Jonas Gessner on 19.08.14.\n//  Copyright "
  },
  {
    "path": "Makefile",
    "chars": 598,
    "preview": "TARGET := iphone:clang:16.5\nINSTALL_TARGET_PROCESSES = TikTok\nTHEOS_DEVICE_IP = 192.168.100.246\nTHEOS_DEVICE_USER = root"
  },
  {
    "path": "README.md",
    "chars": 943,
    "preview": "# BHTikTok++\nAn awesome tweak for TikTok!\n\n# Telegram Channel \nhttps://t.me/BHTikTokPlusPlus\n\n# Features\n- No Ads\n- Down"
  },
  {
    "path": "SecurityViewController.h",
    "chars": 134,
    "preview": "#import <UIKit/UIKit.h>\n#import <LocalAuthentication/LocalAuthentication.h>\n\n@interface SecurityViewController : UIViewC"
  },
  {
    "path": "SecurityViewController.m",
    "chars": 1605,
    "preview": "#import \"SecurityViewController.h\"\n\n@implementation SecurityViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad"
  },
  {
    "path": "Settings/CountryTable.h",
    "chars": 74,
    "preview": "#import <UIKit/UIKit.h>\n\n@interface CountryTable : UIViewController\n\n@end\n"
  },
  {
    "path": "Settings/CountryTable.m",
    "chars": 6563,
    "preview": "#import \"CountryTable.h\"\n\n@interface CountryTable () <UITableViewDelegate, UITableViewDataSource>\n\n@property (nonatomic,"
  },
  {
    "path": "Settings/LiveActions.h",
    "chars": 73,
    "preview": "#import <UIKit/UIKit.h>\n\n@interface LiveActions : UIViewController\n\n@end\n"
  },
  {
    "path": "Settings/LiveActions.m",
    "chars": 3035,
    "preview": "#import \"LiveActions.h\"\n\n@interface LiveActions () <UITableViewDelegate, UITableViewDataSource>\n\n@property (nonatomic, s"
  },
  {
    "path": "Settings/PlaybackSpeed.h",
    "chars": 253,
    "preview": "//\n//  PlaybackSpeed.h\n//  StaticTableView\n//\n//  Created by raul on 16/10/2024.\n//\n\n#import <UIKit/UIKit.h>\n\nNS_ASSUME_"
  },
  {
    "path": "Settings/PlaybackSpeed.m",
    "chars": 2992,
    "preview": "//\n//  PlaybackSpeed.m\n//  StaticTableView\n//\n//  Created by raul on 16/10/2024.\n//\n\n#import \"PlaybackSpeed.h\"\n\n@interfa"
  },
  {
    "path": "Settings/ViewController.h",
    "chars": 255,
    "preview": "//\n//  ViewController.h\n//  StaticTableView\n//\n//  Created by raul on 08/10/2024.\n//\n\n#import <UIKit/UIKit.h>\n#import \"T"
  },
  {
    "path": "Settings/ViewController.m",
    "chars": 24862,
    "preview": "//\n//  ViewController.m\n//  StaticTableView\n//\n//  Created by raul on 08/10/2024.\n//\n\n#import \"ViewController.h\"\n#import"
  },
  {
    "path": "TikTokHeaders.h",
    "chars": 14652,
    "preview": "#import <UIKit/UIKit.h>\n#import <Foundation/Foundation.h>\n#import <SafariServices/SafariServices.h>\n#import \"BHIManager."
  },
  {
    "path": "Tweak.x",
    "chars": 72657,
    "preview": "#import \"TikTokHeaders.h\"\n\nNSArray *jailbreakPaths;\n\nstatic void showConfirmation(void (^okHandler)(void)) {\n  [%c(AWEUI"
  },
  {
    "path": "control",
    "chars": 216,
    "preview": "Package: com.raulsaeed.bhtiktok++\nName: BHTikTok++\nVersion: 1.5\nArchitecture: iphoneos-arm\nDescription: An awesome tweak"
  }
]

About this extraction

This page contains the full source code of the raulsaeed/TikTokPlusPlus GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 48 files (231.5 KB), approximately 56.0k tokens, and a symbol index with 3 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!