Improve theme crossfade animation in apperance section

This commit is contained in:
Ilya Laktyushin 2019-12-16 15:44:03 +04:00
parent 62ae89d18b
commit 9cc5ede440
7 changed files with 179 additions and 29 deletions

View File

@ -1193,9 +1193,14 @@ public class Window1 {
}
public func forEachViewController(_ f: (ContainableController) -> Bool) {
if let navigationController = self._rootController as? NavigationController, let controller = navigationController.topOverlayController {
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) {
break

View File

@ -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()
})
}
}

View File

@ -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<PresentationThemeReference?>(value: nil)
let previousAccentColor = Atomic<PresentationThemeAccentColor?>(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: {
@ -715,10 +726,16 @@ 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?()
}
}

View File

@ -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,10 +412,12 @@ 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]?
@ -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] = []
@ -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()
})
}
}

View File

@ -86,11 +86,17 @@ extension TelegramWallpaper: Codable {
bottomColor = Int32(bitPattern: value.rgb)
}
} else if component.count <= 3, let value = Int32(component) {
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)")
}
}

View File

@ -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

View File

@ -500,9 +500,7 @@ public func patternWallpaperImageInternal(thumbnailData: Data?, fullSizeData: Da
}
}
}
addCorners(context, arguments: arguments)
return context
} else {
return nil