Pattern wallpaper improvements and bug fixes

This commit is contained in:
Ilya Laktyushin 2019-01-27 16:41:20 +03:00
parent 42cc137468
commit dfd335741b
28 changed files with 3440 additions and 2976 deletions

View File

@ -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 */,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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]) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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