From 9cc5ede440ce55f1e8b0a3cea214e3ceb274e55a Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 16 Dec 2019 15:44:03 +0400 Subject: [PATCH] Improve theme crossfade animation in apperance section --- .../Display/Display/WindowContent.swift | 9 +- .../Themes/ThemeSettingsAccentColorItem.swift | 31 ++++- .../Themes/ThemeSettingsController.swift | 111 +++++++++++++++++- .../Themes/ThemeSettingsThemeItem.swift | 37 ++++-- .../Sources/PresentationThemeCodable.swift | 16 ++- .../TelegramUI/TelegramUI/AppDelegate.swift | 2 +- .../Sources/WallpaperResources.swift | 2 - 7 files changed, 179 insertions(+), 29 deletions(-) diff --git a/submodules/Display/Display/WindowContent.swift b/submodules/Display/Display/WindowContent.swift index dbea7fd16f..b172ffe213 100644 --- a/submodules/Display/Display/WindowContent.swift +++ b/submodules/Display/Display/WindowContent.swift @@ -1193,8 +1193,13 @@ public class Window1 { } public func forEachViewController(_ f: (ContainableController) -> Bool) { - if let navigationController = self._rootController as? NavigationController, let controller = navigationController.topOverlayController { - !f(controller) + if let navigationController = self._rootController as? NavigationController { + for case let controller as ContainableController in navigationController.viewControllers { + !f(controller) + } + if let controller = navigationController.topOverlayController { + !f(controller) + } } for (controller, _) in self.presentationContext.controllers { if !f(controller) { diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsAccentColorItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsAccentColorItem.swift index 1c9cd633b1..d9f4cfbda0 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsAccentColorItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsAccentColorItem.swift @@ -230,10 +230,12 @@ private let textFont = Font.regular(11.0) private let itemSize = Font.regular(11.0) class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode { + private let containerNode: ASDisplayNode private let backgroundNode: ASDisplayNode private let topStripeNode: ASDisplayNode private let bottomStripeNode: ASDisplayNode private let maskNode: ASImageNode + private var snapshotView: UIView? private let scrollNode: ASScrollNode private var colorNodes: [ThemeSettingsAccentColorNode] = [] @@ -247,6 +249,8 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode { } init() { + self.containerNode = ASDisplayNode() + self.backgroundNode = ASDisplayNode() self.backgroundNode.isLayerBacked = true @@ -264,6 +268,8 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode { super.init(layerBacked: false, dynamicBounce: false) + self.addSubnode(self.containerNode) + self.customNode.setImage(generateCustomSwatchImage(), for: .normal) self.customNode.addTarget(self, action: #selector(customPressed), forControlEvents: .touchUpInside) @@ -320,16 +326,16 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode { strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor if strongSelf.backgroundNode.supernode == nil { - strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0) + strongSelf.containerNode.insertSubnode(strongSelf.backgroundNode, at: 0) } if strongSelf.topStripeNode.supernode == nil { - strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1) + strongSelf.containerNode.insertSubnode(strongSelf.topStripeNode, at: 1) } if strongSelf.bottomStripeNode.supernode == nil { - strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2) + strongSelf.containerNode.insertSubnode(strongSelf.bottomStripeNode, at: 2) } if strongSelf.maskNode.supernode == nil { - strongSelf.insertSubnode(strongSelf.maskNode, at: 3) + strongSelf.containerNode.insertSubnode(strongSelf.maskNode, at: 3) } let hasCorners = itemListHasRoundedBlockLayout(params) @@ -355,6 +361,7 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } + strongSelf.containerNode.frame = CGRect(x: 0.0, y: 0.0, width: contentSize.width, height: contentSize.height) strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) @@ -451,5 +458,19 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode { override func animateRemoved(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) } + + func prepareCrossfadeTransition() { + self.snapshotView?.removeFromSuperview() + + if let snapshotView = self.containerNode.view.snapshotView(afterScreenUpdates: false) { + self.view.insertSubview(snapshotView, aboveSubview: self.containerNode.view) + self.snapshotView = snapshotView + } + } + + func animateCrossfadeTransition() { + self.snapshotView?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak self] _ in + self?.snapshotView?.removeFromSuperview() + }) + } } - diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift index 4ef49fd500..af994e44b3 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift @@ -361,7 +361,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { } }, contextAction: { theme, node, gesture in arguments.themeContextAction(theme.index == currentTheme.index, theme, node, gesture) - }) + }, tag: ThemeSettingsEntryTag.theme) case let .iconHeader(theme, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .iconItem(theme, strings, icons, value): @@ -437,12 +437,20 @@ private func themeSettingsControllerEntries(presentationData: PresentationData, return entries } +public protocol ThemeSettingsController { + +} + +private final class ThemeSettingsControllerImpl: ItemListController, ThemeSettingsController { +} + public func themeSettingsController(context: AccountContext, focusOnItemTag: ThemeSettingsEntryTag? = nil) -> ViewController { var pushControllerImpl: ((ViewController) -> Void)? var presentControllerImpl: ((ViewController, Any?) -> Void)? var updateControllersImpl: ((([UIViewController]) -> [UIViewController]) -> Void)? var presentInGlobalOverlayImpl: ((ViewController, Any?) -> Void)? var getNavigationControllerImpl: (() -> NavigationController?)? + var presentCrossfadeControllerImpl: (() -> Void)? var selectThemeImpl: ((PresentationThemeReference) -> Void)? var moreImpl: (() -> Void)? @@ -673,6 +681,9 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The presentInGlobalOverlayImpl?(contextController, nil) }) + let previousThemeReference = Atomic(value: nil) + let previousAccentColor = Atomic(value: nil) + let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings]), cloudThemes.get(), availableAppIcons, currentAppIconName.get()) |> map { presentationData, sharedData, cloudThemes, availableAppIcons, currentAppIconName -> (ItemListControllerState, (ItemListNodeState, Any)) in let settings = (sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings] as? PresentationThemeSettings) ?? PresentationThemeSettings.defaultSettings @@ -690,7 +701,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The } let theme = presentationData.theme - let accentColor = settings.themeSpecificAccentColors[themeReference.index]?.color + let accentColor = settings.themeSpecificAccentColors[themeReference.index] let wallpaper = presentationData.chatWallpaper let rightNavigationButton = ItemListNavigationButton(content: .icon(.add), style: .regular, enabled: true, action: { @@ -714,11 +725,17 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.Appearance_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: themeSettingsControllerEntries(presentationData: presentationData, presentationThemeSettings: settings, theme: theme, themeReference: themeReference, themeSpecificAccentColors: settings.themeSpecificAccentColors, availableThemes: availableThemes, autoNightSettings: settings.automaticThemeSwitchSetting, strings: presentationData.strings, wallpaper: wallpaper, fontSize: fontSize, dateTimeFormat: dateTimeFormat, largeEmoji: largeEmoji, disableAnimations: disableAnimations, availableAppIcons: availableAppIcons, currentAppIconName: currentAppIconName), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false) + + let previousThemeIndex = previousThemeReference.swap(themeReference)?.index + let previousAccentColor = previousAccentColor.swap(accentColor) + if previousThemeIndex != nil && (previousThemeIndex != themeReference.index || previousAccentColor != accentColor) { + presentCrossfadeControllerImpl?() + } return (controllerState, (listState, arguments)) } - let controller = ItemListController(context: context, state: signal) + let controller = ThemeSettingsControllerImpl(context: context, state: signal) controller.alwaysSynchronous = true pushControllerImpl = { [weak controller] c in (controller?.navigationController as? NavigationController)?.pushViewController(c) @@ -737,6 +754,54 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The getNavigationControllerImpl = { [weak controller] in return controller?.navigationController as? NavigationController } + presentCrossfadeControllerImpl = { [weak controller] in + if let controller = controller, controller.isNodeLoaded { + var topOffset: CGFloat? + var bottomOffset: CGFloat? + var themeItemNode: ThemeSettingsThemeItemNode? + var colorItemNode: ThemeSettingsAccentColorItemNode? + + controller.forEachItemNode { node in + if let itemNode = node as? ItemListItemNode { + if let itemTag = itemNode.tag { + if itemTag.isEqual(to: ThemeSettingsEntryTag.theme) { + let frame = node.convert(node.bounds, to: controller.displayNode) + topOffset = frame.minY + bottomOffset = frame.maxY + if let itemNode = node as? ThemeSettingsThemeItemNode { + themeItemNode = itemNode + } + } else if itemTag.isEqual(to: ThemeSettingsEntryTag.accentColor) { + let frame = node.convert(node.bounds, to: controller.displayNode) + bottomOffset = frame.maxY + if let itemNode = node as? ThemeSettingsAccentColorItemNode { + colorItemNode = itemNode + } + } + } + } + } + + if let navigationBar = controller.navigationBar { + if let offset = topOffset { + topOffset = max(offset, navigationBar.frame.maxY) + } else { + topOffset = navigationBar.frame.maxY + } + } + + themeItemNode?.prepareCrossfadeTransition() + colorItemNode?.prepareCrossfadeTransition() + + let crossfadeController = ThemeSettingsCrossfadeController(view: controller.view, topOffset: topOffset, bottomOffset: bottomOffset) + crossfadeController.didAppear = { [weak themeItemNode, weak colorItemNode] in + themeItemNode?.animateCrossfadeTransition() + colorItemNode?.animateCrossfadeTransition() + } + + context.sharedContext.presentGlobalController(crossfadeController, nil) + } + } selectThemeImpl = { theme in guard let presentationTheme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: theme) else { return @@ -846,11 +911,37 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The } public final class ThemeSettingsCrossfadeController: ViewController { - private let snapshotView: UIView? + private var snapshotView: UIView? - public init(view: UIView? = nil) { + private var topSnapshotView: UIView? + private var bottomSnapshotView: UIView? + + fileprivate var didAppear: (() -> Void)? + + public init(view: UIView? = nil, topOffset: CGFloat? = nil, bottomOffset: CGFloat? = nil) { if let view = view { - self.snapshotView = view.snapshotContentTree() + if let view = view.snapshotView(afterScreenUpdates: false) { + view.clipsToBounds = true + view.contentMode = .top + if let topOffset = topOffset { + var frame = view.frame + frame.size.height = topOffset + view.frame = frame + } + self.topSnapshotView = view + } + + if let view = view.snapshotView(afterScreenUpdates: false) { + view.clipsToBounds = true + view.contentMode = .bottom + if let bottomOffset = bottomOffset { + var frame = view.frame + frame.origin.y = bottomOffset + frame.size.height -= bottomOffset + view.frame = frame + } + self.bottomSnapshotView = view + } } else { self.snapshotView = UIScreen.main.snapshotView(afterScreenUpdates: false) } @@ -872,6 +963,12 @@ public final class ThemeSettingsCrossfadeController: ViewController { if let snapshotView = self.snapshotView { self.displayNode.view.addSubview(snapshotView) } + if let topSnapshotView = self.topSnapshotView { + self.displayNode.view.addSubview(topSnapshotView) + } + if let bottomSnapshotView = self.bottomSnapshotView { + self.displayNode.view.addSubview(bottomSnapshotView) + } } override public func viewDidAppear(_ animated: Bool) { @@ -880,5 +977,7 @@ public final class ThemeSettingsCrossfadeController: ViewController { self.displayNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak self] _ in self?.presentingViewController?.dismiss(animated: false, completion: nil) }) + + self.didAppear?() } } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsThemeItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsThemeItem.swift index 3006b9664b..172b475279 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsThemeItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsThemeItem.swift @@ -182,7 +182,7 @@ private func createThemeImage(theme: PresentationTheme) -> Signal<(TransformImag c.draw(icon.cgImage!, in: CGRect(origin: CGPoint(x: floor((drawingRect.width - icon.size.width) / 2.0) - 3.0, y: floor((drawingRect.height - icon.size.height) / 2.0)), size: icon.size)) } } - + addCorners(context, arguments: arguments) return context } } @@ -412,11 +412,13 @@ private func ensureThemeVisible(listNode: ListView, themeReference: Presentation } class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode { + private let containerNode: ASDisplayNode private let backgroundNode: ASDisplayNode private let topStripeNode: ASDisplayNode private let bottomStripeNode: ASDisplayNode private let maskNode: ASImageNode - + private var snapshotView: UIView? + private let listNode: ListView private var entries: [ThemeSettingsThemeEntry]? private var enqueuedTransitions: [ThemeSettingsThemeItemNodeTransition] = [] @@ -430,6 +432,8 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode { } init() { + self.containerNode = ASDisplayNode() + self.backgroundNode = ASDisplayNode() self.backgroundNode.isLayerBacked = true @@ -446,6 +450,7 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode { super.init(layerBacked: false, dynamicBounce: false) + self.addSubnode(self.containerNode) self.addSubnode(self.listNode) } @@ -512,16 +517,16 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode { strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor if strongSelf.backgroundNode.supernode == nil { - strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0) + strongSelf.containerNode.insertSubnode(strongSelf.backgroundNode, at: 0) } if strongSelf.topStripeNode.supernode == nil { - strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1) + strongSelf.containerNode.insertSubnode(strongSelf.topStripeNode, at: 1) } if strongSelf.bottomStripeNode.supernode == nil { - strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2) + strongSelf.containerNode.insertSubnode(strongSelf.bottomStripeNode, at: 2) } if strongSelf.maskNode.supernode == nil { - strongSelf.insertSubnode(strongSelf.maskNode, at: 3) + strongSelf.containerNode.insertSubnode(strongSelf.maskNode, at: 3) } let hasCorners = itemListHasRoundedBlockLayout(params) @@ -547,6 +552,7 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } + strongSelf.containerNode.frame = CGRect(x: 0.0, y: 0.0, width: contentSize.width, height: contentSize.height) strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) @@ -559,7 +565,7 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode { listInsets.bottom += params.rightInset + 4.0 strongSelf.listNode.bounds = CGRect(x: 0.0, y: 0.0, width: contentSize.height, height: contentSize.width) - strongSelf.listNode.position = CGPoint(x: contentSize.width / 2.0, y: contentSize.height / 2.0) + strongSelf.listNode.position = CGPoint(x: contentSize.width / 2.0, y: contentSize.height / 2.0 + 2.0) strongSelf.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: CGSize(width: contentSize.height, height: contentSize.width), insets: listInsets, duration: 0.0, curve: .Default(duration: nil)), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) var entries: [ThemeSettingsThemeEntry] = [] @@ -588,7 +594,7 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode { }) } } - + override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) } @@ -596,4 +602,19 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode { override func animateRemoved(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) } + + func prepareCrossfadeTransition() { + self.snapshotView?.removeFromSuperview() + + if let snapshotView = self.containerNode.view.snapshotView(afterScreenUpdates: false) { + self.view.insertSubview(snapshotView, aboveSubview: self.containerNode.view) + self.snapshotView = snapshotView + } + } + + func animateCrossfadeTransition() { + self.snapshotView?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak self] _ in + self?.snapshotView?.removeFromSuperview() + }) + } } diff --git a/submodules/TelegramPresentationData/Sources/PresentationThemeCodable.swift b/submodules/TelegramPresentationData/Sources/PresentationThemeCodable.swift index 0e18340c01..a8cd21d6fb 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationThemeCodable.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationThemeCodable.swift @@ -86,10 +86,16 @@ extension TelegramWallpaper: Codable { bottomColor = Int32(bitPattern: value.rgb) } } else if component.count <= 3, let value = Int32(component) { - if value >= 0 && value <= 100 { - intensity = value - } else { - intensity = 50 + if intensity == nil { + if value >= 0 && value <= 100 { + intensity = value + } else { + intensity = 50 + } + } else if rotation == nil { + if value >= 0 && value < 360 { + rotation = value + } } } } @@ -141,7 +147,7 @@ extension TelegramWallpaper: Codable { if let bottomColor = file.settings.bottomColor { components.append(String(format: "%06x", bottomColor)) } - if let rotation = file.settings.rotation { + if let rotation = file.settings.rotation, rotation != 0 { components.append("\(rotation)") } } diff --git a/submodules/TelegramUI/TelegramUI/AppDelegate.swift b/submodules/TelegramUI/TelegramUI/AppDelegate.swift index 303fb6538f..71e8f9a73f 100644 --- a/submodules/TelegramUI/TelegramUI/AppDelegate.swift +++ b/submodules/TelegramUI/TelegramUI/AppDelegate.swift @@ -804,7 +804,7 @@ final class SharedApplicationContext { } var exists = false strongSelf.mainWindow.forEachViewController { controller in - if controller is ThemeSettingsCrossfadeController { + if controller is ThemeSettingsCrossfadeController || controller is ThemeSettingsController { exists = true } return true diff --git a/submodules/WallpaperResources/Sources/WallpaperResources.swift b/submodules/WallpaperResources/Sources/WallpaperResources.swift index d84c5da42b..195f353fc5 100644 --- a/submodules/WallpaperResources/Sources/WallpaperResources.swift +++ b/submodules/WallpaperResources/Sources/WallpaperResources.swift @@ -500,9 +500,7 @@ public func patternWallpaperImageInternal(thumbnailData: Data?, fullSizeData: Da } } } - addCorners(context, arguments: arguments) - return context } else { return nil