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 */; };
|
||||
0902838821931D960067EFBD /* LanguageSuggestionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0902838721931D960067EFBD /* LanguageSuggestionController.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 */; };
|
||||
090E63EE2196FE3A00E3C035 /* OpenAddContact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E63ED2196FE3A00E3C035 /* OpenAddContact.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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -2881,6 +2885,8 @@
|
||||
D0471B521EFD8EBC0074D609 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
090B48C121FCB32C005083FA /* test1.webp */,
|
||||
090B48C021FCB32C005083FA /* test2.png */,
|
||||
D0955FB32191278C00F89427 /* PresentationStrings.mapping */,
|
||||
09310D13213BC5DE0020033A /* Animations */,
|
||||
092F368B2154AAD6001A9F49 /* Fonts */,
|
||||
@ -5033,6 +5039,7 @@
|
||||
files = (
|
||||
09874E4F21078FA100E190B8 /* Generic.html in Resources */,
|
||||
09874E5021078FA100E190B8 /* GenericUserScript.js in Resources */,
|
||||
090B48C521FCB433005083FA /* test2.png in Resources */,
|
||||
09874E5121078FA100E190B8 /* Instagram.html in Resources */,
|
||||
09874E5221078FA100E190B8 /* Twitch.html in Resources */,
|
||||
09874E5321078FA100E190B8 /* TwitchUserScript.js in Resources */,
|
||||
@ -5093,6 +5100,7 @@
|
||||
D0E9BA981F056F4C00F079A4 /* stp_card_applepay_template@3x.png in Resources */,
|
||||
D0E9BAA51F056F4C00F079A4 /* stp_card_form_applepay@2x.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 */,
|
||||
09310D30213ED5FB0020033A /* anim_unread.json in Resources */,
|
||||
D0E9BAB61F056F4C00F079A4 /* stp_card_visa@3x.png in Resources */,
|
||||
|
@ -128,18 +128,20 @@ final class CachedPatternWallpaperMaskRepresentation: CachedMediaResourceReprese
|
||||
|
||||
final class CachedPatternWallpaperRepresentation: CachedMediaResourceRepresentation {
|
||||
let color: Int32
|
||||
let intensity: Int32
|
||||
|
||||
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.intensity = intensity
|
||||
}
|
||||
|
||||
func isEqual(to: CachedMediaResourceRepresentation) -> Bool {
|
||||
if let to = to as? CachedPatternWallpaperRepresentation {
|
||||
return self.color == to.color
|
||||
return self.color == to.color && self.intensity == intensity
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
@ -102,9 +102,9 @@ func chatControllerBackgroundImage(wallpaper: TelegramWallpaper, mode: Wallpaper
|
||||
}
|
||||
}
|
||||
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?
|
||||
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 {
|
||||
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)
|
||||
|
||||
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))
|
||||
|
||||
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> {
|
||||
return Signal({ subscriber in
|
||||
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
|
||||
arc4random_buf(&randomId, 8)
|
||||
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> {
|
||||
return Signal({ subscriber in
|
||||
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
|
||||
arc4random_buf(&randomId, 8)
|
||||
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 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 rect = CGRect(origin: CGPoint(), size: size)
|
||||
|
@ -54,6 +54,9 @@ class ModernCheckNode: ASDisplayNode {
|
||||
|
||||
var selected = false
|
||||
func setSelected(_ selected: Bool, animated: Bool = false) {
|
||||
guard self.selected != selected else {
|
||||
return
|
||||
}
|
||||
self.selected = selected
|
||||
|
||||
if selected && animated {
|
||||
@ -74,6 +77,7 @@ class ModernCheckNode: ASDisplayNode {
|
||||
animation.duration = 0.21
|
||||
self.pop_add(animation, forKey: "progress")
|
||||
} else {
|
||||
self.pop_removeAllAnimations()
|
||||
self.animationProgress = selected ? 1.0 : 0.0
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
|
@ -417,8 +417,8 @@ func openChatWallpaper(account: Account, message: Message, present: @escaping (V
|
||||
if case let .wallpaper(parameter) = resolvedUrl {
|
||||
let source: WallpaperListSource
|
||||
switch parameter {
|
||||
case let .slug(slug, options):
|
||||
source = .slug(slug, content.file, options)
|
||||
case let .slug(slug, options, color, intensity):
|
||||
source = .slug(slug, content.file, options, color, intensity)
|
||||
case let .color(color):
|
||||
source = .wallpaper(.color(Int32(color.rgb)), nil)
|
||||
}
|
||||
|
@ -200,7 +200,7 @@ func openResolvedUrl(_ resolvedUrl: ResolvedUrl, account: Account, context: Open
|
||||
let signal: Signal<TelegramWallpaper, GetWallpaperError>
|
||||
var options: WallpaperPresentationOptions?
|
||||
switch parameter {
|
||||
case let .slug(slug, wallpaperOptions):
|
||||
case let .slug(slug, wallpaperOptions, color, intensity):
|
||||
signal = getWallpaper(account: account, slug: slug)
|
||||
options = wallpaperOptions
|
||||
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.prepareAnimateIn(from: nil)
|
||||
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)
|
||||
}
|
||||
@ -232,7 +236,15 @@ public final class RadialStatusNode: ASControlNode {
|
||||
backgroundNode.frame = self.bounds
|
||||
self.backgroundNode = backgroundNode
|
||||
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 {
|
||||
self.backgroundNode = nil
|
||||
|
Binary file not shown.
@ -25,6 +25,7 @@ private func whiteColorImage(theme: PresentationTheme) -> Signal<(TransformImage
|
||||
|
||||
final class SettingsThemeWallpaperNode: ASDisplayNode {
|
||||
private var wallpaper: TelegramWallpaper?
|
||||
private var color: UIColor?
|
||||
|
||||
let buttonNode = HighlightTrackingButtonNode()
|
||||
let backgroundNode = ASDisplayNode()
|
||||
@ -33,10 +34,10 @@ final class SettingsThemeWallpaperNode: ASDisplayNode {
|
||||
|
||||
var pressed: (() -> Void)?
|
||||
|
||||
override init() {
|
||||
init(overlayBackgroundColor: UIColor = UIColor(white: 0.0, alpha: 0.3)) {
|
||||
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
|
||||
self.statusNode.frame = CGRect(x: 0.0, y: 0.0, width: progressDiameter, height: progressDiameter)
|
||||
self.statusNode.isUserInteractionEnabled = false
|
||||
@ -51,16 +52,15 @@ final class SettingsThemeWallpaperNode: ASDisplayNode {
|
||||
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
|
||||
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) {
|
||||
self.buttonNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
self.backgroundNode.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
|
||||
self.statusNode.transitionToState(state, animated: false, completion: {})
|
||||
@ -90,7 +90,6 @@ final class SettingsThemeWallpaperNode: ASDisplayNode {
|
||||
self.imageNode.isHidden = true
|
||||
self.backgroundNode.isHidden = false
|
||||
self.backgroundNode.backgroundColor = UIColor(rgb: UInt32(bitPattern: color))
|
||||
self.cornerRadius = cornerRadius
|
||||
}
|
||||
case let .image(representations, _):
|
||||
self.imageNode.isHidden = false
|
||||
@ -112,12 +111,17 @@ final class SettingsThemeWallpaperNode: ASDisplayNode {
|
||||
if file.isPattern {
|
||||
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 {
|
||||
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
|
||||
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 {
|
||||
self.backgroundNode.isHidden = true
|
||||
|
||||
@ -126,7 +130,7 @@ final class SettingsThemeWallpaperNode: ASDisplayNode {
|
||||
self.imageNode.setSignal(imageSignal)
|
||||
|
||||
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()
|
||||
}
|
||||
} else if let wallpaper = self.wallpaper {
|
||||
@ -139,7 +143,7 @@ final class SettingsThemeWallpaperNode: ASDisplayNode {
|
||||
apply()
|
||||
case let .file(file):
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -25,10 +25,8 @@ public final class TelegramRootController: NavigationController {
|
||||
|
||||
super.init(mode: .automaticMasterDetail, theme: NavigationControllerTheme(presentationTheme: self.presentationData.theme))
|
||||
|
||||
//self.permissionsDisposable =
|
||||
|
||||
self.presentationDataDisposable = (account.telegramApplicationContext.presentationData
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
let previousTheme = strongSelf.presentationData.theme
|
||||
strongSelf.presentationData = presentationData
|
||||
|
@ -370,10 +370,24 @@ final class ThemeGridController: ViewController {
|
||||
for wallpaper in wallpapers {
|
||||
var item: String?
|
||||
switch wallpaper {
|
||||
case let .file(_, _, _, _, _, slug, _, _):
|
||||
item = slug
|
||||
case let .file(_, _, _, _, isPattern, slug, _, settings):
|
||||
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):
|
||||
item = "\(String(UInt32(bitPattern: color), radix: 16, uppercase: false))"
|
||||
item = "\(UIColor(rgb: UInt32(bitPattern: color)).hexString)"
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ final class ThemeGridControllerItem: GridItem {
|
||||
|
||||
func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode {
|
||||
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
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ final class ThemeGridControllerItem: GridItem {
|
||||
assertionFailure()
|
||||
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 var selectionNode: GridMessageSelectionNode?
|
||||
|
||||
private var currentState: (Account, TelegramWallpaper, Int, Bool)?
|
||||
private var currentState: (Account, TelegramWallpaper, Bool)?
|
||||
private var interaction: ThemeGridControllerInteraction?
|
||||
|
||||
override init() {
|
||||
@ -57,38 +57,39 @@ final class ThemeGridControllerItemNode: GridItemNode {
|
||||
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
|
||||
|
||||
if self.currentState == nil || self.currentState!.0 !== account || wallpaper != self.currentState!.1 || index != self.currentState!.2 || selected != self.currentState!.3 {
|
||||
self.currentState = (account, wallpaper, index, selected)
|
||||
if self.currentState == nil || self.currentState!.0 !== account || wallpaper != self.currentState!.1 || selected != self.currentState!.2 {
|
||||
self.currentState = (account, wallpaper, selected)
|
||||
self.updateSelectionState(animated: false)
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
if let (_, wallpaper, _, _) = self.currentState {
|
||||
if let (_, wallpaper, _) = self.currentState {
|
||||
self.interaction?.openWallpaper(wallpaper)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateSelectionState(animated: Bool) {
|
||||
if let (account, wallpaper, index, _) = self.currentState {
|
||||
if let (account, wallpaper, _) = self.currentState {
|
||||
var editing = false
|
||||
var selectable = false
|
||||
if case .file = wallpaper {
|
||||
selectable = true
|
||||
var id: Int64?
|
||||
if case let .file(file) = wallpaper {
|
||||
id = file.id
|
||||
}
|
||||
var selectedIndices = Set<Int>()
|
||||
var selectedIndices = Set<Int64>()
|
||||
if let interaction = self.interaction {
|
||||
let (active, indices) = interaction.selectionState
|
||||
editing = active
|
||||
selectedIndices = indices
|
||||
}
|
||||
if editing && selectable {
|
||||
let selected = selectedIndices.contains(index)
|
||||
if let id = id, editing {
|
||||
let selected = selectedIndices.contains(id)
|
||||
|
||||
if let selectionNode = self.selectionNode {
|
||||
selectionNode.updateSelected(selected, animated: animated)
|
||||
@ -97,7 +98,7 @@ final class ThemeGridControllerItemNode: GridItemNode {
|
||||
let theme = account.telegramApplicationContext.currentPresentationData.with { $0 }.theme
|
||||
let selectionNode = GridMessageSelectionNode(theme: theme, toggle: { [weak self] value in
|
||||
if let strongSelf = self {
|
||||
strongSelf.interaction?.toggleWallpaperSelection(index, value)
|
||||
strongSelf.interaction?.toggleWallpaperSelection(id, value)
|
||||
}
|
||||
})
|
||||
|
||||
@ -129,7 +130,7 @@ final class ThemeGridControllerItemNode: GridItemNode {
|
||||
super.layout()
|
||||
|
||||
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.selectionNode?.frame = CGRect(origin: CGPoint(), size: bounds.size)
|
||||
}
|
||||
|
@ -36,13 +36,13 @@ private func areWallpapersEqual(_ lhs: TelegramWallpaper, _ rhs: TelegramWallpap
|
||||
|
||||
struct ThemeGridControllerNodeState: Equatable {
|
||||
let editing: Bool
|
||||
var selectedIndices: Set<Int>
|
||||
var selectedIndices: Set<Int64>
|
||||
|
||||
func withUpdatedEditing(_ editing: Bool) -> ThemeGridControllerNodeState {
|
||||
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)
|
||||
}
|
||||
|
||||
@ -59,12 +59,12 @@ struct ThemeGridControllerNodeState: Equatable {
|
||||
|
||||
final class ThemeGridControllerInteraction {
|
||||
let openWallpaper: (TelegramWallpaper) -> Void
|
||||
let toggleWallpaperSelection: (Int, Bool) -> Void
|
||||
let toggleWallpaperSelection: (Int64, Bool) -> Void
|
||||
let deleteSelectedWallpapers: () -> 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.toggleWallpaperSelection = toggleWallpaperSelection
|
||||
self.deleteSelectedWallpapers = deleteSelectedWallpapers
|
||||
@ -143,21 +143,54 @@ private func selectedWallpapers(entries: [ThemeGridControllerEntry]?, state: The
|
||||
return []
|
||||
}
|
||||
|
||||
var i = 0
|
||||
if let entry = entries.first {
|
||||
i = entry.index
|
||||
}
|
||||
|
||||
var wallpapers: [TelegramWallpaper] = []
|
||||
for entry in entries {
|
||||
if state.selectedIndices.contains(i) {
|
||||
wallpapers.append(entry.wallpaper)
|
||||
if case let .file(file) = entry.wallpaper {
|
||||
if state.selectedIndices.contains(file.id) {
|
||||
wallpapers.append(entry.wallpaper)
|
||||
}
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
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 {
|
||||
private let account: Account
|
||||
private var presentationData: PresentationData
|
||||
@ -269,14 +302,14 @@ final class ThemeGridControllerNode: ASDisplayNode {
|
||||
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 {
|
||||
strongSelf.updateState { current in
|
||||
var updated = current.selectedIndices
|
||||
if value {
|
||||
updated.insert(index)
|
||||
updated.insert(id)
|
||||
} else {
|
||||
updated.remove(index)
|
||||
updated.remove(id)
|
||||
}
|
||||
return current.withUpdatedSelectedIndices(updated)
|
||||
}
|
||||
@ -288,7 +321,8 @@ final class ThemeGridControllerNode: ASDisplayNode {
|
||||
if let strongSelf = self {
|
||||
var updatedWallpapers: [TelegramWallpaper] = []
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -313,7 +347,22 @@ final class ThemeGridControllerNode: ASDisplayNode {
|
||||
|
||||
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)
|
||||
if !selected {
|
||||
entries.append(ThemeGridControllerEntry(index: index, wallpaper: wallpaper, selected: false))
|
||||
|
@ -269,6 +269,8 @@ struct ThemeGridSearchContainerTransition {
|
||||
let insertions: [GridNodeInsertItem]
|
||||
let updates: [GridNodeUpdateItem]
|
||||
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 {
|
||||
@ -281,14 +283,14 @@ private func themeGridSearchContainerPreparedRecentTransition(from fromEntries:
|
||||
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 deletions = deleteIndices
|
||||
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)) }
|
||||
|
||||
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 {
|
||||
@ -308,9 +310,13 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode {
|
||||
private let recentListNode: ListView
|
||||
private let gridNode: GridNode
|
||||
private let dimNode: ASDisplayNode
|
||||
|
||||
private let emptyResultsTitleNode: ImmediateTextNode
|
||||
private let emptyResultsTextNode: ImmediateTextNode
|
||||
|
||||
private var enqueuedRecentTransitions: [(ThemeGridSearchContainerRecentTransition, Bool)] = []
|
||||
private var enqueuedTransitions: [(ThemeGridSearchContainerTransition, Bool)] = []
|
||||
private var validLayout: ContainerViewLayout?
|
||||
private var validLayout: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
private var queryValue: WallpaperSearchQuery = .generic("")
|
||||
private let queryPromise: Promise<WallpaperSearchQuery>
|
||||
@ -339,6 +345,17 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode {
|
||||
self.recentListNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
|
||||
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()
|
||||
|
||||
self.dimNode.backgroundColor = self.presentationData.theme.chatList.backgroundColor
|
||||
@ -349,6 +366,9 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode {
|
||||
self.addSubnode(self.recentListNode)
|
||||
self.addSubnode(self.gridNode)
|
||||
|
||||
self.addSubnode(self.emptyResultsTitleNode)
|
||||
self.addSubnode(self.emptyResultsTextNode)
|
||||
|
||||
let searchContext = Promise<ThemeGridSearchContext?>(nil)
|
||||
let searchContextValue = Atomic<ThemeGridSearchContext?>(value: nil)
|
||||
let updateSearchContext: ((ThemeGridSearchContext?) -> (ThemeGridSearchContext?, Bool)) -> Void = { f in
|
||||
@ -368,21 +388,22 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode {
|
||||
}
|
||||
|
||||
self.gridNode.isHidden = true
|
||||
// self.listNode.visibleBottomContentOffsetChanged = { offset in
|
||||
// guard case let .known(value) = offset, value < 100.0 else {
|
||||
// return
|
||||
// }
|
||||
// updateSearchContext { previous in
|
||||
// guard let previous = previous else {
|
||||
// return (nil, false)
|
||||
// self.gridNode.visibleItemsUpdated = { [weak self] visibleItems in
|
||||
// if let strongSelf = self, let bottom = visibleItems.bottom {
|
||||
// if let context = searchContextValue.with({ $0 }), bottom.0 >= context.result.items.count - 8 {
|
||||
// updateSearchContext { previous in
|
||||
// guard let previous = previous else {
|
||||
// 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
|
||||
@ -495,15 +516,20 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode {
|
||||
}
|
||||
})
|
||||
|
||||
self.searchDisposable.set((combineLatest(foundItems, self.presentationDataPromise.get())
|
||||
|> deliverOnMainQueue).start(next: { [weak self] entriesAndFlags, presentationData in
|
||||
self.searchDisposable.set((combineLatest(foundItems, self.presentationDataPromise.get(), self.queryPromise.get())
|
||||
|> deliverOnMainQueue).start(next: { [weak self] entriesAndFlags, presentationData, query in
|
||||
if let strongSelf = self {
|
||||
strongSelf._isSearching.set(entriesAndFlags?.1 ?? false)
|
||||
|
||||
let previousEntries = previousSearchItems.swap(entriesAndFlags?.0)
|
||||
|
||||
var isEmpty = false
|
||||
if let entriesAndFlags = entriesAndFlags {
|
||||
isEmpty = entriesAndFlags.0.isEmpty && !entriesAndFlags.1
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}))
|
||||
@ -634,6 +660,16 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode {
|
||||
strongSelf.recentListNode.isHidden = displayingResults
|
||||
strongSelf.dimNode.isHidden = displayingResults
|
||||
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)
|
||||
|
||||
let hadValidLayout = self.validLayout != nil
|
||||
self.validLayout = layout
|
||||
self.validLayout = (layout, navigationBarHeight)
|
||||
|
||||
let minSpacing: CGFloat = 8.0
|
||||
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.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 {
|
||||
while !self.enqueuedRecentTransitions.isEmpty {
|
||||
self.dequeueRecentTransition()
|
||||
|
@ -13,7 +13,7 @@ final class ThemeGridSelectionPanelNode: ASDisplayNode {
|
||||
|
||||
private var theme: PresentationTheme
|
||||
|
||||
var selectedIndices = Set<Int>() {
|
||||
var selectedIndices = Set<Int64>() {
|
||||
didSet {
|
||||
if oldValue != self.selectedIndices {
|
||||
self.deleteButton.isEnabled = !self.selectedIndices.isEmpty
|
||||
|
@ -137,9 +137,9 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode {
|
||||
}
|
||||
}
|
||||
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?
|
||||
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 {
|
||||
image = UIImage(contentsOfFile: data.path)?.precomposed()
|
||||
}
|
||||
|
@ -42,6 +42,6 @@ public struct TransformImageArguments: Equatable {
|
||||
}
|
||||
|
||||
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 {
|
||||
case slug(String, WallpaperPresentationOptions)
|
||||
case slug(String, WallpaperPresentationOptions, UIColor?, Int32?)
|
||||
case color(UIColor)
|
||||
}
|
||||
|
||||
@ -182,28 +182,30 @@ func parseInternalUrl(query: String) -> ParsedInternalUrl? {
|
||||
} else {
|
||||
var options: WallpaperPresentationOptions = []
|
||||
var intensity: Int32?
|
||||
var color: Int32?
|
||||
var color: UIColor?
|
||||
if let queryItems = components.queryItems {
|
||||
for queryItem in queryItems {
|
||||
if let value = queryItem.value, queryItem.name == "mode" {
|
||||
for option in value.components(separatedBy: "+") {
|
||||
switch option.lowercased() {
|
||||
case "motion":
|
||||
options.insert(.motion)
|
||||
case "blur":
|
||||
options.insert(.blur)
|
||||
case "intensity":
|
||||
intensity = Int32(value)
|
||||
case "color":
|
||||
color = Int32(value)
|
||||
default:
|
||||
break
|
||||
if let value = queryItem.value{
|
||||
if queryItem.name == "mode" {
|
||||
for option in value.components(separatedBy: "+") {
|
||||
switch option.lowercased() {
|
||||
case "motion":
|
||||
options.insert(.motion)
|
||||
case "blur":
|
||||
options.insert(.blur)
|
||||
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)
|
||||
} else if let value = Int(pathComponents[1]) {
|
||||
|
@ -10,7 +10,19 @@ private let shadowImage: UIImage = {
|
||||
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.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))
|
||||
})!
|
||||
}()
|
||||
@ -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 {
|
||||
var hsv: (CGFloat, CGFloat, CGFloat) = (0.0, 0.0, 1.0) {
|
||||
didSet {
|
||||
self.setNeedsDisplay()
|
||||
if self.hsv != oldValue {
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,7 +114,8 @@ private final class WallpaperColorKnobNode: ASDisplayNode {
|
||||
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.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)
|
||||
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 {
|
||||
private let brightnessNode: WallpaperColorBrightnessNode
|
||||
private let brightnessKnobNode: ASImageNode
|
||||
|
@ -15,7 +15,7 @@ enum WallpaperListType {
|
||||
enum WallpaperListSource {
|
||||
case list(wallpapers: [TelegramWallpaper], central: TelegramWallpaper, type: WallpaperListType)
|
||||
case wallpaper(TelegramWallpaper, WallpaperPresentationOptions?)
|
||||
case slug(String, TelegramMediaFile?, WallpaperPresentationOptions?)
|
||||
case slug(String, TelegramMediaFile?, WallpaperPresentationOptions?, UIColor?, Int32?)
|
||||
case asset(PHAsset)
|
||||
case contextResult(ChatContextResult)
|
||||
case customColor(Int32?)
|
||||
@ -129,9 +129,19 @@ class WallpaperGalleryController: ViewController {
|
||||
if case let .wallpapers(wallpaperOptions) = type, let options = wallpaperOptions {
|
||||
self.initialOptions = options
|
||||
}
|
||||
case let .slug(slug, file, options):
|
||||
case let .slug(slug, file, options, color, intensity):
|
||||
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.initialOptions = options
|
||||
}
|
||||
@ -193,7 +203,7 @@ class WallpaperGalleryController: ViewController {
|
||||
|
||||
self.centralItemAttributesDisposable.add(self.centralItemAction.get().start(next: { [weak self] barButton in
|
||||
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 {
|
||||
var colors = false
|
||||
if case let .list(_, _, type) = self.source, case .colors = type {
|
||||
colors = true
|
||||
}
|
||||
|
||||
var i: Int = 0
|
||||
var updateItems: [GalleryPagerUpdateItem] = []
|
||||
for entry in entries {
|
||||
@ -315,39 +320,6 @@ class WallpaperGalleryController: ViewController {
|
||||
self.colorPanelNode = 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)
|
||||
self.toolbarNode = toolbarNode
|
||||
overlayNode.addSubnode(toolbarNode)
|
||||
@ -395,8 +367,8 @@ class WallpaperGalleryController: ViewController {
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if case let .file(file) = wallpaper, file.isPattern, let color = file.settings.color {
|
||||
let _ = strongSelf.account.postbox.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedPatternWallpaperRepresentation(color: color), complete: true, fetch: true).start(completed: {
|
||||
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, intensity: intensity), complete: true, fetch: true).start(completed: {
|
||||
completion()
|
||||
})
|
||||
} else {
|
||||
@ -447,6 +419,11 @@ class WallpaperGalleryController: ViewController {
|
||||
strongSelf.patternPanelEnabled = enabled
|
||||
strongSelf.galleryNode.scrollView.isScrollEnabled = !enabled
|
||||
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
|
||||
@ -458,12 +435,51 @@ class WallpaperGalleryController: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
// if let (layout, _) = self.validLayout {
|
||||
// self.containerLayoutUpdated(layout, transition: animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate)
|
||||
// }
|
||||
if let entry = node.entry, case let .wallpaper(wallpaper) = entry {
|
||||
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) {
|
||||
let hadLayout = self.validLayout != nil
|
||||
|
||||
@ -570,18 +586,31 @@ class WallpaperGalleryController: ViewController {
|
||||
colorPanelNode.updateLayout(size: colorPanelFrame.size, keyboardHeight: layout.inputHeight ?? 0.0, transition: transition)
|
||||
}
|
||||
|
||||
let currentPatternPanelNode: WallpaperPatternPanelNode
|
||||
if let patternPanelNode = self.patternPanelNode {
|
||||
let panelHeight: CGFloat = 114.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
|
||||
currentPatternPanelNode = patternPanelNode
|
||||
} else {
|
||||
let patternPanelNode = WallpaperPatternPanelNode(account: self.account, theme: presentationData.theme, strings: presentationData.strings)
|
||||
patternPanelNode.patternChanged = { [weak self] pattern, intensity, preview in
|
||||
if let strongSelf = self, strongSelf.validLayout != nil {
|
||||
strongSelf.updateEntries(pattern: pattern, intensity: intensity, preview: preview)
|
||||
}
|
||||
}
|
||||
|
||||
transition.updateFrame(node: patternPanelNode, frame: patternPanelFrame)
|
||||
patternPanelNode.updateLayout(size: patternPanelFrame.size, transition: transition)
|
||||
self.patternPanelNode = patternPanelNode
|
||||
currentPatternPanelNode = patternPanelNode
|
||||
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 {
|
||||
var bottomOffset: CGFloat = layout.size.height - bottomInset - 9.0
|
||||
if colorPanelEnabled {
|
||||
@ -615,24 +644,37 @@ class WallpaperGalleryController: ViewController {
|
||||
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?
|
||||
switch wallpaper {
|
||||
case let .file(_, _, _, _, _, slug, _, _):
|
||||
controller = ShareController(account: account, subject: .url("https://t.me/bg/\(slug)\(options)"))
|
||||
case let .file(_, _, _, _, isPattern, slug, _, settings):
|
||||
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):
|
||||
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:
|
||||
break
|
||||
}
|
||||
|
@ -80,6 +80,7 @@ private let motionAmount: CGFloat = 32.0
|
||||
final class WallpaperGalleryItemNode: GalleryItemNode {
|
||||
private let account: Account
|
||||
var entry: WallpaperGalleryEntry?
|
||||
private var colorPreview: Bool = false
|
||||
private var contentSize: CGSize?
|
||||
private var arguments = WallpaperGalleryItemArguments()
|
||||
|
||||
@ -198,6 +199,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
||||
}
|
||||
|
||||
if self.entry != entry || self.arguments.colorPreview != previousArguments.colorPreview {
|
||||
let previousEntry = self.entry
|
||||
self.entry = entry
|
||||
|
||||
self.patternButtonNode.isSelected = self.arguments.patternEnabled
|
||||
@ -210,6 +212,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
||||
let subtitleSignal: Signal<String?, NoError>
|
||||
var actionSignal: Signal<UIBarButtonItem?, NoError> = .single(nil)
|
||||
var colorSignal: Signal<UIColor, NoError> = serviceColor(from: imagePromise.get())
|
||||
var color: UIColor?
|
||||
|
||||
let displaySize: 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)))
|
||||
|
||||
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 {
|
||||
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
|
||||
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)
|
||||
|
||||
self.colorButtonNode.color = patternColor
|
||||
isBlurrable = false
|
||||
} else {
|
||||
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.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
|
||||
if let strongSelf = self {
|
||||
var image = isBlurrable ? image : nil
|
||||
@ -430,7 +448,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
||||
}))
|
||||
|
||||
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 {
|
||||
self.patternButtonNode.isSelected = self.arguments.patternEnabled
|
||||
@ -443,6 +461,9 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
||||
|
||||
override func screenFrameUpdated(_ frame: CGRect) {
|
||||
let offset = -frame.minX
|
||||
guard self.validOffset != offset else {
|
||||
return
|
||||
}
|
||||
self.validOffset = offset
|
||||
if let layout = self.validLayout {
|
||||
self.updateWrapperLayout(layout: layout, offset: offset, transition: .immediate)
|
||||
@ -536,10 +557,6 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
||||
self.patternButtonNode.setSelected(value, animated: true)
|
||||
|
||||
self.requestPatternPanel?(value)
|
||||
|
||||
if let layout = self.validLayout {
|
||||
self.updateButtonsLayout(layout: layout, offset: CGPoint(), transition: .animated(duration: 0.3, curve: .spring))
|
||||
}
|
||||
}
|
||||
|
||||
var isColorEnabled: Bool {
|
||||
@ -608,7 +625,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
||||
|
||||
var additionalYOffset: CGFloat = 0.0
|
||||
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)
|
||||
@ -647,6 +664,11 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
||||
motionAlpha = 1.0
|
||||
case .color:
|
||||
patternAlpha = 1.0
|
||||
if self.patternButtonNode.isSelected {
|
||||
patternFrame = leftButtonFrame
|
||||
motionAlpha = 1.0
|
||||
motionFrame = rightButtonFrame
|
||||
}
|
||||
case .image:
|
||||
blurAlpha = 1.0
|
||||
blurFrame = leftButtonFrame
|
||||
@ -656,13 +678,17 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
||||
if file.isPattern {
|
||||
if self.arguments.isColorsList {
|
||||
patternAlpha = 1.0
|
||||
patternFrame = leftButtonFrame
|
||||
if self.patternButtonNode.isSelected {
|
||||
patternFrame = leftButtonFrame
|
||||
}
|
||||
} else {
|
||||
colorAlpha = 1.0
|
||||
colorFrame = leftButtonFrame
|
||||
}
|
||||
motionAlpha = 1.0
|
||||
motionFrame = rightButtonFrame
|
||||
if !self.arguments.isColorsList || self.patternButtonNode.isSelected {
|
||||
motionAlpha = 1.0
|
||||
motionFrame = rightButtonFrame
|
||||
}
|
||||
} else {
|
||||
blurAlpha = 1.0
|
||||
blurFrame = leftButtonFrame
|
||||
|
@ -3,6 +3,8 @@ import AsyncDisplayKit
|
||||
import Display
|
||||
|
||||
final class WallpaperGalleryToolbarNode: ASDisplayNode {
|
||||
private var theme: PresentationTheme
|
||||
|
||||
private let cancelButton = HighlightableButtonNode()
|
||||
private let doneButton = HighlightableButtonNode()
|
||||
private let separatorNode = ASDisplayNode()
|
||||
@ -12,6 +14,8 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode {
|
||||
var done: (() -> Void)?
|
||||
|
||||
init(theme: PresentationTheme, strings: PresentationStrings) {
|
||||
self.theme = theme
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.cancelButton)
|
||||
@ -24,7 +28,7 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode {
|
||||
self.cancelButton.highligthedChanged = { [weak self] highlighted in
|
||||
if let strongSelf = self {
|
||||
if highlighted {
|
||||
strongSelf.cancelButton.backgroundColor = UIColor(rgb: 0xd4d4d4)
|
||||
strongSelf.cancelButton.backgroundColor = strongSelf.theme.list.itemHighlightedBackgroundColor
|
||||
} else {
|
||||
UIView.animate(withDuration: 0.3, animations: {
|
||||
strongSelf.cancelButton.backgroundColor = .clear
|
||||
@ -36,7 +40,7 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode {
|
||||
self.doneButton.highligthedChanged = { [weak self] highlighted in
|
||||
if let strongSelf = self {
|
||||
if highlighted {
|
||||
strongSelf.doneButton.backgroundColor = UIColor(rgb: 0xd4d4d4)
|
||||
strongSelf.doneButton.backgroundColor = strongSelf.theme.list.itemHighlightedBackgroundColor
|
||||
} else {
|
||||
UIView.animate(withDuration: 0.3, animations: {
|
||||
strongSelf.doneButton.backgroundColor = .clear
|
||||
@ -55,6 +59,7 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {
|
||||
self.theme = theme
|
||||
self.backgroundColor = theme.rootController.tabBar.backgroundColor
|
||||
self.separatorNode.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 scrollNode: ASScrollNode
|
||||
private let intensityNode: WallpaperIntensityPickerNode
|
||||
|
||||
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.backgroundNode = ASDisplayNode()
|
||||
@ -30,22 +43,27 @@ final class WallpaperPatternPanelNode: ASDisplayNode {
|
||||
|
||||
self.scrollNode = ASScrollNode()
|
||||
|
||||
self.intensityNode = WallpaperIntensityPickerNode(theme: theme, title: strings.WallpaperPreview_PatternIntensity, bordered: false)
|
||||
self.intensityNode.value = 0.4
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.topSeparatorNode)
|
||||
self.addSubnode(self.scrollNode)
|
||||
self.addSubnode(self.intensityNode)
|
||||
|
||||
self.disposable = ((telegramWallpapers(postbox: account.postbox, network: account.network)
|
||||
|> map { wallpapers 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
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
} |> deliverOnMainQueue).start(next: { [weak self] wallpapers in
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] wallpapers in
|
||||
if let strongSelf = self {
|
||||
if let subnodes = strongSelf.scrollNode.subnodes {
|
||||
for node in subnodes {
|
||||
@ -53,38 +71,52 @@ final class WallpaperPatternPanelNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
var wallpapers = wallpapers
|
||||
wallpapers.insert(.color(0xd6e2ee), at: 0)
|
||||
|
||||
var selected = true
|
||||
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 isColor = false
|
||||
if case let .file(file) = updatedWallpaper {
|
||||
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)
|
||||
} 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
|
||||
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 {
|
||||
for case let subnode as SettingsThemeWallpaperNode in subnodes {
|
||||
subnode.setSelected(node === subnode)
|
||||
subnode.setSelected(node === subnode, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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.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 {
|
||||
@ -94,14 +126,26 @@ final class WallpaperPatternPanelNode: ASDisplayNode {
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.scrollNode.view.showsHorizontalScrollIndicator = false
|
||||
self.scrollNode.view.showsVerticalScrollIndicator = false
|
||||
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) {
|
||||
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.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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
var prominent = false
|
||||
@ -313,39 +313,49 @@ func patternWallpaperImage(account: Account, representations: [ImageRepresentati
|
||||
}
|
||||
}
|
||||
|
||||
if fullSizeImage == nil {
|
||||
let context = DrawingContext(size: arguments.drawingSize, scale: 1.0, clear: true)
|
||||
if let combinedColor = arguments.emptyColor {
|
||||
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
|
||||
c.setBlendMode(.copy)
|
||||
c.setFillColor(color.cgColor)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
addCorners(context, arguments: arguments)
|
||||
|
||||
return context
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
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 {
|
||||
c.setBlendMode(.normal)
|
||||
c.interpolationQuality = .medium
|
||||
c.clip(to: fittedRect, mask: fullSizeImage)
|
||||
c.setFillColor(patternColor(for: color, prominent: prominent).cgColor)
|
||||
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 saturation: 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))
|
||||
}
|
||||
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 .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)
|
||||
|> mapToSignal { results -> Signal<ChatContextResultCollection?, NoError> in
|
||||
var collection = existingResults
|
||||
var updated: Bool = false
|
||||
if let existingResults = existingResults, let results = results {
|
||||
var newResults: [ChatContextResult] = []
|
||||
var existingIds = Set<String>()
|
||||
@ -21,13 +22,15 @@ func requestContextResults(account: Account, botId: PeerId, query: String, peerI
|
||||
if !existingIds.contains(result.id) {
|
||||
newResults.append(result)
|
||||
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)
|
||||
} else {
|
||||
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)
|
||||
if collection.results.count > 10 {
|
||||
return .single(collection)
|
||||
|
Loading…
x
Reference in New Issue
Block a user