This is an automated email from the ASF dual-hosted git repository. moshen pushed a commit to branch dark-mode-dev in repository https://gitbox.apache.org/repos/asf/incubator-weex.git
commit 6bd805e7ba3ac2045504656cfb35ffc0359b26b1 Author: qianyuan.wqy <[email protected]> AuthorDate: Tue Nov 12 16:26:14 2019 +0800 Support dark mode. --- ios/sdk/WeexSDK.xcodeproj/project.pbxproj | 18 +++ .../Sources/Component/RecycleList/WXJSASTParser.mm | 2 +- .../Sources/Component/WXComponent_internal.h | 7 ++ .../WeexSDK/Sources/Component/WXImageComponent.m | 121 ++++++++++++--------- ios/sdk/WeexSDK/Sources/Component/WXRichText.mm | 34 +++++- .../WeexSDK/Sources/Component/WXTextComponent.mm | 86 ++++++++++----- .../WeexSDK/Sources/Display/WXComponent+Display.m | 89 +++++++++++++-- ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m | 2 + .../WXDarkThemeDefaultImpl.h} | 11 +- .../WXDarkThemeDefaultImpl.m} | 21 +++- .../WeexSDK/Sources/Manager/WXComponentManager.mm | 6 + ios/sdk/WeexSDK/Sources/Model/WXComponent.h | 18 +++ ios/sdk/WeexSDK/Sources/Model/WXComponent.mm | 30 ++++- ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.h | 28 +++++ ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.m | 85 ++++++++++++++- .../WeexSDK/Sources/Model/WXSDKInstance_private.h | 2 + ios/sdk/WeexSDK/Sources/Module/WXAnimationModule.m | 35 +++++- ios/sdk/WeexSDK/Sources/Module/WXTransition.mm | 65 +++++++++-- .../{WXDestroyProtocol.h => WXDarkThemeProtocol.h} | 23 +++- ios/sdk/WeexSDK/Sources/Utility/WXUtility.h | 8 ++ ios/sdk/WeexSDK/Sources/Utility/WXUtility.m | 19 +++- .../Sources/View/WXComponent+ViewManagement.mm | 46 +++++++- ios/sdk/WeexSDK/Sources/View/WXRootView.m | 36 ++++++ ios/sdk/WeexSDK/Sources/WeexSDK.h | 1 + 24 files changed, 663 insertions(+), 130 deletions(-) diff --git a/ios/sdk/WeexSDK.xcodeproj/project.pbxproj b/ios/sdk/WeexSDK.xcodeproj/project.pbxproj index e006b27..3502f78 100644 --- a/ios/sdk/WeexSDK.xcodeproj/project.pbxproj +++ b/ios/sdk/WeexSDK.xcodeproj/project.pbxproj @@ -618,6 +618,12 @@ D735F1B222D761F800B53CDF /* log_utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D735F1AC22D761F800B53CDF /* log_utils.cpp */; }; D735F1B322D761F800B53CDF /* log_utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D735F1AC22D761F800B53CDF /* log_utils.cpp */; }; D77286FF22C9B22C00E1DA7D /* eagle_bridge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BD9205FA223651D800EDF93D /* eagle_bridge.cpp */; }; + D7C96CF3237AA13400A4599C /* WXDarkThemeProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D7C96CF2237AA13400A4599C /* WXDarkThemeProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D7C96CF4237AA13400A4599C /* WXDarkThemeProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D7C96CF2237AA13400A4599C /* WXDarkThemeProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D7C96CF7237AA16100A4599C /* WXDarkThemeDefaultImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = D7C96CF5237AA16100A4599C /* WXDarkThemeDefaultImpl.h */; }; + D7C96CF8237AA16100A4599C /* WXDarkThemeDefaultImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = D7C96CF5237AA16100A4599C /* WXDarkThemeDefaultImpl.h */; }; + D7C96CF9237AA16100A4599C /* WXDarkThemeDefaultImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = D7C96CF6237AA16100A4599C /* WXDarkThemeDefaultImpl.m */; }; + D7C96CFA237AA16100A4599C /* WXDarkThemeDefaultImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = D7C96CF6237AA16100A4599C /* WXDarkThemeDefaultImpl.m */; }; DC03ADB91D508719003F76E7 /* WXTextAreaComponent.mm in Sources */ = {isa = PBXBuildFile; fileRef = DC03ADB71D508719003F76E7 /* WXTextAreaComponent.mm */; }; DC03ADBA1D508719003F76E7 /* WXTextAreaComponent.h in Headers */ = {isa = PBXBuildFile; fileRef = DC03ADB81D508719003F76E7 /* WXTextAreaComponent.h */; }; DC15A3DB2010BC93009C8977 /* weex-main-jsfm.js in Resources */ = {isa = PBXBuildFile; fileRef = DC15A3D92010BC93009C8977 /* weex-main-jsfm.js */; }; @@ -1351,6 +1357,9 @@ D3FC0DF61C508B2A002B9E31 /* WXTimerModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WXTimerModule.m; sourceTree = "<group>"; }; D735F1AB22D761F800B53CDF /* log_utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = log_utils.h; sourceTree = "<group>"; }; D735F1AC22D761F800B53CDF /* log_utils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = log_utils.cpp; sourceTree = "<group>"; }; + D7C96CF2237AA13400A4599C /* WXDarkThemeProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WXDarkThemeProtocol.h; sourceTree = "<group>"; }; + D7C96CF5237AA16100A4599C /* WXDarkThemeDefaultImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WXDarkThemeDefaultImpl.h; sourceTree = "<group>"; }; + D7C96CF6237AA16100A4599C /* WXDarkThemeDefaultImpl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WXDarkThemeDefaultImpl.m; sourceTree = "<group>"; }; DAB176F008F516E4F9391C61 /* libPods-WeexSDK.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-WeexSDK.a"; sourceTree = BUILT_PRODUCTS_DIR; }; DC03ADB71D508719003F76E7 /* WXTextAreaComponent.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WXTextAreaComponent.mm; sourceTree = "<group>"; }; DC03ADB81D508719003F76E7 /* WXTextAreaComponent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WXTextAreaComponent.h; sourceTree = "<group>"; }; @@ -1476,6 +1485,8 @@ 59A583031CF5B2FD0081FD3E /* Handler */ = { isa = PBXGroup; children = ( + D7C96CF5237AA16100A4599C /* WXDarkThemeDefaultImpl.h */, + D7C96CF6237AA16100A4599C /* WXDarkThemeDefaultImpl.m */, 33CE190C2153443000CF9670 /* WXJSFrameworkLoadDefaultImpl.h */, 33CE190D2153443000CF9670 /* WXJSFrameworkLoadDefaultImpl.m */, 59A583041CF5B2FD0081FD3E /* WXNavigationDefaultImpl.h */, @@ -1857,6 +1868,7 @@ 77D1611C1C02DD3C0010B15B /* Protocol */ = { isa = PBXGroup; children = ( + D7C96CF2237AA13400A4599C /* WXDarkThemeProtocol.h */, 33CE19122153444900CF9670 /* WXJSFrameworkLoadProtocol.h */, 17036A5220FDE7490029AE3D /* WXApmProtocol.h */, 17C74F0E2072147A00AB4CAB /* WXAnalyzerProtocol.h */, @@ -2410,6 +2422,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + D7C96CF3237AA13400A4599C /* WXDarkThemeProtocol.h in Headers */, F75C591C2313C1FC002FFF94 /* WXStreamModule.h in Headers */, 74A4BA9E1CB3C0A100195969 /* WXHandlerFactory.h in Headers */, 59A583081CF5B2FD0081FD3E /* WXNavigationDefaultImpl.h in Headers */, @@ -2497,6 +2510,7 @@ 59CE27E81CC387DB000BE37A /* WXEmbedComponent.h in Headers */, B8D66C0F21255730003960BD /* core_side_in_platform.h in Headers */, B8D66C9121255730003960BD /* render_factory_interface.h in Headers */, + D7C96CF7237AA16100A4599C /* WXDarkThemeDefaultImpl.h in Headers */, DCE2CF9B1F46D4220021BDC4 /* WXVoiceOverModule.h in Headers */, 453F3756219A76CA00A03F1D /* default_request_handler.h in Headers */, 74BB5FB91DFEE81A004FC3DF /* WXMetaModule.h in Headers */, @@ -2656,6 +2670,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + D7C96CF4237AA13400A4599C /* WXDarkThemeProtocol.h in Headers */, DCA4461D1EFA5AAA00D0CFA8 /* WXHandlerFactory.h in Headers */, DCA446101EFA5A8500D0CFA8 /* WXBridgeMethod.h in Headers */, DCA4461A1EFA5AA000D0CFA8 /* WXInvocationConfig.h in Headers */, @@ -2724,6 +2739,7 @@ DCA445A21EFA570100D0CFA8 /* WXScrollerComponent.h in Headers */, B8D66C7621255730003960BD /* render_object.h in Headers */, DCA445B71EFA579200D0CFA8 /* WXImgLoaderProtocol.h in Headers */, + D7C96CF8237AA16100A4599C /* WXDarkThemeDefaultImpl.h in Headers */, 1746EA7420E9D253007E55BD /* WXComponent_performance.h in Headers */, B8D66CEB21255B2A003960BD /* WXWebSocketLoader.h in Headers */, DCA445C21EFA57D700D0CFA8 /* WXBaseViewController.h in Headers */, @@ -3324,6 +3340,7 @@ 749DC27C1D40827B009E1C91 /* WXMonitor.m in Sources */, C4B834271DE69B09007AD27E /* WXPickerModule.m in Sources */, 745B2D691E5A8E1E0092D38A /* WXMultiColumnLayout.m in Sources */, + D7C96CF9237AA16100A4599C /* WXDarkThemeDefaultImpl.m in Sources */, 77788B752229252D000D5102 /* render_page_custom.cpp in Sources */, 77D161391C02DE940010B15B /* WXBridgeManager.m in Sources */, 74BF19F91F5139BB00AEE3D7 /* WXJSASTParser.mm in Sources */, @@ -3515,6 +3532,7 @@ DCA445981EFA55B300D0CFA8 /* WXSDKInstance.m in Sources */, DCA445991EFA55B300D0CFA8 /* WXJSExceptionInfo.m in Sources */, DCA4459A1EFA55B300D0CFA8 /* WXResourceRequest.m in Sources */, + D7C96CFA237AA16100A4599C /* WXDarkThemeDefaultImpl.m in Sources */, 77788B762229252D000D5102 /* render_page_custom.cpp in Sources */, DCA4459B1EFA55B300D0CFA8 /* WXResourceRequestHandlerDefaultImpl.m in Sources */, DCA4459C1EFA55B300D0CFA8 /* WXResourceResponse.m in Sources */, diff --git a/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXJSASTParser.mm b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXJSASTParser.mm index cfea370..2d26ff0 100644 --- a/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXJSASTParser.mm +++ b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXJSASTParser.mm @@ -760,7 +760,7 @@ static int binaryPrecedence(WXJSToken *token) - (WXJSExpression *)parsePrimaryExpression { - int type = _lookahead->type; + WXJSTokenType type = _lookahead->type; if (type == WXJSTokenTypePunctuator) { if (_lookahead->value == "[") { diff --git a/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h b/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h index a97dfcb..6dff036 100644 --- a/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h +++ b/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h @@ -50,7 +50,9 @@ typedef id (^WXDataBindingBlock)(NSDictionary *data, BOOL *needUpdate); * View */ UIColor *_styleBackgroundColor; + UIColor *_darkThemeBackgroundColor; NSString *_backgroundImage; + NSString *_darkThemeBackgroundImage; NSString *_clipRadius; WXClipType _clipToBounds; UIView *_view; @@ -115,9 +117,13 @@ typedef id (^WXDataBindingBlock)(NSDictionary *data, BOOL *needUpdate); WXThreadSafeCounter *_displayCounter; UIColor *_borderTopColor; + UIColor *_darkThemeBorderTopColor; UIColor *_borderRightColor; + UIColor *_darkThemeBorderRightColor; UIColor *_borderLeftColor; + UIColor *_darkThemeBorderLeftColor; UIColor *_borderBottomColor; + UIColor *_darkThemeBorderBottomColor; CGFloat _borderTopWidth; CGFloat _borderRightWidth; @@ -179,6 +185,7 @@ typedef id (^WXDataBindingBlock)(NSDictionary *data, BOOL *needUpdate); DO NOT use "_backgroundColor" directly. The same reason as '_transform'. */ @property (atomic, strong) UIColor* styleBackgroundColor; +@property (atomic, strong) UIColor* darkThemeBackgroundColor; ///-------------------------------------- /// @name Package Internal Methods diff --git a/ios/sdk/WeexSDK/Sources/Component/WXImageComponent.m b/ios/sdk/WeexSDK/Sources/Component/WXImageComponent.m index 1cb8245..2a83fee 100644 --- a/ios/sdk/WeexSDK/Sources/Component/WXImageComponent.m +++ b/ios/sdk/WeexSDK/Sources/Component/WXImageComponent.m @@ -53,14 +53,14 @@ static dispatch_queue_t WXImageUpdateQueue; @interface WXImageComponent () { - NSString * _imageSrc; - pthread_mutex_t _imageSrcMutex; - pthread_mutexattr_t _propertMutexAttr; BOOL _shouldUpdateImage; BOOL _mainImageSuccess; } +@property (atomic, strong) NSString *src; +@property (atomic, strong) NSString *darkThemeSrc; @property (atomic, strong) NSString *placeholdSrc; +@property (atomic, strong) NSString *darkThemePlaceholderSrc; @property (nonatomic, assign) CGFloat blurRadius; @property (nonatomic, assign) UIViewContentMode resizeMode; @property (nonatomic, assign) WXImageQuality imageQuality; @@ -71,7 +71,7 @@ static dispatch_queue_t WXImageUpdateQueue; @property (nonatomic) BOOL imageLoadEvent; @property (nonatomic) BOOL imageDownloadFinish; @property (nonatomic) BOOL downloadImageWithURL; -@property (nonatomic ,strong) NSString* preUrl; +@property (nonatomic, strong) NSString* preUrl; @end @@ -88,17 +88,15 @@ WX_EXPORT_METHOD(@selector(save:)) WXImageUpdateQueue = dispatch_queue_create("com.taobao.weex.ImageUpdateQueue", DISPATCH_QUEUE_SERIAL); } - pthread_mutexattr_init(&(_propertMutexAttr)); - pthread_mutexattr_settype(&(_propertMutexAttr), PTHREAD_MUTEX_RECURSIVE); - pthread_mutex_init(&(_imageSrcMutex), &(_propertMutexAttr)); - if (attributes[@"src"]) { - pthread_mutex_lock(&(_imageSrcMutex)); - _imageSrc = [[WXConvert NSString:attributes[@"src"]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; - pthread_mutex_unlock(&(_imageSrcMutex)); + self.src = [[WXConvert NSString:attributes[@"src"]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; } else { WXLogWarning(@"image src is nil"); } + if (attributes[@"darkThemeSrc"]) { + self.darkThemeSrc = [[WXConvert NSString:attributes[@"darkThemeSrc"]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + } + [self configPlaceHolder:attributes]; NSString *resizeMode = attributes[@"resize"]; @@ -135,6 +133,9 @@ WX_EXPORT_METHOD(@selector(save:)) if (attributes[@"placeHolder"] || attributes[@"placeholder"]) { self.placeholdSrc = [[WXConvert NSString:attributes[@"placeHolder"]?:attributes[@"placeholder"]]stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; } + if (attributes[@"darkThemePlaceholder"]) { + self.darkThemePlaceholderSrc = [[WXConvert NSString:attributes[@"darkThemePlaceholder"]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + } } - (void)configFilter:(NSDictionary *)styles needUpdate:(BOOL)needUpdate @@ -258,12 +259,13 @@ WX_EXPORT_METHOD(@selector(save:)) - (void)dealloc { [self cancelImage]; - pthread_mutex_destroy(&(_imageSrcMutex)); - pthread_mutexattr_destroy(&_propertMutexAttr); } - (void)updateAttributes:(NSDictionary *)attributes { + if (attributes[@"darkThemeSrc"]) { + self.darkThemeSrc = [[WXConvert NSString:attributes[@"darkThemeSrc"]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + } if (attributes[@"src"]) { [self setImageSrc:[[WXConvert NSString:attributes[@"src"]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]]; } @@ -293,7 +295,16 @@ WX_EXPORT_METHOD(@selector(save:)) [self _clipsToBounds]; [self updateImage]; - +} + +- (void)themeDidChange:(NSString*)theme +{ + [super themeDidChange:theme]; + if (_view) { + if (self.darkThemeSrc || self.darkThemePlaceholderSrc) { + [self updateImage]; + } + } } - (BOOL)_needsDrawBorder @@ -358,26 +369,15 @@ WX_EXPORT_METHOD(@selector(save:)) } } -- (NSString *)imageSrc -{ - pthread_mutex_lock(&(_imageSrcMutex)); - NSString * imageSrcCpy = [_imageSrc copy]; - pthread_mutex_unlock(&(_imageSrcMutex)); - - return imageSrcCpy; -} - - (void)setImageSrc:(NSString*)src { - if ([src isEqualToString:_imageSrc]) { + if ([src isEqualToString:self.src]) { // if image src is equal to then ignore it. return; } - pthread_mutex_lock(&(_imageSrcMutex)); - _imageSrc = src; + self.src = src; _imageDownloadFinish = NO; ((UIImageView*)self.view).image = nil; - pthread_mutex_unlock(&(_imageSrcMutex)); [self updateImage]; } @@ -397,14 +397,19 @@ WX_EXPORT_METHOD(@selector(save:)) _shouldUpdateImage = YES; return; } + + BOOL isDarkMode = [self.weexInstance isDarkTheme]; + NSString* choosedSrc = isDarkMode ? (self.darkThemeSrc ?: self.src) : self.src; + NSString* choosedPlaceholder = isDarkMode ? (self.darkThemePlaceholderSrc ?: self.placeholdSrc) : self.placeholdSrc; + __weak typeof(self) weakSelf = self; if (_downloadImageWithURL && [[self imageLoader] respondsToSelector:@selector(setImageViewWithURL:url:placeholderImage:options:progress:completed:)]) { _mainImageSuccess = NO; NSString *newURL = nil; - if (self.placeholdSrc) { - newURL = [self.placeholdSrc copy]; - WX_REWRITE_URL([self placeholdSrc], WXResourceTypeImage, self.weexInstance) + if (choosedPlaceholder) { + newURL = [choosedPlaceholder copy]; + WX_REWRITE_URL(choosedPlaceholder, WXResourceTypeImage, self.weexInstance) NSDictionary* extInfo = @{@"instanceId":[self _safeInstanceId], @"pageURL": self.weexInstance.scriptURL ?: @""}; [[self imageLoader] setImageViewWithURL:(UIImageView*)self.view url:[NSURL URLWithString:newURL] placeholderImage:nil options:extInfo progress:nil completed:^(UIImage *image, NSError *error, WXImageLoaderCacheType cacheType, NSURL *imageURL) { /* We cannot rely on image library even if we call setImage with placeholer before calling setImage with real url. @@ -414,8 +419,8 @@ WX_EXPORT_METHOD(@selector(save:)) UIImageView *imageView = (UIImageView *)strongSelf.view; if (imageView && imageView.image == image && strongSelf->_mainImageSuccess) { // reload image with main image url - NSString* newURL = [[strongSelf imageSrc] copy]; - WX_REWRITE_URL([strongSelf imageSrc], WXResourceTypeImage, strongSelf.weexInstance) + NSString* newURL = [choosedSrc copy]; + WX_REWRITE_URL(choosedSrc, WXResourceTypeImage, strongSelf.weexInstance) NSDictionary *userInfo = @{@"imageQuality":@(strongSelf.imageQuality), @"imageSharp":@(strongSelf.imageSharp), @"blurRadius":@(strongSelf.blurRadius), @"instanceId":[strongSelf _safeInstanceId], @"pageURL": strongSelf.weexInstance.scriptURL ?: @""}; [[strongSelf imageLoader] setImageViewWithURL:imageView url:[NSURL URLWithString:newURL] placeholderImage:nil options:userInfo progress:nil completed:^(UIImage *image, NSError *error, WXImageLoaderCacheType cacheType, NSURL *imageURL) { WXLogInfo(@"Image re-requested because placeholder may override main image. %@", imageURL); @@ -424,11 +429,11 @@ WX_EXPORT_METHOD(@selector(save:)) } }]; } - newURL = [[self imageSrc] copy]; + newURL = [choosedSrc copy]; if ([newURL length] == 0) { return; } - WX_REWRITE_URL([self imageSrc], WXResourceTypeImage, self.weexInstance) + WX_REWRITE_URL(choosedSrc, WXResourceTypeImage, self.weexInstance) NSDictionary *userInfo = @{@"imageQuality":@(self.imageQuality), @"imageSharp":@(self.imageSharp), @"blurRadius":@(self.blurRadius), @"instanceId":[self _safeInstanceId], @"pageURL": self.weexInstance.scriptURL ?: @""}; [[self imageLoader] setImageViewWithURL:(UIImageView*)self.view url:[NSURL URLWithString:newURL] placeholderImage:nil options:userInfo progress:^(NSInteger receivedSize, NSInteger expectedSize) { // progress when loading image @@ -444,9 +449,9 @@ WX_EXPORT_METHOD(@selector(save:)) WXLogError(@"Error downloading image: %@, detail:%@", imageURL.absoluteString, [error localizedDescription]); // retry set placeholder, maybe placeholer image can be downloaded - if (strongSelf.placeholdSrc) { - NSString *newURL = [strongSelf.placeholdSrc copy]; - WX_REWRITE_URL([strongSelf placeholdSrc], WXResourceTypeImage, strongSelf.weexInstance) + if (choosedPlaceholder) { + NSString *newURL = [choosedPlaceholder copy]; + WX_REWRITE_URL(choosedPlaceholder, WXResourceTypeImage, strongSelf.weexInstance) [[strongSelf imageLoader] setImageViewWithURL:(UIImageView*)strongSelf.view url:[NSURL URLWithString:newURL] placeholderImage:nil @@ -513,7 +518,7 @@ WX_EXPORT_METHOD(@selector(save:)) [strongSelf updatePlaceHolderWithFailedBlock:downloadFailed]; [strongSelf updateContentImageWithFailedBlock:downloadFailed]; - if (!strongSelf.imageSrc && !strongSelf.placeholdSrc) { + if (!choosedSrc && !choosedPlaceholder) { dispatch_async(dispatch_get_main_queue(), ^{ strongSelf.layer.contents = nil; strongSelf.imageDownloadFinish = YES; @@ -526,15 +531,16 @@ WX_EXPORT_METHOD(@selector(save:)) - (void)updatePlaceHolderWithFailedBlock:(void(^)(NSString *, NSError *))downloadFailedBlock { - NSString *placeholderSrc = self.placeholdSrc; + BOOL isDarkMode = [self.weexInstance isDarkTheme]; + NSString* choosedPlaceholder = isDarkMode ? (self.darkThemePlaceholderSrc ?: self.placeholdSrc) : self.placeholdSrc; - if ([WXUtility isBlankString:placeholderSrc]) { + if ([WXUtility isBlankString:choosedPlaceholder]) { return; } - WXLogDebug(@"Updating image, component:%@, placeholder:%@ ", self.ref, placeholderSrc); - NSString *newURL = [self.placeholdSrc copy]; - WX_REWRITE_URL(self.placeholdSrc, WXResourceTypeImage, self.weexInstance) + WXLogDebug(@"Updating image, component:%@, placeholder:%@ ", self.ref, choosedPlaceholder); + NSString *newURL = [choosedPlaceholder copy]; + WX_REWRITE_URL(choosedPlaceholder, WXResourceTypeImage, self.weexInstance) __weak typeof(self) weakSelf = self; self.placeholderOperation = [[self imageLoader] downloadImageWithURL:newURL imageFrame:self.calculatedFrame @@ -543,16 +549,21 @@ WX_EXPORT_METHOD(@selector(save:)) { dispatch_async(dispatch_get_main_queue(), ^{ __strong typeof(self) strongSelf = weakSelf; + if (strongSelf == nil) { + return; + } UIImage *viewImage = ((UIImageView *)strongSelf.view).image; if (error) { - downloadFailedBlock(placeholderSrc,error); + downloadFailedBlock(choosedPlaceholder, error); if ([strongSelf isViewLoaded] && !viewImage) { ((UIImageView *)(strongSelf.view)).image = nil; [strongSelf readyToRender]; } return; } - if (![placeholderSrc isEqualToString:strongSelf.placeholdSrc]) { + + NSString* currentPlaceholder = [strongSelf.weexInstance isDarkTheme] ? (strongSelf.darkThemePlaceholderSrc ?: strongSelf.placeholdSrc) : strongSelf.placeholdSrc; + if (![choosedPlaceholder isEqualToString:currentPlaceholder]) { return; } @@ -570,20 +581,25 @@ WX_EXPORT_METHOD(@selector(save:)) - (void)updateContentImageWithFailedBlock:(void(^)(NSString *, NSError *))downloadFailedBlock { - NSString *imageSrc = [NSString stringWithFormat:@"%@", self.imageSrc?:@""]; - if ([WXUtility isBlankString:imageSrc]) { + BOOL isDarkMode = [self.weexInstance isDarkTheme]; + NSString* choosedSrc = isDarkMode ? (self.darkThemeSrc ?: self.src) : self.src; + + if ([WXUtility isBlankString:choosedSrc]) { WXLogError(@"image src is empty"); return; } - WXLogDebug(@"Updating image:%@, component:%@", self.imageSrc, self.ref); + WXLogDebug(@"Updating image:%@, component:%@", choosedSrc, self.ref); NSDictionary *userInfo = @{@"imageQuality":@(self.imageQuality), @"imageSharp":@(self.imageSharp), @"blurRadius":@(self.blurRadius), @"instanceId":[self _safeInstanceId], @"pageURL": self.weexInstance.scriptURL ?: @""}; - NSString * newURL = [imageSrc copy]; - WX_REWRITE_URL(imageSrc, WXResourceTypeImage, self.weexInstance) + NSString * newURL = [choosedSrc copy]; + WX_REWRITE_URL(choosedSrc, WXResourceTypeImage, self.weexInstance) __weak typeof(self) weakSelf = self; weakSelf.imageOperation = [[weakSelf imageLoader] downloadImageWithURL:newURL imageFrame:weakSelf.calculatedFrame userInfo:userInfo completed:^(UIImage *image, NSError *error, BOOL finished) { dispatch_async(dispatch_get_main_queue(), ^{ __strong typeof(self) strongSelf = weakSelf; + if (strongSelf == nil) { + return; + } if (strongSelf.imageLoadEvent) { NSMutableDictionary *sizeDict = [NSMutableDictionary new]; @@ -598,12 +614,13 @@ WX_EXPORT_METHOD(@selector(save:)) [strongSelf fireEvent:@"load" params:@{ @"success": error? @false : @true,@"size":sizeDict}]; } if (error) { - downloadFailedBlock(imageSrc, error); + downloadFailedBlock(choosedSrc, error); [strongSelf readyToRender]; return ; } - if (![imageSrc isEqualToString:strongSelf.imageSrc]) { + NSString* currentSrc = [strongSelf.weexInstance isDarkTheme] ? (strongSelf.darkThemeSrc ?: strongSelf.src) : strongSelf.src; + if (![choosedSrc isEqualToString:currentSrc]) { return ; } diff --git a/ios/sdk/WeexSDK/Sources/Component/WXRichText.mm b/ios/sdk/WeexSDK/Sources/Component/WXRichText.mm index 8ffeaf3..8e30d76 100644 --- a/ios/sdk/WeexSDK/Sources/Component/WXRichText.mm +++ b/ios/sdk/WeexSDK/Sources/Component/WXRichText.mm @@ -27,6 +27,7 @@ #import "WXImgLoaderProtocol.h" #import "WXComponentManager.h" #import "WXLog.h" +#import "WXDarkThemeProtocol.h" #include <pthread/pthread.h> @interface WXRichNode : NSObject @@ -35,7 +36,9 @@ @property (nonatomic, strong) NSString *ref; @property (nonatomic, strong) NSString *text; @property (nonatomic, strong) UIColor *color; +@property (nonatomic, strong) UIColor *darkThemeColor; @property (nonatomic, strong) UIColor *backgroundColor; +@property (nonatomic, strong) UIColor *darkThemeBackgroundColor; @property (nonatomic, strong) NSString *fontFamily; @property (nonatomic, assign) CGFloat fontSize; @property (nonatomic, assign) CGFloat fontWeight; @@ -91,7 +94,9 @@ do {\ id value = styles[@#key]; \ if (value) { \ node.key = [WXConvert type:value];\ - } else if (!([@#key isEqualToString:@"backgroundColor"] || [@#key isEqualToString:@"textDecoration"]) && superNode.key ) { \ + } else if (!([@#key isEqualToString:@"backgroundColor"] || \ + [@#key isEqualToString:@"darkThemeBackgroundColor"] || \ + [@#key isEqualToString:@"textDecoration"]) && superNode.key ) { \ node.key = superNode.key; \ } \ } while(0); @@ -190,7 +195,9 @@ do {\ - (void)fillCSSStyles:(NSDictionary *)styles toNode:(WXRichNode *)node superNode:(WXRichNode *)superNode { WX_STYLE_FILL_RICHTEXT(color, UIColor) + WX_STYLE_FILL_RICHTEXT(darkThemeColor, UIColor) WX_STYLE_FILL_RICHTEXT(backgroundColor, UIColor) + WX_STYLE_FILL_RICHTEXT(darkThemeBackgroundColor, UIColor) WX_STYLE_FILL_RICHTEXT(fontFamily, NSString) WX_STYLE_FILL_RICHTEXT_PIXEL(fontSize) WX_STYLE_FILL_RICHTEXT(fontWeight, WXTextWeight) @@ -421,6 +428,15 @@ do {\ }; } +- (void)themeDidChange:(NSString*)theme +{ + [super themeDidChange:theme]; + if ([self isViewLoaded]) { + // Force inner layout + [self innerLayout]; + } +} + #pragma mark Text Building - (NSMutableAttributedString *)buildAttributeString @@ -434,6 +450,16 @@ do {\ NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] init]; NSUInteger location; + BOOL invert = self.invertForDarkTheme; + + // Invert default background color. + UIColor* defaultTextColor = [UIColor blackColor]; + UIColor* defaultBackgroundColor = _backgroundColor; + if (invert && [self.weexInstance isDarkTheme]) { + defaultTextColor = [[WXSDKInstance darkThemeColorHandler] getInvertedColorFor:[UIColor blackColor] ofScene:[self colorSceneType] withDefault:[UIColor blackColor]]; + defaultBackgroundColor = [[WXSDKInstance darkThemeColorHandler] getInvertedColorFor:_backgroundColor ofScene:[self colorSceneType] withDefault:_backgroundColor]; + } + __weak typeof(self) weakSelf = self; for (WXRichNode *node in array) { location = attrStr.length; @@ -444,8 +470,10 @@ do {\ [attrStr.mutableString appendString:text]; NSRange range = NSMakeRange(location, text.length); - [attrStr addAttribute:NSForegroundColorAttributeName value:node.color ?: [UIColor blackColor] range:range]; - [attrStr addAttribute:NSBackgroundColorAttributeName value:node.backgroundColor ?: _backgroundColor range:range]; + UIColor* textColor = [self.weexInstance chooseColor:node.color darkThemeColor:node.darkThemeColor invert:invert scene:[self colorSceneType]]; + UIColor* bgColor = [self.weexInstance chooseColor:node.backgroundColor darkThemeColor:node.darkThemeBackgroundColor invert:invert scene:[self colorSceneType]]; + [attrStr addAttribute:NSForegroundColorAttributeName value:textColor ?: defaultTextColor range:range]; + [attrStr addAttribute:NSBackgroundColorAttributeName value:bgColor ?: defaultBackgroundColor range:range]; UIFont *font = [WXUtility fontWithSize:node.fontSize textWeight:node.fontWeight textStyle:WXTextStyleNormal fontFamily:node.fontFamily scaleFactor:self.weexInstance.pixelScaleFactor]; if (font) { diff --git a/ios/sdk/WeexSDK/Sources/Component/WXTextComponent.mm b/ios/sdk/WeexSDK/Sources/Component/WXTextComponent.mm index 3a95fac..3bb7948 100644 --- a/ios/sdk/WeexSDK/Sources/Component/WXTextComponent.mm +++ b/ios/sdk/WeexSDK/Sources/Component/WXTextComponent.mm @@ -120,6 +120,8 @@ static CGFloat WXTextDefaultLineThroughWidth = 1.2; @interface WXTextComponent() @property (atomic, strong) NSString *fontFamily; +@property (atomic, strong) UIColor *textColor; +@property (atomic, strong) UIColor *darkThemeTextColor; @end @implementation WXTextComponent @@ -128,7 +130,6 @@ static CGFloat WXTextDefaultLineThroughWidth = 1.2; UIEdgeInsets _padding; NSTextStorage *_textStorage; float _textStorageWidth; - float _color[4]; float _fontSize; float _fontWeight; WXTextStyle _fontStyle; @@ -174,8 +175,6 @@ static CGFloat WXTextDefaultLineThroughWidth = 1.2; } else { _useCoreText = YES; } - - _color[0] = -1.0; [self fillCSSStyles:styles]; [self fillAttributes:attributes]; @@ -261,25 +260,41 @@ do {\ WX_STYLE_FILL_TEXT_PIXEL(letterSpacing, letterSpacing, YES) //!OCLint WX_STYLE_FILL_TEXT(wordWrap, wordWrap, NSString, YES); //!OCLint - UIColor* color = nil; - id value = styles[@"color"]; - if (value) { - if([WXUtility isBlankString:value]){ - color = [UIColor blackColor]; - } else { - color = [WXConvert UIColor:value]; + do { + UIColor* color = nil; + id value = styles[@"color"]; + if (value) { + if([WXUtility isBlankString:value]){ + color = [UIColor blackColor]; + } else { + color = [WXConvert UIColor:value]; + } + if (color) { + self.textColor = color; + [self setNeedsRepaint]; + } } - if (color) { - [self setNeedsRepaint]; - CGFloat red, green, blue, alpha; - [color getRed:&red green:&green blue:&blue alpha:&alpha]; - _color[0] = red; - _color[1] = green; - _color[2] = blue; - _color[3] = alpha; + if (self.textColor == nil) { + self.textColor = [UIColor blackColor]; } - } + } while (0); + do { + UIColor* color = nil; + id value = styles[@"darkThemeColor"]; + if (value) { + if([WXUtility isBlankString:value]){ + color = [UIColor blackColor]; + } else { + color = [WXConvert UIColor:value]; + } + if (color) { + self.darkThemeTextColor = color; + [self setNeedsRepaint]; + } + } + } while (0); + if (self.fontFamily && !_observerIconfont) { // notification received when custom icon font file download finish [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(repaintText:) name:WX_ICONFONT_DOWNLOAD_NOTIFICATION object:nil]; @@ -371,6 +386,20 @@ do {\ [self setNeedsRepaint]; } +- (void)themeDidChange:(NSString*)theme +{ + [self setNeedsRepaint]; + [super themeDidChange:theme]; + if (_view) { + [self setNeedsDisplay]; + } +} + +- (WXColorScene)colorSceneType +{ + return WXColorSceneText; +} + - (BOOL)needsDrawRect { return YES; @@ -497,8 +526,9 @@ do {\ string = @""; } NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString: string]; - if (_color[0] >= 0) { - [attributedString addAttribute:NSForegroundColorAttributeName value:[UIColor colorWithRed:_color[0] green:_color[1] blue:_color[2] alpha:_color[3]] range:NSMakeRange(0, string.length)]; + UIColor* textColor = [self.weexInstance chooseColor:self.textColor darkThemeColor:self.darkThemeTextColor invert:self.invertForDarkTheme scene:[self colorSceneType]]; + if (textColor) { + [attributedString addAttribute:NSForegroundColorAttributeName value:textColor range:NSMakeRange(0, string.length)]; } // set font @@ -585,8 +615,9 @@ do {\ NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:string]; // set textColor - if (_color[0] >= 0) { - [attributedString addAttribute:NSForegroundColorAttributeName value:[UIColor colorWithRed:_color[0] green:_color[1] blue:_color[2] alpha:_color[3]] range:NSMakeRange(0, string.length)]; + UIColor* textColor = [self.weexInstance chooseColor:self.textColor darkThemeColor:self.darkThemeTextColor invert:self.invertForDarkTheme scene:[self colorSceneType]]; + if (textColor) { + [attributedString addAttribute:NSForegroundColorAttributeName value:textColor range:NSMakeRange(0, string.length)]; } // set font @@ -1112,10 +1143,11 @@ NS_INLINE NSRange WXNSRangeFromCFRange(CFRange range) { { [super _resetCSSNodeStyles:styles]; if ([styles containsObject:@"color"]) { - _color[0] = 0; - _color[1] = 0; - _color[2] = 0; - _color[3] = 1.0; + self.textColor = [UIColor blackColor]; + [self setNeedsRepaint]; + } + if ([styles containsObject:@"darkThemeColor"]) { + self.darkThemeTextColor = nil; [self setNeedsRepaint]; } if ([styles containsObject:@"fontSize"]) { diff --git a/ios/sdk/WeexSDK/Sources/Display/WXComponent+Display.m b/ios/sdk/WeexSDK/Sources/Display/WXComponent+Display.m index d209068..7b513e8 100644 --- a/ios/sdk/WeexSDK/Sources/Display/WXComponent+Display.m +++ b/ios/sdk/WeexSDK/Sources/Display/WXComponent+Display.m @@ -28,6 +28,8 @@ #import "UIBezierPath+Weex.h" #import "WXRoundedRect.h" #import "WXSDKInstance.h" +#import "WXDarkThemeProtocol.h" +#import "WXHandlerFactory.h" #pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation" @@ -101,6 +103,29 @@ typedef NS_ENUM(NSInteger, WXComponentBorderRecord) { WXAssertMainThread(); } +- (void)themeDidChange:(NSString*)theme +{ + WXAssertMainThread(); + if (_view) { + if (![self _needsDrawBorder]) { + _layer.borderColor = [self.weexInstance chooseColor:_borderTopColor darkThemeColor:_darkThemeBorderTopColor invert:self.invertForDarkTheme scene:[self colorSceneType]].CGColor; + _layer.backgroundColor = [self.weexInstance chooseColor:self.styleBackgroundColor darkThemeColor:self.darkThemeBackgroundColor invert:self.invertForDarkTheme scene:[self colorSceneType]].CGColor; + } + else { + [self setNeedsDisplay]; + } + + if (_backgroundImage) { + [self setGradientLayer]; + } + } +} + +- (WXColorScene)colorSceneType +{ + return WXColorSceneUnknown; +} + #pragma mark Private - (WXDisplayBlock)_displayBlock @@ -289,7 +314,7 @@ typedef NS_ENUM(NSInteger, WXComponentBorderRecord) { - (void)_collectCompositingDisplayBlocks:(NSMutableArray *)displayBlocks context:(CGContextRef)context isCancelled:(BOOL(^)(void))isCancelled { // TODO: compositingChild has no chance to applyPropertiesToView, need update here? - UIColor *backgroundColor = self.styleBackgroundColor; + UIColor *backgroundColor = [self.weexInstance chooseColor:self.styleBackgroundColor darkThemeColor:self.darkThemeBackgroundColor invert:self.invertForDarkTheme scene:[self colorSceneType]]; BOOL clipsToBounds = _clipToBounds; CGRect frame = self.calculatedFrame; CGRect bounds = CGRectMake(0, 0, frame.size.width, frame.size.height); @@ -349,8 +374,9 @@ typedef NS_ENUM(NSInteger, WXComponentBorderRecord) { CGContextSetAlpha(context, _opacity); // fill background color - if (self.styleBackgroundColor && CGColorGetAlpha(self.styleBackgroundColor.CGColor) > 0) { - CGContextSetFillColorWithColor(context, self.styleBackgroundColor.CGColor); + UIColor* bgColor = [self.weexInstance chooseColor:self.styleBackgroundColor darkThemeColor:self.darkThemeBackgroundColor invert:self.invertForDarkTheme scene:[self colorSceneType]]; + if (bgColor && CGColorGetAlpha(bgColor.CGColor) > 0) { + CGContextSetFillColorWithColor(context, bgColor.CGColor); UIBezierPath *bezierPath = [UIBezierPath wx_bezierPathWithRoundedRect:rect topLeft:topLeft topRight:topRight bottomLeft:bottomLeft bottomRight:bottomRight]; [bezierPath fill]; WXPerformBlockOnMainThread(^{ @@ -367,7 +393,8 @@ typedef NS_ENUM(NSInteger, WXComponentBorderRecord) { CGContextSetLineDash(context, 0, 0, 0); } CGContextSetLineWidth(context, _borderTopWidth); - CGContextSetStrokeColorWithColor(context, _borderTopColor.CGColor); + CGContextSetStrokeColorWithColor(context, + [self.weexInstance chooseColor:_borderTopColor darkThemeColor:_darkThemeBorderTopColor invert:self.invertForDarkTheme scene:[self colorSceneType]].CGColor); CGContextAddArc(context, size.width-topRight, topRight, topRight-_borderTopWidth/2, -M_PI_4+(_borderRightWidth>0?0:M_PI_4), -M_PI_2, 1); CGContextMoveToPoint(context, size.width-topRight, _borderTopWidth/2); CGContextAddLineToPoint(context, topLeft, _borderTopWidth/2); @@ -388,7 +415,8 @@ typedef NS_ENUM(NSInteger, WXComponentBorderRecord) { CGContextSetLineDash(context, 0, 0, 0); } CGContextSetLineWidth(context, _borderLeftWidth); - CGContextSetStrokeColorWithColor(context, _borderLeftColor.CGColor); + CGContextSetStrokeColorWithColor(context, + [self.weexInstance chooseColor:_borderLeftColor darkThemeColor:_darkThemeBorderLeftColor invert:self.invertForDarkTheme scene:[self colorSceneType]].CGColor); CGContextAddArc(context, topLeft, topLeft, topLeft-_borderLeftWidth/2, -M_PI, -M_PI_2-M_PI_4+(_borderTopWidth > 0?0:M_PI_4), 0); CGContextMoveToPoint(context, _borderLeftWidth/2, topLeft); CGContextAddLineToPoint(context, _borderLeftWidth/2, size.height-bottomLeft); @@ -409,7 +437,8 @@ typedef NS_ENUM(NSInteger, WXComponentBorderRecord) { CGContextSetLineDash(context, 0, 0, 0); } CGContextSetLineWidth(context, _borderBottomWidth); - CGContextSetStrokeColorWithColor(context, _borderBottomColor.CGColor); + CGContextSetStrokeColorWithColor(context, + [self.weexInstance chooseColor:_borderBottomColor darkThemeColor:_darkThemeBorderBottomColor invert:self.invertForDarkTheme scene:[self colorSceneType]].CGColor); CGContextAddArc(context, bottomLeft, size.height-bottomLeft, bottomLeft-_borderBottomWidth/2, M_PI-M_PI_4+(_borderLeftWidth>0?0:M_PI_4), M_PI_2, 1); CGContextMoveToPoint(context, bottomLeft, size.height-_borderBottomWidth/2); CGContextAddLineToPoint(context, size.width-bottomRight, size.height-_borderBottomWidth/2); @@ -430,7 +459,8 @@ typedef NS_ENUM(NSInteger, WXComponentBorderRecord) { CGContextSetLineDash(context, 0, 0, 0); } CGContextSetLineWidth(context, _borderRightWidth); - CGContextSetStrokeColorWithColor(context, _borderRightColor.CGColor); + CGContextSetStrokeColorWithColor(context, + [self.weexInstance chooseColor:_borderRightColor darkThemeColor:_darkThemeBorderRightColor invert:self.invertForDarkTheme scene:[self colorSceneType]].CGColor); CGContextAddArc(context, size.width-bottomRight, size.height-bottomRight, bottomRight-_borderRightWidth/2, M_PI_4+(_borderBottomWidth>0?0:M_PI_4), 0, 1); CGContextMoveToPoint(context, size.width-_borderRightWidth/2, size.height-bottomRight); CGContextAddLineToPoint(context, size.width-_borderRightWidth/2, topRight); @@ -486,7 +516,17 @@ typedef NS_ENUM(NSInteger, WXComponentBorderRecord) { if (!radiusEqual) { return YES; } - BOOL colorEqual = [_borderTopColor isEqual:_borderRightColor] && [_borderRightColor isEqual:_borderBottomColor] && [_borderBottomColor isEqual:_borderLeftColor]; + + BOOL invert = self.invertForDarkTheme; + WXColorScene scene = [self colorSceneType]; + UIColor* usingBorderTopColor = [self.weexInstance chooseColor:_borderTopColor darkThemeColor:_darkThemeBorderTopColor invert:invert scene:scene]; + UIColor* usingBorderRightColor = [self.weexInstance chooseColor:_borderRightColor darkThemeColor:_darkThemeBorderRightColor invert:invert scene:scene]; + UIColor* usingBorderBottomColor = [self.weexInstance chooseColor:_borderBottomColor darkThemeColor:_darkThemeBorderBottomColor invert:invert scene:scene]; + UIColor* usingBorderLeftColor = [self.weexInstance chooseColor:_borderLeftColor darkThemeColor:_darkThemeBorderLeftColor invert:invert scene:scene]; + + BOOL colorEqual = [usingBorderTopColor isEqual:usingBorderRightColor] && + [usingBorderRightColor isEqual:usingBorderBottomColor] && + [usingBorderBottomColor isEqual:usingBorderLeftColor]; if (!colorEqual) { return YES; } @@ -579,6 +619,32 @@ do {\ WX_CHECK_BORDER_PROP(Style, Top, Left, Bottom, Right, WXBorderStyle) WX_CHECK_BORDER_PROP(Color, Top, Left, Bottom, Right, UIColor) + do { + BOOL needsDisplay = NO; + if (styles[@"darkThemeBorderColor"]) { + _darkThemeBorderTopColor = _darkThemeBorderLeftColor = _darkThemeBorderRightColor = _darkThemeBorderBottomColor = [WXConvert UIColor:styles[@"darkThemeBorderColor"]]; + needsDisplay = YES; + } + if (styles[@"darkThemeBorderTopColor"]) { + _darkThemeBorderTopColor = [WXConvert UIColor:styles[@"darkThemeBorderTopColor"]]; + needsDisplay = YES; + } + if (styles[@"darkThemeBorderLeftColor"]) { + _darkThemeBorderLeftColor = [WXConvert UIColor:styles[@"darkThemeBorderLeftColor"]]; + needsDisplay = YES; + } + if (styles[@"darkThemeBorderRightColor"]) { + _darkThemeBorderRightColor = [WXConvert UIColor:styles[@"darkThemeBorderRightColor"]]; + needsDisplay = YES; + } + if (styles[@"darkThemeBorderBottomColor"]) { + _darkThemeBorderBottomColor = [WXConvert UIColor:styles[@"darkThemeBorderBottomColor"]]; + needsDisplay = YES; + } + if (needsDisplay && updating) { + [self setNeedsDisplay]; + } + } while (0); WX_CHECK_BORDER_PROP_PIXEL(Width, Top, Left, Bottom, Right) WX_CHECK_BORDER_PROP_PIXEL(Radius, TopLeft, TopRight, BottomLeft, BottomRight) @@ -592,9 +658,9 @@ do {\ } else if (!nowNeedsDrawBorder) { [self _resetNativeBorderRadius]; _layer.borderWidth = _borderTopWidth; - _layer.borderColor = _borderTopColor.CGColor; + _layer.borderColor = [self.weexInstance chooseColor:_borderTopColor darkThemeColor:_darkThemeBorderTopColor invert:self.invertForDarkTheme scene:[self colorSceneType]].CGColor; if ((_transition.transitionOptions & WXTransitionOptionsBackgroundColor) != WXTransitionOptionsBackgroundColor ) { - _layer.backgroundColor = self.styleBackgroundColor.CGColor; + _layer.backgroundColor = [self.weexInstance chooseColor:self.styleBackgroundColor darkThemeColor:self.darkThemeBackgroundColor invert:self.invertForDarkTheme scene:[self colorSceneType]].CGColor; } } } @@ -606,7 +672,8 @@ do {\ WXRoundedRect *borderRect = [[WXRoundedRect alloc] initWithRect:rect topLeft:_borderTopLeftRadius topRight:_borderTopRightRadius bottomLeft:_borderBottomLeftRadius bottomRight:_borderBottomRightRadius]; WXRadii *radii = borderRect.radii; BOOL hasBorderRadius = [radii hasBorderRadius]; - return (!hasBorderRadius) && _opacity == 1.0 && CGColorGetAlpha(self.styleBackgroundColor.CGColor) == 1.0 && [self _needsDrawBorder]; + UIColor* currentBgColor = [self.weexInstance chooseColor:self.styleBackgroundColor darkThemeColor:self.darkThemeBackgroundColor invert:self.invertForDarkTheme scene:[self colorSceneType]]; + return (!hasBorderRadius) && _opacity == 1.0 && CGColorGetAlpha(currentBgColor.CGColor) == 1.0 && [self _needsDrawBorder]; } - (CAShapeLayer *)drawBorderRadiusMaskLayer:(CGRect)rect diff --git a/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m b/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m index ad181ac..f630f02 100644 --- a/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m +++ b/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m @@ -29,6 +29,7 @@ #import "WXNavigationDefaultImpl.h" #import "WXURLRewriteDefaultImpl.h" #import "WXJSFrameworkLoadDefaultImpl.h" +#import "WXDarkThemeDefaultImpl.h" #import "WXSDKManager.h" #import "WXSDKError.h" @@ -205,6 +206,7 @@ [self registerHandler:[WXNavigationDefaultImpl new] withProtocol:@protocol(WXNavigationProtocol)]; [self registerHandler:[WXURLRewriteDefaultImpl new] withProtocol:@protocol(WXURLRewriteProtocol)]; [self registerHandler:[WXJSFrameworkLoadDefaultImpl new] withProtocol:@protocol(WXJSFrameworkLoadProtocol)]; + [self registerHandler:[WXDarkThemeDefaultImpl new] withProtocol:@protocol(WXDarkThemeProtocol)]; } + (void)registerHandler:(id)handler withProtocol:(Protocol *)protocol diff --git a/ios/sdk/WeexSDK/Sources/Protocol/WXDestroyProtocol.h b/ios/sdk/WeexSDK/Sources/Handler/WXDarkThemeDefaultImpl.h similarity index 71% copy from ios/sdk/WeexSDK/Sources/Protocol/WXDestroyProtocol.h copy to ios/sdk/WeexSDK/Sources/Handler/WXDarkThemeDefaultImpl.h index 1875e79..cba2971 100644 --- a/ios/sdk/WeexSDK/Sources/Protocol/WXDestroyProtocol.h +++ b/ios/sdk/WeexSDK/Sources/Handler/WXDarkThemeDefaultImpl.h @@ -18,12 +18,15 @@ */ #import <Foundation/Foundation.h> +#import <WeexSDK/WXDarkThemeProtocol.h> -@protocol WXDestroyProtocol <NSObject> +NS_ASSUME_NONNULL_BEGIN -/** - * @abstract execute unload function before dealloc +/* By default, this implementation class do basic invert of UIColor of RGBA color space. + You should implementation your own handler to better handle dark theme in your application. */ -- (void)unload; +@interface WXDarkThemeDefaultImpl : NSObject <WXDarkThemeProtocol> @end + +NS_ASSUME_NONNULL_END diff --git a/ios/sdk/WeexSDK/Sources/Protocol/WXDestroyProtocol.h b/ios/sdk/WeexSDK/Sources/Handler/WXDarkThemeDefaultImpl.m similarity index 60% copy from ios/sdk/WeexSDK/Sources/Protocol/WXDestroyProtocol.h copy to ios/sdk/WeexSDK/Sources/Handler/WXDarkThemeDefaultImpl.m index 1875e79..0717392 100644 --- a/ios/sdk/WeexSDK/Sources/Protocol/WXDestroyProtocol.h +++ b/ios/sdk/WeexSDK/Sources/Handler/WXDarkThemeDefaultImpl.m @@ -17,13 +17,22 @@ * under the License. */ -#import <Foundation/Foundation.h> +#import "WXDarkThemeDefaultImpl.h" -@protocol WXDestroyProtocol <NSObject> +@implementation WXDarkThemeDefaultImpl -/** - * @abstract execute unload function before dealloc - */ -- (void)unload; +- (void)configureView:(UIView *)view ofComponent:(WXComponent *)component +{ + // Nothing +} + +- (UIColor *_Nullable)getInvertedColorFor:(UIColor *_Nonnull)color ofScene:(WXColorScene)scene withDefault:(UIColor *_Nullable)defaultColor +{ + CGFloat red, blue, green, alpha; + if ([color getRed:&red green:&green blue:&blue alpha:&alpha]) { + return [UIColor colorWithRed:1 - red green:1 - green blue:1 - blue alpha:alpha]; + } + return color; +} @end diff --git a/ios/sdk/WeexSDK/Sources/Manager/WXComponentManager.mm b/ios/sdk/WeexSDK/Sources/Manager/WXComponentManager.mm index f4b29a8..b8f205c 100644 --- a/ios/sdk/WeexSDK/Sources/Manager/WXComponentManager.mm +++ b/ios/sdk/WeexSDK/Sources/Manager/WXComponentManager.mm @@ -260,6 +260,7 @@ static NSThread *WXComponentThread; WXAssert(_rootComponent == nil, @"Create body is invoked twice."); _rootComponent = [self _buildComponent:ref type:type supercomponent:nil styles:styles attributes:attributes events:events renderObject:renderObject]; + _rootComponent.invertForDarkTheme = YES; CGSize size = _weexInstance.frame.size; [WXCoreBridge setDefaultDimensionIntoRoot:_weexInstance.instanceId @@ -327,6 +328,11 @@ static NSThread *WXComponentThread; } } + // Not explicitly declare "invertForDarkTheme", inherit + if (attributes[@"invertForDarkTheme"] == nil) { + component.invertForDarkTheme = supercomponent.invertForDarkTheme; + } + #ifdef DEBUG WXLogDebug(@"flexLayout -> _recursivelyAddComponent : super:(%@,%@):[%f,%f] ,child:(%@,%@):[%f,%f],childClass:%@", supercomponent.type, diff --git a/ios/sdk/WeexSDK/Sources/Model/WXComponent.h b/ios/sdk/WeexSDK/Sources/Model/WXComponent.h index 0528b1d..4c31a95 100644 --- a/ios/sdk/WeexSDK/Sources/Model/WXComponent.h +++ b/ios/sdk/WeexSDK/Sources/Model/WXComponent.h @@ -32,6 +32,12 @@ typedef enum : NSUInteger { WXComponentUpdateStylesCallback } WXComponentCallbackType; +typedef enum : NSUInteger { + WXColorSceneBackground, + WXColorSceneText, + WXColorSceneUnknown, +} WXColorScene; + /** * @abstract the component callback , result can be string or dictionary. * @discussion callback data to js, the id of callback function will be removed to save memory. @@ -364,6 +370,8 @@ NS_ASSUME_NONNULL_BEGIN /// @name Display ///-------------------------------------- +@property (nonatomic, assign) BOOL invertForDarkTheme; + @property (nonatomic, assign) WXDisplayType displayType; /** @@ -379,6 +387,16 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)needsDrawRect; /** + * @abstract Fired on instance theme did changed. + */ +- (void)themeDidChange:(NSString*)theme; + +/** + * @abstract Hint used for better do color invert in dark mode. + */ +- (WXColorScene)colorSceneType; + +/** * @abstract Draws the component’s image within the passed-in rectangle. * @parameter rect The rectangle which is the entire visible bounds of your component. * @return A UIImage containing the contents of the current bitmap graphics context. diff --git a/ios/sdk/WeexSDK/Sources/Model/WXComponent.mm b/ios/sdk/WeexSDK/Sources/Model/WXComponent.mm index 3235dbd..16de3c6 100644 --- a/ios/sdk/WeexSDK/Sources/Model/WXComponent.mm +++ b/ios/sdk/WeexSDK/Sources/Model/WXComponent.mm @@ -42,6 +42,7 @@ #import "WXSDKInstance_performance.h" #import "WXComponent_performance.h" #import "WXCoreBridge.h" +#import "WXDarkThemeProtocol.h" #pragma clang diagnostic ignored "-Wincomplete-implementation" #pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation" @@ -71,6 +72,7 @@ static BOOL bNeedRemoveEvents = YES; @synthesize transform = _transform; @synthesize styleBackgroundColor = _styleBackgroundColor; +@synthesize darkThemeBackgroundColor = _darkThemeBackgroundColor; #pragma mark Life Cycle @@ -104,6 +106,7 @@ static BOOL bNeedRemoveEvents = YES; _eventPenetrationEnabled = NO; _accessibilityHintContent = nil; _cancelsTouchesInView = YES; + _invertForDarkTheme = NO; _async = NO; @@ -122,6 +125,10 @@ static BOOL bNeedRemoveEvents = YES; } } + if (attributes[@"invertForDarkTheme"]) { + _invertForDarkTheme = [WXConvert BOOL:attributes[@"invertForDarkTheme"]]; + } + if (attributes[@"userInteractionEnabled"]) { _userInteractionEnabled = [WXConvert BOOL:attributes[@"userInteractionEnabled"]]; } @@ -380,6 +387,10 @@ static BOOL bNeedRemoveEvents = YES; [self viewWillLoad]; _view = [self loadView]; + + // Provide a chance for dark theme handler to process the view + [[WXSDKInstance darkThemeColorHandler] configureView:_view ofComponent:self]; + #ifdef DEBUG WXLogDebug(@"flexLayout -> loadView:addr-(%p),componentRef-(%@)",_view,self.ref); #endif @@ -388,11 +399,19 @@ static BOOL bNeedRemoveEvents = YES; _view.hidden = _visibility == WXVisibilityShow ? NO : YES; _view.clipsToBounds = _clipToBounds; if (![self _needsDrawBorder]) { - _layer.borderColor = _borderTopColor.CGColor; + _layer.borderColor = [self.weexInstance chooseColor:_borderTopColor darkThemeColor:_darkThemeBorderTopColor invert:_invertForDarkTheme scene:[self colorSceneType]].CGColor; _layer.borderWidth = _borderTopWidth; [self _resetNativeBorderRadius]; _layer.opacity = _opacity; - _view.backgroundColor = self.styleBackgroundColor; + + /* Also set background color to view to fix that problem that system may + set dynamic color to UITableView. Without these codes, event if we set + clear color to layer, the table view could not be transparent. */ + UIColor* choosedColor = [self.weexInstance chooseColor:self.styleBackgroundColor darkThemeColor:self.darkThemeBackgroundColor invert:_invertForDarkTheme scene:[self colorSceneType]]; + if (choosedColor == [UIColor clearColor]) { + _view.backgroundColor = choosedColor; + } + _layer.backgroundColor = choosedColor.CGColor; } if (_backgroundImage) { @@ -845,7 +864,11 @@ static BOOL bNeedRemoveEvents = YES; if (CGRectEqualToRect(self.view.frame, CGRectZero)) { return; } - NSDictionary * linearGradient = [WXUtility linearGradientWithBackgroundImage:_backgroundImage]; + + BOOL isDark = [self.weexInstance isDarkTheme]; + NSString* styleValue = isDark ? (_darkThemeBackgroundImage ?: _backgroundImage) : _backgroundImage; + + NSDictionary * linearGradient = [WXUtility linearGradientWithBackgroundImage:styleValue]; if (!linearGradient) { return ; } @@ -854,6 +877,7 @@ static BOOL bNeedRemoveEvents = YES; dispatch_async(dispatch_get_main_queue(), ^{ __strong typeof(self) strongSelf = weakSelf; if(strongSelf) { + // No need to auto-invert linear-gradient colors. We only allows using 'dark-theme-background-image' style. UIColor * startColor = (UIColor*)linearGradient[@"startColor"]; UIColor * endColor = (UIColor*)linearGradient[@"endColor"]; CAGradientLayer * gradientLayer = [WXUtility gradientLayerFromColors:@[startColor, endColor] locations:nil frame:strongSelf.view.bounds gradientType:(WXGradientType)[linearGradient[@"gradientType"] integerValue]]; diff --git a/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.h b/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.h index c6cf521..e2df9fb 100644 --- a/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.h +++ b/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.h @@ -26,6 +26,8 @@ #import <WeexSDK/WXApmForInstance.h> #import <WeexSDK/WXComponentManager.h> +@protocol WXDarkThemeProtocol; + NS_ASSUME_NONNULL_BEGIN extern NSString *const bundleUrlOptionKey; @@ -447,6 +449,32 @@ typedef enum : NSUInteger { */ + (NSDictionary*)lastPageInfo; +#pragma mark - Theme Support + +/** + Handler for handling color invert. + + @return Handler instance. + */ ++ (id<WXDarkThemeProtocol>)darkThemeColorHandler; + +/** + Return true if current is dark theme for this instance. + */ +- (BOOL)isDarkTheme; + +/** + Get/set interface style of current instance. + */ +- (NSString*)currentThemeName; +- (void)setCurrentThemeName:(NSString*)name; + +/** + Choose final color between original color and dark-mode one. + Also considering invert. + */ +- (UIColor*)chooseColor:(UIColor*)originalColor darkThemeColor:(UIColor*)darkColor invert:(BOOL)invert scene:(WXColorScene)scene; + /** * Deprecated */ diff --git a/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.m b/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.m index 7a22748..a0af7d7 100644 --- a/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.m +++ b/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.m @@ -51,7 +51,8 @@ #import "WXPageEventNotifyEvent.h" #import "WXConvertUtility.h" #import "WXCoreBridge.h" -#import <WeexSDK/WXDataRenderHandler.h> +#import "WXDataRenderHandler.h" +#import "WXDarkThemeProtocol.h" #define WEEX_LITE_URL_SUFFIX @"wlasm" #define WEEX_RENDER_TYPE_PLATFORM @"platform" @@ -102,6 +103,7 @@ typedef enum : NSUInteger { { self = [super init]; if (self) { + self.themeName = [WXUtility isSystemInDarkTheme] ? @"dark" : @"light"; _renderType = renderType; _appearState = YES; @@ -538,9 +540,6 @@ typedef enum : NSUInteger { if (!self.userInfo[@"jsMainBundleStringContentLength"]) { self.userInfo[@"jsMainBundleStringContentLength"] = @([mainBundleString length]); } - if (!self.userInfo[@"jsMainBundleStringContentLength"]) { - self.userInfo[@"jsMainBundleStringContentMd5"] = [WXUtility md5:mainBundleString]; - } id<WXPageEventNotifyEventProtocol> pageEvent = [WXSDKEngine handlerForProtocol:@protocol(WXPageEventNotifyEventProtocol)]; if ([pageEvent respondsToSelector:@selector(pageStart:)]) { @@ -1172,6 +1171,84 @@ typedef enum : NSUInteger { return result; } ++ (id<WXDarkThemeProtocol>)darkThemeColorHandler +{ + static id<WXDarkThemeProtocol> colorHandler; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + colorHandler = [WXHandlerFactory handlerForProtocol:@protocol(WXDarkThemeProtocol)]; + }); + return colorHandler; +} + +- (NSString*)currentThemeName +{ + return self.themeName; +} + +- (BOOL)isDarkTheme +{ + return [self.themeName isEqualToString:@"dark"]; +} + +- (void)setCurrentThemeName:(NSString*)name +{ + if (name && ![name isEqualToString:self.themeName]) { + self.themeName = name; + + // Recursively visit all components and notify that theme had changed. + __weak WXSDKInstance* weakSelf = self; + WXPerformBlockOnComponentThread(^{ + __strong WXSDKInstance* strongSelf = weakSelf; + if (strongSelf == nil) { + return; + } + + if (!strongSelf->_componentManager.isValid) { + return; + } + + [strongSelf->_componentManager enumerateComponentsUsingBlock:^(WXComponent * _Nonnull component, BOOL * _Nonnull stop) { + __weak WXComponent* wcomp = component; + WXPerformBlockOnMainThread(^{ + __strong WXComponent* scomp = wcomp; + if (scomp) { + [scomp themeDidChange:name]; + } + }); + }]; + }); + + [[WXSDKManager bridgeMgr] fireEvent:_instanceId + ref:WX_SDK_ROOT_REF + type:@"themechanged" + params:@{@"theme": self.themeName?:@"light"} + domChanges:nil]; + } +} + +- (UIColor*)chooseColor:(UIColor*)originalColor darkThemeColor:(UIColor*)darkColor invert:(BOOL)invert scene:(WXColorScene)scene +{ + if ([self isDarkTheme]) { + if (darkColor) { + return darkColor; + } + else if (invert) { + // Invert originalColor + if (originalColor == [UIColor clearColor]) { + return originalColor; + } + return [[WXSDKInstance darkThemeColorHandler] getInvertedColorFor:originalColor ofScene:scene withDefault:originalColor]; + } + else { + return originalColor; + } + } + else { + return originalColor; + } +} + @end @implementation WXSDKInstance (Deprecated) diff --git a/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance_private.h b/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance_private.h index 25680da..852275d 100644 --- a/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance_private.h +++ b/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance_private.h @@ -39,6 +39,8 @@ @property (nonatomic, strong) NSString *createInstanceContextResult; @property (nonatomic, strong) NSString *executeRaxApiResult; +@property (atomic, strong) NSString* themeName; + - (void)addModuleEventObservers:(NSString*)event callback:(NSString*)callbackId option:(NSDictionary*)option moduleClassName:(NSString*)moduleClassName; - (void)_addModuleEventObserversWithModuleMethod:(WXModuleMethod*)method; - (void)removeModuleEventObserver:(NSString*)event moduleClassName:(NSString*)moduleClassName; diff --git a/ios/sdk/WeexSDK/Sources/Module/WXAnimationModule.m b/ios/sdk/WeexSDK/Sources/Module/WXAnimationModule.m index 8ee1591..f75728a 100644 --- a/ios/sdk/WeexSDK/Sources/Module/WXAnimationModule.m +++ b/ios/sdk/WeexSDK/Sources/Module/WXAnimationModule.m @@ -26,6 +26,7 @@ #import "WXLength.h" #import "WXTransition.h" #import "WXComponent+Layout.h" +#import "WXDarkThemeProtocol.h" @interface WXAnimationInfo : NSObject<NSCopying> @@ -207,7 +208,30 @@ WX_EXPORT_METHOD(@selector(transition:args:callback:)) } CAMediaTimingFunction *timingFunction = [WXConvert CAMediaTimingFunction:args[@"timingFunction"]]; NSDictionary *styles = args[@"styles"]; + NSDictionary* componentRawStyles = target.styles; + + BOOL isDarkTheme = [target.weexInstance isDarkTheme]; + BOOL updatingDarkThemeBackgroundColor = styles[@"darkThemeBackgroundColor"] != nil; + for (NSString *property in styles) { + if ([property isEqualToString:@"backgroundColor"]) { + if (isDarkTheme && (updatingDarkThemeBackgroundColor || + componentRawStyles[@"darkThemeBackgroundColor"] != nil)) { + /* Updating "darkThemeBackgroundColor" in dark mode, + or this component has dark bg color explicitly defined in styels. + We ignore transition animation for "backgroundColor" */ + continue; + } + } + else if ([property isEqualToString:@"darkThemeBackgroundColor"]) { + if (!isDarkTheme || componentRawStyles[@"darkThemeBackgroundColor"] == nil) { + /* Do not do animation for "darkThemeBackgroundColor" in light mode. + Or there is no dark bg color explicitly defined in styles. + */ + continue; + } + } + WXAnimationInfo *info = [WXAnimationInfo new]; info.duration = duration; info.delay = delay; @@ -287,10 +311,17 @@ WX_EXPORT_METHOD(@selector(transition:args:callback:)) [infos addObject:newInfo]; } target.transform = wxTransform; - } else if ([property isEqualToString:@"backgroundColor"]) { + } else if ([property isEqualToString:@"backgroundColor"] || + [property isEqualToString:@"darkThemeBackgroundColor"]) { info.propertyName = @"backgroundColor"; info.fromValue = (__bridge id)(layer.backgroundColor); - info.toValue = (__bridge id)[WXConvert CGColor:value]; + UIColor* toColor = [WXConvert UIColor:value]; + if ([target.weexInstance isDarkTheme] && target.invertForDarkTheme && + [property isEqualToString:@"backgroundColor"]) { + // Invert color + toColor = [[WXSDKInstance darkThemeColorHandler] getInvertedColorFor:toColor ofScene:[target colorSceneType] withDefault:toColor]; + } + info.toValue = (__bridge id)([toColor CGColor]); [infos addObject:info]; } else if ([property isEqualToString:@"opacity"]) { info.propertyName = @"opacity"; diff --git a/ios/sdk/WeexSDK/Sources/Module/WXTransition.mm b/ios/sdk/WeexSDK/Sources/Module/WXTransition.mm index 8bb616d..b597232 100644 --- a/ios/sdk/WeexSDK/Sources/Module/WXTransition.mm +++ b/ios/sdk/WeexSDK/Sources/Module/WXTransition.mm @@ -30,6 +30,7 @@ #import "WXAssert.h" #import "WXSDKInstance_private.h" #import "WXLength.h" +#import "WXDarkThemeProtocol.h" @implementation WXTransitionInfo @end @@ -82,6 +83,7 @@ @"bottom": @(WXTransitionOptionsBottom), @"top": @(WXTransitionOptionsTop), @"backgroundColor": @(WXTransitionOptionsBackgroundColor), + @"darkThemeBackgroundColor": @(WXTransitionOptionsBackgroundColor), @"transform": @(WXTransitionOptionsTransform), @"opacity": @(WXTransitionOptionsOpacity) }; @@ -106,24 +108,55 @@ _filterStyles = _filterStyles ?:[NSMutableDictionary new]; _oldFilterStyles = _oldFilterStyles ?: [NSMutableDictionary new]; NSMutableDictionary *futileStyles = [NSMutableDictionary new]; + NSDictionary* componentRawStyles = targetComponent.styles; + + BOOL isDarkTheme = [targetComponent.weexInstance isDarkTheme]; + BOOL updatingDarkThemeBackgroundColor = styles[@"darkThemeBackgroundColor"] != nil; for (NSString *key in styles) { if (self.transitionOptions & [self transitionOptionsFromString:key]) { + if ([key isEqualToString:@"backgroundColor"]) { + if (isDarkTheme && (updatingDarkThemeBackgroundColor || + componentRawStyles[@"darkThemeBackgroundColor"] != nil)) { + /* Updating "darkThemeBackgroundColor" in dark mode, + or this component has dark bg color explicitly defined in styels. + We ignore transition animation for "backgroundColor" */ + [futileStyles setObject:styles[key] forKey:key]; + continue; + } + } + else if ([key isEqualToString:@"darkThemeBackgroundColor"]) { + if (!isDarkTheme || componentRawStyles[@"darkThemeBackgroundColor"] == nil) { + /* Do not do animation for "darkThemeBackgroundColor" in light mode. + Or there is no dark bg color explicitly defined in styles. + */ + [futileStyles setObject:styles[key] forKey:key]; + continue; + } + } + [_filterStyles setObject:styles[key] forKey:key]; if (![key isEqualToString:@"transform"]) { if (!isRunning) { - /* style value may not be in component.styles, so we must get - value from layout and convert it to style value. */ - id styleValue = targetComponent.styles[key]; - if (styleValue == nil) { + // Get animation 'from' value from raw styles. + id styleValue = componentRawStyles[key]; + if ([key isEqualToString:@"backgroundColor"] || + [key isEqualToString:@"darkThemeBackgroundColor"]) { + if (styleValue == nil) { + // background color is transparent by default. + styleValue = @"transparent"; + } + } + else if (styleValue == nil) { + /* Flex styles may not be in component.styles, so we must get + value from layout and convert it to style value. */ styleValue = [targetComponent convertLayoutValueToStyleValue:key]; } [_oldFilterStyles setObject:styleValue forKey:key]; } } } - else - { + else { [futileStyles setObject:styles[key] forKey:key]; } } @@ -131,7 +164,7 @@ _targetComponent = targetComponent; NSMutableDictionary *componentStyles = [NSMutableDictionary dictionaryWithDictionary:styles]; - [componentStyles addEntriesFromDictionary:targetComponent.styles]; + [componentStyles addEntriesFromDictionary:componentRawStyles]; _transitionDuration = componentStyles[kWXTransitionDuration] ? [WXConvert CGFloat:componentStyles[kWXTransitionDuration]] : 0; _transitionDelay = componentStyles[kWXTransitionDelay] ? [WXConvert CGFloat:componentStyles[kWXTransitionDelay]] : 0; @@ -200,10 +233,21 @@ if (!_propertyArray) { _propertyArray = [NSMutableArray new]; } - if ([singleProperty isEqualToString:@"backgroundColor"]) { + if ([singleProperty isEqualToString:@"backgroundColor"] || + [singleProperty isEqualToString:@"darkThemeBackgroundColor"]) { + UIColor* fromColor = [WXConvert UIColor:_oldFilterStyles[singleProperty]]; + UIColor* toColor = [WXConvert UIColor:_filterStyles[singleProperty]]; + if ([_targetComponent.weexInstance isDarkTheme] && + _targetComponent.invertForDarkTheme && + [singleProperty isEqualToString:@"backgroundColor"]) { + // Invert color + fromColor = [[WXSDKInstance darkThemeColorHandler] getInvertedColorFor:fromColor ofScene:[_targetComponent colorSceneType] withDefault:fromColor]; + toColor = [[WXSDKInstance darkThemeColorHandler] getInvertedColorFor:toColor ofScene:[_targetComponent colorSceneType] withDefault:toColor]; + } + WXTransitionInfo *info = [WXTransitionInfo new]; - info.fromValue = [self _dealWithColor:[WXConvert UIColor:_oldFilterStyles[singleProperty]]]; - info.toValue = [self _dealWithColor:[WXConvert UIColor:_filterStyles[singleProperty]]]; + info.fromValue = [self _dealWithColor:fromColor]; + info.toValue = [self _dealWithColor:toColor]; info.perValue = [self _calculatePerColorRGB1:info.toValue RGB2:info.fromValue]; info.propertyName = singleProperty; [_propertyArray addObject:info]; @@ -337,6 +381,7 @@ @([info.fromValue[3] floatValue] + [info.perValue[3] floatValue] * per)]; UIColor *color = [UIColor colorWithRed:[array[0] floatValue] green:[array[1] floatValue] blue:[array[2] floatValue] alpha:[array[3] floatValue]]; WXPerformBlockOnMainThread(^{ + // Here we do not need to consider about dark mode. _targetComponent.view.backgroundColor = color; [_targetComponent.view setNeedsDisplay]; }); diff --git a/ios/sdk/WeexSDK/Sources/Protocol/WXDestroyProtocol.h b/ios/sdk/WeexSDK/Sources/Protocol/WXDarkThemeProtocol.h similarity index 53% rename from ios/sdk/WeexSDK/Sources/Protocol/WXDestroyProtocol.h rename to ios/sdk/WeexSDK/Sources/Protocol/WXDarkThemeProtocol.h index 1875e79..c443879 100644 --- a/ios/sdk/WeexSDK/Sources/Protocol/WXDestroyProtocol.h +++ b/ios/sdk/WeexSDK/Sources/Protocol/WXDarkThemeProtocol.h @@ -17,13 +17,28 @@ * under the License. */ -#import <Foundation/Foundation.h> +#import <WeexSDK/WXModuleProtocol.h> -@protocol WXDestroyProtocol <NSObject> +NS_ASSUME_NONNULL_BEGIN + +@protocol WXDarkThemeProtocol <WXModuleProtocol> + +/** +After any view of Weex component is created. Callback dark theme handler to provide a + chance to configure the view. +*/ +- (void)configureView:(UIView*_Nonnull)view ofComponent:(WXComponent*_Nonnull)component; /** - * @abstract execute unload function before dealloc + Get inverted color in dark mode for input color with scene hint. + + @param color Input color. + @param scene Scene indicating the color usage. + @param defaultColor If no inverted one matches, return the default color. + @return Inverted color. */ -- (void)unload; +- (UIColor *_Nullable)getInvertedColorFor:(UIColor *_Nonnull)color ofScene:(WXColorScene)scene withDefault:(UIColor *_Nullable)defaultColor; @end + +NS_ASSUME_NONNULL_END diff --git a/ios/sdk/WeexSDK/Sources/Utility/WXUtility.h b/ios/sdk/WeexSDK/Sources/Utility/WXUtility.h index 61eeccd..4addab0 100644 --- a/ios/sdk/WeexSDK/Sources/Utility/WXUtility.h +++ b/ios/sdk/WeexSDK/Sources/Utility/WXUtility.h @@ -131,6 +131,14 @@ _Nonnull SEL WXSwizzledSelectorForSelector(_Nonnull SEL selector); + (void)performBlock:(void (^_Nonnull)(void))block onThread:(NSThread *_Nonnull)thread; /** + * @abstract Check if system is in dark mode. + * + * @return Boolean + * + */ ++ (BOOL)isSystemInDarkTheme; + +/** * @abstract Returns the environment of current application, you can get some necessary properties such as appVersion、sdkVersion、appName etc. * * @return A dictionary object which contains these properties. diff --git a/ios/sdk/WeexSDK/Sources/Utility/WXUtility.m b/ios/sdk/WeexSDK/Sources/Utility/WXUtility.m index 6ec4223..6d7136b 100644 --- a/ios/sdk/WeexSDK/Sources/Utility/WXUtility.m +++ b/ios/sdk/WeexSDK/Sources/Utility/WXUtility.m @@ -163,6 +163,22 @@ CGFloat WXFloorPixelValue(CGFloat value) return [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:UISemanticContentAttributeUnspecified] == UIUserInterfaceLayoutDirectionRightToLeft ? WXLayoutDirectionRTL : WXLayoutDirectionLTR; } ++ (BOOL)isSystemInDarkTheme +{ + if (@available(iOS 13.0, *)) { + __block BOOL result = NO; + WXPerformBlockSyncOnMainThread(^{ +#ifdef __IPHONE_13_0 + if ([UITraitCollection currentTraitCollection].userInterfaceStyle == UIUserInterfaceStyleDark) { + result = YES; + } +#endif + }); + return result; + } + return NO; +} + + (NSDictionary *)getEnvironment { NSString *platform = @"iOS"; @@ -187,7 +203,8 @@ CGFloat WXFloorPixelValue(CGFloat value) @"deviceWidth":@(deviceWidth * scale), @"deviceHeight":@(deviceHeight * scale), @"scale":@(scale), - @"layoutDirection": [self getEnvLayoutDirection] == WXLayoutDirectionRTL ? @"rtl" : @"ltr" + @"layoutDirection": [self getEnvLayoutDirection] == WXLayoutDirectionRTL ? @"rtl" : @"ltr", + @"theme": [self isSystemInDarkTheme] ? @"dark" : @"light" }]; if ([[[UIDevice currentDevice] systemVersion] integerValue] >= 11) { diff --git a/ios/sdk/WeexSDK/Sources/View/WXComponent+ViewManagement.mm b/ios/sdk/WeexSDK/Sources/View/WXComponent+ViewManagement.mm index ce252d0..accb21a 100644 --- a/ios/sdk/WeexSDK/Sources/View/WXComponent+ViewManagement.mm +++ b/ios/sdk/WeexSDK/Sources/View/WXComponent+ViewManagement.mm @@ -67,6 +67,14 @@ do {\ }\ } while(0); +#define WX_BOARD_RADIUS_DARK_THEME_COLOR_RESET_ALL(key)\ +do {\ + if (styles && [styles containsObject:@#key]) {\ + _darkThemeBorderTopColor = _darkThemeBorderLeftColor = _darkThemeBorderRightColor = _darkThemeBorderBottomColor = [UIColor blackColor];\ + [self setNeedsDisplay];\ + }\ +} while(0); + #define WX_BOARD_COLOR_RESET(key)\ do {\ if (styles && [styles containsObject:@#key]) {\ @@ -175,7 +183,11 @@ do {\ - (void)_initViewPropertyWithStyles:(NSDictionary *)styles { self.styleBackgroundColor = styles[@"backgroundColor"] ? [WXConvert UIColor:styles[@"backgroundColor"]] : [UIColor clearColor]; + if (styles[@"darkThemeBackgroundColor"]) { + self.darkThemeBackgroundColor = [WXConvert UIColor:styles[@"darkThemeBackgroundColor"]]; + } _backgroundImage = styles[@"backgroundImage"] ? [WXConvert NSString:styles[@"backgroundImage"]]: nil; + _darkThemeBackgroundImage = styles[@"darkThemeBackgroundImage"] ? [WXConvert NSString:styles[@"darkThemeBackgroundImage"]] : nil; _opacity = styles[@"opacity"] ? [WXConvert CGFloat:styles[@"opacity"]] : 1.0; _clipToBounds = styles[@"overflow"] ? [WXConvert WXClipType:styles[@"overflow"]] : NO; _visibility = styles[@"visibility"] ? [WXConvert WXVisibility:styles[@"visibility"]] : WXVisibilityShow; @@ -195,6 +207,9 @@ do {\ if (styles[@"backgroundColor"]) { self.styleBackgroundColor = [WXConvert UIColor:styles[@"backgroundColor"]]; } + if (styles[@"darkThemeBackgroundColor"]) { + self.darkThemeBackgroundColor = [WXConvert UIColor:styles[@"darkThemeBackgroundColor"]]; + } if (styles[@"opacity"]) { _opacity = [WXConvert CGFloat:styles[@"opacity"]]; } @@ -215,9 +230,21 @@ do {\ [self setNeedsDisplay]; } + if (styles[@"darkThemeBackgroundColor"]) { + self.darkThemeBackgroundColor = [WXConvert UIColor:styles[@"darkThemeBackgroundColor"]]; + [self setNeedsDisplay]; + } + if (styles[@"backgroundImage"]) { - _backgroundImage = styles[@"backgroundImage"] ? [WXConvert NSString:styles[@"backgroundImage"]]: nil; - if (_backgroundImage) { + _backgroundImage = [WXConvert NSString:styles[@"backgroundImage"]]; + } + + if (styles[@"darkThemeBackgroundImage"]) { + _darkThemeBackgroundImage = [WXConvert NSString:styles[@"darkThemeBackgroundImage"]]; + } + + if (styles[@"backgroundImage"] || styles[@"darkThemeBackgroundImage"]) { + if (_backgroundImage || _darkThemeBackgroundImage) { [self setGradientLayer]; } } @@ -302,6 +329,11 @@ do {\ WX_BOARD_COLOR_RESET(borderLeftColor); WX_BOARD_COLOR_RESET(borderRightColor); WX_BOARD_COLOR_RESET(borderBottomColor); + WX_BOARD_RADIUS_DARK_THEME_COLOR_RESET_ALL(darkThemeBorderColor); + WX_BOARD_COLOR_RESET(darkThemeBorderTopColor); + WX_BOARD_COLOR_RESET(darkThemeBorderLeftColor); + WX_BOARD_COLOR_RESET(darkThemeBorderRightColor); + WX_BOARD_COLOR_RESET(darkThemeBorderBottomColor); } - (void)_resetStyles:(NSArray *)styles @@ -310,6 +342,11 @@ do {\ self.styleBackgroundColor = [UIColor clearColor]; [self setNeedsDisplay]; } + if (styles && [styles containsObject:@"darkThemeBackgroundColor"]) { + self.darkThemeBackgroundColor = nil; + [self setNeedsDisplay]; + } + if (styles && [styles containsObject:@"boxShadow"]) { _lastBoxShadow = _boxShadow; _boxShadow = nil; @@ -317,6 +354,11 @@ do {\ } if (styles && [styles containsObject:@"backgroundImage"]) { _backgroundImage = nil; + } + if (styles && [styles containsObject:@"darkThemeBackgroundImage"]) { + _darkThemeBackgroundImage = nil; + } + if (styles && ([styles containsObject:@"backgroundImage"] || [styles containsObject:@"darkThemeBackgroundImage"])) { [self setGradientLayer]; } diff --git a/ios/sdk/WeexSDK/Sources/View/WXRootView.m b/ios/sdk/WeexSDK/Sources/View/WXRootView.m index 3cc8a36..8b2414b 100644 --- a/ios/sdk/WeexSDK/Sources/View/WXRootView.m +++ b/ios/sdk/WeexSDK/Sources/View/WXRootView.m @@ -23,6 +23,10 @@ #import "WXSDKEngine.h" @interface WXRootView() +{ + BOOL _hasFirstTraitCollectionChange; + BOOL _allowFirstTraitCollectionChange; +} @property (nonatomic, assign) BOOL mHasEvent; @@ -59,4 +63,36 @@ return _mHasEvent; } +- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection +{ + [super traitCollectionDidChange:previousTraitCollection]; + + if (@available(iOS 13.0, *)) { + // When entering background system may call back change twice.. We ignore the first one. + UIUserInterfaceStyle currentStyle = self.traitCollection.userInterfaceStyle; + if (currentStyle != previousTraitCollection.userInterfaceStyle) { + if (_hasFirstTraitCollectionChange) { + _allowFirstTraitCollectionChange = NO; + [self.instance setCurrentThemeName:currentStyle == UIUserInterfaceStyleDark ? @"dark" : @"light"]; + } + else { + __weak WXRootView* weakSelf = self; + _hasFirstTraitCollectionChange = YES; + _allowFirstTraitCollectionChange = YES; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + __strong WXRootView* strongSelf = weakSelf; + if (strongSelf) { + if (strongSelf->_allowFirstTraitCollectionChange) { + if (strongSelf.instance) { + [strongSelf.instance setCurrentThemeName:currentStyle == UIUserInterfaceStyleDark ? @"dark" : @"light"]; + } + } + strongSelf->_hasFirstTraitCollectionChange = NO; + } + }); + } + } + } +} + @end diff --git a/ios/sdk/WeexSDK/Sources/WeexSDK.h b/ios/sdk/WeexSDK/Sources/WeexSDK.h index 8e5f6f4..05dbe21 100644 --- a/ios/sdk/WeexSDK/Sources/WeexSDK.h +++ b/ios/sdk/WeexSDK/Sources/WeexSDK.h @@ -73,6 +73,7 @@ FOUNDATION_EXPORT const unsigned char WeexSDKVersionString[]; #import <WeexSDK/WXDefine.h> #import <WeexSDK/WXDebugTool.h> #import <WeexSDK/WXDataRenderHandler.h> +#import <WeexSDK/WXDarkThemeProtocol.h> #import <WeexSDK/WXConvertUtility.h> #import <WeexSDK/WXConvert.h> #import <WeexSDK/WXConfigCenterProtocol.h>
