diff --git a/submodules/AnimationUI/Sources/AnimationNode.swift b/submodules/AnimationUI/Sources/AnimationNode.swift index c7f9391e20..c7dfb88f43 100644 --- a/submodules/AnimationUI/Sources/AnimationNode.swift +++ b/submodules/AnimationUI/Sources/AnimationNode.swift @@ -131,6 +131,12 @@ public final class AnimationNode: ASDisplayNode { } } + public func setColors(colors: [String: UIColor]) { + for (key, value) in colors { + self.animationView()?.setValueProvider(ColorValueProvider(value.lottieColorValue), keypath: AnimationKeypath(keypath: "\(key).Color")) + } + } + public func setAnimation(data: Data) { if let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] { let animation = try? Animation(dictionary: json) diff --git a/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift b/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift index 8b2f0923a4..a3f373b2b0 100644 --- a/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift +++ b/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift @@ -726,6 +726,16 @@ private func iconColors(theme: PresentationTheme) -> [String: UIColor] { return colors } +private func interpolateColors(from: [String: UIColor], to: [String: UIColor], fraction: CGFloat) -> [String: UIColor] { + var colors: [String: UIColor] = [:] + for (key, fromValue) in from { + if let toValue = to[key] { + colors[key] = fromValue.interpolateTo(toValue, fraction: fraction) + } + } + return colors +} + private let defaultEmoticon = "🏠" private func generateShadowImage() -> UIImage? { @@ -1149,11 +1159,11 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg self.switchThemeButton.highligthedChanged = { [weak self] highlighted in if let strongSelf = self { if highlighted { - strongSelf.animationNode.layer.removeAnimation(forKey: "opacity") - strongSelf.animationNode.alpha = 0.4 + strongSelf.animationContainerNode.layer.removeAnimation(forKey: "opacity") + strongSelf.animationContainerNode.alpha = 0.4 } else { - strongSelf.animationNode.alpha = 1.0 - strongSelf.animationNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + strongSelf.animationContainerNode.alpha = 1.0 + strongSelf.animationContainerNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) } } } @@ -1233,6 +1243,7 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg }) } + private var switchThemeIconAnimator: DisplayLinkAnimator? func updatePresentationData(_ presentationData: PresentationData) { guard !self.animatedOut else { return @@ -1250,22 +1261,20 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg self.cancelButton.setImage(closeButtonImage(theme: self.presentationData.theme), for: .normal) self.doneButton.updateTheme(SolidRoundedButtonTheme(theme: self.presentationData.theme)) - if self.animationNode.isPlaying { - if let animationNode = self.animationNode.makeCopy(colors: iconColors(theme: self.presentationData.theme), progress: 0.2) { - let previousAnimationNode = self.animationNode - self.animationNode = animationNode - - animationNode.completion = { [weak previousAnimationNode] in - previousAnimationNode?.removeFromSupernode() - } - animationNode.isUserInteractionEnabled = false - animationNode.frame = previousAnimationNode.frame - previousAnimationNode.supernode?.insertSubnode(animationNode, belowSubnode: previousAnimationNode) - previousAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: ChatQrCodeScreen.themeCrossfadeDuration, removeOnCompletion: false) - animationNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + let previousIconColors = iconColors(theme: previousTheme) + let newIconColors = iconColors(theme: self.presentationData.theme) + + if !self.switchThemeButton.isUserInteractionEnabled { + Queue.mainQueue().after(ChatThemeScreen.themeCrossfadeDelay) { + self.switchThemeIconAnimator = DisplayLinkAnimator(duration: ChatThemeScreen.themeCrossfadeDuration * UIView.animationDurationFactor(), from: 0.0, to: 1.0, update: { [weak self] value in + self?.animationNode.setColors(colors: interpolateColors(from: previousIconColors, to: newIconColors, fraction: value)) + }, completion: { [weak self] in + self?.switchThemeIconAnimator?.invalidate() + self?.switchThemeIconAnimator = nil + }) } } else { - self.animationNode.setAnimation(name: self.isDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: iconColors(theme: self.presentationData.theme)) + self.animationNode.setAnimation(name: self.isDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: newIconColors) } } @@ -1292,7 +1301,9 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg self.animateCrossfade(animateIcon: false) self.animationNode.setAnimation(name: self.isDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: iconColors(theme: self.presentationData.theme)) - self.animationNode.playOnce() + Queue.mainQueue().justDispatch { + self.animationNode.playOnce() + } let isDarkAppearance = !self.isDarkAppearance @@ -1437,7 +1448,7 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg let switchThemeFrame = CGRect(origin: CGPoint(x: 3.0, y: 6.0), size: switchThemeSize) transition.updateFrame(node: self.switchThemeButton, frame: switchThemeFrame) transition.updateFrame(node: self.animationContainerNode, frame: switchThemeFrame.insetBy(dx: 9.0, dy: 9.0)) - transition.updateFrame(node: self.animationNode, frame: CGRect(origin: CGPoint(), size: self.animationContainerNode.frame.size)) + transition.updateFrameAsPositionAndBounds(node: self.animationNode, frame: CGRect(origin: CGPoint(), size: self.animationContainerNode.frame.size)) let cancelSize = CGSize(width: 44.0, height: 44.0) let cancelFrame = CGRect(origin: CGPoint(x: contentFrame.width - cancelSize.width - 3.0, y: 6.0), size: cancelSize) diff --git a/submodules/TelegramUI/Sources/ChatThemeScreen.swift b/submodules/TelegramUI/Sources/ChatThemeScreen.swift index 2eb52a09ba..dd015c8b58 100644 --- a/submodules/TelegramUI/Sources/ChatThemeScreen.swift +++ b/submodules/TelegramUI/Sources/ChatThemeScreen.swift @@ -708,6 +708,16 @@ private func iconColors(theme: PresentationTheme) -> [String: UIColor] { return colors } +private func interpolateColors(from: [String: UIColor], to: [String: UIColor], fraction: CGFloat) -> [String: UIColor] { + var colors: [String: UIColor] = [:] + for (key, fromValue) in from { + if let toValue = to[key] { + colors[key] = fromValue.interpolateTo(toValue, fraction: fraction) + } + } + return colors +} + private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelegate { private let context: AccountContext private var presentationData: PresentationData @@ -938,11 +948,11 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega self.switchThemeButton.highligthedChanged = { [weak self] highlighted in if let strongSelf = self { if highlighted { - strongSelf.animationNode.layer.removeAnimation(forKey: "opacity") - strongSelf.animationNode.alpha = 0.4 + strongSelf.animationContainerNode.layer.removeAnimation(forKey: "opacity") + strongSelf.animationContainerNode.alpha = 0.4 } else { - strongSelf.animationNode.alpha = 1.0 - strongSelf.animationNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + strongSelf.animationContainerNode.alpha = 1.0 + strongSelf.animationContainerNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) } } } @@ -982,14 +992,19 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega }) } + private var skipButtonsUpdate = false private func setEmoticon(_ emoticon: String?) { self.animateCrossfade(animateIcon: true) + self.skipButtonsUpdate = true self.previewTheme?(emoticon, self.isDarkAppearance) self.selectedEmoticon = emoticon let _ = ensureThemeVisible(listNode: self.listNode, emoticon: emoticon, animated: true) - self.updateButtons() + UIView.transition(with: self.buttonsContentContainerNode.view, duration: ChatThemeScreen.themeCrossfadeDuration, options: [.transitionCrossDissolve, .curveLinear]) { + self.updateButtons() + } + self.skipButtonsUpdate = false self.themeSelectionsCount += 1 if self.themeSelectionsCount == 2 { @@ -1017,16 +1032,18 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega if accentButtonTheme { buttonTheme = SolidRoundedButtonTheme(theme: self.presentationData.theme) } else { - buttonTheme = SolidRoundedButtonTheme(backgroundColor: self.presentationData.theme.actionSheet.itemBackgroundColor, foregroundColor: self.presentationData.theme.actionSheet.controlAccentColor) + buttonTheme = SolidRoundedButtonTheme(backgroundColor: .clear, foregroundColor: self.presentationData.theme.actionSheet.controlAccentColor) + } + UIView.performWithoutAnimation { + self.doneButton.title = doneButtonTitle + self.doneButton.font = accentButtonTheme ? .bold : .regular } - self.doneButton.title = doneButtonTitle - self.doneButton.font = accentButtonTheme ? .bold : .regular self.doneButton.updateTheme(buttonTheme) - self.otherButton.setTitle(otherButtonTitle, with: Font.regular(17.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal) } + private var switchThemeIconAnimator: DisplayLinkAnimator? func updatePresentationData(_ presentationData: PresentationData) { guard !self.animatedOut else { return @@ -1042,25 +1059,28 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega } self.cancelButton.setImage(closeButtonImage(theme: self.presentationData.theme), for: .normal) - self.doneButton.updateTheme(SolidRoundedButtonTheme(theme: self.presentationData.theme)) - self.updateButtons() - if self.animationNode.isPlaying { - if let animationNode = self.animationNode.makeCopy(colors: iconColors(theme: self.presentationData.theme), progress: 0.2) { - let previousAnimationNode = self.animationNode - self.animationNode = animationNode + let previousIconColors = iconColors(theme: previousTheme) + let newIconColors = iconColors(theme: self.presentationData.theme) + + if !self.switchThemeButton.isUserInteractionEnabled { + Queue.mainQueue().after(ChatThemeScreen.themeCrossfadeDelay * UIView.animationDurationFactor()) { + self.switchThemeIconAnimator = DisplayLinkAnimator(duration: ChatThemeScreen.themeCrossfadeDuration * UIView.animationDurationFactor(), from: 0.0, to: 1.0, update: { [weak self] value in + self?.animationNode.setColors(colors: interpolateColors(from: previousIconColors, to: newIconColors, fraction: value)) + }, completion: { [weak self] in + self?.switchThemeIconAnimator?.invalidate() + self?.switchThemeIconAnimator = nil + }) - animationNode.completion = { [weak previousAnimationNode] in - previousAnimationNode?.removeFromSupernode() + UIView.transition(with: self.buttonsContentContainerNode.view, duration: ChatThemeScreen.themeCrossfadeDuration, options: [.transitionCrossDissolve, .curveLinear]) { + self.updateButtons() } - animationNode.isUserInteractionEnabled = false - animationNode.frame = previousAnimationNode.frame - previousAnimationNode.supernode?.insertSubnode(animationNode, belowSubnode: previousAnimationNode) - previousAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: ChatThemeScreen.themeCrossfadeDuration, removeOnCompletion: false) - animationNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } } else { - self.animationNode.setAnimation(name: self.isDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: iconColors(theme: self.presentationData.theme)) + self.animationNode.setAnimation(name: self.isDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: newIconColors) + if !self.skipButtonsUpdate { + self.updateButtons() + } } } @@ -1107,7 +1127,9 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega self.animateCrossfade(animateIcon: false) self.animationNode.setAnimation(name: self.isDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: iconColors(theme: self.presentationData.theme)) - self.animationNode.playOnce() + Queue.mainQueue().justDispatch { + self.animationNode.playOnce() + } let isDarkAppearance = !self.isDarkAppearance self.previewTheme?(self.selectedEmoticon, isDarkAppearance) @@ -1129,8 +1151,8 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega snapshotView?.removeFromSuperview() }) } - - Queue.mainQueue().after(ChatThemeScreen.themeCrossfadeDelay) { + + Queue.mainQueue().after(ChatThemeScreen.themeCrossfadeDelay * UIView.animationDurationFactor()) { if let effectView = self.effectNode.view as? UIVisualEffectView { UIView.animate(withDuration: ChatThemeScreen.themeCrossfadeDuration, delay: 0.0, options: .curveLinear) { effectView.effect = UIBlurEffect(style: self.presentationData.theme.actionSheet.backgroundType == .light ? .light : .dark) @@ -1151,6 +1173,15 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega snapshotView?.removeFromSuperview() }) } + + if !animateIcon, let snapshotView = self.otherButton.view.snapshotView(afterScreenUpdates: false) { + snapshotView.frame = self.otherButton.frame + self.otherButton.view.superview?.insertSubview(snapshotView, aboveSubview: self.otherButton.view) + + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: ChatThemeScreen.themeCrossfadeDuration, delay: ChatThemeScreen.themeCrossfadeDelay, timingFunction: CAMediaTimingFunctionName.linear.rawValue, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } self.listNode.forEachVisibleItemNode { node in if let node = node as? ThemeSettingsThemeItemIconNode { @@ -1293,7 +1324,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega let switchThemeFrame = CGRect(origin: CGPoint(x: 3.0, y: 6.0), size: switchThemeSize) transition.updateFrame(node: self.switchThemeButton, frame: switchThemeFrame) transition.updateFrame(node: self.animationContainerNode, frame: switchThemeFrame.insetBy(dx: 9.0, dy: 9.0)) - transition.updateFrame(node: self.animationNode, frame: CGRect(origin: CGPoint(), size: self.animationContainerNode.frame.size)) + transition.updateFrameAsPositionAndBounds(node: self.animationNode, frame: CGRect(origin: .zero, size: self.animationContainerNode.frame.size)) let cancelSize = CGSize(width: 44.0, height: 44.0) let cancelFrame = CGRect(origin: CGPoint(x: contentFrame.width - cancelSize.width - 3.0, y: 6.0), size: cancelSize)