Skip to content

Commit

Permalink
feat: Refactoring iOS SDK
Browse files Browse the repository at this point in the history
- cleaner code
- SOLID principles
- clean architecture
  • Loading branch information
g-m-99 committed Jan 24, 2024
1 parent fc85afa commit 465f595
Show file tree
Hide file tree
Showing 14 changed files with 527 additions and 504 deletions.
10 changes: 10 additions & 0 deletions ios/BundleUpdater+FileManager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#import "BundleUpdater.h"

@interface BundleUpdater (FileManager)

- (void)copyFilesFromSource:(NSString *)sourceFolder toDestination:(NSString *)destinationFolder;
- (void)clearDocumentsFolder;
- (NSMutableData *)calculateSHA256Hash:(NSData *)script;
- (NSString *)loadHashFromDisk;

@end
102 changes: 102 additions & 0 deletions ios/BundleUpdater+FileManager.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#import "BundleUpdater+FileManager.h"
#import "CommonCrypto/CommonDigest.h"

@implementation BundleUpdater (FileManager)

/*!
* @brief Copy files from source to destination recursively
*
* @param sourceFolder Source folder
* @param destinationFolder Destination folder
*/
- (void)copyFilesFromSource:(NSString *)sourceFolder toDestination:(NSString *)destinationFolder {
NSFileManager *manager = [NSFileManager defaultManager];
NSError *error;
NSArray *contents = [manager contentsOfDirectoryAtPath:sourceFolder error:&error];

if (error) {
NSLog(@"[BUNDLE UPDATER SDK]: Error reading contents of directory %@: %@", sourceFolder, [error localizedDescription]);
return;
}

for (NSString *file in contents) {
NSString *sourceFilePath = [sourceFolder stringByAppendingPathComponent:file];
NSString *destinationFilePath = [destinationFolder stringByAppendingPathComponent:file];

BOOL isDir;
BOOL fileExistsAtPath = [manager fileExistsAtPath:sourceFilePath isDirectory:&isDir];

if (fileExistsAtPath) {
if (isDir) {
// It's a directory, create it in the destination
[manager createDirectoryAtPath:destinationFilePath withIntermediateDirectories:YES attributes:nil error:nil];

// Recursively copy the contents of the subdirectory
[self copyFilesFromSource:sourceFilePath toDestination:[destinationFolder stringByAppendingPathComponent:file]];
} else {
//NSLog(@"Copying to the destination: %@", destinationFilePath);
// It's a file, copy it to the destination
NSData *fileData = [NSData dataWithContentsOfFile:sourceFilePath];
[fileData writeToFile:destinationFilePath atomically:YES];
}
}
}
//Log the content of the destination folder
NSLog(@"[BUNDLE UPDATER SDK]: content of the %@ folder %@", sourceFolder, [manager contentsOfDirectoryAtPath:destinationFolder error:nil]);
}

/*!
* @brief clear the documents folder from files that are not intended to be there
*/
- (void)clearDocumentsFolder{
//TODO - custom method and custom class to interact with the documents folder
NSArray *dirs = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true);
NSArray *documents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:dirs.firstObject error:nil];
NSString *documentPath = dirs.firstObject;
NSFileManager *defManager = [NSFileManager defaultManager];
for (NSString *document in documents){
if([document isEqualToString:@"unzipped"] || [document isEqualToString:@"bundle.zip"]){
NSString *pathToDoc = [documentPath stringByAppendingPathComponent:document];
NSError *errorRemoving;
[defManager removeItemAtPath:pathToDoc error:&errorRemoving];
if(errorRemoving){
// TODO - understand what to do in this case
NSLog(@"[BUNDLE UPDATER ]: Error removing doc %@", document);
}
}
}
}

/*!
* @brief get the hash of a file
*
* @param script - data of the bundle
* *
* @return a data hash
*/
- (NSMutableData *)calculateSHA256Hash:(NSData *)script {
NSMutableData *hash =
[NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
CC_SHA256(script.bytes, (CC_LONG)script.length,
(unsigned char *)hash.mutableBytes);
return hash;
}

/*!
* @brief load the saved hash of the file from disk
*
* @return a string hash of the file
*/
- (NSString *)loadHashFromDisk {
NSString *hashPath = [[NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory, NSUserDomainMask, YES) firstObject]
stringByAppendingPathComponent:@"main.jsbundle.sha256"];
NSString *oldHash = [NSString stringWithContentsOfFile:hashPath
encoding:NSUTF8StringEncoding
error:nil];
return oldHash;
}



@end
7 changes: 7 additions & 0 deletions ios/BundleUpdater+Info.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#import "BundleUpdater.h"
#import <sys/utsname.h>

@interface BundleUpdater (Info)
- (NSString *)getDeviceModelName;
- (NSDictionary *)getMetaData;
@end
77 changes: 77 additions & 0 deletions ios/BundleUpdater+Info.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#import "BundleUpdater+Info.h"
@implementation BundleUpdater (Info)

/*!
* @brief get the device model info
*
* @return a string with the info of the device
*/
- (NSString *)getDeviceModelName {
struct utsname systemInfo;
uname(&systemInfo);
return [NSString stringWithCString:systemInfo.machine
encoding:NSUTF8StringEncoding];
}

/*!
* @brief get the a list of info as summary for the device
*
* @return a dictionary with the info
*/
- (NSDictionary *)getMetaData {
UIDevice *device = [UIDevice currentDevice];
NSString *device_name = device.name;
NSString *device_model = [self getDeviceModelName];
NSString *systemname = device.systemName;
NSString *systemVersion = device.systemVersion;
NSString *deviceIdentifier = [[device identifierForVendor] UUIDString];
NSString *bundleID = NSBundle.mainBundle.bundleIdentifier;
NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
NSString *marketingVersion = infoDictionary[@"CFBundleShortVersionString"];
NSString *projectVersion = infoDictionary[@"CFBundleVersion"];

NSString *preferredUserLocale =
[[[NSBundle mainBundle] preferredLocalizations] firstObject];
NSString *batteryLevel = @"Unknown";
// TODO NSString *phoneChargingState = [self getPhoneBatteryLevel];
// if (![phoneChargingState isEqualToString:@"Unknown"]) {
// batteryLevel = [NSString
// stringWithFormat:@"%.f", (float)[device batteryLevel] * 100];
// }
NSString *lowPowerModeEnabled =
[[NSProcessInfo processInfo] isLowPowerModeEnabled] ? @"true"
: @"false";
// NSDictionary *diskInfo = [self getDiskInfo];
NSString *buildMode = @"RELEASE";
#ifdef DEBUG
buildMode = @"DEBUG";
#endif
float scaleFactor = [[UIScreen mainScreen] scale];
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat screenWidth = screenRect.size.width;
CGFloat screenHeight = screenRect.size.height;
return @{
@"device_name" : device_name,
@"device_model" : device_model,
@"deviceIdentifier" : deviceIdentifier,
@"bundleID" : bundleID,
@"system_name" : systemname,
@"systemVersion" : systemVersion,
@"buildVersionNumber" : marketingVersion,
@"releaseVersionNumber" : projectVersion,
@"preferredUserLocale" : preferredUserLocale,
@"sdkVersion" : @"ALPHA-1", // SDK_VERSION",
@"buildMode" : buildMode,
@"batteryLevel" : batteryLevel,
// @"phoneChargingStatus" : phoneChargingState,
@"batterySaveMode" : lowPowerModeEnabled,
// @"totalDiskSpace" : [diskInfo objectForKey:@"totalSpace"],
// @"totalFreeDiskSpace" : [diskInfo
// objectForKey:@"totalFreeSpace"],
@"devicePixelRatio" : @(scaleFactor),
@"screenWidth" : @(screenWidth),
@"screenHeight" : @(screenHeight)
};
}

@end
7 changes: 7 additions & 0 deletions ios/BundleUpdater+UI.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

#import "BundleUpdater.h"

@interface BundleUpdater (UI)
- (void)showUpdateVC:(NSDictionary *)updateData withNecessaryUpdate:(BOOL)isNecessaryUpdate;
- (void)prepareAndHideBottomSheet;
@end
85 changes: 85 additions & 0 deletions ios/BundleUpdater+UI.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#import "BundleUpdater+UI.h"
#import "BundleUpdaterNitificationVC.h"

@implementation BundleUpdater (UI)

/*!
* @brief show the update bottomsheet if the config are supplied
*
* @param updateData - dictionary with the sheet config
*/
- (void)showUpdateVC:(NSDictionary *)updateData withNecessaryUpdate:(BOOL)isNecessaryUpdate{
NSDictionary *config = updateData[@"configuration"];
NSDictionary *actionBtn = updateData[@"actionBtn"];
// Set the data properties of the bottom sheet view controller
self.updaterVC.image = config[@"image"];
self.updaterVC.titleText = config[@"title"];
self.updaterVC.message = config[@"message"];
self.updaterVC.buttonLabel = actionBtn[@"label"];
self.updaterVC.buttonLink = actionBtn[@"label"];
self.updaterVC.buttonBackgroundColor = actionBtn[@"color"];
self.updaterVC.buttonIcon = [UIImage imageNamed:@"button_icon"];
self.updaterVC.footerLogo = [UIImage imageNamed:@"sdk_logo"];
if(isNecessaryUpdate){
self.updaterVC.isNecessaryUpdate = true;
}
NSString *type = config[@"type"];
if([type isEqualToString:@"notification"]){
//TODO - pass config to the notification
BundleUpdaterNitificationVC *notificationVC = [BundleUpdaterNitificationVC new];
notificationVC.isNecessaryUpdate = isNecessaryUpdate;
if([notificationVC isKindOfClass:[UIViewController class]]){
UIViewController *_notificationVC = (UIViewController *)notificationVC;
_notificationVC.modalPresentationStyle = UIModalPresentationOverCurrentContext;
// present the new viewContoller
dispatch_async(dispatch_get_main_queue(), ^{
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
UIViewController *rootViewController = window.rootViewController;
[rootViewController presentViewController:_notificationVC animated:NO completion:nil];
});
return;
}else{
NSLog(@"[BUNDLE UPDATER SDK]: It's NOT an UIViewController, display normal bottomsheet");
}
}else if([type isEqualToString:@"modal"]){
self.updaterVC.isModal = true;
}
UIViewController *rootViewController =
[[[UIApplication sharedApplication] keyWindow] rootViewController];
self.updaterVC.modalPresentationStyle = UIModalPresentationOverFullScreen;
self.updaterVC.transitioningDelegate = self; // TODO
[rootViewController presentViewController:self.updaterVC
animated:YES
completion:nil];
}

/*!
* @brief prepare the updaterVC before hiding the
*/
- (void)prepareAndHideBottomSheet {
dispatch_async(dispatch_get_main_queue(), ^{
// TODO - weak self
[UIView animateWithDuration:0.2 animations:^{
self.updaterVC.loadingView.alpha = 0;
self.updaterVC.backgroundView.alpha = 0;
}];
[self.updaterVC.spinner stopAnimating];
[NSTimer scheduledTimerWithTimeInterval:0.2
target:self
selector:@selector(hideBottomSheet)
userInfo:nil
repeats:NO];
});
}

/*!
* @brief hide the bottomsheet*
*/
- (void)hideBottomSheet {
dispatch_async(dispatch_get_main_queue(), ^{
[self.updaterVC dismissViewControllerAnimated:YES completion:nil];
});
}


@end
6 changes: 6 additions & 0 deletions ios/BundleUpdater.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@
#else
#import <React/RCTBridgeModule.h>
#import <Foundation/Foundation.h>
#import "BundleUpdaterViewController.h"

NS_ASSUME_NONNULL_BEGIN
@interface BundleUpdater : NSObject <RCTBridgeModule, UIViewControllerTransitioningDelegate>
// managed on Categories
@property (class, nonatomic, readonly) NSString *API_URL;
@property (nonatomic, strong) BundleUpdaterViewController *updaterVC;
// public methods
+ (instancetype)sharedInstance;
- (void)initialization:(NSString *)apiKey
withBranch:(NSString *)branch
Expand Down
Loading

0 comments on commit 465f595

Please sign in to comment.