545 lines
14 KiB
Objective-C
545 lines
14 KiB
Objective-C
//
|
|
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
|
|
//
|
|
|
|
#import "Theme.h"
|
|
#import "UIUtil.h"
|
|
#import <SignalMessaging/SignalMessaging-Swift.h>
|
|
#import <SignalServiceKit/AppReadiness.h>
|
|
#import <SignalServiceKit/SignalServiceKit-Swift.h>
|
|
|
|
NS_ASSUME_NONNULL_BEGIN
|
|
|
|
NSString *const ThemeDidChangeNotification = @"ThemeDidChangeNotification";
|
|
|
|
NSString *const ThemeKeyLegacyThemeEnabled = @"ThemeKeyThemeEnabled";
|
|
NSString *const ThemeKeyCurrentMode = @"ThemeKeyCurrentMode";
|
|
|
|
@interface Theme ()
|
|
|
|
@property (nonatomic) NSNumber *isDarkThemeEnabledNumber;
|
|
@property (nonatomic) NSNumber *cachedCurrentThemeNumber;
|
|
|
|
#if TESTABLE_BUILD
|
|
@property (nonatomic, nullable) NSNumber *isDarkThemeEnabledForTests;
|
|
#endif
|
|
|
|
@end
|
|
|
|
@implementation Theme
|
|
|
|
+ (SDSKeyValueStore *)keyValueStore
|
|
{
|
|
return [[SDSKeyValueStore alloc] initWithCollection:@"ThemeCollection"];
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
+ (Theme *)shared
|
|
{
|
|
static dispatch_once_t onceToken;
|
|
static Theme *instance;
|
|
dispatch_once(&onceToken, ^{ instance = [[self alloc] initDefault]; });
|
|
|
|
return instance;
|
|
}
|
|
|
|
- (instancetype)initDefault
|
|
{
|
|
self = [super init];
|
|
|
|
if (!self) {
|
|
return self;
|
|
}
|
|
|
|
OWSSingletonAssert();
|
|
|
|
AppReadinessRunNowOrWhenAppDidBecomeReadySync(^{
|
|
// IOS-782: +[Theme shared] re-enterant initialization
|
|
// AppReadiness will invoke the block synchronously if the app is already ready.
|
|
// This doesn't work here, because we'll end up reenterantly calling +shared
|
|
// if the app is in dark mode and the first call to +[Theme shared] happens
|
|
// after the app is ready.
|
|
//
|
|
// It looks like that pattern is only hit in the share extension, but we're better off
|
|
// asyncing always to ensure the dependency chain is broken. We're okay waiting, since
|
|
// there's no guarantee that this block in synchronously executed anyway.
|
|
dispatch_async(dispatch_get_main_queue(), ^{ [self notifyIfThemeModeIsNotDefault]; });
|
|
});
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)notifyIfThemeModeIsNotDefault
|
|
{
|
|
if (self.isDarkThemeEnabled || self.defaultTheme != self.getOrFetchCurrentTheme) {
|
|
[self themeDidChange];
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
+ (BOOL)isDarkThemeEnabled
|
|
{
|
|
return [self.shared isDarkThemeEnabled];
|
|
}
|
|
|
|
- (BOOL)isDarkThemeEnabled
|
|
{
|
|
// OWSAssertIsOnMainThread();
|
|
|
|
#if TESTABLE_BUILD
|
|
if (self.isDarkThemeEnabledForTests != nil) {
|
|
return self.isDarkThemeEnabledForTests.boolValue;
|
|
}
|
|
#endif
|
|
|
|
if (!AppReadiness.isAppReady) {
|
|
// Don't cache this value until it reflects the data store.
|
|
return self.isSystemDarkThemeEnabled;
|
|
}
|
|
|
|
if (self.isDarkThemeEnabledNumber == nil) {
|
|
BOOL isDarkThemeEnabled;
|
|
|
|
if (!CurrentAppContext().isMainApp) {
|
|
// Always respect the system theme in extensions
|
|
isDarkThemeEnabled = self.isSystemDarkThemeEnabled;
|
|
} else {
|
|
switch ([self getOrFetchCurrentTheme]) {
|
|
case ThemeMode_System:
|
|
isDarkThemeEnabled = self.isSystemDarkThemeEnabled;
|
|
break;
|
|
case ThemeMode_Dark:
|
|
isDarkThemeEnabled = YES;
|
|
break;
|
|
case ThemeMode_Light:
|
|
isDarkThemeEnabled = NO;
|
|
break;
|
|
}
|
|
}
|
|
|
|
self.isDarkThemeEnabledNumber = @(isDarkThemeEnabled);
|
|
}
|
|
|
|
return self.isDarkThemeEnabledNumber.boolValue;
|
|
}
|
|
|
|
#if TESTABLE_BUILD
|
|
+ (void)setIsDarkThemeEnabledForTests:(BOOL)value
|
|
{
|
|
self.shared.isDarkThemeEnabledForTests = @(value);
|
|
}
|
|
#endif
|
|
|
|
+ (ThemeMode)getOrFetchCurrentTheme
|
|
{
|
|
return [self.shared getOrFetchCurrentTheme];
|
|
}
|
|
|
|
- (ThemeMode)getOrFetchCurrentTheme
|
|
{
|
|
if (self.cachedCurrentThemeNumber) {
|
|
return self.cachedCurrentThemeNumber.unsignedIntegerValue;
|
|
}
|
|
|
|
if (!AppReadiness.isAppReady) {
|
|
return self.defaultTheme;
|
|
}
|
|
|
|
__block ThemeMode currentMode;
|
|
[self.databaseStorage readWithBlock:^(SDSAnyReadTransaction *transaction) {
|
|
BOOL hasDefinedMode = [Theme.keyValueStore hasValueForKey:ThemeKeyCurrentMode transaction:transaction];
|
|
if (!hasDefinedMode) {
|
|
// If the theme has not yet been defined, check if the user ever manually changed
|
|
// themes in a legacy app version. If so, preserve their selection. Otherwise,
|
|
// default to matching the system theme.
|
|
if (![Theme.keyValueStore hasValueForKey:ThemeKeyLegacyThemeEnabled transaction:transaction]) {
|
|
currentMode = ThemeMode_System;
|
|
} else {
|
|
BOOL isLegacyModeDark = [Theme.keyValueStore getBool:ThemeKeyLegacyThemeEnabled
|
|
defaultValue:NO
|
|
transaction:transaction];
|
|
currentMode = isLegacyModeDark ? ThemeMode_Dark : ThemeMode_Light;
|
|
}
|
|
} else {
|
|
currentMode = [Theme.keyValueStore getUInt:ThemeKeyCurrentMode
|
|
defaultValue:ThemeMode_System
|
|
transaction:transaction];
|
|
}
|
|
}];
|
|
|
|
self.cachedCurrentThemeNumber = @(currentMode);
|
|
return currentMode;
|
|
}
|
|
|
|
+ (void)setCurrentTheme:(ThemeMode)mode
|
|
{
|
|
[self.shared setCurrentTheme:mode];
|
|
}
|
|
|
|
- (void)setCurrentTheme:(ThemeMode)mode
|
|
{
|
|
OWSAssertIsOnMainThread();
|
|
|
|
DatabaseStorageWrite(self.databaseStorage, ^(SDSAnyWriteTransaction *transaction) {
|
|
[Theme.keyValueStore setUInt:mode key:ThemeKeyCurrentMode transaction:transaction];
|
|
});
|
|
|
|
NSNumber *previousMode = self.isDarkThemeEnabledNumber;
|
|
|
|
switch (mode) {
|
|
case ThemeMode_Light:
|
|
self.isDarkThemeEnabledNumber = @(NO);
|
|
break;
|
|
case ThemeMode_Dark:
|
|
self.isDarkThemeEnabledNumber = @(YES);
|
|
break;
|
|
case ThemeMode_System:
|
|
self.isDarkThemeEnabledNumber = @(self.isSystemDarkThemeEnabled);
|
|
break;
|
|
}
|
|
|
|
self.cachedCurrentThemeNumber = @(mode);
|
|
|
|
if (![previousMode isEqual:self.isDarkThemeEnabledNumber]) {
|
|
[self themeDidChange];
|
|
}
|
|
}
|
|
|
|
- (BOOL)isSystemDarkThemeEnabled
|
|
{
|
|
if (@available(iOS 13, *)) {
|
|
return UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark;
|
|
} else {
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
- (ThemeMode)defaultTheme
|
|
{
|
|
if (@available(iOS 13, *)) {
|
|
return ThemeMode_System;
|
|
}
|
|
|
|
return ThemeMode_Light;
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
+ (void)systemThemeChanged
|
|
{
|
|
[self.shared systemThemeChanged];
|
|
}
|
|
|
|
- (void)systemThemeChanged
|
|
{
|
|
// Do nothing, since we haven't setup the theme yet.
|
|
if (self.isDarkThemeEnabledNumber == nil) {
|
|
return;
|
|
}
|
|
|
|
// Theme can only be changed externally when in system mode.
|
|
if ([self getOrFetchCurrentTheme] != ThemeMode_System) {
|
|
return;
|
|
}
|
|
|
|
// The system theme has changed since the user was last in the app.
|
|
self.isDarkThemeEnabledNumber = @(self.isSystemDarkThemeEnabled);
|
|
[self themeDidChange];
|
|
}
|
|
|
|
- (void)themeDidChange
|
|
{
|
|
[UIUtil setupSignalAppearence];
|
|
|
|
[UIView performWithoutAnimation:^{
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:ThemeDidChangeNotification object:nil userInfo:nil];
|
|
}];
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
+ (UIColor *)backgroundColor
|
|
{
|
|
return (Theme.isDarkThemeEnabled ? Theme.darkThemeBackgroundColor : UIColor.ows_whiteColor);
|
|
}
|
|
|
|
+ (UIColor *)secondaryBackgroundColor
|
|
{
|
|
return (Theme.isDarkThemeEnabled ? UIColor.ows_gray80Color : UIColor.ows_gray02Color);
|
|
}
|
|
|
|
+ (UIColor *)washColor
|
|
{
|
|
return (Theme.isDarkThemeEnabled ? self.darkThemeWashColor : UIColor.ows_gray05Color);
|
|
}
|
|
|
|
+ (UIColor *)darkThemeWashColor
|
|
{
|
|
return UIColor.ows_gray75Color;
|
|
}
|
|
|
|
+ (UIColor *)primaryTextColor
|
|
{
|
|
return (Theme.isDarkThemeEnabled ? Theme.darkThemePrimaryColor : Theme.lightThemePrimaryColor);
|
|
}
|
|
|
|
+ (UIColor *)primaryIconColor
|
|
{
|
|
return (Theme.isDarkThemeEnabled ? self.darkThemeNavbarIconColor : UIColor.ows_gray75Color);
|
|
}
|
|
|
|
+ (UIColor *)secondaryTextAndIconColor
|
|
{
|
|
return (Theme.isDarkThemeEnabled ? Theme.darkThemeSecondaryTextAndIconColor : UIColor.ows_gray60Color);
|
|
}
|
|
|
|
+ (UIColor *)darkThemeSecondaryTextAndIconColor
|
|
{
|
|
return UIColor.ows_gray25Color;
|
|
}
|
|
|
|
+ (UIColor *)ternaryTextColor
|
|
{
|
|
return UIColor.ows_gray45Color;
|
|
}
|
|
|
|
+ (UIColor *)boldColor
|
|
{
|
|
return (Theme.isDarkThemeEnabled ? UIColor.ows_whiteColor : UIColor.blackColor);
|
|
}
|
|
|
|
+ (UIColor *)middleGrayColor
|
|
{
|
|
return [UIColor colorWithWhite:0.5f alpha:1.f];
|
|
}
|
|
|
|
+ (UIColor *)placeholderColor
|
|
{
|
|
return (Theme.isDarkThemeEnabled ? UIColor.ows_gray45Color : UIColor.ows_gray45Color);
|
|
}
|
|
|
|
+ (UIColor *)hairlineColor
|
|
{
|
|
return (Theme.isDarkThemeEnabled ? UIColor.ows_gray75Color : UIColor.ows_gray15Color);
|
|
}
|
|
|
|
+ (UIColor *)outlineColor
|
|
{
|
|
return Theme.isDarkThemeEnabled ? UIColor.ows_gray75Color : UIColor.ows_gray15Color;
|
|
}
|
|
|
|
+ (UIColor *)backdropColor
|
|
{
|
|
return UIColor.ows_blackAlpha40Color;
|
|
}
|
|
|
|
#pragma mark - Global App Colors
|
|
|
|
+ (UIColor *)navbarBackgroundColor
|
|
{
|
|
return (Theme.isDarkThemeEnabled ? self.darkThemeNavbarBackgroundColor : UIColor.ows_whiteColor);
|
|
}
|
|
|
|
+ (UIColor *)darkThemeNavbarBackgroundColor
|
|
{
|
|
return UIColor.ows_blackColor;
|
|
}
|
|
|
|
+ (UIColor *)darkThemeNavbarIconColor
|
|
{
|
|
return UIColor.ows_gray15Color;
|
|
}
|
|
|
|
+ (UIColor *)navbarTitleColor
|
|
{
|
|
return Theme.primaryTextColor;
|
|
}
|
|
|
|
+ (UIColor *)toolbarBackgroundColor
|
|
{
|
|
return self.navbarBackgroundColor;
|
|
}
|
|
|
|
+ (UIColor *)conversationInputBackgroundColor
|
|
{
|
|
return (Theme.isDarkThemeEnabled ? UIColor.ows_gray75Color : UIColor.ows_gray05Color);
|
|
}
|
|
|
|
+ (UIColor *)attachmentKeyboardItemBackgroundColor
|
|
{
|
|
return self.conversationInputBackgroundColor;
|
|
}
|
|
|
|
+ (UIColor *)attachmentKeyboardItemImageColor
|
|
{
|
|
return (Theme.isDarkThemeEnabled ? [UIColor colorWithRGBHex:0xd8d8d9] : [UIColor colorWithRGBHex:0x636467]);
|
|
}
|
|
|
|
+ (UIColor *)cellSelectedColor
|
|
{
|
|
return (Theme.isDarkThemeEnabled ? [UIColor colorWithWhite:0.2 alpha:1] : [UIColor colorWithWhite:0.92 alpha:1]);
|
|
}
|
|
|
|
+ (UIColor *)cellSeparatorColor
|
|
{
|
|
return Theme.hairlineColor;
|
|
}
|
|
|
|
+ (UIColor *)cursorColor
|
|
{
|
|
return Theme.isDarkThemeEnabled ? UIColor.ows_whiteColor : UIColor.ows_accentBlueColor;
|
|
}
|
|
|
|
+ (UIColor *)accentBlueColor
|
|
{
|
|
return Theme.isDarkThemeEnabled ? UIColor.ows_accentBlueDarkColor : UIColor.ows_accentBlueColor;
|
|
}
|
|
|
|
+ (UIColor *)tableCellBackgroundColor
|
|
{
|
|
return Theme.isDarkThemeEnabled ? UIColor.ows_gray95Color : Theme.backgroundColor;
|
|
}
|
|
|
|
+ (UIColor *)tableViewBackgroundColor
|
|
{
|
|
return (Theme.isDarkThemeEnabled ? UIColor.ows_blackColor : UIColor.ows_gray02Color);
|
|
}
|
|
|
|
+ (UIColor *)tableCell2BackgroundColor
|
|
{
|
|
return Theme.isDarkThemeEnabled ? UIColor.ows_gray90Color : UIColor.ows_whiteColor;
|
|
}
|
|
|
|
+ (UIColor *)tableCell2PresentedBackgroundColor
|
|
{
|
|
return Theme.isDarkThemeEnabled ? UIColor.ows_gray80Color : UIColor.ows_whiteColor;
|
|
}
|
|
|
|
+ (UIColor *)tableCell2SelectedBackgroundColor
|
|
{
|
|
return Theme.isDarkThemeEnabled ? UIColor.ows_gray80Color : UIColor.ows_gray15Color;
|
|
}
|
|
|
|
+ (UIColor *)tableCell2PresentedSelectedBackgroundColor
|
|
{
|
|
return Theme.isDarkThemeEnabled ? UIColor.ows_gray75Color : UIColor.ows_gray15Color;
|
|
}
|
|
|
|
+ (UIColor *)tableView2BackgroundColor
|
|
{
|
|
return (Theme.isDarkThemeEnabled ? UIColor.ows_blackColor : UIColor.ows_gray10Color);
|
|
}
|
|
|
|
+ (UIColor *)tableView2PresentedBackgroundColor
|
|
{
|
|
return (Theme.isDarkThemeEnabled ? UIColor.ows_gray90Color : UIColor.ows_gray10Color);
|
|
}
|
|
|
|
+ (UIColor *)tableView2SeparatorColor
|
|
{
|
|
return (Theme.isDarkThemeEnabled ? UIColor.ows_gray75Color : UIColor.ows_gray20Color);
|
|
}
|
|
|
|
+ (UIColor *)tableView2PresentedSeparatorColor
|
|
{
|
|
return (Theme.isDarkThemeEnabled ? UIColor.ows_gray65Color : UIColor.ows_gray20Color);
|
|
}
|
|
|
|
+ (UIColor *)darkThemeBackgroundColor
|
|
{
|
|
return UIColor.ows_blackColor;
|
|
}
|
|
|
|
+ (UIColor *)darkThemePrimaryColor
|
|
{
|
|
return UIColor.ows_gray02Color;
|
|
}
|
|
|
|
+ (UIColor *)lightThemePrimaryColor
|
|
{
|
|
return UIColor.ows_gray90Color;
|
|
}
|
|
|
|
+ (UIColor *)galleryHighlightColor
|
|
{
|
|
return [UIColor colorWithRGBHex:0x1f8fe8];
|
|
}
|
|
|
|
+ (UIColor *)conversationButtonBackgroundColor
|
|
{
|
|
return (Theme.isDarkThemeEnabled ? UIColor.ows_gray80Color : UIColor.ows_gray02Color);
|
|
}
|
|
|
|
+ (UIColor *)conversationButtonTextColor
|
|
{
|
|
return (Theme.isDarkThemeEnabled ? UIColor.ows_gray05Color : UIColor.ows_accentBlueColor);
|
|
}
|
|
|
|
|
|
+ (UIBlurEffect *)barBlurEffect
|
|
{
|
|
return Theme.isDarkThemeEnabled ? self.darkThemeBarBlurEffect
|
|
: [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
|
|
}
|
|
|
|
+ (UIBlurEffect *)darkThemeBarBlurEffect
|
|
{
|
|
return [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
|
|
}
|
|
|
|
+ (UIKeyboardAppearance)keyboardAppearance
|
|
{
|
|
return Theme.isDarkThemeEnabled ? self.darkThemeKeyboardAppearance : UIKeyboardAppearanceDefault;
|
|
}
|
|
|
|
+ (UIColor *)keyboardBackgroundColor
|
|
{
|
|
return Theme.isDarkThemeEnabled ? UIColor.ows_gray90Color : UIColor.ows_gray02Color;
|
|
}
|
|
|
|
+ (UIKeyboardAppearance)darkThemeKeyboardAppearance
|
|
{
|
|
return UIKeyboardAppearanceDark;
|
|
}
|
|
|
|
#pragma mark - Search Bar
|
|
|
|
+ (UIBarStyle)barStyle
|
|
{
|
|
return Theme.isDarkThemeEnabled ? UIBarStyleBlack : UIBarStyleDefault;
|
|
}
|
|
|
|
+ (UIColor *)searchFieldBackgroundColor
|
|
{
|
|
return Theme.washColor;
|
|
}
|
|
|
|
+ (UIColor *)searchFieldElevatedBackgroundColor
|
|
{
|
|
return Theme.isDarkThemeEnabled ? UIColor.ows_gray75Color : [[UIColor alloc] initWithRgbHex:0xe0e0e0];
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
+ (UIColor *)toastForegroundColor
|
|
{
|
|
return (Theme.isDarkThemeEnabled ? UIColor.ows_whiteColor : UIColor.ows_whiteColor);
|
|
}
|
|
|
|
+ (UIColor *)toastBackgroundColor
|
|
{
|
|
return (Theme.isDarkThemeEnabled ? UIColor.ows_gray75Color : UIColor.ows_gray60Color);
|
|
}
|
|
|
|
+ (UIColor *)scrollButtonBackgroundColor
|
|
{
|
|
return Theme.isDarkThemeEnabled ? [UIColor colorWithWhite:0.25f alpha:1.f]
|
|
: [UIColor colorWithWhite:0.95f alpha:1.f];
|
|
}
|
|
|
|
@end
|
|
|
|
NS_ASSUME_NONNULL_END
|