Browse Source

【功能】拆分ZIP

xingcheng02 1 year ago
parent
commit
faa58b9e0e

+ 87 - 0
XCTheme/Classes/Impl/XCThemeService.m

@@ -8,7 +8,13 @@
 #import "XCThemeProtocol.h"
 #import "XCTheme-Swift.h"
 
+static dispatch_queue_t globalThemeDownloadQueue;
+__attribute__((constructor)) static void init(void) {
+    globalThemeDownloadQueue = dispatch_queue_create("org.forgetive.notebook.themeDownload", DISPATCH_QUEUE_SERIAL);
+}
+
 @xcmlservice(XCThemeProtocol, XCThemeService)
+@synthesize downloadBlock = _downloadBlock;
 
 - (UIUserInterfaceStyle)currentUserInterfaceStyle {
     return XCThemeManager.shared.currentUserInterfaceStyle;
@@ -23,4 +29,85 @@
     }];
 }
 
+#pragma mark - Theme Download
+- (NSArray<XCThemeSpecModel *> *)fetchBundleSpecs {
+    NSString *path = [NSBundle.mainBundle.resourcePath stringByAppendingString:@"/ThemeResources"];
+    NSArray *items = [NSFileManager.defaultManager contentsOfDirectoryAtPath:path error:nil];
+    NSMutableArray<XCThemeSpecModel *> *specs = [NSMutableArray new];
+    for (NSString *file in items) {
+        NSString *filePath = [NSString stringWithFormat:@"%@/%@", path, file];
+        BOOL isDir = NO;
+        [NSFileManager.defaultManager fileExistsAtPath:filePath isDirectory:&isDir];
+        if (isDir) {
+            XCThemeSpec *theme = [[XCThemeSpec alloc] initWithThemeId:filePath];
+            if (theme) {
+                XCThemeSpecModel *specModel = [[XCThemeSpecModel alloc] initWithWrappedObject:theme];
+                [specs addObject:specModel];
+            }
+        }
+    }
+    return [specs copy];
+}
+
+- (NSArray<XCThemeSpecModel *> *)fetchDownloadedSpecs {
+    NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true).firstObject;
+    NSString *path = [documentsPath stringByAppendingString:@"/ThemeResources"];
+    NSArray *items = [NSFileManager.defaultManager contentsOfDirectoryAtPath:path error:nil];
+    NSMutableArray<XCThemeSpecModel *> *specs = [NSMutableArray new];
+    for (NSString *file in items) {
+        NSString *filePath = [NSString stringWithFormat:@"%@/%@", path, file];
+        BOOL isDir = NO;
+        [NSFileManager.defaultManager fileExistsAtPath:filePath isDirectory:&isDir];
+        if (isDir) {
+            XCThemeSpec *theme = [[XCThemeSpec alloc] initWithThemeId:filePath];
+            if (theme) {
+                XCThemeSpecModel *specModel = [[XCThemeSpecModel alloc] initWithWrappedObject:theme];
+                [specs addObject:specModel];
+            }
+        }
+    }
+    return [specs copy];
+}
+
+- (NSArray<XCThemeSpecModel *> *)fetchLocalSpecs {
+    NSMutableArray *specs = [NSMutableArray new];
+    [specs addObjectsFromArray:[self fetchBundleSpecs]];
+    [specs addObjectsFromArray:[self fetchDownloadedSpecs]];
+    return [specs copy];
+}
+
+- (void)fetchThemeData:(NSString *)themeId completion:(void (^)(BOOL, NSString * _Nonnull, XCThemeSpecModel * _Nullable))completion {
+    NSArray<XCThemeSpecModel *> *allLocalSpecs = [self fetchLocalSpecs];
+    for (XCThemeSpecModel *spec in allLocalSpecs) {
+        if ([spec.themeId isEqualToString:themeId]) {
+            if (completion) {
+                completion(YES, @"", spec);
+                return;
+            }
+        }
+    }
+    
+    if (!self.downloadBlock) {
+        completion(NO, @"downloadBlock not specified", nil);
+        @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"downloadBlock not specified" userInfo:nil];
+    }
+    
+    NSString *downloadTHMPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
+    downloadTHMPath = [downloadTHMPath stringByAppendingFormat:@"/ThemeResources/%@.thm", themeId];
+    self.downloadBlock(themeId, downloadTHMPath, ^(BOOL success, NSString * _Nonnull reason) {
+        
+    });
+}
+
+- (XCThemeSpecModel *)currentTheme {
+    if (XCThemeManager.shared.selectedTheme) {
+        return [[XCThemeSpecModel alloc] initWithWrappedObject:XCThemeManager.shared.selectedTheme];
+    }
+    return nil;
+}
+
+- (void)applyTheme:(XCThemeSpecModel *)localSpec {
+    
+}
+
 @end

+ 14 - 1
XCTheme/Classes/Impl/XCThemeSpec.swift

@@ -50,7 +50,20 @@ public class XCThemeSpec: NSObject {
     }
     
     public var themeRootPath: String {
-        return Bundle.main.path(forResource: "ThemeResources/\(themeId)", ofType: nil) ?? ""
+        let searchPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first ?? ""
+        let documentPath = searchPath + "/\(themeId)"
+        let isDir = UnsafeMutablePointer<ObjCBool>.allocate(capacity: 1)
+        defer {
+            isDir.deallocate()
+        }
+        FileManager.default.fileExists(atPath: documentPath, isDirectory: isDir)
+        if isDir.pointee.boolValue {
+            return documentPath
+        }
+        if let bundleResources = Bundle.main.path(forResource: "ThemeResources/\(themeId)", ofType: nil) {
+            return bundleResources
+        }
+        return ""
     }
     
     public func path(forFile file : String) -> String {

+ 35 - 0
XCTheme/Classes/Module/XCThemeProtocol.h

@@ -15,10 +15,45 @@ NS_ASSUME_NONNULL_BEGIN
 
 @protocol XCThemeProtocol <NSObject>
 
+#pragma mark - For Implement Theme Behavior
+/// Get the current user interface style within current theme
 @property (nonatomic, assign, readonly) UIUserInterfaceStyle currentUserInterfaceStyle;
 
+/// Prepare target to do some behaviors when theme changed
+/// - Parameters:
+///   - target: weak target, indicated should discard block
+///   - block: do user interface preparation when changed to specified theme
 - (void)prepareThemeTarget:(nullable NSObject *)target block:(nullable void (^)(XCThemeSpecModel * _Nullable))block;
 
+#pragma mark - For Theme Data Sync
+/// Download the give themeId
+/// @discussion themeId may not the key for storage system, the interface layer should fetch the themeId-downloadURL pairs from server first, then call fetchThemeData method to start download contents. Must be settled before call fetchThemeData:completion:
+@property (nonatomic, copy, nullable) void (^downloadBlock)(NSString *themeId, NSString *writeToPath, void (^)(BOOL success, NSString *reason));
+
+/// Fetch specs from bundle, not mutable
+- (NSArray<XCThemeSpecModel *> *)fetchBundleSpecs;
+
+/// Fetch specs from downloaded, mutable
+- (NSArray<XCThemeSpecModel *> *)fetchDownloadedSpecs;
+
+/// Fetch bundle and downloaded specs
+- (NSArray<XCThemeSpecModel *> *)fetchLocalSpecs;
+
+/// Fetch spec data with specified themeId
+/// @param themeId The id of theme, possible pure number indicate downloaded resources, english words indicate bundle resources
+/// @param completion finish block
+/// @discussion If theme data exists in local, will call completion sync
+/// @discussion If theme data not exists in local, will download first and call completion async
+- (void)fetchThemeData:(NSString *)themeId completion:(nullable void (^)(BOOL success, NSString *errorReason, XCThemeSpecModel *spec))completion;
+
+/// Must exists, return the current selected theme
+/// @discussion Theme always contains systemDark, systemLight or systemAdaptive, so the return value must not null
+- (nonnull XCThemeSpecModel *)currentTheme;
+
+/// Apply new theme
+/// @param localSpec Fetched theme data, must be returned from fetchLocalSpecs or fetchThemeData
+- (void)applyTheme:(XCThemeSpecModel *)localSpec;
+
 @end
 
 NS_ASSUME_NONNULL_END

+ 1 - 0
XCTheme/Classes/Module/XCThemeSpecModel.h

@@ -13,6 +13,7 @@ NS_ASSUME_NONNULL_BEGIN
 
 @property (nonatomic, weak, readonly) id wrappedObject;
 
+@property (nonatomic, copy, readonly) NSString *themeId;
 @property (nonatomic, copy, readonly, nullable) NSString *containerPath;
 @property (nonatomic, copy, readonly, nullable) NSDictionary<NSString *, NSObject *> *originalDict;
 @property (nonatomic, strong, readonly, nullable) UIImage *backgroundImage;

+ 4 - 0
XCTheme/Classes/Module/XCThemeSpecModel.m

@@ -62,4 +62,8 @@
     return self.wrappedObject.themeRootPath;
 }
 
+- (NSString *)themeId {
+    return self.wrappedObject.themeId;
+}
+
 @end