mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-17 11:50:56 +00:00
Wallpaper improvements
This commit is contained in:
parent
47e538ca1e
commit
6a95330842
@ -1,13 +1,12 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import SwiftSignalKit
|
||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
import Accelerate
|
import Accelerate
|
||||||
|
|
||||||
private class BlurLayer: CALayer {
|
private class BlurLayer: CALayer {
|
||||||
private static let blurRadiusKey = "blurRadius"
|
private static let blurRadiusKey = "blurRadius"
|
||||||
private static let blurLayoutKey = "blurLayout"
|
|
||||||
@NSManaged var blurRadius: CGFloat
|
@NSManaged var blurRadius: CGFloat
|
||||||
@NSManaged private var blurLayout: CGFloat
|
|
||||||
|
|
||||||
private var fromBlurRadius: CGFloat?
|
private var fromBlurRadius: CGFloat?
|
||||||
var presentationRadius: CGFloat {
|
var presentationRadius: CGFloat {
|
||||||
@ -23,7 +22,7 @@ private class BlurLayer: CALayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override class func needsDisplay(forKey key: String) -> Bool {
|
override class func needsDisplay(forKey key: String) -> Bool {
|
||||||
if key == blurRadiusKey || key == blurLayoutKey {
|
if key == blurRadiusKey {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return super.needsDisplay(forKey: key)
|
return super.needsDisplay(forKey: key)
|
||||||
@ -42,13 +41,6 @@ private class BlurLayer: CALayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if event == BlurLayer.blurLayoutKey, let action = super.action(forKey: "opacity") as? CABasicAnimation {
|
|
||||||
action.keyPath = event
|
|
||||||
action.fromValue = 0
|
|
||||||
action.toValue = 1
|
|
||||||
return action
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.action(forKey: event)
|
return super.action(forKey: event)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,17 +50,6 @@ private class BlurLayer: CALayer {
|
|||||||
self.contentsGravity = kCAGravityResizeAspectFill
|
self.contentsGravity = kCAGravityResizeAspectFill
|
||||||
}
|
}
|
||||||
|
|
||||||
func refresh() {
|
|
||||||
self.fromBlurRadius = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func animate() {
|
|
||||||
UIView.performWithoutAnimation {
|
|
||||||
self.blurLayout = 0
|
|
||||||
}
|
|
||||||
self.blurLayout = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func render(in context: CGContext, for layer: CALayer) {
|
func render(in context: CGContext, for layer: CALayer) {
|
||||||
layer.render(in: context)
|
layer.render(in: context)
|
||||||
}
|
}
|
||||||
@ -79,20 +60,14 @@ class BlurView: UIView {
|
|||||||
return BlurLayer.self
|
return BlurLayer.self
|
||||||
}
|
}
|
||||||
|
|
||||||
private var displayLink: CADisplayLink?
|
|
||||||
private var blurLayer: BlurLayer {
|
private var blurLayer: BlurLayer {
|
||||||
return self.layer as! BlurLayer
|
return self.layer as! BlurLayer
|
||||||
}
|
}
|
||||||
|
|
||||||
var image: UIImage?
|
var image: UIImage?
|
||||||
|
|
||||||
private let mainQueue = DispatchQueue.main
|
private let queue: Queue = {
|
||||||
private let globalQueue: DispatchQueue = {
|
return Queue(name: nil, qos: .userInteractive)
|
||||||
if #available (iOS 8.0, *) {
|
|
||||||
return .global(qos: .userInteractive)
|
|
||||||
} else {
|
|
||||||
return .global(priority: .high)
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
open var blurRadius: CGFloat {
|
open var blurRadius: CGFloat {
|
||||||
@ -110,17 +85,6 @@ class BlurView: UIView {
|
|||||||
self.isUserInteractionEnabled = false
|
self.isUserInteractionEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
open override func didMoveToSuperview() {
|
|
||||||
super.didMoveToSuperview()
|
|
||||||
|
|
||||||
if self.superview == nil {
|
|
||||||
self.displayLink?.invalidate()
|
|
||||||
self.displayLink = nil
|
|
||||||
} else {
|
|
||||||
self.linkForDisplay()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func async(on queue: DispatchQueue, actions: @escaping () -> Void) {
|
private func async(on queue: DispatchQueue, actions: @escaping () -> Void) {
|
||||||
queue.async(execute: actions)
|
queue.async(execute: actions)
|
||||||
}
|
}
|
||||||
@ -130,9 +94,9 @@ class BlurView: UIView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func draw(_ image: UIImage, blurRadius: CGFloat) {
|
private func draw(_ image: UIImage, blurRadius: CGFloat) {
|
||||||
async(on: globalQueue) { [weak self] in
|
self.queue.async { [weak self] in
|
||||||
if let strongSelf = self, let blurredImage = blurredImage(image, radius: blurRadius) {
|
if let strongSelf = self, let blurredImage = blurredImage(image, radius: blurRadius) {
|
||||||
strongSelf.sync(on: strongSelf.mainQueue) {
|
Queue.mainQueue().sync {
|
||||||
strongSelf.blurLayer.draw(blurredImage)
|
strongSelf.blurLayer.draw(blurredImage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -145,22 +109,13 @@ class BlurView: UIView {
|
|||||||
self.draw(image, blurRadius: blurRadius)
|
self.draw(image, blurRadius: blurRadius)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func linkForDisplay() {
|
|
||||||
self.displayLink?.invalidate()
|
|
||||||
self.displayLink = UIScreen.main.displayLink(withTarget: self, selector: #selector(BlurView.displayDidRefresh(_:)))
|
|
||||||
self.displayLink?.add(to: .main, forMode: RunLoop.Mode(rawValue: ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func displayDidRefresh(_ displayLink: CADisplayLink) {
|
|
||||||
self.display(self.layer)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final class BlurredImageNode: ASDisplayNode {
|
final class BlurredImageNode: ASDisplayNode {
|
||||||
var image: UIImage? {
|
var image: UIImage? {
|
||||||
didSet {
|
didSet {
|
||||||
self.blurView.image = self.image
|
self.blurView.image = self.image
|
||||||
|
self.blurView.layer.setNeedsDisplay()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -242,7 +242,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
|||||||
self.presentationData = (account.applicationContext as! TelegramApplicationContext).currentPresentationData.with { $0 }
|
self.presentationData = (account.applicationContext as! TelegramApplicationContext).currentPresentationData.with { $0 }
|
||||||
self.automaticMediaDownloadSettings = (account.applicationContext as! TelegramApplicationContext).currentAutomaticMediaDownloadSettings.with { $0 }
|
self.automaticMediaDownloadSettings = (account.applicationContext as! TelegramApplicationContext).currentAutomaticMediaDownloadSettings.with { $0 }
|
||||||
|
|
||||||
self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, chatWallpaperMode: self.presentationData.chatWallpaperMode, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, fontSize: self.presentationData.fontSize, accountPeerId: account.peerId, mode: mode, chatLocation: chatLocation)
|
self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, chatWallpaperMode: self.presentationData.chatWallpaperOptions, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, fontSize: self.presentationData.fontSize, accountPeerId: account.peerId, mode: mode, chatLocation: chatLocation)
|
||||||
|
|
||||||
var mediaAccessoryPanelVisibility = MediaAccessoryPanelVisibility.none
|
var mediaAccessoryPanelVisibility = MediaAccessoryPanelVisibility.none
|
||||||
if case .standard = mode {
|
if case .standard = mode {
|
||||||
@ -1656,7 +1656,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
|||||||
state = state.updatedTheme(self.presentationData.theme)
|
state = state.updatedTheme(self.presentationData.theme)
|
||||||
state = state.updatedStrings(self.presentationData.strings)
|
state = state.updatedStrings(self.presentationData.strings)
|
||||||
state = state.updatedDateTimeFormat(self.presentationData.dateTimeFormat)
|
state = state.updatedDateTimeFormat(self.presentationData.dateTimeFormat)
|
||||||
state = state.updatedChatWallpaper(self.presentationData.chatWallpaper, mode: self.presentationData.chatWallpaperMode)
|
state = state.updatedChatWallpaper(self.presentationData.chatWallpaper, mode: self.presentationData.chatWallpaperOptions)
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -5419,7 +5419,6 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func openUrlIn(_ url: String) {
|
private func openUrlIn(_ url: String) {
|
||||||
if let applicationContext = self.account.applicationContext as? TelegramApplicationContext {
|
|
||||||
let actionSheet = OpenInActionSheetController(account: self.account, item: .url(url: url), openUrl: { [weak self] url in
|
let actionSheet = OpenInActionSheetController(account: self.account, item: .url(url: url), openUrl: { [weak self] url in
|
||||||
if let strongSelf = self, let applicationContext = strongSelf.account.applicationContext as? TelegramApplicationContext, let navigationController = strongSelf.navigationController as? NavigationController {
|
if let strongSelf = self, let applicationContext = strongSelf.account.applicationContext as? TelegramApplicationContext, let navigationController = strongSelf.navigationController as? NavigationController {
|
||||||
openExternalUrl(account: strongSelf.account, url: url, forceExternal: true, presentationData: strongSelf.presentationData, applicationContext: applicationContext, navigationController: navigationController, dismissInput: {
|
openExternalUrl(account: strongSelf.account, url: url, forceExternal: true, presentationData: strongSelf.presentationData, applicationContext: applicationContext, navigationController: navigationController, dismissInput: {
|
||||||
@ -5430,7 +5429,6 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
|||||||
self.chatDisplayNode.dismissInput()
|
self.chatDisplayNode.dismissInput()
|
||||||
self.present(actionSheet, in: .window(.root))
|
self.present(actionSheet, in: .window(.root))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func avatarPreviewingController(from sourceView: UIView) -> (UIViewController, CGRect)? {
|
func avatarPreviewingController(from sourceView: UIView) -> (UIViewController, CGRect)? {
|
||||||
guard let buttonView = (self.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.avatarNode.view else {
|
guard let buttonView = (self.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.avatarNode.view else {
|
||||||
@ -5945,9 +5943,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
|||||||
|
|
||||||
if true {
|
if true {
|
||||||
inputShortcuts.append(KeyShortcut(input: UIKeyInputUpArrow, action: { [weak self] in
|
inputShortcuts.append(KeyShortcut(input: UIKeyInputUpArrow, action: { [weak self] in
|
||||||
if let strongSelf = self {
|
|
||||||
|
|
||||||
}
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5978,8 +5974,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
KeyShortcut(input: "W", modifiers: [.command], action: { [weak self] in
|
KeyShortcut(input: "W", modifiers: [.command], action: { [weak self] in
|
||||||
if let strongSelf = self {
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -4,32 +4,32 @@ import Display
|
|||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import Postbox
|
import Postbox
|
||||||
|
|
||||||
|
private let motionAmount: CGFloat = 32.0
|
||||||
|
|
||||||
final class ChatBackgroundNode: ASDisplayNode {
|
final class ChatBackgroundNode: ASDisplayNode {
|
||||||
let contentNode: ASDisplayNode
|
let contentNode: ASDisplayNode
|
||||||
|
|
||||||
var parallaxEnabled: Bool = false {
|
var motionEnabled: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
if oldValue != self.parallaxEnabled {
|
if oldValue != self.motionEnabled {
|
||||||
if self.parallaxEnabled {
|
if self.motionEnabled {
|
||||||
let amount = 24.0
|
|
||||||
|
|
||||||
let horizontal = UIInterpolatingMotionEffect(keyPath: "center.x", type: .tiltAlongHorizontalAxis)
|
let horizontal = UIInterpolatingMotionEffect(keyPath: "center.x", type: .tiltAlongHorizontalAxis)
|
||||||
horizontal.minimumRelativeValue = -amount
|
horizontal.minimumRelativeValue = motionAmount
|
||||||
horizontal.maximumRelativeValue = amount
|
horizontal.maximumRelativeValue = -motionAmount
|
||||||
|
|
||||||
let vertical = UIInterpolatingMotionEffect(keyPath: "center.y", type: .tiltAlongVerticalAxis)
|
let vertical = UIInterpolatingMotionEffect(keyPath: "center.y", type: .tiltAlongVerticalAxis)
|
||||||
vertical.minimumRelativeValue = -amount
|
vertical.minimumRelativeValue = motionAmount
|
||||||
vertical.maximumRelativeValue = amount
|
vertical.maximumRelativeValue = -motionAmount
|
||||||
|
|
||||||
let group = UIMotionEffectGroup()
|
let group = UIMotionEffectGroup()
|
||||||
group.motionEffects = [horizontal, vertical]
|
group.motionEffects = [horizontal, vertical]
|
||||||
|
|
||||||
self.contentNode.view.addMotionEffect(group)
|
self.contentNode.view.addMotionEffect(group)
|
||||||
} else {
|
} else {
|
||||||
for effect in self.contentNode.view.motionEffects {
|
for effect in self.contentNode.view.motionEffects {
|
||||||
self.contentNode.view.removeMotionEffect(effect)
|
self.contentNode.view.removeMotionEffect(effect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.updateScale()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -40,6 +40,15 @@ final class ChatBackgroundNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateScale() {
|
||||||
|
if self.motionEnabled {
|
||||||
|
let scale = (self.frame.width + motionAmount * 2.0) / self.frame.width
|
||||||
|
self.contentNode.transform = CATransform3DMakeScale(scale, scale, 1.0)
|
||||||
|
} else {
|
||||||
|
self.contentNode.transform = CATransform3DIdentity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
self.contentNode = ASDisplayNode()
|
self.contentNode = ASDisplayNode()
|
||||||
self.contentNode.contentMode = .scaleAspectFill
|
self.contentNode.contentMode = .scaleAspectFill
|
||||||
@ -53,7 +62,9 @@ final class ChatBackgroundNode: ASDisplayNode {
|
|||||||
|
|
||||||
override func layout() {
|
override func layout() {
|
||||||
super.layout()
|
super.layout()
|
||||||
self.contentNode.frame = self.bounds
|
self.contentNode.bounds = self.bounds
|
||||||
|
self.contentNode.position = CGPoint(x: self.bounds.midX, y: self.bounds.midY)
|
||||||
|
self.updateScale()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,11 +125,20 @@ func chatControllerBackgroundImage(wallpaper: TelegramWallpaper, mode: Wallpaper
|
|||||||
private func serviceColor(for data: Signal<MediaResourceData, NoError>) -> Signal<UIColor, NoError> {
|
private func serviceColor(for data: Signal<MediaResourceData, NoError>) -> Signal<UIColor, NoError> {
|
||||||
return data
|
return data
|
||||||
|> mapToSignal { data -> Signal<UIColor, NoError> in
|
|> mapToSignal { data -> Signal<UIColor, NoError> in
|
||||||
if data.complete {
|
if data.complete, let image = UIImage(contentsOfFile: data.path) {
|
||||||
let image = UIImage(contentsOfFile: data.path)
|
return serviceColor(from: .single(image))
|
||||||
|
}
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func serviceColor(from image: Signal<UIImage?, NoError>) -> Signal<UIColor, NoError> {
|
||||||
|
return image
|
||||||
|
|> mapToSignal { image -> Signal<UIColor, NoError> in
|
||||||
|
if let image = image {
|
||||||
let context = DrawingContext(size: CGSize(width: 1.0, height: 1.0), scale: 1.0, clear: false)
|
let context = DrawingContext(size: CGSize(width: 1.0, height: 1.0), scale: 1.0, clear: false)
|
||||||
context.withFlippedContext({ context in
|
context.withFlippedContext({ context in
|
||||||
if let cgImage = image?.cgImage {
|
if let cgImage = image.cgImage {
|
||||||
context.draw(cgImage, in: CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0))
|
context.draw(cgImage, in: CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -187,112 +207,3 @@ func chatServiceBackgroundColor(wallpaper: TelegramWallpaper, postbox: Postbox)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func chatBackgroundContrastColor(wallpaper: TelegramWallpaper, postbox: Postbox) -> Signal<UIColor, NoError> {
|
|
||||||
switch wallpaper {
|
|
||||||
case .builtin:
|
|
||||||
return .single(UIColor(rgb: 0x888f96))
|
|
||||||
case let .color(color):
|
|
||||||
return .single(contrastingColor(for: UIColor(rgb: UInt32(bitPattern: color))))
|
|
||||||
case let .image(representations):
|
|
||||||
if let largest = largestImageRepresentation(representations) {
|
|
||||||
return Signal<UIColor, NoError> { subscriber in
|
|
||||||
let fetch = postbox.mediaBox.fetchedResource(largest.resource, parameters: nil).start()
|
|
||||||
let imageSignal = postbox.mediaBox.resourceData(largest.resource)
|
|
||||||
|> mapToSignal { data -> Signal<UIImage?, NoError> in
|
|
||||||
if data.complete, let image = UIImage(contentsOfFile: data.path) {
|
|
||||||
return .single(image)
|
|
||||||
} else {
|
|
||||||
return .complete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let data = backgroundContrastColor(for: imageSignal).start(next: { next in
|
|
||||||
subscriber.putNext(next)
|
|
||||||
}, completed: {
|
|
||||||
subscriber.putCompletion()
|
|
||||||
})
|
|
||||||
return ActionDisposable {
|
|
||||||
fetch.dispose()
|
|
||||||
data.dispose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return .single(.white)
|
|
||||||
}
|
|
||||||
case let .file(file):
|
|
||||||
return Signal<UIColor, NoError> { subscriber in
|
|
||||||
let fetch = postbox.mediaBox.fetchedResource(file.file.resource, parameters: nil).start()
|
|
||||||
let imageSignal = postbox.mediaBox.resourceData(file.file.resource)
|
|
||||||
|> mapToSignal { data -> Signal<UIImage?, NoError> in
|
|
||||||
if data.complete, let image = UIImage(contentsOfFile: data.path) {
|
|
||||||
return .single(image)
|
|
||||||
} else {
|
|
||||||
return .complete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let data = backgroundContrastColor(for: imageSignal).start(next: { next in
|
|
||||||
subscriber.putNext(next)
|
|
||||||
}, completed: {
|
|
||||||
subscriber.putCompletion()
|
|
||||||
})
|
|
||||||
return ActionDisposable {
|
|
||||||
fetch.dispose()
|
|
||||||
data.dispose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func backgroundContrastColor(for image: Signal<UIImage?, NoError>) -> Signal<UIColor, NoError> {
|
|
||||||
return image
|
|
||||||
|> map { image -> UIColor in
|
|
||||||
if let image = image {
|
|
||||||
let context = DrawingContext(size: CGSize(width: 128.0, height: 32.0), scale: 1.0, clear: false)
|
|
||||||
context.withFlippedContext({ context in
|
|
||||||
if let cgImage = image.cgImage {
|
|
||||||
let size = image.size.aspectFilled(CGSize(width: 128.0, height: 128.0))
|
|
||||||
context.draw(cgImage, in: CGRect(x: floor((128.0 - size.width) / 2.0), y: 0.0, width: size.width, height: size.height))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
var matching: Int = 0
|
|
||||||
let total: Int = Int(context.size.width) * Int(context.size.height)
|
|
||||||
for y in 0 ..< Int(context.size.height) {
|
|
||||||
for x in 0 ..< Int(context.size.width) {
|
|
||||||
var saturation: CGFloat = 0.0
|
|
||||||
var brightness: CGFloat = 0.0
|
|
||||||
if context.colorAt(CGPoint(x: x, y: y)).getHue(nil, saturation: nil, brightness: &brightness, alpha: nil) {
|
|
||||||
if brightness > 0.6 {
|
|
||||||
matching += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if CGFloat(matching) / CGFloat(total) > 0.4 {
|
|
||||||
return .black
|
|
||||||
} else {
|
|
||||||
return .white
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return .black
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func contrastingColor(for color: UIColor) -> UIColor {
|
|
||||||
var red: CGFloat = 0.0
|
|
||||||
var green: CGFloat = 0.0
|
|
||||||
var blue: CGFloat = 0.0
|
|
||||||
var luminance: CGFloat = 0.0
|
|
||||||
|
|
||||||
if color.getRed(&red, green: &green, blue: &blue, alpha: nil) {
|
|
||||||
luminance = red * 0.2126 + green * 0.7152 + blue * 0.0722
|
|
||||||
} else if color.getWhite(&luminance, alpha: nil) {
|
|
||||||
}
|
|
||||||
|
|
||||||
if luminance > 0.6 {
|
|
||||||
return .black
|
|
||||||
} else {
|
|
||||||
return .white
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -239,9 +239,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.backgroundNode.image = chatControllerBackgroundImage(wallpaper: chatPresentationInterfaceState.chatWallpaper, mode: chatPresentationInterfaceState.chatWallpaperMode, postbox: account.postbox)
|
self.backgroundNode.image = chatControllerBackgroundImage(wallpaper: chatPresentationInterfaceState.chatWallpaper, mode: chatPresentationInterfaceState.chatWallpaperMode, postbox: account.postbox)
|
||||||
if chatPresentationInterfaceState.chatWallpaperMode.contains(.motion) {
|
self.backgroundNode.motionEnabled = chatPresentationInterfaceState.chatWallpaperMode.contains(.motion)
|
||||||
self.backgroundNode.parallaxEnabled = true
|
|
||||||
}
|
|
||||||
self.historyNode.verticalScrollIndicatorColor = UIColor(white: 0.5, alpha: 0.8)
|
self.historyNode.verticalScrollIndicatorColor = UIColor(white: 0.5, alpha: 0.8)
|
||||||
|
|
||||||
self.addSubnode(self.backgroundNode)
|
self.addSubnode(self.backgroundNode)
|
||||||
@ -1316,12 +1315,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
if self.chatPresentationInterfaceState.chatWallpaper != chatPresentationInterfaceState.chatWallpaper || self.chatPresentationInterfaceState.chatWallpaperMode != chatPresentationInterfaceState.chatWallpaperMode {
|
if self.chatPresentationInterfaceState.chatWallpaper != chatPresentationInterfaceState.chatWallpaper || self.chatPresentationInterfaceState.chatWallpaperMode != chatPresentationInterfaceState.chatWallpaperMode {
|
||||||
self.backgroundNode.image = chatControllerBackgroundImage(wallpaper: chatPresentationInterfaceState.chatWallpaper, mode: chatPresentationInterfaceState.chatWallpaperMode, postbox: account.postbox)
|
self.backgroundNode.image = chatControllerBackgroundImage(wallpaper: chatPresentationInterfaceState.chatWallpaper, mode: chatPresentationInterfaceState.chatWallpaperMode, postbox: account.postbox)
|
||||||
|
self.backgroundNode.motionEnabled = chatPresentationInterfaceState.chatWallpaperMode.contains(.motion)
|
||||||
if chatPresentationInterfaceState.chatWallpaperMode.contains(.motion) {
|
|
||||||
self.backgroundNode.parallaxEnabled = true
|
|
||||||
} else {
|
|
||||||
self.backgroundNode.parallaxEnabled = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.historyNode.verticalScrollIndicatorColor = UIColor(white: 0.5, alpha: 0.8)
|
self.historyNode.verticalScrollIndicatorColor = UIColor(white: 0.5, alpha: 0.8)
|
||||||
|
|||||||
@ -529,7 +529,7 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie
|
|||||||
|
|
||||||
self.chatListDisplayNode.requestOpenRecentPeerOptions = { [weak self] peer in
|
self.chatListDisplayNode.requestOpenRecentPeerOptions = { [weak self] peer in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.view.endEditing(true)
|
strongSelf.view.window?.endEditing(true)
|
||||||
let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme)
|
let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme)
|
||||||
|
|
||||||
actionSheet.setItemGroups([
|
actionSheet.setItemGroups([
|
||||||
|
|||||||
@ -73,8 +73,8 @@ class ContactListActionItem: ListViewItem {
|
|||||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||||
async {
|
async {
|
||||||
let node = ContactListActionItemNode()
|
let node = ContactListActionItemNode()
|
||||||
let (_, _, firstWithHeader) = ContactListActionItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem)
|
let (_, last, firstWithHeader) = ContactListActionItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem)
|
||||||
let (layout, apply) = node.asyncLayout()(self, params, firstWithHeader)
|
let (layout, apply) = node.asyncLayout()(self, params, firstWithHeader, last)
|
||||||
|
|
||||||
node.contentSize = layout.contentSize
|
node.contentSize = layout.contentSize
|
||||||
node.insets = layout.insets
|
node.insets = layout.insets
|
||||||
@ -93,8 +93,8 @@ class ContactListActionItem: ListViewItem {
|
|||||||
let makeLayout = nodeValue.asyncLayout()
|
let makeLayout = nodeValue.asyncLayout()
|
||||||
|
|
||||||
async {
|
async {
|
||||||
let (_, _, firstWithHeader) = ContactListActionItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem)
|
let (_, last, firstWithHeader) = ContactListActionItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem)
|
||||||
let (layout, apply) = makeLayout(self, params, firstWithHeader)
|
let (layout, apply) = makeLayout(self, params, firstWithHeader, last)
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
completion(layout, { _ in
|
completion(layout, { _ in
|
||||||
apply()
|
apply()
|
||||||
@ -192,11 +192,11 @@ class ContactListActionItemNode: ListViewItemNode {
|
|||||||
self.addSubnode(self.titleNode)
|
self.addSubnode(self.titleNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func asyncLayout() -> (_ item: ContactListActionItem, _ params: ListViewItemLayoutParams, _ firstWithHeader: Bool) -> (ListViewItemNodeLayout, () -> Void) {
|
func asyncLayout() -> (_ item: ContactListActionItem, _ params: ListViewItemLayoutParams, _ firstWithHeader: Bool, _ last: Bool) -> (ListViewItemNodeLayout, () -> Void) {
|
||||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||||
let currentTheme = self.theme
|
let currentTheme = self.theme
|
||||||
|
|
||||||
return { item, params, firstWithHeader in
|
return { item, params, firstWithHeader, last in
|
||||||
var updatedTheme: PresentationTheme?
|
var updatedTheme: PresentationTheme?
|
||||||
|
|
||||||
if currentTheme !== item.theme {
|
if currentTheme !== item.theme {
|
||||||
@ -233,7 +233,7 @@ class ContactListActionItemNode: ListViewItemNode {
|
|||||||
let _ = titleApply()
|
let _ = titleApply()
|
||||||
|
|
||||||
var titleOffset = leftInset
|
var titleOffset = leftInset
|
||||||
var hideBottomStripe: Bool = false
|
var hideBottomStripe: Bool = last
|
||||||
if let image = item.icon.image {
|
if let image = item.icon.image {
|
||||||
var iconFrame: CGRect
|
var iconFrame: CGRect
|
||||||
switch item.icon {
|
switch item.icon {
|
||||||
@ -268,7 +268,7 @@ class ContactListActionItemNode: ListViewItemNode {
|
|||||||
strongSelf.topStripeNode.isHidden = true
|
strongSelf.topStripeNode.isHidden = true
|
||||||
strongSelf.bottomStripeNode.isHidden = hideBottomStripe
|
strongSelf.bottomStripeNode.isHidden = hideBottomStripe
|
||||||
|
|
||||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height), size: CGSize(width: params.width - leftInset, height: separatorHeight))
|
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight))
|
||||||
|
|
||||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: titleOffset, y: floor((contentSize.height - titleLayout.size.height) / 2.0)), size: titleLayout.size)
|
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: titleOffset, y: floor((contentSize.height - titleLayout.size.height) / 2.0)), size: titleLayout.size)
|
||||||
|
|
||||||
|
|||||||
@ -281,8 +281,6 @@ class ContactsPeerItem: ListViewItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private let separatorHeight = 1.0 / UIScreen.main.scale
|
|
||||||
|
|
||||||
private let avatarFont: UIFont = UIFont(name: ".SFCompactRounded-Semibold", size: 16.0)!
|
private let avatarFont: UIFont = UIFont(name: ".SFCompactRounded-Semibold", size: 16.0)!
|
||||||
|
|
||||||
class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||||
@ -771,6 +769,8 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
strongSelf.selectionNode = nil
|
strongSelf.selectionNode = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let separatorHeight = UIScreenPixel
|
||||||
|
|
||||||
let topHighlightInset: CGFloat = (first || !nodeLayout.insets.top.isZero) ? 0.0 : separatorHeight
|
let topHighlightInset: CGFloat = (first || !nodeLayout.insets.top.isZero) ? 0.0 : separatorHeight
|
||||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: nodeLayout.contentSize.width, height: nodeLayout.contentSize.height))
|
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: nodeLayout.contentSize.width, height: nodeLayout.contentSize.height))
|
||||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: nodeLayout.size.width, height: nodeLayout.size.height + topHighlightInset))
|
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: nodeLayout.size.width, height: nodeLayout.size.height + topHighlightInset))
|
||||||
|
|||||||
@ -15,7 +15,7 @@ final class CounterContollerTitleView: UIView {
|
|||||||
var title: CounterContollerTitle = CounterContollerTitle(title: "", counter: "") {
|
var title: CounterContollerTitle = CounterContollerTitle(title: "", counter: "") {
|
||||||
didSet {
|
didSet {
|
||||||
if self.title != oldValue {
|
if self.title != oldValue {
|
||||||
self.titleNode.attributedText = NSAttributedString(string: self.title.title, font: Font.medium(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor)
|
self.titleNode.attributedText = NSAttributedString(string: self.title.title, font: Font.bold(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor)
|
||||||
self.subtitleNode.attributedText = NSAttributedString(string: self.title.counter, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
self.subtitleNode.attributedText = NSAttributedString(string: self.title.counter, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
||||||
|
|
||||||
self.setNeedsLayout()
|
self.setNeedsLayout()
|
||||||
|
|||||||
@ -34,6 +34,7 @@ private var telegramUIDeclaredEncodables: Void = {
|
|||||||
declareEncodable(RecentWebSearchQueryItem.self, f: { RecentWebSearchQueryItem(decoder: $0) })
|
declareEncodable(RecentWebSearchQueryItem.self, f: { RecentWebSearchQueryItem(decoder: $0) })
|
||||||
declareEncodable(RecentWallpaperSearchQueryItem.self, f: { RecentWallpaperSearchQueryItem(decoder: $0) })
|
declareEncodable(RecentWallpaperSearchQueryItem.self, f: { RecentWallpaperSearchQueryItem(decoder: $0) })
|
||||||
declareEncodable(VoipDerivedState.self, f: { VoipDerivedState(decoder: $0) })
|
declareEncodable(VoipDerivedState.self, f: { VoipDerivedState(decoder: $0) })
|
||||||
|
declareEncodable(PresentationThemeSpecificSettings.self, f: { PresentationThemeSpecificSettings(decoder: $0) })
|
||||||
return
|
return
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|||||||
@ -46,3 +46,22 @@ func fixNavigationSearchableListNodeScrolling(_ listNode: ListView, searchNode:
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fixNavigationSearchableGridNodeScrolling(_ gridNode: GridNode, searchNode: NavigationBarSearchContentNode) -> Bool {
|
||||||
|
// if searchNode.expansionProgress > 0.0 && searchNode.expansionProgress < 1.0 {
|
||||||
|
// let scrollToItem: ListViewScrollToItem
|
||||||
|
// let targetProgress: CGFloat
|
||||||
|
// if searchNode.expansionProgress < 0.6 {
|
||||||
|
// scrollToItem = ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: true, curve: .Default(duration: 0.3), directionHint: .Up)
|
||||||
|
// targetProgress = 0.0
|
||||||
|
// } else {
|
||||||
|
// scrollToItem = ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: 0.3), directionHint: .Up)
|
||||||
|
// targetProgress = 1.0
|
||||||
|
// }
|
||||||
|
// searchNode.updateExpansionProgress(targetProgress, animated: true)
|
||||||
|
//
|
||||||
|
// listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: scrollToItem, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||||
|
// return true
|
||||||
|
// }
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@ -300,6 +300,9 @@ class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGestureRecog
|
|||||||
func updateDismissTransition(_ value: CGFloat) {
|
func updateDismissTransition(_ value: CGFloat) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateDistanceFromEquilibrium(_ value: CGFloat) {
|
||||||
|
}
|
||||||
|
|
||||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
if self.isDismissed {
|
if self.isDismissed {
|
||||||
return
|
return
|
||||||
@ -318,6 +321,7 @@ class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGestureRecog
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.updateDismissTransition(transition)
|
self.updateDismissTransition(transition)
|
||||||
|
self.updateDistanceFromEquilibrium(distanceFromEquilibrium)
|
||||||
|
|
||||||
if let overlayNode = self.overlayNode {
|
if let overlayNode = self.overlayNode {
|
||||||
overlayNode.alpha = transition
|
overlayNode.alpha = transition
|
||||||
|
|||||||
@ -62,6 +62,9 @@ open class GalleryItemNode: ASDisplayNode {
|
|||||||
open func centralityUpdated(isCentral: Bool) {
|
open func centralityUpdated(isCentral: Bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open func screenFrameUpdated(_ frame: CGRect) {
|
||||||
|
}
|
||||||
|
|
||||||
open func activateAsInitial() {
|
open func activateAsInitial() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -307,7 +307,11 @@ final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i in 0 ..< self.itemNodes.count {
|
for i in 0 ..< self.itemNodes.count {
|
||||||
transition.updateFrame(node: self.itemNodes[i], frame: CGRect(origin: CGPoint(x: CGFloat(i) * self.scrollView.bounds.size.width + self.pageGap, y: 0.0), size: CGSize(width: self.scrollView.bounds.size.width - self.pageGap * 2.0, height: self.scrollView.bounds.size.height)))
|
let node = self.itemNodes[i]
|
||||||
|
transition.updateFrame(node: node, frame: CGRect(origin: CGPoint(x: CGFloat(i) * self.scrollView.bounds.size.width + self.pageGap, y: 0.0), size: CGSize(width: self.scrollView.bounds.size.width - self.pageGap * 2.0, height: self.scrollView.bounds.size.height)))
|
||||||
|
|
||||||
|
let screenFrame = node.convert(node.bounds, to: self.supernode)
|
||||||
|
node.screenFrameUpdated(screenFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
if resetOffsetToCentralItem {
|
if resetOffsetToCentralItem {
|
||||||
@ -352,7 +356,11 @@ final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
let previousCentralCandidateHorizontalOffset = self.scrollView.contentOffset.x - centralItemCandidateNode.frame.minX
|
let previousCentralCandidateHorizontalOffset = self.scrollView.contentOffset.x - centralItemCandidateNode.frame.minX
|
||||||
|
|
||||||
for i in 0 ..< self.itemNodes.count {
|
for i in 0 ..< self.itemNodes.count {
|
||||||
transition.updateFrame(node: self.itemNodes[i], frame: CGRect(origin: CGPoint(x: CGFloat(i) * self.scrollView.bounds.size.width + self.pageGap, y: 0.0), size: CGSize(width: self.scrollView.bounds.size.width - self.pageGap * 2.0, height: self.scrollView.bounds.size.height)))
|
let node = self.itemNodes[i]
|
||||||
|
transition.updateFrame(node: node, frame: CGRect(origin: CGPoint(x: CGFloat(i) * self.scrollView.bounds.size.width + self.pageGap, y: 0.0), size: CGSize(width: self.scrollView.bounds.size.width - self.pageGap * 2.0, height: self.scrollView.bounds.size.height)))
|
||||||
|
|
||||||
|
let screenFrame = node.convert(node.bounds, to: self.supernode)
|
||||||
|
node.screenFrameUpdated(screenFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.scrollView.contentOffset = CGPoint(x: centralItemCandidateNode.frame.minX + previousCentralCandidateHorizontalOffset, y: 0.0)
|
self.scrollView.contentOffset = CGPoint(x: centralItemCandidateNode.frame.minX + previousCentralCandidateHorizontalOffset, y: 0.0)
|
||||||
|
|||||||
@ -597,17 +597,17 @@ class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNode, Ite
|
|||||||
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
||||||
}
|
}
|
||||||
switch neighbors.top {
|
switch neighbors.top {
|
||||||
case .sameSection:
|
case .sameSection(false):
|
||||||
strongSelf.topStripeNode.isHidden = true
|
strongSelf.topStripeNode.isHidden = true
|
||||||
case .none, .otherSection:
|
default:
|
||||||
strongSelf.topStripeNode.isHidden = false
|
strongSelf.topStripeNode.isHidden = false
|
||||||
}
|
}
|
||||||
|
|
||||||
let bottomStripeInset: CGFloat
|
let bottomStripeInset: CGFloat
|
||||||
switch neighbors.bottom {
|
switch neighbors.bottom {
|
||||||
case .sameSection:
|
case .sameSection(false):
|
||||||
bottomStripeInset = params.leftInset + 16.0
|
bottomStripeInset = params.leftInset + 16.0
|
||||||
case .none, .otherSection:
|
default:
|
||||||
bottomStripeInset = 0.0
|
bottomStripeInset = 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -230,7 +230,7 @@ class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNodeDelega
|
|||||||
}
|
}
|
||||||
let bottomStripeInset: CGFloat
|
let bottomStripeInset: CGFloat
|
||||||
switch neighbors.bottom {
|
switch neighbors.bottom {
|
||||||
case .sameSection(true):
|
case .sameSection(false):
|
||||||
bottomStripeInset = leftInset
|
bottomStripeInset = leftInset
|
||||||
default:
|
default:
|
||||||
bottomStripeInset = 0.0
|
bottomStripeInset = 0.0
|
||||||
|
|||||||
@ -1,32 +1,164 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
import Display
|
import Display
|
||||||
|
import LegacyComponents
|
||||||
|
|
||||||
|
struct CheckNodeTheme {
|
||||||
|
let backgroundColor: UIColor
|
||||||
|
let strokeColor: UIColor
|
||||||
|
let borderColor: UIColor
|
||||||
|
let hasShadow: Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CheckNodeContent {
|
||||||
|
case check
|
||||||
|
case counter(Int)
|
||||||
|
}
|
||||||
|
|
||||||
private final class CheckNodeParameters: NSObject {
|
private final class CheckNodeParameters: NSObject {
|
||||||
let progress: CGFloat
|
let theme: CheckNodeTheme
|
||||||
|
let content: CheckNodeContent
|
||||||
|
let animationProgress: CGFloat
|
||||||
|
let selected: Bool
|
||||||
|
|
||||||
init(progress: CGFloat) {
|
init(theme: CheckNodeTheme, content: CheckNodeContent, animationProgress: CGFloat, selected: Bool) {
|
||||||
self.progress = progress
|
self.theme = theme
|
||||||
|
self.content = content
|
||||||
|
self.animationProgress = animationProgress
|
||||||
|
self.selected = selected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ModernCheckNode: ASDisplayNode {
|
class ModernCheckNode: ASDisplayNode {
|
||||||
private var displayProgress: CGFloat = 0.0
|
private var animationProgress: CGFloat = 0.0
|
||||||
|
var theme: CheckNodeTheme {
|
||||||
func setSelected(_ selected: Bool, animated: Bool) {
|
didSet {
|
||||||
if animated {
|
self.setNeedsDisplay()
|
||||||
|
|
||||||
} else {
|
|
||||||
self.displayProgress = selected ? 1.0 : 0.0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init(theme: CheckNodeTheme, content: CheckNodeContent = .check) {
|
||||||
|
self.theme = theme
|
||||||
|
self.content = content
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.isOpaque = false
|
||||||
|
}
|
||||||
|
|
||||||
|
var content: CheckNodeContent {
|
||||||
|
didSet {
|
||||||
|
self.setNeedsDisplay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var selected = false
|
||||||
|
func setSelected(_ selected: Bool, animated: Bool = false) {
|
||||||
|
self.selected = selected
|
||||||
|
|
||||||
|
if selected && animated {
|
||||||
|
let animation = POPBasicAnimation()
|
||||||
|
animation.property = POPAnimatableProperty.property(withName: "progress", initializer: { property in
|
||||||
|
property?.readBlock = { node, values in
|
||||||
|
values?.pointee = (node as! ModernCheckNode).animationProgress
|
||||||
|
}
|
||||||
|
property?.writeBlock = { node, values in
|
||||||
|
(node as! ModernCheckNode).animationProgress = values!.pointee
|
||||||
|
(node as! ModernCheckNode).setNeedsDisplay()
|
||||||
|
}
|
||||||
|
property?.threshold = 0.01
|
||||||
|
}) as! POPAnimatableProperty
|
||||||
|
animation.fromValue = 0.0 as NSNumber
|
||||||
|
animation.toValue = 1.0 as NSNumber
|
||||||
|
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
|
||||||
|
animation.duration = 0.21
|
||||||
|
self.pop_add(animation, forKey: "progress")
|
||||||
|
} else {
|
||||||
|
self.animationProgress = selected ? 1.0 : 0.0
|
||||||
|
self.setNeedsDisplay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setHighlighted(_ highlighted: Bool, animated: Bool = false) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
|
override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
|
||||||
return CheckNodeParameters(progress: self.displayProgress)
|
return CheckNodeParameters(theme: self.theme, content: self.content, animationProgress: self.animationProgress, selected: self.selected)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
|
@objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
|
||||||
|
let context = UIGraphicsGetCurrentContext()!
|
||||||
|
|
||||||
|
if !isRasterizing {
|
||||||
|
context.setBlendMode(.copy)
|
||||||
|
context.setFillColor(UIColor.clear.cgColor)
|
||||||
|
context.fill(bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let parameters = parameters as? CheckNodeParameters {
|
||||||
|
let progress = parameters.animationProgress
|
||||||
|
let diameter = bounds.width
|
||||||
|
let center = CGPoint(x: diameter / 2.0, y: diameter / 2.0)
|
||||||
|
|
||||||
|
var borderWidth: CGFloat = 1.5
|
||||||
|
if UIScreenScale == 3.0 {
|
||||||
|
borderWidth = 5.0 / 3.0
|
||||||
|
}
|
||||||
|
|
||||||
|
context.setStrokeColor(parameters.theme.borderColor.cgColor)
|
||||||
|
context.setLineWidth(borderWidth)
|
||||||
|
context.strokeEllipse(in: bounds.insetBy(dx: borderWidth / 2.0, dy: borderWidth / 2.0))
|
||||||
|
|
||||||
|
context.setFillColor(parameters.theme.backgroundColor.cgColor)
|
||||||
|
context.fillEllipse(in: bounds.insetBy(dx: (diameter - borderWidth) * (1.0 - parameters.animationProgress), dy: (diameter - borderWidth) * (1.0 - parameters.animationProgress)))
|
||||||
|
|
||||||
|
let firstSegment: CGFloat = max(0.0, min(1.0, progress * 3.0))
|
||||||
|
let s = CGPoint(x: center.x - 4.0, y: center.y + UIScreenPixel)
|
||||||
|
let p1 = CGPoint(x: 3.0, y: 3.0)
|
||||||
|
let p2 = CGPoint(x: 5.0, y: -6.0)
|
||||||
|
|
||||||
|
if !firstSegment.isZero {
|
||||||
|
if firstSegment < 1.0 {
|
||||||
|
context.move(to: CGPoint(x: s.x + p1.x * firstSegment, y: s.y + p1.y * firstSegment))
|
||||||
|
context.addLine(to: s)
|
||||||
|
} else {
|
||||||
|
let secondSegment = (progress - 0.33) * 1.5
|
||||||
|
context.move(to: CGPoint(x: s.x + p1.x + p2.x * secondSegment, y: s.y + p1.y + p2.y * secondSegment))
|
||||||
|
context.addLine(to: CGPoint(x: s.x + p1.x, y: s.y + p1.y))
|
||||||
|
context.addLine(to: s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.setStrokeColor(parameters.theme.strokeColor.cgColor)
|
||||||
|
if parameters.theme.strokeColor == .clear {
|
||||||
|
context.setBlendMode(.clear)
|
||||||
|
}
|
||||||
|
context.setLineWidth(borderWidth)
|
||||||
|
context.setLineCap(.round)
|
||||||
|
context.setLineJoin(.round)
|
||||||
|
context.setMiterLimit(10.0)
|
||||||
|
|
||||||
|
switch parameters.content {
|
||||||
|
case .check:
|
||||||
|
break
|
||||||
|
case let .counter(number):
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
context.strokePath()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
|
super.touchesBegan(touches, with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
|
super.touchesEnded(touches, with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func touchesCancelled(_ touches: Set<UITouch>?, with event: UIEvent?) {
|
||||||
|
super.touchesCancelled(touches, with: event)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,6 +52,19 @@ class NavigationBarSearchContentNode: NavigationBarContentNode {
|
|||||||
self.updateExpansionProgress(progress)
|
self.updateExpansionProgress(progress)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateGridVisibleContentOffset(_ offset: GridNodeVisibleContentOffset) {
|
||||||
|
var progress: CGFloat = 0.0
|
||||||
|
switch offset {
|
||||||
|
case let .known(offset):
|
||||||
|
progress = max(0.0, (self.nominalHeight - offset)) / self.nominalHeight
|
||||||
|
case .none:
|
||||||
|
progress = 1.0
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
self.updateExpansionProgress(progress)
|
||||||
|
}
|
||||||
|
|
||||||
func updateExpansionProgress(_ progress: CGFloat, animated: Bool = false) {
|
func updateExpansionProgress(_ progress: CGFloat, animated: Bool = false) {
|
||||||
let newProgress = max(0.0, min(1.0, progress))
|
let newProgress = max(0.0, min(1.0, progress))
|
||||||
if newProgress != self.expansionProgress {
|
if newProgress != self.expansionProgress {
|
||||||
|
|||||||
@ -409,10 +409,10 @@ func openChatWallpaper(account: Account, message: Message, present: @escaping (V
|
|||||||
if case let .wallpaper(parameter) = resolvedUrl {
|
if case let .wallpaper(parameter) = resolvedUrl {
|
||||||
let source: WallpaperListSource
|
let source: WallpaperListSource
|
||||||
switch parameter {
|
switch parameter {
|
||||||
case let .slug(slug):
|
case let .slug(slug, options):
|
||||||
source = .slug(slug, content.file)
|
source = .slug(slug, content.file, options)
|
||||||
case let .color(color):
|
case let .color(color):
|
||||||
source = .wallpaper(.color(Int32(color.rgb)))
|
source = .wallpaper(.color(Int32(color.rgb)), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
let controller = WallpaperGalleryController(account: account, source: source)
|
let controller = WallpaperGalleryController(account: account, source: source)
|
||||||
|
|||||||
@ -198,9 +198,11 @@ func openResolvedUrl(_ resolvedUrl: ResolvedUrl, account: Account, context: Open
|
|||||||
var controller: OverlayStatusController?
|
var controller: OverlayStatusController?
|
||||||
|
|
||||||
let signal: Signal<TelegramWallpaper, GetWallpaperError>
|
let signal: Signal<TelegramWallpaper, GetWallpaperError>
|
||||||
|
var options: WallpaperPresentationOptions?
|
||||||
switch parameter {
|
switch parameter {
|
||||||
case let .slug(slug):
|
case let .slug(slug, wallpaperOptions):
|
||||||
signal = getWallpaper(account: account, slug: slug)
|
signal = getWallpaper(account: account, slug: slug)
|
||||||
|
options = wallpaperOptions
|
||||||
controller = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .loading(cancelled: nil))
|
controller = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .loading(cancelled: nil))
|
||||||
present(controller!, nil)
|
present(controller!, nil)
|
||||||
case let .color(color):
|
case let .color(color):
|
||||||
@ -210,7 +212,7 @@ func openResolvedUrl(_ resolvedUrl: ResolvedUrl, account: Account, context: Open
|
|||||||
let _ = (signal
|
let _ = (signal
|
||||||
|> deliverOnMainQueue).start(next: { [weak controller] wallpaper in
|
|> deliverOnMainQueue).start(next: { [weak controller] wallpaper in
|
||||||
controller?.dismiss()
|
controller?.dismiss()
|
||||||
let galleryController = WallpaperGalleryController(account: account, source: .wallpaper(wallpaper))
|
let galleryController = WallpaperGalleryController(account: account, source: .wallpaper(wallpaper, options))
|
||||||
present(galleryController, nil)
|
present(galleryController, nil)
|
||||||
}, error: { [weak controller] error in
|
}, error: { [weak controller] error in
|
||||||
controller?.dismiss()
|
controller?.dismiss()
|
||||||
|
|||||||
@ -547,6 +547,7 @@ public func openExternalUrl(account: Account, context: OpenURLContext = .generic
|
|||||||
} else if parsedUrl.host == "bg" {
|
} else if parsedUrl.host == "bg" {
|
||||||
if let components = URLComponents(string: "/?" + query) {
|
if let components = URLComponents(string: "/?" + query) {
|
||||||
var parameter: String?
|
var parameter: String?
|
||||||
|
var mode = ""
|
||||||
if let queryItems = components.queryItems {
|
if let queryItems = components.queryItems {
|
||||||
for queryItem in queryItems {
|
for queryItem in queryItems {
|
||||||
if let value = queryItem.value {
|
if let value = queryItem.value {
|
||||||
@ -554,12 +555,14 @@ public func openExternalUrl(account: Account, context: OpenURLContext = .generic
|
|||||||
parameter = value
|
parameter = value
|
||||||
} else if queryItem.name == "color" {
|
} else if queryItem.name == "color" {
|
||||||
parameter = value
|
parameter = value
|
||||||
|
} else if queryItem.name == "mode" {
|
||||||
|
mode = "?mode=\(value)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let parameter = parameter {
|
if let parameter = parameter {
|
||||||
convertedUrl = "https://t.me/bg/\(parameter)"
|
convertedUrl = "https://t.me/bg/\(parameter)\(mode)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -148,7 +148,7 @@ class PeerMediaCollectionControllerNode: ASDisplayNode {
|
|||||||
self.historyEmptyNode = PeerMediaCollectionEmptyNode(mode: self.mediaCollectionInterfaceState.mode, theme: self.presentationData.theme, strings: self.presentationData.strings)
|
self.historyEmptyNode = PeerMediaCollectionEmptyNode(mode: self.mediaCollectionInterfaceState.mode, theme: self.presentationData.theme, strings: self.presentationData.strings)
|
||||||
self.historyEmptyNode.isHidden = true
|
self.historyEmptyNode.isHidden = true
|
||||||
|
|
||||||
self.chatPresentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, chatWallpaperMode: self.presentationData.chatWallpaperMode, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, fontSize: self.presentationData.fontSize, accountPeerId: account.peerId, mode: .standard(previewing: false), chatLocation: .peer(self.peerId))
|
self.chatPresentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, chatWallpaperMode: self.presentationData.chatWallpaperOptions, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, fontSize: self.presentationData.fontSize, accountPeerId: account.peerId, mode: .standard(previewing: false), chatLocation: .peer(self.peerId))
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
|||||||
@ -2196,9 +2196,15 @@ func instantPageImageFile(account: Account, fileReference: FileMediaReference, f
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func avatarGalleryPhotoDatas(account: Account, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], alwaysShowThumbnailFirst: Bool = false, autoFetchFullSize: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> {
|
private func avatarGalleryPhotoDatas(account: Account, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], alwaysShowThumbnailFirst: Bool = false, scaled: Bool = false, autoFetchFullSize: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> {
|
||||||
if let smallestRepresentation = smallestImageRepresentation(representations.map({ $0.representation })), let largestRepresentation = largestImageRepresentation(representations.map({ $0.representation })), let smallestIndex = representations.index(where: { $0.representation == smallestRepresentation }), let largestIndex = representations.index(where: { $0.representation == largestRepresentation }) {
|
if let smallestRepresentation = smallestImageRepresentation(representations.map({ $0.representation })), let largestRepresentation = largestImageRepresentation(representations.map({ $0.representation })), let smallestIndex = representations.index(where: { $0.representation == smallestRepresentation }), let largestIndex = representations.index(where: { $0.representation == largestRepresentation }) {
|
||||||
let maybeFullSize = account.postbox.mediaBox.resourceData(largestRepresentation.resource)
|
|
||||||
|
let maybeFullSize: Signal<MediaResourceData, NoError>
|
||||||
|
if scaled {
|
||||||
|
maybeFullSize = account.postbox.mediaBox.cachedResourceRepresentation(largestRepresentation.resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 320.0, height: 320.0), mode: .aspectFit), complete: false, fetch: false)
|
||||||
|
} else {
|
||||||
|
maybeFullSize = account.postbox.mediaBox.resourceData(largestRepresentation.resource)
|
||||||
|
}
|
||||||
let decodedThumbnailData = fileReference?.media.immediateThumbnailData.flatMap(decodeTinyThumbnail)
|
let decodedThumbnailData = fileReference?.media.immediateThumbnailData.flatMap(decodeTinyThumbnail)
|
||||||
|
|
||||||
let signal = maybeFullSize
|
let signal = maybeFullSize
|
||||||
@ -2274,8 +2280,8 @@ private func avatarGalleryPhotoDatas(account: Account, fileReference: FileMediaR
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func chatAvatarGalleryPhoto(account: Account, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], alwaysShowThumbnailFirst: Bool = false, autoFetchFullSize: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
func chatAvatarGalleryPhoto(account: Account, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], alwaysShowThumbnailFirst: Bool = false, scaled: Bool = false, autoFetchFullSize: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||||
let signal = avatarGalleryPhotoDatas(account: account, fileReference: fileReference, representations: representations, alwaysShowThumbnailFirst: alwaysShowThumbnailFirst, autoFetchFullSize: autoFetchFullSize)
|
let signal = avatarGalleryPhotoDatas(account: account, fileReference: fileReference, representations: representations, alwaysShowThumbnailFirst: alwaysShowThumbnailFirst, scaled: scaled, autoFetchFullSize: autoFetchFullSize)
|
||||||
|
|
||||||
return signal
|
return signal
|
||||||
|> map { (thumbnailData, fullSizeData, fullSizeComplete) in
|
|> map { (thumbnailData, fullSizeData, fullSizeComplete) in
|
||||||
|
|||||||
@ -44,10 +44,12 @@ public struct ApplicationSpecificPreferencesKeys {
|
|||||||
|
|
||||||
private enum ApplicationSpecificItemCacheCollectionIdValues: Int8 {
|
private enum ApplicationSpecificItemCacheCollectionIdValues: Int8 {
|
||||||
case instantPageStoredState = 0
|
case instantPageStoredState = 0
|
||||||
|
case themeSpecificSettings = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct ApplicationSpecificItemCacheCollectionId {
|
public struct ApplicationSpecificItemCacheCollectionId {
|
||||||
public static let instantPageStoredState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.instantPageStoredState.rawValue)
|
public static let instantPageStoredState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.instantPageStoredState.rawValue)
|
||||||
|
public static let themeSpecificSettings = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.themeSpecificSettings.rawValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum ApplicationSpecificOrderedItemListCollectionIdValues: Int32 {
|
private enum ApplicationSpecificOrderedItemListCollectionIdValues: Int32 {
|
||||||
|
|||||||
@ -46,7 +46,7 @@ public final class PresentationData: Equatable {
|
|||||||
public let strings: PresentationStrings
|
public let strings: PresentationStrings
|
||||||
public let theme: PresentationTheme
|
public let theme: PresentationTheme
|
||||||
public let chatWallpaper: TelegramWallpaper
|
public let chatWallpaper: TelegramWallpaper
|
||||||
public let chatWallpaperMode: WallpaperPresentationOptions
|
public let chatWallpaperOptions: WallpaperPresentationOptions
|
||||||
public let volumeControlStatusBarIcons: PresentationVolumeControlStatusBarIcons
|
public let volumeControlStatusBarIcons: PresentationVolumeControlStatusBarIcons
|
||||||
public let fontSize: PresentationFontSize
|
public let fontSize: PresentationFontSize
|
||||||
public let dateTimeFormat: PresentationDateTimeFormat
|
public let dateTimeFormat: PresentationDateTimeFormat
|
||||||
@ -54,11 +54,11 @@ public final class PresentationData: Equatable {
|
|||||||
public let nameSortOrder: PresentationPersonNameOrder
|
public let nameSortOrder: PresentationPersonNameOrder
|
||||||
public let disableAnimations: Bool
|
public let disableAnimations: Bool
|
||||||
|
|
||||||
public init(strings: PresentationStrings, theme: PresentationTheme, chatWallpaper: TelegramWallpaper, chatWallpaperMode: WallpaperPresentationOptions, volumeControlStatusBarIcons: PresentationVolumeControlStatusBarIcons, fontSize: PresentationFontSize, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, nameSortOrder: PresentationPersonNameOrder, disableAnimations: Bool) {
|
public init(strings: PresentationStrings, theme: PresentationTheme, chatWallpaper: TelegramWallpaper, chatWallpaperOptions: WallpaperPresentationOptions, volumeControlStatusBarIcons: PresentationVolumeControlStatusBarIcons, fontSize: PresentationFontSize, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, nameSortOrder: PresentationPersonNameOrder, disableAnimations: Bool) {
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.chatWallpaper = chatWallpaper
|
self.chatWallpaper = chatWallpaper
|
||||||
self.chatWallpaperMode = chatWallpaperMode
|
self.chatWallpaperOptions = chatWallpaperOptions
|
||||||
self.volumeControlStatusBarIcons = volumeControlStatusBarIcons
|
self.volumeControlStatusBarIcons = volumeControlStatusBarIcons
|
||||||
self.fontSize = fontSize
|
self.fontSize = fontSize
|
||||||
self.dateTimeFormat = dateTimeFormat
|
self.dateTimeFormat = dateTimeFormat
|
||||||
@ -68,7 +68,7 @@ public final class PresentationData: Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: PresentationData, rhs: PresentationData) -> Bool {
|
public static func ==(lhs: PresentationData, rhs: PresentationData) -> Bool {
|
||||||
return lhs.strings === rhs.strings && lhs.theme === rhs.theme && lhs.chatWallpaper == rhs.chatWallpaper && lhs.chatWallpaperMode == rhs.chatWallpaperMode && lhs.volumeControlStatusBarIcons == rhs.volumeControlStatusBarIcons && lhs.fontSize == rhs.fontSize && lhs.dateTimeFormat == rhs.dateTimeFormat && lhs.disableAnimations == rhs.disableAnimations
|
return lhs.strings === rhs.strings && lhs.theme === rhs.theme && lhs.chatWallpaper == rhs.chatWallpaper && lhs.chatWallpaperOptions == rhs.chatWallpaperOptions && lhs.volumeControlStatusBarIcons == rhs.volumeControlStatusBarIcons && lhs.fontSize == rhs.fontSize && lhs.dateTimeFormat == rhs.dateTimeFormat && lhs.disableAnimations == rhs.disableAnimations
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -281,7 +281,7 @@ public func currentPresentationDataAndSettings(postbox: Postbox) -> Signal<Initi
|
|||||||
let dateTimeFormat = currentDateTimeFormat()
|
let dateTimeFormat = currentDateTimeFormat()
|
||||||
let nameDisplayOrder = contactSettings.nameDisplayOrder
|
let nameDisplayOrder = contactSettings.nameDisplayOrder
|
||||||
let nameSortOrder = currentPersonNameSortOrder()
|
let nameSortOrder = currentPersonNameSortOrder()
|
||||||
return InitialPresentationDataAndSettings(presentationData: PresentationData(strings: stringsValue, theme: themeValue, chatWallpaper: effectiveChatWallpaper, chatWallpaperMode: effectiveChatWallpaperOptions, volumeControlStatusBarIcons: volumeControlStatusBarIcons(), fontSize: themeSettings.fontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, disableAnimations: themeSettings.disableAnimations), automaticMediaDownloadSettings: automaticMediaDownloadSettings, callListSettings: callListSettings, inAppNotificationSettings: inAppNotificationSettings, mediaInputSettings: mediaInputSettings, experimentalUISettings: experimentalUISettings)
|
return InitialPresentationDataAndSettings(presentationData: PresentationData(strings: stringsValue, theme: themeValue, chatWallpaper: effectiveChatWallpaper, chatWallpaperOptions: effectiveChatWallpaperOptions, volumeControlStatusBarIcons: volumeControlStatusBarIcons(), fontSize: themeSettings.fontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, disableAnimations: themeSettings.disableAnimations), automaticMediaDownloadSettings: automaticMediaDownloadSettings, callListSettings: callListSettings, inAppNotificationSettings: inAppNotificationSettings, mediaInputSettings: mediaInputSettings, experimentalUISettings: experimentalUISettings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -363,8 +363,27 @@ public func updatedPresentationData(postbox: Postbox, applicationBindings: Teleg
|
|||||||
|
|
||||||
let contactSettings: ContactSynchronizationSettings = (view.views[preferencesKey] as! PreferencesView).values[ApplicationSpecificPreferencesKeys.contactSynchronizationSettings] as? ContactSynchronizationSettings ?? ContactSynchronizationSettings.defaultSettings
|
let contactSettings: ContactSynchronizationSettings = (view.views[preferencesKey] as! PreferencesView).values[ApplicationSpecificPreferencesKeys.contactSynchronizationSettings] as? ContactSynchronizationSettings ?? ContactSynchronizationSettings.defaultSettings
|
||||||
|
|
||||||
|
let themeSpecificSettings = postbox.transaction { transaction -> PresentationThemeSpecificSettings? in
|
||||||
|
let key = ValueBoxKey(length: 8)
|
||||||
|
key.setInt64(0, value: themeSettings.theme.index)
|
||||||
|
if let entry = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: ApplicationSpecificItemCacheCollectionId.themeSpecificSettings, key: key)) as? PresentationThemeSpecificSettings {
|
||||||
|
return entry
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeSpecificSettings
|
||||||
|
|> mapToSignal { themeSpecificSettings in
|
||||||
|
let currentWallpaper: TelegramWallpaper
|
||||||
|
if let themeSpecificSettings = themeSpecificSettings {
|
||||||
|
currentWallpaper = themeSpecificSettings.chatWallpaper
|
||||||
|
} else {
|
||||||
|
currentWallpaper = themeSettings.chatWallpaper
|
||||||
|
}
|
||||||
|
|
||||||
return (.single(UIColor(rgb: 0x000000, alpha: 0.3))
|
return (.single(UIColor(rgb: 0x000000, alpha: 0.3))
|
||||||
|> then(chatServiceBackgroundColor(wallpaper: themeSettings.chatWallpaper, postbox: postbox)))
|
|> then(chatServiceBackgroundColor(wallpaper: currentWallpaper, postbox: postbox)))
|
||||||
|> mapToSignal { serviceBackgroundColor in
|
|> mapToSignal { serviceBackgroundColor in
|
||||||
return applicationBindings.applicationInForeground
|
return applicationBindings.applicationInForeground
|
||||||
|> mapToSignal({ inForeground -> Signal<PresentationData, NoError> in
|
|> mapToSignal({ inForeground -> Signal<PresentationData, NoError> in
|
||||||
@ -374,7 +393,7 @@ public func updatedPresentationData(postbox: Postbox, applicationBindings: Teleg
|
|||||||
|> map { shouldSwitch in
|
|> map { shouldSwitch in
|
||||||
let themeValue: PresentationTheme
|
let themeValue: PresentationTheme
|
||||||
let effectiveTheme: PresentationThemeReference
|
let effectiveTheme: PresentationThemeReference
|
||||||
var effectiveChatWallpaper: TelegramWallpaper = themeSettings.chatWallpaper
|
var effectiveChatWallpaper: TelegramWallpaper = currentWallpaper
|
||||||
var effectiveChatWallpaperOptions: WallpaperPresentationOptions = themeSettings.chatWallpaperOptions
|
var effectiveChatWallpaperOptions: WallpaperPresentationOptions = themeSettings.chatWallpaperOptions
|
||||||
|
|
||||||
if shouldSwitch {
|
if shouldSwitch {
|
||||||
@ -429,7 +448,7 @@ public func updatedPresentationData(postbox: Postbox, applicationBindings: Teleg
|
|||||||
let nameDisplayOrder = contactSettings.nameDisplayOrder
|
let nameDisplayOrder = contactSettings.nameDisplayOrder
|
||||||
let nameSortOrder = currentPersonNameSortOrder()
|
let nameSortOrder = currentPersonNameSortOrder()
|
||||||
|
|
||||||
return PresentationData(strings: stringsValue, theme: themeValue, chatWallpaper: effectiveChatWallpaper, chatWallpaperMode: effectiveChatWallpaperOptions, volumeControlStatusBarIcons: volumeControlStatusBarIcons(), fontSize: themeSettings.fontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, disableAnimations: themeSettings.disableAnimations)
|
return PresentationData(strings: stringsValue, theme: themeValue, chatWallpaper: effectiveChatWallpaper, chatWallpaperOptions: effectiveChatWallpaperOptions, volumeControlStatusBarIcons: volumeControlStatusBarIcons(), fontSize: themeSettings.fontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, disableAnimations: themeSettings.disableAnimations)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return .complete()
|
return .complete()
|
||||||
@ -437,6 +456,7 @@ public func updatedPresentationData(postbox: Postbox, applicationBindings: Teleg
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func defaultPresentationData() -> PresentationData {
|
public func defaultPresentationData() -> PresentationData {
|
||||||
@ -445,5 +465,5 @@ public func defaultPresentationData() -> PresentationData {
|
|||||||
let nameSortOrder = currentPersonNameSortOrder()
|
let nameSortOrder = currentPersonNameSortOrder()
|
||||||
|
|
||||||
let themeSettings = PresentationThemeSettings.defaultSettings
|
let themeSettings = PresentationThemeSettings.defaultSettings
|
||||||
return PresentationData(strings: defaultPresentationStrings, theme: defaultPresentationTheme, chatWallpaper: .builtin, chatWallpaperMode: [], volumeControlStatusBarIcons: volumeControlStatusBarIcons(), fontSize: themeSettings.fontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, disableAnimations: themeSettings.disableAnimations)
|
return PresentationData(strings: defaultPresentationStrings, theme: defaultPresentationTheme, chatWallpaper: .builtin, chatWallpaperOptions: [], volumeControlStatusBarIcons: volumeControlStatusBarIcons(), fontSize: themeSettings.fontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, disableAnimations: themeSettings.disableAnimations)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,6 +56,18 @@ public enum PresentationThemeReference: PostboxCoding, Equatable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var index: Int64 {
|
||||||
|
let namespace: Int32
|
||||||
|
let id: Int32
|
||||||
|
switch self {
|
||||||
|
case let .builtin(reference):
|
||||||
|
namespace = 0
|
||||||
|
id = reference.rawValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return (Int64(namespace) << 32) | Int64(bitPattern: UInt64(UInt32(bitPattern: id)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum PresentationFontSize: Int32 {
|
public enum PresentationFontSize: Int32 {
|
||||||
@ -166,7 +178,12 @@ public struct PresentationThemeSettings: PreferencesEntry {
|
|||||||
public var relatedResources: [MediaResourceId] {
|
public var relatedResources: [MediaResourceId] {
|
||||||
switch self.chatWallpaper {
|
switch self.chatWallpaper {
|
||||||
case let .image(representations):
|
case let .image(representations):
|
||||||
return representations.map({ $0.resource.id })
|
return representations.map { $0.resource.id }
|
||||||
|
case let .file(_, _, _, _, _, file):
|
||||||
|
var resources: [MediaResourceId] = []
|
||||||
|
resources.append(file.resource.id)
|
||||||
|
resources.append(contentsOf: file.previewRepresentations.map { $0.resource.id })
|
||||||
|
return resources
|
||||||
default:
|
default:
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
@ -223,6 +240,28 @@ public struct PresentationThemeSettings: PreferencesEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class PresentationThemeSpecificSettings: PostboxCoding {
|
||||||
|
public let chatWallpaper: TelegramWallpaper
|
||||||
|
public let chatWallpaperOptions: WallpaperPresentationOptions
|
||||||
|
|
||||||
|
init(chatWallpaper: TelegramWallpaper, chatWallpaperOptions: WallpaperPresentationOptions) {
|
||||||
|
self.chatWallpaper = chatWallpaper
|
||||||
|
self.chatWallpaperOptions = chatWallpaperOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
init(decoder: PostboxDecoder) {
|
||||||
|
self.chatWallpaper = (decoder.decodeObjectForKey("w", decoder: { TelegramWallpaper(decoder: $0) }) as? TelegramWallpaper) ?? .builtin
|
||||||
|
self.chatWallpaperOptions = WallpaperPresentationOptions(rawValue: decoder.decodeInt32ForKey("o", orElse: 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(_ encoder: PostboxEncoder) {
|
||||||
|
encoder.encodeObject(self.chatWallpaper, forKey: "w")
|
||||||
|
encoder.encodeInt32(self.chatWallpaperOptions.rawValue, forKey: "o")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let collectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 100, highWaterItemCount: 200)
|
||||||
|
|
||||||
public func updatePresentationThemeSettingsInteractively(postbox: Postbox, _ f: @escaping (PresentationThemeSettings) -> PresentationThemeSettings) -> Signal<Void, NoError> {
|
public func updatePresentationThemeSettingsInteractively(postbox: Postbox, _ f: @escaping (PresentationThemeSettings) -> PresentationThemeSettings) -> Signal<Void, NoError> {
|
||||||
return postbox.transaction { transaction -> Void in
|
return postbox.transaction { transaction -> Void in
|
||||||
transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.presentationThemeSettings, { entry in
|
transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.presentationThemeSettings, { entry in
|
||||||
@ -234,5 +273,14 @@ public func updatePresentationThemeSettingsInteractively(postbox: Postbox, _ f:
|
|||||||
}
|
}
|
||||||
return f(currentSettings)
|
return f(currentSettings)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if let preferences = transaction.getPreferencesEntry(key: ApplicationSpecificPreferencesKeys.presentationThemeSettings) as? PresentationThemeSettings {
|
||||||
|
let themeSpecificSettings = PresentationThemeSpecificSettings(chatWallpaper: preferences.chatWallpaper, chatWallpaperOptions: preferences.chatWallpaperOptions)
|
||||||
|
|
||||||
|
let key = ValueBoxKey(length: 8)
|
||||||
|
key.setInt64(0, value: preferences.theme.index)
|
||||||
|
let id = ItemCacheEntryId(collectionId: ApplicationSpecificItemCacheCollectionId.themeSpecificSettings, key: key)
|
||||||
|
transaction.putItemCacheEntry(id: id, entry: themeSpecificSettings, collectionSpec: collectionSpec)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -88,7 +88,7 @@ final class SettingsThemeWallpaperNode: ASDisplayNode {
|
|||||||
self.backgroundNode.isHidden = true
|
self.backgroundNode.isHidden = true
|
||||||
|
|
||||||
let convertedRepresentations: [ImageRepresentationWithReference] = representations.map({ ImageRepresentationWithReference(representation: $0, reference: .wallpaper(resource: $0.resource)) })
|
let convertedRepresentations: [ImageRepresentationWithReference] = representations.map({ ImageRepresentationWithReference(representation: $0, reference: .wallpaper(resource: $0.resource)) })
|
||||||
self.imageNode.setSignal(chatAvatarGalleryPhoto(account: account, representations: convertedRepresentations, autoFetchFullSize: true))
|
self.imageNode.setSignal(chatAvatarGalleryPhoto(account: account, representations: convertedRepresentations, scaled: true, autoFetchFullSize: true))
|
||||||
let apply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: largestImageRepresentation(representations)!.dimensions.aspectFilled(size), boundingSize: size, intrinsicInsets: UIEdgeInsets()))
|
let apply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: largestImageRepresentation(representations)!.dimensions.aspectFilled(size), boundingSize: size, intrinsicInsets: UIEdgeInsets()))
|
||||||
apply()
|
apply()
|
||||||
case let .file(file):
|
case let .file(file):
|
||||||
@ -100,7 +100,7 @@ final class SettingsThemeWallpaperNode: ASDisplayNode {
|
|||||||
convertedRepresentations.append(ImageRepresentationWithReference(representation: representation, reference: .wallpaper(resource: representation.resource)))
|
convertedRepresentations.append(ImageRepresentationWithReference(representation: representation, reference: .wallpaper(resource: representation.resource)))
|
||||||
}
|
}
|
||||||
let dimensions = file.file.dimensions ?? CGSize(width: 100.0, height: 100.0)
|
let dimensions = file.file.dimensions ?? CGSize(width: 100.0, height: 100.0)
|
||||||
self.imageNode.setSignal(chatAvatarGalleryPhoto(account: account, fileReference: .standalone(media: file.file), representations: convertedRepresentations, autoFetchFullSize: true))
|
self.imageNode.setSignal(chatAvatarGalleryPhoto(account: account, fileReference: .standalone(media: file.file), representations: convertedRepresentations, scaled: true, autoFetchFullSize: true))
|
||||||
let apply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: dimensions.aspectFilled(size), boundingSize: size, intrinsicInsets: UIEdgeInsets()))
|
let apply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: dimensions.aspectFilled(size), boundingSize: size, intrinsicInsets: UIEdgeInsets()))
|
||||||
apply()
|
apply()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -123,7 +123,6 @@ final class ThemeColorsGridControllerNode: ASDisplayNode {
|
|||||||
controller.apply = { _, _, _ in
|
controller.apply = { _, _, _ in
|
||||||
pop()
|
pop()
|
||||||
}
|
}
|
||||||
//let controller = WallpaperListPreviewController(account: account, source: .list(wallpapers: wallpapers, central: wallpaper, type: .colors))
|
|
||||||
strongSelf.present(controller, nil)
|
strongSelf.present(controller, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -284,6 +283,13 @@ final class ThemeColorsGridControllerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func scrollToTop() {
|
func scrollToTop() {
|
||||||
|
let offset = self.gridNode.scrollView.contentOffset.y + self.gridNode.scrollView.contentInset.top
|
||||||
|
let duration: Double = 0.25
|
||||||
|
|
||||||
self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: GridNodeScrollToItem(index: 0, position: .top, transition: .animated(duration: 0.25, curve: .easeInOut), directionHint: .up, adjustForSection: true, adjustForTopInset: true), updateLayout: nil, itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in })
|
self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: GridNodeScrollToItem(index: 0, position: .top, transition: .animated(duration: 0.25, curve: .easeInOut), directionHint: .up, adjustForSection: true, adjustForTopInset: true), updateLayout: nil, itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in })
|
||||||
|
|
||||||
|
self.backgroundNode.layer.animatePosition(from: self.backgroundNode.layer.position.offsetBy(dx: 0.0, dy: -offset), to: self.backgroundNode.layer.position, duration: duration)
|
||||||
|
self.separatorNode.layer.animatePosition(from: self.separatorNode.layer.position.offsetBy(dx: 0.0, dy: -offset), to: self.separatorNode.layer.position, duration: duration)
|
||||||
|
self.customColorItemNode.layer.animatePosition(from: self.customColorItemNode.layer.position.offsetBy(dx: 0.0, dy: -offset), to: self.customColorItemNode.layer.position, duration: duration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -105,20 +105,19 @@ final class ThemeGridController: ViewController {
|
|||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
let controller = WallpaperGalleryController(account: strongSelf.account, source: source)
|
let controller = WallpaperGalleryController(account: strongSelf.account, source: source)
|
||||||
controller.apply = { [weak self, weak controller] wallpaper, mode, cropRect in
|
controller.apply = { [weak self, weak controller] wallpaper, mode, cropRect in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.uploadCustomWallpaper(wallpaper, mode: mode, cropRect: cropRect, completion: { [weak self, weak controller] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.deactivateSearch(animated: false)
|
||||||
|
strongSelf.controllerNode.scrollToTop(animated: false)
|
||||||
|
}
|
||||||
|
if let controller = controller {
|
||||||
|
controller.dismiss(forceAway: true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self?.present(controller, in: .window(.root), with: nil, blockInteraction: true)
|
self?.present(controller, in: .window(.root), with: nil, blockInteraction: true)
|
||||||
// let controller = WallpaperListPreviewController(account: strongSelf.account, source: source)
|
|
||||||
// controller.apply = { [weak self, weak controller] wallpaper, mode, cropRect in
|
|
||||||
// if let strongSelf = self {
|
|
||||||
// strongSelf.uploadCustomWallpaper(wallpaper, mode: mode, cropRect: cropRect)
|
|
||||||
// if case .wallpaper = wallpaper {
|
|
||||||
// } else if let controller = controller {
|
|
||||||
// controller.dismiss()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// self?.present(controller, in: .window(.root), with: nil, blockInteraction: true)
|
|
||||||
}
|
}
|
||||||
}, presentGallery: { [weak self] in
|
}, presentGallery: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
@ -130,15 +129,15 @@ final class ThemeGridController: ViewController {
|
|||||||
let controller = generator(legacyController.context)
|
let controller = generator(legacyController.context)
|
||||||
legacyController.bind(controller: controller)
|
legacyController.bind(controller: controller)
|
||||||
legacyController.deferScreenEdgeGestures = [.top]
|
legacyController.deferScreenEdgeGestures = [.top]
|
||||||
controller.selectionBlock = { [weak self, weak legacyController] asset, thumbnailImage in
|
controller.selectionBlock = { [weak self, weak legacyController] asset, _ in
|
||||||
if let strongSelf = self, let asset = asset {
|
if let strongSelf = self, let asset = asset {
|
||||||
let controller = WallpaperListPreviewController(account: strongSelf.account, source: .asset(asset.backingAsset, thumbnailImage))
|
let controller = WallpaperGalleryController(account: strongSelf.account, source: .asset(asset.backingAsset))
|
||||||
controller.apply = { [weak self, weak legacyController, weak controller] wallpaper, mode, cropRect in
|
controller.apply = { [weak self, weak legacyController, weak controller] wallpaper, mode, cropRect in
|
||||||
if let strongSelf = self, let legacyController = legacyController, let controller = controller {
|
if let strongSelf = self, let legacyController = legacyController, let controller = controller {
|
||||||
strongSelf.uploadCustomWallpaper(wallpaper, mode: mode, cropRect: cropRect, completion: { [weak legacyController, weak controller] in
|
strongSelf.uploadCustomWallpaper(wallpaper, mode: mode, cropRect: cropRect, completion: { [weak legacyController, weak controller] in
|
||||||
if let legacyController = legacyController, let controller = controller {
|
if let legacyController = legacyController, let controller = controller {
|
||||||
legacyController.dismiss()
|
legacyController.dismiss()
|
||||||
controller.dismiss()
|
controller.dismiss(forceAway: true)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -181,12 +180,21 @@ final class ThemeGridController: ViewController {
|
|||||||
let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme)
|
let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme)
|
||||||
var items: [ActionSheetItem] = []
|
var items: [ActionSheetItem] = []
|
||||||
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Wallpaper_DeleteConfirmation(Int32(wallpapers.count)), color: .destructive, action: { [weak self, weak actionSheet] in
|
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Wallpaper_DeleteConfirmation(Int32(wallpapers.count)), color: .destructive, action: { [weak self, weak actionSheet] in
|
||||||
|
|
||||||
actionSheet?.dismissAnimated()
|
actionSheet?.dismissAnimated()
|
||||||
completed()
|
completed()
|
||||||
|
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
for wallpaper in wallpapers {
|
||||||
|
if wallpaper == strongSelf.presentationData.chatWallpaper {
|
||||||
|
let _ = (updatePresentationThemeSettingsInteractively(postbox: strongSelf.account.postbox, { current in
|
||||||
|
return PresentationThemeSettings(chatWallpaper: .builtin, chatWallpaperOptions: [], theme: current.theme, themeAccentColor: current.themeAccentColor, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, disableAnimations: current.disableAnimations)
|
||||||
|
})).start()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for wallpaper in wallpapers {
|
for wallpaper in wallpapers {
|
||||||
let _ = deleteWallpaper(account: strongSelf.account, wallpaper: wallpaper).start()
|
let _ = deleteWallpaper(account: strongSelf.account, wallpaper: wallpaper).start()
|
||||||
@ -220,34 +228,30 @@ final class ThemeGridController: ViewController {
|
|||||||
self?.deactivateSearch(animated: true)
|
self?.deactivateSearch(animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.controllerNode.gridNode.scrollingCompleted = {
|
self.controllerNode.gridNode.visibleContentOffsetChanged = { [weak self] offset in
|
||||||
|
if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode {
|
||||||
|
searchContentNode.updateGridVisibleContentOffset(offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.controllerNode.gridNode.scrollingCompleted = { [weak self] in
|
||||||
|
if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode {
|
||||||
|
let _ = fixNavigationSearchableGridNodeScrolling(strongSelf.controllerNode.gridNode, searchNode: searchContentNode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// self.controllerNode.gridNode.scroll = { [weak self] offset in
|
|
||||||
// if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode {
|
|
||||||
// searchContentNode.updateListVisibleContentOffset(offset)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// self.controllerNode.gridNode.scrollingCompleted = { [weak self] in
|
|
||||||
// if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode {
|
|
||||||
// return fixNavigationSearchableListNodeScrolling(listView, searchNode: searchContentNode)
|
|
||||||
// } else {
|
|
||||||
// return false
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
self._ready.set(self.controllerNode.ready.get())
|
self._ready.set(self.controllerNode.ready.get())
|
||||||
|
|
||||||
self.displayNodeDidLoad()
|
self.displayNodeDidLoad()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func uploadCustomWallpaper(_ wallpaper: WallpaperEntry, mode: WallpaperPresentationOptions, cropRect: CGRect?, completion: @escaping () -> Void) {
|
private func uploadCustomWallpaper(_ wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, cropRect: CGRect?, completion: @escaping () -> Void) {
|
||||||
let imageSignal: Signal<UIImage, NoError>
|
let imageSignal: Signal<UIImage, NoError>
|
||||||
switch wallpaper {
|
switch wallpaper {
|
||||||
case .wallpaper:
|
case .wallpaper:
|
||||||
imageSignal = .complete()
|
imageSignal = .complete()
|
||||||
case let .asset(asset, _):
|
completion()
|
||||||
|
case let .asset(asset):
|
||||||
imageSignal = fetchPhotoLibraryImage(localIdentifier: asset.localIdentifier, thumbnail: false)
|
imageSignal = fetchPhotoLibraryImage(localIdentifier: asset.localIdentifier, thumbnail: false)
|
||||||
|> filter { value in
|
|> filter { value in
|
||||||
return !(value?.1 ?? true)
|
return !(value?.1 ?? true)
|
||||||
@ -294,44 +298,53 @@ final class ThemeGridController: ViewController {
|
|||||||
|
|
||||||
let finalCropRect: CGRect
|
let finalCropRect: CGRect
|
||||||
if let cropRect = cropRect {
|
if let cropRect = cropRect {
|
||||||
finalCropRect = cropRect.insetBy(dx: -16.0, dy: 0.0)
|
finalCropRect = cropRect
|
||||||
} else {
|
} else {
|
||||||
var screenSize = TGScreenSize()
|
let screenSize = TGScreenSize()
|
||||||
screenSize.width += 32.0
|
|
||||||
let fittedSize = TGScaleToFit(screenSize, image.size)
|
let fittedSize = TGScaleToFit(screenSize, image.size)
|
||||||
finalCropRect = CGRect(x: (image.size.width - fittedSize.width) / 2.0, y: (image.size.height - fittedSize.height) / 2.0, width: fittedSize.width, height: fittedSize.height)
|
finalCropRect = CGRect(x: (image.size.width - fittedSize.width) / 2.0, y: (image.size.height - fittedSize.height) / 2.0, width: fittedSize.width, height: fittedSize.height)
|
||||||
}
|
}
|
||||||
|
croppedImage = TGPhotoEditorCrop(image, nil, .up, 0.0, finalCropRect, false, CGSize(width: 1440.0, height: 2960.0), image.size, true)
|
||||||
|
|
||||||
croppedImage = TGPhotoEditorCrop(image, nil, .up, 0.0, finalCropRect, false, CGSize(width: 2048.0, height: 2048.0), image.size, false)
|
let thumbnailDimensions = finalCropRect.size.fitted(CGSize(width: 320.0, height: 320.0))
|
||||||
|
let thumbnailImage = generateScaledImage(image: croppedImage, size: thumbnailDimensions, scale: 1.0)
|
||||||
|
|
||||||
|
if let data = UIImageJPEGRepresentation(croppedImage, 0.8), let thumbnailImage = thumbnailImage, let thumbnailData = UIImageJPEGRepresentation(thumbnailImage, 0.4) {
|
||||||
|
let thumbnailResource = LocalFileMediaResource(fileId: arc4random64())
|
||||||
|
self.account.postbox.mediaBox.storeResourceData(thumbnailResource.id, data: thumbnailData)
|
||||||
|
|
||||||
if let data = UIImageJPEGRepresentation(croppedImage, 0.85) {
|
|
||||||
let resource = LocalFileMediaResource(fileId: arc4random64())
|
let resource = LocalFileMediaResource(fileId: arc4random64())
|
||||||
self.account.postbox.mediaBox.storeResourceData(resource.id, data: data)
|
self.account.postbox.mediaBox.storeResourceData(resource.id, data: data)
|
||||||
|
|
||||||
let account = self.account
|
let account = self.account
|
||||||
let updateWallpaper: (TelegramWallpaper) -> Void = { wallpaper in
|
let updateWallpaper: (TelegramWallpaper) -> Void = { [weak self] wallpaper in
|
||||||
let _ = (updatePresentationThemeSettingsInteractively(postbox: account.postbox, { current in
|
let _ = (updatePresentationThemeSettingsInteractively(postbox: account.postbox, { current in
|
||||||
return PresentationThemeSettings(chatWallpaper: wallpaper, chatWallpaperOptions: mode, theme: current.theme, themeAccentColor: current.themeAccentColor, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, disableAnimations: current.disableAnimations)
|
return PresentationThemeSettings(chatWallpaper: wallpaper, chatWallpaperOptions: mode, theme: current.theme, themeAccentColor: current.themeAccentColor, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, disableAnimations: current.disableAnimations)
|
||||||
})).start()
|
})).start()
|
||||||
|
|
||||||
|
if let strongSelf = self, case .file = wallpaper {
|
||||||
|
strongSelf.controllerNode.updateWallpapers()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let apply: () -> Void = {
|
let apply: () -> Void = {
|
||||||
let wallpaper: TelegramWallpaper = .image([TelegramMediaImageRepresentation(dimensions: croppedImage.size, resource: resource)])
|
let wallpaper: TelegramWallpaper = .image([TelegramMediaImageRepresentation(dimensions: thumbnailDimensions, resource: thumbnailResource), TelegramMediaImageRepresentation(dimensions: croppedImage.size, resource: resource)])
|
||||||
updateWallpaper(wallpaper)
|
updateWallpaper(wallpaper)
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
// let _ = uploadWallpaper(account: account, resource: resource).start(next: { status in
|
|
||||||
// if case let .complete(wallpaper) = status {
|
let _ = uploadWallpaper(account: account, resource: resource).start(next: { status in
|
||||||
// if mode.contains(.blur), case let .file(_, _, _, _, _, file) = wallpaper {
|
if case let .complete(wallpaper) = status {
|
||||||
// let _ = account.postbox.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedBlurredWallpaperRepresentation(), complete: true, fetch: true).start(completed: {
|
if mode.contains(.blur), case let .file(_, _, _, _, _, file) = wallpaper {
|
||||||
// updateWallpaper(wallpaper)
|
let _ = account.postbox.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedBlurredWallpaperRepresentation(), complete: true, fetch: true).start(completed: {
|
||||||
// })
|
updateWallpaper(wallpaper)
|
||||||
// } else {
|
})
|
||||||
// updateWallpaper(wallpaper)
|
} else {
|
||||||
// }
|
updateWallpaper(wallpaper)
|
||||||
// }
|
}
|
||||||
// }).start()
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if mode.contains(.blur) {
|
if mode.contains(.blur) {
|
||||||
|
|||||||
@ -39,7 +39,7 @@ struct ThemeGridControllerNodeState: Equatable {
|
|||||||
var selectedIndices: Set<Int>
|
var selectedIndices: Set<Int>
|
||||||
|
|
||||||
func withUpdatedEditing(_ editing: Bool) -> ThemeGridControllerNodeState {
|
func withUpdatedEditing(_ editing: Bool) -> ThemeGridControllerNodeState {
|
||||||
return ThemeGridControllerNodeState(editing: editing, selectedIndices: self.selectedIndices)
|
return ThemeGridControllerNodeState(editing: editing, selectedIndices: editing ? self.selectedIndices : Set())
|
||||||
}
|
}
|
||||||
|
|
||||||
func withUpdatedSelectedIndices(_ selectedIndices: Set<Int>) -> ThemeGridControllerNodeState {
|
func withUpdatedSelectedIndices(_ selectedIndices: Set<Int>) -> ThemeGridControllerNodeState {
|
||||||
@ -85,8 +85,21 @@ private struct ThemeGridControllerEntry: Comparable, Identifiable {
|
|||||||
return lhs.index < rhs.index
|
return lhs.index < rhs.index
|
||||||
}
|
}
|
||||||
|
|
||||||
var stableId: Int {
|
var stableId: Int64 {
|
||||||
return self.index
|
switch self.wallpaper {
|
||||||
|
case .builtin:
|
||||||
|
return 0
|
||||||
|
case let .color(color):
|
||||||
|
return (Int64(0) << 32) | Int64(bitPattern: UInt64(UInt32(bitPattern: color)))
|
||||||
|
case let .file(id, _, _, _, _, _):
|
||||||
|
return (Int64(1) << 32) | id
|
||||||
|
case let .image(representations):
|
||||||
|
if let largest = largestImageRepresentation(representations) {
|
||||||
|
return (Int64(2) << 32) | Int64(largest.resource.id.hashValue)
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func item(account: Account, interaction: ThemeGridControllerInteraction) -> ThemeGridControllerItem {
|
func item(account: Account, interaction: ThemeGridControllerInteraction) -> ThemeGridControllerItem {
|
||||||
@ -157,6 +170,7 @@ final class ThemeGridControllerNode: ASDisplayNode {
|
|||||||
var requestDeactivateSearch: (() -> Void)?
|
var requestDeactivateSearch: (() -> Void)?
|
||||||
|
|
||||||
let ready = ValuePromise<Bool>()
|
let ready = ValuePromise<Bool>()
|
||||||
|
let wallpapersPromise: Promise<[TelegramWallpaper]>
|
||||||
|
|
||||||
private var backgroundNode: ASDisplayNode
|
private var backgroundNode: ASDisplayNode
|
||||||
private var separatorNode: ASDisplayNode
|
private var separatorNode: ASDisplayNode
|
||||||
@ -221,6 +235,10 @@ final class ThemeGridControllerNode: ASDisplayNode {
|
|||||||
self.currentState = ThemeGridControllerNodeState(editing: false, selectedIndices: Set())
|
self.currentState = ThemeGridControllerNodeState(editing: false, selectedIndices: Set())
|
||||||
self.statePromise = ValuePromise(self.currentState, ignoreRepeated: true)
|
self.statePromise = ValuePromise(self.currentState, ignoreRepeated: true)
|
||||||
|
|
||||||
|
let wallpapersPromise: Promise<[TelegramWallpaper]> = Promise()
|
||||||
|
wallpapersPromise.set(telegramWallpapers(postbox: account.postbox, network: account.network))
|
||||||
|
self.wallpapersPromise = wallpapersPromise
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.setViewBlock({
|
self.setViewBlock({
|
||||||
@ -236,22 +254,19 @@ final class ThemeGridControllerNode: ASDisplayNode {
|
|||||||
self.gridNode.addSubnode(self.descriptionItemNode)
|
self.gridNode.addSubnode(self.descriptionItemNode)
|
||||||
self.addSubnode(self.gridNode)
|
self.addSubnode(self.gridNode)
|
||||||
|
|
||||||
let wallpapersPromise: Promise<[TelegramWallpaper]> = Promise()
|
|
||||||
wallpapersPromise.set(telegramWallpapers(postbox: account.postbox, network: account.network))
|
|
||||||
let previousEntries = Atomic<[ThemeGridControllerEntry]?>(value: nil)
|
let previousEntries = Atomic<[ThemeGridControllerEntry]?>(value: nil)
|
||||||
|
|
||||||
let interaction = ThemeGridControllerInteraction(openWallpaper: { [weak self] wallpaper in
|
let interaction = ThemeGridControllerInteraction(openWallpaper: { [weak self] wallpaper in
|
||||||
if let strongSelf = self, !strongSelf.currentState.editing {
|
if let strongSelf = self, !strongSelf.currentState.editing {
|
||||||
let entries = previousEntries.with { $0 }
|
let entries = previousEntries.with { $0 }
|
||||||
if let entries = entries, !entries.isEmpty {
|
if let entries = entries, !entries.isEmpty {
|
||||||
let wallpapers = entries.map { $0.wallpaper }
|
let wallpapers = entries.map { $0.wallpaper }
|
||||||
|
|
||||||
var mode: WallpaperPresentationOptions?
|
var options: WallpaperPresentationOptions?
|
||||||
if wallpaper == strongSelf.presentationData.chatWallpaper {
|
if wallpaper == strongSelf.presentationData.chatWallpaper {
|
||||||
mode = strongSelf.presentationData.chatWallpaperMode
|
options = strongSelf.presentationData.chatWallpaperOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
presentPreviewController(.list(wallpapers: wallpapers, central: wallpaper, type: .wallpapers(mode)))
|
presentPreviewController(.list(wallpapers: wallpapers, central: wallpaper, type: .wallpapers(options)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, toggleWallpaperSelection: { [weak self] index, value in
|
}, toggleWallpaperSelection: { [weak self] index, value in
|
||||||
@ -277,7 +292,9 @@ final class ThemeGridControllerNode: ASDisplayNode {
|
|||||||
updatedWallpapers.append(entry.wallpaper)
|
updatedWallpapers.append(entry.wallpaper)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
wallpapersPromise.set(.single(updatedWallpapers))
|
|
||||||
|
wallpapersPromise.set(.single(updatedWallpapers)
|
||||||
|
|> then(telegramWallpapers(postbox: account.postbox, network: account.network)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -294,16 +311,14 @@ final class ThemeGridControllerNode: ASDisplayNode {
|
|||||||
var entries: [ThemeGridControllerEntry] = []
|
var entries: [ThemeGridControllerEntry] = []
|
||||||
var index = 1
|
var index = 1
|
||||||
|
|
||||||
var hasCurrent = false
|
entries.insert(ThemeGridControllerEntry(index: 0, wallpaper: presentationData.chatWallpaper, selected: true), at: 0)
|
||||||
|
|
||||||
for wallpaper in wallpapers {
|
for wallpaper in wallpapers {
|
||||||
let selected = areWallpapersEqual(presentationData.chatWallpaper, wallpaper)
|
let selected = areWallpapersEqual(presentationData.chatWallpaper, wallpaper)
|
||||||
entries.append(ThemeGridControllerEntry(index: index, wallpaper: wallpaper, selected: selected))
|
if !selected {
|
||||||
hasCurrent = hasCurrent || selected
|
entries.append(ThemeGridControllerEntry(index: index, wallpaper: wallpaper, selected: false))
|
||||||
index += 1
|
|
||||||
}
|
}
|
||||||
|
index += 1
|
||||||
if !hasCurrent {
|
|
||||||
entries.insert(ThemeGridControllerEntry(index: 0, wallpaper: presentationData.chatWallpaper, selected: true), at: 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let previous = previousEntries.swap(entries)
|
let previous = previousEntries.swap(entries)
|
||||||
@ -370,6 +385,10 @@ final class ThemeGridControllerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateWallpapers() {
|
||||||
|
self.wallpapersPromise.set(telegramWallpapers(postbox: self.account.postbox, network: self.account.network))
|
||||||
|
}
|
||||||
|
|
||||||
func updatePresentationData(_ presentationData: PresentationData) {
|
func updatePresentationData(_ presentationData: PresentationData) {
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
|
|
||||||
@ -586,11 +605,23 @@ final class ThemeGridControllerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func scrollToTop() {
|
func scrollToTop(animated: Bool = true) {
|
||||||
if let searchDisplayController = self.searchDisplayController {
|
if let searchDisplayController = self.searchDisplayController {
|
||||||
searchDisplayController.contentNode.scrollToTop()
|
searchDisplayController.contentNode.scrollToTop()
|
||||||
} else {
|
} else {
|
||||||
self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: GridNodeScrollToItem(index: 0, position: .top, transition: .animated(duration: 0.25, curve: .easeInOut), directionHint: .up, adjustForSection: true, adjustForTopInset: true), updateLayout: nil, itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in })
|
let offset = self.gridNode.scrollView.contentOffset.y + self.gridNode.scrollView.contentInset.top
|
||||||
|
let duration: Double = 0.25
|
||||||
|
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: duration, curve: .easeInOut) : .immediate
|
||||||
|
|
||||||
|
self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: GridNodeScrollToItem(index: 0, position: .top, transition: transition, directionHint: .up, adjustForSection: true, adjustForTopInset: true), updateLayout: nil, itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in })
|
||||||
|
|
||||||
|
if animated {
|
||||||
|
self.backgroundNode.layer.animatePosition(from: self.backgroundNode.layer.position.offsetBy(dx: 0.0, dy: -offset), to: self.backgroundNode.layer.position, duration: duration)
|
||||||
|
self.separatorNode.layer.animatePosition(from: self.separatorNode.layer.position.offsetBy(dx: 0.0, dy: -offset), to: self.separatorNode.layer.position, duration: duration)
|
||||||
|
self.colorItemNode.layer.animatePosition(from: self.colorItemNode.layer.position.offsetBy(dx: 0.0, dy: -offset), to: self.colorItemNode.layer.position, duration: duration)
|
||||||
|
self.galleryItemNode.layer.animatePosition(from: self.galleryItemNode.layer.position.offsetBy(dx: 0.0, dy: -offset), to: self.galleryItemNode.layer.position, duration: duration)
|
||||||
|
self.descriptionItemNode.layer.animatePosition(from: self.descriptionItemNode.layer.position.offsetBy(dx: 0.0, dy: -offset), to: self.descriptionItemNode.layer.position, duration: duration)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -254,23 +254,51 @@ public func themeSettingsController(account: Account) -> ViewController {
|
|||||||
let _ = telegramWallpapers(postbox: account.postbox, network: account.network).start()
|
let _ = telegramWallpapers(postbox: account.postbox, network: account.network).start()
|
||||||
|
|
||||||
let arguments = ThemeSettingsControllerArguments(account: account, selectTheme: { index in
|
let arguments = ThemeSettingsControllerArguments(account: account, selectTheme: { index in
|
||||||
let _ = updatePresentationThemeSettingsInteractively(postbox: account.postbox, { current in
|
|
||||||
let wallpaper: TelegramWallpaper
|
|
||||||
let theme: PresentationThemeReference
|
let theme: PresentationThemeReference
|
||||||
if index == 0 {
|
switch index {
|
||||||
wallpaper = .builtin
|
case 1:
|
||||||
theme = .builtin(.dayClassic)
|
|
||||||
} else if index == 1 {
|
|
||||||
wallpaper = .color(0xffffff)
|
|
||||||
theme = .builtin(.day)
|
theme = .builtin(.day)
|
||||||
} else if index == 2 {
|
case 2:
|
||||||
wallpaper = .color(0x000000)
|
|
||||||
theme = .builtin(.nightGrayscale)
|
theme = .builtin(.nightGrayscale)
|
||||||
} else {
|
case 3:
|
||||||
wallpaper = .color(0x18222D)
|
|
||||||
theme = .builtin(.nightAccent)
|
theme = .builtin(.nightAccent)
|
||||||
|
default:
|
||||||
|
theme = .builtin(.dayClassic)
|
||||||
}
|
}
|
||||||
return PresentationThemeSettings(chatWallpaper: wallpaper, chatWallpaperOptions: [], theme: theme, themeAccentColor: current.themeAccentColor, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, disableAnimations: current.disableAnimations)
|
|
||||||
|
let _ = (account.postbox.transaction { transaction -> Void in
|
||||||
|
let wallpaper: TelegramWallpaper
|
||||||
|
let wallpaperOptions: WallpaperPresentationOptions
|
||||||
|
|
||||||
|
let key = ValueBoxKey(length: 8)
|
||||||
|
key.setInt64(0, value: theme.index)
|
||||||
|
if let entry = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: ApplicationSpecificItemCacheCollectionId.themeSpecificSettings, key: key)) as? PresentationThemeSpecificSettings {
|
||||||
|
wallpaper = entry.chatWallpaper
|
||||||
|
wallpaperOptions = entry.chatWallpaperOptions
|
||||||
|
} else {
|
||||||
|
switch index {
|
||||||
|
case 1:
|
||||||
|
wallpaper = .color(0xffffff)
|
||||||
|
case 2:
|
||||||
|
wallpaper = .color(0x000000)
|
||||||
|
case 3:
|
||||||
|
wallpaper = .color(0x18222d)
|
||||||
|
default:
|
||||||
|
wallpaper = .builtin
|
||||||
|
}
|
||||||
|
wallpaperOptions = []
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.presentationThemeSettings, { entry in
|
||||||
|
let current: PresentationThemeSettings
|
||||||
|
if let entry = entry as? PresentationThemeSettings {
|
||||||
|
current = entry
|
||||||
|
} else {
|
||||||
|
current = PresentationThemeSettings.defaultSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
return PresentationThemeSettings(chatWallpaper: wallpaper, chatWallpaperOptions: wallpaperOptions, theme: theme, themeAccentColor: current.themeAccentColor, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, disableAnimations: current.disableAnimations)
|
||||||
|
})
|
||||||
}).start()
|
}).start()
|
||||||
}, selectFontSize: { size in
|
}, selectFontSize: { size in
|
||||||
let _ = updatePresentationThemeSettingsInteractively(postbox: account.postbox, { current in
|
let _ = updatePresentationThemeSettingsInteractively(postbox: account.postbox, { current in
|
||||||
|
|||||||
@ -11,7 +11,7 @@ enum ParsedInternalPeerUrlParameter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum WallpaperUrlParameter {
|
enum WallpaperUrlParameter {
|
||||||
case slug(String)
|
case slug(String, WallpaperPresentationOptions)
|
||||||
case color(UIColor)
|
case color(UIColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,7 +180,24 @@ func parseInternalUrl(query: String) -> ParsedInternalUrl? {
|
|||||||
if component.count == 6, component.rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789abcdefABCDEF").inverted) == nil, let color = UIColor(hexString: component) {
|
if component.count == 6, component.rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789abcdefABCDEF").inverted) == nil, let color = UIColor(hexString: component) {
|
||||||
parameter = .color(color)
|
parameter = .color(color)
|
||||||
} else {
|
} else {
|
||||||
parameter = .slug(component)
|
var options: WallpaperPresentationOptions = []
|
||||||
|
if let queryItems = components.queryItems {
|
||||||
|
for queryItem in queryItems {
|
||||||
|
if let value = queryItem.value, queryItem.name == "mode" {
|
||||||
|
for option in value.components(separatedBy: "+") {
|
||||||
|
switch option.lowercased() {
|
||||||
|
case "motion":
|
||||||
|
options.insert(.motion)
|
||||||
|
case "blur":
|
||||||
|
options.insert(.blur)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parameter = .slug(component, options)
|
||||||
}
|
}
|
||||||
return .wallpaper(parameter)
|
return .wallpaper(parameter)
|
||||||
} else if let value = Int(pathComponents[1]) {
|
} else if let value = Int(pathComponents[1]) {
|
||||||
|
|||||||
@ -14,16 +14,16 @@ enum WallpaperListType {
|
|||||||
|
|
||||||
enum WallpaperListSource {
|
enum WallpaperListSource {
|
||||||
case list(wallpapers: [TelegramWallpaper], central: TelegramWallpaper, type: WallpaperListType)
|
case list(wallpapers: [TelegramWallpaper], central: TelegramWallpaper, type: WallpaperListType)
|
||||||
case wallpaper(TelegramWallpaper)
|
case wallpaper(TelegramWallpaper, WallpaperPresentationOptions?)
|
||||||
case slug(String, TelegramMediaFile?)
|
case slug(String, TelegramMediaFile?, WallpaperPresentationOptions?)
|
||||||
case asset(PHAsset, UIImage?)
|
case asset(PHAsset)
|
||||||
case contextResult(ChatContextResult)
|
case contextResult(ChatContextResult)
|
||||||
case customColor(Int32?)
|
case customColor(Int32?)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum WallpaperGalleryEntry: Equatable {
|
enum WallpaperGalleryEntry: Equatable {
|
||||||
case wallpaper(TelegramWallpaper)
|
case wallpaper(TelegramWallpaper)
|
||||||
case asset(PHAsset, UIImage?)
|
case asset(PHAsset)
|
||||||
case contextResult(ChatContextResult)
|
case contextResult(ChatContextResult)
|
||||||
|
|
||||||
public static func ==(lhs: WallpaperGalleryEntry, rhs: WallpaperGalleryEntry) -> Bool {
|
public static func ==(lhs: WallpaperGalleryEntry, rhs: WallpaperGalleryEntry) -> Bool {
|
||||||
@ -34,8 +34,8 @@ enum WallpaperGalleryEntry: Equatable {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .asset(lhsAsset, _):
|
case let .asset(lhsAsset):
|
||||||
if case let .asset(rhsAsset, _) = rhs, lhsAsset.localIdentifier == rhsAsset.localIdentifier {
|
if case let .asset(rhsAsset) = rhs, lhsAsset.localIdentifier == rhsAsset.localIdentifier {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -61,6 +61,16 @@ class WallpaperGalleryOverlayNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class WallpaperGalleryControllerNode: GalleryControllerNode {
|
||||||
|
override func updateDistanceFromEquilibrium(_ value: CGFloat) {
|
||||||
|
guard let itemNode = self.pager.centralItemNode() as? WallpaperGalleryItemNode else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
itemNode.updateDismissTransition(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class WallpaperGalleryController: ViewController {
|
class WallpaperGalleryController: ViewController {
|
||||||
private var galleryNode: GalleryControllerNode {
|
private var galleryNode: GalleryControllerNode {
|
||||||
return self.displayNode as! GalleryControllerNode
|
return self.displayNode as! GalleryControllerNode
|
||||||
@ -81,6 +91,7 @@ class WallpaperGalleryController: ViewController {
|
|||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
private var presentationDataDisposable: Disposable?
|
private var presentationDataDisposable: Disposable?
|
||||||
|
|
||||||
|
private var initialOptions: WallpaperPresentationOptions?
|
||||||
private var entries: [WallpaperGalleryEntry] = []
|
private var entries: [WallpaperGalleryEntry] = []
|
||||||
private var centralEntryIndex: Int?
|
private var centralEntryIndex: Int?
|
||||||
|
|
||||||
@ -93,8 +104,6 @@ class WallpaperGalleryController: ViewController {
|
|||||||
|
|
||||||
private var overlayNode: WallpaperGalleryOverlayNode?
|
private var overlayNode: WallpaperGalleryOverlayNode?
|
||||||
private var messageNodes: [ListViewItemNode]?
|
private var messageNodes: [ListViewItemNode]?
|
||||||
private var blurredButtonNode: WallpaperOptionButtonNode?
|
|
||||||
private var motionButtonNode: WallpaperOptionButtonNode?
|
|
||||||
private var toolbarNode: WallpaperGalleryToolbarNode?
|
private var toolbarNode: WallpaperGalleryToolbarNode?
|
||||||
|
|
||||||
init(account: Account, source: WallpaperListSource) {
|
init(account: Account, source: WallpaperListSource) {
|
||||||
@ -106,21 +115,28 @@ class WallpaperGalleryController: ViewController {
|
|||||||
|
|
||||||
self.title = self.presentationData.strings.WallpaperPreview_Title
|
self.title = self.presentationData.strings.WallpaperPreview_Title
|
||||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
|
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
|
||||||
|
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||||
|
|
||||||
switch source {
|
switch source {
|
||||||
case let .list(wallpapers, central, type):
|
case let .list(wallpapers, central, type):
|
||||||
self.entries = wallpapers.map { .wallpaper($0) }
|
self.entries = wallpapers.map { .wallpaper($0) }
|
||||||
self.centralEntryIndex = wallpapers.index(of: central)!
|
self.centralEntryIndex = wallpapers.index(of: central)!
|
||||||
case let .slug(slug, file):
|
|
||||||
|
if case let .wallpapers(wallpaperOptions) = type, let options = wallpaperOptions {
|
||||||
|
self.initialOptions = options
|
||||||
|
}
|
||||||
|
case let .slug(slug, file, options):
|
||||||
if let file = file {
|
if let file = file {
|
||||||
self.entries = [.wallpaper(.file(id: 0, accessHash: 0, isCreator: false, isDefault: false, slug: slug, file: file))]
|
self.entries = [.wallpaper(.file(id: 0, accessHash: 0, isCreator: false, isDefault: false, slug: slug, file: file))]
|
||||||
self.centralEntryIndex = 0
|
self.centralEntryIndex = 0
|
||||||
|
self.initialOptions = options
|
||||||
}
|
}
|
||||||
case let .wallpaper(wallpaper):
|
case let .wallpaper(wallpaper, options):
|
||||||
self.entries = [.wallpaper(wallpaper)]
|
self.entries = [.wallpaper(wallpaper)]
|
||||||
self.centralEntryIndex = 0
|
self.centralEntryIndex = 0
|
||||||
case let .asset(asset, thumbnailImage):
|
self.initialOptions = options
|
||||||
self.entries = [.asset(asset, thumbnailImage)]
|
case let .asset(asset):
|
||||||
|
self.entries = [.asset(asset)]
|
||||||
self.centralEntryIndex = 0
|
self.centralEntryIndex = 0
|
||||||
case let .contextResult(result):
|
case let .contextResult(result):
|
||||||
self.entries = [.contextResult(result)]
|
self.entries = [.contextResult(result)]
|
||||||
@ -173,7 +189,7 @@ class WallpaperGalleryController: ViewController {
|
|||||||
|
|
||||||
self.centralItemAttributesDisposable.add(self.centralItemAction.get().start(next: { [weak self] barButton in
|
self.centralItemAttributesDisposable.add(self.centralItemAction.get().start(next: { [weak self] barButton in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.navigationItem.rightBarButtonItem = barButton
|
strongSelf.navigationItem.setRightBarButton(barButton, animated: true)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -196,7 +212,7 @@ class WallpaperGalleryController: ViewController {
|
|||||||
self.toolbarNode?.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings)
|
self.toolbarNode?.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func dismiss(forceAway: Bool) {
|
func dismiss(forceAway: Bool) {
|
||||||
let completion: () -> Void = { [weak self] in
|
let completion: () -> Void = { [weak self] in
|
||||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||||
}
|
}
|
||||||
@ -213,7 +229,7 @@ class WallpaperGalleryController: ViewController {
|
|||||||
self?.dismiss(forceAway: true)
|
self?.dismiss(forceAway: true)
|
||||||
}, replaceRootController: { controller, ready in
|
}, replaceRootController: { controller, ready in
|
||||||
})
|
})
|
||||||
self.displayNode = GalleryControllerNode(controllerInteraction: controllerInteraction, pageGap: 0.0)
|
self.displayNode = WallpaperGalleryControllerNode(controllerInteraction: controllerInteraction, pageGap: 0.0)
|
||||||
self.displayNodeDidLoad()
|
self.displayNodeDidLoad()
|
||||||
|
|
||||||
self.galleryNode.statusBar = self.statusBar
|
self.galleryNode.statusBar = self.statusBar
|
||||||
@ -231,6 +247,10 @@ class WallpaperGalleryController: ViewController {
|
|||||||
node.action = { [weak self] in
|
node.action = { [weak self] in
|
||||||
self?.actionPressed()
|
self?.actionPressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let (layout, _) = strongSelf.validLayout {
|
||||||
|
strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.2, curve: .easeInOut))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -239,6 +259,13 @@ class WallpaperGalleryController: ViewController {
|
|||||||
self.galleryNode.backgroundNode.isOpaque = false
|
self.galleryNode.backgroundNode.isOpaque = false
|
||||||
self.galleryNode.isBackgroundExtendedOverNavigationBar = true
|
self.galleryNode.isBackgroundExtendedOverNavigationBar = true
|
||||||
|
|
||||||
|
switch self.source {
|
||||||
|
case .asset, .contextResult, .customColor:
|
||||||
|
self.galleryNode.scrollView.isScrollEnabled = false
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
let presentationData = self.account.telegramApplicationContext.currentPresentationData.with { $0 }
|
let presentationData = self.account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||||
let toolbarNode = WallpaperGalleryToolbarNode(theme: presentationData.theme, strings: presentationData.strings)
|
let toolbarNode = WallpaperGalleryToolbarNode(theme: presentationData.theme, strings: presentationData.strings)
|
||||||
let overlayNode = WallpaperGalleryOverlayNode()
|
let overlayNode = WallpaperGalleryOverlayNode()
|
||||||
@ -254,15 +281,8 @@ class WallpaperGalleryController: ViewController {
|
|||||||
}
|
}
|
||||||
toolbarNode.done = { [weak self] in
|
toolbarNode.done = { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
var options: WallpaperPresentationOptions = []
|
if let centralItemNode = strongSelf.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode {
|
||||||
if (strongSelf.blurredButtonNode?.isSelected ?? false) {
|
let options = centralItemNode.options
|
||||||
options.insert(.blur)
|
|
||||||
}
|
|
||||||
if (strongSelf.motionButtonNode?.isSelected ?? false) {
|
|
||||||
options.insert(.motion)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let centralItemNode = strongSelf.galleryNode.pager.centralItemNode() {
|
|
||||||
if !strongSelf.entries.isEmpty {
|
if !strongSelf.entries.isEmpty {
|
||||||
let entry = strongSelf.entries[centralItemNode.index]
|
let entry = strongSelf.entries[centralItemNode.index]
|
||||||
switch entry {
|
switch entry {
|
||||||
@ -311,38 +331,12 @@ class WallpaperGalleryController: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let blurredButtonNode = WallpaperOptionButtonNode(title: presentationData.strings.WallpaperPreview_Blurred)
|
|
||||||
blurredButtonNode.addTarget(self, action: #selector(self.toggleBlur), forControlEvents: .touchUpInside)
|
|
||||||
overlayNode.addSubnode(blurredButtonNode)
|
|
||||||
self.blurredButtonNode = blurredButtonNode
|
|
||||||
|
|
||||||
let motionButtonNode = WallpaperOptionButtonNode(title: presentationData.strings.WallpaperPreview_Motion)
|
|
||||||
motionButtonNode.addTarget(self, action: #selector(self.toggleMotion), forControlEvents: .touchUpInside)
|
|
||||||
overlayNode.addSubnode(motionButtonNode)
|
|
||||||
self.motionButtonNode = motionButtonNode
|
|
||||||
|
|
||||||
let ready = self.galleryNode.pager.ready() |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(Void())) |> afterNext { [weak self] _ in
|
let ready = self.galleryNode.pager.ready() |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(Void())) |> afterNext { [weak self] _ in
|
||||||
self?.didSetReady = true
|
self?.didSetReady = true
|
||||||
}
|
}
|
||||||
self._ready.set(ready |> map { true })
|
self._ready.set(ready |> map { true })
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func toggleBlur() {
|
|
||||||
if let centralItemNode = self.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode {
|
|
||||||
let value = !(self.blurredButtonNode?.isSelected ?? false)
|
|
||||||
self.blurredButtonNode?.setSelected(value, animated: true)
|
|
||||||
centralItemNode.setBlurEnabled(value, animated: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func toggleMotion() {
|
|
||||||
if let centralItemNode = self.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode {
|
|
||||||
let value = !(self.motionButtonNode?.isSelected ?? false)
|
|
||||||
self.motionButtonNode?.setSelected(value, animated: true)
|
|
||||||
centralItemNode.setMotionEnabled(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func currentEntry() -> WallpaperGalleryEntry? {
|
private func currentEntry() -> WallpaperGalleryEntry? {
|
||||||
if let centralItemNode = self.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode {
|
if let centralItemNode = self.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode {
|
||||||
return centralItemNode.entry
|
return centralItemNode.entry
|
||||||
@ -363,10 +357,16 @@ class WallpaperGalleryController: ViewController {
|
|||||||
node.action = { [weak self] in
|
node.action = { [weak self] in
|
||||||
self?.actionPressed()
|
self?.actionPressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let (layout, _) = self.validLayout {
|
||||||
|
self.containerLayoutUpdated(layout, transition: .immediate)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
|
let hadLayout = self.validLayout != nil
|
||||||
|
|
||||||
super.containerLayoutUpdated(layout, transition: transition)
|
super.containerLayoutUpdated(layout, transition: transition)
|
||||||
|
|
||||||
self.galleryNode.frame = CGRect(origin: CGPoint(), size: layout.size)
|
self.galleryNode.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||||
@ -381,6 +381,25 @@ class WallpaperGalleryController: ViewController {
|
|||||||
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[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: [])
|
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 bottomInset = layout.intrinsicInsets.bottom + 49.0
|
||||||
|
var optionsAvailable = true
|
||||||
|
if let centralItemNode = self.galleryNode.pager.centralItemNode() {
|
||||||
|
if !self.entries.isEmpty {
|
||||||
|
let entry = self.entries[centralItemNode.index]
|
||||||
|
switch entry {
|
||||||
|
case let .wallpaper(wallpaper):
|
||||||
|
switch wallpaper {
|
||||||
|
case .color:
|
||||||
|
optionsAvailable = false
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let controllerInteraction = ChatControllerInteraction.default
|
let controllerInteraction = ChatControllerInteraction.default
|
||||||
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 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)
|
||||||
|
|
||||||
@ -444,33 +463,9 @@ class WallpaperGalleryController: ViewController {
|
|||||||
self.messageNodes = messageNodes
|
self.messageNodes = messageNodes
|
||||||
}
|
}
|
||||||
|
|
||||||
var bottomInset = layout.intrinsicInsets.bottom + 49.0
|
|
||||||
var optionsAvailable = true
|
|
||||||
if let centralItemNode = self.galleryNode.pager.centralItemNode() {
|
|
||||||
if !self.entries.isEmpty {
|
|
||||||
let entry = self.entries[centralItemNode.index]
|
|
||||||
switch entry {
|
|
||||||
case let .wallpaper(wallpaper):
|
|
||||||
switch wallpaper {
|
|
||||||
case .color:
|
|
||||||
optionsAvailable = false
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
transition.updateFrame(node: self.toolbarNode!, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - 49.0 - layout.intrinsicInsets.bottom), size: CGSize(width: layout.size.width, height: 49.0 + layout.intrinsicInsets.bottom)))
|
transition.updateFrame(node: self.toolbarNode!, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - 49.0 - layout.intrinsicInsets.bottom), size: CGSize(width: layout.size.width, height: 49.0 + layout.intrinsicInsets.bottom)))
|
||||||
self.toolbarNode!.updateLayout(size: CGSize(width: layout.size.width, height: 49.0), layout: layout, transition: transition)
|
self.toolbarNode!.updateLayout(size: CGSize(width: layout.size.width, height: 49.0), layout: layout, transition: transition)
|
||||||
|
|
||||||
let buttonSize = CGSize(width: 100.0, height: 30.0)
|
|
||||||
transition.updateFrame(node: self.blurredButtonNode!, frame: CGRect(origin: CGPoint(x: layout.size.width / 2.0 - buttonSize.width - 10.0, y: layout.size.height - 49.0 - layout.intrinsicInsets.bottom - 54.0), size: buttonSize))
|
|
||||||
|
|
||||||
transition.updateFrame(node: self.motionButtonNode!, frame: CGRect(origin: CGPoint(x: layout.size.width / 2.0 + 10.0, y: layout.size.height - 49.0 - layout.intrinsicInsets.bottom - 54.0), size: buttonSize))
|
|
||||||
|
|
||||||
if let messageNodes = self.messageNodes {
|
if let messageNodes = self.messageNodes {
|
||||||
var bottomOffset: CGFloat = layout.size.height - bottomInset - 9.0
|
var bottomOffset: CGFloat = layout.size.height - bottomInset - 9.0
|
||||||
if optionsAvailable {
|
if optionsAvailable {
|
||||||
@ -482,24 +477,27 @@ class WallpaperGalleryController: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let replace = self.validLayout == nil
|
|
||||||
self.validLayout = (layout, 0.0)
|
self.validLayout = (layout, 0.0)
|
||||||
|
|
||||||
if replace {
|
if !hadLayout {
|
||||||
self.galleryNode.pager.replaceItems(self.entries.map({ WallpaperGalleryItem(account: self.account, entry: $0) }), centralItemIndex: self.centralEntryIndex)
|
self.galleryNode.pager.replaceItems(self.entries.map({ WallpaperGalleryItem(account: self.account, entry: $0) }), centralItemIndex: self.centralEntryIndex)
|
||||||
|
|
||||||
|
if let initialOptions = self.initialOptions, let itemNode = self.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode {
|
||||||
|
itemNode.options = initialOptions
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func actionPressed() {
|
private func actionPressed() {
|
||||||
guard let entry = self.currentEntry(), case let .wallpaper(wallpaper) = entry else {
|
guard let entry = self.currentEntry(), case let .wallpaper(wallpaper) = entry, let itemNode = self.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var options = ""
|
var options = ""
|
||||||
if (self.blurredButtonNode?.isSelected ?? false) {
|
if (itemNode.options.contains(.blur)) {
|
||||||
options = "?mode=blur"
|
options = "?mode=blur"
|
||||||
}
|
}
|
||||||
if (self.motionButtonNode?.isSelected ?? false) {
|
if (itemNode.options.contains(.motion)) {
|
||||||
if options.isEmpty {
|
if options.isEmpty {
|
||||||
options = "?mode=motion"
|
options = "?mode=motion"
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import Postbox
|
|||||||
|
|
||||||
final class WallpaperOptionButtonNode: HighlightTrackingButtonNode {
|
final class WallpaperOptionButtonNode: HighlightTrackingButtonNode {
|
||||||
private let backgroundNode: ASDisplayNode
|
private let backgroundNode: ASDisplayNode
|
||||||
private let checkNode: CheckNode
|
private let checkNode: ModernCheckNode
|
||||||
private let textNode: ASTextNode
|
private let textNode: ASTextNode
|
||||||
|
|
||||||
private var _isSelected: Bool = false
|
private var _isSelected: Bool = false
|
||||||
@ -16,18 +16,19 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode {
|
|||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
self._isSelected = newValue
|
self._isSelected = newValue
|
||||||
self.checkNode.setIsChecked(newValue, animated: false)
|
self.checkNode.setSelected(newValue, animated: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(title: String) {
|
init(title: String) {
|
||||||
self.backgroundNode = ASDisplayNode()
|
self.backgroundNode = ASDisplayNode()
|
||||||
self.backgroundNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.3)
|
self.backgroundNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.3)
|
||||||
self.backgroundNode.cornerRadius = 8.0
|
self.backgroundNode.cornerRadius = 6.0
|
||||||
self.checkNode = CheckNode(strokeColor: .white, fillColor: .white, foregroundColor: .black, style: .plain)
|
|
||||||
|
self.checkNode = ModernCheckNode(theme: CheckNodeTheme(backgroundColor: .white, strokeColor: .clear, borderColor: .white, hasShadow: false))
|
||||||
self.checkNode.isUserInteractionEnabled = false
|
self.checkNode.isUserInteractionEnabled = false
|
||||||
self.textNode = ASTextNode()
|
self.textNode = ASTextNode()
|
||||||
self.textNode.attributedText = NSAttributedString(string: title, font: Font.regular(13), textColor: .white)
|
self.textNode.attributedText = NSAttributedString(string: title, font: Font.medium(13), textColor: .white)
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
@ -60,9 +61,15 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var color: UIColor = UIColor(rgb: 0x000000, alpha: 0.3) {
|
||||||
|
didSet {
|
||||||
|
self.backgroundNode.backgroundColor = self.color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func setSelected(_ selected: Bool, animated: Bool = false) {
|
func setSelected(_ selected: Bool, animated: Bool = false) {
|
||||||
self._isSelected = selected
|
self._isSelected = selected
|
||||||
self.checkNode.setIsChecked(selected, animated: animated)
|
self.checkNode.setSelected(selected, animated: animated)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setEnabled(_ enabled: Bool) {
|
func setEnabled(_ enabled: Bool) {
|
||||||
@ -80,8 +87,8 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode {
|
|||||||
|
|
||||||
self.backgroundNode.frame = self.bounds
|
self.backgroundNode.frame = self.bounds
|
||||||
|
|
||||||
let checkSize = CGSize(width: 32.0, height: 32.0)
|
let checkSize = CGSize(width: 18.0, height: 18.0)
|
||||||
self.checkNode.frame = CGRect(origin: CGPoint(x: 5.0, y: -1.0), size: checkSize)
|
self.checkNode.frame = CGRect(origin: CGPoint(x: 12.0, y: 6.0), size: checkSize)
|
||||||
|
|
||||||
self.textNode.frame = CGRect(x: 39.0, y: 6.0 + UIScreenPixel, width: 100.0, height: 20.0)
|
self.textNode.frame = CGRect(x: 39.0, y: 6.0 + UIScreenPixel, width: 100.0, height: 20.0)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,34 @@ import Postbox
|
|||||||
import TelegramCore
|
import TelegramCore
|
||||||
import LegacyComponents
|
import LegacyComponents
|
||||||
|
|
||||||
|
private class WallpaperMotionEffect: UIInterpolatingMotionEffect {
|
||||||
|
var previousValue: CGFloat?
|
||||||
|
|
||||||
|
override func keyPathsAndRelativeValues(forViewerOffset viewerOffset: UIOffset) -> [String : Any]? {
|
||||||
|
var motionAmplitude: CGFloat = 0.0
|
||||||
|
switch self.type {
|
||||||
|
case .tiltAlongHorizontalAxis:
|
||||||
|
motionAmplitude = viewerOffset.horizontal
|
||||||
|
case .tiltAlongVerticalAxis:
|
||||||
|
motionAmplitude = viewerOffset.vertical
|
||||||
|
}
|
||||||
|
|
||||||
|
if (motionAmplitude > 0) {
|
||||||
|
guard let max = (self.maximumRelativeValue as? CGFloat) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
let value = max * motionAmplitude
|
||||||
|
return [self.keyPath: value]
|
||||||
|
} else {
|
||||||
|
guard let min = (self.minimumRelativeValue as? CGFloat) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
let value = -(min) * motionAmplitude
|
||||||
|
return [self.keyPath: value]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class WallpaperGalleryItem: GalleryItem {
|
class WallpaperGalleryItem: GalleryItem {
|
||||||
let account: Account
|
let account: Account
|
||||||
let entry: WallpaperGalleryEntry
|
let entry: WallpaperGalleryEntry
|
||||||
@ -32,7 +60,8 @@ class WallpaperGalleryItem: GalleryItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let progressDiameter: CGFloat = 50.0
|
private let progressDiameter: CGFloat = 50.0
|
||||||
|
private let motionAmount: CGFloat = 32.0
|
||||||
|
|
||||||
final class WallpaperGalleryItemNode: GalleryItemNode {
|
final class WallpaperGalleryItemNode: GalleryItemNode {
|
||||||
private let account: Account
|
private let account: Account
|
||||||
@ -46,15 +75,23 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
|||||||
private let blurredNode: BlurredImageNode
|
private let blurredNode: BlurredImageNode
|
||||||
let cropNode: WallpaperCropNode
|
let cropNode: WallpaperCropNode
|
||||||
|
|
||||||
|
private var blurButtonNode: WallpaperOptionButtonNode
|
||||||
|
private var motionButtonNode: WallpaperOptionButtonNode
|
||||||
|
|
||||||
fileprivate let _ready = Promise<Void>()
|
fileprivate let _ready = Promise<Void>()
|
||||||
private let fetchDisposable = MetaDisposable()
|
private let fetchDisposable = MetaDisposable()
|
||||||
private let statusDisposable = MetaDisposable()
|
private let statusDisposable = MetaDisposable()
|
||||||
|
private let colorDisposable = MetaDisposable()
|
||||||
|
|
||||||
let subtitle = Promise<String?>(nil)
|
let subtitle = Promise<String?>(nil)
|
||||||
let status = Promise<MediaResourceStatus>(.Local)
|
let status = Promise<MediaResourceStatus>(.Local)
|
||||||
let actionButton = Promise<UIBarButtonItem?>(nil)
|
let actionButton = Promise<UIBarButtonItem?>(nil)
|
||||||
|
let controlsColor = Promise<UIColor>(UIColor(rgb: 0x000000, alpha: 0.3))
|
||||||
var action: (() -> Void)?
|
var action: (() -> Void)?
|
||||||
|
|
||||||
|
private var validLayout: ContainerViewLayout?
|
||||||
|
private var validOffset: CGFloat?
|
||||||
|
|
||||||
init(account: Account) {
|
init(account: Account) {
|
||||||
self.account = account
|
self.account = account
|
||||||
|
|
||||||
@ -69,6 +106,10 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
|||||||
|
|
||||||
self.blurredNode = BlurredImageNode()
|
self.blurredNode = BlurredImageNode()
|
||||||
|
|
||||||
|
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||||
|
self.blurButtonNode = WallpaperOptionButtonNode(title: presentationData.strings.WallpaperPreview_Blurred)
|
||||||
|
self.motionButtonNode = WallpaperOptionButtonNode(title: presentationData.strings.WallpaperPreview_Motion)
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.clipsToBounds = true
|
self.clipsToBounds = true
|
||||||
@ -84,11 +125,18 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
|||||||
self.addSubnode(self.wrapperNode)
|
self.addSubnode(self.wrapperNode)
|
||||||
self.addSubnode(self.statusNode)
|
self.addSubnode(self.statusNode)
|
||||||
self.addSubnode(self.progressNode)
|
self.addSubnode(self.progressNode)
|
||||||
|
|
||||||
|
self.addSubnode(self.blurButtonNode)
|
||||||
|
self.addSubnode(self.motionButtonNode)
|
||||||
|
|
||||||
|
self.blurButtonNode.addTarget(self, action: #selector(self.toggleBlur), forControlEvents: .touchUpInside)
|
||||||
|
self.motionButtonNode.addTarget(self, action: #selector(self.toggleMotion), forControlEvents: .touchUpInside)
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.fetchDisposable.dispose()
|
self.fetchDisposable.dispose()
|
||||||
self.statusDisposable.dispose()
|
self.statusDisposable.dispose()
|
||||||
|
self.colorDisposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
var cropRect: CGRect? {
|
var cropRect: CGRect? {
|
||||||
@ -144,6 +192,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
|||||||
statusSignal = .single(.Local)
|
statusSignal = .single(.Local)
|
||||||
subtitleSignal = .single(nil)
|
subtitleSignal = .single(nil)
|
||||||
self.backgroundColor = UIColor(rgb: UInt32(bitPattern: color))
|
self.backgroundColor = UIColor(rgb: UInt32(bitPattern: color))
|
||||||
|
actionSignal = .single(defaultAction)
|
||||||
case let .file(file):
|
case let .file(file):
|
||||||
let dimensions = file.file.dimensions ?? CGSize(width: 100.0, height: 100.0)
|
let dimensions = file.file.dimensions ?? CGSize(width: 100.0, height: 100.0)
|
||||||
contentSize = dimensions
|
contentSize = dimensions
|
||||||
@ -187,7 +236,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
|||||||
subtitleSignal = .single(nil)
|
subtitleSignal = .single(nil)
|
||||||
}
|
}
|
||||||
self.cropNode.removeFromSupernode()
|
self.cropNode.removeFromSupernode()
|
||||||
case let .asset(asset, _):
|
case let .asset(asset):
|
||||||
let dimensions = CGSize(width: asset.pixelWidth, height: asset.pixelHeight)
|
let dimensions = CGSize(width: asset.pixelWidth, height: asset.pixelHeight)
|
||||||
contentSize = dimensions
|
contentSize = dimensions
|
||||||
displaySize = dimensions.dividedByScreenScale().integralFloor
|
displaySize = dimensions.dividedByScreenScale().integralFloor
|
||||||
@ -300,29 +349,53 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
|||||||
self.subtitle.set(subtitleSignal |> deliverOnMainQueue)
|
self.subtitle.set(subtitleSignal |> deliverOnMainQueue)
|
||||||
self.status.set(statusSignal |> deliverOnMainQueue)
|
self.status.set(statusSignal |> deliverOnMainQueue)
|
||||||
self.actionButton.set(actionSignal |> deliverOnMainQueue)
|
self.actionButton.set(actionSignal |> deliverOnMainQueue)
|
||||||
|
self.controlsColor.set(serviceColor(from: imagePromise.get()) |> deliverOnMainQueue)
|
||||||
|
self.colorDisposable.set((serviceColor(from: imagePromise.get())
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] color in
|
||||||
|
self?.blurButtonNode.color = color
|
||||||
|
self?.motionButtonNode.color = color
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setMotionEnabled(_ enabled: Bool) {
|
override func screenFrameUpdated(_ frame: CGRect) {
|
||||||
if enabled {
|
let offset = -frame.minX
|
||||||
let amount = 24.0
|
self.validOffset = offset
|
||||||
|
if let layout = self.validLayout {
|
||||||
let horizontal = UIInterpolatingMotionEffect(keyPath: "center.x", type: .tiltAlongHorizontalAxis)
|
self.updateButtonsLayout(layout: layout, offset: CGPoint(x: offset, y: 0.0), transition: .immediate)
|
||||||
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.wrapperNode.view.motionEffects {
|
|
||||||
self.wrapperNode.view.removeMotionEffect(effect)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateDismissTransition(_ value: CGFloat) {
|
||||||
|
if let layout = self.validLayout {
|
||||||
|
self.updateButtonsLayout(layout: layout, offset: CGPoint(x: 0.0, y: value), transition: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var options: WallpaperPresentationOptions {
|
||||||
|
get {
|
||||||
|
var options: WallpaperPresentationOptions = []
|
||||||
|
if self.blurButtonNode.isSelected {
|
||||||
|
options.insert(.blur)
|
||||||
|
}
|
||||||
|
if self.motionButtonNode.isSelected {
|
||||||
|
options.insert(.motion)
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
self.setBlurEnabled(newValue.contains(.blur), animated: false)
|
||||||
|
self.blurButtonNode.isSelected = newValue.contains(.blur)
|
||||||
|
|
||||||
|
self.setMotionEnabled(newValue.contains(.motion), animated: false)
|
||||||
|
self.motionButtonNode.isSelected = newValue.contains(.motion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func toggleBlur() {
|
||||||
|
let value = !self.blurButtonNode.isSelected
|
||||||
|
self.blurButtonNode.setSelected(value, animated: true)
|
||||||
|
self.setBlurEnabled(value, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setBlurEnabled(_ enabled: Bool, animated: Bool) {
|
func setBlurEnabled(_ enabled: Bool, animated: Bool) {
|
||||||
@ -334,8 +407,8 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
|||||||
self.blurredNode.frame = self.imageNode.bounds
|
self.blurredNode.frame = self.imageNode.bounds
|
||||||
self.imageNode.addSubnode(self.blurredNode)
|
self.imageNode.addSubnode(self.blurredNode)
|
||||||
} else {
|
} else {
|
||||||
self.blurredNode.frame = self.imageNode.frame
|
self.blurredNode.frame = self.imageNode.bounds
|
||||||
self.addSubnode(self.blurredNode)
|
self.imageNode.addSubnode(self.blurredNode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -364,14 +437,68 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func visibilityUpdated(isVisible: Bool) {
|
@objc func toggleMotion() {
|
||||||
super.visibilityUpdated(isVisible: isVisible)
|
let value = !self.motionButtonNode.isSelected
|
||||||
|
self.motionButtonNode.setSelected(value, animated: true)
|
||||||
|
self.setMotionEnabled(value, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setMotionEnabled(_ enabled: Bool, animated: Bool) {
|
||||||
|
if enabled {
|
||||||
|
let horizontal = WallpaperMotionEffect(keyPath: "center.x", type: .tiltAlongHorizontalAxis)
|
||||||
|
horizontal.minimumRelativeValue = motionAmount
|
||||||
|
horizontal.maximumRelativeValue = -motionAmount
|
||||||
|
|
||||||
|
let vertical = WallpaperMotionEffect(keyPath: "center.y", type: .tiltAlongVerticalAxis)
|
||||||
|
vertical.minimumRelativeValue = motionAmount
|
||||||
|
vertical.maximumRelativeValue = -motionAmount
|
||||||
|
|
||||||
|
let group = UIMotionEffectGroup()
|
||||||
|
group.motionEffects = [horizontal, vertical]
|
||||||
|
self.wrapperNode.view.addMotionEffect(group)
|
||||||
|
|
||||||
|
let scale = (self.frame.width + motionAmount * 2.0) / self.frame.width
|
||||||
|
if animated {
|
||||||
|
self.wrapperNode.layer.animateScale(from: 1.0, to: scale, duration: 0.2, removeOnCompletion: false)
|
||||||
|
} else {
|
||||||
|
self.wrapperNode.transform = CATransform3DMakeScale(scale, scale, 1.0)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let position = self.wrapperNode.layer.presentation()?.position
|
||||||
|
|
||||||
|
for effect in self.wrapperNode.view.motionEffects {
|
||||||
|
self.wrapperNode.view.removeMotionEffect(effect)
|
||||||
|
}
|
||||||
|
|
||||||
|
let scale = (self.frame.width + motionAmount * 2.0) / self.frame.width
|
||||||
|
if animated {
|
||||||
|
self.wrapperNode.layer.animateScale(from: scale, to: 1.0, duration: 0.2, removeOnCompletion: false)
|
||||||
|
if let position = position {
|
||||||
|
self.wrapperNode.layer.animatePosition(from: position, to: self.wrapperNode.layer.position, duration: 0.2)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.wrapperNode.transform = CATransform3DIdentity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateButtonsLayout(layout: ContainerViewLayout, offset: CGPoint, transition: ContainedViewLayoutTransition) {
|
||||||
|
let buttonSize = CGSize(width: 100.0, height: 30.0)
|
||||||
|
let alpha = 1.0 - min(1.0, max(0.0, abs(offset.y) / 50.0))
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.blurButtonNode, frame: CGRect(origin: CGPoint(x: floor(layout.size.width / 2.0 - buttonSize.width - 10.0) + offset.x, y: layout.size.height - 49.0 - layout.intrinsicInsets.bottom - 54.0 + offset.y), size: buttonSize))
|
||||||
|
transition.updateAlpha(node: self.blurButtonNode, alpha: alpha)
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.motionButtonNode, frame: CGRect(origin: CGPoint(x: ceil(layout.size.width / 2.0 + 10.0) + offset.x, y: layout.size.height - 49.0 - layout.intrinsicInsets.bottom - 54.0 + offset.y), size: buttonSize))
|
||||||
|
transition.updateAlpha(node: self.motionButtonNode, alpha: alpha)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
|
super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
|
||||||
|
|
||||||
self.wrapperNode.frame = CGRect(origin: CGPoint(), size: layout.size)
|
self.wrapperNode.bounds = CGRect(origin: CGPoint(), size: layout.size)
|
||||||
|
self.wrapperNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0)
|
||||||
|
|
||||||
if self.cropNode.supernode == nil {
|
if self.cropNode.supernode == nil {
|
||||||
self.imageNode.frame = self.wrapperNode.bounds
|
self.imageNode.frame = self.wrapperNode.bounds
|
||||||
self.blurredNode.frame = self.imageNode.frame
|
self.blurredNode.frame = self.imageNode.frame
|
||||||
@ -389,5 +516,13 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
|||||||
|
|
||||||
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)
|
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)
|
||||||
self.progressNode.frame = CGRect(x: layout.safeInsets.left + floorToScreenPixels((layout.size.width - layout.safeInsets.left - layout.safeInsets.right - progressDiameter) / 2.0), y: floorToScreenPixels((layout.size.height - 15.0) / 2.0), width: progressDiameter, height: progressDiameter)
|
self.progressNode.frame = CGRect(x: layout.safeInsets.left + floorToScreenPixels((layout.size.width - layout.safeInsets.left - layout.safeInsets.right - progressDiameter) / 2.0), y: floorToScreenPixels((layout.size.height - 15.0) / 2.0), width: progressDiameter, height: progressDiameter)
|
||||||
|
|
||||||
|
var offset: CGFloat = 0.0
|
||||||
|
if let validOffset = self.validOffset {
|
||||||
|
offset = validOffset
|
||||||
|
}
|
||||||
|
self.updateButtonsLayout(layout: layout, offset: CGPoint(x: offset, y: 0.0), transition: transition)
|
||||||
|
|
||||||
|
self.validLayout = layout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -232,13 +232,6 @@ private final class WallpaperBackgroundNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
let controlsColorSignal: Signal<UIColor, NoError>
|
|
||||||
if case let .wallpaper(wallpaper) = wallpaper {
|
|
||||||
controlsColorSignal = chatBackgroundContrastColor(wallpaper: wallpaper, postbox: account.postbox)
|
|
||||||
} else {
|
|
||||||
controlsColorSignal = backgroundContrastColor(for: imagePromise.get())
|
|
||||||
}
|
|
||||||
self.controlsColor.set(.single(.white) |> then(controlsColorSignal))
|
|
||||||
self.status.set(statusSignal)
|
self.status.set(statusSignal)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -506,20 +499,20 @@ final class WallpaperListPreviewControllerNode: ViewControllerTracingNode {
|
|||||||
if case let .wallpapers(wallpaperMode) = type, let mode = wallpaperMode {
|
if case let .wallpapers(wallpaperMode) = type, let mode = wallpaperMode {
|
||||||
self.segmentedControl.selectedSegmentIndex = Int(clamping: mode.rawValue)
|
self.segmentedControl.selectedSegmentIndex = Int(clamping: mode.rawValue)
|
||||||
}
|
}
|
||||||
case let .slug(slug, file):
|
case let .slug(slug, file, _):
|
||||||
if let file = file {
|
if let file = file {
|
||||||
let entry = WallpaperEntry.wallpaper(.file(id: 0, accessHash: 0, isCreator: false, isDefault: false, slug: slug, file: file))
|
let entry = WallpaperEntry.wallpaper(.file(id: 0, accessHash: 0, isCreator: false, isDefault: false, slug: slug, file: file))
|
||||||
self.wallpapers = [entry]
|
self.wallpapers = [entry]
|
||||||
self.centralWallpaper = entry
|
self.centralWallpaper = entry
|
||||||
}
|
}
|
||||||
self.ready.set(true)
|
self.ready.set(true)
|
||||||
case let .wallpaper(wallpaper):
|
case let .wallpaper(wallpaper, _):
|
||||||
let entry = WallpaperEntry.wallpaper(wallpaper)
|
let entry = WallpaperEntry.wallpaper(wallpaper)
|
||||||
self.wallpapers = [entry]
|
self.wallpapers = [entry]
|
||||||
self.centralWallpaper = entry
|
self.centralWallpaper = entry
|
||||||
self.ready.set(true)
|
self.ready.set(true)
|
||||||
case let .asset(asset, thumbnailImage):
|
case let .asset(asset):
|
||||||
let entry = WallpaperEntry.asset(asset, thumbnailImage)
|
let entry = WallpaperEntry.asset(asset, nil)
|
||||||
self.wallpapers = [entry]
|
self.wallpapers = [entry]
|
||||||
self.centralWallpaper = entry
|
self.centralWallpaper = entry
|
||||||
self.ready.set(true)
|
self.ready.set(true)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user