From f782ea97df7e82a8a96cb2eddbf39ecf46ae94f5 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Mon, 23 Apr 2018 09:30:37 -0400 Subject: [PATCH] Use loading screen whenever launch is slow. Previously we had to manually account for each version that had a DB change. // FREEBIE --- Signal.xcodeproj/project.pbxproj | 4 + Signal/src/AppDelegate.m | 90 +++------------- .../LoadingViewController.swift | 102 ++++++++++++++++++ .../translations/en.lproj/Localizable.strings | 2 +- 4 files changed, 119 insertions(+), 79 deletions(-) create mode 100644 Signal/src/ViewControllers/LoadingViewController.swift diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 931c39def..c466642a5 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -307,6 +307,7 @@ 45360B911F952AA900FA666C /* MarqueeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E5A6981F61E6DD001E4A8A /* MarqueeLabel.swift */; }; 4539B5861F79348F007141FF /* PushRegistrationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4539B5851F79348F007141FF /* PushRegistrationManager.swift */; }; 4542DF52208B82E9007B4E76 /* ThreadModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4542DF51208B82E9007B4E76 /* ThreadModel.swift */; }; + 4542DF54208D40AC007B4E76 /* LoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4542DF53208D40AC007B4E76 /* LoadingViewController.swift */; }; 45464DBC1DFA041F001D3FD6 /* DataChannelMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45464DBB1DFA041F001D3FD6 /* DataChannelMessage.swift */; }; 454A84042059C787008B8C75 /* MediaTileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 454A84032059C787008B8C75 /* MediaTileViewController.swift */; }; 454A965A1FD6017E008D2A0E /* SignalAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D913491F62D4A500722898 /* SignalAttachment.swift */; }; @@ -931,6 +932,7 @@ 4539B5851F79348F007141FF /* PushRegistrationManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushRegistrationManager.swift; sourceTree = ""; }; 453CC0361D08E1A60040EBA3 /* sn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sn; path = translations/sn.lproj/Localizable.strings; sourceTree = ""; }; 4542DF51208B82E9007B4E76 /* ThreadModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadModel.swift; sourceTree = ""; }; + 4542DF53208D40AC007B4E76 /* LoadingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingViewController.swift; sourceTree = ""; }; 45464DBB1DFA041F001D3FD6 /* DataChannelMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataChannelMessage.swift; sourceTree = ""; }; 454A84032059C787008B8C75 /* MediaTileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaTileViewController.swift; sourceTree = ""; }; 454A965E1FD60EA2008D2A0E /* OWSFlatButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OWSFlatButton.swift; path = SignalMessaging/Views/OWSFlatButton.swift; sourceTree = SOURCE_ROOT; }; @@ -1655,6 +1657,7 @@ 34B3F86E1E8DF1700035BE1A /* SignalsNavigationController.m */, 340FC897204DAC8D007AEB0F /* ThreadSettings */, 34D1F0BE1F8EC1760066283D /* Utils */, + 4542DF53208D40AC007B4E76 /* LoadingViewController.swift */, ); path = ViewControllers; sourceTree = ""; @@ -3200,6 +3203,7 @@ 452314A01F7E9E18003A428C /* DirectionalPanGestureRecognizer.swift in Sources */, 34330AA31E79686200DF2FB9 /* OWSProgressView.m in Sources */, 45D2AC02204885170033C692 /* OWS2FAReminderViewController.swift in Sources */, + 4542DF54208D40AC007B4E76 /* LoadingViewController.swift in Sources */, 34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */, 34D1F0B71F87F8850066283D /* OWSGenericAttachmentView.m in Sources */, 34B3F8801E8DF1700035BE1A /* InviteFlow.swift in Sources */, diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index e0fa2c9e3..31433bb35 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -62,6 +62,8 @@ static NSString *const kInitialViewControllerIdentifier = @"UserInitialViewContr static NSString *const kURLSchemeSGNLKey = @"sgnl"; static NSString *const kURLHostVerifyPrefix = @"verify"; +static NSTimeInterval launchStartedAt; + @interface AppDelegate () @property (nonatomic) BOOL hasInitialRootViewController; @@ -105,6 +107,8 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; // This should be the first thing we do. SetCurrentAppContext([MainAppContext new]); + launchStartedAt = CACurrentMediaTime(); + BOOL isLoggingEnabled; #ifdef DEBUG // Specified at Product -> Scheme -> Edit Scheme -> Test -> Arguments -> Environment to avoid things like @@ -177,8 +181,8 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; UIWindow *mainWindow = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window = mainWindow; CurrentAppContext().mainWindow = mainWindow; - // Show the launch screen until the async database view registrations are complete. - mainWindow.rootViewController = [self loadingRootViewController]; + // Show LoadingViewController until the async database view registrations are complete. + mainWindow.rootViewController = [LoadingViewController new]; [mainWindow makeKeyAndVisible]; // Accept push notification when app is not open @@ -311,11 +315,9 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; - // Show the launch screen until the async database view registrations are complete. - // - // Note: we void using loadingRootViewController, since it will indirectly try to - // instantiate primary storage, which will fail. - self.window.rootViewController = [self loadingRootViewControllerWithShowUpgradeWarning:NO]; + // Show the launch screen + self.window.rootViewController = + [[UIStoryboard storyboardWithName:@"Launch Screen" bundle:nil] instantiateInitialViewController]; [self.window makeKeyAndVisible]; @@ -412,75 +414,6 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; DDLogInfo(@"iPhone Version: %@", platform); } -- (UIViewController *)loadingRootViewController -{ - NSString *lastLaunchedAppVersion = AppVersion.instance.lastAppVersion; - NSString *lastCompletedLaunchAppVersion = AppVersion.instance.lastCompletedLaunchAppVersion; - // Every time we change or add a database view to a large collection or - // add a database migration to a large collection, this can delay launch - // so we need to bump this constant. - // - // We added a database migration around all outgoing messages in v2.25.0. - NSString *kLastVersionWithDatabaseViewChange = @"2.25.0"; - BOOL mayNeedUpgrade = ([TSAccountManager isRegistered] && lastLaunchedAppVersion - && (!lastCompletedLaunchAppVersion || - [VersionMigrations isVersion:lastCompletedLaunchAppVersion - lessThan:kLastVersionWithDatabaseViewChange])); - DDLogInfo(@"%@ mayNeedUpgrade: %d", self.logTag, mayNeedUpgrade); - - return [self loadingRootViewControllerWithShowUpgradeWarning:mayNeedUpgrade]; -} - -- (UIViewController *)loadingRootViewControllerWithShowUpgradeWarning:(BOOL)showUpgradeWarning -{ - UIViewController *viewController = - [[UIStoryboard storyboardWithName:@"Launch Screen" bundle:nil] instantiateInitialViewController]; - - if (!showUpgradeWarning) { - return viewController; - } - - UIView *rootView = viewController.view; - UIImageView *iconView = nil; - for (UIView *subview in viewController.view.subviews) { - if ([subview isKindOfClass:[UIImageView class]]) { - iconView = (UIImageView *)subview; - break; - } - } - if (!iconView) { - OWSFail(@"Database view registration overlay has unexpected contents."); - return viewController; - } - - UILabel *bottomLabel = [UILabel new]; - bottomLabel.text = NSLocalizedString( - @"DATABASE_VIEW_OVERLAY_SUBTITLE", @"Subtitle shown while the app is updating its database."); - bottomLabel.font = [UIFont ows_mediumFontWithSize:16.f]; - bottomLabel.textColor = [UIColor whiteColor]; - bottomLabel.numberOfLines = 0; - bottomLabel.lineBreakMode = NSLineBreakByWordWrapping; - bottomLabel.textAlignment = NSTextAlignmentCenter; - [rootView addSubview:bottomLabel]; - - UILabel *topLabel = [UILabel new]; - topLabel.text - = NSLocalizedString(@"DATABASE_VIEW_OVERLAY_TITLE", @"Title shown while the app is updating its database."); - topLabel.font = [UIFont ows_mediumFontWithSize:20.f]; - topLabel.textColor = [UIColor whiteColor]; - topLabel.numberOfLines = 0; - topLabel.lineBreakMode = NSLineBreakByWordWrapping; - topLabel.textAlignment = NSTextAlignmentCenter; - [rootView addSubview:topLabel]; - - [bottomLabel autoPinWidthToSuperviewWithMargin:20.f]; - [topLabel autoPinWidthToSuperviewWithMargin:20.f]; - [bottomLabel autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:topLabel withOffset:10.f]; - [iconView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:bottomLabel withOffset:40.f]; - - return viewController; -} - - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { OWSAssertIsOnMainThread(); @@ -1145,7 +1078,7 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; [self enableBackgroundRefreshIfNecessary]; if ([TSAccountManager isRegistered]) { - DDLogInfo(@"localNumber: %@", [TSAccountManager localNumber]); + DDLogInfo(@"%@ localNumber: %@", [TSAccountManager localNumber], self.logTag); [[OWSPrimaryStorage sharedManager].newDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { @@ -1177,7 +1110,8 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; } self.hasInitialRootViewController = YES; - DDLogInfo(@"%@ Presenting initial root view controller", self.logTag); + NSTimeInterval startupDuration = CACurrentMediaTime() - launchStartedAt; + DDLogInfo(@"%@ Presenting app %.2f seconds after launch started.", self.logTag, startupDuration); if ([TSAccountManager isRegistered]) { HomeViewController *homeView = [HomeViewController new]; diff --git a/Signal/src/ViewControllers/LoadingViewController.swift b/Signal/src/ViewControllers/LoadingViewController.swift new file mode 100644 index 000000000..52d5d1281 --- /dev/null +++ b/Signal/src/ViewControllers/LoadingViewController.swift @@ -0,0 +1,102 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation +import PromiseKit + +// The initial presentation is intended to be indistinguishable from the Launch Screen. +// After a delay we present some "loading" UI so the user doesn't think the app is frozen. +@objc +public class LoadingViewController: UIViewController { + + var logoView: UIImageView! + var topLabel: UILabel! + var bottomLabel: UILabel! + + override public func loadView() { + self.view = UIView() + view.backgroundColor = UIColor.ows_materialBlue + + self.logoView = UIImageView(image: #imageLiteral(resourceName: "logoSignal")) + view.addSubview(logoView) + + logoView.autoCenterInSuperview() + logoView.autoPinToSquareAspectRatio() + logoView.autoMatch(.width, to: .width, of: view, withMultiplier: 1/3) + + self.topLabel = buildLabel() + topLabel.alpha = 0 + topLabel.font = UIFont.ows_dynamicTypeTitle2 + topLabel.text = NSLocalizedString("DATABASE_VIEW_OVERLAY_TITLE", comment: "Title shown while the app is updating its database.") + + self.bottomLabel = buildLabel() + bottomLabel.alpha = 0 + bottomLabel.font = UIFont.ows_dynamicTypeBody + bottomLabel.text = NSLocalizedString("DATABASE_VIEW_OVERLAY_SUBTITLE", comment: "Subtitle shown while the app is updating its database.") + + let labelStack = UIStackView(arrangedSubviews: [topLabel, bottomLabel]) + labelStack.axis = .vertical + labelStack.alignment = .center + labelStack.spacing = 8 + view.addSubview(labelStack) + + labelStack.autoPinEdge(.top, to: .bottom, of: logoView, withOffset: 20) + labelStack.autoPinLeadingToSuperviewMargin() + labelStack.autoPinTrailingToSuperviewMargin() + labelStack.setCompressionResistanceHigh() + labelStack.setContentHuggingHigh() + } + + var isShowingTopLabel = false + var isShowingBottomLabel = false + override public func viewDidAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + // We only show the "loading" UI if it's a slow launch. Otherwise this ViewController + // should be indistinguishable from the launch screen. + let kTopLabelThreshold: TimeInterval = 5 + DispatchQueue.main.asyncAfter(deadline: .now() + kTopLabelThreshold) { [weak self] in + guard let strongSelf = self else { + return + } + + guard !strongSelf.isShowingTopLabel else { + return + } + + strongSelf.isShowingTopLabel = true + UIView.animate(withDuration: 0.1) { + strongSelf.topLabel.alpha = 1 + } + UIView.animate(withDuration: 0.9, delay: 2, options: [.autoreverse, .repeat, .curveEaseInOut], animations: { + strongSelf.topLabel.alpha = 0.2 + }, completion: nil) + } + + let kBottomLabelThreshold: TimeInterval = 15 + DispatchQueue.main.asyncAfter(deadline: .now() + kBottomLabelThreshold) { [weak self] in + guard let strongSelf = self else { + return + } + guard !strongSelf.isShowingBottomLabel else { + return + } + + strongSelf.isShowingBottomLabel = true + UIView.animate(withDuration: 0.1) { + strongSelf.bottomLabel.alpha = 1 + } + } + } + + private func buildLabel() -> UILabel { + let label = UILabel() + + label.textColor = .white + label.numberOfLines = 0 + label.lineBreakMode = .byWordWrapping + + return label + } +} diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 596023376..4dc1145ad 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -506,7 +506,7 @@ "DATABASE_VIEW_OVERLAY_SUBTITLE" = "This can take a few minutes."; /* Title shown while the app is updating its database. */ -"DATABASE_VIEW_OVERLAY_TITLE" = "Updating Database"; +"DATABASE_VIEW_OVERLAY_TITLE" = "Optimizing Database"; /* The current day. */ "DATE_TODAY" = "Today";