Swiftgram/TelegramUI/WallpaperListPreviewControllerNode.swift
2019-01-22 21:58:59 +04:00

882 lines
47 KiB
Swift

import Foundation
import Display
import AsyncDisplayKit
import SwiftSignalKit
import Postbox
import TelegramCore
import Photos
import LegacyComponents
enum WallpaperEntry: Equatable {
case wallpaper(TelegramWallpaper)
case asset(PHAsset, UIImage?)
case contextResult(ChatContextResult)
public static func ==(lhs: WallpaperEntry, rhs: WallpaperEntry) -> Bool {
switch lhs {
case let .wallpaper(wallpaper):
if case .wallpaper(wallpaper) = rhs {
return true
} else {
return false
}
case let .asset(lhsAsset, _):
if case let .asset(rhsAsset, _) = rhs, lhsAsset.localIdentifier == rhsAsset.localIdentifier {
return true
} else {
return false
}
case let .contextResult(lhsResult):
if case let .contextResult(rhsResult) = rhs, lhsResult.id == rhsResult.id {
return true
} else {
return false
}
}
}
}
private final class WallpaperBackgroundNode: ASDisplayNode {
let wallpaper: WallpaperEntry
private var fetchDisposable: Disposable?
private var statusDisposable: Disposable?
let wrapperNode: ASDisplayNode
let imageNode: TransformImageNode
let cropNode: WallpaperCropNode
private var contentSize: CGSize?
private let statusNode: RadialStatusNode
private let blurredNode: BlurredImageNode
let controlsColor = Promise<UIColor>(.white)
let status = Promise<MediaResourceStatus>(.Local)
init(context: AccountContext, wallpaper: WallpaperEntry) {
self.wallpaper = wallpaper
self.wrapperNode = ASDisplayNode()
self.imageNode = TransformImageNode()
self.imageNode.contentAnimations = .subsequentUpdates
self.cropNode = WallpaperCropNode()
self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.6))
let progressDiameter: CGFloat = 50.0
self.statusNode.frame = CGRect(x: 0.0, y: 0.0, width: progressDiameter, height: progressDiameter)
self.statusNode.isUserInteractionEnabled = false
self.blurredNode = BlurredImageNode()
super.init()
self.clipsToBounds = true
self.backgroundColor = .black
let signal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>
let fetchSignal: Signal<FetchResourceSourceType, FetchResourceError>
let statusSignal: Signal<MediaResourceStatus, NoError>
let displaySize: CGSize
let contentSize: CGSize
switch wallpaper {
case let .wallpaper(wallpaper):
switch wallpaper {
case .builtin:
displaySize = CGSize(width: 640.0, height: 1136.0)
contentSize = displaySize
signal = settingsBuiltinWallpaperImage(account: context.account)
fetchSignal = .complete()
statusSignal = .single(.Local)
case let .color(color):
displaySize = CGSize(width: 1.0, height: 1.0)
contentSize = displaySize
signal = .never()
fetchSignal = .complete()
statusSignal = .single(.Local)
self.backgroundColor = UIColor(rgb: UInt32(bitPattern: color))
case let .file(file):
let dimensions = file.file.dimensions ?? CGSize(width: 100.0, height: 100.0)
contentSize = dimensions
displaySize = dimensions.dividedByScreenScale().integralFloor
var convertedRepresentations: [ImageRepresentationWithReference] = []
for representation in file.file.previewRepresentations {
convertedRepresentations.append(ImageRepresentationWithReference(representation: representation, reference: .standalone(resource: representation.resource)))
}
convertedRepresentations.append(ImageRepresentationWithReference(representation: .init(dimensions: dimensions, resource: file.file.resource), reference: .standalone(resource: file.file.resource)))
signal = chatMessageImageFile(account: context.account, fileReference: .standalone(media: file.file), thumbnail: false)
fetchSignal = fetchedMediaResource(postbox: context.account.postbox, reference: convertedRepresentations[convertedRepresentations.count - 1].reference)
statusSignal = context.account.postbox.mediaBox.resourceStatus(file.file.resource)
case let .image(representations):
if let largestSize = largestImageRepresentation(representations) {
contentSize = largestSize.dimensions
displaySize = largestSize.dimensions.dividedByScreenScale().integralFloor
let convertedRepresentations: [ImageRepresentationWithReference] = representations.map({ ImageRepresentationWithReference(representation: $0, reference: .wallpaper(resource: $0.resource)) })
signal = chatAvatarGalleryPhoto(account: context.account, representations: convertedRepresentations)
if let largestIndex = convertedRepresentations.index(where: { $0.representation == largestSize }) {
fetchSignal = fetchedMediaResource(postbox: context.account.postbox, reference: convertedRepresentations[largestIndex].reference)
} else {
fetchSignal = .complete()
}
statusSignal = context.account.postbox.mediaBox.resourceStatus(largestSize.resource)
} else {
displaySize = CGSize(width: 1.0, height: 1.0)
contentSize = displaySize
signal = .never()
fetchSignal = .complete()
statusSignal = .single(.Local)
}
}
case let .asset(asset, _):
let dimensions = CGSize(width: asset.pixelWidth, height: asset.pixelHeight)
contentSize = dimensions
displaySize = dimensions.dividedByScreenScale().integralFloor
signal = photoWallpaper(postbox: context.account.postbox, photoLibraryResource: PhotoLibraryMediaResource(localIdentifier: asset.localIdentifier, uniqueId: arc4random64()))
fetchSignal = .complete()
statusSignal = .single(.Local)
self.wrapperNode.addSubnode(self.cropNode)
case let .contextResult(result):
var imageDimensions: CGSize?
var imageResource: TelegramMediaResource?
var thumbnailDimensions: CGSize?
var thumbnailResource: TelegramMediaResource?
switch result {
case let .externalReference(_, _, _, _, _, _, content, thumbnail, _):
if let content = content {
imageResource = content.resource
}
if let thumbnail = thumbnail {
thumbnailResource = thumbnail.resource
thumbnailDimensions = thumbnail.dimensions
}
if let dimensions = content?.dimensions {
imageDimensions = dimensions
}
case let .internalReference(_, _, _, _, _, image, _, _):
if let image = image {
if let imageRepresentation = imageRepresentationLargerThan(image.representations, size: CGSize(width: 1000.0, height: 800.0)) {
imageDimensions = imageRepresentation.dimensions
imageResource = imageRepresentation.resource
}
if let thumbnailRepresentation = imageRepresentationLargerThan(image.representations, size: CGSize(width: 200.0, height: 100.0)) {
thumbnailDimensions = thumbnailRepresentation.dimensions
thumbnailResource = thumbnailRepresentation.resource
}
}
}
if let imageResource = imageResource, let imageDimensions = imageDimensions {
contentSize = imageDimensions
displaySize = imageDimensions.dividedByScreenScale().integralFloor
var representations: [TelegramMediaImageRepresentation] = []
if let thumbnailResource = thumbnailResource, let thumbnailDimensions = thumbnailDimensions {
representations.append(TelegramMediaImageRepresentation(dimensions: thumbnailDimensions, resource: thumbnailResource))
}
representations.append(TelegramMediaImageRepresentation(dimensions: imageDimensions, resource: imageResource))
let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil)
signal = chatMessagePhoto(postbox: context.account.postbox, photoReference: .standalone(media: tmpImage))
fetchSignal = fetchedMediaResource(postbox: context.account.postbox, reference: .media(media: .standalone(media: tmpImage), resource: imageResource))
statusSignal = context.account.postbox.mediaBox.resourceStatus(imageResource)
} else {
displaySize = CGSize(width: 1.0, height: 1.0)
contentSize = displaySize
signal = .never()
fetchSignal = .complete()
statusSignal = .single(.Local)
}
self.wrapperNode.addSubnode(self.cropNode)
}
self.contentSize = contentSize
self.addSubnode(self.wrapperNode)
if self.cropNode.supernode == nil {
self.imageNode.contentMode = .scaleAspectFill
self.wrapperNode.addSubnode(self.imageNode)
}
self.addSubnode(self.statusNode)
let imagePromise = Promise<UIImage?>()
self.imageNode.setSignal(signal, dispatchOnDisplayLink: false)
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets()))()
self.imageNode.imageUpdated = { [weak self] image in
if let strongSelf = self {
var image = image
if let scaledImage = image {
if scaledImage.size.width > 2048.0 || scaledImage.size.height > 2048.0 {
image = TGScaleImageToPixelSize(image, scaledImage.size.fitted(CGSize(width: 2048.0, height: 2048.0)))
}
}
strongSelf.blurredNode.image = image
imagePromise.set(.single(image))
}
}
self.fetchDisposable = fetchSignal.start()
let statusForegroundColor = UIColor.white
self.statusDisposable = (statusSignal
|> deliverOnMainQueue).start(next: { [weak self] status in
if let strongSelf = self {
let state: RadialStatusNodeState
switch status {
case let .Fetching(_, progress):
let adjustedProgress = max(progress, 0.027)
state = .progress(color: statusForegroundColor, lineWidth: nil, value: CGFloat(adjustedProgress), cancelEnabled: false)
case .Local:
state = .none
case .Remote:
state = .progress(color: statusForegroundColor, lineWidth: nil, value: 0.027, cancelEnabled: false)
}
strongSelf.statusNode.transitionToState(state, completion: {})
}
})
let controlsColorSignal: Signal<UIColor, NoError>
if case let .wallpaper(wallpaper) = wallpaper {
controlsColorSignal = chatBackgroundContrastColor(wallpaper: wallpaper, postbox: context.account.postbox)
} else {
controlsColorSignal = backgroundContrastColor(for: imagePromise.get())
}
self.controlsColor.set(.single(.white) |> then(controlsColorSignal))
self.status.set(statusSignal)
}
deinit {
self.fetchDisposable?.dispose()
self.statusDisposable?.dispose()
}
var cropRect: CGRect? {
switch self.wallpaper {
case .asset, .contextResult:
return self.cropNode.cropRect
default:
return nil
}
}
func setParallaxEnabled(_ enabled: Bool) {
if enabled {
let amount = 24.0
let horizontal = UIInterpolatingMotionEffect(keyPath: "center.x", type: .tiltAlongHorizontalAxis)
horizontal.minimumRelativeValue = -amount
horizontal.maximumRelativeValue = amount
let vertical = UIInterpolatingMotionEffect(keyPath: "center.y", type: .tiltAlongVerticalAxis)
vertical.minimumRelativeValue = -amount
vertical.maximumRelativeValue = amount
let group = UIMotionEffectGroup()
group.motionEffects = [horizontal, vertical]
self.wrapperNode.view.addMotionEffect(group)
} else {
for effect in self.imageNode.view.motionEffects {
self.wrapperNode.view.removeMotionEffect(effect)
}
}
}
func setBlurEnabled(_ enabled: Bool, animated: Bool) {
let blurRadius: CGFloat = 45.0
if enabled {
if self.blurredNode.supernode == nil {
if self.cropNode.supernode != nil {
self.blurredNode.frame = self.imageNode.bounds
self.imageNode.addSubnode(self.blurredNode)
} else {
self.blurredNode.frame = self.imageNode.frame
self.addSubnode(self.blurredNode)
}
}
if animated {
self.blurredNode.blurView.blurRadius = 0.0
UIView.animate(withDuration: 0.3, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: {
self.blurredNode.blurView.blurRadius = blurRadius
}, completion: nil)
} else {
self.blurredNode.blurView.blurRadius = blurRadius
}
} else {
if self.blurredNode.supernode != nil {
if animated {
UIView.animate(withDuration: 0.3, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: {
self.blurredNode.blurView.blurRadius = 0.0
}, completion: { finished in
if finished {
self.blurredNode.removeFromSupernode()
}
})
} else {
self.blurredNode.removeFromSupernode()
}
}
}
}
func updateLayout(_ layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.wrapperNode.frame = CGRect(origin: CGPoint(), size: layout.size)
if self.cropNode.supernode == nil {
self.imageNode.frame = self.wrapperNode.bounds
self.blurredNode.frame = self.imageNode.frame
} else {
self.cropNode.frame = self.wrapperNode.bounds
self.cropNode.containerLayoutUpdated(layout, transition: transition)
if self.cropNode.supernode != nil, let contentSize = self.contentSize, self.cropNode.zoomableContent == nil {
let fittedSize = TGScaleToFit(self.cropNode.bounds.size, contentSize)
self.cropNode.zoomableContent = (contentSize, self.imageNode)
self.cropNode.zoom(to: CGRect(x: (contentSize.width - fittedSize.width) / 2.0, y: (contentSize.height - fittedSize.height) / 2.0, width: fittedSize.width, height: fittedSize.height))
}
self.blurredNode.frame = self.imageNode.bounds
}
let progressDiameter: CGFloat = 50.0
self.statusNode.frame = CGRect(x: layout.safeInsets.left + floorToScreenPixels((layout.size.width - layout.safeInsets.left - layout.safeInsets.right - progressDiameter) / 2.0), y: floorToScreenPixels((layout.size.height - progressDiameter) / 2.0), width: progressDiameter, height: progressDiameter)
}
}
final class WallpaperListPreviewControllerNode: ViewControllerTracingNode {
private let context: AccountContext
private var presentationData: PresentationData
private let source: WallpaperListSource
private let dismiss: () -> Void
private let apply: (WallpaperEntry, WallpaperPresentationOptions, CGRect?) -> Void
private var validLayout: (ContainerViewLayout, CGFloat)?
private let toolbarBackground: ASDisplayNode
private let toolbarSeparator: ASDisplayNode
private let toolbarVerticalSeparator: ASDisplayNode
private let toolbarButtonCancel: HighlightTrackingButtonNode
private let toolbarButtonCancelBackground: ASDisplayNode
private let toolbarButtonApply: HighlightTrackingButtonNode
private let toolbarButtonApplyBackground: ASDisplayNode
private let segmentedControl: UISegmentedControl
private var segmentedControlColor = Promise<UIColor>(.white)
private var segmentedControlColorDisposable: Disposable?
private let colorPanelNode: WallpaperColorPanelNode
private var status = Promise<MediaResourceStatus>(.Local)
private var statusDisposable: Disposable?
private var wallpapersDisposable: Disposable?
private var wallpapers: [WallpaperEntry]?
let ready = ValuePromise<Bool>(false)
private var messageNodes: [ListViewItemNode]?
private var visibleBackgroundNodes: [WallpaperBackgroundNode] = []
private var centralWallpaper: WallpaperEntry?
private let currentWallpaperPromise = Promise<WallpaperEntry>()
var currentWallpaper: Signal<WallpaperEntry, NoError> {
return self.currentWallpaperPromise.get()
}
private var visibleBackgroundNodesOffset: CGFloat = 0.0
init(context: AccountContext, presentationData: PresentationData, source: WallpaperListSource, dismiss: @escaping () -> Void, apply: @escaping (WallpaperEntry, WallpaperPresentationOptions, CGRect?) -> Void) {
self.context = context
self.presentationData = presentationData
self.source = source
self.dismiss = dismiss
self.apply = apply
self.toolbarBackground = ASDisplayNode()
self.toolbarBackground.backgroundColor = self.presentationData.theme.rootController.navigationBar.backgroundColor
self.toolbarSeparator = ASDisplayNode()
self.toolbarSeparator.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor
self.toolbarVerticalSeparator = ASDisplayNode()
self.toolbarVerticalSeparator.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor
self.toolbarButtonCancelBackground = ASDisplayNode()
self.toolbarButtonCancelBackground.alpha = 0.0
self.toolbarButtonCancelBackground.backgroundColor = self.presentationData.theme.list.itemHighlightedBackgroundColor
self.toolbarButtonCancelBackground.isUserInteractionEnabled = false
self.toolbarButtonCancel = HighlightTrackingButtonNode()
self.toolbarButtonCancel.setAttributedTitle(NSAttributedString(string: self.presentationData.strings.Common_Cancel, font: Font.regular(17.0), textColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor), for: [])
self.toolbarButtonApplyBackground = ASDisplayNode()
self.toolbarButtonApplyBackground.alpha = 0.0
self.toolbarButtonApplyBackground.backgroundColor = self.presentationData.theme.list.itemHighlightedBackgroundColor
self.toolbarButtonApplyBackground.isUserInteractionEnabled = false
self.toolbarButtonApply = HighlightTrackingButtonNode()
self.toolbarButtonApply.setAttributedTitle(NSAttributedString(string: self.presentationData.strings.Wallpaper_Set, font: Font.regular(17.0), textColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor), for: [])
self.segmentedControl = UISegmentedControl(items: [self.presentationData.strings.WallpaperPreview_Still, self.presentationData.strings.WallpaperPreview_Perspective, self.presentationData.strings.WallpaperPreview_Blurred])
self.segmentedControl.selectedSegmentIndex = 0
self.segmentedControl.tintColor = .white
self.colorPanelNode = WallpaperColorPanelNode(theme: presentationData.theme)
super.init()
self.backgroundColor = .black
self.addSubnode(self.toolbarBackground)
self.addSubnode(self.toolbarSeparator)
self.addSubnode(self.toolbarVerticalSeparator)
self.addSubnode(self.toolbarButtonCancel)
self.addSubnode(self.toolbarButtonApply)
self.addSubnode(self.colorPanelNode)
self.view.addSubview(self.segmentedControl)
self.toolbarButtonCancel.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside)
self.toolbarButtonApply.addTarget(self, action: #selector(self.applyPressed), forControlEvents: .touchUpInside)
self.toolbarButtonCancel.highligthedChanged = { [weak self] value in
if let strongSelf = self {
if value {
if strongSelf.toolbarButtonCancelBackground.supernode == nil {
strongSelf.insertSubnode(strongSelf.toolbarButtonCancelBackground, aboveSubnode: strongSelf.toolbarVerticalSeparator)
}
strongSelf.toolbarButtonCancelBackground.layer.removeAnimation(forKey: "opacity")
strongSelf.toolbarButtonCancelBackground.alpha = 1.0
} else if !strongSelf.toolbarButtonCancelBackground.alpha.isZero {
strongSelf.toolbarButtonCancelBackground.alpha = 0.0
strongSelf.toolbarButtonCancelBackground.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25)
}
}
}
self.toolbarButtonApply.highligthedChanged = { [weak self] value in
if let strongSelf = self {
if value {
if strongSelf.toolbarButtonApplyBackground.supernode == nil {
strongSelf.insertSubnode(strongSelf.toolbarButtonApplyBackground, aboveSubnode: strongSelf.toolbarVerticalSeparator)
}
strongSelf.toolbarButtonApplyBackground.layer.removeAnimation(forKey: "opacity")
strongSelf.toolbarButtonApplyBackground.alpha = 1.0
} else if !strongSelf.toolbarButtonApplyBackground.alpha.isZero {
strongSelf.toolbarButtonApplyBackground.alpha = 0.0
strongSelf.toolbarButtonApplyBackground.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25)
}
}
}
self.segmentedControl.addTarget(self, action: #selector(self.indexChanged), for: .valueChanged)
self.segmentedControlColorDisposable = (self.segmentedControlColor.get()
|> deliverOnMainQueue).start(next: { [weak self] color in
if let strongSelf = self {
strongSelf.segmentedControl.tintColor = color
}
})
self.statusDisposable = (self.status.get()
|> deliverOnMainQueue).start(next: { [weak self] status in
if let strongSelf = self {
switch status {
case .Local:
strongSelf.toolbarButtonApply.isEnabled = true
strongSelf.toolbarButtonApply.alpha = 1.0
default:
strongSelf.toolbarButtonApply.isEnabled = false
strongSelf.toolbarButtonApply.alpha = 0.3
}
}
})
self.colorPanelNode.colorChanged = { [weak self] color in
if let strongSelf = self {
let entry = WallpaperEntry.wallpaper(.color(Int32(color.rgb)))
strongSelf.wallpapers = [entry]
strongSelf.centralWallpaper = entry
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.updateVisibleBackgroundNodes(layout: layout, navigationBarHeight: navigationHeight, transition: .immediate)
}
}
}
switch source {
case let .list(wallpapers, central, type):
self.wallpapers = wallpapers.map { .wallpaper($0) }
self.centralWallpaper = WallpaperEntry.wallpaper(central)
self.ready.set(true)
if case let .wallpapers(wallpaperMode) = type, let mode = wallpaperMode {
self.segmentedControl.selectedSegmentIndex = Int(clamping: mode.rawValue)
}
case let .slug(slug, file):
if let file = file {
let entry = WallpaperEntry.wallpaper(.file(id: 0, accessHash: 0, isCreator: false, isDefault: false, slug: slug, file: file))
self.wallpapers = [entry]
self.centralWallpaper = entry
}
self.ready.set(true)
case let .wallpaper(wallpaper):
let entry = WallpaperEntry.wallpaper(wallpaper)
self.wallpapers = [entry]
self.centralWallpaper = entry
self.ready.set(true)
case let .asset(asset, thumbnailImage):
let entry = WallpaperEntry.asset(asset, thumbnailImage)
self.wallpapers = [entry]
self.centralWallpaper = entry
self.ready.set(true)
case let .contextResult(result):
let entry = WallpaperEntry.contextResult(result)
self.wallpapers = [entry]
self.centralWallpaper = entry
self.ready.set(true)
case let .customColor(color):
let initialColor = color ?? 0x000000
let entry = WallpaperEntry.wallpaper(.color(initialColor))
self.wallpapers = [entry]
self.centralWallpaper = entry
self.ready.set(true)
}
if let (layout, navigationHeight) = self.validLayout {
self.updateVisibleBackgroundNodes(layout: layout, navigationBarHeight: navigationHeight, transition: .immediate)
}
if let wallpaper = self.centralWallpaper {
self.currentWallpaperPromise.set(.single(wallpaper))
}
if case let .customColor(colorValue) = self.source, let color = colorValue {
self.colorPanelNode.color = UIColor(rgb: UInt32(bitPattern: color))
}
}
deinit {
self.wallpapersDisposable?.dispose()
self.segmentedControlColorDisposable?.dispose()
self.statusDisposable?.dispose()
}
override func didLoad() {
super.didLoad()
self.view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))))
}
func updatePresentationData(_ presentationData: PresentationData) {
self.presentationData = presentationData
self.toolbarBackground.backgroundColor = self.presentationData.theme.rootController.navigationBar.backgroundColor
self.toolbarSeparator.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor
self.toolbarVerticalSeparator.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor
self.toolbarButtonCancel.setAttributedTitle(NSAttributedString(string: self.presentationData.strings.Common_Cancel, font: Font.regular(17.0), textColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor), for: [])
self.toolbarButtonApply.setAttributedTitle(NSAttributedString(string: self.presentationData.strings.Wallpaper_Set, font: Font.regular(17.0), textColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor), for: [])
self.toolbarButtonCancelBackground.backgroundColor = self.presentationData.theme.list.itemHighlightedBackgroundColor
self.toolbarButtonApplyBackground.backgroundColor = self.presentationData.theme.list.itemHighlightedBackgroundColor
self.backgroundColor = .black
if let (layout, navigationHeight) = self.validLayout {
self.updateVisibleBackgroundNodes(layout: layout, navigationBarHeight: navigationHeight, transition: .immediate)
}
}
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .began:
break
case .cancelled, .ended:
break
default:
break
}
}
private func didAppear() {
guard let centralNode = self.centralNode() else {
return
}
if self.segmentedControl.selectedSegmentIndex == 2 {
centralNode.setBlurEnabled(true, animated: true)
}
}
func animateIn() {
self.layer.animatePosition(from: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), to: self.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak self] _ in
self?.didAppear()
})
}
func animateOut(completion: @escaping () -> Void) {
self.layer.animatePosition(from: self.layer.position, to: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), duration: 0.2, timingFunction: kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: false, completion: { _ in
completion()
})
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.validLayout = (layout, navigationBarHeight)
var items: [ChatMessageItem] = []
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: 1)
let otherPeerId = self.context.account.peerId
var peers = SimpleDictionary<PeerId, Peer>()
let messages = SimpleDictionary<MessageId, Message>()
peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_PreviewReplyAuthor, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [])
peers[otherPeerId] = TelegramUser(id: otherPeerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_PreviewReplyAuthor, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [])
let controllerInteraction = ChatControllerInteraction(openMessage: { _, _ in
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _ in }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _ in }, openWallpaper: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
}, presentController: { _, _ in }, navigationController: {
return nil
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in }, longTap: { _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in
}, canSetupReply: { _ in
return false
}, navigateToFirstDateMessage: { _ in
}, requestRedeliveryOfFailedMessages: { _ in
}, addContact: { _ in
}, rateCall: { _, _ in
}, requestSelectMessagePollOption: { _, _ in
}, openAppStorePage: {
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: AutomaticMediaDownloadSettings.defaultSettings,
pollActionState: ChatInterfacePollActionState())
let chatPresentationData = ChatPresentationData(theme: ChatPresentationThemeData(theme: self.presentationData.theme, wallpaper: self.presentationData.chatWallpaper), fontSize: self.presentationData.fontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: false)
let topMessageText: String
let bottomMessageText: String
switch self.source {
case .wallpaper, .slug:
topMessageText = presentationData.strings.WallpaperPreview_PreviewTopText
bottomMessageText = presentationData.strings.WallpaperPreview_PreviewBottomText
case let .list(_, _, type):
switch type {
case .wallpapers:
topMessageText = presentationData.strings.WallpaperPreview_SwipeTopText
bottomMessageText = presentationData.strings.WallpaperPreview_SwipeBottomText
case .colors:
topMessageText = presentationData.strings.WallpaperPreview_SwipeColorsTopText
bottomMessageText = presentationData.strings.WallpaperPreview_SwipeColorsBottomText
}
case .asset, .contextResult:
topMessageText = presentationData.strings.WallpaperPreview_CropTopText
bottomMessageText = presentationData.strings.WallpaperPreview_CropBottomText
case .customColor:
topMessageText = presentationData.strings.WallpaperPreview_CustomColorTopText
bottomMessageText = presentationData.strings.WallpaperPreview_CustomColorBottomText
}
items.append(ChatMessageItem(presentationData: chatPresentationData, context: self.context, chatLocation: .peer(peerId), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .contact, automaticDownloadNetworkType: .cellular, isRecentActions: false), controllerInteraction: controllerInteraction, content: .message(message: Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: bottomMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), read: true, selection: .none, isAdmin: false), disableDate: true))
items.append(ChatMessageItem(presentationData: chatPresentationData, context: self.context, chatLocation: .peer(peerId), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .contact, automaticDownloadNetworkType: .cellular, isRecentActions: false), controllerInteraction: controllerInteraction, content: .message(message: Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: topMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), read: true, selection: .none, isAdmin: false), disableDate: true))
let params = ListViewItemLayoutParams(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right)
if let messageNodes = self.messageNodes {
for i in 0 ..< items.count {
let itemNode = messageNodes[i]
items[i].updateNode(async: { $0() }, node: {
return itemNode
}, params: params, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], animation: .None, completion: { (layout, apply) in
let nodeFrame = CGRect(origin: itemNode.frame.origin, size: CGSize(width: layout.size.width, height: layout.size.height))
itemNode.contentSize = layout.contentSize
itemNode.insets = layout.insets
itemNode.frame = nodeFrame
itemNode.isUserInteractionEnabled = false
apply(ListViewItemApply(isOnScreen: true))
})
}
} else {
var messageNodes: [ListViewItemNode] = []
for i in 0 ..< items.count {
var itemNode: ListViewItemNode?
items[i].nodeConfiguredForParams(async: { $0() }, params: params, synchronousLoads: false, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], completion: { node, apply in
itemNode = node
apply().1(ListViewItemApply(isOnScreen: true))
})
itemNode!.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
itemNode!.isUserInteractionEnabled = false
messageNodes.append(itemNode!)
self.addSubnode(itemNode!)
}
self.messageNodes = messageNodes
}
var bottomInset = layout.intrinsicInsets.bottom + 49.0
transition.updateFrame(node: self.toolbarBackground, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomInset), size: CGSize(width: layout.size.width, height: bottomInset)))
transition.updateFrame(node: self.toolbarSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomInset), size: CGSize(width: layout.size.width, height: UIScreenPixel)))
transition.updateFrame(node: self.toolbarVerticalSeparator, frame: CGRect(origin: CGPoint(x: floor(layout.size.width / 2.0), y: layout.size.height - bottomInset), size: CGSize(width: UIScreenPixel, height: bottomInset)))
let cancelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomInset), size: CGSize(width: floor(layout.size.width / 2.0), height: 49.0))
transition.updateFrame(node: self.toolbarButtonCancel, frame: cancelFrame)
transition.updateFrame(node: self.toolbarButtonCancelBackground, frame: cancelFrame)
let applyFrame = CGRect(origin: CGPoint(x: floor(layout.size.width / 2.0), y: layout.size.height - bottomInset), size: CGSize(width: ceil(layout.size.width / 2.0), height: 49.0))
transition.updateFrame(node: self.toolbarButtonApply, frame: applyFrame)
transition.updateFrame(node: self.toolbarButtonApplyBackground, frame: applyFrame)
if case .customColor = self.source {
let metrics = DeviceMetrics.forScreenSize(layout.size)
let standardInputHeight = metrics?.standardInputHeight(inLandscape: false) ?? 216.0
let height = standardInputHeight - bottomInset + 47.0
let colorPanelFrame = CGRect(x: 0.0, y: layout.size.height - bottomInset - height, width: layout.size.width, height: height)
transition.updateFrame(node: self.colorPanelNode, frame: colorPanelFrame)
self.colorPanelNode.updateLayout(size: colorPanelFrame.size, keyboardHeight: layout.inputHeight ?? 0.0, transition: transition)
bottomInset += height
}
let optionsAvailable = false //true
// if let centralWallpaper = self.centralWallpaper {
// switch centralWallpaper {
// case let .wallpaper(wallpaper):
// switch wallpaper {
// case .color:
// optionsAvailable = false
// default:
// break
// }
// default:
// break
// }
// }
var segmentedControlSize = self.segmentedControl.sizeThatFits(layout.size)
segmentedControlSize.width = max(270.0, segmentedControlSize.width)
self.segmentedControl.isUserInteractionEnabled = optionsAvailable
transition.updateAlpha(layer: self.segmentedControl.layer, alpha: optionsAvailable ? 1.0 : 0.0)
transition.updateFrame(view: self.segmentedControl, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + floor((layout.size.width - layout.safeInsets.left - layout.safeInsets.right - segmentedControlSize.width) / 2.0), y: layout.size.height - bottomInset - segmentedControlSize.height - 24.0), size: segmentedControlSize))
if let messageNodes = self.messageNodes {
var bottomOffset: CGFloat = layout.size.height - bottomInset - 9.0
if optionsAvailable {
bottomOffset -= segmentedControlSize.height + 37.0
}
for itemNode in messageNodes {
transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: bottomOffset - itemNode.frame.height), size: itemNode.frame.size))
bottomOffset -= itemNode.frame.height
}
}
self.updateVisibleBackgroundNodes(layout: layout, navigationBarHeight: navigationBarHeight, transition: transition)
}
private func updateVisibleBackgroundNodes(layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
var visibleBackgroundNodes: [WallpaperBackgroundNode] = []
if let wallpapers = self.wallpapers, let centralWallpaper = self.centralWallpaper {
outer: for i in 0 ..< wallpapers.count {
if wallpapers[i] == centralWallpaper {
for j in max(0, i - 1) ... min(i + 1, wallpapers.count - 1) {
let itemPostition = j - i
let itemFrame = CGRect(origin: CGPoint(x: CGFloat(itemPostition) * layout.size.width, y: 0.0), size: layout.size)
var currentItemNode: WallpaperBackgroundNode?
inner: for current in self.visibleBackgroundNodes {
if current.wallpaper == wallpapers[j] {
currentItemNode = current
break inner
}
}
let itemNode = currentItemNode ?? WallpaperBackgroundNode(context: self.context, wallpaper: wallpapers[j])
visibleBackgroundNodes.append(itemNode)
let itemNodeTransition: ContainedViewLayoutTransition
if itemNode.supernode == nil {
self.insertSubnode(itemNode, at: 0)
itemNodeTransition = .immediate
} else {
itemNodeTransition = transition
}
if j == i {
self.segmentedControlColor.set(itemNode.controlsColor.get())
self.status.set(itemNode.status.get())
}
itemNodeTransition.updateFrame(node: itemNode, frame: itemFrame)
itemNode.updateLayout(layout, navigationHeight: navigationBarHeight, transition: itemNodeTransition)
visibleBackgroundNodes.append(itemNode)
}
break outer
}
}
}
for itemNode in self.visibleBackgroundNodes {
var found = false
inner: for updatedItemNode in visibleBackgroundNodes {
if itemNode === updatedItemNode {
found = true
break
}
}
if !found {
itemNode.removeFromSupernode()
}
}
self.visibleBackgroundNodes = visibleBackgroundNodes
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let (layout, _) = self.validLayout {
let additionalButtonHeight = layout.intrinsicInsets.bottom
if self.toolbarButtonCancel.isEnabled {
var buttonFrame = self.toolbarButtonCancel.frame
buttonFrame.size.height += additionalButtonHeight
if buttonFrame.contains(point) {
return self.toolbarButtonCancel.view
}
}
if self.toolbarButtonApply.isEnabled {
var buttonFrame = self.toolbarButtonApply.frame
buttonFrame.size.height += additionalButtonHeight
if buttonFrame.contains(point) {
return self.toolbarButtonApply.view
}
}
}
return super.hitTest(point, with: event)
}
private func centralNode() -> WallpaperBackgroundNode? {
for node in self.visibleBackgroundNodes {
if node.wallpaper == self.centralWallpaper {
return node
}
}
return nil
}
@objc private func indexChanged() {
let index = self.segmentedControl.selectedSegmentIndex
if let node = self.centralNode() {
if index == 1 {
node.setParallaxEnabled(true)
node.setBlurEnabled(false, animated: true)
} else if index == 2 {
node.setParallaxEnabled(false)
node.setBlurEnabled(true, animated: true)
} else {
node.setParallaxEnabled(false)
node.setBlurEnabled(false, animated: true)
}
}
}
@objc private func cancelPressed() {
self.dismiss()
}
@objc private func applyPressed() {
if let wallpaper = self.centralWallpaper {
var options: WallpaperPresentationOptions = []
switch self.segmentedControl.selectedSegmentIndex {
case 1:
options.insert(.motion)
case 2:
options.insert(.blur)
default:
break
}
self.apply(wallpaper, options, self.centralNode()?.cropRect)
self.isUserInteractionEnabled = false
}
}
}