From 9402e088b2f239bb08d1c49c1f208d6e76494878 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Thu, 21 Feb 2019 09:53:49 -0500 Subject: [PATCH] Apply design feedback from Myles. --- Signal.xcodeproj/project.pbxproj | 4 ++ .../PrivacySettingsTableViewController.m | 2 +- .../HomeView/HomeViewController.m | 37 +++++++++++--- .../OnboardingPermissionsViewController.swift | 12 ++--- .../OnboardingProfileViewController.swift | 5 +- SignalMessaging/utils/AppPreferences.swift | 37 ++++++++++++++ SignalServiceKit/src/Contacts/TSThread.m | 7 +++ .../Interactions/OWSLinkPreview.swift | 10 ++-- .../src/Storage/YapDatabaseConnection+OWS.m | 8 ++- .../src/Storage/YapDatabaseTransaction+OWS.h | 3 +- .../src/Storage/YapDatabaseTransaction+OWS.m | 16 +++++- .../src/Util/SSKPreferences.swift | 49 +++++++++++++++---- 12 files changed, 152 insertions(+), 38 deletions(-) create mode 100644 SignalMessaging/utils/AppPreferences.swift diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index bcd165bdf..aadd52a2a 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -164,6 +164,7 @@ 3496957421A301A100DCFE74 /* OWSBackupAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3496956B21A301A100DCFE74 /* OWSBackupAPI.swift */; }; 349EA07C2162AEA800F7B17F /* OWS111UDAttributesMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349EA07B2162AEA700F7B17F /* OWS111UDAttributesMigration.swift */; }; 349ED990221B0194008045B0 /* Onboarding2FAViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349ED98F221B0194008045B0 /* Onboarding2FAViewController.swift */; }; + 349ED992221EE80D008045B0 /* AppPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349ED991221EE80D008045B0 /* AppPreferences.swift */; }; 34A4C61E221613D00042EF2E /* OnboardingVerificationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34A4C61D221613D00042EF2E /* OnboardingVerificationViewController.swift */; }; 34A4C62022175C5C0042EF2E /* OnboardingProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34A4C61F22175C5C0042EF2E /* OnboardingProfileViewController.swift */; }; 34A6C28021E503E700B5B12E /* OWSImagePickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34A6C27F21E503E600B5B12E /* OWSImagePickerController.swift */; }; @@ -843,6 +844,7 @@ 3496956D21A301A100DCFE74 /* OWSBackupIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBackupIO.h; sourceTree = ""; }; 349EA07B2162AEA700F7B17F /* OWS111UDAttributesMigration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWS111UDAttributesMigration.swift; sourceTree = ""; }; 349ED98F221B0194008045B0 /* Onboarding2FAViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Onboarding2FAViewController.swift; sourceTree = ""; }; + 349ED991221EE80D008045B0 /* AppPreferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppPreferences.swift; sourceTree = ""; }; 34A4C61D221613D00042EF2E /* OnboardingVerificationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingVerificationViewController.swift; sourceTree = ""; }; 34A4C61F22175C5C0042EF2E /* OnboardingProfileViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingProfileViewController.swift; sourceTree = ""; }; 34A6C27F21E503E600B5B12E /* OWSImagePickerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSImagePickerController.swift; sourceTree = ""; }; @@ -1568,6 +1570,7 @@ 34480B471FD0A60200BC14EF /* utils */ = { isa = PBXGroup; children = ( + 349ED991221EE80D008045B0 /* AppPreferences.swift */, 452EC6E0205FF5DC000E787C /* Bench.swift */, 4C948FF62146EB4800349F0D /* BlockListCache.swift */, 343D3D991E9283F100165CA4 /* BlockListUIUtils.h */, @@ -3423,6 +3426,7 @@ 34612A071FD7238600532771 /* OWSSyncManager.m in Sources */, 450C801220AD1D5B00F3A091 /* UIDevice+featureSupport.swift in Sources */, 451F8A471FD715BA005CB9DA /* OWSAvatarBuilder.m in Sources */, + 349ED992221EE80D008045B0 /* AppPreferences.swift in Sources */, 34AC09E7211B39B100997B47 /* MessageApprovalViewController.swift in Sources */, 34480B591FD0A7A400BC14EF /* OWSScrubbingLogFormatter.m in Sources */, 34AC09F0211B39B100997B47 /* AttachmentApprovalViewController.swift in Sources */, diff --git a/Signal/src/ViewControllers/AppSettings/PrivacySettingsTableViewController.m b/Signal/src/ViewControllers/AppSettings/PrivacySettingsTableViewController.m index de08936f7..fd8b67056 100644 --- a/Signal/src/ViewControllers/AppSettings/PrivacySettingsTableViewController.m +++ b/Signal/src/ViewControllers/AppSettings/PrivacySettingsTableViewController.m @@ -433,7 +433,7 @@ static NSString *const kSealedSenderInfoURL = @"https://signal.org/blog/sealed-s - (void)didToggleLinkPreviewsEnabled:(UISwitch *)sender { OWSLogInfo(@"toggled to: %@", (sender.isOn ? @"ON" : @"OFF")); - [SSKPreferences setAreLinkPreviewsEnabledWithValue:sender.isOn]; + SSKPreferences.areLinkPreviewsEnabled = sender.isOn; } - (void)show2FASettings diff --git a/Signal/src/ViewControllers/HomeView/HomeViewController.m b/Signal/src/ViewControllers/HomeView/HomeViewController.m index c21f0968c..96125433d 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewController.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewController.m @@ -345,7 +345,10 @@ typedef NS_ENUM(NSInteger, HomeViewControllerSection) { [self createFirstConversationCueView]; [self.view addSubview:self.firstConversationCueView]; [self.firstConversationCueView autoPinToTopLayoutGuideOfViewController:self withInset:0.f]; - [self.firstConversationCueView autoPinEdgeToSuperviewEdge:ALEdgeTrailing withInset:10]; + // This inset bakes in assumptions about UINavigationBar layout, but I'm not sure + // there's a better way to do it, since it isn't safe to use iOS auto layout with + // UINavigationBar contents. + [self.firstConversationCueView autoPinEdgeToSuperviewEdge:ALEdgeTrailing withInset:6.f]; [self.firstConversationCueView autoPinEdgeToSuperviewEdge:ALEdgeLeading withInset:10 relation:NSLayoutRelationGreaterThanOrEqual]; @@ -410,9 +413,9 @@ typedef NS_ENUM(NSInteger, HomeViewControllerSection) { label.lineBreakMode = NSLineBreakByWordWrapping; OWSLayerView *layerView = [OWSLayerView new]; - layerView.layoutMargins = UIEdgeInsetsMake(11 + kTailHeight, 16, 7, 16); + layerView.layoutMargins = UIEdgeInsetsMake(11 + kTailHeight, 16, 11, 16); CAShapeLayer *shapeLayer = [CAShapeLayer new]; - shapeLayer.fillColor = [OWSConversationColor ows_wintergreenColor].CGColor; + shapeLayer.fillColor = UIColor.ows_signalBlueColor.CGColor; [layerView.layer addSublayer:shapeLayer]; layerView.layoutCallback = ^(UIView *view) { UIBezierPath *bezierPath = [UIBezierPath new]; @@ -443,10 +446,24 @@ typedef NS_ENUM(NSInteger, HomeViewControllerSection) { [layerView addSubview:label]; [label ows_autoPinToSuperviewMargins]; + layerView.userInteractionEnabled = YES; + [layerView + addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self + action:@selector(firstConversationCueWasTapped:)]]; + self.firstConversationCueView = layerView; self.firstConversationLabel = label; } +- (void)firstConversationCueWasTapped:(UITapGestureRecognizer *)gestureRecognizer +{ + OWSLogInfo(@""); + + AppPreferences.hasDimissedFirstConversationCue = YES; + + [self updateViewState]; +} + - (void)updateFirstConversationLabel { NSArray *signalAccounts = self.contactsManager.signalAccounts; @@ -1453,7 +1470,7 @@ typedef NS_ENUM(NSInteger, HomeViewControllerSection) { [self updateReminderViews]; } -#pragma mark Database delegates +#pragma mark - Database delegates - (YapDatabaseConnection *)uiDatabaseConnection { @@ -1614,10 +1631,7 @@ typedef NS_ENUM(NSInteger, HomeViewControllerSection) { - (void)updateViewState { - NSUInteger inboxCount = self.numberOfInboxThreads; - NSUInteger archiveCount = self.numberOfArchivedThreads; - - if (self.homeViewMode == HomeViewMode_Inbox && inboxCount == 0 && archiveCount == 0) { + if (self.shouldShowFirstConversationCue) { [_tableView setHidden:YES]; [self.emptyInboxView setHidden:NO]; [self.firstConversationCueView setHidden:NO]; @@ -1629,6 +1643,13 @@ typedef NS_ENUM(NSInteger, HomeViewControllerSection) { } } +- (BOOL)shouldShowFirstConversationCue +{ + return (self.homeViewMode == HomeViewMode_Inbox && self.numberOfInboxThreads == 0 + && self.numberOfArchivedThreads == 0 && !AppPreferences.hasDimissedFirstConversationCue + && !SSKPreferences.hasSavedThread); +} + // We want to delay asking for a review until an opportune time. // If the user has *just* launched Signal they intend to do something, we don't want to interrupt them. // If the user hasn't sent a message, we don't want to ask them for a review yet. diff --git a/Signal/src/ViewControllers/Registration/OnboardingPermissionsViewController.swift b/Signal/src/ViewControllers/Registration/OnboardingPermissionsViewController.swift index 904eed0a3..1500343a1 100644 --- a/Signal/src/ViewControllers/Registration/OnboardingPermissionsViewController.swift +++ b/Signal/src/ViewControllers/Registration/OnboardingPermissionsViewController.swift @@ -32,17 +32,14 @@ public class OnboardingPermissionsViewController: OnboardingBaseViewController { comment: "Label for the 'not now' button in the 'onboarding permissions' view."), selector: #selector(notNowPressed)) - let topSpacer = UIView.vStretchingSpacer() - let bottomSpacer = UIView.vStretchingSpacer() let stackView = UIStackView(arrangedSubviews: [ titleLabel, UIView.spacer(withHeight: 20), explanationLabel, - topSpacer, - giveAccessButton, - UIView.spacer(withHeight: 12), + UIView.vStretchingSpacer(), notNowButton, - bottomSpacer + UIView.spacer(withHeight: 12), + giveAccessButton ]) stackView.axis = .vertical stackView.alignment = .fill @@ -52,9 +49,6 @@ public class OnboardingPermissionsViewController: OnboardingBaseViewController { stackView.autoPinWidthToSuperview() stackView.autoPin(toTopLayoutGuideOf: self, withInset: 0) stackView.autoPin(toBottomLayoutGuideOf: self, withInset: 0) - - // Ensure whitespace is balanced, so inputs are vertically centered. - topSpacer.autoMatch(.height, to: .height, of: bottomSpacer) } // MARK: Request Access diff --git a/Signal/src/ViewControllers/Registration/OnboardingProfileViewController.swift b/Signal/src/ViewControllers/Registration/OnboardingProfileViewController.swift index a8c3f9023..052409b28 100644 --- a/Signal/src/ViewControllers/Registration/OnboardingProfileViewController.swift +++ b/Signal/src/ViewControllers/Registration/OnboardingProfileViewController.swift @@ -100,9 +100,8 @@ public class OnboardingProfileViewController: OnboardingBaseViewController { profileRow, UIView.spacer(withHeight: 25), explanationLabel, - UIView.spacer(withHeight: 20), - nextButton, - bottomSpacer + bottomSpacer, + nextButton ]) stackView.axis = .vertical stackView.alignment = .fill diff --git a/SignalMessaging/utils/AppPreferences.swift b/SignalMessaging/utils/AppPreferences.swift new file mode 100644 index 000000000..a0721dc63 --- /dev/null +++ b/SignalMessaging/utils/AppPreferences.swift @@ -0,0 +1,37 @@ +// +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. +// + +import Foundation + +@objc +public class AppPreferences: NSObject { + // Never instantiate this class. + private override init() {} + + private static let collection = "AppPreferences" + + // MARK: - + + private static let hasDimissedFirstConversationCueKey = "hasDimissedFirstConversationCue" + + @objc + public static var hasDimissedFirstConversationCue: Bool { + get { + return getBool(key: hasDimissedFirstConversationCueKey) + } + set { + setBool(newValue, key: hasDimissedFirstConversationCueKey) + } + } + + // MARK: - + + private class func getBool(key: String, defaultValue: Bool = false) -> Bool { + return OWSPrimaryStorage.dbReadConnection().bool(forKey: key, inCollection: collection, defaultValue: defaultValue) + } + + private class func setBool(_ value: Bool, key: String) { + OWSPrimaryStorage.dbReadWriteConnection().setBool(value, forKey: key, inCollection: collection) + } +} diff --git a/SignalServiceKit/src/Contacts/TSThread.m b/SignalServiceKit/src/Contacts/TSThread.m index 441557ff9..744e7c655 100644 --- a/SignalServiceKit/src/Contacts/TSThread.m +++ b/SignalServiceKit/src/Contacts/TSThread.m @@ -151,6 +151,13 @@ ConversationColorName const kConversationColorName_Default = ConversationColorNa return self; } +- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction +{ + [super saveWithTransaction:transaction]; + + [SSKPreferences setHasSavedThreadWithValue:YES transaction:transaction]; +} + - (void)removeWithTransaction:(YapDatabaseReadWriteTransaction *)transaction { [self removeAllThreadInteractionsWithTransaction:transaction]; diff --git a/SignalServiceKit/src/Messages/Interactions/OWSLinkPreview.swift b/SignalServiceKit/src/Messages/Interactions/OWSLinkPreview.swift index 5663017dd..01d416d77 100644 --- a/SignalServiceKit/src/Messages/Interactions/OWSLinkPreview.swift +++ b/SignalServiceKit/src/Messages/Interactions/OWSLinkPreview.swift @@ -195,7 +195,7 @@ public class OWSLinkPreview: MTLModel { guard OWSLinkPreview.featureEnabled else { throw LinkPreviewError.noPreview } - guard SSKPreferences.areLinkPreviewsEnabled() else { + guard SSKPreferences.areLinkPreviewsEnabled else { throw LinkPreviewError.noPreview } let imageAttachmentId = OWSLinkPreview.saveAttachmentIfPossible(inputFilePath: info.imageFilePath, @@ -445,7 +445,7 @@ public class OWSLinkPreview: MTLModel { return nil } - guard SSKPreferences.areLinkPreviewsEnabled() else { + guard SSKPreferences.areLinkPreviewsEnabled else { return nil } @@ -497,7 +497,7 @@ public class OWSLinkPreview: MTLModel { guard OWSLinkPreview.featureEnabled else { return [] } - guard SSKPreferences.areLinkPreviewsEnabled() else { + guard SSKPreferences.areLinkPreviewsEnabled else { return [] } @@ -546,7 +546,7 @@ public class OWSLinkPreview: MTLModel { guard OWSLinkPreview.featureEnabled else { return } - guard SSKPreferences.areLinkPreviewsEnabled() else { + guard SSKPreferences.areLinkPreviewsEnabled else { return } @@ -565,7 +565,7 @@ public class OWSLinkPreview: MTLModel { guard OWSLinkPreview.featureEnabled else { return Promise(error: LinkPreviewError.featureDisabled) } - guard SSKPreferences.areLinkPreviewsEnabled() else { + guard SSKPreferences.areLinkPreviewsEnabled else { return Promise(error: LinkPreviewError.featureDisabled) } guard let previewUrl = previewUrl else { diff --git a/SignalServiceKit/src/Storage/YapDatabaseConnection+OWS.m b/SignalServiceKit/src/Storage/YapDatabaseConnection+OWS.m index 7da0d63a7..12d47b08e 100644 --- a/SignalServiceKit/src/Storage/YapDatabaseConnection+OWS.m +++ b/SignalServiceKit/src/Storage/YapDatabaseConnection+OWS.m @@ -1,5 +1,5 @@ // -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. // #import "YapDatabaseConnection+OWS.h" @@ -137,6 +137,12 @@ NS_ASSUME_NONNULL_BEGIN OWSAssertDebug(key.length > 0); OWSAssertDebug(collection.length > 0); + NSNumber *_Nullable oldValue = [self objectForKey:key inCollection:collection ofExpectedType:[NSNumber class]]; + if (oldValue && [@(value) isEqual:oldValue]) { + // Skip redundant writes. + return; + } + [self setObject:@(value) forKey:key inCollection:collection]; } diff --git a/SignalServiceKit/src/Storage/YapDatabaseTransaction+OWS.h b/SignalServiceKit/src/Storage/YapDatabaseTransaction+OWS.h index 3a98dad0f..d4326e8da 100644 --- a/SignalServiceKit/src/Storage/YapDatabaseTransaction+OWS.h +++ b/SignalServiceKit/src/Storage/YapDatabaseTransaction+OWS.h @@ -1,5 +1,5 @@ // -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. // #import @@ -36,6 +36,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)restoreSnapshotOfCollection:(NSString *)collection snapshotFilePath:(NSString *)snapshotFilePath; #endif +- (void)setBool:(BOOL)value forKey:(NSString *)key inCollection:(NSString *)collection; - (void)setDate:(NSDate *)value forKey:(NSString *)key inCollection:(NSString *)collection; @end diff --git a/SignalServiceKit/src/Storage/YapDatabaseTransaction+OWS.m b/SignalServiceKit/src/Storage/YapDatabaseTransaction+OWS.m index 70de63012..626472aad 100644 --- a/SignalServiceKit/src/Storage/YapDatabaseTransaction+OWS.m +++ b/SignalServiceKit/src/Storage/YapDatabaseTransaction+OWS.m @@ -1,5 +1,5 @@ // -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. // #import "YapDatabaseTransaction+OWS.h" @@ -149,6 +149,20 @@ NS_ASSUME_NONNULL_BEGIN } #endif +- (void)setBool:(BOOL)value forKey:(NSString *)key inCollection:(NSString *)collection +{ + OWSAssertDebug(key.length > 0); + OWSAssertDebug(collection.length > 0); + + NSNumber *_Nullable oldValue = [self objectForKey:key inCollection:collection ofExpectedType:[NSNumber class]]; + if (oldValue && [@(value) isEqual:oldValue]) { + // Skip redundant writes. + return; + } + + [self setObject:@(value) forKey:key inCollection:collection]; +} + - (void)setDate:(NSDate *)value forKey:(NSString *)key inCollection:(NSString *)collection { [self setObject:@(value.timeIntervalSince1970) forKey:key inCollection:collection]; diff --git a/SignalServiceKit/src/Util/SSKPreferences.swift b/SignalServiceKit/src/Util/SSKPreferences.swift index d81e01581..64c7e5795 100644 --- a/SignalServiceKit/src/Util/SSKPreferences.swift +++ b/SignalServiceKit/src/Util/SSKPreferences.swift @@ -10,20 +10,51 @@ public class SSKPreferences: NSObject { private override init() {} private static let collection = "SSKPreferences" + + // MARK: - + private static let areLinkPreviewsEnabledKey = "areLinkPreviewsEnabled" @objc - public class func areLinkPreviewsEnabled() -> Bool { - return OWSPrimaryStorage.dbReadConnection().bool(forKey: areLinkPreviewsEnabledKey, - inCollection: collection, - defaultValue: true) + public static var areLinkPreviewsEnabled: Bool { + get { + return getBool(key: areLinkPreviewsEnabledKey, defaultValue: true) + } + set { + setBool(newValue, key: areLinkPreviewsEnabledKey) + + SSKEnvironment.shared.syncManager.sendConfigurationSyncMessage() + } + } + + // MARK: - + + private static let hasSavedThreadKey = "hasSavedThread" + + @objc + public static var hasSavedThread: Bool { + get { + return getBool(key: hasSavedThreadKey) + } + set { + setBool(newValue, key: hasSavedThreadKey) + } } @objc - public class func setAreLinkPreviewsEnabled(value: Bool) { - OWSPrimaryStorage.dbReadWriteConnection().setBool(value, - forKey: areLinkPreviewsEnabledKey, - inCollection: collection) - SSKEnvironment.shared.syncManager.sendConfigurationSyncMessage() + public class func setHasSavedThread(value: Bool, transaction: YapDatabaseReadWriteTransaction) { + transaction.setBool(value, + forKey: areLinkPreviewsEnabledKey, + inCollection: collection) + } + + // MARK: - + + private class func getBool(key: String, defaultValue: Bool = false) -> Bool { + return OWSPrimaryStorage.dbReadConnection().bool(forKey: key, inCollection: collection, defaultValue: defaultValue) + } + + private class func setBool(_ value: Bool, key: String) { + OWSPrimaryStorage.dbReadWriteConnection().setBool(value, forKey: key, inCollection: collection) } }