mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-02 00:17:02 +00:00
Pattern wallpaper improvements and bug fixes
This commit is contained in:
parent
42cc137468
commit
dfd335741b
@ -11,6 +11,8 @@
|
|||||||
0900678F21ED8E0E00530762 /* HexColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0900678E21ED8E0E00530762 /* HexColor.swift */; };
|
0900678F21ED8E0E00530762 /* HexColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0900678E21ED8E0E00530762 /* HexColor.swift */; };
|
||||||
0902838821931D960067EFBD /* LanguageSuggestionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0902838721931D960067EFBD /* LanguageSuggestionController.swift */; };
|
0902838821931D960067EFBD /* LanguageSuggestionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0902838721931D960067EFBD /* LanguageSuggestionController.swift */; };
|
||||||
0902838D2194AEB90067EFBD /* ImageTransparency.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0902838C2194AEB90067EFBD /* ImageTransparency.swift */; };
|
0902838D2194AEB90067EFBD /* ImageTransparency.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0902838C2194AEB90067EFBD /* ImageTransparency.swift */; };
|
||||||
|
090B48C421FCB431005083FA /* test1.webp in Resources */ = {isa = PBXBuildFile; fileRef = 090B48C121FCB32C005083FA /* test1.webp */; };
|
||||||
|
090B48C521FCB433005083FA /* test2.png in Resources */ = {isa = PBXBuildFile; fileRef = 090B48C021FCB32C005083FA /* test2.png */; };
|
||||||
090E63E62195880F00E3C035 /* ContactAddItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E63E52195880F00E3C035 /* ContactAddItem.swift */; };
|
090E63E62195880F00E3C035 /* ContactAddItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E63E52195880F00E3C035 /* ContactAddItem.swift */; };
|
||||||
090E63EE2196FE3A00E3C035 /* OpenAddContact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E63ED2196FE3A00E3C035 /* OpenAddContact.swift */; };
|
090E63EE2196FE3A00E3C035 /* OpenAddContact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E63ED2196FE3A00E3C035 /* OpenAddContact.swift */; };
|
||||||
0910B0ED21FA178C00F8F87D /* SolidColorMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0910B0EC21FA178C00F8F87D /* SolidColorMedia.swift */; };
|
0910B0ED21FA178C00F8F87D /* SolidColorMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0910B0EC21FA178C00F8F87D /* SolidColorMedia.swift */; };
|
||||||
@ -1141,6 +1143,8 @@
|
|||||||
0900678E21ED8E0E00530762 /* HexColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HexColor.swift; sourceTree = "<group>"; };
|
0900678E21ED8E0E00530762 /* HexColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HexColor.swift; sourceTree = "<group>"; };
|
||||||
0902838721931D960067EFBD /* LanguageSuggestionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageSuggestionController.swift; sourceTree = "<group>"; };
|
0902838721931D960067EFBD /* LanguageSuggestionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageSuggestionController.swift; sourceTree = "<group>"; };
|
||||||
0902838C2194AEB90067EFBD /* ImageTransparency.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageTransparency.swift; sourceTree = "<group>"; };
|
0902838C2194AEB90067EFBD /* ImageTransparency.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageTransparency.swift; sourceTree = "<group>"; };
|
||||||
|
090B48C021FCB32C005083FA /* test2.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = test2.png; path = ../../../../Desktop/test2.png; sourceTree = "<group>"; };
|
||||||
|
090B48C121FCB32C005083FA /* test1.webp */ = {isa = PBXFileReference; lastKnownFileType = file; name = test1.webp; path = ../../../../Desktop/test1.webp; sourceTree = "<group>"; };
|
||||||
090E63E52195880F00E3C035 /* ContactAddItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactAddItem.swift; sourceTree = "<group>"; };
|
090E63E52195880F00E3C035 /* ContactAddItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactAddItem.swift; sourceTree = "<group>"; };
|
||||||
090E63ED2196FE3A00E3C035 /* OpenAddContact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenAddContact.swift; sourceTree = "<group>"; };
|
090E63ED2196FE3A00E3C035 /* OpenAddContact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenAddContact.swift; sourceTree = "<group>"; };
|
||||||
0910B0EC21FA178C00F8F87D /* SolidColorMedia.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SolidColorMedia.swift; sourceTree = "<group>"; };
|
0910B0EC21FA178C00F8F87D /* SolidColorMedia.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SolidColorMedia.swift; sourceTree = "<group>"; };
|
||||||
@ -2881,6 +2885,8 @@
|
|||||||
D0471B521EFD8EBC0074D609 /* Resources */ = {
|
D0471B521EFD8EBC0074D609 /* Resources */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
090B48C121FCB32C005083FA /* test1.webp */,
|
||||||
|
090B48C021FCB32C005083FA /* test2.png */,
|
||||||
D0955FB32191278C00F89427 /* PresentationStrings.mapping */,
|
D0955FB32191278C00F89427 /* PresentationStrings.mapping */,
|
||||||
09310D13213BC5DE0020033A /* Animations */,
|
09310D13213BC5DE0020033A /* Animations */,
|
||||||
092F368B2154AAD6001A9F49 /* Fonts */,
|
092F368B2154AAD6001A9F49 /* Fonts */,
|
||||||
@ -5033,6 +5039,7 @@
|
|||||||
files = (
|
files = (
|
||||||
09874E4F21078FA100E190B8 /* Generic.html in Resources */,
|
09874E4F21078FA100E190B8 /* Generic.html in Resources */,
|
||||||
09874E5021078FA100E190B8 /* GenericUserScript.js in Resources */,
|
09874E5021078FA100E190B8 /* GenericUserScript.js in Resources */,
|
||||||
|
090B48C521FCB433005083FA /* test2.png in Resources */,
|
||||||
09874E5121078FA100E190B8 /* Instagram.html in Resources */,
|
09874E5121078FA100E190B8 /* Instagram.html in Resources */,
|
||||||
09874E5221078FA100E190B8 /* Twitch.html in Resources */,
|
09874E5221078FA100E190B8 /* Twitch.html in Resources */,
|
||||||
09874E5321078FA100E190B8 /* TwitchUserScript.js in Resources */,
|
09874E5321078FA100E190B8 /* TwitchUserScript.js in Resources */,
|
||||||
@ -5093,6 +5100,7 @@
|
|||||||
D0E9BA981F056F4C00F079A4 /* stp_card_applepay_template@3x.png in Resources */,
|
D0E9BA981F056F4C00F079A4 /* stp_card_applepay_template@3x.png in Resources */,
|
||||||
D0E9BAA51F056F4C00F079A4 /* stp_card_form_applepay@2x.png in Resources */,
|
D0E9BAA51F056F4C00F079A4 /* stp_card_form_applepay@2x.png in Resources */,
|
||||||
D0E9BAB81F056F4C00F079A4 /* stp_card_visa_template@3x.png in Resources */,
|
D0E9BAB81F056F4C00F079A4 /* stp_card_visa_template@3x.png in Resources */,
|
||||||
|
090B48C421FCB431005083FA /* test1.webp in Resources */,
|
||||||
D0E9BA9B1F056F4C00F079A4 /* stp_card_cvc_amex@2x.png in Resources */,
|
D0E9BA9B1F056F4C00F079A4 /* stp_card_cvc_amex@2x.png in Resources */,
|
||||||
09310D30213ED5FB0020033A /* anim_unread.json in Resources */,
|
09310D30213ED5FB0020033A /* anim_unread.json in Resources */,
|
||||||
D0E9BAB61F056F4C00F079A4 /* stp_card_visa@3x.png in Resources */,
|
D0E9BAB61F056F4C00F079A4 /* stp_card_visa@3x.png in Resources */,
|
||||||
|
@ -128,18 +128,20 @@ final class CachedPatternWallpaperMaskRepresentation: CachedMediaResourceReprese
|
|||||||
|
|
||||||
final class CachedPatternWallpaperRepresentation: CachedMediaResourceRepresentation {
|
final class CachedPatternWallpaperRepresentation: CachedMediaResourceRepresentation {
|
||||||
let color: Int32
|
let color: Int32
|
||||||
|
let intensity: Int32
|
||||||
|
|
||||||
var uniqueId: String {
|
var uniqueId: String {
|
||||||
return "pattern-wallpaper-\(self.color)"
|
return "pattern-wallpaper-\(self.color)-\(self.intensity)"
|
||||||
}
|
}
|
||||||
|
|
||||||
init(color: Int32) {
|
init(color: Int32, intensity: Int32) {
|
||||||
self.color = color
|
self.color = color
|
||||||
|
self.intensity = intensity
|
||||||
}
|
}
|
||||||
|
|
||||||
func isEqual(to: CachedMediaResourceRepresentation) -> Bool {
|
func isEqual(to: CachedMediaResourceRepresentation) -> Bool {
|
||||||
if let to = to as? CachedPatternWallpaperRepresentation {
|
if let to = to as? CachedPatternWallpaperRepresentation {
|
||||||
return self.color == to.color
|
return self.color == to.color && self.intensity == intensity
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -102,9 +102,9 @@ func chatControllerBackgroundImage(wallpaper: TelegramWallpaper, mode: Wallpaper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case let .file(file):
|
case let .file(file):
|
||||||
if file.isPattern, let color = file.settings.color {
|
if file.isPattern, let color = file.settings.color, let intensity = file.settings.intensity {
|
||||||
var image: UIImage?
|
var image: UIImage?
|
||||||
let _ = postbox.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedPatternWallpaperRepresentation(color: color), complete: true, fetch: true, attemptSynchronously: true).start(next: { data in
|
let _ = postbox.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedPatternWallpaperRepresentation(color: color, intensity: intensity), complete: true, fetch: true, attemptSynchronously: true).start(next: { data in
|
||||||
if data.complete {
|
if data.complete {
|
||||||
image = UIImage(contentsOfFile: data.path)?.precomposed()
|
image = UIImage(contentsOfFile: data.path)?.precomposed()
|
||||||
}
|
}
|
||||||
|
@ -268,7 +268,6 @@ final class ChatHistorySearchContainerNode: SearchDisplayControllerContentNode {
|
|||||||
let emptyTitleY = navigationBarHeight + floorToScreenPixels((layout.size.height - navigationBarHeight - max(insets.bottom, layout.intrinsicInsets.bottom) - emptyTotalHeight) / 2.0)
|
let emptyTitleY = navigationBarHeight + floorToScreenPixels((layout.size.height - navigationBarHeight - max(insets.bottom, layout.intrinsicInsets.bottom) - emptyTotalHeight) / 2.0)
|
||||||
|
|
||||||
transition.updateFrame(node: self.emptyResultsTitleNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + padding + (layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0 - emptyTitleSize.width) / 2.0, y: emptyTitleY), size: emptyTitleSize))
|
transition.updateFrame(node: self.emptyResultsTitleNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + padding + (layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0 - emptyTitleSize.width) / 2.0, y: emptyTitleY), size: emptyTitleSize))
|
||||||
|
|
||||||
transition.updateFrame(node: self.emptyResultsTextNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + padding + (layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0 - emptyTextSize.width) / 2.0, y: emptyTitleY + emptyTitleSize.height + emptyTextSpacing), size: emptyTextSize))
|
transition.updateFrame(node: self.emptyResultsTextNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + padding + (layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0 - emptyTextSize.width) / 2.0, y: emptyTitleY + emptyTitleSize.height + emptyTextSpacing), size: emptyTextSize))
|
||||||
|
|
||||||
self.listNode.frame = CGRect(origin: CGPoint(), size: layout.size)
|
self.listNode.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||||
|
@ -338,7 +338,7 @@ private func fetchCachedBlurredWallpaperRepresentation(account: Account, resourc
|
|||||||
private func fetchCachedPatternWallpaperMaskRepresentation(account: Account, resource: MediaResource, resourceData: MediaResourceData, representation: CachedPatternWallpaperMaskRepresentation) -> Signal<CachedMediaResourceRepresentationResult, NoError> {
|
private func fetchCachedPatternWallpaperMaskRepresentation(account: Account, resource: MediaResource, resourceData: MediaResourceData, representation: CachedPatternWallpaperMaskRepresentation) -> Signal<CachedMediaResourceRepresentationResult, NoError> {
|
||||||
return Signal({ subscriber in
|
return Signal({ subscriber in
|
||||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) {
|
if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) {
|
||||||
if let image = UIImage.convert(fromWebP: data) {
|
if let image = UIImage(data: data) {
|
||||||
var randomId: Int64 = 0
|
var randomId: Int64 = 0
|
||||||
arc4random_buf(&randomId, 8)
|
arc4random_buf(&randomId, 8)
|
||||||
let path = NSTemporaryDirectory() + "\(randomId)"
|
let path = NSTemporaryDirectory() + "\(randomId)"
|
||||||
@ -377,7 +377,7 @@ private func fetchCachedPatternWallpaperMaskRepresentation(account: Account, res
|
|||||||
private func fetchCachedPatternWallpaperRepresentation(account: Account, resource: MediaResource, resourceData: MediaResourceData, representation: CachedPatternWallpaperRepresentation) -> Signal<CachedMediaResourceRepresentationResult, NoError> {
|
private func fetchCachedPatternWallpaperRepresentation(account: Account, resource: MediaResource, resourceData: MediaResourceData, representation: CachedPatternWallpaperRepresentation) -> Signal<CachedMediaResourceRepresentationResult, NoError> {
|
||||||
return Signal({ subscriber in
|
return Signal({ subscriber in
|
||||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) {
|
if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) {
|
||||||
if let image = UIImage.convert(fromWebP: data) {
|
if let image = UIImage(data: data) {
|
||||||
var randomId: Int64 = 0
|
var randomId: Int64 = 0
|
||||||
arc4random_buf(&randomId, 8)
|
arc4random_buf(&randomId, 8)
|
||||||
let path = NSTemporaryDirectory() + "\(randomId)"
|
let path = NSTemporaryDirectory() + "\(randomId)"
|
||||||
@ -386,7 +386,7 @@ private func fetchCachedPatternWallpaperRepresentation(account: Account, resourc
|
|||||||
let size = CGSize(width: image.size.width * image.scale, height: image.size.height * image.scale)
|
let size = CGSize(width: image.size.width * image.scale, height: image.size.height * image.scale)
|
||||||
|
|
||||||
let backgroundColor = UIColor(rgb: UInt32(bitPattern: representation.color))
|
let backgroundColor = UIColor(rgb: UInt32(bitPattern: representation.color))
|
||||||
let foregroundColor = patternColor(for: backgroundColor)
|
let foregroundColor = patternColor(for: backgroundColor, intensity: CGFloat(representation.intensity) / 100.0)
|
||||||
|
|
||||||
let colorImage = generateImage(size, contextGenerator: { size, c in
|
let colorImage = generateImage(size, contextGenerator: { size, c in
|
||||||
let rect = CGRect(origin: CGPoint(), size: size)
|
let rect = CGRect(origin: CGPoint(), size: size)
|
||||||
|
@ -54,6 +54,9 @@ class ModernCheckNode: ASDisplayNode {
|
|||||||
|
|
||||||
var selected = false
|
var selected = false
|
||||||
func setSelected(_ selected: Bool, animated: Bool = false) {
|
func setSelected(_ selected: Bool, animated: Bool = false) {
|
||||||
|
guard self.selected != selected else {
|
||||||
|
return
|
||||||
|
}
|
||||||
self.selected = selected
|
self.selected = selected
|
||||||
|
|
||||||
if selected && animated {
|
if selected && animated {
|
||||||
@ -74,6 +77,7 @@ class ModernCheckNode: ASDisplayNode {
|
|||||||
animation.duration = 0.21
|
animation.duration = 0.21
|
||||||
self.pop_add(animation, forKey: "progress")
|
self.pop_add(animation, forKey: "progress")
|
||||||
} else {
|
} else {
|
||||||
|
self.pop_removeAllAnimations()
|
||||||
self.animationProgress = selected ? 1.0 : 0.0
|
self.animationProgress = selected ? 1.0 : 0.0
|
||||||
self.setNeedsDisplay()
|
self.setNeedsDisplay()
|
||||||
}
|
}
|
||||||
|
@ -417,8 +417,8 @@ 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, options):
|
case let .slug(slug, options, color, intensity):
|
||||||
source = .slug(slug, content.file, options)
|
source = .slug(slug, content.file, options, color, intensity)
|
||||||
case let .color(color):
|
case let .color(color):
|
||||||
source = .wallpaper(.color(Int32(color.rgb)), nil)
|
source = .wallpaper(.color(Int32(color.rgb)), nil)
|
||||||
}
|
}
|
||||||
|
@ -200,7 +200,7 @@ func openResolvedUrl(_ resolvedUrl: ResolvedUrl, account: Account, context: Open
|
|||||||
let signal: Signal<TelegramWallpaper, GetWallpaperError>
|
let signal: Signal<TelegramWallpaper, GetWallpaperError>
|
||||||
var options: WallpaperPresentationOptions?
|
var options: WallpaperPresentationOptions?
|
||||||
switch parameter {
|
switch parameter {
|
||||||
case let .slug(slug, wallpaperOptions):
|
case let .slug(slug, wallpaperOptions, color, intensity):
|
||||||
signal = getWallpaper(account: account, slug: slug)
|
signal = getWallpaper(account: account, slug: slug)
|
||||||
options = wallpaperOptions
|
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))
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -207,6 +207,10 @@ public final class RadialStatusNode: ASControlNode {
|
|||||||
contentNode.frame = self.bounds
|
contentNode.frame = self.bounds
|
||||||
contentNode.prepareAnimateIn(from: nil)
|
contentNode.prepareAnimateIn(from: nil)
|
||||||
self.addSubnode(contentNode)
|
self.addSubnode(contentNode)
|
||||||
|
if animated, case .check = state, self.isNodeLoaded {
|
||||||
|
contentNode.layout()
|
||||||
|
contentNode.animateIn(from: fromState, delay: 0.0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.transitionToBackgroundColor(backgroundColor, previousContentNode: nil, animated: animated, completion: completion)
|
self.transitionToBackgroundColor(backgroundColor, previousContentNode: nil, animated: animated, completion: completion)
|
||||||
}
|
}
|
||||||
@ -232,7 +236,15 @@ public final class RadialStatusNode: ASControlNode {
|
|||||||
backgroundNode.frame = self.bounds
|
backgroundNode.frame = self.bounds
|
||||||
self.backgroundNode = backgroundNode
|
self.backgroundNode = backgroundNode
|
||||||
self.insertSubnode(backgroundNode, at: 0)
|
self.insertSubnode(backgroundNode, at: 0)
|
||||||
completion()
|
|
||||||
|
if animated {
|
||||||
|
backgroundNode.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2, removeOnCompletion: false)
|
||||||
|
backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||||
|
completion()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
completion()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if let backgroundNode = self.backgroundNode {
|
} else if let backgroundNode = self.backgroundNode {
|
||||||
self.backgroundNode = nil
|
self.backgroundNode = nil
|
||||||
|
Binary file not shown.
@ -25,6 +25,7 @@ private func whiteColorImage(theme: PresentationTheme) -> Signal<(TransformImage
|
|||||||
|
|
||||||
final class SettingsThemeWallpaperNode: ASDisplayNode {
|
final class SettingsThemeWallpaperNode: ASDisplayNode {
|
||||||
private var wallpaper: TelegramWallpaper?
|
private var wallpaper: TelegramWallpaper?
|
||||||
|
private var color: UIColor?
|
||||||
|
|
||||||
let buttonNode = HighlightTrackingButtonNode()
|
let buttonNode = HighlightTrackingButtonNode()
|
||||||
let backgroundNode = ASDisplayNode()
|
let backgroundNode = ASDisplayNode()
|
||||||
@ -33,10 +34,10 @@ final class SettingsThemeWallpaperNode: ASDisplayNode {
|
|||||||
|
|
||||||
var pressed: (() -> Void)?
|
var pressed: (() -> Void)?
|
||||||
|
|
||||||
override init() {
|
init(overlayBackgroundColor: UIColor = UIColor(white: 0.0, alpha: 0.3)) {
|
||||||
self.imageNode.contentAnimations = [.subsequentUpdates]
|
self.imageNode.contentAnimations = [.subsequentUpdates]
|
||||||
|
|
||||||
self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.3))
|
self.statusNode = RadialStatusNode(backgroundNodeColor: overlayBackgroundColor)
|
||||||
let progressDiameter: CGFloat = 50.0
|
let progressDiameter: CGFloat = 50.0
|
||||||
self.statusNode.frame = CGRect(x: 0.0, y: 0.0, width: progressDiameter, height: progressDiameter)
|
self.statusNode.frame = CGRect(x: 0.0, y: 0.0, width: progressDiameter, height: progressDiameter)
|
||||||
self.statusNode.isUserInteractionEnabled = false
|
self.statusNode.isUserInteractionEnabled = false
|
||||||
@ -51,16 +52,15 @@ final class SettingsThemeWallpaperNode: ASDisplayNode {
|
|||||||
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setSelected(_ selected: Bool) {
|
func setSelected(_ selected: Bool, animated: Bool = false) {
|
||||||
let state: RadialStatusNodeState = selected ? .check(.white) : .none
|
let state: RadialStatusNodeState = selected ? .check(.white) : .none
|
||||||
self.statusNode.transitionToState(state, animated: false, completion: {})
|
self.statusNode.transitionToState(state, animated: animated, completion: {})
|
||||||
}
|
}
|
||||||
|
|
||||||
func setWallpaper(account: Account, wallpaper: TelegramWallpaper, selected: Bool, size: CGSize, cornerRadius: CGFloat = 0.0) {
|
func setWallpaper(account: Account, wallpaper: TelegramWallpaper, selected: Bool, size: CGSize, cornerRadius: CGFloat = 0.0) {
|
||||||
self.buttonNode.frame = CGRect(origin: CGPoint(), size: size)
|
self.buttonNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size)
|
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
self.imageNode.frame = CGRect(origin: CGPoint(), size: size)
|
self.imageNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
self.cornerRadius = 0.0
|
|
||||||
|
|
||||||
let state: RadialStatusNodeState = selected ? .check(.white) : .none
|
let state: RadialStatusNodeState = selected ? .check(.white) : .none
|
||||||
self.statusNode.transitionToState(state, animated: false, completion: {})
|
self.statusNode.transitionToState(state, animated: false, completion: {})
|
||||||
@ -90,7 +90,6 @@ final class SettingsThemeWallpaperNode: ASDisplayNode {
|
|||||||
self.imageNode.isHidden = true
|
self.imageNode.isHidden = true
|
||||||
self.backgroundNode.isHidden = false
|
self.backgroundNode.isHidden = false
|
||||||
self.backgroundNode.backgroundColor = UIColor(rgb: UInt32(bitPattern: color))
|
self.backgroundNode.backgroundColor = UIColor(rgb: UInt32(bitPattern: color))
|
||||||
self.cornerRadius = cornerRadius
|
|
||||||
}
|
}
|
||||||
case let .image(representations, _):
|
case let .image(representations, _):
|
||||||
self.imageNode.isHidden = false
|
self.imageNode.isHidden = false
|
||||||
@ -112,12 +111,17 @@ final class SettingsThemeWallpaperNode: ASDisplayNode {
|
|||||||
if file.isPattern {
|
if file.isPattern {
|
||||||
self.backgroundNode.isHidden = false
|
self.backgroundNode.isHidden = false
|
||||||
|
|
||||||
var patternColor = UIColor(rgb: 0xd6e2ee)
|
var patternColor = UIColor(rgb: 0xd6e2ee, alpha: 0.5)
|
||||||
|
var patternIntensity: CGFloat = 0.5
|
||||||
if let color = file.settings.color {
|
if let color = file.settings.color {
|
||||||
patternColor = UIColor(rgb: UInt32(bitPattern: color))
|
if let intensity = file.settings.intensity {
|
||||||
|
patternIntensity = CGFloat(intensity) / 100.0
|
||||||
|
}
|
||||||
|
patternColor = UIColor(rgb: UInt32(bitPattern: color), alpha: patternIntensity)
|
||||||
}
|
}
|
||||||
self.backgroundNode.backgroundColor = patternColor
|
self.backgroundNode.backgroundColor = patternColor
|
||||||
imageSignal = patternWallpaperImage(account: account, representations: convertedRepresentations, color: patternColor, mode: .thumbnail, autoFetchFullSize: true)
|
self.color = patternColor
|
||||||
|
imageSignal = patternWallpaperImage(account: account, representations: convertedRepresentations, mode: .thumbnail, autoFetchFullSize: true)
|
||||||
} else {
|
} else {
|
||||||
self.backgroundNode.isHidden = true
|
self.backgroundNode.isHidden = true
|
||||||
|
|
||||||
@ -126,7 +130,7 @@ final class SettingsThemeWallpaperNode: ASDisplayNode {
|
|||||||
self.imageNode.setSignal(imageSignal)
|
self.imageNode.setSignal(imageSignal)
|
||||||
|
|
||||||
let dimensions = file.file.dimensions ?? CGSize(width: 100.0, height: 100.0)
|
let dimensions = file.file.dimensions ?? CGSize(width: 100.0, height: 100.0)
|
||||||
let apply = self.imageNode.asyncLayout()(TransformImageArguments(corners: corners, imageSize: dimensions.aspectFilled(size), boundingSize: size, intrinsicInsets: UIEdgeInsets()))
|
let apply = self.imageNode.asyncLayout()(TransformImageArguments(corners: corners, imageSize: dimensions.aspectFilled(size), boundingSize: size, intrinsicInsets: UIEdgeInsets(), emptyColor: self.color))
|
||||||
apply()
|
apply()
|
||||||
}
|
}
|
||||||
} else if let wallpaper = self.wallpaper {
|
} else if let wallpaper = self.wallpaper {
|
||||||
@ -139,7 +143,7 @@ final class SettingsThemeWallpaperNode: ASDisplayNode {
|
|||||||
apply()
|
apply()
|
||||||
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)
|
||||||
let apply = self.imageNode.asyncLayout()(TransformImageArguments(corners: corners, imageSize: dimensions.aspectFilled(size), boundingSize: size, intrinsicInsets: UIEdgeInsets()))
|
let apply = self.imageNode.asyncLayout()(TransformImageArguments(corners: corners, imageSize: dimensions.aspectFilled(size), boundingSize: size, intrinsicInsets: UIEdgeInsets(), emptyColor: self.color))
|
||||||
apply()
|
apply()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,10 +25,8 @@ public final class TelegramRootController: NavigationController {
|
|||||||
|
|
||||||
super.init(mode: .automaticMasterDetail, theme: NavigationControllerTheme(presentationTheme: self.presentationData.theme))
|
super.init(mode: .automaticMasterDetail, theme: NavigationControllerTheme(presentationTheme: self.presentationData.theme))
|
||||||
|
|
||||||
//self.permissionsDisposable =
|
|
||||||
|
|
||||||
self.presentationDataDisposable = (account.telegramApplicationContext.presentationData
|
self.presentationDataDisposable = (account.telegramApplicationContext.presentationData
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
let previousTheme = strongSelf.presentationData.theme
|
let previousTheme = strongSelf.presentationData.theme
|
||||||
strongSelf.presentationData = presentationData
|
strongSelf.presentationData = presentationData
|
||||||
|
@ -370,10 +370,24 @@ final class ThemeGridController: ViewController {
|
|||||||
for wallpaper in wallpapers {
|
for wallpaper in wallpapers {
|
||||||
var item: String?
|
var item: String?
|
||||||
switch wallpaper {
|
switch wallpaper {
|
||||||
case let .file(_, _, _, _, _, slug, _, _):
|
case let .file(_, _, _, _, isPattern, slug, _, settings):
|
||||||
item = slug
|
var options: [String] = []
|
||||||
|
if isPattern {
|
||||||
|
if let color = settings.color {
|
||||||
|
options.append("bg_color=\(UIColor(rgb: UInt32(bitPattern: color)).hexString)")
|
||||||
|
}
|
||||||
|
if let intensity = settings.intensity {
|
||||||
|
options.append("intensity=\(intensity)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var optionsString = ""
|
||||||
|
if !options.isEmpty {
|
||||||
|
optionsString = "?\(options.joined(separator: "&"))"
|
||||||
|
}
|
||||||
|
item = slug + optionsString
|
||||||
case let .color(color):
|
case let .color(color):
|
||||||
item = "\(String(UInt32(bitPattern: color), radix: 16, uppercase: false))"
|
item = "\(UIColor(rgb: UInt32(bitPattern: color)).hexString)"
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ final class ThemeGridControllerItem: GridItem {
|
|||||||
|
|
||||||
func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode {
|
func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode {
|
||||||
let node = ThemeGridControllerItemNode()
|
let node = ThemeGridControllerItemNode()
|
||||||
node.setup(account: self.account, wallpaper: self.wallpaper, index: self.index, selected: self.selected, interaction: self.interaction)
|
node.setup(account: self.account, wallpaper: self.wallpaper, selected: self.selected, interaction: self.interaction)
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ final class ThemeGridControllerItem: GridItem {
|
|||||||
assertionFailure()
|
assertionFailure()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
node.setup(account: self.account, wallpaper: self.wallpaper, index: self.index, selected: self.selected, interaction: self.interaction)
|
node.setup(account: self.account, wallpaper: self.wallpaper, selected: self.selected, interaction: self.interaction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ final class ThemeGridControllerItemNode: GridItemNode {
|
|||||||
private let wallpaperNode: SettingsThemeWallpaperNode
|
private let wallpaperNode: SettingsThemeWallpaperNode
|
||||||
private var selectionNode: GridMessageSelectionNode?
|
private var selectionNode: GridMessageSelectionNode?
|
||||||
|
|
||||||
private var currentState: (Account, TelegramWallpaper, Int, Bool)?
|
private var currentState: (Account, TelegramWallpaper, Bool)?
|
||||||
private var interaction: ThemeGridControllerInteraction?
|
private var interaction: ThemeGridControllerInteraction?
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
@ -57,38 +57,39 @@ final class ThemeGridControllerItemNode: GridItemNode {
|
|||||||
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||||
}
|
}
|
||||||
|
|
||||||
func setup(account: Account, wallpaper: TelegramWallpaper, index: Int, selected: Bool, interaction: ThemeGridControllerInteraction) {
|
func setup(account: Account, wallpaper: TelegramWallpaper, selected: Bool, interaction: ThemeGridControllerInteraction) {
|
||||||
self.interaction = interaction
|
self.interaction = interaction
|
||||||
|
|
||||||
if self.currentState == nil || self.currentState!.0 !== account || wallpaper != self.currentState!.1 || index != self.currentState!.2 || selected != self.currentState!.3 {
|
if self.currentState == nil || self.currentState!.0 !== account || wallpaper != self.currentState!.1 || selected != self.currentState!.2 {
|
||||||
self.currentState = (account, wallpaper, index, selected)
|
self.currentState = (account, wallpaper, selected)
|
||||||
|
self.updateSelectionState(animated: false)
|
||||||
self.setNeedsLayout()
|
self.setNeedsLayout()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
@objc func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||||
if case .ended = recognizer.state {
|
if case .ended = recognizer.state {
|
||||||
if let (_, wallpaper, _, _) = self.currentState {
|
if let (_, wallpaper, _) = self.currentState {
|
||||||
self.interaction?.openWallpaper(wallpaper)
|
self.interaction?.openWallpaper(wallpaper)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateSelectionState(animated: Bool) {
|
func updateSelectionState(animated: Bool) {
|
||||||
if let (account, wallpaper, index, _) = self.currentState {
|
if let (account, wallpaper, _) = self.currentState {
|
||||||
var editing = false
|
var editing = false
|
||||||
var selectable = false
|
var id: Int64?
|
||||||
if case .file = wallpaper {
|
if case let .file(file) = wallpaper {
|
||||||
selectable = true
|
id = file.id
|
||||||
}
|
}
|
||||||
var selectedIndices = Set<Int>()
|
var selectedIndices = Set<Int64>()
|
||||||
if let interaction = self.interaction {
|
if let interaction = self.interaction {
|
||||||
let (active, indices) = interaction.selectionState
|
let (active, indices) = interaction.selectionState
|
||||||
editing = active
|
editing = active
|
||||||
selectedIndices = indices
|
selectedIndices = indices
|
||||||
}
|
}
|
||||||
if editing && selectable {
|
if let id = id, editing {
|
||||||
let selected = selectedIndices.contains(index)
|
let selected = selectedIndices.contains(id)
|
||||||
|
|
||||||
if let selectionNode = self.selectionNode {
|
if let selectionNode = self.selectionNode {
|
||||||
selectionNode.updateSelected(selected, animated: animated)
|
selectionNode.updateSelected(selected, animated: animated)
|
||||||
@ -97,7 +98,7 @@ final class ThemeGridControllerItemNode: GridItemNode {
|
|||||||
let theme = account.telegramApplicationContext.currentPresentationData.with { $0 }.theme
|
let theme = account.telegramApplicationContext.currentPresentationData.with { $0 }.theme
|
||||||
let selectionNode = GridMessageSelectionNode(theme: theme, toggle: { [weak self] value in
|
let selectionNode = GridMessageSelectionNode(theme: theme, toggle: { [weak self] value in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.interaction?.toggleWallpaperSelection(index, value)
|
strongSelf.interaction?.toggleWallpaperSelection(id, value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -129,7 +130,7 @@ final class ThemeGridControllerItemNode: GridItemNode {
|
|||||||
super.layout()
|
super.layout()
|
||||||
|
|
||||||
let bounds = self.bounds
|
let bounds = self.bounds
|
||||||
if let (account, wallpaper, _, selected) = self.currentState {
|
if let (account, wallpaper, selected) = self.currentState {
|
||||||
self.wallpaperNode.setWallpaper(account: account, wallpaper: wallpaper, selected: selected, size: bounds.size)
|
self.wallpaperNode.setWallpaper(account: account, wallpaper: wallpaper, selected: selected, size: bounds.size)
|
||||||
self.selectionNode?.frame = CGRect(origin: CGPoint(), size: bounds.size)
|
self.selectionNode?.frame = CGRect(origin: CGPoint(), size: bounds.size)
|
||||||
}
|
}
|
||||||
|
@ -36,13 +36,13 @@ private func areWallpapersEqual(_ lhs: TelegramWallpaper, _ rhs: TelegramWallpap
|
|||||||
|
|
||||||
struct ThemeGridControllerNodeState: Equatable {
|
struct ThemeGridControllerNodeState: Equatable {
|
||||||
let editing: Bool
|
let editing: Bool
|
||||||
var selectedIndices: Set<Int>
|
var selectedIndices: Set<Int64>
|
||||||
|
|
||||||
func withUpdatedEditing(_ editing: Bool) -> ThemeGridControllerNodeState {
|
func withUpdatedEditing(_ editing: Bool) -> ThemeGridControllerNodeState {
|
||||||
return ThemeGridControllerNodeState(editing: editing, selectedIndices: editing ? self.selectedIndices : Set())
|
return ThemeGridControllerNodeState(editing: editing, selectedIndices: editing ? self.selectedIndices : Set())
|
||||||
}
|
}
|
||||||
|
|
||||||
func withUpdatedSelectedIndices(_ selectedIndices: Set<Int>) -> ThemeGridControllerNodeState {
|
func withUpdatedSelectedIndices(_ selectedIndices: Set<Int64>) -> ThemeGridControllerNodeState {
|
||||||
return ThemeGridControllerNodeState(editing: self.editing, selectedIndices: selectedIndices)
|
return ThemeGridControllerNodeState(editing: self.editing, selectedIndices: selectedIndices)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,12 +59,12 @@ struct ThemeGridControllerNodeState: Equatable {
|
|||||||
|
|
||||||
final class ThemeGridControllerInteraction {
|
final class ThemeGridControllerInteraction {
|
||||||
let openWallpaper: (TelegramWallpaper) -> Void
|
let openWallpaper: (TelegramWallpaper) -> Void
|
||||||
let toggleWallpaperSelection: (Int, Bool) -> Void
|
let toggleWallpaperSelection: (Int64, Bool) -> Void
|
||||||
let deleteSelectedWallpapers: () -> Void
|
let deleteSelectedWallpapers: () -> Void
|
||||||
let shareSelectedWallpapers: () -> Void
|
let shareSelectedWallpapers: () -> Void
|
||||||
var selectionState: (Bool, Set<Int>) = (false, Set())
|
var selectionState: (Bool, Set<Int64>) = (false, Set())
|
||||||
|
|
||||||
init(openWallpaper: @escaping (TelegramWallpaper) -> Void, toggleWallpaperSelection: @escaping (Int, Bool) -> Void, deleteSelectedWallpapers: @escaping () -> Void, shareSelectedWallpapers: @escaping () -> Void) {
|
init(openWallpaper: @escaping (TelegramWallpaper) -> Void, toggleWallpaperSelection: @escaping (Int64, Bool) -> Void, deleteSelectedWallpapers: @escaping () -> Void, shareSelectedWallpapers: @escaping () -> Void) {
|
||||||
self.openWallpaper = openWallpaper
|
self.openWallpaper = openWallpaper
|
||||||
self.toggleWallpaperSelection = toggleWallpaperSelection
|
self.toggleWallpaperSelection = toggleWallpaperSelection
|
||||||
self.deleteSelectedWallpapers = deleteSelectedWallpapers
|
self.deleteSelectedWallpapers = deleteSelectedWallpapers
|
||||||
@ -143,21 +143,54 @@ private func selectedWallpapers(entries: [ThemeGridControllerEntry]?, state: The
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
var i = 0
|
|
||||||
if let entry = entries.first {
|
|
||||||
i = entry.index
|
|
||||||
}
|
|
||||||
|
|
||||||
var wallpapers: [TelegramWallpaper] = []
|
var wallpapers: [TelegramWallpaper] = []
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
if state.selectedIndices.contains(i) {
|
if case let .file(file) = entry.wallpaper {
|
||||||
wallpapers.append(entry.wallpaper)
|
if state.selectedIndices.contains(file.id) {
|
||||||
|
wallpapers.append(entry.wallpaper)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
i += 1
|
|
||||||
}
|
}
|
||||||
return wallpapers
|
return wallpapers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func isDarkWallpaper(_ wallpaper: TelegramWallpaper) -> Bool {
|
||||||
|
if case let .file(file) = wallpaper {
|
||||||
|
if let data = file.file.immediateThumbnailData {
|
||||||
|
let options = NSMutableDictionary()
|
||||||
|
options.setValue(1 as NSNumber, forKey: kCGImageSourceThumbnailMaxPixelSize as String)
|
||||||
|
options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailFromImageAlways as String)
|
||||||
|
|
||||||
|
let thumbnailData = decodeTinyThumbnail(data: data)
|
||||||
|
if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, nil), let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) {
|
||||||
|
let pixelData = image.dataProvider!.data
|
||||||
|
let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)
|
||||||
|
|
||||||
|
let point = CGPoint()
|
||||||
|
let pixelInfo: Int = ((Int(image.width) * Int(point.y)) + Int(point.x)) * 4
|
||||||
|
let r = CGFloat(data[pixelInfo+1]) / CGFloat(255.0)
|
||||||
|
let g = CGFloat(data[pixelInfo+2]) / CGFloat(255.0)
|
||||||
|
let b = CGFloat(data[pixelInfo+3]) / CGFloat(255.0)
|
||||||
|
|
||||||
|
let color = UIColor(red: r, green: g, blue: b, alpha: 1.0)
|
||||||
|
|
||||||
|
var hue: CGFloat = 0.0
|
||||||
|
var saturation: CGFloat = 0.0
|
||||||
|
var brightness: CGFloat = 0.0
|
||||||
|
var alpha: CGFloat = 0.0
|
||||||
|
if color.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) {
|
||||||
|
if brightness > 0.5 {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
final class ThemeGridControllerNode: ASDisplayNode {
|
final class ThemeGridControllerNode: ASDisplayNode {
|
||||||
private let account: Account
|
private let account: Account
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
@ -269,14 +302,14 @@ final class ThemeGridControllerNode: ASDisplayNode {
|
|||||||
presentPreviewController(.list(wallpapers: wallpapers, central: wallpaper, type: .wallpapers(options)))
|
presentPreviewController(.list(wallpapers: wallpapers, central: wallpaper, type: .wallpapers(options)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, toggleWallpaperSelection: { [weak self] index, value in
|
}, toggleWallpaperSelection: { [weak self] id, value in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.updateState { current in
|
strongSelf.updateState { current in
|
||||||
var updated = current.selectedIndices
|
var updated = current.selectedIndices
|
||||||
if value {
|
if value {
|
||||||
updated.insert(index)
|
updated.insert(id)
|
||||||
} else {
|
} else {
|
||||||
updated.remove(index)
|
updated.remove(id)
|
||||||
}
|
}
|
||||||
return current.withUpdatedSelectedIndices(updated)
|
return current.withUpdatedSelectedIndices(updated)
|
||||||
}
|
}
|
||||||
@ -288,7 +321,8 @@ final class ThemeGridControllerNode: ASDisplayNode {
|
|||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
var updatedWallpapers: [TelegramWallpaper] = []
|
var updatedWallpapers: [TelegramWallpaper] = []
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
if !strongSelf.currentState.selectedIndices.contains(entry.index) {
|
if case let .file(file) = entry.wallpaper, strongSelf.currentState.selectedIndices.contains(file.id) {
|
||||||
|
} else {
|
||||||
updatedWallpapers.append(entry.wallpaper)
|
updatedWallpapers.append(entry.wallpaper)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -313,7 +347,22 @@ final class ThemeGridControllerNode: ASDisplayNode {
|
|||||||
|
|
||||||
entries.insert(ThemeGridControllerEntry(index: 0, wallpaper: presentationData.chatWallpaper, selected: true), at: 0)
|
entries.insert(ThemeGridControllerEntry(index: 0, wallpaper: presentationData.chatWallpaper, selected: true), at: 0)
|
||||||
|
|
||||||
for wallpaper in wallpapers {
|
var sortedWallpapers: [TelegramWallpaper] = []
|
||||||
|
if presentationData.theme.overallDarkAppearance {
|
||||||
|
var darkWallpapers: [TelegramWallpaper] = []
|
||||||
|
for wallpaper in wallpapers {
|
||||||
|
if isDarkWallpaper(wallpaper) {
|
||||||
|
darkWallpapers.append(wallpaper)
|
||||||
|
} else {
|
||||||
|
sortedWallpapers.append(wallpaper)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sortedWallpapers = darkWallpapers + sortedWallpapers
|
||||||
|
} else {
|
||||||
|
sortedWallpapers = wallpapers
|
||||||
|
}
|
||||||
|
|
||||||
|
for wallpaper in sortedWallpapers {
|
||||||
let selected = areWallpapersEqual(presentationData.chatWallpaper, wallpaper)
|
let selected = areWallpapersEqual(presentationData.chatWallpaper, wallpaper)
|
||||||
if !selected {
|
if !selected {
|
||||||
entries.append(ThemeGridControllerEntry(index: index, wallpaper: wallpaper, selected: false))
|
entries.append(ThemeGridControllerEntry(index: index, wallpaper: wallpaper, selected: false))
|
||||||
|
@ -269,6 +269,8 @@ struct ThemeGridSearchContainerTransition {
|
|||||||
let insertions: [GridNodeInsertItem]
|
let insertions: [GridNodeInsertItem]
|
||||||
let updates: [GridNodeUpdateItem]
|
let updates: [GridNodeUpdateItem]
|
||||||
let displayingResults: Bool
|
let displayingResults: Bool
|
||||||
|
let isEmpty: Bool
|
||||||
|
let query: String
|
||||||
}
|
}
|
||||||
|
|
||||||
private func themeGridSearchContainerPreparedRecentTransition(from fromEntries: [ThemeGridRecentEntry], to toEntries: [ThemeGridRecentEntry], account: Account, theme: PresentationTheme, strings: PresentationStrings, interaction: ThemeGridSearchInteraction, header: ListViewItemHeader) -> ThemeGridSearchContainerRecentTransition {
|
private func themeGridSearchContainerPreparedRecentTransition(from fromEntries: [ThemeGridRecentEntry], to toEntries: [ThemeGridRecentEntry], account: Account, theme: PresentationTheme, strings: PresentationStrings, interaction: ThemeGridSearchInteraction, header: ListViewItemHeader) -> ThemeGridSearchContainerRecentTransition {
|
||||||
@ -281,14 +283,14 @@ private func themeGridSearchContainerPreparedRecentTransition(from fromEntries:
|
|||||||
return ThemeGridSearchContainerRecentTransition(deletions: deletions, insertions: insertions, updates: updates)
|
return ThemeGridSearchContainerRecentTransition(deletions: deletions, insertions: insertions, updates: updates)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func themeGridSearchContainerPreparedTransition(from fromEntries: [ThemeGridSearchEntry], to toEntries: [ThemeGridSearchEntry], displayingResults: Bool, account: Account, theme: PresentationTheme, interaction: ThemeGridSearchInteraction) -> ThemeGridSearchContainerTransition {
|
private func themeGridSearchContainerPreparedTransition(from fromEntries: [ThemeGridSearchEntry], to toEntries: [ThemeGridSearchEntry], displayingResults: Bool, account: Account, theme: PresentationTheme, isEmpty: Bool, query: String, interaction: ThemeGridSearchInteraction) -> ThemeGridSearchContainerTransition {
|
||||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||||
|
|
||||||
let deletions = deleteIndices
|
let deletions = deleteIndices
|
||||||
let insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(account: account, theme: theme, interaction: interaction), previousIndex: $0.2) }
|
let insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(account: account, theme: theme, interaction: interaction), previousIndex: $0.2) }
|
||||||
let updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, interaction: interaction)) }
|
let updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, interaction: interaction)) }
|
||||||
|
|
||||||
return ThemeGridSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, displayingResults: displayingResults)
|
return ThemeGridSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, displayingResults: displayingResults, isEmpty: isEmpty, query: query)
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct ThemeGridSearchResult {
|
private struct ThemeGridSearchResult {
|
||||||
@ -308,9 +310,13 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode {
|
|||||||
private let recentListNode: ListView
|
private let recentListNode: ListView
|
||||||
private let gridNode: GridNode
|
private let gridNode: GridNode
|
||||||
private let dimNode: ASDisplayNode
|
private let dimNode: ASDisplayNode
|
||||||
|
|
||||||
|
private let emptyResultsTitleNode: ImmediateTextNode
|
||||||
|
private let emptyResultsTextNode: ImmediateTextNode
|
||||||
|
|
||||||
private var enqueuedRecentTransitions: [(ThemeGridSearchContainerRecentTransition, Bool)] = []
|
private var enqueuedRecentTransitions: [(ThemeGridSearchContainerRecentTransition, Bool)] = []
|
||||||
private var enqueuedTransitions: [(ThemeGridSearchContainerTransition, Bool)] = []
|
private var enqueuedTransitions: [(ThemeGridSearchContainerTransition, Bool)] = []
|
||||||
private var validLayout: ContainerViewLayout?
|
private var validLayout: (ContainerViewLayout, CGFloat)?
|
||||||
|
|
||||||
private var queryValue: WallpaperSearchQuery = .generic("")
|
private var queryValue: WallpaperSearchQuery = .generic("")
|
||||||
private let queryPromise: Promise<WallpaperSearchQuery>
|
private let queryPromise: Promise<WallpaperSearchQuery>
|
||||||
@ -339,6 +345,17 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode {
|
|||||||
self.recentListNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
|
self.recentListNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
|
||||||
self.gridNode = GridNode()
|
self.gridNode = GridNode()
|
||||||
|
|
||||||
|
|
||||||
|
self.emptyResultsTitleNode = ImmediateTextNode()
|
||||||
|
self.emptyResultsTitleNode.attributedText = NSAttributedString(string: self.presentationData.strings.SharedMedia_SearchNoResults, font: Font.semibold(17.0), textColor: self.presentationData.theme.list.freeTextColor)
|
||||||
|
self.emptyResultsTitleNode.textAlignment = .center
|
||||||
|
self.emptyResultsTitleNode.isHidden = true
|
||||||
|
|
||||||
|
self.emptyResultsTextNode = ImmediateTextNode()
|
||||||
|
self.emptyResultsTextNode.maximumNumberOfLines = 0
|
||||||
|
self.emptyResultsTextNode.textAlignment = .center
|
||||||
|
self.emptyResultsTextNode.isHidden = true
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.dimNode.backgroundColor = self.presentationData.theme.chatList.backgroundColor
|
self.dimNode.backgroundColor = self.presentationData.theme.chatList.backgroundColor
|
||||||
@ -349,6 +366,9 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode {
|
|||||||
self.addSubnode(self.recentListNode)
|
self.addSubnode(self.recentListNode)
|
||||||
self.addSubnode(self.gridNode)
|
self.addSubnode(self.gridNode)
|
||||||
|
|
||||||
|
self.addSubnode(self.emptyResultsTitleNode)
|
||||||
|
self.addSubnode(self.emptyResultsTextNode)
|
||||||
|
|
||||||
let searchContext = Promise<ThemeGridSearchContext?>(nil)
|
let searchContext = Promise<ThemeGridSearchContext?>(nil)
|
||||||
let searchContextValue = Atomic<ThemeGridSearchContext?>(value: nil)
|
let searchContextValue = Atomic<ThemeGridSearchContext?>(value: nil)
|
||||||
let updateSearchContext: ((ThemeGridSearchContext?) -> (ThemeGridSearchContext?, Bool)) -> Void = { f in
|
let updateSearchContext: ((ThemeGridSearchContext?) -> (ThemeGridSearchContext?, Bool)) -> Void = { f in
|
||||||
@ -368,21 +388,22 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.gridNode.isHidden = true
|
self.gridNode.isHidden = true
|
||||||
// self.listNode.visibleBottomContentOffsetChanged = { offset in
|
// self.gridNode.visibleItemsUpdated = { [weak self] visibleItems in
|
||||||
// guard case let .known(value) = offset, value < 100.0 else {
|
// if let strongSelf = self, let bottom = visibleItems.bottom {
|
||||||
// return
|
// if let context = searchContextValue.with({ $0 }), bottom.0 >= context.result.items.count - 8 {
|
||||||
// }
|
// updateSearchContext { previous in
|
||||||
// updateSearchContext { previous in
|
// guard let previous = previous else {
|
||||||
// guard let previous = previous else {
|
// return (nil, false)
|
||||||
// return (nil, false)
|
// }
|
||||||
|
// if previous.loadMoreIndex != nil {
|
||||||
|
// return (previous, false)
|
||||||
|
// }
|
||||||
|
// guard let last = previous.result.items.last else {
|
||||||
|
// return (previous, false)
|
||||||
|
// }
|
||||||
|
// return (ThemeGridSearchContext(result: previous.result, loadMoreIndex: MessageIndex(last)), true)
|
||||||
|
// }
|
||||||
// }
|
// }
|
||||||
// if previous.loadMoreIndex != nil {
|
|
||||||
// return (previous, false)
|
|
||||||
// }
|
|
||||||
// guard let last = previous.result.messages.last else {
|
|
||||||
// return (previous, false)
|
|
||||||
// }
|
|
||||||
// return (ChatListSearchMessagesContext(result: previous.result, loadMoreIndex: MessageIndex(last)), true)
|
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
self.recentListNode.isHidden = false
|
self.recentListNode.isHidden = false
|
||||||
@ -495,15 +516,20 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
self.searchDisposable.set((combineLatest(foundItems, self.presentationDataPromise.get())
|
self.searchDisposable.set((combineLatest(foundItems, self.presentationDataPromise.get(), self.queryPromise.get())
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] entriesAndFlags, presentationData in
|
|> deliverOnMainQueue).start(next: { [weak self] entriesAndFlags, presentationData, query in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf._isSearching.set(entriesAndFlags?.1 ?? false)
|
strongSelf._isSearching.set(entriesAndFlags?.1 ?? false)
|
||||||
|
|
||||||
let previousEntries = previousSearchItems.swap(entriesAndFlags?.0)
|
let previousEntries = previousSearchItems.swap(entriesAndFlags?.0)
|
||||||
|
|
||||||
|
var isEmpty = false
|
||||||
|
if let entriesAndFlags = entriesAndFlags {
|
||||||
|
isEmpty = entriesAndFlags.0.isEmpty && !entriesAndFlags.1
|
||||||
|
}
|
||||||
|
|
||||||
let firstTime = previousEntries == nil
|
let firstTime = previousEntries == nil
|
||||||
let transition = themeGridSearchContainerPreparedTransition(from: previousEntries ?? [], to: entriesAndFlags?.0 ?? [], displayingResults: entriesAndFlags?.0 != nil, account: account, theme: presentationData.theme, interaction: interaction)
|
let transition = themeGridSearchContainerPreparedTransition(from: previousEntries ?? [], to: entriesAndFlags?.0 ?? [], displayingResults: entriesAndFlags?.0 != nil, account: account, theme: presentationData.theme, isEmpty: isEmpty, query: query.query, interaction: interaction)
|
||||||
strongSelf.enqueueTransition(transition, firstTime: firstTime)
|
strongSelf.enqueueTransition(transition, firstTime: firstTime)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
@ -634,6 +660,16 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode {
|
|||||||
strongSelf.recentListNode.isHidden = displayingResults
|
strongSelf.recentListNode.isHidden = displayingResults
|
||||||
strongSelf.dimNode.isHidden = displayingResults
|
strongSelf.dimNode.isHidden = displayingResults
|
||||||
strongSelf.backgroundColor = strongSelf.presentationData.theme.chatList.backgroundColor
|
strongSelf.backgroundColor = strongSelf.presentationData.theme.chatList.backgroundColor
|
||||||
|
|
||||||
|
strongSelf.emptyResultsTextNode.attributedText = NSAttributedString(string: strongSelf.presentationData.strings.WebSearch_SearchNoResultsDescription(transition.query).0, font: Font.regular(15.0), textColor: strongSelf.presentationData.theme.list.freeTextColor)
|
||||||
|
|
||||||
|
let emptyResults = displayingResults && transition.isEmpty
|
||||||
|
strongSelf.emptyResultsTitleNode.isHidden = !emptyResults
|
||||||
|
strongSelf.emptyResultsTextNode.isHidden = !emptyResults
|
||||||
|
|
||||||
|
if let (layout, navigationBarHeight) = strongSelf.validLayout {
|
||||||
|
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -643,7 +679,7 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode {
|
|||||||
super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
|
super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
|
||||||
|
|
||||||
let hadValidLayout = self.validLayout != nil
|
let hadValidLayout = self.validLayout != nil
|
||||||
self.validLayout = layout
|
self.validLayout = (layout, navigationBarHeight)
|
||||||
|
|
||||||
let minSpacing: CGFloat = 8.0
|
let minSpacing: CGFloat = 8.0
|
||||||
let referenceImageSize: CGSize
|
let referenceImageSize: CGSize
|
||||||
@ -688,6 +724,18 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode {
|
|||||||
self.gridNode.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)
|
self.gridNode.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)
|
||||||
self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: layout.size, insets: UIEdgeInsets(top: navigationBarHeight + spacing, left: layout.safeInsets.left, bottom: layout.insets(options: [.input]).bottom, right: layout.safeInsets.right), preloadSize: 300.0, type: .fixed(itemSize: imageSize, fillWidth: nil, lineSpacing: spacing, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in })
|
self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: layout.size, insets: UIEdgeInsets(top: navigationBarHeight + spacing, left: layout.safeInsets.left, bottom: layout.insets(options: [.input]).bottom, right: layout.safeInsets.right), preloadSize: 300.0, type: .fixed(itemSize: imageSize, fillWidth: nil, lineSpacing: spacing, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in })
|
||||||
|
|
||||||
|
let padding: CGFloat = 16.0
|
||||||
|
let emptyTitleSize = self.emptyResultsTitleNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0, height: CGFloat.greatestFiniteMagnitude))
|
||||||
|
let emptyTextSize = self.emptyResultsTextNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0, height: CGFloat.greatestFiniteMagnitude))
|
||||||
|
|
||||||
|
let insets = layout.insets(options: [.input])
|
||||||
|
let emptyTextSpacing: CGFloat = 8.0
|
||||||
|
let emptyTotalHeight = emptyTitleSize.height + emptyTextSize.height + emptyTextSpacing
|
||||||
|
let emptyTitleY = navigationBarHeight + floorToScreenPixels((layout.size.height - navigationBarHeight - max(insets.bottom, layout.intrinsicInsets.bottom) - emptyTotalHeight) / 2.0)
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.emptyResultsTitleNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + padding + (layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0 - emptyTitleSize.width) / 2.0, y: emptyTitleY), size: emptyTitleSize))
|
||||||
|
transition.updateFrame(node: self.emptyResultsTextNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + padding + (layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0 - emptyTextSize.width) / 2.0, y: emptyTitleY + emptyTitleSize.height + emptyTextSpacing), size: emptyTextSize))
|
||||||
|
|
||||||
if !hadValidLayout {
|
if !hadValidLayout {
|
||||||
while !self.enqueuedRecentTransitions.isEmpty {
|
while !self.enqueuedRecentTransitions.isEmpty {
|
||||||
self.dequeueRecentTransition()
|
self.dequeueRecentTransition()
|
||||||
|
@ -13,7 +13,7 @@ final class ThemeGridSelectionPanelNode: ASDisplayNode {
|
|||||||
|
|
||||||
private var theme: PresentationTheme
|
private var theme: PresentationTheme
|
||||||
|
|
||||||
var selectedIndices = Set<Int>() {
|
var selectedIndices = Set<Int64>() {
|
||||||
didSet {
|
didSet {
|
||||||
if oldValue != self.selectedIndices {
|
if oldValue != self.selectedIndices {
|
||||||
self.deleteButton.isEnabled = !self.selectedIndices.isEmpty
|
self.deleteButton.isEnabled = !self.selectedIndices.isEmpty
|
||||||
|
@ -137,9 +137,9 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case let .file(file):
|
case let .file(file):
|
||||||
if file.isPattern, let color = file.settings.color {
|
if file.isPattern, let color = file.settings.color, let intensity = file.settings.intensity {
|
||||||
var image: UIImage?
|
var image: UIImage?
|
||||||
let _ = item.account.postbox.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedPatternWallpaperRepresentation(color: color), complete: true, fetch: true, attemptSynchronously: true).start(next: { data in
|
let _ = item.account.postbox.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedPatternWallpaperRepresentation(color: color, intensity: intensity), complete: true, fetch: true, attemptSynchronously: true).start(next: { data in
|
||||||
if data.complete {
|
if data.complete {
|
||||||
image = UIImage(contentsOfFile: data.path)?.precomposed()
|
image = UIImage(contentsOfFile: data.path)?.precomposed()
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,6 @@ public struct TransformImageArguments: Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: TransformImageArguments, rhs: TransformImageArguments) -> Bool {
|
public static func ==(lhs: TransformImageArguments, rhs: TransformImageArguments) -> Bool {
|
||||||
return lhs.imageSize == rhs.imageSize && lhs.boundingSize == rhs.boundingSize && lhs.corners == rhs.corners
|
return lhs.imageSize == rhs.imageSize && lhs.boundingSize == rhs.boundingSize && lhs.corners == rhs.corners && lhs.emptyColor == rhs.emptyColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ enum ParsedInternalPeerUrlParameter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum WallpaperUrlParameter {
|
enum WallpaperUrlParameter {
|
||||||
case slug(String, WallpaperPresentationOptions)
|
case slug(String, WallpaperPresentationOptions, UIColor?, Int32?)
|
||||||
case color(UIColor)
|
case color(UIColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,28 +182,30 @@ func parseInternalUrl(query: String) -> ParsedInternalUrl? {
|
|||||||
} else {
|
} else {
|
||||||
var options: WallpaperPresentationOptions = []
|
var options: WallpaperPresentationOptions = []
|
||||||
var intensity: Int32?
|
var intensity: Int32?
|
||||||
var color: Int32?
|
var color: UIColor?
|
||||||
if let queryItems = components.queryItems {
|
if let queryItems = components.queryItems {
|
||||||
for queryItem in queryItems {
|
for queryItem in queryItems {
|
||||||
if let value = queryItem.value, queryItem.name == "mode" {
|
if let value = queryItem.value{
|
||||||
for option in value.components(separatedBy: "+") {
|
if queryItem.name == "mode" {
|
||||||
switch option.lowercased() {
|
for option in value.components(separatedBy: "+") {
|
||||||
case "motion":
|
switch option.lowercased() {
|
||||||
options.insert(.motion)
|
case "motion":
|
||||||
case "blur":
|
options.insert(.motion)
|
||||||
options.insert(.blur)
|
case "blur":
|
||||||
case "intensity":
|
options.insert(.blur)
|
||||||
intensity = Int32(value)
|
default:
|
||||||
case "color":
|
break
|
||||||
color = Int32(value)
|
}
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
} else if queryItem.name == "bg_color" {
|
||||||
|
color = UIColor(hexString: value)
|
||||||
|
} else if queryItem.name == "intensity" {
|
||||||
|
intensity = Int32(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
parameter = .slug(component, options)
|
parameter = .slug(component, options, color, intensity)
|
||||||
}
|
}
|
||||||
return .wallpaper(parameter)
|
return .wallpaper(parameter)
|
||||||
} else if let value = Int(pathComponents[1]) {
|
} else if let value = Int(pathComponents[1]) {
|
||||||
|
@ -10,7 +10,19 @@ private let shadowImage: UIImage = {
|
|||||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||||
context.setBlendMode(.normal)
|
context.setBlendMode(.normal)
|
||||||
context.setShadow(offset: CGSize(width: 0.0, height: 1.5), blur: 4.5, color: UIColor(rgb: 0x000000, alpha: 0.5).cgColor)
|
context.setShadow(offset: CGSize(width: 0.0, height: 1.5), blur: 4.5, color: UIColor(rgb: 0x000000, alpha: 0.5).cgColor)
|
||||||
context.setFillColor(UIColor.black.cgColor)
|
context.setFillColor(UIColor(rgb: 0x000000, alpha: 0.5).cgColor)
|
||||||
|
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: 3.0 + UIScreenPixel, dy: 3.0 + UIScreenPixel))
|
||||||
|
})!
|
||||||
|
}()
|
||||||
|
|
||||||
|
private let smallShadowImage: UIImage = {
|
||||||
|
return generateImage(CGSize(width: 24.0, height: 24.0), opaque: false, scale: nil, rotatedContext: { size, context in
|
||||||
|
context.setBlendMode(.clear)
|
||||||
|
context.setFillColor(UIColor.clear.cgColor)
|
||||||
|
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||||
|
context.setBlendMode(.normal)
|
||||||
|
context.setShadow(offset: CGSize(width: 0.0, height: 1.5), blur: 4.5, color: UIColor(rgb: 0x000000, alpha: 0.65).cgColor)
|
||||||
|
context.setFillColor(UIColor(rgb: 0x000000, alpha: 0.5).cgColor)
|
||||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: 3.0 + UIScreenPixel, dy: 3.0 + UIScreenPixel))
|
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: 3.0 + UIScreenPixel, dy: 3.0 + UIScreenPixel))
|
||||||
})!
|
})!
|
||||||
}()
|
}()
|
||||||
@ -56,10 +68,25 @@ private final class HSVParameter: NSObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class IntensitySliderParameter: NSObject {
|
||||||
|
let bordered: Bool
|
||||||
|
let min: HSVParameter
|
||||||
|
let max: HSVParameter
|
||||||
|
|
||||||
|
init(bordered: Bool, min: HSVParameter, max: HSVParameter) {
|
||||||
|
self.bordered = bordered
|
||||||
|
self.min = min
|
||||||
|
self.max = max
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final class WallpaperColorKnobNode: ASDisplayNode {
|
private final class WallpaperColorKnobNode: ASDisplayNode {
|
||||||
var hsv: (CGFloat, CGFloat, CGFloat) = (0.0, 0.0, 1.0) {
|
var hsv: (CGFloat, CGFloat, CGFloat) = (0.0, 0.0, 1.0) {
|
||||||
didSet {
|
didSet {
|
||||||
self.setNeedsDisplay()
|
if self.hsv != oldValue {
|
||||||
|
self.setNeedsDisplay()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +114,8 @@ private final class WallpaperColorKnobNode: ASDisplayNode {
|
|||||||
context.fill(bounds)
|
context.fill(bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
context.draw(shadowImage.cgImage!, in: bounds)
|
let image = bounds.width > 30.0 ? shadowImage : smallShadowImage
|
||||||
|
context.draw(image.cgImage!, in: bounds)
|
||||||
|
|
||||||
context.setBlendMode(.normal)
|
context.setBlendMode(.normal)
|
||||||
context.setFillColor(UIColor.white.cgColor)
|
context.setFillColor(UIColor.white.cgColor)
|
||||||
@ -95,7 +123,9 @@ private final class WallpaperColorKnobNode: ASDisplayNode {
|
|||||||
|
|
||||||
let color = UIColor(hue: parameters.hue, saturation: parameters.saturation, brightness: parameters.value, alpha: 1.0)
|
let color = UIColor(hue: parameters.hue, saturation: parameters.saturation, brightness: parameters.value, alpha: 1.0)
|
||||||
context.setFillColor(color.cgColor)
|
context.setFillColor(color.cgColor)
|
||||||
context.fillEllipse(in: bounds.insetBy(dx: 5.0 - UIScreenPixel, dy: 5.0 - UIScreenPixel))
|
|
||||||
|
let borderWidth: CGFloat = bounds.width > 30.0 ? 5.0 : 5.0
|
||||||
|
context.fillEllipse(in: bounds.insetBy(dx: borderWidth - UIScreenPixel, dy: borderWidth - UIScreenPixel))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,6 +214,163 @@ private final class WallpaperColorBrightnessNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class WallpaperIntensitySliderNode: ASDisplayNode {
|
||||||
|
private let bordered: Bool
|
||||||
|
var extrema: ((CGFloat, CGFloat, CGFloat), (CGFloat, CGFloat, CGFloat)) = ((0.0, 1.0, 0.0), (0.0, 1.0, 1.0)) {
|
||||||
|
didSet {
|
||||||
|
self.setNeedsDisplay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(bordered: Bool) {
|
||||||
|
self.bordered = bordered
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.isOpaque = bordered
|
||||||
|
self.displaysAsynchronously = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
|
||||||
|
return IntensitySliderParameter(bordered: self.bordered, min: HSVParameter(hue: self.extrema.0.0, saturation: self.extrema.0.1, value: self.extrema.0.2), max: HSVParameter(hue: self.extrema.1.0, saturation: self.extrema.1.1, value: self.extrema.1.2))
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
|
||||||
|
guard let parameters = parameters as? IntensitySliderParameter else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let context = UIGraphicsGetCurrentContext()!
|
||||||
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
|
|
||||||
|
if parameters.bordered {
|
||||||
|
context.setFillColor(UIColor(white: parameters.min.value, alpha: 1.0).cgColor)
|
||||||
|
context.fill(bounds)
|
||||||
|
|
||||||
|
let path = UIBezierPath(roundedRect: bounds, cornerRadius: bounds.height / 2.0)
|
||||||
|
context.addPath(path.cgPath)
|
||||||
|
context.setFillColor(UIColor.white.cgColor)
|
||||||
|
context.fillPath()
|
||||||
|
} else if !isRasterizing {
|
||||||
|
context.setBlendMode(.copy)
|
||||||
|
context.setFillColor(UIColor.clear.cgColor)
|
||||||
|
context.fill(bounds)
|
||||||
|
context.setBlendMode(.normal)
|
||||||
|
}
|
||||||
|
|
||||||
|
let innerPath = UIBezierPath(roundedRect: bounds.insetBy(dx: 1.0, dy: 1.0), cornerRadius: bounds.height / 2.0)
|
||||||
|
context.addPath(innerPath.cgPath)
|
||||||
|
context.clip()
|
||||||
|
|
||||||
|
let minColor = UIColor(hue: parameters.min.hue, saturation: parameters.min.saturation, brightness: parameters.min.value, alpha: 1.0)
|
||||||
|
let maxColor = UIColor(hue: parameters.max.hue, saturation: parameters.max.saturation, brightness: parameters.max.value, alpha: 1.0)
|
||||||
|
let colors = [minColor.cgColor, maxColor.cgColor]
|
||||||
|
var locations: [CGFloat] = [0.0, 1.0]
|
||||||
|
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||||
|
context.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: bounds.width, y: 0.0), options: CGGradientDrawingOptions())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class WallpaperIntensityPickerNode: ASDisplayNode {
|
||||||
|
private let labelNode: ASTextNode
|
||||||
|
private let sliderNode: WallpaperIntensitySliderNode
|
||||||
|
private let knobNode: WallpaperColorKnobNode
|
||||||
|
|
||||||
|
var valueChanged: ((CGFloat) -> Void)?
|
||||||
|
var valueChangeEnded: ((CGFloat) -> Void)?
|
||||||
|
|
||||||
|
var value: CGFloat = 0.0 {
|
||||||
|
didSet {
|
||||||
|
self.setNeedsLayout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var intensity: Int32 {
|
||||||
|
return Int32(self.value * 100.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
init(theme: PresentationTheme, title: String, bordered: Bool) {
|
||||||
|
self.labelNode = ASTextNode()
|
||||||
|
self.labelNode.attributedText = NSAttributedString(string: title.uppercased(), font: Font.regular(14.0), textColor: bordered ? .black : theme.rootController.navigationBar.secondaryTextColor)
|
||||||
|
self.sliderNode = WallpaperIntensitySliderNode(bordered: bordered)
|
||||||
|
self.sliderNode.hitTestSlop = UIEdgeInsetsMake(-16.0, -16.0, -16.0, -16.0)
|
||||||
|
self.knobNode = WallpaperColorKnobNode()
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.addSubnode(self.labelNode)
|
||||||
|
self.addSubnode(self.sliderNode)
|
||||||
|
self.addSubnode(self.knobNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateExtrema(min: (CGFloat, CGFloat, CGFloat), max: (CGFloat, CGFloat, CGFloat)) {
|
||||||
|
self.sliderNode.extrema = (min, max)
|
||||||
|
self.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func update() {
|
||||||
|
let extrema = self.sliderNode.extrema
|
||||||
|
let hsv = (extrema.0.0 + (extrema.1.0 - extrema.0.0) * self.value, extrema.0.1 + (extrema.1.1 - extrema.0.1) * self.value, extrema.0.2 + (extrema.1.2 - extrema.0.2) * self.value)
|
||||||
|
self.knobNode.hsv = hsv
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didLoad() {
|
||||||
|
super.didLoad()
|
||||||
|
|
||||||
|
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(WallpaperIntensityPickerNode.pan))
|
||||||
|
self.sliderNode.view.addGestureRecognizer(panRecognizer)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func pan(_ recognizer: UIPanGestureRecognizer) {
|
||||||
|
let size = self.bounds.size
|
||||||
|
|
||||||
|
let previousValue = self.value
|
||||||
|
|
||||||
|
let transition = recognizer.translation(in: recognizer.view)
|
||||||
|
let width: CGFloat = size.width - 5.0 * 2.0
|
||||||
|
let newValue = max(0.0, min(1.0, self.value + transition.x / width))
|
||||||
|
self.value = newValue
|
||||||
|
|
||||||
|
var ended = false
|
||||||
|
switch recognizer.state {
|
||||||
|
case .changed:
|
||||||
|
self.updateKnobLayout(size: size)
|
||||||
|
recognizer.setTranslation(CGPoint(), in: recognizer.view)
|
||||||
|
case .ended:
|
||||||
|
self.updateKnobLayout(size: size)
|
||||||
|
ended = true
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.value != previousValue || ended {
|
||||||
|
if ended {
|
||||||
|
self.valueChangeEnded?(self.value)
|
||||||
|
} else {
|
||||||
|
self.valueChanged?(self.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateKnobLayout(size: CGSize) {
|
||||||
|
let knobSize = CGSize(width: 24.0, height: 24.0)
|
||||||
|
|
||||||
|
let inset: CGFloat = 5.0
|
||||||
|
let knobFrame = CGRect(x: inset - knobSize.width / 2.0 + (size.width - inset * 2.0) * self.value, y: 17.0, width: knobSize.width, height: knobSize.height)
|
||||||
|
self.knobNode.frame = knobFrame
|
||||||
|
self.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func layout() {
|
||||||
|
super.layout()
|
||||||
|
|
||||||
|
let labelSize = self.labelNode.measure(self.bounds.size)
|
||||||
|
self.labelNode.frame = CGRect(origin: CGPoint(), size: labelSize)
|
||||||
|
|
||||||
|
self.sliderNode.frame = CGRect(x: 0.0, y: 27.0, width: self.bounds.width, height: 4.0)
|
||||||
|
self.updateKnobLayout(size: self.bounds.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final class WallpaperColorPickerNode: ASDisplayNode {
|
final class WallpaperColorPickerNode: ASDisplayNode {
|
||||||
private let brightnessNode: WallpaperColorBrightnessNode
|
private let brightnessNode: WallpaperColorBrightnessNode
|
||||||
private let brightnessKnobNode: ASImageNode
|
private let brightnessKnobNode: ASImageNode
|
||||||
|
@ -15,7 +15,7 @@ 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, WallpaperPresentationOptions?)
|
case wallpaper(TelegramWallpaper, WallpaperPresentationOptions?)
|
||||||
case slug(String, TelegramMediaFile?, WallpaperPresentationOptions?)
|
case slug(String, TelegramMediaFile?, WallpaperPresentationOptions?, UIColor?, Int32?)
|
||||||
case asset(PHAsset)
|
case asset(PHAsset)
|
||||||
case contextResult(ChatContextResult)
|
case contextResult(ChatContextResult)
|
||||||
case customColor(Int32?)
|
case customColor(Int32?)
|
||||||
@ -129,9 +129,19 @@ class WallpaperGalleryController: ViewController {
|
|||||||
if case let .wallpapers(wallpaperOptions) = type, let options = wallpaperOptions {
|
if case let .wallpapers(wallpaperOptions) = type, let options = wallpaperOptions {
|
||||||
self.initialOptions = options
|
self.initialOptions = options
|
||||||
}
|
}
|
||||||
case let .slug(slug, file, options):
|
case let .slug(slug, file, options, color, intensity):
|
||||||
if let file = file {
|
if let file = file {
|
||||||
self.entries = [.wallpaper(.file(id: 0, accessHash: 0, isCreator: false, isDefault: false, isPattern: false, slug: slug, file: file, settings: WallpaperSettings()))]
|
let isPattern = file.mimeType == "image/png"
|
||||||
|
var colorValue: Int32?
|
||||||
|
var intensityValue: Int32?
|
||||||
|
if let color = color {
|
||||||
|
colorValue = Int32(bitPattern: color.rgb)
|
||||||
|
intensityValue = intensity
|
||||||
|
} else {
|
||||||
|
colorValue = 0xd6e2ee
|
||||||
|
intensityValue = 50
|
||||||
|
}
|
||||||
|
self.entries = [.wallpaper(.file(id: 0, accessHash: 0, isCreator: false, isDefault: false, isPattern: isPattern, slug: slug, file: file, settings: WallpaperSettings(blur: false, motion: false, color: colorValue, intensity: intensityValue)))]
|
||||||
self.centralEntryIndex = 0
|
self.centralEntryIndex = 0
|
||||||
self.initialOptions = options
|
self.initialOptions = options
|
||||||
}
|
}
|
||||||
@ -193,7 +203,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.setRightBarButton(barButton, animated: true)
|
strongSelf.navigationItem.rightBarButtonItem = barButton
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -225,11 +235,6 @@ class WallpaperGalleryController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func updateTransaction(entries: [WallpaperGalleryEntry], arguments: WallpaperGalleryItemArguments) -> GalleryPagerTransaction {
|
private func updateTransaction(entries: [WallpaperGalleryEntry], arguments: WallpaperGalleryItemArguments) -> GalleryPagerTransaction {
|
||||||
var colors = false
|
|
||||||
if case let .list(_, _, type) = self.source, case .colors = type {
|
|
||||||
colors = true
|
|
||||||
}
|
|
||||||
|
|
||||||
var i: Int = 0
|
var i: Int = 0
|
||||||
var updateItems: [GalleryPagerUpdateItem] = []
|
var updateItems: [GalleryPagerUpdateItem] = []
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
@ -315,39 +320,6 @@ class WallpaperGalleryController: ViewController {
|
|||||||
self.colorPanelNode = colorPanelNode
|
self.colorPanelNode = colorPanelNode
|
||||||
overlayNode.addSubnode(colorPanelNode)
|
overlayNode.addSubnode(colorPanelNode)
|
||||||
|
|
||||||
let patternPanelNode = WallpaperPatternPanelNode(account: self.account, theme: presentationData.theme)
|
|
||||||
patternPanelNode.patternChanged = { [weak self] pattern in
|
|
||||||
if let strongSelf = self, strongSelf.validLayout != nil {
|
|
||||||
var updatedEntries: [WallpaperGalleryEntry] = []
|
|
||||||
for entry in strongSelf.entries {
|
|
||||||
var entryColor: Int32?
|
|
||||||
if case let .wallpaper(wallpaper) = entry {
|
|
||||||
if case let .color(color) = wallpaper {
|
|
||||||
entryColor = color
|
|
||||||
} else if case let .file(file) = wallpaper {
|
|
||||||
entryColor = file.settings.color
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let entryColor = entryColor {
|
|
||||||
if case let .file(file) = pattern {
|
|
||||||
let newSettings = WallpaperSettings(blur: file.settings.blur, motion: file.settings.motion, color: entryColor, intensity: 100)
|
|
||||||
let newWallpaper = TelegramWallpaper.file(id: file.id, accessHash: file.accessHash, isCreator: file.isCreator, isDefault: file.isDefault, isPattern: file.isPattern, slug: file.slug, file: file.file, settings: newSettings)
|
|
||||||
updatedEntries.append(.wallpaper(newWallpaper))
|
|
||||||
} else {
|
|
||||||
let newWallpaper = TelegramWallpaper.color(entryColor)
|
|
||||||
updatedEntries.append(.wallpaper(newWallpaper))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
strongSelf.entries = updatedEntries
|
|
||||||
strongSelf.galleryNode.pager.transaction(strongSelf.updateTransaction(entries: updatedEntries, arguments: WallpaperGalleryItemArguments(colorPreview: false, isColorsList: true, patternEnabled: strongSelf.patternPanelEnabled)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.patternPanelNode = patternPanelNode
|
|
||||||
overlayNode.addSubnode(patternPanelNode)
|
|
||||||
|
|
||||||
let toolbarNode = WallpaperGalleryToolbarNode(theme: presentationData.theme, strings: presentationData.strings)
|
let toolbarNode = WallpaperGalleryToolbarNode(theme: presentationData.theme, strings: presentationData.strings)
|
||||||
self.toolbarNode = toolbarNode
|
self.toolbarNode = toolbarNode
|
||||||
overlayNode.addSubnode(toolbarNode)
|
overlayNode.addSubnode(toolbarNode)
|
||||||
@ -395,8 +367,8 @@ class WallpaperGalleryController: ViewController {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if case let .file(file) = wallpaper, file.isPattern, let color = file.settings.color {
|
if case let .file(file) = wallpaper, file.isPattern, let color = file.settings.color, let intensity = file.settings.intensity {
|
||||||
let _ = strongSelf.account.postbox.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedPatternWallpaperRepresentation(color: color), complete: true, fetch: true).start(completed: {
|
let _ = strongSelf.account.postbox.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedPatternWallpaperRepresentation(color: color, intensity: intensity), complete: true, fetch: true).start(completed: {
|
||||||
completion()
|
completion()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -447,6 +419,11 @@ class WallpaperGalleryController: ViewController {
|
|||||||
strongSelf.patternPanelEnabled = enabled
|
strongSelf.patternPanelEnabled = enabled
|
||||||
strongSelf.galleryNode.scrollView.isScrollEnabled = !enabled
|
strongSelf.galleryNode.scrollView.isScrollEnabled = !enabled
|
||||||
strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.3, curve: .spring))
|
strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.3, curve: .spring))
|
||||||
|
if enabled {
|
||||||
|
strongSelf.patternPanelNode?.didAppear()
|
||||||
|
} else {
|
||||||
|
strongSelf.updateEntries(pattern: .color(0), preview: false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
node.requestColorPanel = { [weak self] color in
|
node.requestColorPanel = { [weak self] color in
|
||||||
@ -458,12 +435,51 @@ class WallpaperGalleryController: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if let (layout, _) = self.validLayout {
|
if let entry = node.entry, case let .wallpaper(wallpaper) = entry {
|
||||||
// self.containerLayoutUpdated(layout, transition: animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate)
|
var entryColor: UIColor = .white
|
||||||
// }
|
switch wallpaper {
|
||||||
|
case let .color(color):
|
||||||
|
entryColor = UIColor(rgb: UInt32(bitPattern: color))
|
||||||
|
case let .file(file):
|
||||||
|
if let color = file.settings.color {
|
||||||
|
entryColor = UIColor(rgb: UInt32(bitPattern: color))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
self.patternPanelNode?.color = entryColor
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func updateEntries(pattern: TelegramWallpaper, intensity: Int32? = nil, preview: Bool = false) {
|
||||||
|
var updatedEntries: [WallpaperGalleryEntry] = []
|
||||||
|
for entry in self.entries {
|
||||||
|
var entryColor: Int32?
|
||||||
|
if case let .wallpaper(wallpaper) = entry {
|
||||||
|
if case let .color(color) = wallpaper {
|
||||||
|
entryColor = color
|
||||||
|
} else if case let .file(file) = wallpaper {
|
||||||
|
entryColor = file.settings.color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let entryColor = entryColor {
|
||||||
|
if case let .file(file) = pattern {
|
||||||
|
let newSettings = WallpaperSettings(blur: file.settings.blur, motion: file.settings.motion, color: entryColor, intensity: intensity)
|
||||||
|
let newWallpaper = TelegramWallpaper.file(id: file.id, accessHash: file.accessHash, isCreator: file.isCreator, isDefault: file.isDefault, isPattern: file.isPattern, slug: file.slug, file: file.file, settings: newSettings)
|
||||||
|
updatedEntries.append(.wallpaper(newWallpaper))
|
||||||
|
} else {
|
||||||
|
let newWallpaper = TelegramWallpaper.color(entryColor)
|
||||||
|
updatedEntries.append(.wallpaper(newWallpaper))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.entries = updatedEntries
|
||||||
|
self.galleryNode.pager.transaction(self.updateTransaction(entries: updatedEntries, arguments: WallpaperGalleryItemArguments(colorPreview: preview, isColorsList: true, patternEnabled: self.patternPanelEnabled)))
|
||||||
|
}
|
||||||
|
|
||||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
let hadLayout = self.validLayout != nil
|
let hadLayout = self.validLayout != nil
|
||||||
|
|
||||||
@ -570,18 +586,31 @@ class WallpaperGalleryController: ViewController {
|
|||||||
colorPanelNode.updateLayout(size: colorPanelFrame.size, keyboardHeight: layout.inputHeight ?? 0.0, transition: transition)
|
colorPanelNode.updateLayout(size: colorPanelFrame.size, keyboardHeight: layout.inputHeight ?? 0.0, transition: transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let currentPatternPanelNode: WallpaperPatternPanelNode
|
||||||
if let patternPanelNode = self.patternPanelNode {
|
if let patternPanelNode = self.patternPanelNode {
|
||||||
let panelHeight: CGFloat = 114.0
|
currentPatternPanelNode = patternPanelNode
|
||||||
var patternPanelFrame = CGRect(x: 0.0, y: layout.size.height, width: layout.size.width, height: panelHeight)
|
} else {
|
||||||
if self.patternPanelEnabled {
|
let patternPanelNode = WallpaperPatternPanelNode(account: self.account, theme: presentationData.theme, strings: presentationData.strings)
|
||||||
patternPanelFrame.origin = CGPoint(x: 0.0, y: layout.size.height - bottomInset - panelHeight)
|
patternPanelNode.patternChanged = { [weak self] pattern, intensity, preview in
|
||||||
bottomInset += panelHeight
|
if let strongSelf = self, strongSelf.validLayout != nil {
|
||||||
|
strongSelf.updateEntries(pattern: pattern, intensity: intensity, preview: preview)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
self.patternPanelNode = patternPanelNode
|
||||||
transition.updateFrame(node: patternPanelNode, frame: patternPanelFrame)
|
currentPatternPanelNode = patternPanelNode
|
||||||
patternPanelNode.updateLayout(size: patternPanelFrame.size, transition: transition)
|
self.overlayNode?.insertSubnode(patternPanelNode, belowSubnode: self.toolbarNode!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let panelHeight: CGFloat = 190.0
|
||||||
|
var patternPanelFrame = CGRect(x: 0.0, y: layout.size.height, width: layout.size.width, height: panelHeight)
|
||||||
|
if self.patternPanelEnabled {
|
||||||
|
patternPanelFrame.origin = CGPoint(x: 0.0, y: layout.size.height - bottomInset - panelHeight)
|
||||||
|
bottomInset += panelHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.updateFrame(node: currentPatternPanelNode, frame: patternPanelFrame)
|
||||||
|
currentPatternPanelNode.updateLayout(size: patternPanelFrame.size, transition: transition)
|
||||||
|
|
||||||
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 colorPanelEnabled {
|
if colorPanelEnabled {
|
||||||
@ -615,24 +644,37 @@ class WallpaperGalleryController: ViewController {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var options = ""
|
|
||||||
if (itemNode.options.contains(.blur)) {
|
|
||||||
options = "?mode=blur"
|
|
||||||
}
|
|
||||||
if (itemNode.options.contains(.motion)) {
|
|
||||||
if options.isEmpty {
|
|
||||||
options = "?mode=motion"
|
|
||||||
} else {
|
|
||||||
options += "+motion"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var controller: ShareController?
|
var controller: ShareController?
|
||||||
switch wallpaper {
|
switch wallpaper {
|
||||||
case let .file(_, _, _, _, _, slug, _, _):
|
case let .file(_, _, _, _, isPattern, slug, _, settings):
|
||||||
controller = ShareController(account: account, subject: .url("https://t.me/bg/\(slug)\(options)"))
|
var options: [String] = []
|
||||||
|
if (itemNode.options.contains(.blur) && !isPattern) {
|
||||||
|
if (itemNode.options.contains(.motion)) {
|
||||||
|
options.append("mode=blur+motion")
|
||||||
|
} else {
|
||||||
|
options.append("mode=blur")
|
||||||
|
}
|
||||||
|
} else if (itemNode.options.contains(.motion)) {
|
||||||
|
options.append("mode=motion")
|
||||||
|
}
|
||||||
|
|
||||||
|
if isPattern {
|
||||||
|
if let color = settings.color {
|
||||||
|
options.append("bg_color=\(UIColor(rgb: UInt32(bitPattern: color)).hexString)")
|
||||||
|
}
|
||||||
|
if let intensity = settings.intensity {
|
||||||
|
options.append("intensity=\(intensity)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var optionsString = ""
|
||||||
|
if !options.isEmpty {
|
||||||
|
optionsString = "?\(options.joined(separator: "&"))"
|
||||||
|
}
|
||||||
|
|
||||||
|
controller = ShareController(account: account, subject: .url("https://t.me/bg/\(slug)\(optionsString)"))
|
||||||
case let .color(color):
|
case let .color(color):
|
||||||
controller = ShareController(account: account, subject: .url("https://t.me/bg/\(String(UInt32(bitPattern: color), radix: 16, uppercase: false).rightJustified(width: 6, pad: "0"))"))
|
controller = ShareController(account: account, subject: .url("https://t.me/bg/\(UIColor(rgb: UInt32(bitPattern: color)).hexString)"))
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -80,6 +80,7 @@ private let motionAmount: CGFloat = 32.0
|
|||||||
final class WallpaperGalleryItemNode: GalleryItemNode {
|
final class WallpaperGalleryItemNode: GalleryItemNode {
|
||||||
private let account: Account
|
private let account: Account
|
||||||
var entry: WallpaperGalleryEntry?
|
var entry: WallpaperGalleryEntry?
|
||||||
|
private var colorPreview: Bool = false
|
||||||
private var contentSize: CGSize?
|
private var contentSize: CGSize?
|
||||||
private var arguments = WallpaperGalleryItemArguments()
|
private var arguments = WallpaperGalleryItemArguments()
|
||||||
|
|
||||||
@ -198,6 +199,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.entry != entry || self.arguments.colorPreview != previousArguments.colorPreview {
|
if self.entry != entry || self.arguments.colorPreview != previousArguments.colorPreview {
|
||||||
|
let previousEntry = self.entry
|
||||||
self.entry = entry
|
self.entry = entry
|
||||||
|
|
||||||
self.patternButtonNode.isSelected = self.arguments.patternEnabled
|
self.patternButtonNode.isSelected = self.arguments.patternEnabled
|
||||||
@ -210,6 +212,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
|||||||
let subtitleSignal: Signal<String?, NoError>
|
let subtitleSignal: Signal<String?, NoError>
|
||||||
var actionSignal: Signal<UIBarButtonItem?, NoError> = .single(nil)
|
var actionSignal: Signal<UIBarButtonItem?, NoError> = .single(nil)
|
||||||
var colorSignal: Signal<UIColor, NoError> = serviceColor(from: imagePromise.get())
|
var colorSignal: Signal<UIColor, NoError> = serviceColor(from: imagePromise.get())
|
||||||
|
var color: UIColor?
|
||||||
|
|
||||||
let displaySize: CGSize
|
let displaySize: CGSize
|
||||||
let contentSize: CGSize
|
let contentSize: CGSize
|
||||||
@ -254,16 +257,31 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
|||||||
convertedRepresentations.append(ImageRepresentationWithReference(representation: .init(dimensions: dimensions, resource: file.file.resource), reference: .wallpaper(resource: file.file.resource)))
|
convertedRepresentations.append(ImageRepresentationWithReference(representation: .init(dimensions: dimensions, resource: file.file.resource), reference: .wallpaper(resource: file.file.resource)))
|
||||||
|
|
||||||
if file.isPattern {
|
if file.isPattern {
|
||||||
var patternColor = UIColor(rgb: 0xd6e2ee)
|
var patternColor = UIColor(rgb: 0xd6e2ee, alpha: 0.5)
|
||||||
|
var patternIntensity: CGFloat = 0.5
|
||||||
if let color = file.settings.color {
|
if let color = file.settings.color {
|
||||||
patternColor = UIColor(rgb: UInt32(bitPattern: color))
|
if let intensity = file.settings.intensity {
|
||||||
|
patternIntensity = CGFloat(intensity) / 100.0
|
||||||
|
}
|
||||||
|
patternColor = UIColor(rgb: UInt32(bitPattern: color), alpha: patternIntensity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.colorButtonNode.color = patternColor
|
||||||
self.backgroundColor = patternColor
|
self.backgroundColor = patternColor
|
||||||
signal = patternWallpaperImage(account: account, representations: convertedRepresentations, color: patternColor, mode: self.arguments.colorPreview ? .fastScreen : .screen, autoFetchFullSize: true)
|
|
||||||
|
if let previousEntry = previousEntry, case let .wallpaper(wallpaper) = previousEntry, case let .file(previousFile) = wallpaper, file.id == previousFile.id && file.settings.color != previousFile.settings.color && self.colorPreview == self.arguments.colorPreview {
|
||||||
|
|
||||||
|
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets(), emptyColor: patternColor))()
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
color = patternColor
|
||||||
|
}
|
||||||
|
|
||||||
|
self.colorPreview = self.arguments.colorPreview
|
||||||
|
|
||||||
|
signal = patternWallpaperImage(account: account, representations: convertedRepresentations, mode: self.arguments.colorPreview ? .fastScreen : .screen, autoFetchFullSize: true)
|
||||||
colorSignal = chatServiceBackgroundColor(wallpaper: wallpaper, postbox: self.account.postbox)
|
colorSignal = chatServiceBackgroundColor(wallpaper: wallpaper, postbox: self.account.postbox)
|
||||||
|
|
||||||
self.colorButtonNode.color = patternColor
|
|
||||||
isBlurrable = false
|
isBlurrable = false
|
||||||
} else {
|
} else {
|
||||||
signal = wallpaperImage(account: account, fileReference: .standalone(media: file.file), representations: convertedRepresentations, alwaysShowThumbnailFirst: true, autoFetchFullSize: false)
|
signal = wallpaperImage(account: account, fileReference: .standalone(media: file.file), representations: convertedRepresentations, alwaysShowThumbnailFirst: true, autoFetchFullSize: false)
|
||||||
@ -373,7 +391,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.imageNode.setSignal(signal, dispatchOnDisplayLink: false)
|
self.imageNode.setSignal(signal, dispatchOnDisplayLink: false)
|
||||||
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets()))()
|
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets(), emptyColor: color))()
|
||||||
self.imageNode.imageUpdated = { [weak self] image in
|
self.imageNode.imageUpdated = { [weak self] image in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
var image = isBlurrable ? image : nil
|
var image = isBlurrable ? image : nil
|
||||||
@ -430,7 +448,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
if let layout = self.validLayout {
|
if let layout = self.validLayout {
|
||||||
self.updateButtonsLayout(layout: layout, offset: CGPoint(), transition: animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate)
|
//self.updateButtonsLayout(layout: layout, offset: CGPoint(), transition: animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate)
|
||||||
}
|
}
|
||||||
} else if self.arguments.patternEnabled != previousArguments.patternEnabled {
|
} else if self.arguments.patternEnabled != previousArguments.patternEnabled {
|
||||||
self.patternButtonNode.isSelected = self.arguments.patternEnabled
|
self.patternButtonNode.isSelected = self.arguments.patternEnabled
|
||||||
@ -443,6 +461,9 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
|||||||
|
|
||||||
override func screenFrameUpdated(_ frame: CGRect) {
|
override func screenFrameUpdated(_ frame: CGRect) {
|
||||||
let offset = -frame.minX
|
let offset = -frame.minX
|
||||||
|
guard self.validOffset != offset else {
|
||||||
|
return
|
||||||
|
}
|
||||||
self.validOffset = offset
|
self.validOffset = offset
|
||||||
if let layout = self.validLayout {
|
if let layout = self.validLayout {
|
||||||
self.updateWrapperLayout(layout: layout, offset: offset, transition: .immediate)
|
self.updateWrapperLayout(layout: layout, offset: offset, transition: .immediate)
|
||||||
@ -536,10 +557,6 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
|||||||
self.patternButtonNode.setSelected(value, animated: true)
|
self.patternButtonNode.setSelected(value, animated: true)
|
||||||
|
|
||||||
self.requestPatternPanel?(value)
|
self.requestPatternPanel?(value)
|
||||||
|
|
||||||
if let layout = self.validLayout {
|
|
||||||
self.updateButtonsLayout(layout: layout, offset: CGPoint(), transition: .animated(duration: 0.3, curve: .spring))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var isColorEnabled: Bool {
|
var isColorEnabled: Bool {
|
||||||
@ -608,7 +625,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
|||||||
|
|
||||||
var additionalYOffset: CGFloat = 0.0
|
var additionalYOffset: CGFloat = 0.0
|
||||||
if self.patternButtonNode.isSelected {
|
if self.patternButtonNode.isSelected {
|
||||||
additionalYOffset = -114.0
|
additionalYOffset = -190.0
|
||||||
}
|
}
|
||||||
|
|
||||||
let leftButtonFrame = 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 + additionalYOffset), size: buttonSize)
|
let leftButtonFrame = 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 + additionalYOffset), size: buttonSize)
|
||||||
@ -647,6 +664,11 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
|||||||
motionAlpha = 1.0
|
motionAlpha = 1.0
|
||||||
case .color:
|
case .color:
|
||||||
patternAlpha = 1.0
|
patternAlpha = 1.0
|
||||||
|
if self.patternButtonNode.isSelected {
|
||||||
|
patternFrame = leftButtonFrame
|
||||||
|
motionAlpha = 1.0
|
||||||
|
motionFrame = rightButtonFrame
|
||||||
|
}
|
||||||
case .image:
|
case .image:
|
||||||
blurAlpha = 1.0
|
blurAlpha = 1.0
|
||||||
blurFrame = leftButtonFrame
|
blurFrame = leftButtonFrame
|
||||||
@ -656,13 +678,17 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
|||||||
if file.isPattern {
|
if file.isPattern {
|
||||||
if self.arguments.isColorsList {
|
if self.arguments.isColorsList {
|
||||||
patternAlpha = 1.0
|
patternAlpha = 1.0
|
||||||
patternFrame = leftButtonFrame
|
if self.patternButtonNode.isSelected {
|
||||||
|
patternFrame = leftButtonFrame
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
colorAlpha = 1.0
|
colorAlpha = 1.0
|
||||||
colorFrame = leftButtonFrame
|
colorFrame = leftButtonFrame
|
||||||
}
|
}
|
||||||
motionAlpha = 1.0
|
if !self.arguments.isColorsList || self.patternButtonNode.isSelected {
|
||||||
motionFrame = rightButtonFrame
|
motionAlpha = 1.0
|
||||||
|
motionFrame = rightButtonFrame
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
blurAlpha = 1.0
|
blurAlpha = 1.0
|
||||||
blurFrame = leftButtonFrame
|
blurFrame = leftButtonFrame
|
||||||
|
@ -3,6 +3,8 @@ import AsyncDisplayKit
|
|||||||
import Display
|
import Display
|
||||||
|
|
||||||
final class WallpaperGalleryToolbarNode: ASDisplayNode {
|
final class WallpaperGalleryToolbarNode: ASDisplayNode {
|
||||||
|
private var theme: PresentationTheme
|
||||||
|
|
||||||
private let cancelButton = HighlightableButtonNode()
|
private let cancelButton = HighlightableButtonNode()
|
||||||
private let doneButton = HighlightableButtonNode()
|
private let doneButton = HighlightableButtonNode()
|
||||||
private let separatorNode = ASDisplayNode()
|
private let separatorNode = ASDisplayNode()
|
||||||
@ -12,6 +14,8 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode {
|
|||||||
var done: (() -> Void)?
|
var done: (() -> Void)?
|
||||||
|
|
||||||
init(theme: PresentationTheme, strings: PresentationStrings) {
|
init(theme: PresentationTheme, strings: PresentationStrings) {
|
||||||
|
self.theme = theme
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.addSubnode(self.cancelButton)
|
self.addSubnode(self.cancelButton)
|
||||||
@ -24,7 +28,7 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode {
|
|||||||
self.cancelButton.highligthedChanged = { [weak self] highlighted in
|
self.cancelButton.highligthedChanged = { [weak self] highlighted in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if highlighted {
|
if highlighted {
|
||||||
strongSelf.cancelButton.backgroundColor = UIColor(rgb: 0xd4d4d4)
|
strongSelf.cancelButton.backgroundColor = strongSelf.theme.list.itemHighlightedBackgroundColor
|
||||||
} else {
|
} else {
|
||||||
UIView.animate(withDuration: 0.3, animations: {
|
UIView.animate(withDuration: 0.3, animations: {
|
||||||
strongSelf.cancelButton.backgroundColor = .clear
|
strongSelf.cancelButton.backgroundColor = .clear
|
||||||
@ -36,7 +40,7 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode {
|
|||||||
self.doneButton.highligthedChanged = { [weak self] highlighted in
|
self.doneButton.highligthedChanged = { [weak self] highlighted in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if highlighted {
|
if highlighted {
|
||||||
strongSelf.doneButton.backgroundColor = UIColor(rgb: 0xd4d4d4)
|
strongSelf.doneButton.backgroundColor = strongSelf.theme.list.itemHighlightedBackgroundColor
|
||||||
} else {
|
} else {
|
||||||
UIView.animate(withDuration: 0.3, animations: {
|
UIView.animate(withDuration: 0.3, animations: {
|
||||||
strongSelf.doneButton.backgroundColor = .clear
|
strongSelf.doneButton.backgroundColor = .clear
|
||||||
@ -55,6 +59,7 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {
|
func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {
|
||||||
|
self.theme = theme
|
||||||
self.backgroundColor = theme.rootController.tabBar.backgroundColor
|
self.backgroundColor = theme.rootController.tabBar.backgroundColor
|
||||||
self.separatorNode.backgroundColor = theme.rootController.tabBar.separatorColor
|
self.separatorNode.backgroundColor = theme.rootController.tabBar.separatorColor
|
||||||
self.topSeparatorNode.backgroundColor = theme.rootController.tabBar.separatorColor
|
self.topSeparatorNode.backgroundColor = theme.rootController.tabBar.separatorColor
|
||||||
|
@ -14,12 +14,25 @@ final class WallpaperPatternPanelNode: ASDisplayNode {
|
|||||||
private let topSeparatorNode: ASDisplayNode
|
private let topSeparatorNode: ASDisplayNode
|
||||||
|
|
||||||
private let scrollNode: ASScrollNode
|
private let scrollNode: ASScrollNode
|
||||||
|
private let intensityNode: WallpaperIntensityPickerNode
|
||||||
|
|
||||||
private var disposable: Disposable?
|
private var disposable: Disposable?
|
||||||
|
private var wallpapers: [TelegramWallpaper] = []
|
||||||
|
private var currentWallpaper: TelegramWallpaper?
|
||||||
|
|
||||||
var patternChanged: ((TelegramWallpaper) -> Void)?
|
var color: UIColor = .white {
|
||||||
|
didSet {
|
||||||
|
let color = patternColor(for: self.color, intensity: 1.0)
|
||||||
|
var min = self.color.hsv
|
||||||
|
var max = patternColor(for: self.color, intensity: 1.0).hsv
|
||||||
|
|
||||||
init(account: Account, theme: PresentationTheme) {
|
self.intensityNode.updateExtrema(min: min, max: max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var patternChanged: ((TelegramWallpaper, Int32?, Bool) -> Void)?
|
||||||
|
|
||||||
|
init(account: Account, theme: PresentationTheme, strings: PresentationStrings) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
|
||||||
self.backgroundNode = ASDisplayNode()
|
self.backgroundNode = ASDisplayNode()
|
||||||
@ -30,22 +43,27 @@ final class WallpaperPatternPanelNode: ASDisplayNode {
|
|||||||
|
|
||||||
self.scrollNode = ASScrollNode()
|
self.scrollNode = ASScrollNode()
|
||||||
|
|
||||||
|
self.intensityNode = WallpaperIntensityPickerNode(theme: theme, title: strings.WallpaperPreview_PatternIntensity, bordered: false)
|
||||||
|
self.intensityNode.value = 0.4
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.addSubnode(self.backgroundNode)
|
self.addSubnode(self.backgroundNode)
|
||||||
self.addSubnode(self.topSeparatorNode)
|
self.addSubnode(self.topSeparatorNode)
|
||||||
self.addSubnode(self.scrollNode)
|
self.addSubnode(self.scrollNode)
|
||||||
|
self.addSubnode(self.intensityNode)
|
||||||
|
|
||||||
self.disposable = ((telegramWallpapers(postbox: account.postbox, network: account.network)
|
self.disposable = ((telegramWallpapers(postbox: account.postbox, network: account.network)
|
||||||
|> map { wallpapers in
|
|> map { wallpapers in
|
||||||
return wallpapers.filter { wallpaper in
|
return wallpapers.filter { wallpaper in
|
||||||
if case let .file(file) = wallpaper, file.isPattern {
|
if case let .file(file) = wallpaper, file.isPattern, file.file.mimeType != "image/webp" {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} |> deliverOnMainQueue).start(next: { [weak self] wallpapers in
|
}
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] wallpapers in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if let subnodes = strongSelf.scrollNode.subnodes {
|
if let subnodes = strongSelf.scrollNode.subnodes {
|
||||||
for node in subnodes {
|
for node in subnodes {
|
||||||
@ -53,38 +71,52 @@ final class WallpaperPatternPanelNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var wallpapers = wallpapers
|
var selected = true
|
||||||
wallpapers.insert(.color(0xd6e2ee), at: 0)
|
|
||||||
|
|
||||||
for wallpaper in wallpapers {
|
for wallpaper in wallpapers {
|
||||||
let node = SettingsThemeWallpaperNode()
|
let node = SettingsThemeWallpaperNode(overlayBackgroundColor: UIColor(rgb: 0x748698, alpha: 0.4))
|
||||||
|
node.clipsToBounds = true
|
||||||
|
node.cornerRadius = 5.0
|
||||||
|
|
||||||
var updatedWallpaper = wallpaper
|
var updatedWallpaper = wallpaper
|
||||||
var isColor = false
|
|
||||||
if case let .file(file) = updatedWallpaper {
|
if case let .file(file) = updatedWallpaper {
|
||||||
let settings = WallpaperSettings(blur: false, motion: false, color: 0xd6e2ee, intensity: 100)
|
let settings = WallpaperSettings(blur: false, motion: false, color: 0xd6e2ee, intensity: 100)
|
||||||
updatedWallpaper = .file(id: file.id, accessHash: file.accessHash, isCreator: file.isCreator, isDefault: file.isDefault, isPattern: file.isPattern, slug: file.slug, file: file.file, settings: settings)
|
updatedWallpaper = .file(id: file.id, accessHash: file.accessHash, isCreator: file.isCreator, isDefault: file.isDefault, isPattern: file.isPattern, slug: file.slug, file: file.file, settings: settings)
|
||||||
} else {
|
|
||||||
isColor = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
node.setWallpaper(account: account, wallpaper: updatedWallpaper, selected: isColor, size: itemSize, cornerRadius: 5.0)
|
node.setWallpaper(account: account, wallpaper: updatedWallpaper, selected: selected, size: itemSize)
|
||||||
node.pressed = { [weak self, weak node] in
|
node.pressed = { [weak self, weak node] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.patternChanged?(updatedWallpaper)
|
strongSelf.currentWallpaper = updatedWallpaper
|
||||||
|
strongSelf.patternChanged?(updatedWallpaper, Int32(strongSelf.intensityNode.value * 100), false)
|
||||||
if let subnodes = strongSelf.scrollNode.subnodes {
|
if let subnodes = strongSelf.scrollNode.subnodes {
|
||||||
for case let subnode as SettingsThemeWallpaperNode in subnodes {
|
for case let subnode as SettingsThemeWallpaperNode in subnodes {
|
||||||
subnode.setSelected(node === subnode)
|
subnode.setSelected(node === subnode, animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
strongSelf.scrollNode.addSubnode(node)
|
strongSelf.scrollNode.addSubnode(node)
|
||||||
|
|
||||||
|
selected = false
|
||||||
}
|
}
|
||||||
strongSelf.scrollNode.view.contentSize = CGSize(width: (itemSize.width + inset) * CGFloat(wallpapers.count) + inset, height: 114.0)
|
strongSelf.scrollNode.view.contentSize = CGSize(width: (itemSize.width + inset) * CGFloat(wallpapers.count) + inset, height: 190.0)
|
||||||
strongSelf.layoutItemNodes(transition: .immediate)
|
strongSelf.layoutItemNodes(transition: .immediate)
|
||||||
|
|
||||||
|
strongSelf.wallpapers = wallpapers
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
self.intensityNode.valueChanged = { [weak self] value in
|
||||||
|
if let strongSelf = self, let wallpaper = strongSelf.currentWallpaper {
|
||||||
|
strongSelf.patternChanged?(wallpaper, strongSelf.intensityNode.intensity, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.intensityNode.valueChangeEnded = { [weak self] value in
|
||||||
|
if let strongSelf = self, let wallpaper = strongSelf.currentWallpaper {
|
||||||
|
strongSelf.patternChanged?(wallpaper, strongSelf.intensityNode.intensity, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
@ -94,14 +126,26 @@ final class WallpaperPatternPanelNode: ASDisplayNode {
|
|||||||
override func didLoad() {
|
override func didLoad() {
|
||||||
super.didLoad()
|
super.didLoad()
|
||||||
|
|
||||||
|
self.scrollNode.view.showsHorizontalScrollIndicator = false
|
||||||
|
self.scrollNode.view.showsVerticalScrollIndicator = false
|
||||||
self.scrollNode.view.alwaysBounceHorizontal = true
|
self.scrollNode.view.alwaysBounceHorizontal = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func didAppear() {
|
||||||
|
if let wallpaper = self.wallpapers.first {
|
||||||
|
self.currentWallpaper = wallpaper
|
||||||
|
self.intensityNode.value = 0.4
|
||||||
|
|
||||||
|
self.patternChanged?(wallpaper, self.intensityNode.intensity, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||||
let separatorHeight = UIScreenPixel
|
let separatorHeight = UIScreenPixel
|
||||||
transition.updateFrame(node: self.backgroundNode, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
|
transition.updateFrame(node: self.backgroundNode, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
|
||||||
transition.updateFrame(node: self.topSeparatorNode, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: separatorHeight))
|
transition.updateFrame(node: self.topSeparatorNode, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: separatorHeight))
|
||||||
transition.updateFrame(node: self.scrollNode, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
|
transition.updateFrame(node: self.scrollNode, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: 114.0))
|
||||||
|
transition.updateFrame(node: self.intensityNode, frame: CGRect(x: 16.0, y: 114.0 + 13.0, width: size.width - 16.0 * 2.0, height: 50.0))
|
||||||
|
|
||||||
self.layoutItemNodes(transition: transition)
|
self.layoutItemNodes(transition: transition)
|
||||||
}
|
}
|
||||||
|
@ -277,7 +277,7 @@ private func patternWallpaperDatas(account: Account, representations: [ImageRepr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func patternWallpaperImage(account: Account, representations: [ImageRepresentationWithReference], color: UIColor, mode: PatternWallpaperDrawMode, autoFetchFullSize: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
func patternWallpaperImage(account: Account, representations: [ImageRepresentationWithReference], mode: PatternWallpaperDrawMode, autoFetchFullSize: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||||
let signal = patternWallpaperDatas(account: account, representations: representations, mode: mode, autoFetchFullSize: autoFetchFullSize)
|
let signal = patternWallpaperDatas(account: account, representations: representations, mode: mode, autoFetchFullSize: autoFetchFullSize)
|
||||||
|
|
||||||
var prominent = false
|
var prominent = false
|
||||||
@ -313,39 +313,49 @@ func patternWallpaperImage(account: Account, representations: [ImageRepresentati
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if fullSizeImage == nil {
|
if let combinedColor = arguments.emptyColor {
|
||||||
let context = DrawingContext(size: arguments.drawingSize, scale: 1.0, clear: true)
|
let color = combinedColor.withAlphaComponent(1.0)
|
||||||
|
let intensity = combinedColor.alpha
|
||||||
|
|
||||||
|
if fullSizeImage == nil {
|
||||||
|
let context = DrawingContext(size: arguments.drawingSize, scale: 1.0, clear: true)
|
||||||
|
context.withFlippedContext { c in
|
||||||
|
c.setBlendMode(.copy)
|
||||||
|
c.setFillColor(color.cgColor)
|
||||||
|
c.fill(arguments.drawingRect)
|
||||||
|
}
|
||||||
|
|
||||||
|
addCorners(context, arguments: arguments)
|
||||||
|
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
let context = DrawingContext(size: arguments.drawingSize, scale: scale, clear: true)
|
||||||
context.withFlippedContext { c in
|
context.withFlippedContext { c in
|
||||||
c.setBlendMode(.copy)
|
c.setBlendMode(.copy)
|
||||||
c.setFillColor(color.cgColor)
|
c.setFillColor(color.cgColor)
|
||||||
c.fill(arguments.drawingRect)
|
c.fill(arguments.drawingRect)
|
||||||
|
|
||||||
|
if let fullSizeImage = fullSizeImage {
|
||||||
|
c.setBlendMode(.normal)
|
||||||
|
c.interpolationQuality = .medium
|
||||||
|
c.clip(to: fittedRect, mask: fullSizeImage)
|
||||||
|
c.setFillColor(patternColor(for: color, intensity: intensity, prominent: prominent).cgColor)
|
||||||
|
c.fill(arguments.drawingRect)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return context
|
|
||||||
}
|
|
||||||
|
|
||||||
let context = DrawingContext(size: arguments.drawingSize, scale: scale, clear: true)
|
|
||||||
context.withFlippedContext { c in
|
|
||||||
c.setBlendMode(.copy)
|
|
||||||
c.setFillColor(color.cgColor)
|
|
||||||
c.fill(arguments.drawingRect)
|
|
||||||
|
|
||||||
if let fullSizeImage = fullSizeImage {
|
addCorners(context, arguments: arguments)
|
||||||
c.setBlendMode(.normal)
|
|
||||||
c.interpolationQuality = .medium
|
return context
|
||||||
c.clip(to: fittedRect, mask: fullSizeImage)
|
} else {
|
||||||
c.setFillColor(patternColor(for: color, prominent: prominent).cgColor)
|
return nil
|
||||||
c.fill(arguments.drawingRect)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addCorners(context, arguments: arguments)
|
|
||||||
|
|
||||||
return context
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func patternColor(for color: UIColor, prominent: Bool = false) -> UIColor {
|
func patternColor(for color: UIColor, intensity: CGFloat, prominent: Bool = false) -> UIColor {
|
||||||
var hue: CGFloat = 0.0
|
var hue: CGFloat = 0.0
|
||||||
var saturation: CGFloat = 0.0
|
var saturation: CGFloat = 0.0
|
||||||
var brightness: CGFloat = 0.0
|
var brightness: CGFloat = 0.0
|
||||||
@ -357,7 +367,7 @@ func patternColor(for color: UIColor, prominent: Bool = false) -> UIColor {
|
|||||||
brightness = max(0.0, min(1.0, 1.0 - brightness * 0.65))
|
brightness = max(0.0, min(1.0, 1.0 - brightness * 0.65))
|
||||||
}
|
}
|
||||||
saturation = min(1.0, saturation + 0.05 + 0.1 * (1.0 - saturation))
|
saturation = min(1.0, saturation + 0.05 + 0.1 * (1.0 - saturation))
|
||||||
alpha = prominent ? 0.5 : 0.4
|
alpha = (prominent ? 0.5 : 0.4) * intensity
|
||||||
return UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: alpha)
|
return UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: alpha)
|
||||||
}
|
}
|
||||||
return .black
|
return .black
|
||||||
|
@ -10,6 +10,7 @@ func requestContextResults(account: Account, botId: PeerId, query: String, peerI
|
|||||||
return requestChatContextResults(account: account, botId: botId, peerId: peerId, query: query, offset: offset)
|
return requestChatContextResults(account: account, botId: botId, peerId: peerId, query: query, offset: offset)
|
||||||
|> mapToSignal { results -> Signal<ChatContextResultCollection?, NoError> in
|
|> mapToSignal { results -> Signal<ChatContextResultCollection?, NoError> in
|
||||||
var collection = existingResults
|
var collection = existingResults
|
||||||
|
var updated: Bool = false
|
||||||
if let existingResults = existingResults, let results = results {
|
if let existingResults = existingResults, let results = results {
|
||||||
var newResults: [ChatContextResult] = []
|
var newResults: [ChatContextResult] = []
|
||||||
var existingIds = Set<String>()
|
var existingIds = Set<String>()
|
||||||
@ -21,13 +22,15 @@ func requestContextResults(account: Account, botId: PeerId, query: String, peerI
|
|||||||
if !existingIds.contains(result.id) {
|
if !existingIds.contains(result.id) {
|
||||||
newResults.append(result)
|
newResults.append(result)
|
||||||
existingIds.insert(result.id)
|
existingIds.insert(result.id)
|
||||||
|
updated = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
collection = ChatContextResultCollection(botId: existingResults.botId, peerId: existingResults.peerId, query: existingResults.query, geoPoint: existingResults.geoPoint, queryId: results.queryId, nextOffset: results.nextOffset, presentation: existingResults.presentation, switchPeer: existingResults.switchPeer, results: newResults, cacheTimeout: existingResults.cacheTimeout)
|
collection = ChatContextResultCollection(botId: existingResults.botId, peerId: existingResults.peerId, query: existingResults.query, geoPoint: existingResults.geoPoint, queryId: results.queryId, nextOffset: results.nextOffset, presentation: existingResults.presentation, switchPeer: existingResults.switchPeer, results: newResults, cacheTimeout: existingResults.cacheTimeout)
|
||||||
} else {
|
} else {
|
||||||
collection = results
|
collection = results
|
||||||
|
updated = true
|
||||||
}
|
}
|
||||||
if let collection = collection, collection.results.count < limit, let nextOffset = collection.nextOffset {
|
if let collection = collection, collection.results.count < limit, let nextOffset = collection.nextOffset, updated {
|
||||||
let nextResults = requestContextResults(account: account, botId: botId, query: query, peerId: peerId, offset: nextOffset, existingResults: collection, limit: limit)
|
let nextResults = requestContextResults(account: account, botId: botId, query: query, peerId: peerId, offset: nextOffset, existingResults: collection, limit: limit)
|
||||||
if collection.results.count > 10 {
|
if collection.results.count > 10 {
|
||||||
return .single(collection)
|
return .single(collection)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user