From e45460698717deaa8c42027c0454a220f25352b3 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Sat, 7 Sep 2019 18:49:36 +0400 Subject: [PATCH 01/16] Fix RTL --- submodules/Display/Display/TextNode.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/submodules/Display/Display/TextNode.swift b/submodules/Display/Display/TextNode.swift index 7217a3cb4f..5a5797a1be 100644 --- a/submodules/Display/Display/TextNode.swift +++ b/submodules/Display/Display/TextNode.swift @@ -60,9 +60,10 @@ private func displayLineFrame(frame: CGRect, isRTL: Bool, boundingRect: CGRect, return frame } var lineFrame = frame + let intersectionFrame = lineFrame.offsetBy(dx: 0.0, dy: -lineFrame.height) + if isRTL { lineFrame.origin.x = max(0.0, floor(boundingRect.width - lineFrame.size.width)) - let intersectionFrame = lineFrame.offsetBy(dx: 0.0, dy: -lineFrame.height / 4.5) if let topRight = cutout?.topRight { let topRightRect = CGRect(origin: CGPoint(x: boundingRect.width - topRight.width, y: 0.0), size: topRight) if intersectionFrame.intersects(topRightRect) { From 6893a77aa7420ffb83a55dbe2dce79b1ef0aa4f2 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Sat, 7 Sep 2019 18:49:49 +0400 Subject: [PATCH 02/16] Don't crash in production --- .../MediaPlayer/Sources/FFMpegMediaFrameSourceContext.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/MediaPlayer/Sources/FFMpegMediaFrameSourceContext.swift b/submodules/MediaPlayer/Sources/FFMpegMediaFrameSourceContext.swift index 51743bc2db..5040178a67 100644 --- a/submodules/MediaPlayer/Sources/FFMpegMediaFrameSourceContext.swift +++ b/submodules/MediaPlayer/Sources/FFMpegMediaFrameSourceContext.swift @@ -75,7 +75,7 @@ private func readPacketCallback(userData: UnsafeMutableRawPointer?, buffer: Unsa let readCount = min(resourceSize - context.readingOffset, Int(bufferSize)) let requestRange: Range = context.readingOffset ..< (context.readingOffset + readCount) - precondition(readCount < 3 * 1024 * 1024) + assert(readCount < 16 * 1024 * 1024) if let maximumFetchSize = context.maximumFetchSize { context.touchedRanges.insert(integersIn: requestRange) From cb1ca575debad86965b26bea04eb275eb29cabfe Mon Sep 17 00:00:00 2001 From: Peter <> Date: Sat, 7 Sep 2019 18:51:12 +0400 Subject: [PATCH 03/16] Cleanup --- submodules/SettingsUI/Sources/OpenSettings.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/SettingsUI/Sources/OpenSettings.swift b/submodules/SettingsUI/Sources/OpenSettings.swift index dcb8580e6f..833459e1c2 100644 --- a/submodules/SettingsUI/Sources/OpenSettings.swift +++ b/submodules/SettingsUI/Sources/OpenSettings.swift @@ -50,7 +50,7 @@ func openEditSettings(context: AccountContext, accountsAndPeers: Signal<((Accoun } openEditingDisposable.set((signal |> deliverOnMainQueue).start(next: { peer, cachedData, canAddAccounts in - pushController(editSettingsController(context: context, currentName: .personName(firstName: peer.firstName ?? "", lastName: peer.lastName ?? " "), currentBioText: cachedData.about ?? "", accountManager: context.sharedContext.accountManager, canAddAccounts: canAddAccounts, focusOnItemTag: focusOnItemTag)) + pushController(editSettingsController(context: context, currentName: .personName(firstName: peer.firstName ?? "", lastName: peer.lastName ?? ""), currentBioText: cachedData.about ?? "", accountManager: context.sharedContext.accountManager, canAddAccounts: canAddAccounts, focusOnItemTag: focusOnItemTag)) })) return openEditingDisposable } From 48b0250cd0b6f34bdef75bd9f4419b74f87d710d Mon Sep 17 00:00:00 2001 From: Peter <> Date: Sat, 7 Sep 2019 18:49:36 +0400 Subject: [PATCH 04/16] Fix RTL --- submodules/Display/Display/TextNode.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/submodules/Display/Display/TextNode.swift b/submodules/Display/Display/TextNode.swift index 7217a3cb4f..5a5797a1be 100644 --- a/submodules/Display/Display/TextNode.swift +++ b/submodules/Display/Display/TextNode.swift @@ -60,9 +60,10 @@ private func displayLineFrame(frame: CGRect, isRTL: Bool, boundingRect: CGRect, return frame } var lineFrame = frame + let intersectionFrame = lineFrame.offsetBy(dx: 0.0, dy: -lineFrame.height) + if isRTL { lineFrame.origin.x = max(0.0, floor(boundingRect.width - lineFrame.size.width)) - let intersectionFrame = lineFrame.offsetBy(dx: 0.0, dy: -lineFrame.height / 4.5) if let topRight = cutout?.topRight { let topRightRect = CGRect(origin: CGPoint(x: boundingRect.width - topRight.width, y: 0.0), size: topRight) if intersectionFrame.intersects(topRightRect) { From 067543f6f48e0f714a8a03db10069c437be10997 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Sat, 7 Sep 2019 18:49:49 +0400 Subject: [PATCH 05/16] Don't crash in production --- .../MediaPlayer/Sources/FFMpegMediaFrameSourceContext.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/MediaPlayer/Sources/FFMpegMediaFrameSourceContext.swift b/submodules/MediaPlayer/Sources/FFMpegMediaFrameSourceContext.swift index 51743bc2db..5040178a67 100644 --- a/submodules/MediaPlayer/Sources/FFMpegMediaFrameSourceContext.swift +++ b/submodules/MediaPlayer/Sources/FFMpegMediaFrameSourceContext.swift @@ -75,7 +75,7 @@ private func readPacketCallback(userData: UnsafeMutableRawPointer?, buffer: Unsa let readCount = min(resourceSize - context.readingOffset, Int(bufferSize)) let requestRange: Range = context.readingOffset ..< (context.readingOffset + readCount) - precondition(readCount < 3 * 1024 * 1024) + assert(readCount < 16 * 1024 * 1024) if let maximumFetchSize = context.maximumFetchSize { context.touchedRanges.insert(integersIn: requestRange) From 41c0d81c2c9c5345e0482fa16b3ea16b3b60b96f Mon Sep 17 00:00:00 2001 From: Peter <> Date: Sat, 7 Sep 2019 19:16:54 +0400 Subject: [PATCH 06/16] Fix missing method --- submodules/OpusBinding/Sources/OggOpusReader.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/OpusBinding/Sources/OggOpusReader.m b/submodules/OpusBinding/Sources/OggOpusReader.m index bf569da19f..5e8bd5af53 100644 --- a/submodules/OpusBinding/Sources/OggOpusReader.m +++ b/submodules/OpusBinding/Sources/OggOpusReader.m @@ -10,7 +10,7 @@ @implementation OggOpusReader -- (instancetype _Nullable)init:(NSString *)path { +- (instancetype _Nullable)initWithPath:(NSString *)path { self = [super init]; if (self != nil) { int error = OPUS_OK; From 228b8ee9977aa7ea690b994ab2dc770b23e96958 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Sat, 7 Sep 2019 19:17:13 +0400 Subject: [PATCH 07/16] Only apply offset when it's an iPad --- .../GlobalOverlayPresentationContext.swift | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/submodules/Display/Display/GlobalOverlayPresentationContext.swift b/submodules/Display/Display/GlobalOverlayPresentationContext.swift index 7c06bc8a0e..b0b89b2d24 100644 --- a/submodules/Display/Display/GlobalOverlayPresentationContext.swift +++ b/submodules/Display/Display/GlobalOverlayPresentationContext.swift @@ -119,7 +119,11 @@ final class GlobalOverlayPresentationContext { } } if let presentationView = self.currentPresentationView(underStatusBar: underStatusBar), let initialLayout = self.layout { - controller.view.frame = CGRect(origin: CGPoint(x: presentationView.bounds.width - initialLayout.size.width, y: 0.0), size: initialLayout.size) + if initialLayout.metrics.widthClass == .regular { + controller.view.frame = CGRect(origin: CGPoint(x: presentationView.bounds.width - initialLayout.size.width, y: 0.0), size: initialLayout.size) + } else { + controller.view.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: initialLayout.size) + } controller.containerLayoutUpdated(initialLayout, transition: .immediate) self.presentationDisposables.add(controllerReady.start(next: { [weak self] _ in @@ -137,7 +141,11 @@ final class GlobalOverlayPresentationContext { }, rootController: nil) (controller as? UIViewController)?.setIgnoreAppearanceMethodInvocations(true) if layout != initialLayout { - controller.view.frame = CGRect(origin: CGPoint(x: view.bounds.width - layout.size.width, y: 0.0), size: layout.size) + if layout.metrics.widthClass == .regular { + controller.view.frame = CGRect(origin: CGPoint(x: view.bounds.width - layout.size.width, y: 0.0), size: layout.size) + } else { + controller.view.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: layout.size) + } view.addSubview(controller.view) controller.containerLayoutUpdated(layout, transition: .immediate) } else { @@ -204,7 +212,11 @@ final class GlobalOverlayPresentationContext { } view.addSubview(controller.view) if !justMove { - controller.view.frame = CGRect(origin: CGPoint(x: view.bounds.width - layout.size.width, y: 0.0), size: layout.size) + if layout.metrics.widthClass == .regular { + controller.view.frame = CGRect(origin: CGPoint(x: view.bounds.width - layout.size.width, y: 0.0), size: layout.size) + } else { + controller.view.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: layout.size) + } controller.containerLayoutUpdated(layout, transition: .immediate) controller.viewDidAppear(false) } From 3a93972bbcde47323a2adb5d06b78f7c11d8c5a7 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Sat, 7 Sep 2019 19:18:06 +0400 Subject: [PATCH 08/16] Bump version number --- NotificationContent/Info.plist | 2 +- NotificationService/Info.plist | 2 +- Share/Info.plist | 2 +- SiriIntents/Info.plist | 2 +- Telegram-iOS/Info.plist | 2 +- Watch/App/Info.plist | 2 +- Watch/Extension/Info.plist | 2 +- Widget/Info.plist | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/NotificationContent/Info.plist b/NotificationContent/Info.plist index 63a5db75f2..f982785438 100644 --- a/NotificationContent/Info.plist +++ b/NotificationContent/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 5.11 + 5.12 CFBundleVersion ${BUILD_NUMBER} NSExtension diff --git a/NotificationService/Info.plist b/NotificationService/Info.plist index 14cecfaf70..b744f4746d 100644 --- a/NotificationService/Info.plist +++ b/NotificationService/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 5.11 + 5.12 CFBundleVersion ${BUILD_NUMBER} NSExtension diff --git a/Share/Info.plist b/Share/Info.plist index 9ce95e61f2..21a471347b 100644 --- a/Share/Info.plist +++ b/Share/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 5.11 + 5.12 CFBundleVersion ${BUILD_NUMBER} NSExtension diff --git a/SiriIntents/Info.plist b/SiriIntents/Info.plist index ff9f035c1e..d6b99942eb 100644 --- a/SiriIntents/Info.plist +++ b/SiriIntents/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 5.11 + 5.12 CFBundleVersion ${BUILD_NUMBER} NSExtension diff --git a/Telegram-iOS/Info.plist b/Telegram-iOS/Info.plist index 0c3c9d00ae..501fd34c18 100644 --- a/Telegram-iOS/Info.plist +++ b/Telegram-iOS/Info.plist @@ -185,7 +185,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 5.11 + 5.12 CFBundleSignature ???? CFBundleURLTypes diff --git a/Watch/App/Info.plist b/Watch/App/Info.plist index dbf2e283c9..651ef9ce4a 100644 --- a/Watch/App/Info.plist +++ b/Watch/App/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 5.11 + 5.12 CFBundleVersion ${BUILD_NUMBER} UISupportedInterfaceOrientations diff --git a/Watch/Extension/Info.plist b/Watch/Extension/Info.plist index 08c256367e..963171e6e6 100644 --- a/Watch/Extension/Info.plist +++ b/Watch/Extension/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 5.11 + 5.12 CFBundleVersion ${BUILD_NUMBER} NSExtension diff --git a/Widget/Info.plist b/Widget/Info.plist index 4c71d0c0a8..103bb1a25a 100644 --- a/Widget/Info.plist +++ b/Widget/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 5.11 + 5.12 CFBundleVersion ${BUILD_NUMBER} NSExtension From e6bb62115a955558216809dbe35030d042a03a05 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Sat, 7 Sep 2019 19:50:25 +0400 Subject: [PATCH 09/16] Fix animated stickers in secret chats --- submodules/TelegramUI/TelegramUI/ChatMessageItem.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageItem.swift b/submodules/TelegramUI/TelegramUI/ChatMessageItem.swift index 0f5cd71c96..5d39e2bc3a 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageItem.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageItem.swift @@ -370,7 +370,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible { loop: for media in self.message.media { if let telegramFile = media as? TelegramMediaFile { - if telegramFile.isAnimatedSticker, !telegramFile.previewRepresentations.isEmpty, let size = telegramFile.size, size > 0 && size <= 128 * 1024 { + if telegramFile.isAnimatedSticker, (self.message.id.peerId.namespace == Namespaces.Peer.SecretChat || !telegramFile.previewRepresentations.isEmpty), let size = telegramFile.size, size > 0 && size <= 128 * 1024 { if self.message.id.peerId.namespace == Namespaces.Peer.SecretChat { if telegramFile.fileId.namespace == Namespaces.Media.CloudFile { viewClassName = ChatMessageAnimatedStickerItemNode.self From e27556c33299d79121b04cdddfd618516b4454f9 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Mon, 9 Sep 2019 01:05:40 +0400 Subject: [PATCH 10/16] Fix alpha updates --- submodules/HockeySDK-iOS/BUCK | 4 ++- .../Classes/BITAppVersionMetaInfo.m | 2 +- .../Classes/BITStoreUpdateManager.m | 8 ++--- .../HockeySDK-iOS/Classes/BITUpdateManager.m | 34 +++++++++---------- .../Classes/BITUpdateViewController.m | 4 +-- .../TelegramUI/TelegramUI/AppDelegate.swift | 2 -- 6 files changed, 27 insertions(+), 27 deletions(-) diff --git a/submodules/HockeySDK-iOS/BUCK b/submodules/HockeySDK-iOS/BUCK index 46eb139de8..582118aa4b 100644 --- a/submodules/HockeySDK-iOS/BUCK +++ b/submodules/HockeySDK-iOS/BUCK @@ -32,6 +32,8 @@ static_library( "Classes/BITCrashDetails.h", "Classes/BITCrashMetaData.h", + "Classes/BITAuthenticator.h", + "Classes/BITUpdateManager.h", "Classes/BITUpdateManagerDelegate.h", "Classes/BITUpdateViewController.h", @@ -45,7 +47,7 @@ static_library( "-DHOCKEYSDK_FEATURE_CRASH_REPORTER=1", "-DHOCKEYSDK_FEATURE_UPDATES=1", "-DHOCKEYSDK_FEATURE_FEEDBACK=0", - "-DHOCKEYSDK_FEATURE_AUTHENTICATOR=0", + "-DHOCKEYSDK_FEATURE_AUTHENTICATOR=1", "-DHOCKEYSDK_FEATURE_METRICS=0", ], deps = [ diff --git a/submodules/HockeySDK-iOS/Classes/BITAppVersionMetaInfo.m b/submodules/HockeySDK-iOS/Classes/BITAppVersionMetaInfo.m index 3f4906e020..9320ed5092 100644 --- a/submodules/HockeySDK-iOS/Classes/BITAppVersionMetaInfo.m +++ b/submodules/HockeySDK-iOS/Classes/BITAppVersionMetaInfo.m @@ -166,7 +166,7 @@ - (NSString *)versionString { NSString *shortString = ([self.shortVersion respondsToSelector:@selector(length)] && [self.shortVersion length]) ? [NSString stringWithFormat:@"%@", self.shortVersion] : @""; NSString *versionString = [shortString length] ? [NSString stringWithFormat:@" (%@)", self.version] : self.version; - return [NSString stringWithFormat:@"%@ %@%@", BITHockeyLocalizedString(@"UpdateVersion"), shortString, versionString]; + return [NSString stringWithFormat:@"%@ %@%@", BITHockeyLocalizedString(@"Version"), shortString, versionString]; } - (NSString *)dateString { diff --git a/submodules/HockeySDK-iOS/Classes/BITStoreUpdateManager.m b/submodules/HockeySDK-iOS/Classes/BITStoreUpdateManager.m index 0626805d71..52b9a4e885 100644 --- a/submodules/HockeySDK-iOS/Classes/BITStoreUpdateManager.m +++ b/submodules/HockeySDK-iOS/Classes/BITStoreUpdateManager.m @@ -435,27 +435,27 @@ - (void)showUpdateAlert { dispatch_async(dispatch_get_main_queue(), ^{ if (!self.updateAlertShowing) { - NSString *versionString = [NSString stringWithFormat:@"%@ %@", BITHockeyLocalizedString(@"UpdateVersion"), self.latestStoreVersion]; + NSString *versionString = [NSString stringWithFormat:@"%@ %@", BITHockeyLocalizedString(@"Version"), self.latestStoreVersion]; __weak typeof(self) weakSelf = self; UIAlertController *alertController = [UIAlertController alertControllerWithTitle:BITHockeyLocalizedString(@"UpdateAvailable") message:[NSString stringWithFormat:BITHockeyLocalizedString(@"UpdateAlertTextWithAppVersion"), versionString] preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *ignoreAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"UpdateIgnore") + UIAlertAction *ignoreAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"Ignore") style:UIAlertActionStyleCancel handler:^(UIAlertAction __unused *action) { typeof(self) strongSelf = weakSelf; [strongSelf ignoreAction]; }]; [alertController addAction:ignoreAction]; - UIAlertAction *remindAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"UpdateRemindMe") + UIAlertAction *remindAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"Remind Me") style:UIAlertActionStyleDefault handler:^(UIAlertAction __unused *action) { typeof(self) strongSelf = weakSelf; [strongSelf remindAction]; }]; [alertController addAction:remindAction]; - UIAlertAction *showAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"UpdateShow") + UIAlertAction *showAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"Show") style:UIAlertActionStyleDefault handler:^(UIAlertAction __unused *action) { typeof(self) strongSelf = weakSelf; diff --git a/submodules/HockeySDK-iOS/Classes/BITUpdateManager.m b/submodules/HockeySDK-iOS/Classes/BITUpdateManager.m index 7dcb7b7645..5516eb2173 100644 --- a/submodules/HockeySDK-iOS/Classes/BITUpdateManager.m +++ b/submodules/HockeySDK-iOS/Classes/BITUpdateManager.m @@ -88,10 +88,10 @@ typedef NS_ENUM(NSInteger, BITUpdateAlertViewTag) { // only show error if we enable that if (self.showFeedback) { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:BITHockeyLocalizedString(@"UpdateError") + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:BITHockeyLocalizedString(@"An Error Occurred") message:[error localizedDescription] preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *okAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"HockeyOK") + UIAlertAction *okAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"OK") style:UIAlertActionStyleDefault handler:^(UIAlertAction __unused *action) {}]; [alertController addAction:okAction]; @@ -223,9 +223,9 @@ typedef NS_ENUM(NSInteger, BITUpdateAlertViewTag) { } if (shouldShowDefaultAlert) { - NSString *appName = bit_appName(BITHockeyLocalizedString(@"HockeyAppNamePlaceholder")); + NSString *appName = bit_appName(BITHockeyLocalizedString(@"Telegram")); if (!self.blockingScreenMessage) - self.blockingScreenMessage = [NSString stringWithFormat:BITHockeyLocalizedString(@"UpdateExpired"), appName]; + self.blockingScreenMessage = [NSString stringWithFormat:BITHockeyLocalizedString(@"Update expired"), appName]; [self showBlockingScreen:self.blockingScreenMessage image:@"authorize_denied.png"]; if ([strongDelegate respondsToSelector:@selector(didDisplayExpiryAlertForUpdateManager:)]) { @@ -530,14 +530,14 @@ typedef NS_ENUM(NSInteger, BITUpdateAlertViewTag) { } if (!self.updateAlertShowing) { - NSString *title = BITHockeyLocalizedString(@"UpdateAvailable"); + NSString *title = BITHockeyLocalizedString(@"Update Available"); NSString *message = [NSString stringWithFormat:BITHockeyLocalizedString(@"UpdateAlertMandatoryTextWithAppVersion"), [self.newestAppVersion nameAndVersionString]]; if ([self hasNewerMandatoryVersion]) { __weak typeof(self) weakSelf = self; UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *showAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"UpdateShow") + UIAlertAction *showAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"Show") style:UIAlertActionStyleDefault handler:^(UIAlertAction __unused *action) { typeof(self) strongSelf = weakSelf; @@ -548,7 +548,7 @@ typedef NS_ENUM(NSInteger, BITUpdateAlertViewTag) { [strongSelf showUpdateView]; }]; [alertController addAction:showAction]; - UIAlertAction *installAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"UpdateInstall") + UIAlertAction *installAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"Install") style:UIAlertActionStyleDefault handler:^(UIAlertAction __unused *action) { typeof(self) strongSelf = weakSelf; @@ -559,13 +559,13 @@ typedef NS_ENUM(NSInteger, BITUpdateAlertViewTag) { [self showAlertController:alertController]; self.updateAlertShowing = YES; } else { - message = [NSString stringWithFormat:BITHockeyLocalizedString(@"UpdateAlertTextWithAppVersion"), [self.newestAppVersion nameAndVersionString]]; + message = [NSString stringWithFormat:BITHockeyLocalizedString(@"An update is available"), [self.newestAppVersion nameAndVersionString]]; __weak typeof(self) weakSelf = self; UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *ignoreAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"UpdateIgnore") + UIAlertAction *ignoreAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"Ignore") style:UIAlertActionStyleCancel handler:^(UIAlertAction __unused *action) { typeof(self) strongSelf = weakSelf; @@ -575,7 +575,7 @@ typedef NS_ENUM(NSInteger, BITUpdateAlertViewTag) { } }]; [alertController addAction:ignoreAction]; - UIAlertAction *showAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"UpdateShow") + UIAlertAction *showAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"Show") style:UIAlertActionStyleDefault handler:^(UIAlertAction __unused *action) { typeof(self) strongSelf = weakSelf; @@ -587,7 +587,7 @@ typedef NS_ENUM(NSInteger, BITUpdateAlertViewTag) { }]; [alertController addAction:showAction]; if (self.isShowingDirectInstallOption) { - UIAlertAction *installAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"UpdateInstall") + UIAlertAction *installAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"Install") style:UIAlertActionStyleDefault handler:^(UIAlertAction __unused *action) { typeof(self) strongSelf = weakSelf; @@ -631,7 +631,7 @@ typedef NS_ENUM(NSInteger, BITUpdateAlertViewTag) { if (!self.disableUpdateCheckOptionWhenExpired) { UIButton *checkForUpdateButton = [UIButton buttonWithType:kBITButtonTypeSystem]; checkForUpdateButton.frame = CGRectMake((frame.size.width - 140) / (CGFloat)2.0, frame.size.height - 100, 140, 25); - [checkForUpdateButton setTitle:BITHockeyLocalizedString(@"UpdateButtonCheck") forState:UIControlStateNormal]; + [checkForUpdateButton setTitle:BITHockeyLocalizedString(@"Check") forState:UIControlStateNormal]; [checkForUpdateButton addTarget:self action:@selector(checkForUpdateForExpiredVersion) forControlEvents:UIControlEventTouchUpInside]; @@ -674,7 +674,7 @@ typedef NS_ENUM(NSInteger, BITUpdateAlertViewTag) { UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:message preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *okAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"HockeyOK") + UIAlertAction *okAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"OK") style:UIAlertActionStyleDefault handler:^(UIAlertAction __unused *action) { typeof(self) strongSelf = weakSelf; @@ -682,7 +682,7 @@ typedef NS_ENUM(NSInteger, BITUpdateAlertViewTag) { }]; [alertController addAction:okAction]; if (!self.disableUpdateCheckOptionWhenExpired && [message isEqualToString:self.blockingScreenMessage]) { - UIAlertAction *checkAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"UpdateButtonCheck") + UIAlertAction *checkAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"Check") style:UIAlertActionStyleDefault handler:^(UIAlertAction __unused *action) { typeof(self) strongSelf = weakSelf; @@ -814,10 +814,10 @@ typedef NS_ENUM(NSInteger, BITUpdateAlertViewTag) { #if TARGET_OS_SIMULATOR - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:BITHockeyLocalizedString(@"UpdateWarning") + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:BITHockeyLocalizedString(@"Warning") message:BITHockeyLocalizedString(@"UpdateSimulatorMessage") preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *okAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"HockeyOK") + UIAlertAction *okAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"OK") style:UIAlertActionStyleDefault handler:^(UIAlertAction __unused *action) {}]; [alertController addAction:okAction]; @@ -966,7 +966,7 @@ typedef NS_ENUM(NSInteger, BITUpdateAlertViewTag) { NSString *shortVersionString = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; shortVersionString = shortVersionString ? [NSString stringWithFormat:@"%@ ", shortVersionString] : @""; versionString = [shortVersionString length] ? [NSString stringWithFormat:@"(%@)", versionString] : versionString; - NSString *currentVersionString = [NSString stringWithFormat:@"%@ %@ %@%@", self.newestAppVersion.name, BITHockeyLocalizedString(@"UpdateVersion"), shortVersionString, versionString]; + NSString *currentVersionString = [NSString stringWithFormat:@"%@ %@ %@%@", self.newestAppVersion.name, BITHockeyLocalizedString(@"Version"), shortVersionString, versionString]; NSString *alertMsg = [NSString stringWithFormat:BITHockeyLocalizedString(@"UpdateNoUpdateAvailableMessage"), currentVersionString]; __weak typeof(self) weakSelf = self; UIAlertController *alertController = [UIAlertController alertControllerWithTitle:BITHockeyLocalizedString(@"UpdateNoUpdateAvailableTitle") diff --git a/submodules/HockeySDK-iOS/Classes/BITUpdateViewController.m b/submodules/HockeySDK-iOS/Classes/BITUpdateViewController.m index ee9ab55d21..f480a7a15b 100644 --- a/submodules/HockeySDK-iOS/Classes/BITUpdateViewController.m +++ b/submodules/HockeySDK-iOS/Classes/BITUpdateViewController.m @@ -509,10 +509,10 @@ [self.appStoreButton setButtonData:[BITStoreButtonData dataWithLabel:BITHockeyLocalizedString(@"UpdateButtonSearching") enabled:NO] animated:animated]; break; case AppStoreButtonStateUpdate: - [self.appStoreButton setButtonData:[BITStoreButtonData dataWithLabel:BITHockeyLocalizedString(@"UpdateButtonUpdate") enabled:YES] animated:animated]; + [self.appStoreButton setButtonData:[BITStoreButtonData dataWithLabel:BITHockeyLocalizedString(@"Update") enabled:YES] animated:animated]; break; case AppStoreButtonStateInstalling: - [self.appStoreButton setButtonData:[BITStoreButtonData dataWithLabel:BITHockeyLocalizedString(@"UpdateButtonInstalling") enabled:NO] animated:animated]; + [self.appStoreButton setButtonData:[BITStoreButtonData dataWithLabel:BITHockeyLocalizedString(@"Installing") enabled:NO] animated:animated]; break; default: break; diff --git a/submodules/TelegramUI/TelegramUI/AppDelegate.swift b/submodules/TelegramUI/TelegramUI/AppDelegate.swift index 35e6f58f71..c86dc53093 100644 --- a/submodules/TelegramUI/TelegramUI/AppDelegate.swift +++ b/submodules/TelegramUI/TelegramUI/AppDelegate.swift @@ -1185,9 +1185,7 @@ final class SharedApplicationContext { BITHockeyManager.shared().configure(withIdentifier: hockeyAppId, delegate: self) BITHockeyManager.shared().crashManager.crashManagerStatus = .alwaysAsk BITHockeyManager.shared().start() - #if !BUCK BITHockeyManager.shared().authenticator.authenticateInstallation() - #endif } NotificationCenter.default.addObserver(forName: UIWindow.didBecomeHiddenNotification, object: nil, queue: nil, using: { notification in From d10f5c493bb8afd1c8991a99bb13d7005b758efb Mon Sep 17 00:00:00 2001 From: Peter <> Date: Mon, 9 Sep 2019 01:11:53 +0400 Subject: [PATCH 11/16] Temporary fix scrolling on non-3dtouch-capable devices --- submodules/ContextUI/Sources/ContextGesture.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/ContextUI/Sources/ContextGesture.swift b/submodules/ContextUI/Sources/ContextGesture.swift index 138ac07c79..b99981c1ed 100644 --- a/submodules/ContextUI/Sources/ContextGesture.swift +++ b/submodules/ContextUI/Sources/ContextGesture.swift @@ -92,7 +92,7 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg if let touch = touches.first { if #available(iOS 9.0, *) { - let maxForce: CGFloat = min(3.0, touch.maximumPossibleForce) + let maxForce: CGFloat = max(2.5, min(3.0, touch.maximumPossibleForce)) let progress = touch.force / maxForce self.currentProgress = progress if self.isValidated { From bc908d7abb06103f755153e1e10fc5b0caf825af Mon Sep 17 00:00:00 2001 From: Peter <> Date: Mon, 9 Sep 2019 21:37:58 +0400 Subject: [PATCH 12/16] Cleanup --- submodules/Display/BUCK | 4 ++-- .../Display/Display/NavigationController.swift | 17 ----------------- 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/submodules/Display/BUCK b/submodules/Display/BUCK index 5a427b027d..bdb1a1dbb6 100644 --- a/submodules/Display/BUCK +++ b/submodules/Display/BUCK @@ -3,8 +3,8 @@ load("//Config:buck_rule_macros.bzl", "framework") framework( name = "Display", srcs = glob([ - "Display/*.swift", - "Display/*.m", + "Display/**/*.swift", + "Display/**/*.m", ]), headers = glob([ "Display/*.h", diff --git a/submodules/Display/Display/NavigationController.swift b/submodules/Display/Display/NavigationController.swift index 11799d6f45..7850239b47 100644 --- a/submodules/Display/Display/NavigationController.swift +++ b/submodules/Display/Display/NavigationController.swift @@ -51,17 +51,6 @@ private final class NavigationControllerView: UITracingLayerView { var masterDetailsBlackout: ASDisplayNode? var topControllerNode: ASDisplayNode? - /*override var accessibilityElements: [Any]? { - get { - var accessibilityElements: [Any] = [] - if let topControllerNode = self.topControllerNode { - addAccessibilityChildren(of: topControllerNode, container: self, to: &accessibilityElements) - } - return accessibilityElements - } set(value) { - } - }*/ - override init(frame: CGRect) { self.containerView = NavigationControllerContainerView() self.separatorView = UIView() @@ -235,12 +224,6 @@ open class NavigationController: UINavigationController, ContainableController, self.controllerView.separatorView.backgroundColor = theme.navigationBar.separatorColor self.controllerView.navigationBackgroundView?.backgroundColor = theme.navigationBar.backgroundColor self.controllerView.navigationSeparatorView?.backgroundColor = theme.navigationBar.separatorColor -// if let emptyDetailView = self.controllerView.emptyDetailView { -// emptyDetailView.image = theme.emptyDetailIcon -// if let image = theme.emptyDetailIcon { -// emptyDetailView.frame = CGRect(origin: CGPoint(x: floor(emptyDetailView.center.x - image.size.width / 2.0), y: floor(emptyDetailView.center.y - image.size.height / 2.0)), size: image.size) -// } -// } } } From 1e94bfff260d518a424fc58dd431a6f954649483 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 10 Sep 2019 17:21:50 +0400 Subject: [PATCH 13/16] Use InfoPlist.strings --- BUCK | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/BUCK b/BUCK index 555f98f385..660f12f65b 100644 --- a/BUCK +++ b/BUCK @@ -45,6 +45,7 @@ resource_dependencies = [ "//submodules/TelegramUI:TelegramUIResources", "//:AppResources", "//:AppStringResources", + "//:InfoPlistStringResources", "//:AppIntentVocabularyResources", "//:Icons", "//:AdditionalIcons", @@ -80,6 +81,15 @@ apple_resource( visibility = ["PUBLIC"], ) +apple_resource( + name = "InfoPlistStringResources", + files = [], + variants = glob([ + "Telegram-iOS/*.lproj/InfoPlist.strings", + ]), + visibility = ["PUBLIC"], +) + apple_asset_catalog( name = "Icons", dirs = [ From decdb54c5ad90fd51513099a8e86132d256b9443 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 10 Sep 2019 17:22:18 +0400 Subject: [PATCH 14/16] Allow separate master and detail navigation --- .../ChatListUI/Sources/ChatContextMenus.swift | 2 +- .../Sources/ChatListController.swift | 3 + .../Sources/ContactsController.swift | 4 +- .../Sources/ContextActionsContainerNode.swift | 31 +- .../Sources/ContextContentSourceNode.swift | 4 +- .../ContextUI/Sources/ContextController.swift | 226 +++++-- .../Sources/ContextControllerSourceNode.swift | 8 +- .../ContextUI/Sources/ContextGesture.swift | 38 +- .../Display/ContainableController.swift | 1 + .../Display/Display/DisplayLinkAnimator.swift | 7 +- .../Display/NavigationController.swift | 637 ++++++++++++++---- .../Display/Display/ViewController.swift | 26 +- .../GalleryUI/Sources/GalleryController.swift | 10 +- .../TelegramUI/TelegramRootController.swift | 3 +- 14 files changed, 789 insertions(+), 211 deletions(-) diff --git a/submodules/ChatListUI/Sources/ChatContextMenus.swift b/submodules/ChatListUI/Sources/ChatContextMenus.swift index 50be5f7c80..6e6860de4f 100644 --- a/submodules/ChatListUI/Sources/ChatContextMenus.swift +++ b/submodules/ChatListUI/Sources/ChatContextMenus.swift @@ -114,7 +114,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, source: ChatC if let (group, index) = groupAndIndex { if !isSavedMessages { let isArchived = group == Namespaces.PeerGroup.archive - items.append(.action(ContextMenuActionItem(text: isArchived ? strings.ChatList_Context_Archive : strings.ChatList_Context_Unarchive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isArchived ? "Chat/Context Menu/Unarchive" : "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor) }, action: { _, f in + items.append(.action(ContextMenuActionItem(text: isArchived ? strings.ChatList_Context_Unarchive : strings.ChatList_Context_Archive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isArchived ? "Chat/Context Menu/Unarchive" : "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor) }, action: { _, f in if isArchived { let _ = (context.account.postbox.transaction { transaction -> Void in updatePeerGroupIdInteractively(transaction: transaction, peerId: peerId, groupId: .root) diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index ce97d0e0b7..6eed3e288f 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -509,6 +509,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, if let strongSelf = self { if let navigationController = strongSelf.navigationController as? NavigationController { let chatListController = ChatListControllerImpl(context: strongSelf.context, groupId: groupId, controlsHistoryPreload: false, enableDebugActions: false) + chatListController.navigationPresentation = .master navigationController.pushViewController(chatListController) strongSelf.chatListDisplayNode.chatListNode.clearHighlightAnimated(true) } @@ -664,6 +665,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, switch item.content { case let .groupReference(groupReference): let chatListController = ChatListControllerImpl(context: strongSelf.context, groupId: groupReference.groupId, controlsHistoryPreload: false, enableDebugActions: false) + chatListController.navigationPresentation = .master let contextController = ContextController(account: strongSelf.context.account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node)), items: archiveContextMenuItems(context: strongSelf.context, groupId: groupReference.groupId, chatListController: strongSelf), reactionItems: [], gesture: gesture) strongSelf.presentInGlobalOverlay(contextController) case let .peer(peer): @@ -1094,6 +1096,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, } case let .groupReference(groupId, _, _, _, _): let chatListController = ChatListControllerImpl(context: self.context, groupId: groupId, controlsHistoryPreload: false, enableDebugActions: false) + chatListController.navigationPresentation = .master chatListController.containerLayoutUpdated(ContainerViewLayout(size: contentSize, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: .immediate) return (chatListController, sourceRect) } diff --git a/submodules/ContactListUI/Sources/ContactsController.swift b/submodules/ContactListUI/Sources/ContactsController.swift index 17c4a536a9..1ef6b4a377 100644 --- a/submodules/ContactListUI/Sources/ContactsController.swift +++ b/submodules/ContactListUI/Sources/ContactsController.swift @@ -281,7 +281,8 @@ public class ContactsController: ViewController { } let presentPeersNearby = { let controller = strongSelf.context.sharedContext.makePeersNearbyController(context: strongSelf.context) - (strongSelf.navigationController as? NavigationController)?.replaceAllButRootController(controller, animated: true, completion: { [weak self] in + controller.navigationPresentation = .master + (strongSelf.navigationController as? NavigationController)?.pushViewController(controller, animated: true, completion: { [weak self] in if let strongSelf = self { strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true) } @@ -294,6 +295,7 @@ public class ContactsController: ViewController { default: let controller = PermissionController(context: strongSelf.context, splashScreen: false) controller.setState(.permission(.nearbyLocation(status: PermissionRequestStatus(accessType: status))), animated: false) + controller.navigationPresentation = .master controller.proceed = { result in if result { presentPeersNearby() diff --git a/submodules/ContextUI/Sources/ContextActionsContainerNode.swift b/submodules/ContextUI/Sources/ContextActionsContainerNode.swift index 4aecca23d1..ad251153c8 100644 --- a/submodules/ContextUI/Sources/ContextActionsContainerNode.swift +++ b/submodules/ContextUI/Sources/ContextActionsContainerNode.swift @@ -10,6 +10,7 @@ private enum ContextItemNode { } final class ContextActionsContainerNode: ASDisplayNode { + private var effectView: UIVisualEffectView? private var itemNodes: [ContextItemNode] init(theme: PresentationTheme, items: [ContextMenuItem], getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void) { @@ -51,8 +52,28 @@ final class ContextActionsContainerNode: ASDisplayNode { }) } - func updateLayout(constrainedWidth: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { - let minActionsWidth = min(constrainedWidth, max(250.0, floor(constrainedWidth / 3.0))) + func updateLayout(widthClass: ContainerViewLayoutSizeClass, constrainedWidth: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { + var minActionsWidth: CGFloat = 250.0 + switch widthClass { + case .compact: + minActionsWidth = max(minActionsWidth, floor(constrainedWidth / 3.0)) + if let effectView = self.effectView { + self.effectView = nil + effectView.removeFromSuperview() + } + case .regular: + if self.effectView == nil { + let effectView: UIVisualEffectView + if #available(iOS 10.0, *) { + effectView = UIVisualEffectView(effect: UIBlurEffect(style: .regular)) + } else { + effectView = UIVisualEffectView(effect: UIBlurEffect(style: .light)) + } + self.effectView = effectView + self.view.insertSubview(effectView, at: 0) + } + } + minActionsWidth = min(minActionsWidth, constrainedWidth) let separatorHeight: CGFloat = 8.0 var maxWidth: CGFloat = 0.0 @@ -111,7 +132,11 @@ final class ContextActionsContainerNode: ASDisplayNode { } } - return CGSize(width: maxWidth, height: verticalOffset) + let size = CGSize(width: maxWidth, height: verticalOffset) + if let effectView = self.effectView { + transition.updateFrame(view: effectView, frame: CGRect(origin: CGPoint(), size: size)) + } + return size } func updateTheme(theme: PresentationTheme) { diff --git a/submodules/ContextUI/Sources/ContextContentSourceNode.swift b/submodules/ContextUI/Sources/ContextContentSourceNode.swift index 80a93d9a56..edfbea80de 100644 --- a/submodules/ContextUI/Sources/ContextContentSourceNode.swift +++ b/submodules/ContextUI/Sources/ContextContentSourceNode.swift @@ -27,9 +27,11 @@ public final class ContextExtractedContentNode: ASDisplayNode { } final class ContextControllerContentNode: ASDisplayNode { + let sourceNode: ASDisplayNode let controller: ViewController - init(controller: ViewController) { + init(sourceNode: ASDisplayNode, controller: ViewController) { + self.sourceNode = sourceNode self.controller = controller super.init() diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index 851071299c..331da8efd9 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -80,6 +80,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi private var propertyAnimator: AnyObject? private var displayLinkAnimator: DisplayLinkAnimator? private let dimNode: ASDisplayNode + private let withoutBlurDimNode: ASDisplayNode private let dismissNode: ASDisplayNode private let clippingNode: ASDisplayNode @@ -132,6 +133,10 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi self.dimNode.backgroundColor = theme.contextMenu.dimColor self.dimNode.alpha = 0.0 + self.withoutBlurDimNode = ASDisplayNode() + self.withoutBlurDimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.4) + self.withoutBlurDimNode.alpha = 0.0 + self.dismissNode = ASDisplayNode() self.clippingNode = ASDisplayNode() @@ -164,8 +169,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi self.scrollNode.view.delegate = self - self.view.addSubview(self.effectView) self.addSubnode(self.dimNode) + self.addSubnode(self.withoutBlurDimNode) self.addSubnode(self.clippingNode) @@ -437,14 +442,14 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi self.originalProjectedContentViewFrame = (convertFrame(takenViewInfo.contentContainingNode.frame, from: parentSupernode.view, to: self.view), convertFrame(takenViewInfo.contentContainingNode.contentRect, from: takenViewInfo.contentContainingNode.view, to: self.view)) } case let .controller(source): - let contentParentNode = ContextControllerContentNode(controller: source.controller) - self.contentContainerNode.contentNode = .controller(contentParentNode) - self.contentContainerNode.clipsToBounds = true - self.contentContainerNode.cornerRadius = 14.0 - self.contentContainerNode.addSubnode(contentParentNode) - let transitionInfo = source.transitionInfo() if let transitionInfo = transitionInfo, let (sourceNode, sourceNodeRect) = transitionInfo.sourceNode() { + let contentParentNode = ContextControllerContentNode(sourceNode: sourceNode, controller: source.controller) + self.contentContainerNode.contentNode = .controller(contentParentNode) + self.contentContainerNode.clipsToBounds = true + self.contentContainerNode.cornerRadius = 14.0 + self.contentContainerNode.addSubnode(contentParentNode) + let projectedFrame = convertFrame(sourceNodeRect, from: sourceNode.view, to: self.view) self.originalProjectedContentViewFrame = (projectedFrame, projectedFrame) } @@ -483,8 +488,13 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi self.updateLayout(layout: validLayout, transition: .immediate, previousActionsContainerNode: nil) } - self.dimNode.alpha = 1.0 - self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2 * animationDurationFactor) + if !self.dimNode.isHidden { + self.dimNode.alpha = 1.0 + self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2 * animationDurationFactor) + } else { + self.withoutBlurDimNode.alpha = 1.0 + self.withoutBlurDimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2 * animationDurationFactor) + } if #available(iOS 10.0, *) { if let propertyAnimator = self.propertyAnimator { @@ -662,7 +672,12 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi }) } - self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: transitionDuration * animationDurationFactor, removeOnCompletion: false) + if !self.dimNode.isHidden { + self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: transitionDuration * animationDurationFactor, removeOnCompletion: false) + } else { + self.withoutBlurDimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: transitionDuration * animationDurationFactor, removeOnCompletion: false) + } + self.actionsContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2 * animationDurationFactor, removeOnCompletion: false, completion: { _ in completedActionsNode = true intermediateCompletion() @@ -799,7 +814,11 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi }) } - self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: transitionDuration * animationDurationFactor, removeOnCompletion: false) + if !self.dimNode.isHidden { + self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: transitionDuration * animationDurationFactor, removeOnCompletion: false) + } else { + self.withoutBlurDimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: transitionDuration * animationDurationFactor, removeOnCompletion: false) + } self.actionsContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2 * animationDurationFactor, removeOnCompletion: false, completion: { _ in completedActionsNode = true intermediateCompletion() @@ -872,10 +891,6 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi strongSelf.reactionContextNode = nil reactionCompleted = true intermediateCompletion() - /*strongSelf.animateOut(result: .default, completion: { - reactionCompleted = true - intermediateCompletion() - })*/ }) } @@ -939,6 +954,31 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi transition.updateFrame(view: self.effectView, frame: CGRect(origin: CGPoint(), size: layout.size)) transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size)) + transition.updateFrame(node: self.withoutBlurDimNode, frame: CGRect(origin: CGPoint(), size: layout.size)) + + switch layout.metrics.widthClass { + case .compact: + if self.effectView.superview == nil { + self.view.insertSubview(self.effectView, at: 0) + if #available(iOS 10.0, *) { + if let propertyAnimator = self.propertyAnimator { + let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator + propertyAnimator?.stopAnimation(true) + } + } + self.effectView.effect = makeCustomZoomBlurEffect() + self.dimNode.alpha = 1.0 + } + self.dimNode.isHidden = false + self.withoutBlurDimNode.isHidden = true + case .regular: + if self.effectView.superview != nil { + self.effectView.removeFromSuperview() + self.withoutBlurDimNode.alpha = 1.0 + } + self.dimNode.isHidden = true + self.withoutBlurDimNode.isHidden = false + } transition.updateFrame(node: self.clippingNode, frame: CGRect(origin: CGPoint(), size: layout.size)) transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: layout.size)) @@ -958,7 +998,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi let isInitialLayout = self.actionsContainerNode.frame.size.width.isZero let previousContainerFrame = self.view.convert(self.contentContainerNode.frame, from: self.scrollNode.view) - let actionsSize = self.actionsContainerNode.updateLayout(constrainedWidth: layout.size.width - actionsSideInset * 2.0, transition: actionsContainerTransition) + let actionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: layout.size.width - actionsSideInset * 2.0, transition: actionsContainerTransition) let contentSize = originalProjectedContentViewFrame.1.size self.contentContainerNode.updateLayout(size: contentSize, scaledSize: contentSize, transition: transition) @@ -1006,65 +1046,105 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi } } case let .controller(contentParentNode): - let contentActionsSpacing: CGFloat = actionsSideInset - let topEdge = max(contentTopInset, self.contentAreaInScreenSpace?.minY ?? 0.0) + let projectedFrame = convertFrame(contentParentNode.sourceNode.bounds, from: contentParentNode.sourceNode.view, to: self.view) + self.originalProjectedContentViewFrame = (projectedFrame, projectedFrame) - //contentParentNode.updateLayout(size: layout.size, transition: transition) - - let isInitialLayout = self.actionsContainerNode.frame.size.width.isZero - let previousContainerFrame = self.view.convert(self.contentContainerNode.frame, from: self.scrollNode.view) - - let actionsSize = self.actionsContainerNode.updateLayout(constrainedWidth: layout.size.width - actionsSideInset * 2.0, transition: actionsContainerTransition) - let contentScale = (layout.size.width - actionsSideInset * 2.0) / layout.size.width - let contentUnscaledSize: CGSize - if !contentParentNode.controller.preferredContentSize.width.isZero { - contentUnscaledSize = contentParentNode.controller.preferredContentSize - } else { - let proposedContentHeight = layout.size.height - topEdge - contentActionsSpacing - actionsSize.height - layout.intrinsicInsets.bottom - actionsBottomInset - contentUnscaledSize = CGSize(width: layout.size.width, height: max(400.0, proposedContentHeight)) - } - let contentSize = CGSize(width: floor(contentUnscaledSize.width * contentScale), height: floor(contentUnscaledSize.height * contentScale)) - - self.contentContainerNode.updateLayout(size: contentUnscaledSize, scaledSize: contentSize, transition: transition) - - let maximumActionsFrameOrigin = max(60.0, layout.size.height - layout.intrinsicInsets.bottom - actionsBottomInset - actionsSize.height) - var originalActionsFrame = CGRect(origin: CGPoint(x: actionsSideInset, y: min(maximumActionsFrameOrigin, floor((layout.size.height - contentActionsSpacing - contentSize.height) / 2.0) + contentSize.height + contentActionsSpacing)), size: actionsSize) - var originalContentFrame = CGRect(origin: CGPoint(x: actionsSideInset, y: originalActionsFrame.minY - contentActionsSpacing - contentSize.height), size: contentSize) - if originalContentFrame.minY < topEdge { - let requiredOffset = topEdge - originalContentFrame.minY - let availableOffset = max(0.0, layout.size.height - layout.intrinsicInsets.bottom - actionsBottomInset - originalActionsFrame.maxY) - let offset = min(requiredOffset, availableOffset) - originalActionsFrame = originalActionsFrame.offsetBy(dx: 0.0, dy: offset) - originalContentFrame = originalContentFrame.offsetBy(dx: 0.0, dy: offset) - } - - let contentHeight = max(layout.size.height, max(layout.size.height, originalActionsFrame.maxY + actionsBottomInset) - originalContentFrame.minY + contentTopInset) - - let scrollContentSize = CGSize(width: layout.size.width, height: contentHeight) - if self.scrollNode.view.contentSize != scrollContentSize { - self.scrollNode.view.contentSize = scrollContentSize - } - - let overflowOffset = min(0.0, originalContentFrame.minY - contentTopInset) - - let contentContainerFrame = originalContentFrame - transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame.offsetBy(dx: 0.0, dy: -overflowOffset)) - actionsContainerTransition.updateFrame(node: self.actionsContainerNode, frame: originalActionsFrame.offsetBy(dx: 0.0, dy: -overflowOffset)) - - if isInitialLayout { - self.scrollNode.view.contentOffset = CGPoint(x: 0.0, y: -overflowOffset) - let currentContainerFrame = self.view.convert(self.contentContainerNode.frame, from: self.scrollNode.view) - if overflowOffset < 0.0 { - transition.animateOffsetAdditive(node: self.scrollNode, offset: currentContainerFrame.minY - previousContainerFrame.minY) + if let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame { + let contentActionsSpacing: CGFloat = actionsSideInset + let topEdge = max(contentTopInset, self.contentAreaInScreenSpace?.minY ?? 0.0) + + let isInitialLayout = self.actionsContainerNode.frame.size.width.isZero + let previousContainerFrame = self.view.convert(self.contentContainerNode.frame, from: self.scrollNode.view) + + let actionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: layout.size.width - actionsSideInset * 2.0, transition: actionsContainerTransition) + let contentScale = (layout.size.width - actionsSideInset * 2.0) / layout.size.width + var contentUnscaledSize: CGSize + if case .compact = layout.metrics.widthClass { + let proposedContentHeight = layout.size.height - topEdge - contentActionsSpacing - actionsSize.height - layout.intrinsicInsets.bottom - actionsBottomInset + contentUnscaledSize = CGSize(width: layout.size.width, height: max(400.0, proposedContentHeight)) + + if let preferredSize = contentParentNode.controller.preferredContentSizeForLayout(ContainerViewLayout(size: contentUnscaledSize, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false)) { + contentUnscaledSize = preferredSize + } + } else { + let proposedContentHeight = layout.size.height - topEdge - contentActionsSpacing - actionsSize.height - layout.intrinsicInsets.bottom - actionsBottomInset + contentUnscaledSize = CGSize(width: min(layout.size.width, 340.0), height: min(568.0, proposedContentHeight)) + + if let preferredSize = contentParentNode.controller.preferredContentSizeForLayout(ContainerViewLayout(size: contentUnscaledSize, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false)) { + contentUnscaledSize = preferredSize + } + } + let contentSize = CGSize(width: floor(contentUnscaledSize.width * contentScale), height: floor(contentUnscaledSize.height * contentScale)) + + self.contentContainerNode.updateLayout(size: contentUnscaledSize, scaledSize: contentSize, transition: transition) + + let maximumActionsFrameOrigin = max(60.0, layout.size.height - layout.intrinsicInsets.bottom - actionsBottomInset - actionsSize.height) + var originalActionsFrame: CGRect + var originalContentFrame: CGRect + var contentHeight: CGFloat + if case .compact = layout.metrics.widthClass { + originalActionsFrame = CGRect(origin: CGPoint(x: actionsSideInset, y: min(maximumActionsFrameOrigin, floor((layout.size.height - contentActionsSpacing - contentSize.height) / 2.0) + contentSize.height + contentActionsSpacing)), size: actionsSize) + originalContentFrame = CGRect(origin: CGPoint(x: actionsSideInset, y: originalActionsFrame.minY - contentActionsSpacing - contentSize.height), size: contentSize) + if originalContentFrame.minY < topEdge { + let requiredOffset = topEdge - originalContentFrame.minY + let availableOffset = max(0.0, layout.size.height - layout.intrinsicInsets.bottom - actionsBottomInset - originalActionsFrame.maxY) + let offset = min(requiredOffset, availableOffset) + originalActionsFrame = originalActionsFrame.offsetBy(dx: 0.0, dy: offset) + originalContentFrame = originalContentFrame.offsetBy(dx: 0.0, dy: offset) + } + contentHeight = max(layout.size.height, max(layout.size.height, originalActionsFrame.maxY + actionsBottomInset) - originalContentFrame.minY + contentTopInset) + } else { + originalContentFrame = CGRect(origin: CGPoint(x: floor(originalProjectedContentViewFrame.1.midX - contentSize.width / 2.0), y: floor(originalProjectedContentViewFrame.1.midY - contentSize.height / 2.0)), size: contentSize) + originalContentFrame.origin.x = min(originalContentFrame.origin.x, layout.size.width - actionsSideInset - contentSize.width) + originalContentFrame.origin.x = max(originalContentFrame.origin.x, actionsSideInset) + originalContentFrame.origin.y = min(originalContentFrame.origin.y, layout.size.height - layout.intrinsicInsets.bottom - actionsSideInset - contentSize.height) + originalContentFrame.origin.y = max(originalContentFrame.origin.y, contentTopInset) + if originalContentFrame.maxX <= layout.size.width - actionsSideInset - actionsSize.width - contentActionsSpacing { + originalActionsFrame = CGRect(origin: CGPoint(x: originalContentFrame.maxX + contentActionsSpacing, y: originalContentFrame.minY), size: actionsSize) + if originalActionsFrame.maxX > layout.size.width - actionsSideInset { + let offset = originalActionsFrame.maxX - (layout.size.width - actionsSideInset) + originalActionsFrame.origin.x -= offset + originalContentFrame.origin.x -= offset + } + } else { + originalActionsFrame = CGRect(origin: CGPoint(x: originalContentFrame.minX - contentActionsSpacing - actionsSize.width, y: originalContentFrame.minY), size: actionsSize) + if originalActionsFrame.minX < actionsSideInset { + let offset = actionsSideInset - originalActionsFrame.minX + originalActionsFrame.origin.x += offset + originalContentFrame.origin.x += offset + } + } + contentHeight = layout.size.height + contentHeight = max(contentHeight, originalActionsFrame.maxY + actionsBottomInset) + contentHeight = max(contentHeight, originalContentFrame.maxY + actionsBottomInset) + } + + let scrollContentSize = CGSize(width: layout.size.width, height: contentHeight) + if self.scrollNode.view.contentSize != scrollContentSize { + self.scrollNode.view.contentSize = scrollContentSize + } + + let overflowOffset = min(0.0, originalContentFrame.minY - contentTopInset) + + let contentContainerFrame = originalContentFrame + transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame.offsetBy(dx: 0.0, dy: -overflowOffset)) + actionsContainerTransition.updateFrame(node: self.actionsContainerNode, frame: originalActionsFrame.offsetBy(dx: 0.0, dy: -overflowOffset)) + + if isInitialLayout { + self.scrollNode.view.contentOffset = CGPoint(x: 0.0, y: -overflowOffset) + let currentContainerFrame = self.view.convert(self.contentContainerNode.frame, from: self.scrollNode.view) + if overflowOffset < 0.0 { + transition.animateOffsetAdditive(node: self.scrollNode, offset: currentContainerFrame.minY - previousContainerFrame.minY) + } + } + + let absoluteContentRect = contentContainerFrame.offsetBy(dx: 0.0, dy: -self.scrollNode.view.contentOffset.y) + + if let reactionContextNode = self.reactionContextNode { + let insets = layout.insets(options: [.statusBar]) + transition.updateFrame(node: reactionContextNode, frame: CGRect(origin: CGPoint(), size: layout.size)) + reactionContextNode.updateLayout(size: layout.size, insets: insets, anchorRect: CGRect(origin: CGPoint(x: absoluteContentRect.minX, y: absoluteContentRect.minY), size: contentSize), transition: transition) } - } - - let absoluteContentRect = contentContainerFrame.offsetBy(dx: 0.0, dy: -self.scrollNode.view.contentOffset.y) - - if let reactionContextNode = self.reactionContextNode { - let insets = layout.insets(options: [.statusBar]) - transition.updateFrame(node: reactionContextNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - reactionContextNode.updateLayout(size: layout.size, insets: insets, anchorRect: CGRect(origin: CGPoint(x: absoluteContentRect.minX, y: absoluteContentRect.minY), size: contentSize), transition: transition) } } } diff --git a/submodules/ContextUI/Sources/ContextControllerSourceNode.swift b/submodules/ContextUI/Sources/ContextControllerSourceNode.swift index 18137bbe91..0ff94a03f0 100644 --- a/submodules/ContextUI/Sources/ContextControllerSourceNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerSourceNode.swift @@ -23,21 +23,17 @@ public final class ContextControllerSourceNode: ASDisplayNode { guard let strongSelf = self, !strongSelf.bounds.width.isZero else { return } - let minScale: CGFloat = (strongSelf.bounds.width - 20.0) / strongSelf.bounds.width + let minScale: CGFloat = (strongSelf.bounds.width - 10.0) / strongSelf.bounds.width let currentScale = 1.0 * (1.0 - progress) + minScale * progress switch update { case .update: strongSelf.layer.sublayerTransform = CATransform3DMakeScale(currentScale, currentScale, 1.0) case .begin: strongSelf.layer.sublayerTransform = CATransform3DMakeScale(currentScale, currentScale, 1.0) - /*let previousProgress: CGFloat = 0.0 - let previousScale = 1.0 * (1.0 - previousProgress) + minScale * previousProgress - strongSelf.layer.sublayerTransform = CATransform3DMakeScale(currentScale, currentScale, 1.0) - strongSelf.layer.animate(from: previousScale as NSNumber, to: currentScale as NSNumber, keyPath: "sublayerTransform.scale", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.3, additive: false)*/ case let .ended(previousProgress): let previousScale = 1.0 * (1.0 - previousProgress) + minScale * previousProgress strongSelf.layer.sublayerTransform = CATransform3DMakeScale(currentScale, currentScale, 1.0) - strongSelf.layer.animate(from: previousScale as NSNumber, to: currentScale as NSNumber, keyPath: "sublayerTransform.scale", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.3) + strongSelf.layer.animateSpring(from: previousScale as NSNumber, to: currentScale as NSNumber, keyPath: "sublayerTransform.scale", duration: 0.5, delay: 0.0, initialVelocity: 0.0, damping: 90.0) } } contextGesture.activated = { [weak self] gesture in diff --git a/submodules/ContextUI/Sources/ContextGesture.swift b/submodules/ContextUI/Sources/ContextGesture.swift index b99981c1ed..6450b1f77c 100644 --- a/submodules/ContextUI/Sources/ContextGesture.swift +++ b/submodules/ContextUI/Sources/ContextGesture.swift @@ -40,6 +40,7 @@ private func cancelParentGestures(view: UIView) { public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDelegate { private var currentProgress: CGFloat = 0.0 private var delayTimer: Timer? + private var animator: DisplayLinkAnimator? private var isValidated: Bool = false public var activationProgress: ((CGFloat, ContextGestureTransition) -> Void)? @@ -62,6 +63,8 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg self.isValidated = false self.externalUpdated = nil self.externalEnded = nil + self.animator?.invalidate() + self.animator = nil } public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { @@ -80,6 +83,33 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg return } strongSelf.isValidated = true + if strongSelf.animator == nil { + strongSelf.animator = DisplayLinkAnimator(duration: 0.3, from: 0.0, to: 1.0, update: { value in + guard let strongSelf = self else { + return + } + if strongSelf.isValidated { + strongSelf.currentProgress = value + strongSelf.activationProgress?(value, .update) + } + }, completion: { + guard let strongSelf = self else { + return + } + switch strongSelf.state { + case .possible: + strongSelf.delayTimer?.invalidate() + strongSelf.animator?.invalidate() + strongSelf.activated?(strongSelf) + if let view = strongSelf.view?.superview { + cancelParentGestures(view: view) + } + strongSelf.state = .began + default: + break + } + }) + } strongSelf.activationProgress?(strongSelf.currentProgress, .begin) }, selector: #selector(TimerTargetWrapper.timerEvent), userInfo: nil, repeats: false) self.delayTimer = delayTimer @@ -91,7 +121,7 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg super.touchesMoved(touches, with: event) if let touch = touches.first { - if #available(iOS 9.0, *) { + /*if #available(iOS 9.0, *) { let maxForce: CGFloat = max(2.5, min(3.0, touch.maximumPossibleForce)) let progress = touch.force / maxForce self.currentProgress = progress @@ -111,7 +141,7 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg break } } - } + }*/ self.externalUpdated?(self.view, touch.location(in: self.view)) } @@ -131,6 +161,7 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg } self.delayTimer?.invalidate() + self.animator?.invalidate() self.state = .failed } @@ -145,6 +176,7 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg } self.delayTimer?.invalidate() + self.animator?.invalidate() self.state = .failed } @@ -154,6 +186,7 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg self.activationProgress?(0.0, .ended(self.currentProgress)) self.delayTimer?.invalidate() + self.animator?.invalidate() self.state = .failed } } @@ -163,6 +196,7 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg let previousProgress = self.currentProgress self.currentProgress = 0.0 self.delayTimer?.invalidate() + self.animator?.invalidate() self.isValidated = false self.activationProgress?(0.0, .ended(previousProgress)) } diff --git a/submodules/Display/Display/ContainableController.swift b/submodules/Display/Display/ContainableController.swift index 75265cf39f..3080367511 100644 --- a/submodules/Display/Display/ContainableController.swift +++ b/submodules/Display/Display/ContainableController.swift @@ -22,6 +22,7 @@ public protocol ContainableController: class { func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) func updateToInterfaceOrientation(_ orientation: UIInterfaceOrientation) func updateModalTransition(_ value: CGFloat, transition: ContainedViewLayoutTransition) + func preferredContentSizeForLayout(_ layout: ContainerViewLayout) -> CGSize? func viewWillAppear(_ animated: Bool) func viewWillDisappear(_ animated: Bool) diff --git a/submodules/Display/Display/DisplayLinkAnimator.swift b/submodules/Display/Display/DisplayLinkAnimator.swift index 40892dcc0e..a7cbd79858 100644 --- a/submodules/Display/Display/DisplayLinkAnimator.swift +++ b/submodules/Display/Display/DisplayLinkAnimator.swift @@ -44,7 +44,12 @@ public final class DisplayLinkAnimator { self.displayLink.invalidate() } - @objc func tick() { + public func invalidate() { + self.displayLink.isPaused = true + self.displayLink.invalidate() + } + + @objc private func tick() { if self.completed { return } diff --git a/submodules/Display/Display/NavigationController.swift b/submodules/Display/Display/NavigationController.swift index 7850239b47..4983d226c4 100644 --- a/submodules/Display/Display/NavigationController.swift +++ b/submodules/Display/Display/NavigationController.swift @@ -42,6 +42,7 @@ private final class NavigationControllerView: UITracingLayerView { var inTransition = false let sharedStatusBar: StatusBar + let masterContainerView: NavigationControllerContainerView let containerView: NavigationControllerContainerView let separatorView: UIView var navigationBackgroundView: UIView? @@ -52,12 +53,14 @@ private final class NavigationControllerView: UITracingLayerView { var topControllerNode: ASDisplayNode? override init(frame: CGRect) { + self.masterContainerView = NavigationControllerContainerView() self.containerView = NavigationControllerContainerView() self.separatorView = UIView() self.sharedStatusBar = StatusBar() super.init(frame: frame) + self.addSubview(self.masterContainerView) self.addSubview(self.containerView) } @@ -138,6 +141,7 @@ open class NavigationController: UINavigationController, ContainableController, private var scheduledLayoutTransitionRequestId: Int = 0 private var scheduledLayoutTransitionRequest: (Int, ContainedViewLayoutTransition)? + private var masterTransitionCoordinator: NavigationTransitionCoordinator? private var navigationTransitionCoordinator: NavigationTransitionCoordinator? private var currentPushDisposable = MetaDisposable() @@ -230,6 +234,10 @@ open class NavigationController: UINavigationController, ContainableController, private var previouslyLaidOutMasterController: UIViewController? private var previouslyLaidOutTopController: UIViewController? + open func preferredContentSizeForLayout(_ layout: ContainerViewLayout) -> CGSize? { + return nil + } + private func layoutConfiguration(for layout: ContainerViewLayout) -> ControllerLayoutConfiguration { switch self.mode { case .single: @@ -275,7 +283,7 @@ open class NavigationController: UINavigationController, ContainableController, switch layoutConfiguration { case .masterDetail: - self.viewControllers.first?.view.clipsToBounds = true + self.controllerView.masterContainerView.clipsToBounds = true self.controllerView.containerView.clipsToBounds = true let masterData = layoutDataForConfiguration(layoutConfiguration, layout: layout, index: 0) firstControllerFrameAndLayout = masterData @@ -402,6 +410,7 @@ open class NavigationController: UINavigationController, ContainableController, }) } } + self.controllerView.masterContainerView.clipsToBounds = false self.controllerView.containerView.clipsToBounds = false lastControllerFrameAndLayout = layoutDataForConfiguration(layoutConfiguration, layout: layout, index: 1) transition.updateFrame(view: self.controllerView.separatorView, frame: CGRect(origin: CGPoint(x: -UIScreenPixel, y: 0.0), size: CGSize(width: UIScreenPixel, height: layout.size.height)), completion: { [weak self] completed in @@ -410,6 +419,9 @@ open class NavigationController: UINavigationController, ContainableController, } }) } + if let firstControllerFrameAndLayout = firstControllerFrameAndLayout { + transition.updateFrame(view: self.controllerView.masterContainerView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: firstControllerFrameAndLayout.0.size)) + } transition.updateFrame(view: self.controllerView.containerView, frame: CGRect(origin: CGPoint(x: firstControllerFrameAndLayout?.0.maxX ?? 0.0, y: 0.0), size: lastControllerFrameAndLayout.0.size)) switch layoutConfiguration { @@ -434,11 +446,28 @@ open class NavigationController: UINavigationController, ContainableController, } var controllersAndFrames: [(Bool, ControllerRecord, ContainerViewLayout)] = [] + var masterRange: ClosedRange? + if case .masterDetail = layoutConfiguration { + for i in 0 ..< self._viewControllers.count { + if let controller = self._viewControllers[i].controller as? ViewController { + switch controller.navigationPresentation { + case .default: + break + case .master: + if let masterRangeValue = masterRange { + masterRange = masterRangeValue.lowerBound ... i + } else { + masterRange = i ... i + } + } + } + } + } for i in 0 ..< self._viewControllers.count { if let controller = self._viewControllers[i].controller as? ViewController { if i == 0 { controller.navigationBar?.previousItem = nil - } else if case .masterDetail = layoutConfiguration, i == 1 { + } else if let masterRange = masterRange, i == masterRange.upperBound + 1 { controller.navigationBar?.previousItem = .close } else { controller.navigationBar?.previousItem = .item(self.viewControllers[i - 1].navigationItem) @@ -451,13 +480,25 @@ open class NavigationController: UINavigationController, ContainableController, } self.viewControllers[i].navigation_setNavigationController(self) - if i == 0, let (_, layout) = firstControllerFrameAndLayout { + if let (_, layout) = firstControllerFrameAndLayout, let controller = self._viewControllers[i].controller as? ViewController, case .master = controller.navigationPresentation { controllersAndFrames.append((true, self._viewControllers[i], layout)) } else if i == self._viewControllers.count - 1 { controllersAndFrames.append((false, self._viewControllers[i], lastControllerFrameAndLayout.1)) } } + while controllersAndFrames.count >= 2 { + if controllersAndFrames[0].0 { + if controllersAndFrames[1].0 { + controllersAndFrames.remove(at: 0) + } else { + break + } + } else { + break + } + } + var masterController: UIViewController? var appearingMasterController: ControllerRecord? var appearingDetailController: ControllerRecord? @@ -489,10 +530,10 @@ open class NavigationController: UINavigationController, ContainableController, } else { appearingDetailController = record } - } else if record.controller.view.superview !== (isMaster ? self.controllerView : self.controllerView.containerView) { + } else if record.controller.view.superview !== (isMaster ? self.controllerView.masterContainerView : self.controllerView.containerView) { record.controller.setIgnoreAppearanceMethodInvocations(true) if isMaster { - self.controllerView.insertSubview(record.controller.view, at: 0) + self.controllerView.masterContainerView.addSubview(record.controller.view) } else { self.controllerView.containerView.addSubview(record.controller.view) } @@ -518,8 +559,76 @@ open class NavigationController: UINavigationController, ContainableController, } var animatedAppearingDetailController = false + var animatedAppearingMasterController = false - if let previousController = self.previouslyLaidOutTopController, !controllersAndFrames.contains(where: { $0.1.controller === previousController }), previousController.view.superview != nil { + if let previousController = self.previouslyLaidOutMasterController, !controllersAndFrames.contains(where: { $0.1.controller === previousController }), previousController.view.superview != nil { + if transition.isAnimated, let record = appearingMasterController { + animatedAppearingMasterController = true + + previousController.viewWillDisappear(true) + record.controller.viewWillAppear(true) + record.controller.setIgnoreAppearanceMethodInvocations(true) + + if let controller = record.controller as? ViewController, !controller.hasActiveInput { + let (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: layout), layout: layout, index: 0) + + let appliedLayout = controllerLayout.withUpdatedInputHeight(controller.hasActiveInput ? controllerLayout.inputHeight : nil) + controller.containerLayoutUpdated(appliedLayout, transition: .immediate) + } + self.controllerView.masterContainerView.addSubview(record.controller.view) + record.controller.setIgnoreAppearanceMethodInvocations(false) + + if let _ = previousControllers.firstIndex(where: { $0.controller === record.controller }) { + let masterTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.controllerView.masterContainerView, topView: previousController.view, topNavigationBar: (previousController as? ViewController)?.navigationBar, bottomView: record.controller.view, bottomNavigationBar: (record.controller as? ViewController)?.navigationBar) + self.masterTransitionCoordinator = masterTransitionCoordinator + + self.controllerView.inTransition = true + masterTransitionCoordinator.animateCompletion(0.0, completion: { [weak self] in + if let strongSelf = self { + strongSelf.masterTransitionCoordinator = nil + strongSelf.controllerView.inTransition = false + + record.controller.viewDidAppear(true) + + previousController.setIgnoreAppearanceMethodInvocations(true) + previousController.view.removeFromSuperview() + previousController.setIgnoreAppearanceMethodInvocations(false) + previousController.viewDidDisappear(true) + } + }) + } else { + if let index = self._viewControllers.firstIndex(where: { $0.controller === previousController }) { + self._viewControllers[index].transition = .appearance + } + let masterTransitionCoordinator = NavigationTransitionCoordinator(transition: .Push, container: self.controllerView.masterContainerView, topView: record.controller.view, topNavigationBar: (record.controller as? ViewController)?.navigationBar, bottomView: previousController.view, bottomNavigationBar: (previousController as? ViewController)?.navigationBar) + self.masterTransitionCoordinator = masterTransitionCoordinator + + self.controllerView.inTransition = true + masterTransitionCoordinator.animateCompletion(0.0, completion: { [weak self] in + if let strongSelf = self { + if let index = strongSelf._viewControllers.firstIndex(where: { $0.controller === previousController }) { + strongSelf._viewControllers[index].transition = .none + } + strongSelf.masterTransitionCoordinator = nil + strongSelf.controllerView.inTransition = false + + record.controller.viewDidAppear(true) + + previousController.setIgnoreAppearanceMethodInvocations(true) + previousController.view.removeFromSuperview() + previousController.setIgnoreAppearanceMethodInvocations(false) + previousController.viewDidDisappear(true) + } + }) + } + } else { + previousController.viewWillDisappear(false) + previousController.view.removeFromSuperview() + previousController.viewDidDisappear(false) + } + } + + if let previousController = self.previouslyLaidOutTopController, previousController !== self.previouslyLaidOutMasterController, !controllersAndFrames.contains(where: { $0.1.controller === previousController }), previousController.view.superview != nil { if transition.isAnimated, let record = appearingDetailController { animatedAppearingDetailController = true @@ -537,7 +646,6 @@ open class NavigationController: UINavigationController, ContainableController, record.controller.setIgnoreAppearanceMethodInvocations(false) if let _ = previousControllers.firstIndex(where: { $0.controller === record.controller }) { - //previousControllers[index].transition = .appearance let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.controllerView.containerView, topView: previousController.view, topNavigationBar: (previousController as? ViewController)?.navigationBar, bottomView: record.controller.view, bottomNavigationBar: (record.controller as? ViewController)?.navigationBar) self.navigationTransitionCoordinator = navigationTransitionCoordinator @@ -598,10 +706,10 @@ open class NavigationController: UINavigationController, ContainableController, } } - if let record = appearingMasterController, let firstControllerFrameAndLayout = firstControllerFrameAndLayout { + if !animatedAppearingMasterController, let record = appearingMasterController, let firstControllerFrameAndLayout = firstControllerFrameAndLayout { record.controller.viewWillAppear(false) record.controller.setIgnoreAppearanceMethodInvocations(true) - self.controllerView.insertSubview(record.controller.view, belowSubview: self.controllerView.containerView) + self.controllerView.masterContainerView.addSubview(record.controller.view) record.controller.setIgnoreAppearanceMethodInvocations(false) record.controller.viewDidAppear(false) if let controller = record.controller as? ViewController { @@ -782,127 +890,281 @@ open class NavigationController: UINavigationController, ContainableController, @objc func panGesture(_ recognizer: UIPanGestureRecognizer) { switch recognizer.state { - case .began: - guard let layout = self.validLayout else { + case .began: + guard let layout = self.validLayout else { + return + } + var beginMasterGesture = false + var beginDetailGesture = false + + let masterControllers: [ControllerRecord] + let detailControllers: [ControllerRecord] + + switch self.layoutConfiguration(for: layout) { + case .masterDetail: + masterControllers = self._viewControllers.filter({ record in + if let controller = record.controller as? ViewController { + if case .master = controller.navigationPresentation { + return true + } else { + return false + } + } else { + return false + } + }) + detailControllers = self._viewControllers.filter({ record in + if let controller = record.controller as? ViewController { + if case .master = controller.navigationPresentation { + return false + } else { + return true + } + } else { + return true + } + }) + + let locationInMaster = recognizer.location(in: self.controllerView.masterContainerView) + let locationInDetail = recognizer.location(in: self.controllerView.containerView) + + if self.controllerView.masterContainerView.bounds.contains(locationInMaster) { + beginMasterGesture = masterControllers.count >= 2 + } + if self.controllerView.containerView.bounds.contains(locationInDetail) { + beginDetailGesture = detailControllers.count >= 2 + } + case .single: + beginDetailGesture = self._viewControllers.count >= 2 + masterControllers = [] + detailControllers = self._viewControllers + } + + if beginMasterGesture { + guard self.masterTransitionCoordinator == nil else { return } + + let topController = masterControllers[masterControllers.count - 1].controller + let bottomController = masterControllers[masterControllers.count - 2].controller + + if let topController = topController as? ViewController { + if !topController.attemptNavigation({ [weak self, weak topController] in + if let topController = topController { + self?.filterController(topController, animated: true) + } + }) { + return + } + } + + topController.viewWillDisappear(true) + let topView = topController.view! + if let bottomController = bottomController as? ViewController { + let (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: layout), layout: layout, index: 0) + + let appliedLayout = controllerLayout.withUpdatedInputHeight(bottomController.hasActiveInput ? controllerLayout.inputHeight : nil) + bottomController.containerLayoutUpdated(appliedLayout, transition: .immediate) + } + bottomController.viewWillAppear(true) + let bottomView = bottomController.view! + + let masterTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.controllerView.masterContainerView, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar, didUpdateProgress: { [weak self, weak topController, weak bottomController] progress, transition in + if let strongSelf = self, let topController = topController, let bottomController = bottomController { + (bottomController as? ViewController)?.updateNavigationCustomData((topController as? ViewController)?.customData, progress: 1.0 - progress, transition: transition) + } + }) + if let bottomController = bottomController as? ViewController { + bottomController.displayNode.recursivelyEnsureDisplaySynchronously(true) + } + self.masterTransitionCoordinator = masterTransitionCoordinator + } + if beginDetailGesture { guard self.navigationTransitionCoordinator == nil else { return } - let beginGesture: Bool - switch self.layoutConfiguration(for: layout) { - case .masterDetail: - let location = recognizer.location(in: self.controllerView.containerView) - if self.controllerView.containerView.bounds.contains(location) { - beginGesture = self._viewControllers.count >= 3 - } else { - beginGesture = false - } - case .single: - beginGesture = self._viewControllers.count >= 2 + let topController = detailControllers[detailControllers.count - 1].controller + let bottomController = detailControllers[detailControllers.count - 2].controller + + if let topController = topController as? ViewController { + if !topController.attemptNavigation({ [weak self] in + let _ = self?.popViewController(animated: true) + }) { + return + } } - if beginGesture { - let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController - let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController + topController.viewWillDisappear(true) + let topView = topController.view! + if let bottomController = bottomController as? ViewController { + let (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: layout), layout: layout, index: 1) - if let topController = topController as? ViewController { - if !topController.attemptNavigation({ [weak self] in - let _ = self?.popViewController(animated: true) - }) { - return - } - } - - topController.viewWillDisappear(true) - let topView = topController.view! - if let bottomController = bottomController as? ViewController { - let (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: layout), layout: layout, index: self.viewControllers.count - 2) - - let appliedLayout = controllerLayout.withUpdatedInputHeight(bottomController.hasActiveInput ? controllerLayout.inputHeight : nil) - bottomController.containerLayoutUpdated(appliedLayout, transition: .immediate) - } - bottomController.viewWillAppear(true) - let bottomView = bottomController.view! - - let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.controllerView.containerView, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar, didUpdateProgress: { [weak self] progress, transition in - if let strongSelf = self { - for i in 0 ..< strongSelf._viewControllers.count { - if let controller = strongSelf._viewControllers[i].controller as? ViewController { - if i < strongSelf._viewControllers.count - 1 { - controller.updateNavigationCustomData((strongSelf.viewControllers[i + 1] as? ViewController)?.customData, progress: 1.0 - progress, transition: transition) - } else { - controller.updateNavigationCustomData(nil, progress: 1.0 - progress, transition: transition) - } - } - } - } - }) - if let bottomController = bottomController as? ViewController { - bottomController.displayNode.recursivelyEnsureDisplaySynchronously(true) - } - self.navigationTransitionCoordinator = navigationTransitionCoordinator + let appliedLayout = controllerLayout.withUpdatedInputHeight(bottomController.hasActiveInput ? controllerLayout.inputHeight : nil) + bottomController.containerLayoutUpdated(appliedLayout, transition: .immediate) } - case .changed: - if let navigationTransitionCoordinator = self.navigationTransitionCoordinator, !navigationTransitionCoordinator.animatingCompletion { + bottomController.viewWillAppear(true) + let bottomView = bottomController.view! + + let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.controllerView.containerView, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar, didUpdateProgress: { [weak self, weak topController, weak bottomController] progress, transition in + if let strongSelf = self, let topController = topController, let bottomController = bottomController { + (bottomController as? ViewController)?.updateNavigationCustomData((topController as? ViewController)?.customData, progress: 1.0 - progress, transition: transition) + } + }) + if let bottomController = bottomController as? ViewController { + bottomController.displayNode.recursivelyEnsureDisplaySynchronously(true) + } + self.navigationTransitionCoordinator = navigationTransitionCoordinator + } + case .changed: + if let layout = self.validLayout { + if let masterTransitionCoordinator = self.masterTransitionCoordinator, !masterTransitionCoordinator.animatingCompletion { + let (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: layout), layout: layout, index: 0) let translation = recognizer.translation(in: self.view).x - let progress = max(0.0, min(1.0, translation / self.view.frame.width)) + let progress = max(0.0, min(1.0, translation / controllerLayout.size.width)) + masterTransitionCoordinator.progress = progress + } + if let navigationTransitionCoordinator = self.navigationTransitionCoordinator, !navigationTransitionCoordinator.animatingCompletion { + let (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: layout), layout: layout, index: 1) + let translation = recognizer.translation(in: self.view).x + let progress = max(0.0, min(1.0, translation / controllerLayout.size.width)) navigationTransitionCoordinator.progress = progress } - case .ended: - if let navigationTransitionCoordinator = self.navigationTransitionCoordinator, !navigationTransitionCoordinator.animatingCompletion { - let velocity = recognizer.velocity(in: self.view).x - - if velocity > 1000 || navigationTransitionCoordinator.progress > 0.2 { - (self.view as! NavigationControllerView).inTransition = true - navigationTransitionCoordinator.animateCompletion(velocity, completion: { - (self.view as! NavigationControllerView).inTransition = false - - self.navigationTransitionCoordinator = nil - - if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { - let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController - let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController - - topController.setIgnoreAppearanceMethodInvocations(true) - bottomController.setIgnoreAppearanceMethodInvocations(true) - let _ = self.popViewController(animated: false) - topController.setIgnoreAppearanceMethodInvocations(false) - bottomController.setIgnoreAppearanceMethodInvocations(false) - - topController.viewDidDisappear(true) - bottomController.viewDidAppear(true) + } + case .ended: + if let masterTransitionCoordinator = self.masterTransitionCoordinator, !masterTransitionCoordinator.animatingCompletion { + let velocity = recognizer.velocity(in: self.view).x + + let masterControllers = self._viewControllers.filter({ record in + if let controller = record.controller as? ViewController, case .master = controller.navigationPresentation { + return true + } + return false + }) + + if velocity > 1000 || masterTransitionCoordinator.progress > 0.2 { + (self.view as! NavigationControllerView).inTransition = true + masterTransitionCoordinator.animateCompletion(velocity, completion: { + (self.view as! NavigationControllerView).inTransition = false + self.masterTransitionCoordinator = nil + + let masterControllers = self._viewControllers.filter({ record in + if let controller = record.controller as? ViewController, case .master = controller.navigationPresentation { + return true } + return false }) - } else { - if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { - let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController - let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController + + if masterControllers.count >= 2 && self.masterTransitionCoordinator == nil { + let topController = masterControllers[masterControllers.count - 1].controller + let bottomController = masterControllers[masterControllers.count - 2].controller - topController.viewWillAppear(true) - bottomController.viewWillDisappear(true) + topController.setIgnoreAppearanceMethodInvocations(true) + bottomController.setIgnoreAppearanceMethodInvocations(true) + if let topController = topController as? ViewController { + self.filterController(topController, animated: false) + } + topController.setIgnoreAppearanceMethodInvocations(false) + bottomController.setIgnoreAppearanceMethodInvocations(false) + + topController.viewDidDisappear(true) + bottomController.viewDidAppear(true) + } + }) + } else { + if masterControllers.count >= 2 && masterControllers == nil { + let topController = masterControllers[masterControllers.count - 1].controller + let bottomController = masterControllers[masterControllers.count - 2].controller + + topController.viewWillAppear(true) + bottomController.viewWillDisappear(true) + } + + (self.view as! NavigationControllerView).inTransition = true + masterTransitionCoordinator.animateCancel({ + (self.view as! NavigationControllerView).inTransition = false + self.masterTransitionCoordinator = nil + + let masterControllers = self._viewControllers.filter({ record in + if let controller = record.controller as? ViewController, case .master = controller.navigationPresentation { + return true + } + return false + }) + + if masterControllers.count >= 2 && self.masterTransitionCoordinator == nil { + let topController = masterControllers[masterControllers.count - 1].controller + let bottomController = masterControllers[masterControllers.count - 2].controller + + topController.viewDidAppear(true) + bottomController.viewDidDisappear(true) + } + }) + } + } + if let layout = self.validLayout, let navigationTransitionCoordinator = self.navigationTransitionCoordinator, !navigationTransitionCoordinator.animatingCompletion { + let velocity = recognizer.velocity(in: self.view).x + + let detailControllers: [ControllerRecord] + switch self.layoutConfiguration(for: layout) { + case .masterDetail: + detailControllers = self._viewControllers.filter({ record in + if let controller = record.controller as? ViewController { + if case .master = controller.navigationPresentation { + return false + } else { + return true + } + } else { + return true + } + }) + case .single: + detailControllers = self._viewControllers + } + + if velocity > 1000 || navigationTransitionCoordinator.progress > 0.2 { + (self.view as! NavigationControllerView).inTransition = true + navigationTransitionCoordinator.animateCompletion(velocity, completion: { + (self.view as! NavigationControllerView).inTransition = false + + let detailControllers: [ControllerRecord] + switch self.layoutConfiguration(for: layout) { + case .masterDetail: + detailControllers = self._viewControllers.filter({ record in + if let controller = record.controller as? ViewController { + if case .master = controller.navigationPresentation { + return false + } else { + return true + } + } else { + return true + } + }) + case .single: + detailControllers = self._viewControllers } - (self.view as! NavigationControllerView).inTransition = true - navigationTransitionCoordinator.animateCancel({ - (self.view as! NavigationControllerView).inTransition = false - self.navigationTransitionCoordinator = nil + self.navigationTransitionCoordinator = nil + + if detailControllers.count >= 2 && self.navigationTransitionCoordinator == nil { + let topController = detailControllers[detailControllers.count - 1].controller + let bottomController = detailControllers[detailControllers.count - 2].controller - if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { - let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController - let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController - - topController.viewDidAppear(true) - bottomController.viewDidDisappear(true) - } - }) - } - } - case .cancelled: - if let navigationTransitionCoordinator = self.navigationTransitionCoordinator, !navigationTransitionCoordinator.animatingCompletion { - if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { - let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController - let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController + topController.setIgnoreAppearanceMethodInvocations(true) + bottomController.setIgnoreAppearanceMethodInvocations(true) + self.filterController(topController as! ViewController, animated: false) + topController.setIgnoreAppearanceMethodInvocations(false) + bottomController.setIgnoreAppearanceMethodInvocations(false) + + topController.viewDidDisappear(true) + bottomController.viewDidAppear(true) + } + }) + } else { + if detailControllers.count >= 2 && self.navigationTransitionCoordinator == nil { + let topController = detailControllers[detailControllers.count - 1].controller + let bottomController = detailControllers[detailControllers.count - 2].controller topController.viewWillAppear(true) bottomController.viewWillDisappear(true) @@ -913,17 +1175,133 @@ open class NavigationController: UINavigationController, ContainableController, (self.view as! NavigationControllerView).inTransition = false self.navigationTransitionCoordinator = nil - if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { - let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController - let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController + let detailControllers: [ControllerRecord] + switch self.layoutConfiguration(for: layout) { + case .masterDetail: + detailControllers = self._viewControllers.filter({ record in + if let controller = record.controller as? ViewController { + if case .master = controller.navigationPresentation { + return false + } else { + return true + } + } else { + return true + } + }) + case .single: + detailControllers = self._viewControllers + } + + if detailControllers.count >= 2 && self.navigationTransitionCoordinator == nil { + let topController = detailControllers[detailControllers.count - 1].controller + let bottomController = detailControllers[detailControllers.count - 2].controller topController.viewDidAppear(true) bottomController.viewDidDisappear(true) } }) } - default: - break + } + case .cancelled: + if let masterTransitionCoordinator = self.masterTransitionCoordinator, !masterTransitionCoordinator.animatingCompletion { + let masterControllers = self._viewControllers.filter({ record in + if let controller = record.controller as? ViewController, case .master = controller.navigationPresentation { + return true + } + return false + }) + + if masterControllers.count >= 2 && self.masterTransitionCoordinator == nil { + let topController = masterControllers[masterControllers.count - 1].controller + let bottomController = masterControllers[masterControllers.count - 2].controller + + topController.viewWillAppear(true) + bottomController.viewWillDisappear(true) + } + + (self.view as! NavigationControllerView).inTransition = true + masterTransitionCoordinator.animateCancel({ + (self.view as! NavigationControllerView).inTransition = false + self.masterTransitionCoordinator = nil + + let masterControllers = self._viewControllers.filter({ record in + if let controller = record.controller as? ViewController, case .master = controller.navigationPresentation { + return true + } + return false + }) + + if masterControllers.count >= 2 && self.masterTransitionCoordinator == nil { + let topController = masterControllers[masterControllers.count - 1].controller + let bottomController = masterControllers[masterControllers.count - 2].controller + + topController.viewDidAppear(true) + bottomController.viewDidDisappear(true) + } + }) + } + if let layout = self.validLayout, let navigationTransitionCoordinator = self.navigationTransitionCoordinator, !navigationTransitionCoordinator.animatingCompletion { + let detailControllers: [ControllerRecord] + switch self.layoutConfiguration(for: layout) { + case .masterDetail: + detailControllers = self._viewControllers.filter({ record in + if let controller = record.controller as? ViewController { + if case .master = controller.navigationPresentation { + return false + } else { + return true + } + } else { + return true + } + }) + case .single: + detailControllers = self._viewControllers + } + + if detailControllers.count >= 2 && self.navigationTransitionCoordinator == nil { + let topController = detailControllers[detailControllers.count - 1].controller + let bottomController = detailControllers[detailControllers.count - 2].controller + + topController.viewWillAppear(true) + bottomController.viewWillDisappear(true) + } + + (self.view as! NavigationControllerView).inTransition = true + navigationTransitionCoordinator.animateCancel({ + (self.view as! NavigationControllerView).inTransition = false + self.navigationTransitionCoordinator = nil + + let detailControllers: [ControllerRecord] + switch self.layoutConfiguration(for: layout) { + case .masterDetail: + detailControllers = self._viewControllers.filter({ record in + if let controller = record.controller as? ViewController { + if case .master = controller.navigationPresentation { + return false + } else { + return true + } + } else { + return true + } + }) + case .single: + detailControllers = self._viewControllers + } + + if detailControllers.count >= 2 && self.navigationTransitionCoordinator == nil { + let topController = detailControllers[detailControllers.count - 1].controller + let bottomController = detailControllers[detailControllers.count - 2].controller + + topController.viewDidAppear(true) + bottomController.viewDidDisappear(true) + } + }) + } + default: + break } } @@ -985,7 +1363,30 @@ open class NavigationController: UINavigationController, ContainableController, self.currentPushDisposable.set(nil) var controllers = self.viewControllers - controllers.append(viewController) + if let controller = viewController as? ViewController { + switch controller.navigationPresentation { + case .default: + controllers.append(viewController) + case .master: + var i = 0 + loop: while i < controllers.count { + if let currentController = controllers[i] as? ViewController { + switch currentController.navigationPresentation { + case .master: + break + case .default: + break loop + } + } else { + break loop + } + i += 1 + } + controllers.insert(viewController, at: i) + } + } else { + controllers.append(viewController) + } self.setViewControllers(controllers, animated: animated) } diff --git a/submodules/Display/Display/ViewController.swift b/submodules/Display/Display/ViewController.swift index 923a40f8e8..c3764f6e03 100644 --- a/submodules/Display/Display/ViewController.swift +++ b/submodules/Display/Display/ViewController.swift @@ -55,6 +55,11 @@ open class ViewControllerPresentationArguments { } } +public enum ViewControllerNavigationPresentation { + case `default` + case master +} + @objc open class ViewController: UIViewController, ContainableController { private var validLayout: ContainerViewLayout? public var currentlyAppliedLayout: ContainerViewLayout? { @@ -117,6 +122,8 @@ open class ViewControllerPresentationArguments { return self.preferNavigationUIHidden } + open var navigationPresentation: ViewControllerNavigationPresentation = .default + public var presentationArguments: Any? public var tabBarItemDebugTapAction: (() -> Void)? @@ -236,6 +243,10 @@ open class ViewControllerPresentationArguments { return true } + open func preferredContentSizeForLayout(_ layout: ContainerViewLayout) -> CGSize? { + return nil + } + private func updateScrollToTopView() { if self.scrollToTop != nil { if let displayNode = self._displayNode , self.scrollToTopView == nil { @@ -267,9 +278,20 @@ open class ViewControllerPresentationArguments { self.navigationBar?.backPressed = { [weak self] in if let strongSelf = self, strongSelf.attemptNavigation({ - self?.navigationController?.popViewController(animated: true) + guard let strongSelf = self else { + return + } + if let navigationController = strongSelf.navigationController as? NavigationController { + navigationController.filterController(strongSelf, animated: true) + } else { + strongSelf.navigationController?.popViewController(animated: true) + } }) { - strongSelf.navigationController?.popViewController(animated: true) + if let navigationController = strongSelf.navigationController as? NavigationController { + navigationController.filterController(strongSelf, animated: true) + } else { + strongSelf.navigationController?.popViewController(animated: true) + } } } self.navigationBar?.requestContainerLayout = { [weak self] transition in diff --git a/submodules/GalleryUI/Sources/GalleryController.swift b/submodules/GalleryUI/Sources/GalleryController.swift index bcea3d7da7..a5b146553a 100644 --- a/submodules/GalleryUI/Sources/GalleryController.swift +++ b/submodules/GalleryUI/Sources/GalleryController.swift @@ -945,6 +945,14 @@ public class GalleryController: ViewController { self.accountInUseDisposable.set(nil) } + override public func preferredContentSizeForLayout(_ layout: ContainerViewLayout) -> CGSize? { + if let centralItemNode = self.galleryNode.pager.centralItemNode(), let itemSize = centralItemNode.contentSize() { + return itemSize.aspectFitted(layout.size) + } else { + return nil + } + } + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) @@ -955,7 +963,7 @@ public class GalleryController: ViewController { self.adjustedForInitialPreviewingLayout = true self.galleryNode.setControlsHidden(true, animated: false) if let centralItemNode = self.galleryNode.pager.centralItemNode(), let itemSize = centralItemNode.contentSize() { - self.preferredContentSize = itemSize.aspectFitted(self.view.bounds.size) + self.preferredContentSize = itemSize.aspectFitted(layout.size) self.containerLayoutUpdated(ContainerViewLayout(size: self.preferredContentSize, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: .immediate) } } diff --git a/submodules/TelegramUI/TelegramUI/TelegramRootController.swift b/submodules/TelegramUI/TelegramUI/TelegramRootController.swift index 50cff99aea..c0de067a68 100644 --- a/submodules/TelegramUI/TelegramUI/TelegramRootController.swift +++ b/submodules/TelegramUI/TelegramUI/TelegramRootController.swift @@ -63,8 +63,6 @@ public final class TelegramRootController: NavigationController { if previousTheme !== presentationData.theme { strongSelf.rootTabController?.updateTheme(navigationBarPresentationData: NavigationBarPresentationData(presentationData: presentationData), theme: TabBarControllerTheme(rootControllerTheme: presentationData.theme)) strongSelf.rootTabController?.statusBar.statusBarStyle = presentationData.theme.rootController.statusBarStyle.style - - } } }) @@ -81,6 +79,7 @@ public final class TelegramRootController: NavigationController { public func addRootControllers(showCallsTab: Bool) { let tabBarController = TabBarController(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), theme: TabBarControllerTheme(rootControllerTheme: self.presentationData.theme)) + tabBarController.navigationPresentation = .master let chatListController = self.context.sharedContext.makeChatListController(context: self.context, groupId: .root, controlsHistoryPreload: true, hideNetworkActivityStatus: false, enableDebugActions: !GlobalExperimentalSettings.isAppStoreBuild) if let sharedContext = self.context.sharedContext as? SharedAccountContextImpl { chatListController.tabBarItem.badgeValue = sharedContext.switchingData.chatListBadge From 73eaba0960c84f3e7e5b1f2419e450b9e0b22659 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 10 Sep 2019 00:05:04 +0400 Subject: [PATCH 15/16] Another reactions experiment --- .../project.pbxproj | 20 ++ .../MessageReactionListController.swift | 79 +++-- ...essageReactionListLoadingPlaceholder.swift | 80 +++++ .../project.pbxproj | 20 ++ .../Sources/ReactionAttachedNode.swift | 322 ++++++++++++++++++ .../Sources/ReactionSelectionNode.swift | 40 ++- .../Sources/ReactionSelectionParentNode.swift | 16 +- .../ReactionSwipeGestureRecognizer.swift | 23 +- .../TelegramCore/MessageReactionList.swift | 5 +- .../TelegramUI/TelegramUI/AppDelegate.swift | 3 + .../TelegramUI/ChatController.swift | 1 + .../TelegramUI/ChatControllerNode.swift | 8 +- .../ChatMessageBubbleItemNode.swift | 14 +- 13 files changed, 571 insertions(+), 60 deletions(-) create mode 100644 submodules/MessageReactionListUI/Sources/MessageReactionListLoadingPlaceholder.swift create mode 100644 submodules/ReactionSelectionNode/Sources/ReactionAttachedNode.swift diff --git a/submodules/MessageReactionListUI/MessageReactionListUI.xcodeproj/project.pbxproj b/submodules/MessageReactionListUI/MessageReactionListUI.xcodeproj/project.pbxproj index 436c007781..4343579642 100644 --- a/submodules/MessageReactionListUI/MessageReactionListUI.xcodeproj/project.pbxproj +++ b/submodules/MessageReactionListUI/MessageReactionListUI.xcodeproj/project.pbxproj @@ -587,6 +587,17 @@ sourceTree SOURCE_ROOT + 1DD70E29B0C2D20A00000000 + + isa + PBXFileReference + name + MessageReactionListLoadingPlaceholder.swift + path + Sources/MessageReactionListLoadingPlaceholder.swift + sourceTree + SOURCE_ROOT + B401C979EAB5339800000000 isa @@ -599,6 +610,7 @@ 1DD70E298DEA121500000000 1DD70E29C02D1D4F00000000 + 1DD70E29B0C2D20A00000000 B401C979639FD30200000000 @@ -671,6 +683,13 @@ fileRef 1DD70E29C02D1D4F00000000 + E7A30F04B0C2D20A00000000 + + isa + PBXBuildFile + fileRef + 1DD70E29B0C2D20A00000000 + 1870857F0000000000000000 isa @@ -679,6 +698,7 @@ E7A30F048DEA121500000000 E7A30F04C02D1D4F00000000 + E7A30F04B0C2D20A00000000 E7A30F04D65BA68200000000 diff --git a/submodules/MessageReactionListUI/Sources/MessageReactionListController.swift b/submodules/MessageReactionListUI/Sources/MessageReactionListController.swift index edcd28fd0e..40c7c8063d 100644 --- a/submodules/MessageReactionListUI/Sources/MessageReactionListController.swift +++ b/submodules/MessageReactionListUI/Sources/MessageReactionListController.swift @@ -11,7 +11,7 @@ import ItemListPeerItem public final class MessageReactionListController: ViewController { private let context: AccountContext private let messageId: MessageId - private let presentatonData: PresentationData + private let presentationData: PresentationData private let initialReactions: [MessageReaction] private var controllerNode: MessageReactionListControllerNode { @@ -28,7 +28,7 @@ public final class MessageReactionListController: ViewController { public init(context: AccountContext, messageId: MessageId, initialReactions: [MessageReaction]) { self.context = context self.messageId = messageId - self.presentatonData = context.sharedContext.currentPresentationData.with { $0 } + self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.initialReactions = initialReactions super.init(navigationBarPresentationData: nil) @@ -41,7 +41,7 @@ public final class MessageReactionListController: ViewController { } override public func loadDisplayNode() { - self.displayNode = MessageReactionListControllerNode(context: self.context, presentatonData: self.presentatonData, messageId: messageId, initialReactions: initialReactions, dismiss: { [weak self] in + self.displayNode = MessageReactionListControllerNode(context: self.context, presentationData: self.presentationData, messageId: messageId, initialReactions: initialReactions, dismiss: { [weak self] in self?.dismiss() }) @@ -113,14 +113,14 @@ private let itemHeight: CGFloat = 50.0 private func topInsetForLayout(layout: ContainerViewLayout, itemCount: Int) -> CGFloat { let contentHeight = CGFloat(itemCount) * itemHeight - let minimumItemHeights: CGFloat = contentHeight + let minimumItemHeights: CGFloat = max(contentHeight, itemHeight * 5.0) return max(layout.size.height - layout.intrinsicInsets.bottom - minimumItemHeights, headerHeight) } private final class MessageReactionListControllerNode: ViewControllerTracingNode { private let context: AccountContext - private let presentatonData: PresentationData + private let presentationData: PresentationData private let dismiss: () -> Void private let listContext: MessageReactionListContext @@ -129,9 +129,12 @@ private final class MessageReactionListControllerNode: ViewControllerTracingNode private let backgroundNode: ASDisplayNode private let contentHeaderContainerNode: ASDisplayNode private let contentHeaderContainerBackgroundNode: ASImageNode + private let contentHeaderContainerSeparatorNode: ASDisplayNode private var categoryItemNodes: [MessageReactionCategoryNode] = [] private let categoryScrollNode: ASScrollNode private let listNode: ListView + private var placeholderNode: MessageReactionListLoadingPlaceholder? + private var placeholderNodeIsAnimatingOut = false private var validLayout: ContainerViewLayout? @@ -146,26 +149,29 @@ private final class MessageReactionListControllerNode: ViewControllerTracingNode private var forceHeaderTransition: ContainedViewLayoutTransition? - init(context: AccountContext, presentatonData: PresentationData, messageId: MessageId, initialReactions: [MessageReaction], dismiss: @escaping () -> Void) { + init(context: AccountContext, presentationData: PresentationData, messageId: MessageId, initialReactions: [MessageReaction], dismiss: @escaping () -> Void) { self.context = context - self.presentatonData = presentatonData + self.presentationData = presentationData self.dismiss = dismiss self.dimNode = ASDisplayNode() self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) self.backgroundNode = ASDisplayNode() - self.backgroundNode.backgroundColor = self.presentatonData.theme.actionSheet.opaqueItemBackgroundColor + self.backgroundNode.backgroundColor = self.presentationData.theme.actionSheet.opaqueItemBackgroundColor self.contentHeaderContainerNode = ASDisplayNode() self.contentHeaderContainerBackgroundNode = ASImageNode() self.contentHeaderContainerBackgroundNode.displaysAsynchronously = false + self.contentHeaderContainerSeparatorNode = ASDisplayNode() + self.contentHeaderContainerSeparatorNode.backgroundColor = self.presentationData.theme.list.itemPlainSeparatorColor + self.categoryScrollNode = ASScrollNode() self.contentHeaderContainerBackgroundNode.displayWithoutProcessing = true self.contentHeaderContainerBackgroundNode.image = generateImage(CGSize(width: 10.0, height: 10.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(presentatonData.theme.rootController.navigationBar.backgroundColor.cgColor) + context.setFillColor(presentationData.theme.rootController.navigationBar.backgroundColor.cgColor) context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) context.fill(CGRect(origin: CGPoint(x: 0.0, y: size.height / 2.0), size: CGSize(width: size.width, height: size.height / 2.0))) })?.stretchableImage(withLeftCapWidth: 5, topCapHeight: 5) @@ -173,6 +179,9 @@ private final class MessageReactionListControllerNode: ViewControllerTracingNode self.listNode = ListView() self.listNode.limitHitTestToNodes = true + self.placeholderNode = MessageReactionListLoadingPlaceholder(theme: presentationData.theme, itemHeight: itemHeight) + self.placeholderNode?.isUserInteractionEnabled = false + self.listContext = MessageReactionListContext(postbox: self.context.account.postbox, network: self.context.account.network, messageId: messageId, initialReactions: initialReactions) super.init() @@ -182,9 +191,11 @@ private final class MessageReactionListControllerNode: ViewControllerTracingNode self.listNode.stackFromBottom = false self.addSubnode(self.listNode) + self.placeholderNode.flatMap(self.addSubnode) self.addSubnode(self.contentHeaderContainerNode) self.contentHeaderContainerNode.addSubnode(self.contentHeaderContainerBackgroundNode) + self.contentHeaderContainerNode.addSubnode(self.contentHeaderContainerSeparatorNode) self.contentHeaderContainerNode.addSubnode(self.categoryScrollNode) self.listNode.updateFloatingHeaderOffset = { [weak self] offset, listTransition in @@ -198,6 +209,10 @@ private final class MessageReactionListControllerNode: ViewControllerTracingNode let topOffset = offset transition.updateFrame(node: strongSelf.contentHeaderContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topOffset - headerHeight), size: CGSize(width: layout.size.width, height: headerHeight))) transition.updateFrame(node: strongSelf.contentHeaderContainerBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: headerHeight))) + transition.updateFrame(node: strongSelf.contentHeaderContainerSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: headerHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel))) + if let placeholderNode = strongSelf.placeholderNode { + transition.updateFrame(node: placeholderNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topOffset), size: placeholderNode.bounds.size)) + } transition.updateFrame(node: strongSelf.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topOffset - headerHeight / 2.0), size: CGSize(width: layout.size.width, height: layout.size.height + 300.0))) } @@ -223,11 +238,8 @@ private final class MessageReactionListControllerNode: ViewControllerTracingNode transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - //transition.updateBounds(node: self.listNode, bounds: CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)) - //transition.updatePosition(node: self.listNode, position: CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0)) - - self.listNode.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height) - self.listNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0) + transition.updateBounds(node: self.listNode, bounds: CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)) + transition.updatePosition(node: self.listNode, position: CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0)) var currentCategoryItemCount = 0 if let currentState = self.currentState { @@ -243,6 +255,12 @@ private final class MessageReactionListControllerNode: ViewControllerTracingNode insets.top = topInsetForLayout(layout: layout, itemCount: currentCategoryItemCount) insets.bottom = layout.intrinsicInsets.bottom + if let placeholderNode = self.placeholderNode, !self.placeholderNodeIsAnimatingOut { + let placeholderHeight = min(CGFloat(currentCategoryItemCount) * itemHeight, layout.size.height) + UIScreenPixel + placeholderNode.frame = CGRect(origin: placeholderNode.frame.origin, size: CGSize(width: layout.size.width, height: placeholderHeight)) + placeholderNode.updateLayout(size: CGSize(width: layout.size.width, height: placeholderHeight)) + } + var duration: Double = 0.0 var curve: UInt = 0 switch transition { @@ -322,7 +340,7 @@ private final class MessageReactionListControllerNode: ViewControllerTracingNode let states = self.currentState?.states ?? [] for (category, categoryState) in states { if self.categoryItemNodes.count <= index { - let itemNode = MessageReactionCategoryNode(theme: self.presentatonData.theme, category: category, count: categoryState.count, action: { [weak self] in + let itemNode = MessageReactionCategoryNode(theme: self.presentationData.theme, category: category, count: categoryState.count, action: { [weak self] in self?.setCategory(category) }) self.categoryItemNodes.append(itemNode) @@ -341,7 +359,7 @@ private final class MessageReactionListControllerNode: ViewControllerTracingNode } index += 1 } - let transaction = preparedTransition(from: self.currentEntries ?? [], to: entries, context: self.context, presentationData: self.presentatonData) + let transaction = preparedTransition(from: self.currentEntries ?? [], to: entries, context: self.context, presentationData: self.presentationData) let previousWasEmpty = self.currentEntries == nil || self.currentEntries?.count == 0 let isEmpty = entries.isEmpty self.currentEntries = entries @@ -350,7 +368,20 @@ private final class MessageReactionListControllerNode: ViewControllerTracingNode self.dequeueTransaction() if previousWasEmpty && !isEmpty { - self.listNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18) + if let placeholderNode = self.placeholderNode { + self.placeholderNodeIsAnimatingOut = true + placeholderNode.allowsGroupOpacity = true + placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak self] _ in + guard let strongSelf = self else { + return + } + strongSelf.placeholderNode?.removeFromSupernode() + strongSelf.placeholderNode = nil + }) + } + self.listNode.forEachItemNode({ itemNode in + itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18) + }) } } @@ -380,9 +411,8 @@ private final class MessageReactionListControllerNode: ViewControllerTracingNode var options = ListViewDeleteAndInsertOptions() options.insert(.Synchronous) - //options.insert(.AnimateTopItemPosition) - //options.insert(.AnimateCrossfade) options.insert(.PreferSynchronousResourceLoading) + options.insert(.PreferSynchronousDrawing) var currentCategoryItemCount = 0 if let currentState = self.currentState { @@ -417,6 +447,15 @@ private final class MessageReactionListControllerNode: ViewControllerTracingNode return result } } - return super.hitTest(point, with: event) + if let result = self.listNode.hitTest(self.view.convert(point, to: self.listNode.view), with: event) { + return result + } + if point.y >= self.contentHeaderContainerNode.frame.minY && point.y < self.bounds.height { + return self.listNode.view + } + if point.y >= 0 && point.y < self.contentHeaderContainerNode.frame.minY { + return self.dimNode.view + } + return nil } } diff --git a/submodules/MessageReactionListUI/Sources/MessageReactionListLoadingPlaceholder.swift b/submodules/MessageReactionListUI/Sources/MessageReactionListLoadingPlaceholder.swift new file mode 100644 index 0000000000..676947eb9c --- /dev/null +++ b/submodules/MessageReactionListUI/Sources/MessageReactionListLoadingPlaceholder.swift @@ -0,0 +1,80 @@ +import Foundation +import AsyncDisplayKit +import Display +import TelegramPresentationData +import TelegramCore + +final class MessageReactionListLoadingPlaceholder: ASDisplayNode { + private let theme: PresentationTheme + private let itemHeight: CGFloat + private let itemImage: UIImage? + + private let backgroundNode: ASDisplayNode + private let separatorNode: ASDisplayNode + private let highlightNode: ASImageNode + private var itemNodes: [ASImageNode] = [] + + init(theme: PresentationTheme, itemHeight: CGFloat) { + self.theme = theme + self.itemHeight = itemHeight + + self.backgroundNode = ASDisplayNode() + self.backgroundNode.backgroundColor = UIColor(white: 0.92, alpha: 1.0) + + self.separatorNode = ASDisplayNode() + self.separatorNode.backgroundColor = theme.list.itemPlainSeparatorColor + + self.highlightNode = ASImageNode() + self.highlightNode.displaysAsynchronously = false + self.highlightNode.displayWithoutProcessing = true + + let leftInset: CGFloat = 15.0 + let avatarSize: CGFloat = 40.0 + let avatarSpacing: CGFloat = 11.0 + let contentWidth: CGFloat = 4.0 + let contentHeight: CGFloat = 14.0 + let rightInset: CGFloat = 54.0 + self.itemImage = generateImage(CGSize(width: leftInset + avatarSize + avatarSpacing + contentWidth + rightInset, height: itemHeight), rotatedContext: { size, context in + context.setFillColor(theme.actionSheet.opaqueItemBackgroundColor.cgColor) + context.fill(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(theme.list.itemPlainSeparatorColor.cgColor) + context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: UIScreenPixel))) + context.setBlendMode(.copy) + context.setFillColor(UIColor.clear.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: leftInset, y: floor((itemHeight - avatarSize) / 2.0)), size: CGSize(width: avatarSize, height: avatarSize))) + let contentOrigin = leftInset + avatarSize + avatarSpacing + context.fill(CGRect(origin: CGPoint(x: contentOrigin, y: floor((size.height - contentHeight) / 2.0)), size: CGSize(width: size.width - contentOrigin - rightInset, height: contentHeight))) + })?.stretchableImage(withLeftCapWidth: Int(leftInset + avatarSize + avatarSpacing + 1), topCapHeight: 0) + + super.init() + + self.addSubnode(self.backgroundNode) + self.addSubnode(self.highlightNode) + self.addSubnode(self.separatorNode) + } + + func updateLayout(size: CGSize) { + self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size) + + var verticalOffset: CGFloat = 0.0 + var index = 0 + while verticalOffset < size.height - 1.0 { + if self.itemNodes.count >= index { + let itemNode = ASImageNode() + itemNode.image = self.itemImage + self.itemNodes.append(itemNode) + self.addSubnode(itemNode) + } + self.itemNodes[index].frame = CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: CGSize(width: size.width, height: self.itemHeight)) + verticalOffset += self.itemHeight + index += 1 + } + self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: CGSize(width: size.width, height: UIScreenPixel)) + if index < self.itemNodes.count { + for i in index ..< self.itemNodes.count { + self.itemNodes[i].removeFromSupernode() + } + self.itemNodes.removeLast(self.itemNodes.count - index) + } + } +} diff --git a/submodules/ReactionSelectionNode/ReactionSelectionNode.xcodeproj/project.pbxproj b/submodules/ReactionSelectionNode/ReactionSelectionNode.xcodeproj/project.pbxproj index b61bbac9d2..5e0a81a077 100644 --- a/submodules/ReactionSelectionNode/ReactionSelectionNode.xcodeproj/project.pbxproj +++ b/submodules/ReactionSelectionNode/ReactionSelectionNode.xcodeproj/project.pbxproj @@ -283,6 +283,17 @@ explicitFileType text.script.python + 1DD70E2982B4C61400000000 + + isa + PBXFileReference + name + ReactionAttachedNode.swift + path + Sources/ReactionAttachedNode.swift + sourceTree + SOURCE_ROOT + 1DD70E29CCCE0EED00000000 isa @@ -348,6 +359,7 @@ ]]> children + 1DD70E2982B4C61400000000 1DD70E29CCCE0EED00000000 1DD70E2930A5FA5800000000 1DD70E29691388CA00000000 @@ -385,6 +397,13 @@ B401C979E09F1DE500000000 + E7A30F0482B4C61400000000 + + isa + PBXBuildFile + fileRef + 1DD70E2982B4C61400000000 + E7A30F04CCCE0EED00000000 isa @@ -426,6 +445,7 @@ PBXSourcesBuildPhase files + E7A30F0482B4C61400000000 E7A30F04CCCE0EED00000000 E7A30F0430A5FA5800000000 E7A30F04691388CA00000000 diff --git a/submodules/ReactionSelectionNode/Sources/ReactionAttachedNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionAttachedNode.swift new file mode 100644 index 0000000000..466a1bed6f --- /dev/null +++ b/submodules/ReactionSelectionNode/Sources/ReactionAttachedNode.swift @@ -0,0 +1,322 @@ +import Foundation +import AsyncDisplayKit +import AnimationUI +import Display +import Postbox +import TelegramCore +import TelegramPresentationData +import AppBundle + +private func generateBubbleImage(foreground: UIColor, diameter: CGFloat, shadowBlur: CGFloat) -> UIImage? { + return generateImage(CGSize(width: diameter + shadowBlur * 2.0, height: diameter + shadowBlur * 2.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(foreground.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter))) + })?.stretchableImage(withLeftCapWidth: Int(diameter / 2.0 + shadowBlur / 2.0), topCapHeight: Int(diameter / 2.0 + shadowBlur / 2.0)) +} + +private func generateBubbleShadowImage(shadow: UIColor, diameter: CGFloat, shadowBlur: CGFloat) -> UIImage? { + return generateImage(CGSize(width: diameter + shadowBlur * 2.0, height: diameter + shadowBlur * 2.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(UIColor.white.cgColor) + context.setShadow(offset: CGSize(), blur: shadowBlur, color: shadow.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter))) + context.setShadow(offset: CGSize(), blur: 1.0, color: shadow.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter))) + context.setFillColor(UIColor.clear.cgColor) + context.setBlendMode(.copy) + context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter))) + })?.stretchableImage(withLeftCapWidth: Int(diameter / 2.0 + shadowBlur / 2.0), topCapHeight: Int(diameter / 2.0 + shadowBlur / 2.0)) +} + +final class ReactionAttachedNode: ASDisplayNode { + private let account: Account + private let theme: PresentationTheme + private let reactions: [ReactionGestureItem] + + private let backgroundNode: ASImageNode + private let backgroundShadowNode: ASImageNode + private let bubbleNodes: [(ASImageNode, ASImageNode)] + private var reactionNodes: [ReactionNode] = [] + private var hasSelectedNode = false + + private let hapticFeedback = HapticFeedback() + + private var shadowBlur: CGFloat = 8.0 + private var minimizedReactionSize: CGFloat = 30.0 + private var maximizedReactionSize: CGFloat = 60.0 + private var smallCircleSize: CGFloat = 8.0 + + public init(account: Account, theme: PresentationTheme, reactions: [ReactionGestureItem]) { + self.account = account + self.theme = theme + self.reactions = reactions + + self.backgroundNode = ASImageNode() + self.backgroundNode.displaysAsynchronously = false + self.backgroundNode.displayWithoutProcessing = true + + self.backgroundShadowNode = ASImageNode() + self.backgroundShadowNode.displaysAsynchronously = false + self.backgroundShadowNode.displayWithoutProcessing = true + + self.bubbleNodes = (0 ..< 2).map { i -> (ASImageNode, ASImageNode) in + let imageNode = ASImageNode() + imageNode.displaysAsynchronously = false + imageNode.displayWithoutProcessing = true + + let shadowNode = ASImageNode() + shadowNode.displaysAsynchronously = false + shadowNode.displayWithoutProcessing = true + + return (imageNode, shadowNode) + } + + super.init() + + self.bubbleNodes.forEach { _, shadow in + self.addSubnode(shadow) + } + self.addSubnode(self.backgroundShadowNode) + self.bubbleNodes.forEach { foreground, _ in + self.addSubnode(foreground) + } + self.addSubnode(self.backgroundNode) + } + + func updateLayout(constrainedSize: CGSize, startingPoint: CGPoint, offsetFromStart: CGFloat, isInitial: Bool) { + let initialAnchorX = startingPoint.x + + if isInitial && self.reactionNodes.isEmpty { + let availableContentWidth = constrainedSize.width + var minimizedReactionSize = (availableContentWidth - self.maximizedReactionSize) / (CGFloat(self.reactions.count - 1) + CGFloat(self.reactions.count + 1) * 0.2) + minimizedReactionSize = max(16.0, floor(minimizedReactionSize)) + minimizedReactionSize = min(30.0, minimizedReactionSize) + + self.minimizedReactionSize = minimizedReactionSize + self.shadowBlur = floor(minimizedReactionSize * 0.26) + self.smallCircleSize = 8.0 + + let backgroundHeight = floor(minimizedReactionSize * 1.4) + + self.backgroundNode.image = generateBubbleImage(foreground: .white, diameter: backgroundHeight, shadowBlur: self.shadowBlur) + self.backgroundShadowNode.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: backgroundHeight, shadowBlur: self.shadowBlur) + for i in 0 ..< self.bubbleNodes.count { + self.bubbleNodes[i].0.image = generateBubbleImage(foreground: .white, diameter: CGFloat(i + 1) * self.smallCircleSize, shadowBlur: self.shadowBlur) + self.bubbleNodes[i].1.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: CGFloat(i + 1) * self.smallCircleSize, shadowBlur: self.shadowBlur) + } + + self.reactionNodes = self.reactions.map { reaction -> ReactionNode in + return ReactionNode(account: self.account, theme: self.theme, reaction: reaction, maximizedReactionSize: self.maximizedReactionSize, loadFirstFrame: true) + } + self.reactionNodes.forEach(self.addSubnode(_:)) + } + + let backgroundHeight: CGFloat = floor(self.minimizedReactionSize * 1.4) + + let reactionSpacing: CGFloat = floor(self.minimizedReactionSize * 0.2) + let minimizedReactionVerticalInset: CGFloat = floor((backgroundHeight - minimizedReactionSize) / 2.0) + + let contentWidth: CGFloat = CGFloat(self.reactionNodes.count - 1) * (minimizedReactionSize) + maximizedReactionSize + CGFloat(self.reactionNodes.count + 1) * reactionSpacing + + var backgroundFrame = CGRect(origin: CGPoint(x: -shadowBlur, y: -shadowBlur), size: CGSize(width: contentWidth + shadowBlur * 2.0, height: backgroundHeight + shadowBlur * 2.0)) + backgroundFrame = backgroundFrame.offsetBy(dx: initialAnchorX - contentWidth + backgroundHeight / 2.0, dy: startingPoint.y - backgroundHeight - 16.0) + backgroundFrame.origin.x = max(0.0, backgroundFrame.minX) + backgroundFrame.origin.x = min(constrainedSize.width - backgroundFrame.width, backgroundFrame.minX) + + self.backgroundNode.frame = backgroundFrame + self.backgroundShadowNode.frame = backgroundFrame + + let anchorMinX = backgroundFrame.minX + shadowBlur + backgroundHeight / 2.0 + let anchorMaxX = backgroundFrame.maxX - shadowBlur - backgroundHeight / 2.0 + let anchorX = max(anchorMinX, min(anchorMaxX, offsetFromStart)) + + var reactionX: CGFloat = backgroundFrame.minX + shadowBlur + reactionSpacing + if offsetFromStart > backgroundFrame.maxX - shadowBlur || offsetFromStart < backgroundFrame.minX { + self.hasSelectedNode = false + } else { + self.hasSelectedNode = true + } + + var maximizedIndex = Int(((anchorX - anchorMinX) / (anchorMaxX - anchorMinX)) * CGFloat(self.reactionNodes.count)) + maximizedIndex = max(0, min(self.reactionNodes.count - 1, maximizedIndex)) + + for iterationIndex in 0 ..< self.reactionNodes.count { + var i = iterationIndex + let isMaximized = i == maximizedIndex + + let reactionSize: CGFloat + if isMaximized { + reactionSize = maximizedReactionSize + } else { + reactionSize = minimizedReactionSize + } + + let transition: ContainedViewLayoutTransition + if isInitial { + transition = .immediate + } else { + transition = .animated(duration: 0.18, curve: .easeInOut) + } + + if self.reactionNodes[i].isMaximized != isMaximized { + self.reactionNodes[i].isMaximized = isMaximized + self.reactionNodes[i].updateIsAnimating(isMaximized, animated: !isInitial) + if isMaximized && !isInitial { + self.hapticFeedback.tap() + } + } + + var reactionFrame = CGRect(origin: CGPoint(x: reactionX, y: backgroundFrame.maxY - shadowBlur - minimizedReactionVerticalInset - reactionSize), size: CGSize(width: reactionSize, height: reactionSize)) + if isMaximized { + reactionFrame.origin.x -= 9.0 + reactionFrame.size.width += 18.0 + } + self.reactionNodes[i].updateLayout(size: reactionFrame.size, scale: reactionFrame.size.width / (maximizedReactionSize + 18.0), transition: transition, displayText: isMaximized) + + transition.updateFrame(node: self.reactionNodes[i], frame: reactionFrame, beginWithCurrentState: true) + + reactionX += reactionSize + reactionSpacing + } + + let mainBubbleFrame = CGRect(origin: CGPoint(x: anchorX - self.smallCircleSize - shadowBlur, y: backgroundFrame.maxY - shadowBlur - self.smallCircleSize - shadowBlur), size: CGSize(width: self.smallCircleSize * 2.0 + shadowBlur * 2.0, height: self.smallCircleSize * 2.0 + shadowBlur * 2.0)) + self.bubbleNodes[1].0.frame = mainBubbleFrame + self.bubbleNodes[1].1.frame = mainBubbleFrame + + let secondaryBubbleFrame = CGRect(origin: CGPoint(x: mainBubbleFrame.midX - 10.0 - (self.smallCircleSize + shadowBlur * 2.0) / 2.0, y: mainBubbleFrame.midY + 10.0 - (self.smallCircleSize + shadowBlur * 2.0) / 2.0), size: CGSize(width: self.smallCircleSize + shadowBlur * 2.0, height: self.smallCircleSize + shadowBlur * 2.0)) + self.bubbleNodes[0].0.frame = secondaryBubbleFrame + self.bubbleNodes[0].1.frame = secondaryBubbleFrame + } + + func animateIn() { + self.bubbleNodes[1].0.layer.animateScale(from: 0.01, to: 1.0, duration: 0.11, delay: 0.0, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue) + self.bubbleNodes[1].1.layer.animateScale(from: 0.01, to: 1.0, duration: 0.11, delay: 0.0, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue) + + self.bubbleNodes[0].0.layer.animateScale(from: 0.01, to: 1.0, duration: 0.11, delay: 0.05, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue) + self.bubbleNodes[0].1.layer.animateScale(from: 0.01, to: 1.0, duration: 0.11, delay: 0.05, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue) + + let backgroundOffset = CGPoint(x: -(self.backgroundNode.frame.width - shadowBlur) / 2.0 + 42.0, y: (self.backgroundNode.frame.height - shadowBlur) / 2.0) + let damping: CGFloat = 100.0 + + for i in 0 ..< self.reactionNodes.count { + let animationOffset: Double = 1.0 - Double(i) / Double(self.reactionNodes.count - 1) + var nodeOffset = CGPoint(x: self.reactionNodes[i].frame.minX - (self.backgroundNode.frame.minX + shadowBlur) / 2.0 - 42.0, y: self.reactionNodes[i].frame.minY - self.backgroundNode.frame.maxY - shadowBlur) + nodeOffset.x = -nodeOffset.x + nodeOffset.y = 30.0 + self.reactionNodes[i].layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5 + animationOffset * 0.28, initialVelocity: 0.0, damping: damping) + self.reactionNodes[i].layer.animateSpring(from: NSValue(cgPoint: nodeOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.5, initialVelocity: 0.0, damping: damping, additive: true) + } + + self.backgroundNode.layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, initialVelocity: 0.0, damping: damping) + self.backgroundNode.layer.animateSpring(from: NSValue(cgPoint: backgroundOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.5, initialVelocity: 0.0, damping: damping, additive: true) + self.backgroundShadowNode.layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, initialVelocity: 0.0, damping: damping) + self.backgroundShadowNode.layer.animateSpring(from: NSValue(cgPoint: backgroundOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.5, initialVelocity: 0.0, damping: damping, additive: true) + } + + func animateOut(into targetNode: ASImageNode?, hideTarget: Bool, completion: @escaping () -> Void) { + self.hapticFeedback.prepareTap() + + var completedContainer = false + var completedTarget = true + + let intermediateCompletion: () -> Void = { + if completedContainer && completedTarget { + completion() + } + } + + if let targetNode = targetNode { + for i in 0 ..< self.reactionNodes.count { + if let isMaximized = self.reactionNodes[i].isMaximized, isMaximized { + if let snapshotView = self.reactionNodes[i].view.snapshotContentTree() { + let targetSnapshotView = UIImageView() + targetSnapshotView.image = targetNode.image + targetSnapshotView.frame = self.view.convert(targetNode.bounds, from: targetNode.view) + self.reactionNodes[i].isHidden = true + self.view.addSubview(targetSnapshotView) + self.view.addSubview(snapshotView) + completedTarget = false + let targetPosition = self.view.convert(targetNode.bounds.center, from: targetNode.view) + let duration: Double = 0.3 + if hideTarget { + targetNode.isHidden = true + } + + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) + targetSnapshotView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + targetSnapshotView.layer.animateScale(from: snapshotView.bounds.width / targetSnapshotView.bounds.width, to: 0.5, duration: 0.3, removeOnCompletion: false) + + + let sourcePoint = snapshotView.center + let midPoint = CGPoint(x: (sourcePoint.x + targetPosition.x) / 2.0, y: sourcePoint.y - 30.0) + + let x1 = sourcePoint.x + let y1 = sourcePoint.y + let x2 = midPoint.x + let y2 = midPoint.y + let x3 = targetPosition.x + let y3 = targetPosition.y + + let a = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / ((x1 - x2) * (x1 - x3) * (x2 - x3)) + let b = (x1 * x1 * (y2 - y3) + x3 * x3 * (y1 - y2) + x2 * x2 * (y3 - y1)) / ((x1 - x2) * (x1 - x3) * (x2 - x3)) + let c = (x2 * x2 * (x3 * y1 - x1 * y3) + x2 * (x1 * x1 * y3 - x3 * x3 * y1) + x1 * x3 * (x3 - x1) * y2) / ((x1 - x2) * (x1 - x3) * (x2 - x3)) + + var keyframes: [AnyObject] = [] + for i in 0 ..< 10 { + let k = CGFloat(i) / CGFloat(10 - 1) + let x = sourcePoint.x * (1.0 - k) + targetPosition.x * k + let y = a * x * x + b * x + c + keyframes.append(NSValue(cgPoint: CGPoint(x: x, y: y))) + } + + snapshotView.layer.animateKeyframes(values: keyframes, duration: 0.3, keyPath: "position", removeOnCompletion: false, completion: { [weak self] _ in + if let strongSelf = self { + strongSelf.hapticFeedback.tap() + } + completedTarget = true + if hideTarget { + targetNode.isHidden = false + targetNode.layer.animateSpring(from: 0.5 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: duration, initialVelocity: 0.0, damping: 90.0) + } + intermediateCompletion() + }) + targetSnapshotView.layer.animateKeyframes(values: keyframes, duration: 0.3, keyPath: "position", removeOnCompletion: false) + + snapshotView.layer.animateScale(from: 1.0, to: (targetSnapshotView.bounds.width * 0.5) / snapshotView.bounds.width, duration: 0.3, removeOnCompletion: false) + } + break + } + } + } + + self.backgroundNode.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false) + self.backgroundShadowNode.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false) + self.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + self.backgroundShadowNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in + completedContainer = true + intermediateCompletion() + }) + for (node, shadow) in self.bubbleNodes { + node.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false) + node.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + shadow.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false) + shadow.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + } + for i in 0 ..< self.reactionNodes.count { + self.reactionNodes[i].layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false) + self.reactionNodes[i].layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + } + } + + func selectedReaction() -> ReactionGestureItem? { + if !self.hasSelectedNode { + return nil + } + for i in 0 ..< self.reactionNodes.count { + if let isMaximized = self.reactionNodes[i].isMaximized, isMaximized { + return self.reactionNodes[i].reaction + } + } + return nil + } +} diff --git a/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift index 8ebb44a1f8..6a3be9ecf7 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift @@ -227,16 +227,16 @@ final class ReactionSelectionNode: ASDisplayNode { super.init() self.bubbleNodes.forEach { _, shadow in - self.addSubnode(shadow) + //self.addSubnode(shadow) } self.addSubnode(self.backgroundShadowNode) self.bubbleNodes.forEach { foreground, _ in - self.addSubnode(foreground) + //self.addSubnode(foreground) } self.addSubnode(self.backgroundNode) } - func updateLayout(constrainedSize: CGSize, startingPoint: CGPoint, offsetFromStart: CGFloat, isInitial: Bool) { + func updateLayout(constrainedSize: CGSize, startingPoint: CGPoint, offsetFromStart: CGFloat, isInitial: Bool, touchPoint: CGPoint) { let initialAnchorX = startingPoint.x var isRightAligned = false @@ -285,23 +285,40 @@ final class ReactionSelectionNode: ASDisplayNode { backgroundFrame.origin.x = max(0.0, backgroundFrame.minX) backgroundFrame.origin.x = min(constrainedSize.width - backgroundFrame.width, backgroundFrame.minX) - self.isRightAligned = isRightAligned - self.backgroundNode.frame = backgroundFrame - self.backgroundShadowNode.frame = backgroundFrame - let anchorMinX = backgroundFrame.minX + shadowBlur + backgroundHeight / 2.0 let anchorMaxX = backgroundFrame.maxX - shadowBlur - backgroundHeight / 2.0 let anchorX = max(anchorMinX, min(anchorMaxX, offsetFromStart)) + var maximizedIndex = -1 + if let reaction = self.reactions.last, case .reply = reaction { + maximizedIndex = self.reactions.count - 1 + } + if backgroundFrame.insetBy(dx: -10.0, dy: -10.0).contains(touchPoint) { + maximizedIndex = Int(((touchPoint.x - anchorMinX) / (anchorMaxX - anchorMinX)) * CGFloat(self.reactionNodes.count)) + maximizedIndex = max(0, min(self.reactionNodes.count - 1, maximizedIndex)) + } + if maximizedIndex == -1 { + backgroundFrame.size.width -= maximizedReactionSize - minimizedReactionSize + backgroundFrame.origin.x += maximizedReactionSize - minimizedReactionSize + } + + self.isRightAligned = isRightAligned + + let backgroundTransition: ContainedViewLayoutTransition + if isInitial { + backgroundTransition = .immediate + } else { + backgroundTransition = .animated(duration: 0.18, curve: .easeInOut) + } + backgroundTransition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) + backgroundTransition.updateFrame(node: self.backgroundShadowNode, frame: backgroundFrame) + var reactionX: CGFloat = backgroundFrame.minX + shadowBlur + reactionSpacing if offsetFromStart > backgroundFrame.maxX - shadowBlur || offsetFromStart < backgroundFrame.minX { self.hasSelectedNode = false } else { self.hasSelectedNode = true } - - var maximizedIndex = Int(((anchorX - anchorMinX) / (anchorMaxX - anchorMinX)) * CGFloat(self.reactionNodes.count)) - maximizedIndex = max(0, min(self.reactionNodes.count - 1, maximizedIndex)) for iterationIndex in 0 ..< self.reactionNodes.count { var i = iterationIndex @@ -484,9 +501,6 @@ final class ReactionSelectionNode: ASDisplayNode { } func selectedReaction() -> ReactionGestureItem? { - if !self.hasSelectedNode { - return nil - } for i in 0 ..< self.reactionNodes.count { if let isMaximized = self.reactionNodes[i].isMaximized, isMaximized { return self.reactionNodes[i].reaction diff --git a/submodules/ReactionSelectionNode/Sources/ReactionSelectionParentNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionSelectionParentNode.swift index 02719c5884..8290a9a4b1 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionSelectionParentNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionSelectionParentNode.swift @@ -10,7 +10,7 @@ public final class ReactionSelectionParentNode: ASDisplayNode { private let theme: PresentationTheme private var currentNode: ReactionSelectionNode? - private var currentLocation: (CGPoint, CGFloat)? + private var currentLocation: (CGPoint, CGFloat, CGPoint)? private var validLayout: (size: CGSize, insets: UIEdgeInsets)? @@ -21,7 +21,7 @@ public final class ReactionSelectionParentNode: ASDisplayNode { super.init() } - func displayReactions(_ reactions: [ReactionGestureItem], at point: CGPoint) { + func displayReactions(_ reactions: [ReactionGestureItem], at point: CGPoint, touchPoint: CGPoint) { if let currentNode = self.currentNode { currentNode.removeFromSupernode() self.currentNode = nil @@ -30,7 +30,7 @@ public final class ReactionSelectionParentNode: ASDisplayNode { let reactionNode = ReactionSelectionNode(account: self.account, theme: self.theme, reactions: reactions) self.addSubnode(reactionNode) self.currentNode = reactionNode - self.currentLocation = (point, point.x) + self.currentLocation = (point, point.x, touchPoint) if let (size, insets) = self.validLayout { self.update(size: size, insets: insets, isInitial: true) @@ -55,9 +55,9 @@ public final class ReactionSelectionParentNode: ASDisplayNode { } } - func updateReactionsAnchor(point: CGPoint) { - if let (currentPoint, _) = self.currentLocation { - self.currentLocation = (currentPoint, point.x) + func updateReactionsAnchor(point: CGPoint, touchPoint: CGPoint) { + if let (currentPoint, _, _) = self.currentLocation { + self.currentLocation = (currentPoint, point.x, touchPoint) if let (size, insets) = self.validLayout { self.update(size: size, insets: insets, isInitial: false) @@ -72,8 +72,8 @@ public final class ReactionSelectionParentNode: ASDisplayNode { } private func update(size: CGSize, insets: UIEdgeInsets, isInitial: Bool) { - if let currentNode = self.currentNode, let (point, offset) = currentLocation { - currentNode.updateLayout(constrainedSize: size, startingPoint: point, offsetFromStart: offset, isInitial: isInitial) + if let currentNode = self.currentNode, let (point, offset, touchPoint) = self.currentLocation { + currentNode.updateLayout(constrainedSize: size, startingPoint: CGPoint(x: size.width - 32.0, y: point.y), offsetFromStart: offset, isInitial: isInitial, touchPoint: touchPoint) currentNode.frame = CGRect(origin: CGPoint(), size: size) } } diff --git a/submodules/ReactionSelectionNode/Sources/ReactionSwipeGestureRecognizer.swift b/submodules/ReactionSelectionNode/Sources/ReactionSwipeGestureRecognizer.swift index ceb67815f1..f82c3df64f 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionSwipeGestureRecognizer.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionSwipeGestureRecognizer.swift @@ -15,12 +15,16 @@ public final class ReactionSwipeGestureRecognizer: UIPanGestureRecognizer { public var availableReactions: (() -> [ReactionGestureItem])? public var getReactionContainer: (() -> ReactionSelectionParentNode?)? + public var getAnchorPoint: (() -> CGPoint?)? public var began: (() -> Void)? public var updateOffset: ((CGFloat, Bool) -> Void)? public var completed: ((ReactionGestureItem?) -> Void)? public var displayReply: ((CGFloat) -> Void)? public var activateReply: (() -> Void)? + private var currentAnchorPoint: CGPoint? + private var currentAnchorStartPoint: CGPoint? + override public init(target: Any?, action: Selector?) { super.init(target: target, action: action) @@ -95,17 +99,20 @@ public final class ReactionSwipeGestureRecognizer: UIPanGestureRecognizer { self.f() } } - let activationTimer = Timer(timeInterval: 0.3, target: TimerTarget { [weak self] in + let activationTimer = Timer(timeInterval: 0.1, target: TimerTarget { [weak self] in guard let strongSelf = self else { return } strongSelf.activationTimer = nil if strongSelf.validatedGesture { let location = strongSelf.currentLocation - if !strongSelf.currentReactions.isEmpty, let reactionContainer = strongSelf.getReactionContainer?() { + if !strongSelf.currentReactions.isEmpty, let reactionContainer = strongSelf.getReactionContainer?(), let localAnchorPoint = strongSelf.getAnchorPoint?() { strongSelf.currentContainer = reactionContainer - let reactionContainerLocation = reactionContainer.view.convert(location, from: nil) - reactionContainer.displayReactions(strongSelf.currentReactions, at: reactionContainerLocation) + let reactionContainerLocation = reactionContainer.view.convert(localAnchorPoint, from: strongSelf.view) + let reactionContainerTouchPoint = reactionContainer.view.convert(location, from: nil) + strongSelf.currentAnchorPoint = reactionContainerLocation + strongSelf.currentAnchorStartPoint = location + reactionContainer.displayReactions(strongSelf.currentReactions, at: reactionContainerLocation, touchPoint: reactionContainerTouchPoint) } } }, selector: #selector(TimerTarget.event), userInfo: nil, repeats: false) @@ -124,9 +131,11 @@ public final class ReactionSwipeGestureRecognizer: UIPanGestureRecognizer { self.displayReply?(-min(0.0, translation.x)) } } else { - if let reactionContainer = self.currentContainer { - let reactionContainerLocation = reactionContainer.view.convert(location, from: nil) - reactionContainer.updateReactionsAnchor(point: reactionContainerLocation) + if let reactionContainer = self.currentContainer, let currentAnchorPoint = self.currentAnchorPoint, let currentAnchorStartPoint = self.currentAnchorStartPoint { + let anchorPoint = CGPoint(x: currentAnchorPoint.x + location.x - currentAnchorStartPoint.x, y: currentAnchorPoint.y) + let reactionContainerLocation = anchorPoint + let reactionContainerTouchPoint = reactionContainer.view.convert(location, from: nil) + reactionContainer.updateReactionsAnchor(point: reactionContainerLocation, touchPoint: reactionContainerTouchPoint) } } super.touchesMoved(touches, with: event) diff --git a/submodules/TelegramCore/TelegramCore/MessageReactionList.swift b/submodules/TelegramCore/TelegramCore/MessageReactionList.swift index 85a6b34d8b..bbae3101af 100644 --- a/submodules/TelegramCore/TelegramCore/MessageReactionList.swift +++ b/submodules/TelegramCore/TelegramCore/MessageReactionList.swift @@ -93,7 +93,7 @@ private final class MessageReactionCategoryContext { } let messageId = self.messageId let offset = self.state.nextOffset - let request = self.postbox.transaction { transaction -> Api.InputPeer? in + var request = self.postbox.transaction { transaction -> Api.InputPeer? in let inputPeer = transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) return inputPeer } @@ -107,6 +107,9 @@ private final class MessageReactionCategoryContext { return .generic } } + //#if DEBUG + request = request |> delay(1.0, queue: .mainQueue()) + //#endif self.loadingDisposable.set((request |> deliverOnMainQueue).start(next: { [weak self] result in guard let strongSelf = self else { diff --git a/submodules/TelegramUI/TelegramUI/AppDelegate.swift b/submodules/TelegramUI/TelegramUI/AppDelegate.swift index c86dc53093..1f3a2fce50 100644 --- a/submodules/TelegramUI/TelegramUI/AppDelegate.swift +++ b/submodules/TelegramUI/TelegramUI/AppDelegate.swift @@ -1185,7 +1185,10 @@ final class SharedApplicationContext { BITHockeyManager.shared().configure(withIdentifier: hockeyAppId, delegate: self) BITHockeyManager.shared().crashManager.crashManagerStatus = .alwaysAsk BITHockeyManager.shared().start() + #if targetEnvironment(simulator) + #else BITHockeyManager.shared().authenticator.authenticateInstallation() + #endif } NotificationCenter.default.addObserver(forName: UIWindow.didBecomeHiddenNotification, object: nil, queue: nil, using: { notification in diff --git a/submodules/TelegramUI/TelegramUI/ChatController.swift b/submodules/TelegramUI/TelegramUI/ChatController.swift index 77a896f042..269b64f0f3 100644 --- a/submodules/TelegramUI/TelegramUI/ChatController.swift +++ b/submodules/TelegramUI/TelegramUI/ChatController.swift @@ -1667,6 +1667,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if !initialReactions.isEmpty { + strongSelf.chatDisplayNode.dismissInput() strongSelf.present(MessageReactionListController(context: strongSelf.context, messageId: message.id, initialReactions: initialReactions), in: .window(.root)) } }) diff --git a/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift b/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift index 4fd2a80d66..b8c3db0ce6 100644 --- a/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift @@ -1045,13 +1045,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { let previousInputPanelBackgroundFrame = self.inputPanelBackgroundNode.frame transition.updateFrame(node: self.inputPanelBackgroundNode, frame: apparentInputBackgroundFrame) - let separatorOffset: CGFloat - if apparentInputBackgroundFrame.maxY >= layout.size.height { - separatorOffset = UIScreenPixel - } else { - separatorOffset = -UIScreenPixel - } - transition.updateFrame(node: self.inputPanelBackgroundSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: apparentInputBackgroundFrame.origin.y + separatorOffset), size: CGSize(width: apparentInputBackgroundFrame.size.width, height: UIScreenPixel))) + transition.updateFrame(node: self.inputPanelBackgroundSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: apparentInputBackgroundFrame.origin.y), size: CGSize(width: apparentInputBackgroundFrame.size.width, height: UIScreenPixel))) transition.updateFrame(node: self.navigateButtons, frame: apparentNavigateButtonsFrame) if let titleAccessoryPanelNode = self.titleAccessoryPanelNode, let titleAccessoryPanelFrame = titleAccessoryPanelFrame, !titleAccessoryPanelNode.frame.equalTo(titleAccessoryPanelFrame) { diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift index 2a474aa1ae..087e68358e 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift @@ -428,7 +428,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode } let reactions: [(String, String, String)] = [ - ("😒", "Sad", "sad"), + ("😔", "Sad", "sad"), ("😳", "Surprised", "surprised"), ("😂", "Fun", "lol"), ("👍", "Like", "thumbsup"), @@ -436,19 +436,25 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode ] var reactionItems: [ReactionGestureItem] = [] - for (value, text, name) in reactions { + for (value, text, name) in reactions.reversed() { if let path = getAppBundle().path(forResource: name, ofType: "tgs") { reactionItems.append(.reaction(value: value, text: text, path: path)) } } if item.controllerInteraction.canSetupReply(item.message) { - reactionItems.append(.reply) + //reactionItems.append(.reply) } return reactionItems } reactionRecognizer.getReactionContainer = { [weak self] in return self?.item?.controllerInteraction.reactionContainerNode() } + reactionRecognizer.getAnchorPoint = { [weak self] in + guard let strongSelf = self else { + return nil + } + return CGPoint(x: strongSelf.backgroundNode.frame.maxX, y: strongSelf.backgroundNode.frame.minY) + } reactionRecognizer.began = { [weak self] in guard let strongSelf = self, let item = strongSelf.item else { return @@ -497,7 +503,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode strongSelf.swipeToReplyFeedback = HapticFeedback() } strongSelf.swipeToReplyFeedback?.tap() - if strongSelf.swipeToReplyNode == nil, false { + if strongSelf.swipeToReplyNode == nil { let swipeToReplyNode = ChatMessageSwipeToReplyNode(fillColor: bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.shareButtonFillColor, wallpaper: item.presentationData.theme.wallpaper), strokeColor: bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.shareButtonStrokeColor, wallpaper: item.presentationData.theme.wallpaper), foregroundColor: bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.shareButtonForegroundColor, wallpaper: item.presentationData.theme.wallpaper)) strongSelf.swipeToReplyNode = swipeToReplyNode strongSelf.insertSubnode(swipeToReplyNode, belowSubnode: strongSelf.messageAccessibilityArea) From aee6a29467dfca0ee2e61017a03a696a7e627918 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 10 Sep 2019 23:50:22 +0400 Subject: [PATCH 16/16] Fix context menu animation --- submodules/ContextUI/Sources/ContextController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index 331da8efd9..57b7d3244e 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -169,6 +169,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi self.scrollNode.view.delegate = self + self.view.addSubview(self.effectView) self.addSubnode(self.dimNode) self.addSubnode(self.withoutBlurDimNode)