Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
+29
View File
@@ -0,0 +1,29 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "TelegramPresentationData",
module_name = "TelegramPresentationData",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/TelegramCore:TelegramCore",
"//submodules/Postbox:Postbox",
"//submodules/Display:Display",
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
"//submodules/MediaResources:MediaResources",
"//submodules/AppBundle:AppBundle",
"//submodules/StringPluralization:StringPluralization",
"//submodules/Sunrise:Sunrise",
"//submodules/TinyThumbnail:TinyThumbnail",
"//submodules/FastBlur:FastBlur",
"//Telegram:PresentationStrings",
],
visibility = [
"//visibility:public",
],
)
@@ -0,0 +1,294 @@
import Foundation
import UIKit
import AsyncDisplayKit
import TelegramCore
import Display
import SwiftSignalKit
import Postbox
import MediaResources
import AppBundle
import TinyThumbnail
import FastBlur
private var backgroundImageForWallpaper: (TelegramWallpaper, Bool, UIImage)?
public func chatControllerBackgroundImage(theme: PresentationTheme?, wallpaper initialWallpaper: TelegramWallpaper, mediaBox: MediaBox, composed: Bool = true, knockoutMode: Bool, cached: Bool = true) -> UIImage? {
var wallpaper = initialWallpaper
if knockoutMode, let theme = theme {
switch theme.name {
case let .builtin(name):
switch name {
case .day, .night, .nightAccent:
wallpaper = theme.chat.defaultWallpaper
case .dayClassic:
break
}
case .custom:
break
}
}
var backgroundImage: UIImage?
if cached && composed && wallpaper == backgroundImageForWallpaper?.0, (wallpaper.settings?.blur ?? false) == backgroundImageForWallpaper?.1 {
backgroundImage = backgroundImageForWallpaper?.2
} else {
var succeed = true
switch wallpaper {
case .builtin, .emoticon:
if let filePath = getAppBundle().path(forResource: "ChatWallpaperBuiltin0", ofType: "jpg") {
backgroundImage = UIImage(contentsOfFile: filePath)?.precomposed()
}
case let .color(color):
backgroundImage = generateImage(CGSize(width: 1.0, height: 1.0), rotatedContext: { size, context in
context.setFillColor(UIColor(argb: color).withAlphaComponent(1.0).cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
})
case let .gradient(gradient):
backgroundImage = generateImage(CGSize(width: 640.0, height: 1280.0), rotatedContext: { size, context in
let gradientColors = [UIColor(argb: gradient.colors.count >= 1 ? gradient.colors[0] : 0).cgColor, UIColor(argb: gradient.colors.count >= 2 ? gradient.colors[1] : 0).cgColor] as CFArray
var locations: [CGFloat] = [0.0, 1.0]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let cgGradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
context.translateBy(x: 320.0, y: 640.0)
context.rotate(by: CGFloat(gradient.settings.rotation ?? 0) * CGFloat.pi / 180.0)
context.translateBy(x: -320.0, y: -640.0)
context.drawLinearGradient(cgGradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: [.drawsBeforeStartLocation, .drawsAfterEndLocation])
})
case let .image(representations, settings):
if let largest = largestImageRepresentation(representations) {
if settings.blur && composed {
var image: UIImage?
let _ = mediaBox.cachedResourceRepresentation(largest.resource, representation: CachedBlurredWallpaperRepresentation(), complete: true, fetch: true, attemptSynchronously: true).start(next: { data in
if data.complete {
image = UIImage(contentsOfFile: data.path)?.precomposed()
}
})
backgroundImage = image
}
if backgroundImage == nil, let path = mediaBox.completedResourcePath(largest.resource) {
succeed = false
backgroundImage = UIImage(contentsOfFile: path)?.precomposed()
}
}
case let .file(file):
if wallpaper.isPattern {
backgroundImage = nil
} else {
if file.settings.blur && composed {
var image: UIImage?
let _ = mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedBlurredWallpaperRepresentation(), complete: true, fetch: true, attemptSynchronously: true).start(next: { data in
if data.complete {
image = UIImage(contentsOfFile: data.path)?.precomposed()
}
})
backgroundImage = image
}
if backgroundImage == nil, let path = mediaBox.completedResourcePath(file.file.resource) {
succeed = false
backgroundImage = UIImage(contentsOfFile: path)?.precomposed()
}
}
}
if let backgroundImage = backgroundImage, composed && succeed {
backgroundImageForWallpaper = (wallpaper, (wallpaper.settings?.blur ?? false), backgroundImage)
}
}
return backgroundImage
}
private var signalBackgroundImageForWallpaper: (TelegramWallpaper, Bool, UIImage)?
public func chatControllerBackgroundImageSignal(wallpaper: TelegramWallpaper, mediaBox: MediaBox, accountMediaBox: MediaBox) -> Signal<(UIImage?, Bool)?, NoError> {
if wallpaper == signalBackgroundImageForWallpaper?.0, (wallpaper.settings?.blur ?? false) == signalBackgroundImageForWallpaper?.1, let image = signalBackgroundImageForWallpaper?.2 {
return .single((image, true))
} else {
func cacheWallpaper(_ image: UIImage?) {
if let image = image {
Queue.mainQueue().async {
signalBackgroundImageForWallpaper = (wallpaper, (wallpaper.settings?.blur ?? false), image)
}
}
}
switch wallpaper {
case .builtin, .emoticon:
if let filePath = getAppBundle().path(forResource: "ChatWallpaperBuiltin0", ofType: "jpg") {
return .single((UIImage(contentsOfFile: filePath)?.precomposed(), true))
|> afterNext { image in
cacheWallpaper(image?.0)
}
}
case let .color(color):
return .single((generateImage(CGSize(width: 1.0, height: 1.0), rotatedContext: { size, context in
context.setFillColor(UIColor(argb: color).withAlphaComponent(1.0).cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
}), true))
|> afterNext { image in
cacheWallpaper(image?.0)
}
case let .gradient(gradient):
return .single((generateImage(CGSize(width: 640.0, height: 1280.0).fitted(CGSize(width: 100.0, height: 100.0)), rotatedContext: { size, context in
let gradientColors = [UIColor(rgb: gradient.colors.count >= 1 ? gradient.colors[0] : 0).cgColor, UIColor(rgb: gradient.colors.count >= 2 ? gradient.colors[1] : 0).cgColor] as CFArray
var locations: [CGFloat] = [0.0, 1.0]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let cgGradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
context.rotate(by: CGFloat(gradient.settings.rotation ?? 0) * CGFloat.pi / 180.0)
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
context.drawLinearGradient(cgGradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: [.drawsBeforeStartLocation, .drawsAfterEndLocation])
}), true))
|> afterNext { image in
cacheWallpaper(image?.0)
}
case let .image(representations, settings):
if let largest = largestImageRepresentation(representations) {
if settings.blur {
return mediaBox.cachedResourceRepresentation(largest.resource, representation: CachedBlurredWallpaperRepresentation(), complete: true, fetch: true, attemptSynchronously: true)
|> map { data -> (UIImage?, Bool)? in
if data.complete {
return (UIImage(contentsOfFile: data.path)?.precomposed(), true)
} else {
return nil
}
}
|> afterNext { image in
cacheWallpaper(image?.0)
}
} else if let path = mediaBox.completedResourcePath(largest.resource) {
return .single((UIImage(contentsOfFile: path)?.precomposed(), true))
|> afterNext { image in
cacheWallpaper(image?.0)
}
}
}
case let .file(file):
if wallpaper.isPattern {
return .single((nil, true))
} else {
if file.settings.blur {
let representation = CachedBlurredWallpaperRepresentation()
if FileManager.default.fileExists(atPath: mediaBox.cachedRepresentationCompletePath(file.file.resource.id, representation: representation)) {
let effectiveMediaBox = mediaBox
return effectiveMediaBox.cachedResourceRepresentation(file.file.resource, representation: representation, complete: true, fetch: true, attemptSynchronously: true)
|> map { data -> (UIImage?, Bool)? in
if data.complete {
return (UIImage(contentsOfFile: data.path)?.precomposed(), true)
} else {
return nil
}
}
|> afterNext { image in
cacheWallpaper(image?.0)
}
} else {
return Signal { subscriber in
let fetch = fetchedMediaResource(mediaBox: accountMediaBox, userLocation: .other, userContentType: .other, reference: MediaResourceReference.wallpaper(wallpaper: WallpaperReference.slug(file.slug), resource: file.file.resource)).start()
var didOutputBlurred = false
let data = accountMediaBox.cachedResourceRepresentation(file.file.resource, representation: representation, complete: true, fetch: true, attemptSynchronously: true).start(next: { data in
if data.complete {
if let image = UIImage(contentsOfFile: data.path)?.precomposed() {
mediaBox.copyResourceData(file.file.resource.id, fromTempPath: data.path)
subscriber.putNext((image, true))
}
} else if !didOutputBlurred {
didOutputBlurred = true
if let immediateThumbnailData = file.file.immediateThumbnailData, let decodedData = decodeTinyThumbnail(data: immediateThumbnailData) {
if let thumbnailImage = UIImage(data: decodedData)?.precomposed() {
let thumbnailContextSize = CGSize(width: thumbnailImage.size.width * 6.0, height: thumbnailImage.size.height * 6.0)
guard let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) else {
subscriber.putNext((thumbnailImage, false))
return
}
thumbnailContext.withFlippedContext { c in
if let image = thumbnailImage.cgImage {
c.draw(image, in: CGRect(origin: CGPoint(), size: thumbnailContextSize))
}
}
telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes)
telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes)
if let blurredThumbnailImage = thumbnailContext.generateImage() {
subscriber.putNext((blurredThumbnailImage, false))
} else {
subscriber.putNext((thumbnailImage, false))
}
}
}
}
})
return ActionDisposable {
fetch.dispose()
data.dispose()
}
}
}
} else {
var path: String?
if let maybePath = mediaBox.completedResourcePath(file.file.resource) {
path = maybePath
} else if let maybePath = accountMediaBox.completedResourcePath(file.file.resource) {
path = maybePath
}
if let path = path {
return .single((UIImage(contentsOfFile: path)?.precomposed(), true))
|> afterNext { image in
cacheWallpaper(image?.0)
}
} else {
return Signal { subscriber in
let fetch = fetchedMediaResource(mediaBox: accountMediaBox, userLocation: .other, userContentType: .other, reference: MediaResourceReference.wallpaper(wallpaper: WallpaperReference.slug(file.slug), resource: file.file.resource)).start()
var didOutputBlurred = false
let data = accountMediaBox.resourceData(file.file.resource).start(next: { data in
if data.complete {
if let image = UIImage(contentsOfFile: data.path)?.precomposed() {
mediaBox.copyResourceData(file.file.resource.id, fromTempPath: data.path)
subscriber.putNext((image, true))
}
} else if !didOutputBlurred {
didOutputBlurred = true
if let immediateThumbnailData = file.file.immediateThumbnailData, let decodedData = decodeTinyThumbnail(data: immediateThumbnailData) {
if let thumbnailImage = UIImage(data: decodedData)?.precomposed() {
let thumbnailContextSize = CGSize(width: thumbnailImage.size.width * 6.0, height: thumbnailImage.size.height * 6.0)
guard let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) else {
subscriber.putNext((thumbnailImage, false))
return
}
thumbnailContext.withFlippedContext { c in
if let image = thumbnailImage.cgImage {
c.draw(image, in: CGRect(origin: CGPoint(), size: thumbnailContextSize))
}
}
telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes)
telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes)
if let blurredThumbnailImage = thumbnailContext.generateImage() {
subscriber.putNext((blurredThumbnailImage, false))
} else {
subscriber.putNext((thumbnailImage, false))
}
}
}
}
})
return ActionDisposable {
fetch.dispose()
data.dispose()
}
}
}
}
}
}
}
return .complete()
}
@@ -0,0 +1,531 @@
import Foundation
import UIKit
import Display
import TelegramCore
public enum MessageBubbleImageNeighbors {
case none
case top(side: Bool)
case bottom
case both
case side
case extracted
}
public func messageSingleBubbleLikeImage(fillColor: UIColor, strokeColor: UIColor) -> UIImage {
let diameter: CGFloat = 36.0
return generateImage(CGSize(width: 36.0, height: diameter), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
let lineWidth: CGFloat = 0.5
context.setFillColor(strokeColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
context.setFillColor(fillColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: lineWidth, y: lineWidth), size: CGSize(width: size.width - lineWidth * 2.0, height: size.height - lineWidth * 2.0)))
})!.stretchableImage(withLeftCapWidth: Int(diameter / 2.0), topCapHeight: Int(diameter / 2.0))
}
private let minRadiusForFullTailCorner: CGFloat = 14.0
func mediaBubbleCornerImage(incoming: Bool, radius: CGFloat, inset: CGFloat) -> UIImage {
let imageSize = CGSize(width: radius + 7.0, height: 8.0)
let fixedMainDiameter: CGFloat = 33.0
let formContext = DrawingContext(size: imageSize)!
formContext.withFlippedContext { context in
context.clear(CGRect(origin: CGPoint(), size: imageSize))
context.translateBy(x: imageSize.width / 2.0, y: imageSize.height / 2.0)
context.scaleBy(x: incoming ? -1.0 : 1.0, y: -1.0)
context.translateBy(x: -imageSize.width / 2.0, y: -imageSize.height / 2.0)
context.setFillColor(UIColor.black.cgColor)
let bottomEllipse = CGRect(origin: CGPoint(x: 24.0, y: 16.0), size: CGSize(width: 27.0, height: 17.0)).insetBy(dx: inset, dy: inset).offsetBy(dx: inset, dy: inset)
let topEllipse = CGRect(origin: CGPoint(x: 33.0, y: 14.0), size: CGSize(width: 23.0, height: 21.0)).insetBy(dx: -inset, dy: -inset).offsetBy(dx: inset, dy: inset)
context.translateBy(x: -fixedMainDiameter + imageSize.width - 6.0, y: -fixedMainDiameter + imageSize.height)
let topLeftRadius: CGFloat = 2.0
let topRightRadius: CGFloat = 2.0
let bottomLeftRadius: CGFloat = 2.0
let bottomRightRadius: CGFloat = radius
context.move(to: CGPoint(x: 0.0, y: topLeftRadius))
context.addArc(tangent1End: CGPoint(x: 0.0, y: 0.0), tangent2End: CGPoint(x: topLeftRadius, y: 0.0), radius: topLeftRadius)
context.addLine(to: CGPoint(x: fixedMainDiameter - topRightRadius, y: 0.0))
context.addArc(tangent1End: CGPoint(x: fixedMainDiameter, y: 0.0), tangent2End: CGPoint(x: fixedMainDiameter, y: topRightRadius), radius: topRightRadius)
context.addLine(to: CGPoint(x: fixedMainDiameter, y: fixedMainDiameter - bottomRightRadius))
context.addArc(tangent1End: CGPoint(x: fixedMainDiameter, y: fixedMainDiameter), tangent2End: CGPoint(x: fixedMainDiameter - bottomRightRadius, y: fixedMainDiameter), radius: bottomRightRadius)
context.addLine(to: CGPoint(x: bottomLeftRadius, y: fixedMainDiameter))
context.addArc(tangent1End: CGPoint(x: 0.0, y: fixedMainDiameter), tangent2End: CGPoint(x: 0.0, y: fixedMainDiameter - bottomLeftRadius), radius: bottomLeftRadius)
context.addLine(to: CGPoint(x: 0.0, y: topLeftRadius))
context.fillPath()
if radius >= minRadiusForFullTailCorner {
context.move(to: CGPoint(x: bottomEllipse.minX, y: bottomEllipse.midY))
context.addQuadCurve(to: CGPoint(x: bottomEllipse.midX, y: bottomEllipse.maxY), control: CGPoint(x: bottomEllipse.minX, y: bottomEllipse.maxY))
context.addQuadCurve(to: CGPoint(x: bottomEllipse.maxX, y: bottomEllipse.midY), control: CGPoint(x: bottomEllipse.maxX, y: bottomEllipse.maxY))
context.fillPath()
} else {
context.fill(CGRect(origin: CGPoint(x: bottomEllipse.minX - 5.0, y: bottomEllipse.midY), size: CGSize(width: bottomEllipse.width + 5.0, height: bottomEllipse.height / 2.0)))
}
context.fill(CGRect(origin: CGPoint(x: fixedMainDiameter / 2.0, y: floor(fixedMainDiameter / 2.0)), size: CGSize(width: fixedMainDiameter / 2.0, height: ceil(bottomEllipse.midY) - floor(fixedMainDiameter / 2.0))))
context.setFillColor(UIColor.clear.cgColor)
context.setBlendMode(.copy)
context.fillEllipse(in: topEllipse)
}
return formContext.generateImage()!
}
public func messageBubbleImage(maxCornerRadius: CGFloat, minCornerRadius: CGFloat, incoming: Bool, fillColor: UIColor, strokeColor: UIColor, neighbors: MessageBubbleImageNeighbors, theme: PresentationThemeChat, wallpaper: TelegramWallpaper, knockout knockoutValue: Bool, mask: Bool = false, extendedEdges: Bool = false, onlyOutline: Bool = false, onlyShadow: Bool = false, alwaysFillColor: Bool = false) -> UIImage {
let bubbleColors = incoming ? theme.message.incoming : theme.message.outgoing
return messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: incoming, fillColor: fillColor, strokeColor: strokeColor, neighbors: neighbors, shadow: bubbleColors.bubble.withWallpaper.shadow, wallpaper: wallpaper, knockout: knockoutValue, mask: mask, extendedEdges: extendedEdges, onlyOutline: onlyOutline, onlyShadow: onlyShadow, alwaysFillColor: alwaysFillColor)
}
public func messageBubbleArguments(maxCornerRadius: CGFloat, minCornerRadius: CGFloat, incoming: Bool, neighbors: MessageBubbleImageNeighbors) -> (topLeftRadius: CGFloat, topRightRadius: CGFloat, bottomLeftRadius: CGFloat, bottomRightRadius: CGFloat, drawTail: Bool) {
var topLeftRadius: CGFloat
var topRightRadius: CGFloat
var bottomLeftRadius: CGFloat
var bottomRightRadius: CGFloat
var drawTail: Bool
switch neighbors {
case .none:
topLeftRadius = maxCornerRadius
topRightRadius = maxCornerRadius
bottomLeftRadius = maxCornerRadius
bottomRightRadius = maxCornerRadius
drawTail = true
case .both:
topLeftRadius = maxCornerRadius
topRightRadius = minCornerRadius
bottomLeftRadius = maxCornerRadius
bottomRightRadius = minCornerRadius
drawTail = false
case .bottom:
topLeftRadius = maxCornerRadius
topRightRadius = minCornerRadius
bottomLeftRadius = maxCornerRadius
bottomRightRadius = maxCornerRadius
drawTail = true
case .side:
topLeftRadius = maxCornerRadius
topRightRadius = maxCornerRadius
bottomLeftRadius = minCornerRadius
bottomRightRadius = minCornerRadius
drawTail = false
case let .top(side):
topLeftRadius = maxCornerRadius
topRightRadius = maxCornerRadius
bottomLeftRadius = side ? minCornerRadius : maxCornerRadius
bottomRightRadius = minCornerRadius
drawTail = false
case .extracted:
topLeftRadius = maxCornerRadius
topRightRadius = maxCornerRadius
bottomLeftRadius = maxCornerRadius
bottomRightRadius = maxCornerRadius
drawTail = false
}
if incoming {
var tmp = topRightRadius
topRightRadius = topLeftRadius
topLeftRadius = tmp
tmp = bottomRightRadius
bottomRightRadius = bottomLeftRadius
bottomLeftRadius = tmp
}
return (topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius, drawTail)
}
public func messageBubbleImage(maxCornerRadius: CGFloat, minCornerRadius: CGFloat, incoming: Bool, fillColor: UIColor, strokeColor: UIColor, neighbors: MessageBubbleImageNeighbors, shadow: PresentationThemeBubbleShadow?, wallpaper: TelegramWallpaper, knockout knockoutValue: Bool, mask: Bool = false, extendedEdges: Bool = false, onlyOutline: Bool = false, onlyShadow: Bool = false, alwaysFillColor: Bool = false) -> UIImage {
let topLeftRadius: CGFloat
let topRightRadius: CGFloat
let bottomLeftRadius: CGFloat
let bottomRightRadius: CGFloat
let drawTail: Bool
switch neighbors {
case .none:
topLeftRadius = maxCornerRadius
topRightRadius = maxCornerRadius
bottomLeftRadius = maxCornerRadius
bottomRightRadius = maxCornerRadius
drawTail = true
case .both:
topLeftRadius = maxCornerRadius
topRightRadius = minCornerRadius
bottomLeftRadius = maxCornerRadius
bottomRightRadius = minCornerRadius
drawTail = false
case .bottom:
topLeftRadius = maxCornerRadius
topRightRadius = minCornerRadius
bottomLeftRadius = maxCornerRadius
bottomRightRadius = maxCornerRadius
drawTail = true
case .side:
topLeftRadius = maxCornerRadius
topRightRadius = maxCornerRadius
bottomLeftRadius = minCornerRadius
bottomRightRadius = minCornerRadius
drawTail = false
case let .top(side):
topLeftRadius = maxCornerRadius
topRightRadius = maxCornerRadius
bottomLeftRadius = side ? minCornerRadius : maxCornerRadius
bottomRightRadius = minCornerRadius
drawTail = false
case .extracted:
topLeftRadius = maxCornerRadius
topRightRadius = maxCornerRadius
bottomLeftRadius = maxCornerRadius
bottomRightRadius = maxCornerRadius
drawTail = false
}
let fixedMainDiameter: CGFloat = 33.0
let innerSize = CGSize(width: fixedMainDiameter + 6.0, height: fixedMainDiameter)
let strokeInset: CGFloat = 1.0
let sourceRawSize = CGSize(width: innerSize.width + strokeInset * 2.0, height: innerSize.height + strokeInset * 2.0)
let additionalInset: CGFloat = onlyShadow ? 10.0 : 1.0
let imageSize = CGSize(width: sourceRawSize.width + additionalInset * 2.0, height: sourceRawSize.height + additionalInset * 2.0)
let outgoingStretchPoint: (x: Int, y: Int) = (Int(additionalInset + strokeInset + round(fixedMainDiameter / 2.0)) - 1, Int(additionalInset + strokeInset + round(fixedMainDiameter / 2.0)))
let incomingStretchPoint: (x: Int, y: Int) = (Int(sourceRawSize.width) - outgoingStretchPoint.x + Int(additionalInset), outgoingStretchPoint.y)
let knockout = knockoutValue && !mask
let rawSize = imageSize
let bottomEllipse = CGRect(origin: CGPoint(x: 24.0, y: 16.0), size: CGSize(width: 27.0, height: 17.0))
let topEllipse = CGRect(origin: CGPoint(x: 33.0, y: 14.0), size: CGSize(width: 23.0, height: 21.0))
let formContext = DrawingContext(size: imageSize)!
formContext.withFlippedContext { context in
context.clear(CGRect(origin: CGPoint(), size: rawSize))
context.translateBy(x: additionalInset + strokeInset, y: additionalInset + strokeInset)
context.setFillColor(UIColor.black.cgColor)
context.move(to: CGPoint(x: 0.0, y: topLeftRadius))
context.addArc(tangent1End: CGPoint(x: 0.0, y: 0.0), tangent2End: CGPoint(x: topLeftRadius, y: 0.0), radius: topLeftRadius)
context.addLine(to: CGPoint(x: fixedMainDiameter - topRightRadius, y: 0.0))
context.addArc(tangent1End: CGPoint(x: fixedMainDiameter, y: 0.0), tangent2End: CGPoint(x: fixedMainDiameter, y: topRightRadius), radius: topRightRadius)
context.addLine(to: CGPoint(x: fixedMainDiameter, y: fixedMainDiameter - bottomRightRadius))
context.addArc(tangent1End: CGPoint(x: fixedMainDiameter, y: fixedMainDiameter), tangent2End: CGPoint(x: fixedMainDiameter - bottomRightRadius, y: fixedMainDiameter), radius: bottomRightRadius)
context.addLine(to: CGPoint(x: bottomLeftRadius, y: fixedMainDiameter))
context.addArc(tangent1End: CGPoint(x: 0.0, y: fixedMainDiameter), tangent2End: CGPoint(x: 0.0, y: fixedMainDiameter - bottomLeftRadius), radius: bottomLeftRadius)
context.addLine(to: CGPoint(x: 0.0, y: topLeftRadius))
context.fillPath()
if drawTail {
if maxCornerRadius >= minRadiusForFullTailCorner {
context.move(to: CGPoint(x: bottomEllipse.minX, y: bottomEllipse.midY))
context.addQuadCurve(to: CGPoint(x: bottomEllipse.midX, y: bottomEllipse.maxY), control: CGPoint(x: bottomEllipse.minX, y: bottomEllipse.maxY))
context.addQuadCurve(to: CGPoint(x: bottomEllipse.maxX, y: bottomEllipse.midY), control: CGPoint(x: bottomEllipse.maxX, y: bottomEllipse.maxY))
context.fillPath()
} else {
context.fill(CGRect(origin: CGPoint(x: bottomEllipse.minX - 2.0, y: bottomEllipse.midY), size: CGSize(width: bottomEllipse.width + 2.0, height: bottomEllipse.height / 2.0)))
}
context.fill(CGRect(origin: CGPoint(x: fixedMainDiameter / 2.0, y: floor(fixedMainDiameter / 2.0)), size: CGSize(width: fixedMainDiameter / 2.0, height: ceil(bottomEllipse.midY) - floor(fixedMainDiameter / 2.0))))
context.setFillColor(UIColor.clear.cgColor)
context.setBlendMode(.copy)
context.fillEllipse(in: topEllipse)
}
}
let formImage = formContext.generateImage()!
let outlineContext = DrawingContext(size: imageSize)!
outlineContext.withFlippedContext { context in
context.clear(CGRect(origin: CGPoint(), size: rawSize))
context.translateBy(x: additionalInset + strokeInset, y: additionalInset + strokeInset)
context.setStrokeColor(UIColor.black.cgColor)
let borderWidth: CGFloat
let borderOffset: CGFloat
let innerExtension: CGFloat
if knockout && !mask {
innerExtension = 0.25
} else {
innerExtension = 0.25
}
if abs(UIScreenPixel - 0.5) < CGFloat.ulpOfOne {
borderWidth = UIScreenPixel + innerExtension
borderOffset = -innerExtension / 2.0 + UIScreenPixel / 2.0
} else {
borderWidth = UIScreenPixel + innerExtension
borderOffset = -innerExtension / 2.0// + UIScreenPixel * 2.0 / 2.0
}
context.setLineWidth(borderWidth)
context.move(to: CGPoint(x: -borderOffset, y: topLeftRadius + borderOffset))
context.addArc(tangent1End: CGPoint(x: -borderOffset, y: -borderOffset), tangent2End: CGPoint(x: topLeftRadius + borderOffset, y: -borderOffset), radius: topLeftRadius + borderOffset * 2.0)
context.addLine(to: CGPoint(x: fixedMainDiameter - topRightRadius - borderOffset, y: -borderOffset))
context.addArc(tangent1End: CGPoint(x: fixedMainDiameter + borderOffset, y: -borderOffset), tangent2End: CGPoint(x: fixedMainDiameter + borderOffset, y: topRightRadius + borderOffset), radius: topRightRadius + borderOffset * 2.0)
context.addLine(to: CGPoint(x: fixedMainDiameter + borderOffset, y: fixedMainDiameter - bottomRightRadius - borderOffset))
context.addArc(tangent1End: CGPoint(x: fixedMainDiameter + borderOffset, y: fixedMainDiameter + borderOffset), tangent2End: CGPoint(x: fixedMainDiameter - bottomRightRadius - borderOffset, y: fixedMainDiameter + borderOffset), radius: bottomRightRadius + borderOffset * 2.0)
context.addLine(to: CGPoint(x: bottomLeftRadius + borderOffset, y: fixedMainDiameter + borderOffset))
context.addArc(tangent1End: CGPoint(x: -borderOffset, y: fixedMainDiameter + borderOffset), tangent2End: CGPoint(x: -borderOffset, y: fixedMainDiameter - bottomLeftRadius - borderOffset), radius: bottomLeftRadius + borderOffset * 2.0)
context.closePath()
context.strokePath()
if drawTail {
let outlineBottomEllipse = bottomEllipse.insetBy(dx: -borderOffset, dy: -borderOffset)
let outlineInnerTopEllipse = topEllipse.insetBy(dx: borderOffset, dy: borderOffset)
context.setBlendMode(.copy)
context.setFillColor(UIColor.clear.cgColor)
if maxCornerRadius >= minRadiusForFullTailCorner {
context.move(to: CGPoint(x: bottomEllipse.minX, y: bottomEllipse.midY))
context.addQuadCurve(to: CGPoint(x: bottomEllipse.midX, y: bottomEllipse.maxY), control: CGPoint(x: bottomEllipse.minX, y: bottomEllipse.maxY))
context.addQuadCurve(to: CGPoint(x: bottomEllipse.maxX, y: bottomEllipse.midY), control: CGPoint(x: bottomEllipse.maxX, y: bottomEllipse.maxY))
context.fillPath()
} else {
context.fill(CGRect(origin: CGPoint(x: bottomEllipse.minX - 2.0, y: floor(bottomEllipse.midY)), size: CGSize(width: bottomEllipse.width + 2.0, height: ceil(bottomEllipse.height / 2.0))))
}
context.fill(CGRect(origin: CGPoint(x: floor(fixedMainDiameter / 2.0), y: fixedMainDiameter / 2.0), size: CGSize(width: fixedMainDiameter / 2.0 + borderWidth, height: ceil(bottomEllipse.midY) - floor(fixedMainDiameter / 2.0))))
context.setBlendMode(.normal)
context.move(to: CGPoint(x: fixedMainDiameter + borderOffset, y: fixedMainDiameter / 2.0))
context.addLine(to: CGPoint(x: fixedMainDiameter + borderOffset, y: outlineBottomEllipse.midY))
context.strokePath()
let bubbleTailContext = DrawingContext(size: imageSize)!
bubbleTailContext.withFlippedContext { context in
context.clear(CGRect(origin: CGPoint(), size: rawSize))
context.translateBy(x: additionalInset + strokeInset, y: additionalInset + strokeInset)
context.setStrokeColor(UIColor.black.cgColor)
context.setLineWidth(borderWidth)
if maxCornerRadius >= minRadiusForFullTailCorner {
context.move(to: CGPoint(x: outlineBottomEllipse.minX, y: outlineBottomEllipse.midY))
context.addQuadCurve(to: CGPoint(x: outlineBottomEllipse.midX, y: outlineBottomEllipse.maxY), control: CGPoint(x: outlineBottomEllipse.minX, y: outlineBottomEllipse.maxY))
context.addQuadCurve(to: CGPoint(x: outlineBottomEllipse.maxX, y: outlineBottomEllipse.midY), control: CGPoint(x: outlineBottomEllipse.maxX, y: outlineBottomEllipse.maxY))
} else {
context.move(to: CGPoint(x: outlineBottomEllipse.minX - 2.0, y: outlineBottomEllipse.maxY))
context.addLine(to: CGPoint(x: outlineBottomEllipse.minX, y: outlineBottomEllipse.maxY))
context.addLine(to: CGPoint(x: outlineBottomEllipse.maxX, y: outlineBottomEllipse.maxY))
}
context.strokePath()
context.setFillColor(UIColor.clear.cgColor)
context.setBlendMode(.copy)
context.fillEllipse(in: outlineInnerTopEllipse)
context.move(to: CGPoint(x: 0.0, y: topLeftRadius))
context.addArc(tangent1End: CGPoint(x: 0.0, y: 0.0), tangent2End: CGPoint(x: topLeftRadius, y: 0.0), radius: topLeftRadius)
context.addLine(to: CGPoint(x: fixedMainDiameter - topRightRadius, y: 0.0))
context.addArc(tangent1End: CGPoint(x: fixedMainDiameter, y: 0.0), tangent2End: CGPoint(x: fixedMainDiameter, y: topRightRadius), radius: topRightRadius)
context.addLine(to: CGPoint(x: fixedMainDiameter, y: fixedMainDiameter - bottomRightRadius))
context.addArc(tangent1End: CGPoint(x: fixedMainDiameter, y: fixedMainDiameter), tangent2End: CGPoint(x: fixedMainDiameter - bottomRightRadius, y: fixedMainDiameter), radius: bottomRightRadius)
context.addLine(to: CGPoint(x: bottomLeftRadius, y: fixedMainDiameter))
context.addArc(tangent1End: CGPoint(x: 0.0, y: fixedMainDiameter), tangent2End: CGPoint(x: 0.0, y: fixedMainDiameter - bottomLeftRadius), radius: bottomLeftRadius)
context.addLine(to: CGPoint(x: 0.0, y: topLeftRadius))
context.fillPath()
let bottomEllipseMask = generateImage(bottomEllipse.insetBy(dx: -1.0, dy: -1.0).size, contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor.black.cgColor)
if maxCornerRadius >= minRadiusForFullTailCorner {
context.fillEllipse(in: CGRect(origin: CGPoint(x: 1.0 - borderOffset, y: 1.0 - borderOffset), size: CGSize(width: outlineBottomEllipse.width, height: outlineBottomEllipse.height)))
} else {
context.fill(CGRect(origin: CGPoint(x: 1.0 - borderOffset, y: 1.0 - borderOffset), size: CGSize(width: outlineBottomEllipse.width, height: outlineBottomEllipse.height)))
}
context.clear(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height / 2.0)))
})!
context.clip(to: bottomEllipse.insetBy(dx: -1.0, dy: -1.0), mask: bottomEllipseMask.cgImage!)
context.strokeEllipse(in: outlineInnerTopEllipse)
context.resetClip()
}
context.translateBy(x: -(additionalInset + strokeInset), y: -(additionalInset + strokeInset))
context.draw(bubbleTailContext.generateImage()!.cgImage!, in: CGRect(origin: CGPoint(), size: rawSize))
context.translateBy(x: additionalInset + strokeInset, y: additionalInset + strokeInset)
}
}
let outlineImage = generateImage(outlineContext.size, contextGenerator: { size, context in
context.setBlendMode(.copy)
let image = outlineContext.generateImage()!
context.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: size))
})!
let drawingContext = DrawingContext(size: imageSize)!
drawingContext.withFlippedContext { context in
if onlyShadow {
context.clear(CGRect(origin: CGPoint(), size: rawSize))
if let shadow = shadow {
context.translateBy(x: rawSize.width / 2.0, y: rawSize.height / 2.0)
context.scaleBy(x: incoming ? -1.0 : 1.0, y: -1.0)
context.translateBy(x: -rawSize.width / 2.0, y: -rawSize.height / 2.0)
context.setShadow(offset: CGSize(width: 0.0, height: -shadow.verticalOffset), blur: shadow.radius, color: shadow.color.cgColor)
context.draw(formImage.cgImage!, in: CGRect(origin: CGPoint(), size: rawSize))
context.setBlendMode(.copy)
context.setFillColor(UIColor.clear.cgColor)
context.clip(to: CGRect(origin: CGPoint(), size: rawSize), mask: formImage.cgImage!)
context.fill(CGRect(origin: CGPoint(), size: rawSize))
}
} else {
var drawWithClearColor = false
if knockout {
drawWithClearColor = !mask
if case let .color(color) = wallpaper {
context.setFillColor(UIColor(rgb: UInt32(color)).cgColor)
context.fill(CGRect(origin: CGPoint(), size: rawSize))
} else {
context.clear(CGRect(origin: CGPoint(), size: rawSize))
}
} else {
context.clear(CGRect(origin: CGPoint(), size: rawSize))
}
if drawWithClearColor {
context.setBlendMode(.copy)
context.setFillColor(UIColor.clear.cgColor)
} else {
context.setBlendMode(.normal)
context.setFillColor(fillColor.cgColor)
}
context.saveGState()
context.translateBy(x: rawSize.width / 2.0, y: rawSize.height / 2.0)
context.scaleBy(x: incoming ? -1.0 : 1.0, y: -1.0)
context.translateBy(x: -rawSize.width / 2.0, y: -rawSize.height / 2.0)
if !onlyOutline {
context.clip(to: CGRect(origin: CGPoint(), size: rawSize), mask: formImage.cgImage!)
context.fill(CGRect(origin: CGPoint(), size: rawSize))
if alwaysFillColor && drawWithClearColor {
context.setBlendMode(.normal)
context.setFillColor(fillColor.cgColor)
context.fill(CGRect(origin: CGPoint(), size: rawSize))
}
} else {
context.setFillColor(strokeColor.cgColor)
context.clip(to: CGRect(origin: CGPoint(), size: rawSize), mask: outlineImage.cgImage!)
context.fill(CGRect(origin: CGPoint(), size: rawSize))
}
context.restoreGState()
}
}
return drawingContext.generateImage()!.stretchableImage(withLeftCapWidth: incoming ? incomingStretchPoint.x : outgoingStretchPoint.x, topCapHeight: incoming ? incomingStretchPoint.y : outgoingStretchPoint.y)
}
public enum MessageBubbleActionButtonPosition {
case middle
case bottomLeft
case bottomRight
case bottomSingle
}
public func messageBubbleActionButtonImage(color: UIColor, strokeColor: UIColor, position: MessageBubbleActionButtonPosition, bubbleCorners: PresentationChatBubbleCorners) -> UIImage {
let largeRadius: CGFloat = bubbleCorners.mainRadius
let smallRadius: CGFloat = (bubbleCorners.mergeBubbleCorners && largeRadius >= 10.0) ? bubbleCorners.auxiliaryRadius : bubbleCorners.mainRadius
let size: CGSize
if case .middle = position {
size = CGSize(width: smallRadius + smallRadius, height: smallRadius + smallRadius)
} else {
size = CGSize(width: largeRadius + largeRadius, height: largeRadius + largeRadius)
}
return generateImage(size, contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
if case .bottomRight = position {
context.scaleBy(x: -1.0, y: -1.0)
} else {
context.scaleBy(x: 1.0, y: -1.0)
}
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
context.setBlendMode(.copy)
var effectiveStrokeColor: UIColor?
var strokeAlpha: CGFloat = 0.0
strokeColor.getRed(nil, green: nil, blue: nil, alpha: &strokeAlpha)
if !strokeAlpha.isZero {
effectiveStrokeColor = strokeColor
}
context.setFillColor(color.cgColor)
let lineWidth: CGFloat = 1.0
let halfLineWidth = lineWidth / 2.0
if let effectiveStrokeColor = effectiveStrokeColor {
context.setStrokeColor(effectiveStrokeColor.cgColor)
context.setLineWidth(lineWidth)
}
switch position {
case .middle:
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
if effectiveStrokeColor != nil {
context.setBlendMode(.normal)
context.strokeEllipse(in: CGRect(origin: CGPoint(x: halfLineWidth, y: halfLineWidth), size: CGSize(width: size.width - lineWidth, height: size.height - lineWidth)))
context.setBlendMode(.copy)
}
case .bottomLeft, .bottomRight:
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: smallRadius + smallRadius, height: smallRadius + smallRadius)))
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - smallRadius - smallRadius, y: 0.0), size: CGSize(width: smallRadius + smallRadius, height: smallRadius + smallRadius)))
context.fill(CGRect(origin: CGPoint(x: smallRadius, y: 0.0), size: CGSize(width: size.width - smallRadius - smallRadius, height: smallRadius + smallRadius)))
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: smallRadius + smallRadius, height: smallRadius + smallRadius)))
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - smallRadius - smallRadius, y: 0.0), size: CGSize(width: smallRadius + smallRadius, height: smallRadius + smallRadius)))
context.fill(CGRect(origin: CGPoint(x: smallRadius, y: 0.0), size: CGSize(width: size.width - smallRadius - smallRadius, height: smallRadius + smallRadius)))
context.fill(CGRect(origin: CGPoint(x: 0.0, y: smallRadius), size: CGSize(width: size.width, height: size.height - largeRadius - smallRadius)))
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - smallRadius - smallRadius, y: size.height - smallRadius - smallRadius), size: CGSize(width: smallRadius + smallRadius, height: smallRadius + smallRadius)))
context.fill(CGRect(origin: CGPoint(x: largeRadius, y: size.height - largeRadius - largeRadius), size: CGSize(width: size.width - smallRadius - largeRadius, height: largeRadius + largeRadius)))
context.fill(CGRect(origin: CGPoint(x: size.width - smallRadius, y: size.height - largeRadius), size: CGSize(width: smallRadius, height: largeRadius - smallRadius)))
if effectiveStrokeColor != nil {
context.setBlendMode(.normal)
context.beginPath()
context.move(to: CGPoint(x: halfLineWidth, y: smallRadius + halfLineWidth))
context.addArc(tangent1End: CGPoint(x: halfLineWidth, y: halfLineWidth), tangent2End: CGPoint(x: halfLineWidth + smallRadius, y: halfLineWidth), radius: smallRadius)
context.addLine(to: CGPoint(x: size.width - smallRadius, y: halfLineWidth))
context.addArc(tangent1End: CGPoint(x: size.width - halfLineWidth, y: halfLineWidth), tangent2End: CGPoint(x: size.width - halfLineWidth, y: halfLineWidth + smallRadius), radius: smallRadius)
context.addLine(to: CGPoint(x: size.width - halfLineWidth, y: size.height - halfLineWidth - smallRadius))
context.addArc(tangent1End: CGPoint(x: size.width - halfLineWidth, y: size.height - halfLineWidth), tangent2End: CGPoint(x: size.width - halfLineWidth - smallRadius, y: size.height - halfLineWidth), radius: smallRadius)
context.addLine(to: CGPoint(x: halfLineWidth + largeRadius, y: size.height - halfLineWidth))
context.addArc(tangent1End: CGPoint(x: halfLineWidth, y: size.height - halfLineWidth), tangent2End: CGPoint(x: halfLineWidth, y: size.height - halfLineWidth - largeRadius), radius: largeRadius)
context.closePath()
context.strokePath()
context.setBlendMode(.copy)
}
case .bottomSingle:
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: smallRadius + smallRadius, height: smallRadius + smallRadius)))
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - smallRadius - smallRadius, y: 0.0), size: CGSize(width: smallRadius + smallRadius, height: smallRadius + smallRadius)))
context.fill(CGRect(origin: CGPoint(x: smallRadius, y: 0.0), size: CGSize(width: size.width - smallRadius - smallRadius, height: smallRadius + smallRadius)))
context.fill(CGRect(origin: CGPoint(x: 0.0, y: smallRadius), size: CGSize(width: size.width, height: size.height - largeRadius - smallRadius)))
if effectiveStrokeColor != nil {
context.setBlendMode(.normal)
context.beginPath()
context.move(to: CGPoint(x: halfLineWidth, y: smallRadius + halfLineWidth))
context.addArc(tangent1End: CGPoint(x: halfLineWidth, y: halfLineWidth), tangent2End: CGPoint(x: halfLineWidth + smallRadius, y: halfLineWidth), radius: smallRadius)
context.addLine(to: CGPoint(x: size.width - smallRadius, y: halfLineWidth))
context.addArc(tangent1End: CGPoint(x: size.width - halfLineWidth, y: halfLineWidth), tangent2End: CGPoint(x: size.width - halfLineWidth, y: halfLineWidth + smallRadius), radius: smallRadius)
context.addLine(to: CGPoint(x: size.width - halfLineWidth, y: size.height - halfLineWidth - largeRadius))
context.addArc(tangent1End: CGPoint(x: size.width - halfLineWidth, y: size.height - halfLineWidth), tangent2End: CGPoint(x: size.width - halfLineWidth - largeRadius, y: size.height - halfLineWidth), radius: largeRadius)
context.addLine(to: CGPoint(x: halfLineWidth + largeRadius, y: size.height - halfLineWidth))
context.addArc(tangent1End: CGPoint(x: halfLineWidth, y: size.height - halfLineWidth), tangent2End: CGPoint(x: halfLineWidth, y: size.height - halfLineWidth - largeRadius), radius: largeRadius)
context.closePath()
context.strokePath()
context.setBlendMode(.copy)
}
}
})!.stretchableImage(withLeftCapWidth: Int(size.width / 2.0), topCapHeight: Int(size.height / 2.0))
}
@@ -0,0 +1,88 @@
import Foundation
import UIKit
import Display
import TelegramCore
import TelegramUIPreferences
public final class ChatPresentationThemeData: Equatable {
public let theme: PresentationTheme
public let wallpaper: TelegramWallpaper
public init(theme: PresentationTheme, wallpaper: TelegramWallpaper) {
self.theme = theme
self.wallpaper = wallpaper
}
public func withTheme(_ theme: PresentationTheme) -> ChatPresentationThemeData {
return ChatPresentationThemeData(theme: theme, wallpaper: self.wallpaper)
}
public static func ==(lhs: ChatPresentationThemeData, rhs: ChatPresentationThemeData) -> Bool {
return lhs.theme === rhs.theme && lhs.wallpaper == rhs.wallpaper
}
}
public final class ChatPresentationData {
public let theme: ChatPresentationThemeData
public let fontSize: PresentationFontSize
public let strings: PresentationStrings
public let dateTimeFormat: PresentationDateTimeFormat
public let nameDisplayOrder: PresentationPersonNameOrder
public let disableAnimations: Bool
public let largeEmoji: Bool
public let chatBubbleCorners: PresentationChatBubbleCorners
public let animatedEmojiScale: CGFloat
public let isPreview: Bool
public let messageFont: UIFont
public let messageEmojiFont: UIFont
public let messageBoldFont: UIFont
public let messageItalicFont: UIFont
public let messageBoldItalicFont: UIFont
public let messageFixedFont: UIFont
public let messageBlockQuoteFont: UIFont
public init(theme: ChatPresentationThemeData, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, disableAnimations: Bool, largeEmoji: Bool, chatBubbleCorners: PresentationChatBubbleCorners, animatedEmojiScale: CGFloat = 1.0, isPreview: Bool = false) {
self.theme = theme
self.fontSize = fontSize
self.strings = strings
self.dateTimeFormat = dateTimeFormat
self.nameDisplayOrder = nameDisplayOrder
self.disableAnimations = disableAnimations
self.chatBubbleCorners = chatBubbleCorners
self.largeEmoji = largeEmoji
self.isPreview = isPreview
let baseFontSize = fontSize.baseDisplaySize
self.messageFont = Font.regular(baseFontSize)
self.messageEmojiFont = Font.regular(53.0)
self.messageBoldFont = Font.bold(baseFontSize)
self.messageItalicFont = Font.italic(baseFontSize)
self.messageBoldItalicFont = Font.semiboldItalic(baseFontSize)
self.messageFixedFont = Font.monospace(baseFontSize)
self.messageBlockQuoteFont = Font.regular(baseFontSize - 1.0)
self.animatedEmojiScale = animatedEmojiScale
}
public func withTheme(_ theme: ChatPresentationThemeData) -> ChatPresentationData {
return ChatPresentationData(
theme: self.theme,
fontSize: self.fontSize,
strings: self.strings,
dateTimeFormat: self.dateTimeFormat,
nameDisplayOrder: self.nameDisplayOrder,
disableAnimations: self.disableAnimations,
largeEmoji: self.largeEmoji,
chatBubbleCorners: self.chatBubbleCorners,
animatedEmojiScale: self.animatedEmojiScale,
isPreview: self.isPreview
)
}
}
extension ChatPresentationData {
public convenience init(presentationData: PresentationData) {
self.init(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper), fontSize: presentationData.chatFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true, largeEmoji: presentationData.largeEmoji, chatBubbleCorners: presentationData.chatBubbleCorners)
}
}
@@ -0,0 +1,133 @@
import Foundation
import UIKit
import Display
import TelegramUIPreferences
import PresentationStrings
public extension PresentationFontSize {
init(systemFontSize: CGFloat) {
var closestIndex = 0
let allSizes = PresentationFontSize.allCases
for i in 0 ..< allSizes.count {
if abs(allSizes[i].baseDisplaySize - systemFontSize) < abs(allSizes[closestIndex].baseDisplaySize - systemFontSize) {
closestIndex = i
}
}
self = allSizes[closestIndex]
}
}
public extension PresentationFontSize {
var baseDisplaySize: CGFloat {
switch self {
case .extraSmall:
return 14.0
case .small:
return 15.0
case .medium:
return 16.0
case .regular:
return 17.0
case .large:
return 19.0
case .extraLarge:
return 23.0
case .extraLargeX2:
return 26.0
}
}
}
public extension ToolbarTheme {
convenience init(rootControllerTheme: PresentationTheme) {
let theme = rootControllerTheme.rootController.tabBar
self.init(barBackgroundColor: theme.backgroundColor, barSeparatorColor: theme.separatorColor, barTextColor: theme.textColor, barSelectedTextColor: theme.selectedTextColor)
}
}
public extension NavigationBarTheme {
convenience init(rootControllerTheme: PresentationTheme, enableBackgroundBlur: Bool = true, hideBackground: Bool = false, hideBadge: Bool = false, hideSeparator: Bool = false) {
let theme = rootControllerTheme.rootController.navigationBar
self.init(buttonColor: theme.buttonColor, disabledButtonColor: theme.disabledButtonColor, primaryTextColor: theme.primaryTextColor, backgroundColor: hideBackground ? .clear : theme.blurredBackgroundColor, opaqueBackgroundColor: hideBackground ? .clear : theme.opaqueBackgroundColor, enableBackgroundBlur: enableBackgroundBlur, separatorColor: hideBackground || hideSeparator ? .clear : theme.separatorColor, badgeBackgroundColor: hideBadge ? .clear : theme.badgeBackgroundColor, badgeStrokeColor: hideBadge ? .clear : theme.badgeStrokeColor, badgeTextColor: hideBadge ? .clear : theme.badgeTextColor)
}
}
public extension NavigationBarStrings {
convenience init(presentationStrings: PresentationStrings) {
self.init(back: presentationStrings.Common_Back, close: presentationStrings.Common_Close)
}
}
public extension NavigationBarPresentationData {
convenience init(presentationData: PresentationData) {
self.init(theme: NavigationBarTheme(rootControllerTheme: presentationData.theme), strings: NavigationBarStrings(presentationStrings: presentationData.strings))
}
convenience init(presentationData: PresentationData, hideBackground: Bool, hideBadge: Bool, hideSeparator: Bool = false) {
self.init(theme: NavigationBarTheme(rootControllerTheme: presentationData.theme, hideBackground: hideBackground, hideBadge: hideBadge, hideSeparator: hideSeparator), strings: NavigationBarStrings(presentationStrings: presentationData.strings))
}
convenience init(presentationTheme: PresentationTheme, presentationStrings: PresentationStrings) {
self.init(theme: NavigationBarTheme(rootControllerTheme: presentationTheme), strings: NavigationBarStrings(presentationStrings: presentationStrings))
}
}
public extension ActionSheetControllerTheme {
convenience init(presentationData: PresentationData) {
let presentationTheme = presentationData.theme
let actionSheet = presentationTheme.actionSheet
self.init(dimColor: actionSheet.dimColor, backgroundType: actionSheet.backgroundType == .light ? .light : .dark, itemBackgroundColor: actionSheet.itemBackgroundColor, itemHighlightedBackgroundColor: actionSheet.itemHighlightedBackgroundColor, standardActionTextColor: actionSheet.standardActionTextColor, destructiveActionTextColor: actionSheet.destructiveActionTextColor, disabledActionTextColor: actionSheet.disabledActionTextColor, primaryTextColor: actionSheet.primaryTextColor, secondaryTextColor: actionSheet.secondaryTextColor, controlAccentColor: actionSheet.controlAccentColor, controlColor: presentationTheme.list.disclosureArrowColor, switchFrameColor: presentationTheme.list.itemSwitchColors.frameColor, switchContentColor: presentationTheme.list.itemSwitchColors.contentColor, switchHandleColor: presentationTheme.list.itemSwitchColors.handleColor, baseFontSize: presentationData.listsFontSize.baseDisplaySize)
}
convenience init(presentationTheme: PresentationTheme, fontSize: PresentationFontSize) {
let actionSheet = presentationTheme.actionSheet
self.init(dimColor: actionSheet.dimColor, backgroundType: actionSheet.backgroundType == .light ? .light : .dark, itemBackgroundColor: actionSheet.itemBackgroundColor, itemHighlightedBackgroundColor: actionSheet.itemHighlightedBackgroundColor, standardActionTextColor: actionSheet.standardActionTextColor, destructiveActionTextColor: actionSheet.destructiveActionTextColor, disabledActionTextColor: actionSheet.disabledActionTextColor, primaryTextColor: actionSheet.primaryTextColor, secondaryTextColor: actionSheet.secondaryTextColor, controlAccentColor: actionSheet.controlAccentColor, controlColor: presentationTheme.list.disclosureArrowColor, switchFrameColor: presentationTheme.list.itemSwitchColors.frameColor, switchContentColor: presentationTheme.list.itemSwitchColors.contentColor, switchHandleColor: presentationTheme.list.itemSwitchColors.handleColor, baseFontSize: fontSize.baseDisplaySize)
}
}
public extension ActionSheetController {
convenience init(presentationData: PresentationData, allowInputInset: Bool = false) {
self.init(theme: ActionSheetControllerTheme(presentationData: presentationData), allowInputInset: allowInputInset)
}
}
public extension AlertControllerTheme {
convenience init(presentationTheme: PresentationTheme, fontSize: PresentationFontSize) {
let actionSheet = presentationTheme.actionSheet
self.init(backgroundType: actionSheet.backgroundType == .light ? .light : .dark, backgroundColor: actionSheet.itemBackgroundColor, separatorColor: actionSheet.itemHighlightedBackgroundColor, highlightedItemColor: actionSheet.itemHighlightedBackgroundColor, primaryColor: actionSheet.primaryTextColor, secondaryColor: actionSheet.secondaryTextColor, accentColor: actionSheet.controlAccentColor, contrastColor: presentationTheme.list.itemCheckColors.foregroundColor, destructiveColor: actionSheet.destructiveActionTextColor, disabledColor: actionSheet.disabledActionTextColor, controlBorderColor: presentationTheme.list.itemCheckColors.strokeColor, baseFontSize: fontSize.baseDisplaySize)
}
convenience init(presentationData: PresentationData) {
let presentationTheme = presentationData.theme
let actionSheet = presentationTheme.actionSheet
self.init(backgroundType: actionSheet.backgroundType == .light ? .light : .dark, backgroundColor: actionSheet.itemBackgroundColor, separatorColor: actionSheet.itemHighlightedBackgroundColor, highlightedItemColor: actionSheet.itemHighlightedBackgroundColor, primaryColor: actionSheet.primaryTextColor, secondaryColor: actionSheet.secondaryTextColor, accentColor: actionSheet.controlAccentColor, contrastColor: presentationData.theme.list.itemCheckColors.foregroundColor, destructiveColor: actionSheet.destructiveActionTextColor, disabledColor: actionSheet.disabledActionTextColor, controlBorderColor: presentationData.theme.list.itemCheckColors.strokeColor, baseFontSize: presentationData.listsFontSize.baseDisplaySize)
}
}
public extension NavigationControllerTheme {
convenience init(presentationTheme: PresentationTheme) {
let navigationStatusBar: NavigationStatusBarStyle
switch presentationTheme.rootController.statusBarStyle {
case .black:
navigationStatusBar = .black
case .white:
navigationStatusBar = .white
}
self.init(statusBar: navigationStatusBar, navigationBar: NavigationBarTheme(rootControllerTheme: presentationTheme), emptyAreaColor: presentationTheme.chatList.backgroundColor)
}
}
public extension PresentationThemeBubbleColorComponents {
var hasSingleFillColor: Bool {
if self.fill.count == 1 {
return true
}
for i in 0 ..< self.fill.count - 1 {
if self.fill[i].argb != self.fill[i + 1].argb {
return false
}
}
return true
}
}
@@ -0,0 +1,825 @@
import Foundation
import UIKit
import TelegramCore
import TelegramUIPreferences
public let defaultDarkPresentationTheme = makeDefaultDarkPresentationTheme(preview: false)
public let defaultDarkColorPresentationTheme = customizeDefaultDarkPresentationTheme(theme: defaultDarkPresentationTheme, editing: false, title: nil, accentColor: UIColor(rgb: 0x3e88f7), backgroundColors: [], bubbleColors: [], animateBubbleColors: false, wallpaper: nil, baseColor: nil)
private extension PresentationThemeBaseColor {
var colorWallpaper: (BuiltinWallpaperData, Int32, [UInt32])? {
switch self {
case .blue:
return nil
case .cyan:
return (.variant5, -30, [0xa4dbff, 0x009fdd, 0x527bdd])
case .green:
return (.variant3, -20, [0x7fa381, 0xfff5c5, 0x336f55, 0xfbe37d])
case .pink:
return (.variant9, -35, [0xe4b2ea, 0x8376c2, 0xeab9d9, 0xb493e6])
case .orange:
return (.variant2, -40, [0xfec496, 0xdd6cb9, 0x962fbf, 0x4f5bd5])
case .purple:
return (.variant6, -30, [0x8adbf2, 0x888dec, 0xe39fea, 0x679ced])
case .red:
return (.variant4, -35, [0xe4b2ea, 0x8376c2, 0xeab9d9, 0xb493e6])
case .yellow:
return (.variant1, -30, [0xeaa36e, 0xf0e486, 0xf29ebf, 0xe8c06e])
case .gray:
return nil
case .black:
return nil
case .white:
return nil
case .custom, .preset, .theme:
return nil
}
}
}
public func customizeDefaultDarkPresentationTheme(theme: PresentationTheme, editing: Bool, title: String?, accentColor: UIColor?, backgroundColors: [UInt32], bubbleColors: [UInt32], animateBubbleColors: Bool?, wallpaper forcedWallpaper: TelegramWallpaper? = nil, baseColor: PresentationThemeBaseColor? = nil) -> PresentationTheme {
if (theme.referenceTheme != .night) {
return theme
}
var intro = theme.intro
var rootController = theme.rootController
var list = theme.list
var chatList = theme.chatList
var chat = theme.chat
var actionSheet = theme.actionSheet
var bubbleColors = bubbleColors
var monochrome = false
if bubbleColors.isEmpty, editing {
let accentColor = accentColor ?? UIColor(rgb: 0xffffff)
if accentColor.rgb == 0xffffff {
monochrome = true
bubbleColors = [UIColor(rgb: 0x313131).rgb, UIColor(rgb: 0x313131).rgb]
} else if accentColor.rgb == 0x3e88f7 {
bubbleColors = [
0x0771ff,
0x9047ff,
0xa256bf,
].reversed()
} else {
bubbleColors = [accentColor.withMultiplied(hue: 0.966, saturation: 0.61, brightness: 0.98).rgb, accentColor.rgb]
}
} else {
let accentColor = accentColor ?? UIColor(rgb: 0xffffff)
if accentColor.rgb == 0xffffff {
monochrome = true
}
}
var badgeFillColor: UIColor?
var badgeTextColor: UIColor?
var secondaryBadgeTextColor: UIColor?
var accentColor = accentColor
if let initialAccentColor = accentColor {
if monochrome {
badgeFillColor = UIColor(rgb: 0xffffff)
badgeTextColor = UIColor(rgb: 0x000000)
secondaryBadgeTextColor = UIColor(rgb: 0x000000)
} else {
badgeFillColor = UIColor(rgb: 0xeb5545)
badgeTextColor = UIColor(rgb: 0xffffff)
if initialAccentColor.lightness > 0.735 {
secondaryBadgeTextColor = UIColor(rgb: 0x000000)
} else {
secondaryBadgeTextColor = UIColor(rgb: 0xffffff)
let hsb = initialAccentColor.hsb
accentColor = UIColor(hue: hsb.0, saturation: hsb.1, brightness: max(hsb.2, 0.55), alpha: 1.0)
}
}
intro = intro.withUpdated(accentTextColor: accentColor, startButtonColor: accentColor)
rootController = rootController.withUpdated(
tabBar: rootController.tabBar.withUpdated(selectedIconColor: accentColor, selectedTextColor: accentColor, badgeBackgroundColor: badgeFillColor, badgeTextColor: badgeTextColor),
navigationBar: rootController.navigationBar.withUpdated(buttonColor: accentColor, accentTextColor: accentColor, badgeBackgroundColor: badgeFillColor, badgeTextColor: badgeTextColor),
navigationSearchBar: rootController.navigationSearchBar.withUpdated(accentColor: accentColor)
)
list = list.withUpdated(
itemAccentColor: accentColor,
itemCheckColors: list.itemCheckColors.withUpdated(fillColor: accentColor, foregroundColor: secondaryBadgeTextColor),
itemBarChart: list.itemBarChart.withUpdated(color1: accentColor)
)
chatList = chatList.withUpdated(
checkmarkColor: accentColor,
unreadBadgeActiveBackgroundColor: accentColor,
unreadBadgeActiveTextColor: secondaryBadgeTextColor,
verifiedIconFillColor: accentColor,
verifiedIconForegroundColor: badgeTextColor
)
actionSheet = actionSheet.withUpdated(
standardActionTextColor: accentColor,
controlAccentColor: accentColor,
checkContentColor: secondaryBadgeTextColor
)
}
var defaultWallpaper: TelegramWallpaper?
if let forcedWallpaper = forcedWallpaper {
defaultWallpaper = forcedWallpaper
} else if let baseColor = baseColor, let (variant, intensity, colors) = baseColor.colorWallpaper, !colors.isEmpty {
defaultWallpaper = defaultBuiltinWallpaper(data: variant, colors: colors, intensity: intensity)
} else if !backgroundColors.isEmpty {
if backgroundColors.count >= 2 {
defaultWallpaper = .gradient(TelegramWallpaper.Gradient(id: nil, colors: backgroundColors, settings: WallpaperSettings()))
} else {
defaultWallpaper = .color(backgroundColors[0])
}
}
var outgoingBubbleFillColors: [UIColor]?
var outgoingPrimaryTextColor: UIColor?
var outgoingSecondaryTextColor: UIColor?
var outgoingLinkTextColor: UIColor?
var outgoingScamColor: UIColor?
var outgoingCheckColor: UIColor?
if !bubbleColors.isEmpty {
var topBubbleColor = UIColor(rgb: bubbleColors[0])
var bottomBubbleColor = UIColor(rgb: bubbleColors.last ?? bubbleColors[0])
if topBubbleColor.rgb != bottomBubbleColor.rgb {
let topBubbleColorLightness = topBubbleColor.lightness
let bottomBubbleColorLightness = bottomBubbleColor.lightness
if abs(topBubbleColorLightness - bottomBubbleColorLightness) > 0.7 {
if topBubbleColorLightness > bottomBubbleColorLightness {
topBubbleColor = topBubbleColor.withMultiplied(hue: 1.0, saturation: 1.0, brightness: 0.85)
} else {
bottomBubbleColor = bottomBubbleColor.withMultiplied(hue: 1.0, saturation: 1.0, brightness: 0.85)
}
}
}
outgoingBubbleFillColors = bubbleColors.map(UIColor.init(rgb:))
let lightnessColor = topBubbleColor.mixedWith(bottomBubbleColor, alpha: 0.5)
if lightnessColor.lightness > 0.735 {
outgoingPrimaryTextColor = UIColor(rgb: 0x000000)
outgoingSecondaryTextColor = UIColor(rgb: 0x000000, alpha: 0.5)
outgoingLinkTextColor = UIColor(rgb: 0x000000)
outgoingScamColor = UIColor(rgb: 0x000000)
outgoingCheckColor = UIColor(rgb: 0x000000, alpha: 0.5)
} else {
outgoingPrimaryTextColor = UIColor(rgb: 0xffffff)
outgoingSecondaryTextColor = UIColor(rgb: 0xffffff, alpha: 0.5)
outgoingLinkTextColor = UIColor(rgb: 0xffffff)
outgoingScamColor = UIColor(rgb: 0xffffff)
outgoingCheckColor = UIColor(rgb: 0xffffff)
}
}
var highlightedBubbleFillColor: UIColor?
if let outgoingBubbleFillColors {
let middleBubbleFillColor = outgoingBubbleFillColors[Int(floor(Float(outgoingBubbleFillColors.count) / 2))]
if middleBubbleFillColor.brightness > 0.98 {
highlightedBubbleFillColor = middleBubbleFillColor.withMultiplied(hue: 1.0, saturation: 0.87, brightness: 1.0)
} else {
highlightedBubbleFillColor = middleBubbleFillColor.withMultiplied(hue: 1.0, saturation: 1.1, brightness: 1.121)
}
}
chat = chat.withUpdated(
defaultWallpaper: defaultWallpaper,
animateMessageColors: animateBubbleColors,
message: chat.message.withUpdated(
incoming: chat.message.incoming.withUpdated(
bubble: chat.message.outgoing.bubble.withUpdated(
withWallpaper: chat.message.incoming.bubble.withWallpaper.withUpdated(
reactionInactiveBackground: UIColor(rgb: 0xffffff, alpha: 0.07),
reactionInactiveForeground: UIColor(rgb: 0xffffff),
reactionActiveBackground: accentColor,
reactionActiveForeground: monochrome ? UIColor(rgb: 0x000000) : UIColor(rgb: 0xffffff),
reactionInactiveMediaPlaceholder: UIColor(rgb: 0xffffff, alpha: 0.1),
reactionActiveMediaPlaceholder: UIColor(rgb: 0xffffff, alpha: 0.1)
),
withoutWallpaper: chat.message.incoming.bubble.withoutWallpaper.withUpdated(
reactionInactiveBackground: UIColor(rgb: 0xffffff, alpha: 0.07),
reactionInactiveForeground: UIColor(rgb: 0xffffff),
reactionActiveBackground: accentColor,
reactionActiveForeground: monochrome ? UIColor(rgb: 0x000000) : UIColor(rgb: 0xffffff),
reactionInactiveMediaPlaceholder: UIColor(rgb: 0xffffff, alpha: 0.1),
reactionActiveMediaPlaceholder: UIColor(rgb: 0xffffff, alpha: 0.1)
)
),
linkTextColor: accentColor,
linkHighlightColor: accentColor?.withAlphaComponent(0.5),
accentTextColor: accentColor,
accentControlColor: accentColor,
mediaActiveControlColor: accentColor,
mediaInactiveControlColor: accentColor?.withAlphaComponent(0.4),
fileTitleColor: accentColor,
polls: chat.message.incoming.polls.withUpdated(
radioProgress: accentColor,
highlight: UIColor(rgb: 0xffffff, alpha: 0.5),
bar: accentColor,
barIconForeground: accentColor.flatMap { accentColor -> UIColor in
if accentColor.rgb == 0xffffff {
return .clear
} else {
return .white
}
}
),
textSelectionColor: accentColor?.withAlphaComponent(0.2),
textSelectionKnobColor: accentColor
),
outgoing: chat.message.outgoing.withUpdated(
bubble: chat.message.outgoing.bubble.withUpdated(
withWallpaper: chat.message.outgoing.bubble.withWallpaper.withUpdated(
fill: outgoingBubbleFillColors,
highlightedFill: highlightedBubbleFillColor,
stroke: .clear,
reactionInactiveBackground: UIColor(rgb: 0xffffff, alpha: 0.12),
reactionInactiveForeground: UIColor(rgb: 0xffffff),
reactionActiveBackground: UIColor(rgb: 0xffffff),
reactionActiveForeground: UIColor(rgb: 0x000000, alpha: 0.0),
reactionInactiveMediaPlaceholder: UIColor(rgb: 0xffffff, alpha: 0.1),
reactionActiveMediaPlaceholder: UIColor(rgb: 0xffffff, alpha: 0.1)
),
withoutWallpaper: chat.message.outgoing.bubble.withoutWallpaper.withUpdated(
fill: outgoingBubbleFillColors,
highlightedFill: highlightedBubbleFillColor,
stroke: .clear,
reactionInactiveBackground: UIColor(rgb: 0xffffff, alpha: 0.12),
reactionInactiveForeground: UIColor(rgb: 0xffffff),
reactionActiveBackground: UIColor(rgb: 0xffffff),
reactionActiveForeground: UIColor(rgb: 0x000000, alpha: 0.0),
reactionInactiveMediaPlaceholder: UIColor(rgb: 0xffffff, alpha: 0.1),
reactionActiveMediaPlaceholder: UIColor(rgb: 0xffffff, alpha: 0.1)
)
),
primaryTextColor: outgoingPrimaryTextColor,
secondaryTextColor: outgoingSecondaryTextColor,
linkTextColor: outgoingLinkTextColor,
scamColor: outgoingScamColor,
accentTextColor: outgoingPrimaryTextColor,
accentControlColor: outgoingPrimaryTextColor,
mediaActiveControlColor: outgoingPrimaryTextColor,
mediaInactiveControlColor: outgoingSecondaryTextColor,
mediaControlInnerBackgroundColor: outgoingBubbleFillColors?.first,
pendingActivityColor: outgoingSecondaryTextColor,
fileTitleColor: outgoingPrimaryTextColor,
fileDescriptionColor: outgoingSecondaryTextColor,
fileDurationColor: outgoingSecondaryTextColor,
polls: chat.message.outgoing.polls.withUpdated(radioButton: outgoingPrimaryTextColor, radioProgress: outgoingPrimaryTextColor, highlight: outgoingPrimaryTextColor?.withAlphaComponent(0.12), separator: outgoingSecondaryTextColor, bar: outgoingPrimaryTextColor)
),
freeform: chat.message.freeform.withUpdated(
withWallpaper: chat.message.freeform.withWallpaper.withUpdated(
reactionInactiveBackground: UIColor(rgb: 0xffffff, alpha: 0.07),
reactionInactiveForeground: UIColor(rgb: 0xffffff),
reactionActiveBackground: accentColor,
reactionActiveForeground: monochrome ? UIColor(rgb: 0x000000) : UIColor(rgb: 0xffffff),
reactionInactiveMediaPlaceholder: UIColor(rgb: 0xffffff, alpha: 0.1),
reactionActiveMediaPlaceholder: UIColor(rgb: 0xffffff, alpha: 0.1)
),
withoutWallpaper: chat.message.freeform.withoutWallpaper.withUpdated(
reactionInactiveBackground: UIColor(rgb: 0xffffff, alpha: 0.07),
reactionInactiveForeground: UIColor(rgb: 0xffffff),
reactionActiveBackground: accentColor,
reactionActiveForeground: monochrome ? UIColor(rgb: 0x000000) : UIColor(rgb: 0xffffff),
reactionInactiveMediaPlaceholder: UIColor(rgb: 0xffffff, alpha: 0.1),
reactionActiveMediaPlaceholder: UIColor(rgb: 0xffffff, alpha: 0.1)
)
),
infoLinkTextColor: accentColor,
outgoingCheckColor: outgoingCheckColor,
selectionControlColors: chat.message.selectionControlColors.withUpdated(fillColor: accentColor, foregroundColor: badgeTextColor)
),
inputPanel: chat.inputPanel.withUpdated(
panelControlAccentColor: accentColor,
actionControlFillColor: accentColor,
actionControlForegroundColor: secondaryBadgeTextColor,
mediaRecordingControl: chat.inputPanel.mediaRecordingControl.withUpdated(
buttonColor: accentColor,
micLevelColor: accentColor?.withAlphaComponent(0.2),
activeIconColor: secondaryBadgeTextColor
)
),
historyNavigation: chat.historyNavigation.withUpdated(
badgeBackgroundColor: accentColor,
badgeStrokeColor: accentColor,
badgeTextColor: badgeTextColor
)
)
return PresentationTheme(
name: title.flatMap { .custom($0) } ?? theme.name,
index: theme.index,
referenceTheme: theme.referenceTheme,
overallDarkAppearance: theme.overallDarkAppearance,
intro: intro,
passcode: theme.passcode,
rootController: rootController,
list: list,
chatList: chatList,
chat: chat,
actionSheet: actionSheet,
contextMenu: theme.contextMenu,
inAppNotification: theme.inAppNotification,
chart: theme.chart,
preview: theme.preview
)
}
public let defaultDarkWallpaperGradientColors: [UIColor] = [
UIColor(rgb: 0x598bf6),
UIColor(rgb: 0x7a5eef),
UIColor(rgb: 0xd67cff),
UIColor(rgb: 0xf38b58)
]
public func makeDefaultDarkPresentationTheme(extendingThemeReference: PresentationThemeReference? = nil, preview: Bool) -> PresentationTheme {
let rootNavigationBar = PresentationThemeRootNavigationBar(
buttonColor: UIColor(rgb: 0xffffff),
disabledButtonColor: UIColor(rgb: 0x525252),
primaryTextColor: UIColor(rgb: 0xffffff),
secondaryTextColor: UIColor(rgb: 0xffffff, alpha: 0.5),
controlColor: UIColor(rgb: 0x767676),
accentTextColor: UIColor(rgb: 0xffffff),
blurredBackgroundColor: UIColor(rgb: 0x1d1d1d, alpha: 0.9),
opaqueBackgroundColor: UIColor(rgb: 0x1d1d1d).mixedWith(UIColor(rgb: 0x000000), alpha: 0.1),
separatorColor: UIColor(rgb: 0x545458, alpha: 0.55),
badgeBackgroundColor: UIColor(rgb: 0xffffff),
badgeStrokeColor: UIColor(rgb: 0x1c1c1d),
badgeTextColor: UIColor(rgb: 0x000000),
segmentedBackgroundColor: UIColor(rgb: 0xffffff, alpha: 0.11),
segmentedForegroundColor: UIColor(rgb: 0xffffff, alpha: 0.36),
segmentedTextColor: UIColor(rgb: 0xffffff),
segmentedDividerColor: UIColor(rgb: 0x505155),
clearButtonBackgroundColor: UIColor(rgb: 0xffffff, alpha: 0.1),
clearButtonForegroundColor: UIColor(rgb: 0xffffff)
)
let rootTabBar = PresentationThemeRootTabBar(
backgroundColor: rootNavigationBar.blurredBackgroundColor,
separatorColor: UIColor(rgb: 0x545458, alpha: 0.55),
iconColor: UIColor(rgb: 0xffffff),
selectedIconColor: UIColor(rgb: 0xffffff),
textColor: UIColor(rgb: 0xffffff),
selectedTextColor: UIColor(rgb: 0xffffff),
badgeBackgroundColor: UIColor(rgb: 0xffffff),
badgeStrokeColor: UIColor(rgb: 0x1c1c1d),
badgeTextColor: UIColor(rgb: 0x000000)
)
let navigationSearchBar = PresentationThemeNavigationSearchBar(
backgroundColor: UIColor(rgb: 0x1c1c1d),
accentColor: UIColor(rgb: 0xffffff),
inputFillColor: UIColor(rgb: 0x0f0f0f),
inputTextColor: UIColor(rgb: 0xffffff),
inputPlaceholderTextColor: UIColor(rgb: 0x8f8f8f),
inputIconColor: UIColor(rgb: 0x8f8f8f),
inputClearButtonColor: UIColor(rgb: 0x8f8f8f),
separatorColor: UIColor(rgb: 0x545458, alpha: 0.55)
)
let intro = PresentationThemeIntro(
statusBarStyle: .white,
primaryTextColor: UIColor(rgb: 0xffffff),
accentTextColor: UIColor(rgb: 0xffffff),
disabledTextColor: UIColor(rgb: 0x525252),
startButtonColor: UIColor(rgb: 0xffffff),
dotColor: UIColor(rgb: 0x5e5e5e)
)
let passcode = PresentationThemePasscode(
backgroundColors: PresentationThemeGradientColors(topColor: UIColor(rgb: 0x000000), bottomColor: UIColor(rgb: 0x000000)),
buttonColor: UIColor(rgb: 0x1c1c1d)
)
let rootController = PresentationThemeRootController(
statusBarStyle: .white,
tabBar: rootTabBar,
navigationBar: rootNavigationBar,
navigationSearchBar: navigationSearchBar,
keyboardColor: .dark
)
let switchColors = PresentationThemeSwitch(
frameColor: UIColor(rgb: 0x39393d),
handleColor: UIColor(rgb: 0x121212),
contentColor: UIColor(rgb: 0x67ce67),
positiveColor: UIColor(rgb: 0x08a723),
negativeColor: UIColor(rgb: 0xeb5545)
)
let list = PresentationThemeList(
blocksBackgroundColor: UIColor(rgb: 0x000000),
modalBlocksBackgroundColor: UIColor(rgb: 0x1c1c1d),
plainBackgroundColor: UIColor(rgb: 0x000000),
modalPlainBackgroundColor: UIColor(rgb: 0x1c1c1d),
itemPrimaryTextColor: UIColor(rgb: 0xffffff),
itemSecondaryTextColor: UIColor(rgb: 0x98989e),
itemDisabledTextColor: UIColor(rgb: 0x8f8f8f),
itemAccentColor: UIColor(rgb: 0xffffff),
itemHighlightedColor: UIColor(rgb: 0x28b772),
itemDestructiveColor: UIColor(rgb: 0xeb5545),
itemPlaceholderTextColor: UIColor(rgb: 0x4d4d4d),
itemBlocksBackgroundColor: UIColor(rgb: 0x1c1c1d),
itemModalBlocksBackgroundColor: UIColor(rgb: 0x2c2c2e),
itemHighlightedBackgroundColor: UIColor(rgb: 0x313135),
itemBlocksSeparatorColor: UIColor(rgb: 0x545458, alpha: 0.55),
itemPlainSeparatorColor: UIColor(rgb: 0x545458, alpha: 0.55),
disclosureArrowColor: UIColor(rgb: 0xffffff, alpha: 0.28),
sectionHeaderTextColor: UIColor(rgb: 0x8d8e93),
freeTextColor: UIColor(rgb: 0x8d8e93),
freeTextErrorColor: UIColor(rgb: 0xcf3030),
freeTextSuccessColor: UIColor(rgb: 0x30cf30),
freeMonoIconColor: UIColor(rgb: 0x8d8e93),
itemSwitchColors: switchColors,
itemDisclosureActions: PresentationThemeItemDisclosureActions(
neutral1: PresentationThemeFillForeground(fillColor: UIColor(rgb: 0x666666), foregroundColor: UIColor(rgb: 0xffffff)),
neutral2: PresentationThemeFillForeground(fillColor: UIColor(rgb: 0xcd7800), foregroundColor: UIColor(rgb: 0xffffff)),
destructive: PresentationThemeFillForeground(fillColor: UIColor(rgb: 0xc70c0c), foregroundColor: UIColor(rgb: 0xffffff)),
constructive: PresentationThemeFillForeground(fillColor: UIColor(rgb: 0x08a723), foregroundColor: UIColor(rgb: 0xffffff)),
accent: PresentationThemeFillForeground(fillColor: UIColor(rgb: 0x666666), foregroundColor: UIColor(rgb: 0xffffff)),
warning: PresentationThemeFillForeground(fillColor: UIColor(rgb: 0xcd7800), foregroundColor: UIColor(rgb: 0xffffff)),
inactive: PresentationThemeFillForeground(fillColor: UIColor(rgb: 0x666666), foregroundColor: UIColor(rgb: 0xffffff))
),
itemCheckColors: PresentationThemeFillStrokeForeground(
fillColor: UIColor(rgb: 0xffffff),
strokeColor: UIColor(rgb: 0xffffff, alpha: 0.3),
foregroundColor: UIColor(rgb: 0x000000)
),
controlSecondaryColor: UIColor(rgb: 0xffffff, alpha: 0.5),
freeInputField: PresentationInputFieldTheme(
backgroundColor: UIColor(rgb: 0x272728),
strokeColor: UIColor(rgb: 0x272728),
placeholderColor: UIColor(rgb: 0x98989e),
primaryColor: UIColor(rgb: 0xffffff),
controlColor: UIColor(rgb: 0x98989e)
),
freePlainInputField: PresentationInputFieldTheme(
backgroundColor: UIColor(rgb: 0x272728),
strokeColor: UIColor(rgb: 0x272728),
placeholderColor: UIColor(rgb: 0x98989e),
primaryColor: UIColor(rgb: 0xffffff),
controlColor: UIColor(rgb: 0x98989e)
),
mediaPlaceholderColor: UIColor(rgb: 0xffffff).mixedWith(UIColor(rgb: 0x1c1c1d), alpha: 0.9),
scrollIndicatorColor: UIColor(rgb: 0xffffff, alpha: 0.5),
pageIndicatorInactiveColor: UIColor(white: 1.0, alpha: 0.3),
inputClearButtonColor: UIColor(rgb: 0x8b9197),
itemBarChart: PresentationThemeItemBarChart(color1: UIColor(rgb: 0xffffff), color2: UIColor(rgb: 0x929196), color3: UIColor(rgb: 0x333333)),
itemInputField: PresentationInputFieldTheme(backgroundColor: UIColor(rgb: 0x0f0f0f), strokeColor: UIColor(rgb: 0x0f0f0f), placeholderColor: UIColor(rgb: 0x8f8f8f), primaryColor: UIColor(rgb: 0xffffff), controlColor: UIColor(rgb: 0x8f8f8f)),
paymentOption: PresentationThemeList.PaymentOption(
inactiveFillColor: UIColor(rgb: 0x00A650).withMultipliedAlpha(0.3),
inactiveForegroundColor: UIColor(rgb: 0x00A650),
activeFillColor: UIColor(rgb: 0x00A650),
activeForegroundColor: UIColor(rgb: 0xffffff)
)
)
let chatList = PresentationThemeChatList(
backgroundColor: UIColor(rgb: 0x000000),
itemSeparatorColor: UIColor(rgb: 0x545458, alpha: 0.55),
itemBackgroundColor: UIColor(rgb: 0x000000),
pinnedItemBackgroundColor: UIColor(rgb: 0x1c1c1d),
itemHighlightedBackgroundColor: UIColor(rgb: 0x121212),
pinnedItemHighlightedBackgroundColor: UIColor(rgb: 0x2b2b2c),
itemSelectedBackgroundColor: UIColor(rgb: 0x191919),
titleColor: UIColor(rgb: 0xffffff),
secretTitleColor: UIColor(rgb: 0x00b12c),
dateTextColor: UIColor(rgb: 0x8d8e93),
authorNameColor: UIColor(rgb: 0xffffff),
messageTextColor: UIColor(rgb: 0x8d8e93),
messageHighlightedTextColor: UIColor(rgb: 0xffffff),
messageDraftTextColor: UIColor(rgb: 0xdd4b39),
checkmarkColor: UIColor(rgb: 0xffffff),
pendingIndicatorColor: UIColor(rgb: 0xffffff),
failedFillColor: UIColor(rgb: 0xeb5545),
failedForegroundColor: UIColor(rgb: 0xffffff),
muteIconColor: UIColor(rgb: 0x8d8e93),
unreadBadgeActiveBackgroundColor: UIColor(rgb: 0xffffff),
unreadBadgeActiveTextColor: UIColor(rgb: 0x000000),
unreadBadgeInactiveBackgroundColor: UIColor(rgb: 0x666666),
unreadBadgeInactiveTextColor:UIColor(rgb: 0x000000),
reactionBadgeActiveBackgroundColor: UIColor(rgb: 0xFF2D55),
pinnedBadgeColor: UIColor(rgb: 0x767677),
pinnedSearchBarColor: UIColor(rgb: 0x272728),
regularSearchBarColor: UIColor(rgb: 0x272728),
sectionHeaderFillColor: UIColor(rgb: 0x1c1c1d),
sectionHeaderTextColor: UIColor(rgb: 0xffffff),
verifiedIconFillColor: UIColor(rgb: 0xffffff),
verifiedIconForegroundColor: UIColor(rgb: 0x000000),
secretIconColor: UIColor(rgb: 0x00b12c),
pinnedArchiveAvatarColor: PresentationThemeArchiveAvatarColors(backgroundColors: PresentationThemeGradientColors(topColor: UIColor(rgb: 0x72d5fd), bottomColor: UIColor(rgb: 0x2a9ef1)), foregroundColor: UIColor(rgb: 0xffffff)),
unpinnedArchiveAvatarColor: PresentationThemeArchiveAvatarColors(backgroundColors: PresentationThemeGradientColors(topColor: UIColor(rgb: 0x666666), bottomColor: UIColor(rgb: 0x666666)), foregroundColor: UIColor(rgb: 0x000000)),
onlineDotColor: UIColor(rgb: 0x4cc91f),
storyUnseenColors: PresentationThemeGradientColors(topColor: UIColor(rgb: 0x34C76F), bottomColor: UIColor(rgb: 0x3DA1FD)),
storyUnseenPrivateColors: PresentationThemeGradientColors(topColor: UIColor(rgb: 0x7CD636), bottomColor: UIColor(rgb: 0x26B470)),
storySeenColors: PresentationThemeGradientColors(topColor: UIColor(rgb: 0x48484A), bottomColor: UIColor(rgb: 0x48484A))
)
let incomingBubbleAlpha: CGFloat = 0.9
let message = PresentationThemeChatMessage(
incoming: PresentationThemePartedColors(
bubble: PresentationThemeBubbleColor(
withWallpaper: PresentationThemeBubbleColorComponents(
fill: [UIColor(rgb: 0x1D1D1D, alpha: incomingBubbleAlpha)],
highlightedFill: UIColor(rgb: 0xffffff, alpha: 0.35),
stroke: .clear,
shadow: nil,
reactionInactiveBackground: UIColor(rgb: 0xffffff, alpha: 0.07),
reactionInactiveForeground: UIColor(rgb: 0xffffff),
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
reactionActiveForeground: .clear,
reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2),
reactionStarsInactiveForeground: UIColor(rgb: 0xFFD738),
reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0),
reactionStarsActiveForeground: .white,
reactionInactiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1),
reactionActiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1)
),
withoutWallpaper: PresentationThemeBubbleColorComponents(
fill: [UIColor(rgb: 0x1D1D1D, alpha: incomingBubbleAlpha)],
highlightedFill: UIColor(rgb: 0xffffff, alpha: 0.35),
stroke: .clear,
shadow: nil,
reactionInactiveBackground: UIColor(rgb: 0xffffff, alpha: 0.07),
reactionInactiveForeground: UIColor(rgb: 0xffffff),
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
reactionActiveForeground: .clear,
reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2),
reactionStarsInactiveForeground: UIColor(rgb: 0xFFD738),
reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0),
reactionStarsActiveForeground: .white,
reactionInactiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1),
reactionActiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1)
)
),
primaryTextColor: UIColor(rgb: 0xffffff),
secondaryTextColor: UIColor(rgb: 0xffffff, alpha: 0.5), linkTextColor: UIColor(rgb: 0xffffff), linkHighlightColor: UIColor(rgb: 0xffffff, alpha: 0.5), scamColor: UIColor(rgb: 0xeb5545), textHighlightColor: UIColor(rgb: 0xf5c038), accentTextColor: UIColor(rgb: 0xffffff), accentControlColor: UIColor(rgb: 0xffffff), accentControlDisabledColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaActiveControlColor: UIColor(rgb: 0xffffff), mediaInactiveControlColor: UIColor(rgb: 0xffffff, alpha: 0.4), mediaControlInnerBackgroundColor: UIColor(rgb: 0x262628), pendingActivityColor: UIColor(rgb: 0xffffff, alpha: 0.5), fileTitleColor: UIColor(rgb: 0xffffff), fileDescriptionColor: UIColor(rgb: 0xffffff, alpha: 0.5), fileDurationColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaPlaceholderColor: UIColor(rgb: 0x1f1f1f).mixedWith(UIColor(rgb: 0xffffff), alpha: 0.05), polls: PresentationThemeChatBubblePolls(radioButton: UIColor(rgb: 0x737373), radioProgress: UIColor(rgb: 0xffffff), highlight: UIColor(rgb: 0xffffff, alpha: 0.5), separator: UIColor(rgb: 0x000000), bar: UIColor(rgb: 0xffffff), barIconForeground: .clear, barPositive: UIColor(rgb: 0x00A700), barNegative: UIColor(rgb: 0xFE3824)), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xb2b2b2, alpha: 0.18)), actionButtonsTextColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xffffff)), textSelectionColor: UIColor(rgb: 0xffffff, alpha: 0.2), textSelectionKnobColor: UIColor(rgb: 0xffffff)
),
outgoing: PresentationThemePartedColors(
bubble: PresentationThemeBubbleColor(
withWallpaper: PresentationThemeBubbleColorComponents(
fill: [UIColor(rgb: 0x61BCF9), UIColor(rgb: 0x0088ff)],
highlightedFill: UIColor(rgb: 0x61BCF9),
stroke: .clear,
shadow: nil,
reactionInactiveBackground: UIColor(rgb: 0xffffff, alpha: 0.1),
reactionInactiveForeground: UIColor(rgb: 0xffffff),
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
reactionActiveForeground: .clear,
reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2),
reactionStarsInactiveForeground: UIColor(rgb: 0xFFD738),
reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0),
reactionStarsActiveForeground: .white,
reactionInactiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1),
reactionActiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1)
),
withoutWallpaper: PresentationThemeBubbleColorComponents(
fill: [UIColor(rgb: 0x61BCF9), UIColor(rgb: 0x0088ff)],
highlightedFill: UIColor(rgb: 0x61BCF9),
stroke: .clear,
shadow: nil,
reactionInactiveBackground: UIColor(rgb: 0xffffff, alpha: 0.1),
reactionInactiveForeground: UIColor(rgb: 0xffffff),
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
reactionActiveForeground: .clear,
reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2),
reactionStarsInactiveForeground: UIColor(rgb: 0xFFD738),
reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0),
reactionStarsActiveForeground: .white,
reactionInactiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1),
reactionActiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1)
)
), primaryTextColor: UIColor(rgb: 0xffffff), secondaryTextColor: UIColor(rgb: 0xffffff, alpha: 0.5), linkTextColor: UIColor(rgb: 0xffffff), linkHighlightColor: UIColor(rgb: 0xffffff, alpha: 0.5), scamColor: UIColor(rgb: 0xeb5545), textHighlightColor: UIColor(rgb: 0xf5c038), accentTextColor: UIColor(rgb: 0xffffff), accentControlColor: UIColor(rgb: 0xffffff), accentControlDisabledColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaActiveControlColor: UIColor(rgb: 0xffffff), mediaInactiveControlColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaControlInnerBackgroundColor: UIColor(rgb: 0x313131), pendingActivityColor: UIColor(rgb: 0xffffff, alpha: 0.5), fileTitleColor: UIColor(rgb: 0xffffff), fileDescriptionColor: UIColor(rgb: 0xffffff, alpha: 0.5), fileDurationColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaPlaceholderColor: UIColor(rgb: 0xffffff, alpha: 0.2), polls: PresentationThemeChatBubblePolls(radioButton: UIColor(rgb: 0xffffff), radioProgress: UIColor(rgb: 0xffffff), highlight: UIColor(rgb: 0xffffff).withAlphaComponent(0.12), separator: UIColor(rgb: 0xffffff, alpha: 0.5), bar: UIColor(rgb: 0xffffff), barIconForeground: .clear, barPositive: UIColor(rgb: 0xffffff), barNegative: UIColor(rgb: 0xffffff)), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xb2b2b2, alpha: 0.18)), actionButtonsTextColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xffffff)), textSelectionColor: UIColor(rgb: 0xffffff, alpha: 0.2), textSelectionKnobColor: UIColor(rgb: 0xffffff)
),
freeform: PresentationThemeBubbleColor(
withWallpaper: PresentationThemeBubbleColorComponents(
fill: [UIColor(rgb: 0x1f1f1f)],
highlightedFill: UIColor(rgb: 0x2a2a2a),
stroke: UIColor(rgb: 0x1f1f1f),
shadow: nil,
reactionInactiveBackground: UIColor(rgb: 0xffffff, alpha: 0.07),
reactionInactiveForeground: UIColor(rgb: 0xffffff),
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
reactionActiveForeground: .clear,
reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2),
reactionStarsInactiveForeground: UIColor(rgb: 0xFFD738),
reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0),
reactionStarsActiveForeground: .white,
reactionInactiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1),
reactionActiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1)
),
withoutWallpaper: PresentationThemeBubbleColorComponents(
fill: [UIColor(rgb: 0x1f1f1f)],
highlightedFill: UIColor(rgb: 0x2a2a2a),
stroke: UIColor(rgb: 0x1f1f1f),
shadow: nil,
reactionInactiveBackground: UIColor(rgb: 0xffffff, alpha: 0.07),
reactionInactiveForeground: UIColor(rgb: 0xffffff),
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
reactionActiveForeground: .clear,
reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2),
reactionStarsInactiveForeground: UIColor(rgb: 0xFFD738),
reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0),
reactionStarsActiveForeground: .white,
reactionInactiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1),
reactionActiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1)
)
),
infoPrimaryTextColor: UIColor(rgb: 0xffffff),
infoLinkTextColor: UIColor(rgb: 0xffffff),
outgoingCheckColor: UIColor(rgb: 0xffffff),
mediaDateAndStatusFillColor: UIColor(white: 0.0, alpha: 0.3),
mediaDateAndStatusTextColor: UIColor(rgb: 0xffffff),
shareButtonFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)),
shareButtonStrokeColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0xb2b2b2, alpha: 0.18), withoutWallpaper: UIColor(rgb: 0xb2b2b2, alpha: 0.18)),
shareButtonForegroundColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0xffffff), withoutWallpaper: UIColor(rgb: 0xffffff)),
mediaOverlayControlColors: PresentationThemeFillForeground(fillColor: UIColor(rgb: 0x000000, alpha: 0.6), foregroundColor: UIColor(rgb: 0xffffff)),
selectionControlColors: PresentationThemeFillStrokeForeground(fillColor: UIColor(rgb: 0xffffff), strokeColor: UIColor(rgb: 0xffffff), foregroundColor: UIColor(rgb: 0x000000)),
deliveryFailedColors: PresentationThemeFillForeground(fillColor: UIColor(rgb: 0xeb5545), foregroundColor: UIColor(rgb: 0xffffff)),
mediaHighlightOverlayColor: UIColor(white: 1.0, alpha: 0.6),
stickerPlaceholderColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0xffffff, alpha: 0.1), withoutWallpaper: UIColor(rgb: 0xffffff, alpha: 0.1)),
stickerPlaceholderShimmerColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0xffffff, alpha: 0.1), withoutWallpaper: UIColor(rgb: 0xffffff, alpha: 0.1))
)
let serviceMessage = PresentationThemeServiceMessage(
components: PresentationThemeServiceMessageColor(withDefaultWallpaper: PresentationThemeServiceMessageColorComponents(fill: UIColor(rgb: 0x1f1f1f, alpha: 1.0), primaryText: UIColor(rgb: 0xffffff), linkHighlight: UIColor(rgb: 0xffffff, alpha: 0.12), scam: UIColor(rgb: 0xeb5545), dateFillStatic: UIColor(rgb: 0x000000, alpha: 0.2), dateFillFloating: UIColor(rgb: 0x000000, alpha: 0.2)), withCustomWallpaper: PresentationThemeServiceMessageColorComponents(fill: UIColor(rgb: 0x1f1f1f, alpha: 1.0), primaryText: UIColor(rgb: 0xffffff), linkHighlight: UIColor(rgb: 0xffffff, alpha: 0.12), scam: UIColor(rgb: 0xeb5545), dateFillStatic: UIColor(rgb: 0x000000, alpha: 0.2), dateFillFloating: UIColor(rgb: 0x000000, alpha: 0.2))),
unreadBarFillColor: UIColor(rgb: 0x1b1b1b),
unreadBarStrokeColor: UIColor(rgb: 0x1b1b1b),
unreadBarTextColor: UIColor(rgb: 0xffffff),
dateTextColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xffffff))
)
let inputPanelMediaRecordingControl = PresentationThemeChatInputPanelMediaRecordingControl(
buttonColor: UIColor(rgb: 0xffffff),
micLevelColor: UIColor(rgb: 0xffffff, alpha: 0.2),
activeIconColor: UIColor(rgb: 0x000000)
)
let inputPanel = PresentationThemeChatInputPanel(
panelBackgroundColor: rootNavigationBar.blurredBackgroundColor,
panelBackgroundColorNoWallpaper: UIColor(rgb: 0x000000),
panelSeparatorColor: UIColor(rgb: 0x545458, alpha: 0.55),
panelControlAccentColor: UIColor(rgb: 0xffffff),
panelControlColor: UIColor(rgb: 0xffffff),
panelControlDisabledColor: UIColor(rgb: 0x808080, alpha: 0.5),
panelControlDestructiveColor: UIColor(rgb: 0xff3b30),
inputBackgroundColor: UIColor(rgb: 0xffffff, alpha: 0.14).blitOver(.black, alpha: 1.0).withAlphaComponent(0.95),
inputStrokeColor: UIColor(rgb: 0xffffff, alpha: 0.1),
inputPlaceholderColor: UIColor(rgb: 0x7b7b7b),
inputTextColor: UIColor(rgb: 0xffffff),
inputControlColor: UIColor(rgb: 0xffffff, alpha: 0.5),
actionControlFillColor: UIColor(rgb: 0xffffff),
actionControlForegroundColor: UIColor(rgb: 0x000000),
primaryTextColor: UIColor(rgb: 0xffffff),
secondaryTextColor: UIColor(rgb: 0xffffff, alpha: 0.5),
mediaRecordingDotColor: UIColor(rgb: 0xeb5545),
mediaRecordingControl: inputPanelMediaRecordingControl
)
let inputMediaBackgroundColor = UIColor(rgb: 0xffffff, alpha: 0.14).blitOver(.black, alpha: 1.0).withAlphaComponent(0.95).withMultipliedAlpha(0.7)
let inputMediaPanel = PresentationThemeInputMediaPanel(
panelSeparatorColor: UIColor(rgb: 0x545458, alpha: 0.55),
panelIconColor: UIColor(rgb: 0x808080),
panelHighlightedIconBackgroundColor: UIColor(rgb: 0x808080).withMultipliedAlpha(0.25),
panelHighlightedIconColor: UIColor(rgb: 0x808080).mixedWith(UIColor(rgb: 0xffffff), alpha: 0.35),
panelContentVibrantOverlayColor: UIColor(rgb: 0x808080),
panelContentControlVibrantOverlayColor: UIColor(rgb: 0x808080).mixedWith(UIColor(rgb: 0x000000), alpha: 0.35),
panelContentControlVibrantSelectionColor: UIColor(white: 1.0, alpha: 0.1),
panelContentControlOpaqueOverlayColor: UIColor(white: 1.0, alpha: 0.1),
panelContentControlOpaqueSelectionColor: UIColor(white: 1.0, alpha: 0.1),
panelContentVibrantSearchOverlayColor: UIColor(rgb: 0x808080),
panelContentVibrantSearchOverlaySelectedColor: UIColor(rgb: 0x808080),
panelContentVibrantSearchOverlayHighlightColor: UIColor(rgb: 0x808080).withMultipliedAlpha(0.25),
panelContentOpaqueSearchOverlayColor: UIColor(rgb: 0x808080),
panelContentOpaqueSearchOverlaySelectedColor: UIColor(rgb: 0x808080),
panelContentOpaqueSearchOverlayHighlightColor: UIColor(rgb: 0x808080).withMultipliedAlpha(0.25),
stickersBackgroundColor: inputMediaBackgroundColor,
stickersSectionTextColor: UIColor(rgb: 0x7b7b7b),
stickersSearchBackgroundColor: UIColor(rgb: 0x1c1c1d),
stickersSearchPlaceholderColor: UIColor(rgb: 0x8d8e93),
stickersSearchPrimaryColor: UIColor(rgb: 0xffffff),
stickersSearchControlColor: UIColor(rgb: 0x8d8e93),
gifsBackgroundColor: inputMediaBackgroundColor,
backgroundColor: inputMediaBackgroundColor
)
let inputButtonPanel = PresentationThemeInputButtonPanel(
panelSeparatorColor: UIColor(rgb: 0x545458, alpha: 0.3),
panelBackgroundColor: UIColor(rgb: 0x141414, alpha: 0.85),
buttonFillColor: UIColor(rgb: 0xe9e9e9, alpha: 0.85),
buttonHighlightColor: UIColor(rgb: 0xffffff, alpha: 0.05),
buttonStrokeColor: UIColor(rgb: 0x000000, alpha: 0.85),
buttonHighlightedFillColor: UIColor(rgb: 0x5a5a5a, alpha: 0.7),
buttonHighlightedStrokeColor: UIColor(rgb: 0x0c0c0c),
buttonTextColor: UIColor(rgb: 0xffffff)
)
let historyNavigation = PresentationThemeChatHistoryNavigation(
fillColor: UIColor(rgb: 0x1c1c1d),
strokeColor: UIColor(rgb: 0x545458, alpha: 0.55),
foregroundColor: UIColor(rgb: 0xffffff),
badgeBackgroundColor: UIColor(rgb: 0xffffff),
badgeStrokeColor: UIColor(rgb: 0xffffff),
badgeTextColor: UIColor(rgb: 0x000000)
)
let defaultPatternWallpaper: TelegramWallpaper = defaultBuiltinWallpaper(data: .default, colors: defaultDarkWallpaperGradientColors.map(\.rgb), intensity: -34)
let chat = PresentationThemeChat(
defaultWallpaper: defaultPatternWallpaper,
animateMessageColors: false,
message: message,
serviceMessage: serviceMessage,
inputPanel: inputPanel,
inputMediaPanel: inputMediaPanel,
inputButtonPanel: inputButtonPanel,
historyNavigation: historyNavigation
)
let actionSheet = PresentationThemeActionSheet(
dimColor: UIColor(white: 0.0, alpha: 0.5),
backgroundType: .dark,
opaqueItemBackgroundColor: UIColor(rgb: 0x1c1c1d),
itemBackgroundColor: UIColor(rgb: 0x1c1c1d, alpha: 0.8),
opaqueItemHighlightedBackgroundColor: UIColor(white: 0.0, alpha: 1.0),
itemHighlightedBackgroundColor: UIColor(rgb: 0x000000, alpha: 0.5),
opaqueItemSeparatorColor: UIColor(rgb: 0x545458, alpha: 0.55),
standardActionTextColor: UIColor(rgb: 0xffffff),
destructiveActionTextColor: UIColor(rgb: 0xeb5545),
disabledActionTextColor: UIColor(rgb: 0x4d4d4d),
primaryTextColor: UIColor(rgb: 0xffffff),
secondaryTextColor: UIColor(rgb: 0x5e5e5e),
controlAccentColor: UIColor(rgb: 0xffffff),
inputBackgroundColor: UIColor(rgb: 0x0f0f0f),
inputHollowBackgroundColor: UIColor(rgb: 0x0f0f0f),
inputBorderColor: UIColor(rgb: 0x0f0f0f),
inputPlaceholderColor: UIColor(rgb: 0x8f8f8f),
inputTextColor: UIColor(rgb: 0xffffff),
inputClearButtonColor: UIColor(rgb: 0x8f8f8f),
checkContentColor: UIColor(rgb: 0x000000)
)
let contextMenu = PresentationThemeContextMenu(
dimColor: UIColor(rgb: 0x000000, alpha: 0.6),
backgroundColor: UIColor(rgb: 0x252525, alpha: 0.78),
itemSeparatorColor: UIColor(rgb: 0xffffff, alpha: 0.15),
sectionSeparatorColor: UIColor(rgb: 0x000000, alpha: 0.2),
itemBackgroundColor: UIColor(rgb: 0x000000, alpha: 0.0),
itemHighlightedBackgroundColor: UIColor(rgb: 0xffffff, alpha: 0.15),
primaryColor: UIColor(rgb: 0xffffff, alpha: 1.0),
secondaryColor: UIColor(rgb: 0xffffff, alpha: 0.5),
destructiveColor: UIColor(rgb: 0xeb5545),
badgeFillColor: UIColor(rgb: 0xffffff),
badgeForegroundColor: UIColor(rgb: 0x000000),
badgeInactiveFillColor: UIColor(rgb: 0xffffff).withAlphaComponent(0.5),
badgeInactiveForegroundColor: UIColor(rgb: 0x000000),
extractedContentTintColor: UIColor(rgb: 0xffffff, alpha: 1.0)
)
let inAppNotification = PresentationThemeInAppNotification(
fillColor: UIColor(rgb: 0x1c1c1d),
primaryTextColor: UIColor(rgb: 0xffffff),
expandedNotification: PresentationThemeExpandedNotification(
backgroundType: .dark,
navigationBar: PresentationThemeExpandedNotificationNavigationBar(
backgroundColor: UIColor(rgb: 0x1c1c1d),
primaryTextColor: UIColor(rgb: 0xffffff),
controlColor: UIColor(rgb: 0xffffff),
separatorColor: UIColor(rgb: 0x000000)
)
)
)
let chart = PresentationThemeChart(
labelsColor: UIColor(rgb: 0x8e8e93),
helperLinesColor: UIColor(rgb: 0xd8d8d8, alpha: 0.35),
strongLinesColor: UIColor(rgb: 0xd8d8d8, alpha: 0.35),
barStrongLinesColor: UIColor(rgb: 0xd8d8d8, alpha: 0.45),
detailsTextColor: UIColor(rgb: 0xffffff),
detailsArrowColor: UIColor(rgb: 0xd8d8d8),
detailsViewColor: UIColor(rgb: 0x000000),
rangeViewFrameColor: UIColor(rgb: 0x6d6d72),
rangeViewMarkerColor: UIColor(rgb: 0xffffff)
)
return PresentationTheme(
name: extendingThemeReference?.name ?? .builtin(.night),
index: extendingThemeReference?.index ?? PresentationThemeReference.builtin(.night).index,
referenceTheme: .night,
overallDarkAppearance: true,
intro: intro,
passcode: passcode,
rootController: rootController,
list: list,
chatList: chatList,
chat: chat,
actionSheet: actionSheet,
contextMenu: contextMenu,
inAppNotification: inAppNotification,
chart: chart,
preview: preview
)
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,125 @@
import Foundation
import AppBundle
import PresentationStrings
public typealias PresentationStrings = _PresentationStrings
public extension PresentationStrings {
typealias FormattedString = _FormattedString
typealias Component = _PresentationStringsComponent
}
public extension _FormattedString {
typealias Range = _FormattedStringRange
var _tuple: (String, [(Int, NSRange)]) {
return (self.string, self.ranges.map { item -> (Int, NSRange) in
return (item.index, item.range)
})
}
}
public func formatWithArgumentRanges(_ value: String, _ ranges: [(Int, NSRange)], _ arguments: [String]) -> (String, [(Int, NSRange)]) {
let string = value as NSString
var resultingRanges: [(Int, NSRange)] = []
var currentLocation = 0
let result = NSMutableString()
for (index, range) in ranges {
if currentLocation < range.location {
result.append(string.substring(with: NSRange(location: currentLocation, length: range.location - currentLocation)))
}
resultingRanges.append((index, NSRange(location: result.length, length: (arguments[index] as NSString).length)))
result.append(arguments[index])
currentLocation = range.location + range.length
}
if currentLocation != string.length {
result.append(string.substring(with: NSRange(location: currentLocation, length: string.length - currentLocation)))
}
return (result as String, resultingRanges)
}
public let defaultPresentationStrings = PresentationStrings(primaryComponent: PresentationStrings.Component(languageCode: "en", localizedName: "English", pluralizationRulesCode: nil, dict: NSDictionary(contentsOf: URL(fileURLWithPath: getAppBundle().path(forResource: "Localizable", ofType: "strings", inDirectory: nil, forLocalization: "en")!)) as! [String : String]), secondaryComponent: nil, groupingSeparator: "")
public func dataSizeString(_ size: Int, forceDecimal: Bool = false, formatting: DataSizeStringFormatting) -> String {
return dataSizeString(Int64(size), forceDecimal: forceDecimal, formatting: formatting)
}
public struct DataSizeStringFormatting {
let decimalSeparator: String
let byte: (String) -> PresentationStrings.FormattedString
let kilobyte: (String) -> PresentationStrings.FormattedString
let megabyte: (String) -> PresentationStrings.FormattedString
let gigabyte: (String) -> PresentationStrings.FormattedString
public init(
decimalSeparator: String,
byte: @escaping (String) -> PresentationStrings.FormattedString,
kilobyte: @escaping (String) -> PresentationStrings.FormattedString,
megabyte: @escaping (String) -> PresentationStrings.FormattedString,
gigabyte: @escaping (String) -> PresentationStrings.FormattedString
) {
self.decimalSeparator = decimalSeparator
self.byte = byte
self.kilobyte = kilobyte
self.megabyte = megabyte
self.gigabyte = gigabyte
}
}
public func dataSizeString(_ size: Int64, forceDecimal: Bool = false, formatting: DataSizeStringFormatting) -> String {
if size >= 1024 * 1024 * 1024 {
let remainder = Int64((Double(size % (1024 * 1024 * 1024)) / (1024 * 1024 * 102.4)).rounded(.down))
if remainder != 0 || forceDecimal {
return formatting.gigabyte("\(size / (1024 * 1024 * 1024))\(formatting.decimalSeparator)\(remainder)").string
} else {
return formatting.gigabyte("\(size / (1024 * 1024 * 1024))").string
}
} else if size >= 1024 * 1024 {
let remainder = Int64((Double(size % (1024 * 1024)) / (1024.0 * 102.4)).rounded(.down))
if remainder != 0 || forceDecimal {
return formatting.megabyte( "\(size / (1024 * 1024))\(formatting.decimalSeparator)\(remainder)").string
} else {
return formatting.megabyte("\(size / (1024 * 1024))").string
}
} else if size >= 1024 {
let remainder = (size % (1024)) / (102)
if remainder != 0 || forceDecimal {
return formatting.kilobyte("\(size / 1024)\(formatting.decimalSeparator)\(remainder)").string
} else {
return formatting.kilobyte("\(size / 1024)").string
}
} else {
return formatting.byte("\(size)").string
}
}
public func countString(_ count: Int64, forceDecimal: Bool = false) -> String {
let decimalSeparator = "."
if count >= 1000 * 1000 * 1000 {
let remainder = Int64((Double(count % (1000 * 1000 * 1000)) / (1000 * 1000 * 100.0)).rounded(.down))
if remainder != 0 || forceDecimal {
return "\(count / (1000 * 1000 * 1000))\(decimalSeparator)\(remainder)T"
} else {
return "\(count / (1000 * 1000 * 1000))T"
}
} else if count >= 1000 * 1000 {
let remainder = Int64((Double(count % (1000 * 1000)) / (1000.0 * 100.0)).rounded(.down))
if remainder != 0 || forceDecimal {
return "\(count / (1000 * 1000))\(decimalSeparator)\(remainder)M"
} else {
return "\(count / (1000 * 1000))M"
}
} else if count >= 1000 {
let remainder = (count % (1000)) / (102)
if remainder != 0 || forceDecimal {
return "\(count / 1000)\(decimalSeparator)\(remainder)K"
} else {
return "\(count / 1000)K"
}
} else {
return "\(count)"
}
}
@@ -0,0 +1,134 @@
import Foundation
import UIKit
import Postbox
import TelegramUIPreferences
import TelegramCore
public func makeDefaultPresentationTheme(reference: PresentationBuiltinThemeReference, extendingThemeReference: PresentationThemeReference? = nil, serviceBackgroundColor: UIColor?, preview: Bool = false) -> PresentationTheme {
let theme: PresentationTheme
switch reference {
case .dayClassic:
theme = makeDefaultDayPresentationTheme(extendingThemeReference: extendingThemeReference, serviceBackgroundColor: serviceBackgroundColor, day: false, preview: preview)
case .day:
theme = makeDefaultDayPresentationTheme(extendingThemeReference: extendingThemeReference, serviceBackgroundColor: serviceBackgroundColor, day: true, preview: preview)
case .night:
theme = makeDefaultDarkPresentationTheme(extendingThemeReference: extendingThemeReference, preview: preview)
case .nightAccent:
theme = makeDefaultDarkTintedPresentationTheme(extendingThemeReference: extendingThemeReference, preview: preview)
}
return theme
}
public func customizePresentationTheme(_ theme: PresentationTheme, editing: Bool, title: String? = nil, accentColor: UIColor?, outgoingAccentColor: UIColor?, backgroundColors: [UInt32], bubbleColors: [UInt32], animateBubbleColors: Bool?, wallpaper: TelegramWallpaper? = nil, baseColor: PresentationThemeBaseColor? = nil) -> PresentationTheme {
if accentColor == nil && bubbleColors.isEmpty && backgroundColors.isEmpty && wallpaper == nil {
return theme
}
switch theme.referenceTheme {
case .day, .dayClassic:
return customizeDefaultDayTheme(theme: theme, editing: editing, title: title, accentColor: accentColor, outgoingAccentColor: outgoingAccentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, animateBubbleColors: animateBubbleColors ?? false, wallpaper: wallpaper, serviceBackgroundColor: nil)
case .night:
return customizeDefaultDarkPresentationTheme(theme: theme, editing: editing, title: title, accentColor: accentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, animateBubbleColors: animateBubbleColors ?? false, wallpaper: wallpaper, baseColor: baseColor)
case .nightAccent:
return customizeDefaultDarkTintedPresentationTheme(theme: theme, editing: editing, title: title, accentColor: accentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, animateBubbleColors: animateBubbleColors ?? false, wallpaper: wallpaper, baseColor: baseColor)
}
}
public func makePresentationTheme(settings: TelegramThemeSettings, title: String? = nil, serviceBackgroundColor: UIColor? = nil) -> PresentationTheme? {
let defaultTheme = makeDefaultPresentationTheme(reference: PresentationBuiltinThemeReference(baseTheme: settings.baseTheme), extendingThemeReference: nil, serviceBackgroundColor: serviceBackgroundColor, preview: false)
return customizePresentationTheme(defaultTheme, editing: true, title: title, accentColor: UIColor(argb: settings.accentColor), outgoingAccentColor: settings.outgoingAccentColor.flatMap { UIColor(argb: $0) }, backgroundColors: [], bubbleColors: settings.messageColors, animateBubbleColors: settings.animateMessageColors, wallpaper: settings.wallpaper)
}
public func makePresentationTheme(cloudTheme: TelegramTheme, dark: Bool = false) -> PresentationTheme? {
let settings: TelegramThemeSettings?
if let exactSettings = cloudTheme.settings?.first(where: { dark ? ($0.baseTheme == .night || $0.baseTheme == .tinted) : ($0.baseTheme == .classic || $0.baseTheme == .day) }) {
settings = exactSettings
} else if let firstSettings = cloudTheme.settings?.first {
settings = firstSettings
} else {
settings = nil
}
guard let settings else {
return nil
}
let defaultTheme = makeDefaultPresentationTheme(reference: PresentationBuiltinThemeReference(baseTheme: settings.baseTheme), extendingThemeReference: .cloud(PresentationCloudTheme(theme: cloudTheme, resolvedWallpaper: nil, creatorAccountId: nil)), serviceBackgroundColor: nil, preview: false)
return customizePresentationTheme(defaultTheme, editing: true, accentColor: UIColor(argb: settings.accentColor), outgoingAccentColor: settings.outgoingAccentColor.flatMap { UIColor(argb: $0) }, backgroundColors: [], bubbleColors: settings.messageColors, animateBubbleColors: settings.animateMessageColors, wallpaper: settings.wallpaper)
}
public func makePresentationTheme(chatTheme: ChatTheme, dark: Bool = false) -> PresentationTheme? {
guard case let .gift(_, themeSettings) = chatTheme else {
return nil
}
let settings: TelegramThemeSettings?
if let exactSettings = themeSettings.first(where: { dark ? ($0.baseTheme == .night || $0.baseTheme == .tinted) : ($0.baseTheme == .classic || $0.baseTheme == .day) }) {
settings = exactSettings
} else if let firstSettings = themeSettings.first {
settings = firstSettings
} else {
settings = nil
}
guard let settings else {
return nil
}
let defaultTheme = makeDefaultPresentationTheme(reference: PresentationBuiltinThemeReference(baseTheme: settings.baseTheme), serviceBackgroundColor: nil, preview: false)
let theme = customizePresentationTheme(defaultTheme, editing: false, accentColor: UIColor(rgb: settings.accentColor), outgoingAccentColor: settings.outgoingAccentColor.flatMap { UIColor(rgb: $0) }, backgroundColors: [], bubbleColors: settings.messageColors, animateBubbleColors: settings.animateMessageColors, wallpaper: settings.wallpaper)
if case let .gift(starGiftValue, _) = chatTheme {
theme.starGift = starGiftValue
}
return theme
}
public func makePresentationTheme(cloudTheme: TelegramTheme, baseTheme: TelegramBaseTheme? = nil) -> PresentationTheme? {
let settings: TelegramThemeSettings?
if let exactSettings = cloudTheme.settings?.first(where: { $0.baseTheme == baseTheme }) {
settings = exactSettings
} else if let firstSettings = cloudTheme.settings?.first {
settings = firstSettings
} else {
settings = nil
}
guard let settings = settings else {
return nil
}
let defaultTheme = makeDefaultPresentationTheme(reference: PresentationBuiltinThemeReference(baseTheme: settings.baseTheme), extendingThemeReference: nil, serviceBackgroundColor: nil, preview: false)
return customizePresentationTheme(defaultTheme, editing: true, accentColor: UIColor(argb: settings.accentColor), outgoingAccentColor: settings.outgoingAccentColor.flatMap { UIColor(argb: $0) }, backgroundColors: [], bubbleColors: settings.messageColors, animateBubbleColors: settings.animateMessageColors, wallpaper: settings.wallpaper)
}
public func makePresentationTheme(mediaBox: MediaBox, themeReference: PresentationThemeReference, baseTheme: TelegramBaseTheme? = nil, extendingThemeReference: PresentationThemeReference? = nil, accentColor: UIColor? = nil, outgoingAccentColor: UIColor? = nil, backgroundColors: [UInt32] = [], bubbleColors: [UInt32] = [], animateBubbleColors: Bool? = nil, wallpaper: TelegramWallpaper? = nil, baseColor: PresentationThemeBaseColor? = nil, serviceBackgroundColor: UIColor? = nil, preview: Bool = false) -> PresentationTheme? {
var accentColor = accentColor
if accentColor == .clear {
accentColor = nil
}
let theme: PresentationTheme
switch themeReference {
case let .builtin(reference):
let defaultTheme = makeDefaultPresentationTheme(reference: reference, extendingThemeReference: extendingThemeReference, serviceBackgroundColor: serviceBackgroundColor, preview: preview)
theme = customizePresentationTheme(defaultTheme, editing: true, accentColor: accentColor, outgoingAccentColor: outgoingAccentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, animateBubbleColors: animateBubbleColors, wallpaper: wallpaper, baseColor: baseColor)
case let .local(info):
if let path = mediaBox.completedResourcePath(info.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead), let loadedTheme = makePresentationTheme(data: data, themeReference: themeReference, resolvedWallpaper: info.resolvedWallpaper) {
theme = customizePresentationTheme(loadedTheme, editing: false, accentColor: accentColor, outgoingAccentColor: outgoingAccentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, animateBubbleColors: animateBubbleColors, wallpaper: wallpaper)
} else {
return nil
}
case let .cloud(info):
let settings: TelegramThemeSettings?
if let exactSettings = info.theme.settings?.first(where: { $0.baseTheme == baseTheme }) {
settings = exactSettings
} else if let firstSettings = info.theme.settings?.first {
settings = firstSettings
} else {
settings = nil
}
if let settings = settings {
if let loadedTheme = makePresentationTheme(mediaBox: mediaBox, themeReference: .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)), extendingThemeReference: themeReference, accentColor: accentColor ?? UIColor(argb: settings.accentColor), outgoingAccentColor: outgoingAccentColor ?? settings.outgoingAccentColor.flatMap { UIColor(argb: $0) }, backgroundColors: [], bubbleColors: bubbleColors.isEmpty ? settings.messageColors : bubbleColors, animateBubbleColors: animateBubbleColors ?? settings.animateMessageColors, wallpaper: wallpaper ?? settings.wallpaper, serviceBackgroundColor: serviceBackgroundColor, preview: preview) {
theme = loadedTheme
} else {
return nil
}
} else if let file = info.theme.file, let path = mediaBox.completedResourcePath(file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead), let loadedTheme = makePresentationTheme(data: data, themeReference: themeReference, resolvedWallpaper: info.resolvedWallpaper) {
theme = customizePresentationTheme(loadedTheme, editing: false, accentColor: accentColor, outgoingAccentColor: outgoingAccentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, animateBubbleColors: animateBubbleColors, wallpaper: wallpaper)
} else {
return nil
}
}
return theme
}
@@ -0,0 +1,311 @@
import Foundation
import PresentationStrings
import TelegramCore
public func compactNumericCountString(_ count: Int, decimalSeparator: String = ".", showDecimalPart: Bool = true) -> String {
if count >= 1000 * 1000 {
let remainder = (count % (1000 * 1000)) / (1000 * 100)
if remainder != 0 && showDecimalPart {
return "\(count / (1000 * 1000))\(decimalSeparator)\(remainder)M"
} else {
return "\(count / (1000 * 1000))M"
}
} else if count >= 1000 {
let remainder = (count % (1000)) / (100)
if remainder != 0 && showDecimalPart {
return "\(count / 1000)\(decimalSeparator)\(remainder)K"
} else {
return "\(count / 1000)K"
}
} else {
return "\(count)"
}
}
public func presentationStringsFormattedNumber(_ count: Int32, _ groupingSeparator: String = "") -> String {
let string = "\(count)"
if groupingSeparator.isEmpty || abs(count) < 1000 {
return string
} else {
if count < 0 {
return "-\(presentationStringsFormattedNumber(abs(count), groupingSeparator))"
} else {
var groupedString: String = ""
for i in 0 ..< Int(ceil(Double(string.count) / 3.0)) {
let index = string.count - Int(i + 1) * 3
if !groupedString.isEmpty {
groupedString = groupingSeparator + groupedString
}
groupedString = String(string[string.index(string.startIndex, offsetBy: max(0, index)) ..< string.index(string.startIndex, offsetBy: index + 3)]) + groupedString
}
return groupedString
}
}
}
public func presentationStringsFormattedNumber(_ starsAmount: StarsAmount, _ groupingSeparator: String = "") -> String {
if starsAmount.nanos == 0 {
let count = Int32(starsAmount.value)
let string = "\(count)"
if groupingSeparator.isEmpty || abs(count) < 1000 {
return string
} else {
var groupedString: String = ""
for i in 0 ..< Int(ceil(Double(string.count) / 3.0)) {
let index = string.count - Int(i + 1) * 3
if !groupedString.isEmpty {
groupedString = groupingSeparator + groupedString
}
groupedString = String(string[string.index(string.startIndex, offsetBy: max(0, index)) ..< string.index(string.startIndex, offsetBy: index + 3)]) + groupedString
}
return groupedString
}
} else {
return starsAmount.stringValue
}
}
public func dayIntervalString(strings: PresentationStrings, days: Int32) -> String {
return strings.MessageTimer_Days(max(0, days))
}
public func hoursIntervalString(strings: PresentationStrings, hours: Int32) -> String {
return strings.MessageTimer_Hours(max(0, hours))
}
public func minutesIntervalString(strings: PresentationStrings, minutes: Int32) -> String {
return strings.MessageTimer_Minutes(max(0, minutes))
}
public enum TimeIntervalStringUsage {
case generic
case afterTime
case timer
}
public func timeIntervalString(strings: PresentationStrings, value: Int32, usage: TimeIntervalStringUsage = .generic, preferLowerValue: Bool = false) -> String {
switch usage {
case .generic:
if preferLowerValue {
if value <= 60 {
return strings.MessageTimer_Seconds(max(1, value))
} else if value <= 60 * 60 {
return strings.MessageTimer_Minutes(max(1, value / 60))
} else if value <= 60 * 60 * 24 {
return strings.MessageTimer_Hours(max(1, value / (60 * 60)))
} else if value <= 60 * 60 * 24 * 7 {
return strings.MessageTimer_Days(max(1, value / (60 * 60 * 24)))
} else if value <= 60 * 60 * 24 * 30 {
return strings.MessageTimer_Weeks(max(1, value / (60 * 60 * 24 * 7)))
} else if value <= 60 * 60 * 24 * 365 {
return strings.MessageTimer_Months(max(1, value / (60 * 60 * 24 * 30)))
} else {
return strings.MessageTimer_Years(max(1, value / (60 * 60 * 24 * 365)))
}
} else {
if value < 60 {
return strings.MessageTimer_Seconds(max(1, value))
} else if value < 60 * 60 {
return strings.MessageTimer_Minutes(max(1, value / 60))
} else if value < 60 * 60 * 24 {
return strings.MessageTimer_Hours(max(1, value / (60 * 60)))
} else if value < 60 * 60 * 24 * 7 {
return strings.MessageTimer_Days(max(1, value / (60 * 60 * 24)))
} else if value < 60 * 60 * 24 * 30 {
return strings.MessageTimer_Weeks(max(1, value / (60 * 60 * 24 * 7)))
} else if value < 60 * 60 * 24 * 365 {
return strings.MessageTimer_Months(max(1, value / (60 * 60 * 24 * 30)))
} else {
return strings.MessageTimer_Years(max(1, value / (60 * 60 * 24 * 365)))
}
}
case .afterTime:
if preferLowerValue {
if value <= 60 {
return strings.Time_AfterSeconds(max(1, value))
} else if value <= 60 * 60 {
return strings.Time_AfterMinutes(max(1, value / 60))
} else if value <= 60 * 60 * 24 {
return strings.Time_AfterHours(max(1, value / (60 * 60)))
} else if value <= 60 * 60 * 24 * 7 {
return strings.Time_AfterDays(max(1, value / (60 * 60 * 24)))
} else if value <= 60 * 60 * 24 * 30 {
return strings.Time_AfterWeeks(max(1, value / (60 * 60 * 24 * 7)))
} else if value <= 60 * 60 * 24 * 365 {
return strings.Time_AfterMonths(max(1, value / (60 * 60 * 24 * 30)))
} else {
return strings.Time_AfterYears(max(1, value / (60 * 60 * 24 * 365)))
}
} else {
if value < 60 {
return strings.Time_AfterSeconds(max(1, value))
} else if value < 60 * 60 {
return strings.Time_AfterMinutes(max(1, value / 60))
} else if value < 60 * 60 * 24 {
return strings.Time_AfterHours(max(1, value / (60 * 60)))
} else if value < 60 * 60 * 24 * 7 {
return strings.Time_AfterDays(max(1, value / (60 * 60 * 24)))
} else if value < 60 * 60 * 24 * 30 {
return strings.Time_AfterWeeks(max(1, value / (60 * 60 * 24 * 7)))
} else if value < 60 * 60 * 24 * 365 {
return strings.Time_AfterMonths(max(1, value / (60 * 60 * 24 * 30)))
} else {
return strings.Time_AfterYears(max(1, value / (60 * 60 * 24 * 365)))
}
}
case .timer:
if preferLowerValue {
if value <= 60 {
return strings.Time_TimerSeconds(max(1, value))
} else if value <= 60 * 60 {
return strings.Time_TimerMinutes(max(1, value / 60))
} else if value <= 60 * 60 * 24 {
return strings.Time_TimerHours(max(1, value / (60 * 60)))
} else if value <= 60 * 60 * 24 * 7 {
return strings.Time_TimerDays(max(1, value / (60 * 60 * 24)))
} else if value <= 60 * 60 * 24 * 30 {
return strings.Time_TimerWeeks(max(1, value / (60 * 60 * 24 * 7)))
} else if value <= 60 * 60 * 24 * 365 {
return strings.Time_TimerMonths(max(1, value / (60 * 60 * 24 * 30)))
} else {
return strings.Time_TimerYears(max(1, value / (60 * 60 * 24 * 365)))
}
} else {
if value < 60 {
return strings.Time_TimerSeconds(max(1, value))
} else if value < 60 * 60 {
return strings.Time_TimerMinutes(max(1, value / 60))
} else if value < 60 * 60 * 24 {
return strings.Time_TimerHours(max(1, value / (60 * 60)))
} else if value < 60 * 60 * 24 * 7 {
return strings.Time_TimerDays(max(1, value / (60 * 60 * 24)))
} else if value < 60 * 60 * 24 * 30 {
return strings.Time_TimerWeeks(max(1, value / (60 * 60 * 24 * 7)))
} else if value < 60 * 60 * 24 * 365 {
return strings.Time_TimerMonths(max(1, value / (60 * 60 * 24 * 30)))
} else {
return strings.Time_TimerYears(max(1, value / (60 * 60 * 24 * 365)))
}
}
}
}
public func scheduledTimeIntervalString(strings: PresentationStrings, value: Int32, preferLowerValue: Bool = false) -> String {
if preferLowerValue {
if value <= 60 {
return strings.ScheduledIn_Seconds(max(1, value))
} else if value <= 60 * 60 {
return strings.ScheduledIn_Minutes(max(1, value / 60))
} else if value <= 60 * 60 * 24 {
return strings.ScheduledIn_Hours(max(1, value / (60 * 60)))
} else if value <= 60 * 60 * 24 * 7 {
return strings.ScheduledIn_Days(max(1, value / (60 * 60 * 24)))
} else if value <= 60 * 60 * 24 * 30 {
return strings.ScheduledIn_Weeks(max(1, value / (60 * 60 * 24 * 7)))
} else {
return strings.ScheduledIn_Months(max(1, value / (60 * 60 * 24 * 30)))
}
} else {
if value < 60 {
return strings.ScheduledIn_Seconds(max(1, value))
} else if value < 60 * 60 {
return strings.ScheduledIn_Minutes(max(1, value / 60))
} else if value < 60 * 60 * 24 {
return strings.ScheduledIn_Hours(max(1, value / (60 * 60)))
} else if value < 60 * 60 * 24 * 7 {
return strings.ScheduledIn_Days(max(1, value / (60 * 60 * 24)))
} else if value < 60 * 60 * 24 * 30 {
return strings.ScheduledIn_Weeks(max(1, value / (60 * 60 * 24 * 7)))
} else {
return strings.ScheduledIn_Months(max(1, value / (60 * 60 * 24 * 30)))
}
}
}
public func shortTimeIntervalString(strings: PresentationStrings, value: Int32, useLargeFormat: Bool = false) -> String {
if useLargeFormat {
if value < 60 {
return strings.MessageTimer_LargeShortSeconds(max(1, value))
} else if value < 60 * 60 {
return strings.MessageTimer_LargeShortMinutes(max(1, value / 60))
} else if value < 60 * 60 * 24 {
return strings.MessageTimer_LargeShortHours(max(1, value / (60 * 60)))
} else if value < 60 * 60 * 24 * 7 {
return strings.MessageTimer_LargeShortDays(max(1, value / (60 * 60 * 24)))
} else if value < 60 * 60 * 24 * 31 {
return strings.MessageTimer_LargeShortWeeks(max(1, value / (60 * 60 * 24 * 7)))
} else if value < 60 * 60 * 24 * 365 {
return strings.MessageTimer_LargeShortMonths(max(1, value / (60 * 60 * 24 * 30)))
} else {
return strings.MessageTimer_LargeShortYears(max(1, value / (60 * 60 * 24 * 365)))
}
} else {
if value < 60 {
return strings.MessageTimer_ShortSeconds(max(1, value))
} else if value < 60 * 60 {
return strings.MessageTimer_ShortMinutes(max(1, value / 60))
} else if value < 60 * 60 * 24 {
return strings.MessageTimer_ShortHours(max(1, value / (60 * 60)))
} else if value < 60 * 60 * 24 * 7 {
return strings.MessageTimer_ShortDays(max(1, value / (60 * 60 * 24)))
} else if value < 60 * 60 * 24 * 31 {
return strings.MessageTimer_ShortWeeks(max(1, value / (60 * 60 * 24 * 7)))
} else if value < 60 * 60 * 24 * 365 {
return strings.MessageTimer_ShortMonths(max(1, value / (60 * 60 * 24 * 30)))
} else {
return strings.MessageTimer_ShortYears(max(1, value / (60 * 60 * 24 * 365)))
}
}
}
public func muteForIntervalString(strings: PresentationStrings, value: Int32) -> String {
if value < 60 * 60 {
return strings.MuteFor_Minutes(max(1, value / (60)))
} else if value < 60 * 60 * 24 {
return strings.MuteFor_Hours(max(1, value / (60 * 60)))
} else {
return strings.MuteFor_Days(max(1, value / (60 * 60 * 24)))
}
}
public func setTimeoutForIntervalString(strings: PresentationStrings, value: Int32) -> String {
if value < 60 * 60 {
return strings.SetTimeoutFor_Minutes(max(1, value / (60)))
} else if value < 60 * 60 * 24 {
return strings.SetTimeoutFor_Hours(max(1, value / (60 * 60)))
} else {
return strings.SetTimeoutFor_Days(max(1, value / (60 * 60 * 24)))
}
}
public func mutedForTimeIntervalString(strings: PresentationStrings, value: Int32) -> String {
if value < 60 * 60 {
return strings.MutedForTime_Minutes(max(1, value / (60)))
} else if value < 60 * 60 * 24 {
return strings.MutedForTime_Hours(max(1, value / (60 * 60)))
} else {
return strings.MutedForTime_Days(max(1, value / (60 * 60 * 24)))
}
}
public func unmuteIntervalString(strings: PresentationStrings, value: Int32) -> String {
if value < 60 * 60 {
return strings.MuteExpires_Minutes(max(1, value / 60))
} else if value < 60 * 60 * 24 {
return strings.MuteExpires_Hours(max(1, value / (60 * 60)))
} else {
return strings.MuteExpires_Days(max(1, value / (60 * 60 * 24)))
}
}
public func callDurationString(strings: PresentationStrings, value: Int32) -> String {
if value < 60 {
return strings.Call_Seconds(max(1, value))
} else if value < 60 * 60 {
return strings.Call_Minutes(max(1, value / 60))
} else if value < 60 * 60 * 24 {
return strings.Call_Hours(max(1, value / (60 * 60)))
} else {
return strings.Call_Days(max(1, value / (60 * 60 * 24)))
}
}
@@ -0,0 +1,904 @@
import Foundation
import UIKit
import SwiftSignalKit
import Postbox
import TelegramCore
import Contacts
import Display
import TelegramUIPreferences
import AppBundle
import Sunrise
import PresentationStrings
public struct PresentationDateTimeFormat: Equatable {
public let timeFormat: PresentationTimeFormat
public let dateFormat: PresentationDateFormat
public let dateSeparator: String
public let dateSuffix: String
public let requiresFullYear: Bool
public let decimalSeparator: String
public let groupingSeparator: String
public init() {
self.timeFormat = .regular
self.dateFormat = .monthFirst
self.dateSeparator = "."
self.dateSuffix = ""
self.requiresFullYear = false
self.decimalSeparator = "."
self.groupingSeparator = "."
}
public init(timeFormat: PresentationTimeFormat, dateFormat: PresentationDateFormat, dateSeparator: String, dateSuffix: String, requiresFullYear: Bool, decimalSeparator: String, groupingSeparator: String) {
self.timeFormat = timeFormat
self.dateFormat = dateFormat
self.dateSeparator = dateSeparator
self.dateSuffix = dateSuffix
self.requiresFullYear = requiresFullYear
self.decimalSeparator = decimalSeparator
self.groupingSeparator = groupingSeparator
}
}
public struct PresentationAppIcon: Equatable {
public let name: String
public let imageName: String
public let isDefault: Bool
public let isPremium: Bool
public init(name: String, imageName: String, isDefault: Bool = false, isPremium: Bool = false) {
self.name = name
self.imageName = imageName
self.isDefault = isDefault
self.isPremium = isPremium
}
}
public enum PresentationTimeFormat {
case regular
case military
}
public enum PresentationDateFormat {
case monthFirst
case dayFirst
}
public struct PresentationChatBubbleCorners: Equatable, Hashable {
public var mainRadius: CGFloat
public var auxiliaryRadius: CGFloat
public var mergeBubbleCorners: Bool
public var hasTails: Bool
public init(mainRadius: CGFloat, auxiliaryRadius: CGFloat, mergeBubbleCorners: Bool, hasTails: Bool = true) {
self.mainRadius = mainRadius
self.auxiliaryRadius = auxiliaryRadius
self.mergeBubbleCorners = mergeBubbleCorners
self.hasTails = hasTails
}
}
public final class PresentationData: Equatable {
public let strings: PresentationStrings
public let theme: PresentationTheme
public let autoNightModeTriggered: Bool
public let chatWallpaper: TelegramWallpaper
public let chatFontSize: PresentationFontSize
public let chatBubbleCorners: PresentationChatBubbleCorners
public let listsFontSize: PresentationFontSize
public let dateTimeFormat: PresentationDateTimeFormat
public let nameDisplayOrder: PresentationPersonNameOrder
public let nameSortOrder: PresentationPersonNameOrder
public let reduceMotion: Bool
public let largeEmoji: Bool
public init(strings: PresentationStrings, theme: PresentationTheme, autoNightModeTriggered: Bool, chatWallpaper: TelegramWallpaper, chatFontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, listsFontSize: PresentationFontSize, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, nameSortOrder: PresentationPersonNameOrder, reduceMotion: Bool, largeEmoji: Bool) {
self.strings = strings
self.theme = theme
self.autoNightModeTriggered = autoNightModeTriggered
self.chatWallpaper = chatWallpaper
self.chatFontSize = chatFontSize
self.chatBubbleCorners = chatBubbleCorners
self.listsFontSize = listsFontSize
self.dateTimeFormat = dateTimeFormat
self.nameDisplayOrder = nameDisplayOrder
self.nameSortOrder = nameSortOrder
self.reduceMotion = reduceMotion
self.largeEmoji = largeEmoji
}
public func withUpdated(theme: PresentationTheme) -> PresentationData {
return PresentationData(strings: self.strings, theme: theme, autoNightModeTriggered: self.autoNightModeTriggered, chatWallpaper: self.chatWallpaper, chatFontSize: self.chatFontSize, chatBubbleCorners: self.chatBubbleCorners, listsFontSize: self.listsFontSize, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, nameSortOrder: self.nameSortOrder, reduceMotion: self.reduceMotion, largeEmoji: self.largeEmoji)
}
public func withUpdated(chatWallpaper: TelegramWallpaper) -> PresentationData {
return PresentationData(strings: self.strings, theme: self.theme, autoNightModeTriggered: self.autoNightModeTriggered, chatWallpaper: chatWallpaper, chatFontSize: self.chatFontSize, chatBubbleCorners: self.chatBubbleCorners, listsFontSize: self.listsFontSize, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, nameSortOrder: self.nameSortOrder, reduceMotion: self.reduceMotion, largeEmoji: self.largeEmoji)
}
public func withUpdate(listsFontSize: PresentationFontSize) -> PresentationData {
return PresentationData(strings: self.strings, theme: self.theme, autoNightModeTriggered: self.autoNightModeTriggered, chatWallpaper: self.chatWallpaper, chatFontSize: self.chatFontSize, chatBubbleCorners: self.chatBubbleCorners, listsFontSize: listsFontSize, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, nameSortOrder: self.nameSortOrder, reduceMotion: self.reduceMotion, largeEmoji: self.largeEmoji)
}
public static func ==(lhs: PresentationData, rhs: PresentationData) -> Bool {
return lhs.strings === rhs.strings && lhs.theme === rhs.theme && lhs.autoNightModeTriggered == rhs.autoNightModeTriggered && lhs.chatWallpaper == rhs.chatWallpaper && lhs.chatFontSize == rhs.chatFontSize && lhs.chatBubbleCorners == rhs.chatBubbleCorners && lhs.listsFontSize == rhs.listsFontSize && lhs.dateTimeFormat == rhs.dateTimeFormat && lhs.reduceMotion == rhs.reduceMotion && lhs.largeEmoji == rhs.largeEmoji
}
}
public func dictFromLocalization(_ value: Localization) -> [String: String] {
var dict: [String: String] = [:]
for entry in value.entries {
switch entry {
case let .string(key, value):
dict[key] = value
case let .pluralizedString(key, zero, one, two, few, many, other):
if let zero = zero {
dict["\(key)_zero"] = zero
}
if let one = one {
dict["\(key)_1"] = one
}
if let two = two {
dict["\(key)_2"] = two
}
if let few = few {
dict["\(key)_3_10"] = few
}
if let many = many {
dict["\(key)_many"] = many
}
dict["\(key)_any"] = other
}
}
return dict
}
private func currentDateTimeFormat() -> PresentationDateTimeFormat {
let locale = Locale.current
let dateFormatter = DateFormatter()
dateFormatter.locale = locale
dateFormatter.dateStyle = .none
dateFormatter.timeStyle = .medium
dateFormatter.timeZone = TimeZone.current
let dateString = dateFormatter.string(from: Date())
let timeFormat: PresentationTimeFormat
if dateString.contains(dateFormatter.amSymbol) || dateString.contains(dateFormatter.pmSymbol) {
timeFormat = .regular
} else {
timeFormat = .military
}
let dateFormat: PresentationDateFormat
var dateSeparator = "/"
var dateSuffix = ""
var requiresFullYear = false
if let dateString = DateFormatter.dateFormat(fromTemplate: "MdY", options: 0, locale: locale) {
for separator in [". ", ".", "/", "-", "/"] {
if dateString.contains(separator) {
if separator == ". " {
dateSuffix = "."
dateSeparator = "."
requiresFullYear = true
} else {
dateSeparator = separator
}
break
}
}
if dateString.contains("M\(dateSeparator)d") {
dateFormat = .monthFirst
} else {
dateFormat = .dayFirst
}
} else {
dateFormat = .dayFirst
}
let decimalSeparator = locale.decimalSeparator ?? "."
let groupingSeparator = locale.groupingSeparator ?? ""
return PresentationDateTimeFormat(timeFormat: timeFormat, dateFormat: dateFormat, dateSeparator: dateSeparator, dateSuffix: dateSuffix, requiresFullYear: requiresFullYear, decimalSeparator: decimalSeparator, groupingSeparator: groupingSeparator)
}
private func currentPersonNameSortOrder() -> PresentationPersonNameOrder {
switch CNContactsUserDefaults.shared().sortOrder {
case .givenName:
return .firstLast
default:
return .lastFirst
}
}
public final class InitialPresentationDataAndSettings {
public let presentationData: PresentationData
public let automaticMediaDownloadSettings: MediaAutoDownloadSettings
public let autodownloadSettings: AutodownloadSettings
public let callListSettings: CallListSettings
public let inAppNotificationSettings: InAppNotificationSettings
public let mediaInputSettings: MediaInputSettings
public let mediaDisplaySettings: MediaDisplaySettings
public let stickerSettings: StickerSettings
public let experimentalUISettings: ExperimentalUISettings
public init(presentationData: PresentationData, automaticMediaDownloadSettings: MediaAutoDownloadSettings, autodownloadSettings: AutodownloadSettings, callListSettings: CallListSettings, inAppNotificationSettings: InAppNotificationSettings, mediaInputSettings: MediaInputSettings, mediaDisplaySettings: MediaDisplaySettings, stickerSettings: StickerSettings, experimentalUISettings: ExperimentalUISettings) {
self.presentationData = presentationData
self.automaticMediaDownloadSettings = automaticMediaDownloadSettings
self.autodownloadSettings = autodownloadSettings
self.callListSettings = callListSettings
self.inAppNotificationSettings = inAppNotificationSettings
self.mediaInputSettings = mediaInputSettings
self.mediaDisplaySettings = mediaDisplaySettings
self.stickerSettings = stickerSettings
self.experimentalUISettings = experimentalUISettings
}
}
public func currentPresentationDataAndSettings(accountManager: AccountManager<TelegramAccountManagerTypes>, systemUserInterfaceStyle: WindowUserInterfaceStyle) -> Signal<InitialPresentationDataAndSettings, NoError> {
struct InternalData {
var localizationSettings: PreferencesEntry?
var presentationThemeSettings: PreferencesEntry?
var automaticMediaDownloadSettings: PreferencesEntry?
var autodownloadSettings: PreferencesEntry?
var callListSettings: PreferencesEntry?
var inAppNotificationSettings: PreferencesEntry?
var mediaInputSettings: PreferencesEntry?
var mediaDisplaySettings: PreferencesEntry?
var experimentalUISettings: PreferencesEntry?
var contactSynchronizationSettings: PreferencesEntry?
var stickerSettings: PreferencesEntry?
init(
localizationSettings: PreferencesEntry?,
presentationThemeSettings: PreferencesEntry?,
automaticMediaDownloadSettings: PreferencesEntry?,
autodownloadSettings: PreferencesEntry?,
callListSettings: PreferencesEntry?,
inAppNotificationSettings: PreferencesEntry?,
mediaInputSettings: PreferencesEntry?,
mediaDisplaySettings: PreferencesEntry?,
experimentalUISettings: PreferencesEntry?,
contactSynchronizationSettings: PreferencesEntry?,
stickerSettings: PreferencesEntry?
) {
self.localizationSettings = localizationSettings
self.presentationThemeSettings = presentationThemeSettings
self.automaticMediaDownloadSettings = automaticMediaDownloadSettings
self.autodownloadSettings = autodownloadSettings
self.callListSettings = callListSettings
self.inAppNotificationSettings = inAppNotificationSettings
self.mediaInputSettings = mediaInputSettings
self.mediaDisplaySettings = mediaDisplaySettings
self.experimentalUISettings = experimentalUISettings
self.contactSynchronizationSettings = contactSynchronizationSettings
self.stickerSettings = stickerSettings
}
}
return accountManager.transaction { transaction -> InternalData in
let localizationSettings = transaction.getSharedData(SharedDataKeys.localizationSettings)
let presentationThemeSettings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings)
let automaticMediaDownloadSettings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.automaticMediaDownloadSettings)
let autodownloadSettings = transaction.getSharedData(SharedDataKeys.autodownloadSettings)
let callListSettings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.callListSettings)
let inAppNotificationSettings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.inAppNotificationSettings)
let mediaInputSettings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.mediaInputSettings)
let mediaDisplaySettings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.mediaDisplaySettings)
let experimentalUISettings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings)
let contactSynchronizationSettings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.contactSynchronizationSettings)
let stickerSettings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.stickerSettings)
return InternalData(
localizationSettings: localizationSettings,
presentationThemeSettings: presentationThemeSettings,
automaticMediaDownloadSettings: automaticMediaDownloadSettings,
autodownloadSettings: autodownloadSettings,
callListSettings: callListSettings,
inAppNotificationSettings: inAppNotificationSettings,
mediaInputSettings: mediaInputSettings,
mediaDisplaySettings: mediaDisplaySettings,
experimentalUISettings: experimentalUISettings,
contactSynchronizationSettings: contactSynchronizationSettings,
stickerSettings: stickerSettings
)
}
|> deliverOn(Queue(name: "PresentationData-Load", qos: .userInteractive))
|> map { internalData -> InitialPresentationDataAndSettings in
let localizationSettings: LocalizationSettings?
if let current = internalData.localizationSettings?.get(LocalizationSettings.self) {
localizationSettings = current
} else {
localizationSettings = nil
}
let themeSettings: PresentationThemeSettings
if let current = internalData.presentationThemeSettings?.get(PresentationThemeSettings.self) {
themeSettings = current
} else {
themeSettings = PresentationThemeSettings.defaultSettings
}
let automaticMediaDownloadSettings: MediaAutoDownloadSettings
if let value = internalData.automaticMediaDownloadSettings?.get(MediaAutoDownloadSettings.self) {
automaticMediaDownloadSettings = value
} else {
automaticMediaDownloadSettings = MediaAutoDownloadSettings.defaultSettings
}
let autodownloadSettings: AutodownloadSettings
if let value = internalData.autodownloadSettings?.get(AutodownloadSettings.self) {
autodownloadSettings = value
} else {
autodownloadSettings = .defaultSettings
}
let callListSettings: CallListSettings
if let value = internalData.callListSettings?.get(CallListSettings.self) {
callListSettings = value
} else {
callListSettings = CallListSettings.defaultSettings
}
let inAppNotificationSettings: InAppNotificationSettings
if let value = internalData.inAppNotificationSettings?.get(InAppNotificationSettings.self) {
inAppNotificationSettings = value
} else {
inAppNotificationSettings = InAppNotificationSettings.defaultSettings
}
let mediaInputSettings: MediaInputSettings
if let value = internalData.mediaInputSettings?.get(MediaInputSettings.self) {
mediaInputSettings = value
} else {
mediaInputSettings = MediaInputSettings.defaultSettings
}
let mediaDisplaySettings: MediaDisplaySettings
if let value = internalData.mediaDisplaySettings?.get(MediaDisplaySettings.self) {
mediaDisplaySettings = value
} else {
mediaDisplaySettings = MediaDisplaySettings.defaultSettings
}
let stickerSettings: StickerSettings
if let value = internalData.stickerSettings?.get(StickerSettings.self) {
stickerSettings = value
} else {
stickerSettings = StickerSettings.defaultSettings
}
let experimentalUISettings: ExperimentalUISettings = internalData.experimentalUISettings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
let contactSettings: ContactSynchronizationSettings = internalData.contactSynchronizationSettings?.get(ContactSynchronizationSettings.self) ?? ContactSynchronizationSettings.defaultSettings
let effectiveTheme: PresentationThemeReference
var preferredBaseTheme: TelegramBaseTheme?
let parameters = AutomaticThemeSwitchParameters(settings: themeSettings.automaticThemeSwitchSetting)
let autoNightModeTriggered: Bool
if automaticThemeShouldSwitchNow(parameters, systemUserInterfaceStyle: systemUserInterfaceStyle) {
effectiveTheme = themeSettings.automaticThemeSwitchSetting.theme
autoNightModeTriggered = true
if let baseTheme = themeSettings.themePreferredBaseTheme[effectiveTheme.index], [.night, .tinted].contains(baseTheme) {
preferredBaseTheme = baseTheme
} else {
preferredBaseTheme = .night
}
} else {
effectiveTheme = themeSettings.theme
autoNightModeTriggered = false
if let baseTheme = themeSettings.themePreferredBaseTheme[effectiveTheme.index], [.classic, .day].contains(baseTheme) {
preferredBaseTheme = baseTheme
}
}
let effectiveColors = themeSettings.themeSpecificAccentColors[effectiveTheme.index]
let theme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: effectiveTheme, baseTheme: preferredBaseTheme, accentColor: effectiveColors?.colorFor(baseTheme: preferredBaseTheme ?? .day), bubbleColors: effectiveColors?.customBubbleColors ?? [], baseColor: effectiveColors?.baseColor) ?? defaultPresentationTheme
var effectiveChatWallpaper: TelegramWallpaper = (themeSettings.themeSpecificChatWallpapers[coloredThemeIndex(reference: effectiveTheme, accentColor: effectiveColors)] ?? themeSettings.themeSpecificChatWallpapers[effectiveTheme.index]) ?? theme.chat.defaultWallpaper
if case .builtin = effectiveChatWallpaper {
effectiveChatWallpaper = defaultBuiltinWallpaper(data: .legacy, colors: legacyBuiltinWallpaperGradientColors.map(\.rgb))
}
let dateTimeFormat = currentDateTimeFormat()
let stringsValue: PresentationStrings
if let localizationSettings = localizationSettings {
stringsValue = PresentationStrings(primaryComponent: PresentationStrings.Component(languageCode: localizationSettings.primaryComponent.languageCode, localizedName: localizationSettings.primaryComponent.localizedName, pluralizationRulesCode: localizationSettings.primaryComponent.customPluralizationCode, dict: dictFromLocalization(localizationSettings.primaryComponent.localization)), secondaryComponent: localizationSettings.secondaryComponent.flatMap({ PresentationStrings.Component(languageCode: $0.languageCode, localizedName: $0.localizedName, pluralizationRulesCode: $0.customPluralizationCode, dict: dictFromLocalization($0.localization)) }), groupingSeparator: dateTimeFormat.groupingSeparator)
} else {
stringsValue = defaultPresentationStrings
}
let nameDisplayOrder = contactSettings.nameDisplayOrder
let nameSortOrder = currentPersonNameSortOrder()
let (chatFontSize, listsFontSize) = resolveFontSize(settings: themeSettings)
let chatBubbleCorners = PresentationChatBubbleCorners(mainRadius: CGFloat(themeSettings.chatBubbleSettings.mainRadius), auxiliaryRadius: CGFloat(themeSettings.chatBubbleSettings.auxiliaryRadius), mergeBubbleCorners: themeSettings.chatBubbleSettings.mergeBubbleCorners)
return InitialPresentationDataAndSettings(presentationData: PresentationData(strings: stringsValue, theme: theme, autoNightModeTriggered: autoNightModeTriggered, chatWallpaper: effectiveChatWallpaper, chatFontSize: chatFontSize, chatBubbleCorners: chatBubbleCorners, listsFontSize: listsFontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, reduceMotion: themeSettings.reduceMotion, largeEmoji: themeSettings.largeEmoji), automaticMediaDownloadSettings: automaticMediaDownloadSettings, autodownloadSettings: autodownloadSettings, callListSettings: callListSettings, inAppNotificationSettings: inAppNotificationSettings, mediaInputSettings: mediaInputSettings, mediaDisplaySettings: mediaDisplaySettings, stickerSettings: stickerSettings, experimentalUISettings: experimentalUISettings)
}
}
private var first = true
private func roundTimeToDay(_ timestamp: Int32) -> Int32 {
let calendar = Calendar.current
let offset = 0
let components = calendar.dateComponents([.hour, .minute, .second], from: Date(timeIntervalSince1970: Double(timestamp + Int32(offset))))
return Int32(components.hour! * 60 * 60 + components.minute! * 60 + components.second!)
}
private enum PreparedAutomaticThemeSwitchTrigger {
case explicitNone
case explicitForce
case system
case time(fromSeconds: Int32, toSeconds: Int32)
case brightness(threshold: Double)
}
private struct AutomaticThemeSwitchParameters {
let trigger: PreparedAutomaticThemeSwitchTrigger
let theme: PresentationThemeReference
init(settings: AutomaticThemeSwitchSetting) {
let trigger: PreparedAutomaticThemeSwitchTrigger
if settings.force {
trigger = .explicitForce
} else {
switch settings.trigger {
case .system:
trigger = .system
case .explicitNone:
trigger = .explicitNone
case let .timeBased(setting):
let fromValue: Int32
let toValue: Int32
switch setting {
case let .automatic(latitude, longitude, _):
let calculator = EDSunriseSet(date: Date(), timezone: TimeZone.current, latitude: latitude, longitude: longitude)!
fromValue = roundTimeToDay(Int32(calculator.sunset.timeIntervalSince1970))
toValue = roundTimeToDay(Int32(calculator.sunrise.timeIntervalSince1970))
case let .manual(fromSeconds, toSeconds):
fromValue = fromSeconds
toValue = toSeconds
}
trigger = .time(fromSeconds: fromValue, toSeconds: toValue)
case let .brightness(threshold):
trigger = .brightness(threshold: threshold)
}
}
self.trigger = trigger
self.theme = settings.theme
}
}
private func automaticThemeShouldSwitchNow(_ parameters: AutomaticThemeSwitchParameters, systemUserInterfaceStyle: WindowUserInterfaceStyle) -> Bool {
switch parameters.trigger {
case .explicitNone:
return false
case .explicitForce:
return true
case .system:
return systemUserInterfaceStyle == .dark
case let .time(fromValue, toValue):
let roundedTimestamp = roundTimeToDay(Int32(Date().timeIntervalSince1970))
if roundedTimestamp >= fromValue || roundedTimestamp <= toValue {
return true
} else {
return false
}
case let .brightness(threshold):
return UIScreen.main.brightness <= CGFloat(threshold)
}
}
public func automaticThemeShouldSwitchNow(settings: AutomaticThemeSwitchSetting, systemUserInterfaceStyle: WindowUserInterfaceStyle) -> Bool {
let parameters = AutomaticThemeSwitchParameters(settings: settings)
return automaticThemeShouldSwitchNow(parameters, systemUserInterfaceStyle: systemUserInterfaceStyle)
}
private func automaticThemeShouldSwitch(_ settings: AutomaticThemeSwitchSetting, systemUserInterfaceStyle: WindowUserInterfaceStyle) -> Signal<Bool, NoError> {
if settings.force {
return .single(true)
} else if case .explicitNone = settings.trigger {
return .single(false)
} else {
return Signal { subscriber in
let parameters = AutomaticThemeSwitchParameters(settings: settings)
subscriber.putNext(automaticThemeShouldSwitchNow(parameters, systemUserInterfaceStyle: systemUserInterfaceStyle))
let timer = SwiftSignalKit.Timer(timeout: 1.0, repeat: true, completion: {
subscriber.putNext(automaticThemeShouldSwitchNow(parameters, systemUserInterfaceStyle: systemUserInterfaceStyle))
}, queue: Queue.mainQueue())
timer.start()
return ActionDisposable {
timer.invalidate()
}
}
|> runOn(Queue.mainQueue())
|> distinctUntilChanged
}
}
public func automaticEnergyUsageShouldBeOnNow(settings: MediaAutoDownloadSettings) -> Bool {
if settings.energyUsageSettings.activationThreshold <= 4 {
return false
} else if settings.energyUsageSettings.activationThreshold >= 96 {
return true
} else {
let batteryLevel = UIDevice.current.batteryLevel
if batteryLevel < 0.0 {
return false
} else {
return batteryLevel <= Float(settings.energyUsageSettings.activationThreshold) / 100.0
}
}
}
public func automaticEnergyUsageShouldBeOn(settings: MediaAutoDownloadSettings) -> Signal<Bool, NoError> {
if settings.energyUsageSettings.activationThreshold <= 4 {
return .single(false)
} else if settings.energyUsageSettings.activationThreshold >= 96 {
return .single(true)
} else {
return Signal { subscriber in
subscriber.putNext(automaticEnergyUsageShouldBeOnNow(settings: settings))
let observer = NotificationCenter.default.addObserver(forName: UIDevice.batteryLevelDidChangeNotification, object: nil, queue: OperationQueue.main, using: { _ in
subscriber.putNext(automaticEnergyUsageShouldBeOnNow(settings: settings))
})
return ActionDisposable {
NotificationCenter.default.removeObserver(observer)
}
}
|> runOn(Queue.mainQueue())
|> distinctUntilChanged
}
}
private func serviceColor(for data: Signal<MediaResourceData, NoError>) -> Signal<UIColor, NoError> {
return data
|> mapToSignal { data -> Signal<UIColor, NoError> in
if data.complete, let image = UIImage(contentsOfFile: data.path) {
return serviceColor(from: .single(image))
}
return .complete()
}
}
public func averageColor(from image: UIImage) -> UIColor {
let context = DrawingContext(size: CGSize(width: 1.0, height: 1.0), scale: 1.0, clear: false)!
context.withFlippedContext({ context in
if let cgImage = image.cgImage {
context.draw(cgImage, in: CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0))
}
})
return context.colorAt(CGPoint())
}
public func serviceColor(from image: Signal<UIImage?, NoError>) -> Signal<UIColor, NoError> {
return image
|> mapToSignal { image -> Signal<UIColor, NoError> in
if let image = image {
return .single(serviceColor(with: averageColor(from: image)))
}
return .complete()
}
}
public func serviceColor(for wallpaper: (TelegramWallpaper, UIImage?)) -> UIColor {
switch wallpaper.0 {
case .builtin:
return UIColor(rgb: 0x748391, alpha: 0.45)
case let .color(color):
return serviceColor(with: UIColor(argb: color))
case let .gradient(gradient):
if gradient.colors.count == 2 {
let mixedColor = UIColor(argb: gradient.colors[0]).mixedWith(UIColor(argb: gradient.colors[1]), alpha: 0.5)
return serviceColor(with: mixedColor)
} else {
return UIColor(rgb: 0x000000, alpha: 0.3)
}
case .image:
if let image = wallpaper.1 {
return serviceColor(with: averageColor(from: image))
} else {
return UIColor(rgb: 0x000000, alpha: 0.3)
}
case let .file(file):
if wallpaper.0.isPattern {
if file.settings.colors.count >= 1 && file.settings.colors.count <= 2 {
var mixedColor = UIColor(argb: file.settings.colors[0])
if file.settings.colors.count >= 2 {
mixedColor = mixedColor.mixedWith(UIColor(argb: file.settings.colors[1]), alpha: 0.5)
}
return serviceColor(with: mixedColor)
} else {
return UIColor(rgb: 0x000000, alpha: 0.3)
}
} else if let image = wallpaper.1 {
return serviceColor(with: averageColor(from: image))
} else {
return UIColor(rgb: 0x000000, alpha: 0.3)
}
case .emoticon:
return UIColor(rgb: 0x000000, alpha: 0.3)
}
}
public func serviceColor(with color: UIColor) -> UIColor {
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 saturation > 0.0 {
saturation = min(1.0, saturation + 0.05 + 0.1 * (1.0 - saturation))
}
brightness = max(0.0, brightness * 0.65)
alpha = 0.4
return UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: alpha)
}
return color
}
private var serviceBackgroundColorForWallpaper: (TelegramWallpaper, UIColor)?
public func chatServiceBackgroundColor(wallpaper: TelegramWallpaper, mediaBox: MediaBox) -> Signal<UIColor, NoError> {
if wallpaper == serviceBackgroundColorForWallpaper?.0, let color = serviceBackgroundColorForWallpaper?.1 {
return .single(color)
} else {
switch wallpaper {
case .builtin:
return .single(UIColor(rgb: 0x000000, alpha: 0.2))
case let .color(color):
return .single(serviceColor(with: UIColor(argb: color)))
case let .gradient(gradient):
if gradient.colors.count == 2 {
let mixedColor = UIColor(argb: gradient.colors[0]).mixedWith(UIColor(argb: gradient.colors[1]), alpha: 0.5)
return .single(
serviceColor(with: mixedColor))
} else {
return .single(UIColor(rgb: 0x000000, alpha: 0.3))
}
case let .image(representations, _):
if let largest = largestImageRepresentation(representations) {
return Signal<UIColor, NoError> { subscriber in
let fetch = mediaBox.fetchedResource(largest.resource, parameters: nil).start()
let data = serviceColor(for: mediaBox.resourceData(largest.resource)).start(next: { next in
subscriber.putNext(next)
}, completed: {
subscriber.putCompletion()
})
return ActionDisposable {
fetch.dispose()
data.dispose()
}
}
|> afterNext { color in
serviceBackgroundColorForWallpaper = (wallpaper, color)
}
} else {
return .single(UIColor(rgb: 0x000000, alpha: 0.3))
}
case let .file(file):
if wallpaper.isPattern {
if file.settings.colors.count >= 1 && file.settings.colors.count <= 2 {
var mixedColor = UIColor(argb: file.settings.colors[0])
if file.settings.colors.count >= 2 {
mixedColor = mixedColor.mixedWith(UIColor(argb: file.settings.colors[1]), alpha: 0.5)
}
return .single(serviceColor(with: mixedColor))
} else {
return .single(UIColor(rgb: 0x000000, alpha: 0.3))
}
} else {
return Signal<UIColor, NoError> { subscriber in
let data = serviceColor(for: mediaBox.resourceData(file.file.resource)).start(next: { next in
subscriber.putNext(next)
}, completed: {
subscriber.putCompletion()
})
return ActionDisposable {
data.dispose()
}
}
|> afterNext { color in
serviceBackgroundColorForWallpaper = (wallpaper, color)
}
}
case .emoticon:
return .single(UIColor(rgb: 0x000000, alpha: 0.3))
}
}
}
public func updatedPresentationData(accountManager: AccountManager<TelegramAccountManagerTypes>, applicationInForeground: Signal<Bool, NoError>, systemUserInterfaceStyle: Signal<WindowUserInterfaceStyle, NoError>) -> Signal<PresentationData, NoError> {
return combineLatest(accountManager.sharedData(keys: [SharedDataKeys.localizationSettings, ApplicationSpecificSharedDataKeys.presentationThemeSettings, ApplicationSpecificSharedDataKeys.contactSynchronizationSettings]), systemUserInterfaceStyle)
|> mapToSignal { sharedData, systemUserInterfaceStyle -> Signal<PresentationData, NoError> in
let themeSettings: PresentationThemeSettings
if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings]?.get(PresentationThemeSettings.self) {
themeSettings = current
} else {
themeSettings = PresentationThemeSettings.defaultSettings
}
let contactSettings: ContactSynchronizationSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.contactSynchronizationSettings]?.get(ContactSynchronizationSettings.self) ?? ContactSynchronizationSettings.defaultSettings
var currentColors = themeSettings.themeSpecificAccentColors[themeSettings.theme.index]
if let colors = currentColors, colors.baseColor == .theme {
currentColors = nil
}
let themeSpecificWallpaper = (themeSettings.themeSpecificChatWallpapers[coloredThemeIndex(reference: themeSettings.theme, accentColor: currentColors)] ?? themeSettings.themeSpecificChatWallpapers[themeSettings.theme.index])
let currentWallpaper: TelegramWallpaper
if let themeSpecificWallpaper = themeSpecificWallpaper {
currentWallpaper = themeSpecificWallpaper
} else {
let theme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: themeSettings.theme, accentColor: currentColors?.color, bubbleColors: currentColors?.customBubbleColors ?? [], wallpaper: currentColors?.wallpaper, baseColor: currentColors?.baseColor) ?? defaultPresentationTheme
currentWallpaper = theme.chat.defaultWallpaper
}
return (.single(defaultServiceBackgroundColor)
|> then(chatServiceBackgroundColor(wallpaper: currentWallpaper, mediaBox: accountManager.mediaBox)))
|> mapToSignal { serviceBackgroundColor in
return applicationInForeground
|> mapToSignal { inForeground -> Signal<PresentationData, NoError> in
if inForeground {
return automaticThemeShouldSwitch(themeSettings.automaticThemeSwitchSetting, systemUserInterfaceStyle: systemUserInterfaceStyle)
|> distinctUntilChanged
|> map { autoNightModeTriggered in
var effectiveTheme: PresentationThemeReference
var effectiveChatWallpaper = currentWallpaper
var effectiveColors = currentColors
var switchedToNightModeWallpaper = false
var preferredBaseTheme: TelegramBaseTheme?
if autoNightModeTriggered {
let automaticTheme = themeSettings.automaticThemeSwitchSetting.theme
effectiveColors = themeSettings.themeSpecificAccentColors[automaticTheme.index]
if automaticTheme == .builtin(.night) && effectiveColors == nil {
effectiveColors = PresentationThemeAccentColor(baseColor: .blue)
}
let themeSpecificWallpaper = (themeSettings.themeSpecificChatWallpapers[coloredThemeIndex(reference: automaticTheme, accentColor: effectiveColors)] ?? themeSettings.themeSpecificChatWallpapers[automaticTheme.index])
if let themeSpecificWallpaper = themeSpecificWallpaper {
effectiveChatWallpaper = themeSpecificWallpaper
switchedToNightModeWallpaper = true
}
effectiveTheme = automaticTheme
if let baseTheme = themeSettings.themePreferredBaseTheme[effectiveTheme.index], [.night, .tinted].contains(baseTheme) {
preferredBaseTheme = baseTheme
} else {
preferredBaseTheme = .night
}
} else {
effectiveTheme = themeSettings.theme
if let baseTheme = themeSettings.themePreferredBaseTheme[effectiveTheme.index], [.classic, .day].contains(baseTheme) {
preferredBaseTheme = baseTheme
}
}
if case .builtin = effectiveChatWallpaper {
effectiveChatWallpaper = defaultBuiltinWallpaper(data: .legacy, colors: legacyBuiltinWallpaperGradientColors.map(\.rgb))
}
if let colors = effectiveColors, colors.baseColor == .theme {
effectiveColors = nil
}
let themeValue = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: effectiveTheme, baseTheme: preferredBaseTheme, accentColor: effectiveColors?.colorFor(baseTheme: preferredBaseTheme ?? .day), bubbleColors: effectiveColors?.customBubbleColors ?? [], wallpaper: effectiveColors?.wallpaper, baseColor: effectiveColors?.baseColor, serviceBackgroundColor: serviceBackgroundColor) ?? defaultPresentationTheme
if autoNightModeTriggered && !switchedToNightModeWallpaper {
switch effectiveChatWallpaper {
case .builtin, .color, .gradient:
effectiveChatWallpaper = themeValue.chat.defaultWallpaper
case .file:
if effectiveChatWallpaper.isPattern {
effectiveChatWallpaper = themeValue.chat.defaultWallpaper
}
default:
break
}
}
let localizationSettings: LocalizationSettings?
if let current = sharedData.entries[SharedDataKeys.localizationSettings]?.get(LocalizationSettings.self) {
localizationSettings = current
} else {
localizationSettings = nil
}
let dateTimeFormat = currentDateTimeFormat()
let stringsValue: PresentationStrings
if let localizationSettings = localizationSettings {
stringsValue = PresentationStrings(primaryComponent: PresentationStrings.Component(languageCode: localizationSettings.primaryComponent.languageCode, localizedName: localizationSettings.primaryComponent.localizedName, pluralizationRulesCode: localizationSettings.primaryComponent.customPluralizationCode, dict: dictFromLocalization(localizationSettings.primaryComponent.localization)), secondaryComponent: localizationSettings.secondaryComponent.flatMap({ PresentationStrings.Component(languageCode: $0.languageCode, localizedName: $0.localizedName, pluralizationRulesCode: $0.customPluralizationCode, dict: dictFromLocalization($0.localization)) }), groupingSeparator: dateTimeFormat.groupingSeparator)
} else {
stringsValue = defaultPresentationStrings
}
let nameDisplayOrder = contactSettings.nameDisplayOrder
let nameSortOrder = currentPersonNameSortOrder()
let (chatFontSize, listsFontSize) = resolveFontSize(settings: themeSettings)
let chatBubbleCorners = PresentationChatBubbleCorners(mainRadius: CGFloat(themeSettings.chatBubbleSettings.mainRadius), auxiliaryRadius: CGFloat(themeSettings.chatBubbleSettings.auxiliaryRadius), mergeBubbleCorners: themeSettings.chatBubbleSettings.mergeBubbleCorners)
return PresentationData(strings: stringsValue, theme: themeValue, autoNightModeTriggered: autoNightModeTriggered, chatWallpaper: effectiveChatWallpaper, chatFontSize: chatFontSize, chatBubbleCorners: chatBubbleCorners, listsFontSize: listsFontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, reduceMotion: themeSettings.reduceMotion, largeEmoji: themeSettings.largeEmoji)
}
} else {
return .complete()
}
}
}
}
}
private func resolveFontSize(settings: PresentationThemeSettings) -> (chat: PresentationFontSize, lists: PresentationFontSize) {
let fontSize: PresentationFontSize
let listsFontSize: PresentationFontSize
if settings.useSystemFont {
let pointSize = UIFont.preferredFont(forTextStyle: .body).pointSize
fontSize = PresentationFontSize(systemFontSize: pointSize)
listsFontSize = fontSize
} else {
fontSize = settings.fontSize
listsFontSize = settings.listsFontSize
}
return (fontSize, listsFontSize)
}
public func defaultPresentationData() -> PresentationData {
let dateTimeFormat = currentDateTimeFormat()
let nameDisplayOrder: PresentationPersonNameOrder = .firstLast
let nameSortOrder = currentPersonNameSortOrder()
let themeSettings = PresentationThemeSettings.defaultSettings
let (chatFontSize, listsFontSize) = resolveFontSize(settings: themeSettings)
let chatBubbleCorners = PresentationChatBubbleCorners(mainRadius: CGFloat(themeSettings.chatBubbleSettings.mainRadius), auxiliaryRadius: CGFloat(themeSettings.chatBubbleSettings.auxiliaryRadius), mergeBubbleCorners: themeSettings.chatBubbleSettings.mergeBubbleCorners)
return PresentationData(strings: defaultPresentationStrings, theme: defaultPresentationTheme, autoNightModeTriggered: false, chatWallpaper: defaultPresentationTheme.chat.defaultWallpaper, chatFontSize: chatFontSize, chatBubbleCorners: chatBubbleCorners, listsFontSize: listsFontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, reduceMotion: themeSettings.reduceMotion, largeEmoji: themeSettings.largeEmoji)
}
public extension PresentationData {
func withFontSizes(chatFontSize: PresentationFontSize, listsFontSize: PresentationFontSize) -> PresentationData {
return PresentationData(strings: self.strings, theme: self.theme, autoNightModeTriggered: self.autoNightModeTriggered, chatWallpaper: self.chatWallpaper, chatFontSize: chatFontSize, chatBubbleCorners: self.chatBubbleCorners, listsFontSize: listsFontSize, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, nameSortOrder: self.nameSortOrder, reduceMotion: self.reduceMotion, largeEmoji: self.largeEmoji)
}
func withChatBubbleCorners(_ chatBubbleCorners: PresentationChatBubbleCorners) -> PresentationData {
return PresentationData(strings: self.strings, theme: self.theme, autoNightModeTriggered: self.autoNightModeTriggered, chatWallpaper: self.chatWallpaper, chatFontSize: self.chatFontSize, chatBubbleCorners: chatBubbleCorners, listsFontSize: self.listsFontSize, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, nameSortOrder: self.nameSortOrder, reduceMotion: self.reduceMotion, largeEmoji: self.largeEmoji)
}
func withStrings(_ strings: PresentationStrings) -> PresentationData {
return PresentationData(strings: strings, theme: self.theme, autoNightModeTriggered: self.autoNightModeTriggered, chatWallpaper: self.chatWallpaper, chatFontSize: self.chatFontSize, chatBubbleCorners: chatBubbleCorners, listsFontSize: self.listsFontSize, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, nameSortOrder: self.nameSortOrder, reduceMotion: self.reduceMotion, largeEmoji: self.largeEmoji)
}
}
public func themeDisplayName(strings: PresentationStrings, reference: PresentationThemeReference) -> String {
let name: String
switch reference {
case let .builtin(theme):
switch theme {
case .dayClassic:
name = strings.Appearance_ThemeCarouselClassic
case .day:
name = strings.Appearance_ThemeCarouselDay
case .night:
name = strings.Appearance_ThemeCarouselNewNight
case .nightAccent:
name = strings.Appearance_ThemeCarouselTintedNight
}
case let .local(theme):
name = theme.title
case let .cloud(theme):
if let emoticon = theme.theme.emoticon {
name = emoticon
} else {
name = theme.theme.title
}
}
return name
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,993 @@
import Foundation
import UIKit
import TelegramCore
import TelegramUIPreferences
public func encodePresentationTheme(_ theme: PresentationTheme) -> String? {
let encoding = PresentationThemeEncoding()
if let _ = try? theme.encode(to: encoding) {
return encoding.data.formatted
} else {
return nil
}
}
private func renderNodes(string: inout String, nodes: [PresentationThemeEncoding.Node], level: Int = 0) {
for node in nodes {
if level > 1 {
string.append(String(repeating: " ", count: level - 1))
}
switch node.value {
case let .string(value):
if let key = node.key {
string.append("\(key): \(value)\n")
}
case let .subnode(nodes):
if let key = node.key {
string.append("\(key):\n")
}
renderNodes(string: &string, nodes: nodes, level: level + 1)
}
}
}
fileprivate class PresentationThemeEncoding: Encoder {
fileprivate enum NodeValue {
case string(String)
case subnode([Node])
}
fileprivate final class Node {
var key: String?
var value: NodeValue
init(key: String? = nil, value: NodeValue) {
self.key = key
self.value = value
}
}
fileprivate final class Data {
private(set) var rootNode = Node(value: .subnode([]))
func encode(key codingKey: [CodingKey], value: String) {
var currentNode: Node = self.rootNode
for i in 0 ..< codingKey.count {
let key = codingKey[i].stringValue
var found = false
switch currentNode.value {
case var .subnode(nodes):
for node in nodes {
if node.key == key {
currentNode = node
found = true
}
}
if !found {
let newNode: Node
if i == codingKey.count - 1 {
newNode = Node(key: key, value: .string(value))
} else {
newNode = Node(key: key, value: .subnode([]))
}
nodes.append(newNode)
currentNode.value = .subnode(nodes)
currentNode = newNode
}
case .string:
break
}
}
}
var formatted: String {
var result = ""
renderNodes(string: &result, nodes: [self.rootNode])
return result
}
}
fileprivate var data: Data
var codingPath: [CodingKey] = []
let userInfo: [CodingUserInfoKey : Any] = [:]
init(to encodedData: Data = Data()) {
self.data = encodedData
}
func container<Key: CodingKey>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> {
var container = StringsKeyedEncoding<Key>(to: self.data)
container.codingPath = self.codingPath
return KeyedEncodingContainer(container)
}
func unkeyedContainer() -> UnkeyedEncodingContainer {
var container = StringsUnkeyedEncoding(to: self.data)
container.codingPath = self.codingPath
return container
}
func singleValueContainer() -> SingleValueEncodingContainer {
var container = StringsSingleValueEncoding(to: self.data)
container.codingPath = self.codingPath
return container
}
private func dictionaryForNodes(_ nodes: [Node]) -> [String: Any] {
var dictionary: [String: Any] = [:]
for node in nodes {
var value: Any?
switch node.value {
case let .string(string):
value = string
case let .subnode(subnodes):
value = dictionaryForNodes(subnodes)
}
if let key = node.key {
dictionary[key] = value
}
}
return dictionary
}
func entry(for codingKey: [String]) -> Any? {
var currentNode: Node = self.data.rootNode
for component in codingKey {
switch currentNode.value {
case let .subnode(nodes):
inner: for node in nodes {
if node.key == component {
if component == codingKey.last {
if case let .string(string) = node.value {
return string
} else if case let .subnode(nodes) = node.value {
return dictionaryForNodes(nodes)
}
} else {
currentNode = node
break inner
}
}
}
case let .string(string):
if component == codingKey.last {
return string
}
}
}
return nil
}
}
fileprivate struct StringsKeyedEncoding<Key: CodingKey>: KeyedEncodingContainerProtocol {
private let data: PresentationThemeEncoding.Data
var codingPath: [CodingKey] = []
init(to data: PresentationThemeEncoding.Data) {
self.data = data
}
mutating func encodeNil(forKey key: Key) throws {
}
mutating func encode(_ value: Bool, forKey key: Key) throws {
self.data.encode(key: self.codingPath + [key], value: value.description)
}
mutating func encode(_ value: String, forKey key: Key) throws {
self.data.encode(key: self.codingPath + [key], value: value)
}
mutating func encode(_ value: Int32, forKey key: Key) throws {
self.data.encode(key: self.codingPath + [key], value: value.description)
}
mutating func encode<T: Encodable>(_ value: T, forKey key: Key) throws {
let stringsEncoding = PresentationThemeEncoding(to: self.data)
stringsEncoding.codingPath = self.codingPath + [key]
try value.encode(to: stringsEncoding)
}
mutating func nestedContainer<NestedKey: CodingKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> {
var container = StringsKeyedEncoding<NestedKey>(to: self.data)
container.codingPath = self.codingPath + [key]
return KeyedEncodingContainer(container)
}
mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
var container = StringsUnkeyedEncoding(to: data)
container.codingPath = self.codingPath + [key]
return container
}
mutating func superEncoder() -> Encoder {
let superKey = Key(stringValue: "super")!
return superEncoder(forKey: superKey)
}
mutating func superEncoder(forKey key: Key) -> Encoder {
let stringsEncoding = PresentationThemeEncoding(to: self.data)
stringsEncoding.codingPath = self.codingPath + [key]
return stringsEncoding
}
}
fileprivate struct StringsUnkeyedEncoding: UnkeyedEncodingContainer {
private let data: PresentationThemeEncoding.Data
var codingPath: [CodingKey] = []
private(set) var count: Int = 0
init(to data: PresentationThemeEncoding.Data) {
self.data = data
}
private mutating func nextIndexedKey() -> CodingKey {
let nextCodingKey = IndexedCodingKey(intValue: count)!
self.count += 1
return nextCodingKey
}
private struct IndexedCodingKey: CodingKey {
let intValue: Int?
let stringValue: String
init?(intValue: Int) {
self.intValue = intValue
self.stringValue = intValue.description
}
init?(stringValue: String) {
return nil
}
}
mutating func encodeNil() throws {
}
mutating func encode(_ value: Bool) throws {
self.data.encode(key: self.codingPath + [self.nextIndexedKey()], value: value.description)
}
mutating func encode(_ value: String) throws {
self.data.encode(key: self.codingPath + [self.nextIndexedKey()], value: value)
}
mutating func encode(_ value: Int32) throws {
self.data.encode(key: self.codingPath + [self.nextIndexedKey()], value: value.description)
}
mutating func encode<T: Encodable>(_ value: T) throws {
let stringsEncoding = PresentationThemeEncoding(to: self.data)
stringsEncoding.codingPath = self.codingPath + [self.nextIndexedKey()]
try value.encode(to: stringsEncoding)
}
mutating func nestedContainer<NestedKey: CodingKey>(
keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer<NestedKey> {
var container = StringsKeyedEncoding<NestedKey>(to: self.data)
container.codingPath = self.codingPath + [self.nextIndexedKey()]
return KeyedEncodingContainer(container)
}
mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer {
var container = StringsUnkeyedEncoding(to: self.data)
container.codingPath = self.codingPath + [self.nextIndexedKey()]
return container
}
mutating func superEncoder() -> Encoder {
let stringsEncoding = PresentationThemeEncoding(to: self.data)
stringsEncoding.codingPath = self.codingPath
return stringsEncoding
}
}
fileprivate struct StringsSingleValueEncoding: SingleValueEncodingContainer {
private let data: PresentationThemeEncoding.Data
var codingPath: [CodingKey] = []
init(to data: PresentationThemeEncoding.Data) {
self.data = data
}
mutating func encodeNil() throws {
}
mutating func encode(_ value: Bool) throws {
self.data.encode(key: self.codingPath, value: value.description)
}
mutating func encode(_ value: String) throws {
self.data.encode(key: self.codingPath, value: value)
}
mutating func encode(_ value: Int32) throws {
self.data.encode(key: self.codingPath, value: value.description)
}
mutating func encode<T: Encodable>(_ value: T) throws {
let stringsEncoding = PresentationThemeEncoding(to: self.data)
stringsEncoding.codingPath = self.codingPath
try value.encode(to: stringsEncoding)
}
}
public enum PresentationThemeDecodingError: Error {
case generic
case dataCorrupted
case valueNotFound
case typeMismatch
case keyNotFound
}
internal protocol _YAMLStringDictionaryDecodableMarker {
static var elementType: Decodable.Type { get }
}
extension Dictionary : _YAMLStringDictionaryDecodableMarker where Key == String, Value: Decodable {
static var elementType: Decodable.Type { return Value.self }
}
private class PresentationThemeDecodingLevel {
let data: NSMutableDictionary
let index: Int
let previous: PresentationThemeDecodingLevel?
init(data: NSMutableDictionary, index: Int, previous: PresentationThemeDecodingLevel?) {
self.data = data
self.index = index
self.previous = previous
}
}
public func makePresentationTheme(data: Data, themeReference: PresentationThemeReference? = nil, resolvedWallpaper: TelegramWallpaper? = nil) -> PresentationTheme? {
guard let string = String(data: data, encoding: .utf8) else {
return nil
}
let lines = string.split { $0.isNewline }
let topLevel = PresentationThemeDecodingLevel(data: NSMutableDictionary(), index: 0, previous: nil)
var currentLevel = topLevel
for line in lines {
let trimmedLine = line.trimmingCharacters(in: .whitespaces)
if trimmedLine.hasPrefix("#") || trimmedLine.hasPrefix("//") {
continue
}
if let rangeOfColon = line.firstIndex(of: ":") {
let key = line.prefix(upTo: rangeOfColon)
var lineLevel = 0
inner: for c in key {
if c == " " {
lineLevel += 1
} else {
break inner
}
}
guard lineLevel % 2 == 0 else {
return nil
}
lineLevel = lineLevel / 2
guard lineLevel <= currentLevel.index else {
return nil
}
while lineLevel < currentLevel.index, let previous = currentLevel.previous {
currentLevel = previous
}
let value: String?
if let valueStartIndex = line.index(rangeOfColon, offsetBy: 1, limitedBy: line.endIndex) {
let substring = line.suffix(from: valueStartIndex).trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
if !substring.isEmpty {
value = substring
} else {
value = nil
}
} else {
value = nil
}
let trimmedKey = key.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
if let value = value {
currentLevel.data[trimmedKey] = value
} else {
let newLevel = PresentationThemeDecodingLevel(data: NSMutableDictionary(), index: currentLevel.index + 1, previous: currentLevel)
currentLevel.data[trimmedKey] = newLevel.data
currentLevel = newLevel
}
}
}
let decoder = PresentationThemeDecoding(referencing: topLevel.data)
decoder.reference = themeReference
decoder.resolvedWallpaper = resolvedWallpaper
if let value = try? decoder.unbox(topLevel.data, as: PresentationTheme.self) {
return value
}
return nil
}
class PresentationThemeDecoding: Decoder {
fileprivate var storage: PresentationThemeDecodingStorage
fileprivate(set) public var codingPath: [CodingKey]
public var userInfo: [CodingUserInfoKey : Any] {
return [:]
}
var reference: PresentationThemeReference?
var referenceTheme: PresentationTheme?
var serviceBackgroundColor: UIColor?
var resolvedWallpaper: TelegramWallpaper?
var fallbackKeys: [String: String] = [:]
private var _referenceCoding: PresentationThemeEncoding?
fileprivate var referenceCoding: PresentationThemeEncoding? {
if let referenceCoding = self._referenceCoding {
return referenceCoding
}
let encoding = PresentationThemeEncoding()
if let theme = self.referenceTheme, let _ = try? theme.encode(to: encoding) {
self._referenceCoding = encoding
return encoding
} else {
return nil
}
}
fileprivate init(referencing container: Any, at codingPath: [CodingKey] = []) {
self.storage = PresentationThemeDecodingStorage()
self.storage.push(container: container)
self.codingPath = codingPath
}
public func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> {
guard !(self.storage.topContainer is NSNull) else {
throw PresentationThemeDecodingError.valueNotFound
}
guard let topContainer = self.storage.topContainer as? [String : Any] else {
throw PresentationThemeDecodingError.typeMismatch
}
let container = PresentationThemeKeyedDecodingContainer<Key>(referencing: self, wrapping: topContainer)
return KeyedDecodingContainer(container)
}
public func unkeyedContainer() throws -> UnkeyedDecodingContainer {
guard !(self.storage.topContainer is NSNull) else {
throw PresentationThemeDecodingError.valueNotFound
}
guard let topContainer = self.storage.topContainer as? [Any] else {
if let topContainer = self.storage.topContainer as? [String : Any] {
let sortedKeys = topContainer.keys.sorted(by: { lhs, rhs in
if let lhsValue = Int(lhs), let rhsValue = Int(rhs), lhsValue < rhsValue {
return true
} else {
return false
}
})
var array: [Any] = []
for key in sortedKeys {
if let value = topContainer[key] {
array.append(value)
}
}
return PresentationThemeUnkeyedDecodingContainer(referencing: self, wrapping: array)
}
throw PresentationThemeDecodingError.typeMismatch
}
return PresentationThemeUnkeyedDecodingContainer(referencing: self, wrapping: topContainer)
}
public func singleValueContainer() throws -> SingleValueDecodingContainer {
return self
}
}
fileprivate struct PresentationThemeDecodingStorage {
private(set) fileprivate var containers: [Any] = []
fileprivate init() {}
fileprivate var count: Int {
return self.containers.count
}
fileprivate var topContainer: Any {
return self.containers.last!
}
fileprivate mutating func push(container: __owned Any) {
self.containers.append(container)
}
fileprivate mutating func popContainer() {
self.containers.removeLast()
}
}
fileprivate struct PresentationThemeKeyedDecodingContainer<K : CodingKey>: KeyedDecodingContainerProtocol {
typealias Key = K
private let decoder: PresentationThemeDecoding
private let container: [String : Any]
private(set) public var codingPath: [CodingKey]
fileprivate init(referencing decoder: PresentationThemeDecoding, wrapping container: [String : Any]) {
self.decoder = decoder
self.container = container
self.codingPath = decoder.codingPath
}
public var allKeys: [Key] {
return self.container.keys.compactMap { Key(stringValue: $0) }
}
public func contains(_ key: Key) -> Bool {
return self.container[key.stringValue] != nil
}
private func _errorDescription(of key: CodingKey) -> String {
return "\(key) (\"\(key.stringValue)\")"
}
public func decodeNil(forKey key: Key) throws -> Bool {
guard let entry = self.container[key.stringValue] else {
throw PresentationThemeDecodingError.keyNotFound
}
return entry is NSNull
}
private func storageEntry(forKey key: [String]) -> Any? {
if let container = self.decoder.storage.containers.first as? [String: Any] {
func entry(container: [String: Any], forKey key: [String]) -> Any? {
if let keyComponent = key.first, let value = container[keyComponent] {
if key.count == 1 {
return value
} else if let subContainer = value as? [String: Any] {
return entry(container: subContainer, forKey: Array(key.suffix(from: 1)))
}
}
return nil
}
return entry(container: container, forKey: key)
} else {
return nil
}
}
private func containerEntry(forKey key: Key) -> Any? {
var containerEntry: Any? = self.container[key.stringValue]
if containerEntry == nil {
let initialKey = self.codingPath.map { $0.stringValue } + [key.stringValue]
let initialKeyString = initialKey.joined(separator: ".")
if let fallbackKeyString = self.decoder.fallbackKeys[initialKeyString] {
let fallbackKey = fallbackKeyString.components(separatedBy: ".")
containerEntry = self.storageEntry(forKey: fallbackKey)
}
if containerEntry == nil {
containerEntry = self.decoder.referenceCoding?.entry(for: initialKey)
}
}
return containerEntry
}
public func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool {
guard let entry = self.containerEntry(forKey: key) else {
throw PresentationThemeDecodingError.keyNotFound
}
self.decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
guard let value = try self.decoder.unbox(entry, as: Bool.self) else {
throw PresentationThemeDecodingError.valueNotFound
}
return value
}
public func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 {
guard let entry = self.containerEntry(forKey: key) else {
throw PresentationThemeDecodingError.keyNotFound
}
self.decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
guard let value = try self.decoder.unbox(entry, as: Int32.self) else {
throw PresentationThemeDecodingError.valueNotFound
}
return value
}
public func decode(_ type: String.Type, forKey key: Key) throws -> String {
guard let entry = self.containerEntry(forKey: key) else {
throw PresentationThemeDecodingError.keyNotFound
}
self.decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
guard let value = try self.decoder.unbox(entry, as: String.self) else {
throw PresentationThemeDecodingError.valueNotFound
}
return value
}
public func decode<T : Decodable>(_ type: T.Type, forKey key: Key) throws -> T {
guard let entry = self.containerEntry(forKey: key) else {
throw PresentationThemeDecodingError.keyNotFound
}
self.decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
guard let value = try self.decoder.unbox(entry, as: type) else {
throw PresentationThemeDecodingError.valueNotFound
}
return value
}
public func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer<NestedKey> {
self.decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
guard let value = self.container[key.stringValue] else {
throw PresentationThemeDecodingError.keyNotFound
}
guard let dictionary = value as? [String : Any] else {
throw PresentationThemeDecodingError.typeMismatch
}
let container = PresentationThemeKeyedDecodingContainer<NestedKey>(referencing: self.decoder, wrapping: dictionary)
return KeyedDecodingContainer(container)
}
public func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
self.decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
guard let value = self.container[key.stringValue] else {
throw PresentationThemeDecodingError.keyNotFound
}
guard let array = value as? [Any] else {
throw PresentationThemeDecodingError.typeMismatch
}
return PresentationThemeUnkeyedDecodingContainer(referencing: self.decoder, wrapping: array)
}
private func _superDecoder(forKey key: __owned CodingKey) throws -> Decoder {
self.decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
let value: Any = self.container[key.stringValue] ?? NSNull()
return PresentationThemeDecoding(referencing: value, at: self.decoder.codingPath)
}
public func superDecoder() throws -> Decoder {
return try _superDecoder(forKey: YAMLKey.super)
}
public func superDecoder(forKey key: Key) throws -> Decoder {
return try _superDecoder(forKey: key)
}
}
fileprivate struct PresentationThemeUnkeyedDecodingContainer: UnkeyedDecodingContainer {
private let decoder: PresentationThemeDecoding
private let container: [Any]
private(set) public var codingPath: [CodingKey]
private(set) public var currentIndex: Int
fileprivate init(referencing decoder: PresentationThemeDecoding, wrapping container: [Any]) {
self.decoder = decoder
self.container = container
self.codingPath = decoder.codingPath
self.currentIndex = 0
}
public var count: Int? {
return self.container.count
}
public var isAtEnd: Bool {
return self.currentIndex >= self.count!
}
public mutating func decodeNil() throws -> Bool {
guard !self.isAtEnd else {
throw PresentationThemeDecodingError.valueNotFound
}
if self.container[self.currentIndex] is NSNull {
self.currentIndex += 1
return true
} else {
return false
}
}
public mutating func decode(_ type: Bool.Type) throws -> Bool {
guard !self.isAtEnd else {
throw PresentationThemeDecodingError.valueNotFound
}
self.decoder.codingPath.append(YAMLKey(index: self.currentIndex))
defer { self.decoder.codingPath.removeLast() }
guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Bool.self) else {
throw PresentationThemeDecodingError.valueNotFound
}
self.currentIndex += 1
return decoded
}
public mutating func decode(_ type: Int32.Type) throws -> Int32 {
guard !self.isAtEnd else {
throw PresentationThemeDecodingError.valueNotFound
}
self.decoder.codingPath.append(YAMLKey(index: self.currentIndex))
defer { self.decoder.codingPath.removeLast() }
guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Int32.self) else {
throw PresentationThemeDecodingError.valueNotFound
}
self.currentIndex += 1
return decoded
}
public mutating func decode(_ type: String.Type) throws -> String {
guard !self.isAtEnd else {
throw PresentationThemeDecodingError.valueNotFound
}
self.decoder.codingPath.append(YAMLKey(index: self.currentIndex))
defer { self.decoder.codingPath.removeLast() }
guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: String.self) else {
throw PresentationThemeDecodingError.valueNotFound
}
self.currentIndex += 1
return decoded
}
public mutating func decode<T : Decodable>(_ type: T.Type) throws -> T {
guard !self.isAtEnd else {
throw PresentationThemeDecodingError.valueNotFound
}
self.decoder.codingPath.append(YAMLKey(index: self.currentIndex))
defer { self.decoder.codingPath.removeLast() }
guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: type) else {
throw PresentationThemeDecodingError.valueNotFound
}
self.currentIndex += 1
return decoded
}
public mutating func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer<NestedKey> {
self.decoder.codingPath.append(YAMLKey(index: self.currentIndex))
defer { self.decoder.codingPath.removeLast() }
guard !self.isAtEnd else {
throw PresentationThemeDecodingError.valueNotFound
}
let value = self.container[self.currentIndex]
guard !(value is NSNull) else {
throw PresentationThemeDecodingError.valueNotFound
}
guard let dictionary = value as? [String : Any] else {
throw PresentationThemeDecodingError.typeMismatch
}
self.currentIndex += 1
let container = PresentationThemeKeyedDecodingContainer<NestedKey>(referencing: self.decoder, wrapping: dictionary)
return KeyedDecodingContainer(container)
}
public mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer {
self.decoder.codingPath.append(YAMLKey(index: self.currentIndex))
defer { self.decoder.codingPath.removeLast() }
guard !self.isAtEnd else {
throw PresentationThemeDecodingError.valueNotFound
}
let value = self.container[self.currentIndex]
guard !(value is NSNull) else {
throw PresentationThemeDecodingError.valueNotFound
}
guard let array = value as? [Any] else {
throw PresentationThemeDecodingError.typeMismatch
}
self.currentIndex += 1
return PresentationThemeUnkeyedDecodingContainer(referencing: self.decoder, wrapping: array)
}
public mutating func superDecoder() throws -> Decoder {
self.decoder.codingPath.append(YAMLKey(index: self.currentIndex))
defer { self.decoder.codingPath.removeLast() }
guard !self.isAtEnd else {
throw PresentationThemeDecodingError.valueNotFound
}
let value = self.container[self.currentIndex]
self.currentIndex += 1
return PresentationThemeDecoding(referencing: value, at: self.decoder.codingPath)
}
}
extension PresentationThemeDecoding: SingleValueDecodingContainer {
private func expectNonNull<T>(_ type: T.Type) throws {
guard !self.decodeNil() else {
throw PresentationThemeDecodingError.valueNotFound
}
}
public func decodeNil() -> Bool {
return self.storage.topContainer is NSNull
}
public func decode(_ type: Bool.Type) throws -> Bool {
try expectNonNull(Bool.self)
return try self.unbox(self.storage.topContainer, as: Bool.self)!
}
public func decode(_ type: Int32.Type) throws -> Int32 {
try expectNonNull(Int32.self)
return try self.unbox(self.storage.topContainer, as: Int32.self)!
}
public func decode(_ type: String.Type) throws -> String {
try expectNonNull(String.self)
return try self.unbox(self.storage.topContainer, as: String.self)!
}
public func decode<T : Decodable>(_ type: T.Type) throws -> T {
try expectNonNull(type)
return try self.unbox(self.storage.topContainer, as: type)!
}
}
extension PresentationThemeDecoding {
fileprivate func unbox(_ value: Any, as type: Bool.Type) throws -> Bool? {
guard !(value is NSNull) else { return nil }
if let number = value as? NSNumber {
if number === kCFBooleanTrue as NSNumber {
return true
} else if number === kCFBooleanFalse as NSNumber {
return false
}
} else if let string = value as? String {
if string.lowercased() == "true" {
return true
} else if string.lowercased() == "false" {
return false
}
}
throw PresentationThemeDecodingError.typeMismatch
}
fileprivate func unbox(_ value: Any, as type: Int32.Type) throws -> Int32? {
guard !(value is NSNull) else { return nil }
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
throw PresentationThemeDecodingError.typeMismatch
}
let int32 = number.int32Value
guard NSNumber(value: int32) == number else {
throw PresentationThemeDecodingError.dataCorrupted
}
return int32
}
fileprivate func unbox(_ value: Any, as type: String.Type) throws -> String? {
guard !(value is NSNull) else { return nil }
guard let string = value as? String else {
throw PresentationThemeDecodingError.typeMismatch
}
return string
}
fileprivate func unbox<T>(_ value: Any, as type: _YAMLStringDictionaryDecodableMarker.Type) throws -> T? {
guard !(value is NSNull) else { return nil }
var result = [String : Any]()
guard let dict = value as? NSDictionary else {
throw PresentationThemeDecodingError.typeMismatch
}
let elementType = type.elementType
for (key, value) in dict {
let key = key as! String
self.codingPath.append(YAMLKey(stringValue: key, intValue: nil))
defer { self.codingPath.removeLast() }
result[key] = try unbox_(value, as: elementType)
}
return result as? T
}
fileprivate func unbox<T : Decodable>(_ value: Any, as type: T.Type) throws -> T? {
return try unbox_(value, as: type) as? T
}
fileprivate func unbox_(_ value: Any, as type: Decodable.Type) throws -> Any? {
if type == Decimal.self || type == NSDecimalNumber.self {
if let value = value as? String {
return Decimal(string: value)
} else {
return try self.unbox(value, as: Decimal.self)
}
} else if let stringKeyedDictType = type as? _YAMLStringDictionaryDecodableMarker.Type {
return try self.unbox(value, as: stringKeyedDictType)
} else {
self.storage.push(container: value)
defer { self.storage.popContainer() }
return try type.init(from: self)
}
}
}
fileprivate struct YAMLKey: CodingKey {
public var stringValue: String
public var intValue: Int?
public init?(stringValue: String) {
self.stringValue = stringValue
self.intValue = nil
}
public init?(intValue: Int) {
self.stringValue = "\(intValue)"
self.intValue = intValue
}
public init(stringValue: String, intValue: Int?) {
self.stringValue = stringValue
self.intValue = intValue
}
fileprivate init(index: Int) {
self.stringValue = "Index \(index)"
self.intValue = index
}
fileprivate static let `super` = YAMLKey(stringValue: "super")!
}
@@ -0,0 +1,648 @@
import Foundation
import UIKit
import Display
import TelegramCore
import TelegramUIPreferences
import AppBundle
func generateCheckImage(partial: Bool, color: UIColor, width: CGFloat) -> UIImage? {
return generateImage(CGSize(width: width, height: floor(width * 9.0 / 11.0)), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.scaleBy(x: width / 11.0, y: width / 11.0)
context.translateBy(x: 1.0, y: 1.0)
context.setStrokeColor(color.cgColor)
context.setLineWidth(0.99)
context.setLineCap(.round)
context.setLineJoin(.round)
if partial {
let _ = try? drawSvgPath(context, path: "M0.5,7 L7,0 S ")
} else {
let _ = try? drawSvgPath(context, path: "M0,4 L2.95157047,6.95157047 L2.95157047,6.95157047 C2.97734507,6.97734507 3.01913396,6.97734507 3.04490857,6.95157047 C3.04548448,6.95099456 3.04604969,6.95040803 3.04660389,6.9498112 L9.5,0 S ")
}
})
}
private func generateClockFrameImage(color: UIColor) -> UIImage? {
return generateImage(CGSize(width: 11.0, height: 11.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(color.cgColor)
context.setFillColor(color.cgColor)
let strokeWidth: CGFloat = 1.0
context.setLineWidth(strokeWidth)
context.strokeEllipse(in: CGRect(x: strokeWidth / 2.0, y: strokeWidth / 2.0, width: size.width - strokeWidth, height: size.height - strokeWidth))
context.fill(CGRect(x: (11.0 - strokeWidth) / 2.0, y: strokeWidth * 3.0, width: strokeWidth, height: 11.0 / 2.0 - strokeWidth * 3.0))
})
}
private func generateClockMinImage(color: UIColor) -> UIImage? {
return generateImage(CGSize(width: 11.0, height: 11.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(color.cgColor)
let strokeWidth: CGFloat = 1.0
context.fill(CGRect(x: (11.0 - strokeWidth) / 2.0, y: (11.0 - strokeWidth) / 2.0, width: 11.0 / 2.0 - strokeWidth, height: strokeWidth))
})
}
public func chatBubbleActionButtonImage(fillColor: UIColor, strokeColor: UIColor, foregroundColor: UIColor, image: UIImage?, iconOffset: CGPoint = CGPoint()) -> UIImage? {
return generateImage(CGSize(width: 29.0, height: 29.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(fillColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
let lineWidth: CGFloat = 1.0
let halfLineWidth = lineWidth / 2.0
var strokeAlpha: CGFloat = 0.0
strokeColor.getRed(nil, green: nil, blue: nil, alpha: &strokeAlpha)
if !strokeAlpha.isZero {
context.setStrokeColor(strokeColor.cgColor)
context.setLineWidth(lineWidth)
context.strokeEllipse(in: CGRect(origin: CGPoint(x: halfLineWidth, y: halfLineWidth), size: CGSize(width: size.width - lineWidth, height: size.width - lineWidth)))
}
if let image = image {
let imageRect = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0) + iconOffset.x, y: floor((size.height - image.size.height) / 2.0) + iconOffset.y), size: image.size)
context.translateBy(x: imageRect.midX, y: imageRect.midY)
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -imageRect.midX, y: -imageRect.midY)
context.clip(to: imageRect, mask: image.cgImage!)
context.setFillColor(foregroundColor.cgColor)
context.fill(imageRect)
}
})
}
public final class PrincipalThemeEssentialGraphics {
public let chatMessageBackgroundIncomingMaskImage: UIImage
public let chatMessageBackgroundIncomingExtractedMaskImage: UIImage
public let chatMessageBackgroundIncomingImage: UIImage
public let chatMessageBackgroundIncomingExtractedImage: UIImage
public let chatMessageBackgroundIncomingOutlineImage: UIImage
public let chatMessageBackgroundIncomingExtractedOutlineImage: UIImage
public let chatMessageBackgroundIncomingShadowImage: UIImage
public let chatMessageBackgroundIncomingHighlightedImage: UIImage
public let chatMessageBackgroundIncomingMergedTopMaskImage: UIImage
public let chatMessageBackgroundIncomingMergedTopImage: UIImage
public let chatMessageBackgroundIncomingMergedTopOutlineImage: UIImage
public let chatMessageBackgroundIncomingMergedTopShadowImage: UIImage
public let chatMessageBackgroundIncomingMergedTopHighlightedImage: UIImage
public let chatMessageBackgroundIncomingMergedTopSideMaskImage: UIImage
public let chatMessageBackgroundIncomingMergedTopSideImage: UIImage
public let chatMessageBackgroundIncomingMergedTopSideOutlineImage: UIImage
public let chatMessageBackgroundIncomingMergedTopSideShadowImage: UIImage
public let chatMessageBackgroundIncomingMergedTopSideHighlightedImage: UIImage
public let chatMessageBackgroundIncomingMergedBottomMaskImage: UIImage
public let chatMessageBackgroundIncomingMergedBottomImage: UIImage
public let chatMessageBackgroundIncomingMergedBottomOutlineImage: UIImage
public let chatMessageBackgroundIncomingMergedBottomShadowImage: UIImage
public let chatMessageBackgroundIncomingMergedBottomHighlightedImage: UIImage
public let chatMessageBackgroundIncomingMergedBothMaskImage: UIImage
public let chatMessageBackgroundIncomingMergedBothImage: UIImage
public let chatMessageBackgroundIncomingMergedBothOutlineImage: UIImage
public let chatMessageBackgroundIncomingMergedBothShadowImage: UIImage
public let chatMessageBackgroundIncomingMergedBothHighlightedImage: UIImage
public let chatMessageBackgroundIncomingMergedSideMaskImage: UIImage
public let chatMessageBackgroundIncomingMergedSideImage: UIImage
public let chatMessageBackgroundIncomingMergedSideOutlineImage: UIImage
public let chatMessageBackgroundIncomingMergedSideShadowImage: UIImage
public let chatMessageBackgroundIncomingMergedSideHighlightedImage: UIImage
public let chatMessageBackgroundOutgoingMaskImage: UIImage
public let chatMessageBackgroundOutgoingExtractedMaskImage: UIImage
public let chatMessageBackgroundOutgoingImage: UIImage
public let chatMessageBackgroundOutgoingExtractedImage: UIImage
public let chatMessageBackgroundOutgoingOutlineImage: UIImage
public let chatMessageBackgroundOutgoingExtractedOutlineImage: UIImage
public let chatMessageBackgroundOutgoingShadowImage: UIImage
public let chatMessageBackgroundOutgoingHighlightedImage: UIImage
public let chatMessageBackgroundOutgoingMergedTopMaskImage: UIImage
public let chatMessageBackgroundOutgoingMergedTopImage: UIImage
public let chatMessageBackgroundOutgoingMergedTopOutlineImage: UIImage
public let chatMessageBackgroundOutgoingMergedTopShadowImage: UIImage
public let chatMessageBackgroundOutgoingMergedTopHighlightedImage: UIImage
public let chatMessageBackgroundOutgoingMergedTopSideMaskImage: UIImage
public let chatMessageBackgroundOutgoingMergedTopSideImage: UIImage
public let chatMessageBackgroundOutgoingMergedTopSideOutlineImage: UIImage
public let chatMessageBackgroundOutgoingMergedTopSideShadowImage: UIImage
public let chatMessageBackgroundOutgoingMergedTopSideHighlightedImage: UIImage
public let chatMessageBackgroundOutgoingMergedBottomMaskImage: UIImage
public let chatMessageBackgroundOutgoingMergedBottomImage: UIImage
public let chatMessageBackgroundOutgoingMergedBottomOutlineImage: UIImage
public let chatMessageBackgroundOutgoingMergedBottomShadowImage: UIImage
public let chatMessageBackgroundOutgoingMergedBottomHighlightedImage: UIImage
public let chatMessageBackgroundOutgoingMergedBothMaskImage: UIImage
public let chatMessageBackgroundOutgoingMergedBothImage: UIImage
public let chatMessageBackgroundOutgoingMergedBothOutlineImage: UIImage
public let chatMessageBackgroundOutgoingMergedBothShadowImage: UIImage
public let chatMessageBackgroundOutgoingMergedBothHighlightedImage: UIImage
public let chatMessageBackgroundOutgoingMergedSideMaskImage: UIImage
public let chatMessageBackgroundOutgoingMergedSideImage: UIImage
public let chatMessageBackgroundOutgoingMergedSideOutlineImage: UIImage
public let chatMessageBackgroundOutgoingMergedSideShadowImage: UIImage
public let chatMessageBackgroundOutgoingMergedSideHighlightedImage: UIImage
public let checkBubbleFullImage: UIImage
public let checkBubblePartialImage: UIImage
public let checkMediaFullImage: UIImage
public let checkMediaPartialImage: UIImage
public let checkFreeFullImage: UIImage
public let checkFreePartialImage: UIImage
public let clockBubbleIncomingFrameImage: UIImage
public let clockBubbleIncomingMinImage: UIImage
public let clockBubbleOutgoingFrameImage: UIImage
public let clockBubbleOutgoingMinImage: UIImage
public let clockMediaFrameImage: UIImage
public let clockMediaMinImage: UIImage
public let clockFreeFrameImage: UIImage
public let clockFreeMinImage: UIImage
public let dateAndStatusMediaBackground: UIImage
public let dateAndStatusFreeBackground: UIImage
public let incomingDateAndStatusImpressionIcon: UIImage
public let outgoingDateAndStatusImpressionIcon: UIImage
public let mediaImpressionIcon: UIImage
public let freeImpressionIcon: UIImage
public let incomingDateAndStatusRepliesIcon: UIImage
public let outgoingDateAndStatusRepliesIcon: UIImage
public let mediaRepliesIcon: UIImage
public let freeRepliesIcon: UIImage
public let incomingDateAndStatusStarsIcon: UIImage
public let outgoingDateAndStatusStarsIcon: UIImage
public let mediaStarsIcon: UIImage
public let freeStarsIcon: UIImage
public let incomingDateAndStatusPinnedIcon: UIImage
public let outgoingDateAndStatusPinnedIcon: UIImage
public let mediaPinnedIcon: UIImage
public let freePinnedIcon: UIImage
public let incomingDateAndStatusSelfExpiringIcon: UIImage
public let outgoingDateAndStatusSelfExpiringIcon: UIImage
public let mediaSelfExpiringIcon: UIImage
public let freeSelfExpiringIcon: UIImage
public let dateStaticBackground: UIImage
public let dateFloatingBackground: UIImage
public let radialIndicatorFileIconIncoming: UIImage
public let radialIndicatorFileIconOutgoing: UIImage
public let radialIndicatorViewOnceIcon: UIImage
public let incomingBubbleGradientImage: UIImage?
public let outgoingBubbleGradientImage: UIImage?
public let hasWallpaper: Bool
init(presentationTheme: PresentationTheme, wallpaper initialWallpaper: TelegramWallpaper, preview: Bool = false, bubbleCorners: PresentationChatBubbleCorners) {
let theme = presentationTheme.chat
let wallpaper = initialWallpaper
self.hasWallpaper = !wallpaper.isEmpty
let incoming: PresentationThemeBubbleColorComponents = wallpaper.isEmpty ? theme.message.incoming.bubble.withoutWallpaper : theme.message.incoming.bubble.withWallpaper
let outgoing: PresentationThemeBubbleColorComponents = wallpaper.isEmpty ? theme.message.outgoing.bubble.withoutWallpaper : theme.message.outgoing.bubble.withWallpaper
var incomingGradientColors: [UIColor]?
if Set(incoming.fill.map(\.argb)).count > 1 {
incomingGradientColors = incoming.fill
}
if let incomingGradientColors = incomingGradientColors {
self.incomingBubbleGradientImage = generateImage(CGSize(width: 1.0, height: 512.0), opaque: true, scale: 1.0, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
var locations: [CGFloat] = []
var colors: [CGColor] = []
for i in 0 ..< incomingGradientColors.count {
let t = CGFloat(i) / CGFloat(incomingGradientColors.count - 1)
locations.append(t)
colors.append(incomingGradientColors[i].cgColor)
}
let colorSpace = deviceColorSpace
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as NSArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
})
} else {
self.incomingBubbleGradientImage = nil
}
var outgoingGradientColors: [UIColor]?
if Set(outgoing.fill.map(\.argb)).count > 1 {
outgoingGradientColors = outgoing.fill
}
if let outgoingGradientColors = outgoingGradientColors {
self.outgoingBubbleGradientImage = generateImage(CGSize(width: 1.0, height: 512.0), opaque: true, scale: 1.0, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
var locations: [CGFloat] = []
var colors: [CGColor] = []
for i in 0 ..< outgoingGradientColors.count {
let t = CGFloat(i) / CGFloat(outgoingGradientColors.count - 1)
locations.append(t)
colors.append(outgoingGradientColors[i].cgColor)
}
let colorSpace = deviceColorSpace
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as NSArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
})
} else {
self.outgoingBubbleGradientImage = nil
}
let incomingKnockout = self.incomingBubbleGradientImage != nil
let outgoingKnockout = self.outgoingBubbleGradientImage != nil
let highlightKnockout: Bool
highlightKnockout = false
let serviceColor = serviceMessageColorComponents(chatTheme: theme, wallpaper: wallpaper)
let maxCornerRadius = bubbleCorners.mainRadius
let minCornerRadius = (bubbleCorners.mergeBubbleCorners && maxCornerRadius >= 10.0) ? bubbleCorners.auxiliaryRadius : bubbleCorners.mainRadius
let emptyImage = UIImage()
if preview {
self.chatMessageBackgroundIncomingMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .none, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundIncomingExtractedMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: UIColor.black, strokeColor: UIColor.clear, neighbors: .extracted, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundIncomingImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true)
self.chatMessageBackgroundIncomingExtractedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true)
self.chatMessageBackgroundIncomingOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundIncomingExtractedOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundIncomingShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true)
self.chatMessageBackgroundOutgoingMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .none, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundOutgoingExtractedMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .extracted, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundOutgoingImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
self.chatMessageBackgroundOutgoingExtractedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
self.chatMessageBackgroundOutgoingOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundOutgoingExtractedOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundOutgoingShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true)
self.checkBubbleFullImage = generateCheckImage(partial: false, color: theme.message.outgoingCheckColor, width: 11.0)!
self.checkBubblePartialImage = generateCheckImage(partial: true, color: theme.message.outgoingCheckColor, width: 11.0)!
self.chatMessageBackgroundIncomingHighlightedImage = emptyImage
self.chatMessageBackgroundIncomingMergedTopMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .top(side: false), theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundIncomingMergedTopImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true)
self.chatMessageBackgroundIncomingMergedTopOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundIncomingMergedTopShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true)
self.chatMessageBackgroundIncomingMergedTopHighlightedImage = emptyImage
self.chatMessageBackgroundIncomingMergedTopSideMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .top(side: true), theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundIncomingMergedTopSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true)
self.chatMessageBackgroundIncomingMergedTopSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundIncomingMergedTopSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true)
self.chatMessageBackgroundIncomingMergedTopSideHighlightedImage = emptyImage
self.chatMessageBackgroundIncomingMergedBottomMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .bottom, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundIncomingMergedBottomImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true)
self.chatMessageBackgroundIncomingMergedBottomOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundIncomingMergedBottomShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true)
self.chatMessageBackgroundIncomingMergedBottomHighlightedImage = emptyImage
self.chatMessageBackgroundIncomingMergedBothMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .both, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundIncomingMergedBothImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true)
self.chatMessageBackgroundIncomingMergedBothOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundIncomingMergedBothShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true)
self.chatMessageBackgroundIncomingMergedBothHighlightedImage = emptyImage
self.chatMessageBackgroundIncomingMergedSideMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .side, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundIncomingMergedSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
self.chatMessageBackgroundIncomingMergedSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundIncomingMergedSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true)
self.chatMessageBackgroundOutgoingMergedSideMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .side, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundIncomingMergedSideHighlightedImage = emptyImage
self.chatMessageBackgroundOutgoingHighlightedImage = emptyImage
self.chatMessageBackgroundOutgoingMergedTopMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .top(side: false), theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundOutgoingMergedTopImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
self.chatMessageBackgroundOutgoingMergedTopOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundOutgoingMergedTopShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true)
self.chatMessageBackgroundOutgoingMergedTopHighlightedImage = emptyImage
self.chatMessageBackgroundOutgoingMergedTopSideMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .top(side: true), theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundOutgoingMergedTopSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
self.chatMessageBackgroundOutgoingMergedTopSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundOutgoingMergedTopSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true)
self.chatMessageBackgroundOutgoingMergedTopSideHighlightedImage = emptyImage
self.chatMessageBackgroundOutgoingMergedBottomMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .bottom, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundOutgoingMergedBottomImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
self.chatMessageBackgroundOutgoingMergedBottomOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundOutgoingMergedBottomShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true)
self.chatMessageBackgroundOutgoingMergedBottomHighlightedImage = emptyImage
self.chatMessageBackgroundOutgoingMergedBothMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .both, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundOutgoingMergedBothImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
self.chatMessageBackgroundOutgoingMergedBothOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundOutgoingMergedBothShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true)
self.chatMessageBackgroundOutgoingMergedBothHighlightedImage = emptyImage
self.chatMessageBackgroundOutgoingMergedSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
self.chatMessageBackgroundOutgoingMergedSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundOutgoingMergedSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true)
self.chatMessageBackgroundOutgoingMergedSideHighlightedImage = emptyImage
self.checkMediaFullImage = emptyImage
self.checkMediaPartialImage = emptyImage
self.checkFreeFullImage = emptyImage
self.checkFreePartialImage = emptyImage
self.clockBubbleIncomingFrameImage = emptyImage
self.clockBubbleIncomingMinImage = emptyImage
self.clockBubbleOutgoingFrameImage = emptyImage
self.clockBubbleOutgoingMinImage = emptyImage
self.clockMediaFrameImage = emptyImage
self.clockMediaMinImage = emptyImage
self.clockFreeFrameImage = emptyImage
self.clockFreeMinImage = emptyImage
self.dateAndStatusMediaBackground = generateStretchableFilledCircleImage(diameter: 18.0, color: theme.message.mediaDateAndStatusFillColor)!
self.dateAndStatusFreeBackground = generateStretchableFilledCircleImage(diameter: 18.0, color: serviceColor.dateFillStatic)!
let impressionCountImage = UIImage(bundleImageName: "Chat/Message/ImpressionCount")!
self.incomingDateAndStatusImpressionIcon = generateTintedImage(image: impressionCountImage, color: theme.message.incoming.secondaryTextColor)!
self.outgoingDateAndStatusImpressionIcon = generateTintedImage(image: impressionCountImage, color: theme.message.outgoing.secondaryTextColor)!
self.mediaImpressionIcon = generateTintedImage(image: impressionCountImage, color: .white)!
self.freeImpressionIcon = generateTintedImage(image: impressionCountImage, color: serviceColor.primaryText)!
let repliesImage = UIImage(bundleImageName: "Chat/Message/ReplyCount")!
self.incomingDateAndStatusRepliesIcon = generateTintedImage(image: repliesImage, color: theme.message.incoming.secondaryTextColor)!
self.outgoingDateAndStatusRepliesIcon = generateTintedImage(image: repliesImage, color: theme.message.outgoing.secondaryTextColor)!
self.mediaRepliesIcon = generateTintedImage(image: repliesImage, color: .white)!
self.freeRepliesIcon = generateTintedImage(image: repliesImage, color: serviceColor.primaryText)!
let starsImage = UIImage(bundleImageName: "Chat/Message/StarsCount")!
self.incomingDateAndStatusStarsIcon = generateTintedImage(image: starsImage, color: theme.message.incoming.secondaryTextColor)!
self.outgoingDateAndStatusStarsIcon = generateTintedImage(image: starsImage, color: theme.message.outgoing.secondaryTextColor)!
self.mediaStarsIcon = generateTintedImage(image: starsImage, color: .white)!
self.freeStarsIcon = generateTintedImage(image: starsImage, color: serviceColor.primaryText)!
let pinnedImage = UIImage(bundleImageName: "Chat/Message/Pinned")!
self.incomingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.incoming.secondaryTextColor)!
self.outgoingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.outgoing.secondaryTextColor)!
self.mediaPinnedIcon = generateTintedImage(image: pinnedImage, color: .white)!
self.freePinnedIcon = generateTintedImage(image: pinnedImage, color: serviceColor.primaryText)!
let selfExpiringImage = UIImage(bundleImageName: "Chat/Message/SelfExpiring")!
self.incomingDateAndStatusSelfExpiringIcon = generateTintedImage(image: selfExpiringImage, color: theme.message.incoming.secondaryTextColor)!
self.outgoingDateAndStatusSelfExpiringIcon = generateTintedImage(image: selfExpiringImage, color: theme.message.outgoing.secondaryTextColor)!
self.mediaSelfExpiringIcon = generateTintedImage(image: selfExpiringImage, color: .white)!
self.freeSelfExpiringIcon = generateTintedImage(image: selfExpiringImage, color: serviceColor.primaryText)!
self.radialIndicatorFileIconIncoming = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/RadialProgressIconDocument"), color: .black)!
self.radialIndicatorFileIconOutgoing = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/RadialProgressIconDocument"), color: .black)!
self.radialIndicatorViewOnceIcon = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/ViewOnce"), color: .black)!
} else {
self.chatMessageBackgroundIncomingMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .none, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundIncomingExtractedMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .extracted, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundIncomingImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true)
self.chatMessageBackgroundIncomingExtractedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true)
self.chatMessageBackgroundIncomingOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundIncomingExtractedOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundIncomingShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true)
self.chatMessageBackgroundIncomingHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .white, strokeColor: .clear, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true).withRenderingMode(.alwaysTemplate)
self.chatMessageBackgroundIncomingMergedTopMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .top(side: false), theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundIncomingMergedTopImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true)
self.chatMessageBackgroundIncomingMergedTopOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundIncomingMergedTopShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true)
self.chatMessageBackgroundIncomingMergedTopHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .white, strokeColor: .clear, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true).withRenderingMode(.alwaysTemplate)
self.chatMessageBackgroundIncomingMergedTopSideMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .top(side: true), theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundIncomingMergedTopSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true)
self.chatMessageBackgroundIncomingMergedTopSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundIncomingMergedTopSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true)
self.chatMessageBackgroundIncomingMergedTopSideHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .white, strokeColor: .clear, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true).withRenderingMode(.alwaysTemplate)
self.chatMessageBackgroundIncomingMergedBottomMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .bottom, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundIncomingMergedBottomImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true)
self.chatMessageBackgroundIncomingMergedBottomOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundIncomingMergedBottomShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true)
self.chatMessageBackgroundIncomingMergedBottomHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .white, strokeColor: .clear, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true).withRenderingMode(.alwaysTemplate)
self.chatMessageBackgroundIncomingMergedBothMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .both, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundIncomingMergedBothImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true)
self.chatMessageBackgroundIncomingMergedBothOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundIncomingMergedBothShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true)
self.chatMessageBackgroundIncomingMergedBothHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .white, strokeColor: .clear, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true).withRenderingMode(.alwaysTemplate)
self.chatMessageBackgroundOutgoingMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .none, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundOutgoingExtractedMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .extracted, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundOutgoingImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
self.chatMessageBackgroundOutgoingExtractedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
self.chatMessageBackgroundOutgoingOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundOutgoingExtractedOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundOutgoingShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true)
self.chatMessageBackgroundOutgoingHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .white, strokeColor: .clear, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true).withRenderingMode(.alwaysTemplate)
self.chatMessageBackgroundOutgoingMergedTopMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .top(side: false), theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundOutgoingMergedTopImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
self.chatMessageBackgroundOutgoingMergedTopOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundOutgoingMergedTopShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true)
self.chatMessageBackgroundOutgoingMergedTopHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .white, strokeColor: .clear, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true).withRenderingMode(.alwaysTemplate)
self.chatMessageBackgroundOutgoingMergedTopSideMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .top(side: true), theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundOutgoingMergedTopSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
self.chatMessageBackgroundOutgoingMergedTopSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundOutgoingMergedTopSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true)
self.chatMessageBackgroundOutgoingMergedTopSideHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .white, strokeColor: .clear, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true).withRenderingMode(.alwaysTemplate)
self.chatMessageBackgroundOutgoingMergedBottomMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .bottom, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundOutgoingMergedBottomImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
self.chatMessageBackgroundOutgoingMergedBottomOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundOutgoingMergedBottomShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true)
self.chatMessageBackgroundOutgoingMergedBottomHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .white, strokeColor: .clear, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true).withRenderingMode(.alwaysTemplate)
self.chatMessageBackgroundOutgoingMergedBothMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .both, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundOutgoingMergedBothImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
self.chatMessageBackgroundOutgoingMergedBothOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundOutgoingMergedBothShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true)
self.chatMessageBackgroundOutgoingMergedBothHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .white, strokeColor: .clear, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true).withRenderingMode(.alwaysTemplate)
self.chatMessageBackgroundIncomingMergedSideMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .side, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundIncomingMergedSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true)
self.chatMessageBackgroundIncomingMergedSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundIncomingMergedSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true)
self.chatMessageBackgroundOutgoingMergedSideMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .side, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
self.chatMessageBackgroundOutgoingMergedSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
self.chatMessageBackgroundOutgoingMergedSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
self.chatMessageBackgroundOutgoingMergedSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true)
self.chatMessageBackgroundIncomingMergedSideHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .white, strokeColor: .clear, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true).withRenderingMode(.alwaysTemplate)
self.chatMessageBackgroundOutgoingMergedSideHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .white, strokeColor: .clear, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true).withRenderingMode(.alwaysTemplate)
self.checkBubbleFullImage = generateCheckImage(partial: false, color: theme.message.outgoingCheckColor, width: 11.0)!
self.checkBubblePartialImage = generateCheckImage(partial: true, color: theme.message.outgoingCheckColor, width: 11.0)!
self.checkMediaFullImage = generateCheckImage(partial: false, color: .white, width: 11.0)!
self.checkMediaPartialImage = generateCheckImage(partial: true, color: .white, width: 11.0)!
self.checkFreeFullImage = generateCheckImage(partial: false, color: serviceColor.primaryText, width: 11.0)!
self.checkFreePartialImage = generateCheckImage(partial: true, color: serviceColor.primaryText, width: 11.0)!
self.clockBubbleIncomingFrameImage = generateClockFrameImage(color: theme.message.incoming.pendingActivityColor)!
self.clockBubbleIncomingMinImage = generateClockMinImage(color: theme.message.incoming.pendingActivityColor)!
self.clockBubbleOutgoingFrameImage = generateClockFrameImage(color: theme.message.outgoing.pendingActivityColor)!
self.clockBubbleOutgoingMinImage = generateClockMinImage(color: theme.message.outgoing.pendingActivityColor)!
self.clockMediaFrameImage = generateClockFrameImage(color: .white)!
self.clockMediaMinImage = generateClockMinImage(color: .white)!
self.clockFreeFrameImage = generateClockFrameImage(color: serviceColor.primaryText)!
self.clockFreeMinImage = generateClockMinImage(color: serviceColor.primaryText)!
self.dateAndStatusMediaBackground = generateStretchableFilledCircleImage(diameter: 18.0, color: theme.message.mediaDateAndStatusFillColor)!
self.dateAndStatusFreeBackground = generateStretchableFilledCircleImage(diameter: 18.0, color: serviceColor.dateFillStatic)!
let impressionCountImage = UIImage(bundleImageName: "Chat/Message/ImpressionCount")!
self.incomingDateAndStatusImpressionIcon = generateTintedImage(image: impressionCountImage, color: theme.message.incoming.secondaryTextColor)!
self.outgoingDateAndStatusImpressionIcon = generateTintedImage(image: impressionCountImage, color: theme.message.outgoing.secondaryTextColor)!
self.mediaImpressionIcon = generateTintedImage(image: impressionCountImage, color: .white)!
self.freeImpressionIcon = generateTintedImage(image: impressionCountImage, color: serviceColor.primaryText)!
let repliesImage = UIImage(bundleImageName: "Chat/Message/ReplyCount")!
self.incomingDateAndStatusRepliesIcon = generateTintedImage(image: repliesImage, color: theme.message.incoming.secondaryTextColor)!
self.outgoingDateAndStatusRepliesIcon = generateTintedImage(image: repliesImage, color: theme.message.outgoing.secondaryTextColor)!
self.mediaRepliesIcon = generateTintedImage(image: repliesImage, color: .white)!
self.freeRepliesIcon = generateTintedImage(image: repliesImage, color: serviceColor.primaryText)!
let starsImage = UIImage(bundleImageName: "Chat/Message/StarsCount")!
self.incomingDateAndStatusStarsIcon = generateTintedImage(image: starsImage, color: theme.message.incoming.secondaryTextColor)!
self.outgoingDateAndStatusStarsIcon = generateTintedImage(image: starsImage, color: theme.message.outgoing.secondaryTextColor)!
self.mediaStarsIcon = generateTintedImage(image: starsImage, color: .white)!
self.freeStarsIcon = generateTintedImage(image: starsImage, color: serviceColor.primaryText)!
let pinnedImage = UIImage(bundleImageName: "Chat/Message/Pinned")!
self.incomingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.incoming.secondaryTextColor)!
self.outgoingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.outgoing.secondaryTextColor)!
self.mediaPinnedIcon = generateTintedImage(image: pinnedImage, color: .white)!
self.freePinnedIcon = generateTintedImage(image: pinnedImage, color: serviceColor.primaryText)!
let selfExpiringImage = UIImage(bundleImageName: "Chat/Message/SelfExpiring")!
self.incomingDateAndStatusSelfExpiringIcon = generateTintedImage(image: selfExpiringImage, color: theme.message.incoming.secondaryTextColor)!
self.outgoingDateAndStatusSelfExpiringIcon = generateTintedImage(image: selfExpiringImage, color: theme.message.outgoing.secondaryTextColor)!
self.mediaSelfExpiringIcon = generateTintedImage(image: selfExpiringImage, color: .white)!
self.freeSelfExpiringIcon = generateTintedImage(image: selfExpiringImage, color: serviceColor.primaryText)!
self.radialIndicatorFileIconIncoming = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/RadialProgressIconDocument"), color: .black)!
self.radialIndicatorFileIconOutgoing = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/RadialProgressIconDocument"), color: .black)!
self.radialIndicatorViewOnceIcon = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/ViewOnce"), color: .black)!
}
let chatDateSize: CGFloat = 20.0
self.dateStaticBackground = generateImage(CGSize(width: chatDateSize, height: chatDateSize), contextGenerator: { size, context -> Void in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(serviceColor.dateFillStatic.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
})!.stretchableImage(withLeftCapWidth: Int(chatDateSize) / 2, topCapHeight: Int(chatDateSize) / 2)
self.dateFloatingBackground = generateImage(CGSize(width: chatDateSize, height: chatDateSize), contextGenerator: { size, context -> Void in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(serviceColor.dateFillFloating.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
})!.stretchableImage(withLeftCapWidth: Int(chatDateSize) / 2, topCapHeight: Int(chatDateSize) / 2)
}
}
public final class PrincipalThemeAdditionalGraphics {
public let chatServiceBubbleFillImage: UIImage
public let chatServiceVerticalLineImage: UIImage
public let chatFreeformContentAdditionalInfoBackgroundImage: UIImage
public let chatEmptyItemBackgroundImage: UIImage
public let chatLoadingIndicatorBackgroundImage: UIImage
public let chatBubbleNavigateButtonImage: UIImage
public let chatBubbleActionButtonIncomingMiddleImage: UIImage
public let chatBubbleActionButtonMiddleMaskImage: UIImage
public let chatBubbleActionButtonIncomingBottomLeftImage: UIImage
public let chatBubbleActionButtonBottomLeftMaskImage: UIImage
public let chatBubbleActionButtonIncomingBottomRightImage: UIImage
public let chatBubbleActionButtonBottomRightMaskImage: UIImage
public let chatBubbleActionButtonIncomingBottomSingleImage: UIImage
public let chatBubbleActionButtonBottomSingleMaskImage: UIImage
public let chatBubbleActionButtonOutgoingMiddleImage: UIImage
public let chatBubbleActionButtonOutgoingBottomLeftImage: UIImage
public let chatBubbleActionButtonOutgoingBottomRightImage: UIImage
public let chatBubbleActionButtonOutgoingBottomSingleImage: UIImage
public let chatBubbleActionButtonIncomingMessageIconImage: UIImage
public let chatBubbleActionButtonIncomingLinkIconImage: UIImage
public let chatBubbleActionButtonIncomingShareIconImage: UIImage
public let chatBubbleActionButtonIncomingPhoneIconImage: UIImage
public let chatBubbleActionButtonIncomingLocationIconImage: UIImage
public let chatBubbleActionButtonIncomingPaymentIconImage: UIImage
public let chatBubbleActionButtonIncomingProfileIconImage: UIImage
public let chatBubbleActionButtonIncomingAddToChatIconImage: UIImage
public let chatBubbleActionButtonIncomingWebAppIconImage: UIImage
public let chatBubbleActionButtonIncomingCopyIconImage: UIImage
public let chatBubbleActionButtonOutgoingMessageIconImage: UIImage
public let chatBubbleActionButtonOutgoingLinkIconImage: UIImage
public let chatBubbleActionButtonOutgoingShareIconImage: UIImage
public let chatBubbleActionButtonOutgoingPhoneIconImage: UIImage
public let chatBubbleActionButtonOutgoingLocationIconImage: UIImage
public let chatBubbleActionButtonOutgoingPaymentIconImage: UIImage
public let chatBubbleActionButtonOutgoingProfileIconImage: UIImage
public let chatBubbleActionButtonOutgoingAddToChatIconImage: UIImage
public let chatBubbleActionButtonOutgoingWebAppIconImage: UIImage
public let chatBubbleActionButtonOutgoingCopyIconImage: UIImage
public let chatEmptyItemLockIcon: UIImage
public let emptyChatListCheckIcon: UIImage
init(_ theme: PresentationThemeChat, wallpaper: TelegramWallpaper, bubbleCorners: PresentationChatBubbleCorners) {
let serviceColor = serviceMessageColorComponents(chatTheme: theme, wallpaper: wallpaper)
self.chatServiceBubbleFillImage = generateImage(CGSize(width: 20.0, height: 20.0), contextGenerator: { size, context -> Void in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(serviceColor.fill.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
})!.stretchableImage(withLeftCapWidth: 8, topCapHeight: 8)
self.chatServiceVerticalLineImage = generateImage(CGSize(width: 2.0, height: 3.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(serviceColor.primaryText.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: 2.0, height: 2.0)))
context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: 1.0), size: CGSize(width: 2.0, height: 2.0)))
})!.stretchableImage(withLeftCapWidth: 0, topCapHeight: 1)
self.chatFreeformContentAdditionalInfoBackgroundImage = generateStretchableFilledCircleImage(radius: 10.0, color: serviceColor.fill)!
self.chatEmptyItemBackgroundImage = generateStretchableFilledCircleImage(radius: 14.0, color: serviceColor.fill)!
self.chatLoadingIndicatorBackgroundImage = generateStretchableFilledCircleImage(diameter: 30.0, color: serviceColor.fill)!
self.chatBubbleNavigateButtonImage = chatBubbleActionButtonImage(fillColor: bubbleVariableColor(variableColor: theme.message.shareButtonFillColor, wallpaper: wallpaper), strokeColor: bubbleVariableColor(variableColor: theme.message.shareButtonStrokeColor, wallpaper: wallpaper), foregroundColor: bubbleVariableColor(variableColor: theme.message.shareButtonForegroundColor, wallpaper: wallpaper), image: UIImage(bundleImageName: "Chat/Message/NavigateToMessageIcon"), iconOffset: CGPoint(x: 0.0, y: 1.0))!
self.chatBubbleActionButtonIncomingMiddleImage = messageBubbleActionButtonImage(color: bubbleVariableColor(variableColor: theme.message.incoming.actionButtonsFillColor, wallpaper: wallpaper), strokeColor: bubbleVariableColor(variableColor: theme.message.incoming.actionButtonsStrokeColor, wallpaper: wallpaper), position: .middle, bubbleCorners: bubbleCorners)
self.chatBubbleActionButtonMiddleMaskImage = messageBubbleActionButtonImage(color: .white, strokeColor: .clear, position: .middle, bubbleCorners: bubbleCorners)
self.chatBubbleActionButtonIncomingBottomLeftImage = messageBubbleActionButtonImage(color: bubbleVariableColor(variableColor: theme.message.incoming.actionButtonsFillColor, wallpaper: wallpaper), strokeColor: bubbleVariableColor(variableColor: theme.message.incoming.actionButtonsStrokeColor, wallpaper: wallpaper), position: .bottomLeft, bubbleCorners: bubbleCorners)
self.chatBubbleActionButtonBottomLeftMaskImage = messageBubbleActionButtonImage(color: .black, strokeColor: .clear, position: .bottomLeft, bubbleCorners: bubbleCorners)
self.chatBubbleActionButtonIncomingBottomRightImage = messageBubbleActionButtonImage(color: bubbleVariableColor(variableColor: theme.message.incoming.actionButtonsFillColor, wallpaper: wallpaper), strokeColor: bubbleVariableColor(variableColor: theme.message.incoming.actionButtonsStrokeColor, wallpaper: wallpaper), position: .bottomRight, bubbleCorners: bubbleCorners)
self.chatBubbleActionButtonBottomRightMaskImage = messageBubbleActionButtonImage(color: .black, strokeColor: .clear, position: .bottomRight, bubbleCorners: bubbleCorners)
self.chatBubbleActionButtonIncomingBottomSingleImage = messageBubbleActionButtonImage(color: bubbleVariableColor(variableColor: theme.message.incoming.actionButtonsFillColor, wallpaper: wallpaper), strokeColor: bubbleVariableColor(variableColor: theme.message.incoming.actionButtonsStrokeColor, wallpaper: wallpaper), position: .bottomSingle, bubbleCorners: bubbleCorners)
self.chatBubbleActionButtonBottomSingleMaskImage = messageBubbleActionButtonImage(color: .black, strokeColor: .clear, position: .bottomSingle, bubbleCorners: bubbleCorners)
self.chatBubbleActionButtonOutgoingMiddleImage = messageBubbleActionButtonImage(color: bubbleVariableColor(variableColor: theme.message.outgoing.actionButtonsFillColor, wallpaper: wallpaper), strokeColor: bubbleVariableColor(variableColor: theme.message.outgoing.actionButtonsStrokeColor, wallpaper: wallpaper), position: .middle, bubbleCorners: bubbleCorners)
self.chatBubbleActionButtonOutgoingBottomLeftImage = messageBubbleActionButtonImage(color: bubbleVariableColor(variableColor: theme.message.outgoing.actionButtonsFillColor, wallpaper: wallpaper), strokeColor: bubbleVariableColor(variableColor: theme.message.outgoing.actionButtonsStrokeColor, wallpaper: wallpaper), position: .bottomLeft, bubbleCorners: bubbleCorners)
self.chatBubbleActionButtonOutgoingBottomRightImage = messageBubbleActionButtonImage(color: bubbleVariableColor(variableColor: theme.message.outgoing.actionButtonsFillColor, wallpaper: wallpaper), strokeColor: bubbleVariableColor(variableColor: theme.message.outgoing.actionButtonsStrokeColor, wallpaper: wallpaper), position: .bottomRight, bubbleCorners: bubbleCorners)
self.chatBubbleActionButtonOutgoingBottomSingleImage = messageBubbleActionButtonImage(color: bubbleVariableColor(variableColor: theme.message.outgoing.actionButtonsFillColor, wallpaper: wallpaper), strokeColor: bubbleVariableColor(variableColor: theme.message.outgoing.actionButtonsStrokeColor, wallpaper: wallpaper), position: .bottomSingle, bubbleCorners: bubbleCorners)
self.chatBubbleActionButtonIncomingMessageIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotMessage"), color: bubbleVariableColor(variableColor: theme.message.incoming.actionButtonsTextColor, wallpaper: wallpaper))!
self.chatBubbleActionButtonIncomingLinkIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotLink"), color: bubbleVariableColor(variableColor: theme.message.incoming.actionButtonsTextColor, wallpaper: wallpaper))!
self.chatBubbleActionButtonIncomingShareIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotShare"), color: bubbleVariableColor(variableColor: theme.message.incoming.actionButtonsTextColor, wallpaper: wallpaper))!
self.chatBubbleActionButtonIncomingPhoneIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotPhone"), color: bubbleVariableColor(variableColor: theme.message.incoming.actionButtonsTextColor, wallpaper: wallpaper))!
self.chatBubbleActionButtonIncomingLocationIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotLocation"), color: bubbleVariableColor(variableColor: theme.message.incoming.actionButtonsTextColor, wallpaper: wallpaper))!
self.chatBubbleActionButtonIncomingPaymentIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotPayment"), color: bubbleVariableColor(variableColor: theme.message.incoming.actionButtonsTextColor, wallpaper: wallpaper))!
self.chatBubbleActionButtonIncomingProfileIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotProfile"), color: bubbleVariableColor(variableColor: theme.message.incoming.actionButtonsTextColor, wallpaper: wallpaper))!
self.chatBubbleActionButtonIncomingAddToChatIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotAddToChat"), color: bubbleVariableColor(variableColor: theme.message.incoming.actionButtonsTextColor, wallpaper: wallpaper))!
self.chatBubbleActionButtonIncomingWebAppIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotWebApp"), color: bubbleVariableColor(variableColor: theme.message.incoming.actionButtonsTextColor, wallpaper: wallpaper))!
self.chatBubbleActionButtonIncomingCopyIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotCopy"), color: bubbleVariableColor(variableColor: theme.message.incoming.actionButtonsTextColor, wallpaper: wallpaper))!
self.chatBubbleActionButtonOutgoingMessageIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotMessage"), color: bubbleVariableColor(variableColor: theme.message.outgoing.actionButtonsTextColor, wallpaper: wallpaper))!
self.chatBubbleActionButtonOutgoingLinkIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotLink"), color: bubbleVariableColor(variableColor: theme.message.outgoing.actionButtonsTextColor, wallpaper: wallpaper))!
self.chatBubbleActionButtonOutgoingShareIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotShare"), color: bubbleVariableColor(variableColor: theme.message.outgoing.actionButtonsTextColor, wallpaper: wallpaper))!
self.chatBubbleActionButtonOutgoingPhoneIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotPhone"), color: bubbleVariableColor(variableColor: theme.message.outgoing.actionButtonsTextColor, wallpaper: wallpaper))!
self.chatBubbleActionButtonOutgoingLocationIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotLocation"), color: bubbleVariableColor(variableColor: theme.message.outgoing.actionButtonsTextColor, wallpaper: wallpaper))!
self.chatBubbleActionButtonOutgoingPaymentIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotPayment"), color: bubbleVariableColor(variableColor: theme.message.outgoing.actionButtonsTextColor, wallpaper: wallpaper))!
self.chatBubbleActionButtonOutgoingProfileIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotProfile"), color: bubbleVariableColor(variableColor: theme.message.outgoing.actionButtonsTextColor, wallpaper: wallpaper))!
self.chatBubbleActionButtonOutgoingAddToChatIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotAddToChat"), color: bubbleVariableColor(variableColor: theme.message.outgoing.actionButtonsTextColor, wallpaper: wallpaper))!
self.chatBubbleActionButtonOutgoingWebAppIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotWebApp"), color: bubbleVariableColor(variableColor: theme.message.outgoing.actionButtonsTextColor, wallpaper: wallpaper))!
self.chatBubbleActionButtonOutgoingCopyIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotCopy"), color: bubbleVariableColor(variableColor: theme.message.outgoing.actionButtonsTextColor, wallpaper: wallpaper))!
self.chatEmptyItemLockIcon = generateImage(CGSize(width: 9.0, height: 13.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.translateBy(x: 0.0, y: 1.0)
context.setFillColor(serviceColor.primaryText.cgColor)
context.setStrokeColor(serviceColor.primaryText.cgColor)
context.setLineWidth(1.32)
let _ = try? drawSvgPath(context, path: "M4.5,0.600000024 C5.88071187,0.600000024 7,1.88484952 7,3.46979169 L7,7.39687502 C7,8.9818172 5.88071187,10.2666667 4.5,10.2666667 C3.11928813,10.2666667 2,8.9818172 2,7.39687502 L2,3.46979169 C2,1.88484952 3.11928813,0.600000024 4.5,0.600000024 S ")
let _ = try? drawSvgPath(context, path: "M1.32,5.65999985 L7.68,5.65999985 C8.40901587,5.65999985 9,6.25098398 9,6.97999985 L9,10.6733332 C9,11.4023491 8.40901587,11.9933332 7.68,11.9933332 L1.32,11.9933332 C0.59098413,11.9933332 1.11022302e-16,11.4023491 0,10.6733332 L2.22044605e-16,6.97999985 C1.11022302e-16,6.25098398 0.59098413,5.65999985 1.32,5.65999985 Z ")
})!
self.emptyChatListCheckIcon = generateTintedImage(image: UIImage(bundleImageName: "Chat/Empty Chat/ListCheckIcon"), color: serviceColor.primaryText)!
}
}
@@ -0,0 +1,131 @@
import Foundation
import UIKit
import SwiftSignalKit
#if DEBUG
private final class CacheStats {
var totalSize: Int = 0
var reportSize: Int = 0
func logImage(image: UIImage, added: Bool) {
let sign = added ? 1 : -1
self.totalSize += sign * Int(image.size.width * image.scale) * Int(image.size.height * image.scale) * 4
if abs(self.totalSize - self.reportSize) >= 1024 * 1024 {
self.reportSize = self.totalSize
print("UI resource cache: \((self.totalSize) / (1024 * 1024)) MB")
}
}
}
private let cacheStats = Atomic<CacheStats>(value: CacheStats())
#endif
private final class PresentationsResourceCacheHolder {
var images: [Int32: UIImage] = [:]
var parameterImages: [PresentationResourceParameterKey: UIImage] = [:]
deinit {
#if DEBUG
cacheStats.with { cacheStats in
for (_, image) in self.images {
cacheStats.logImage(image: image, added: false)
}
for (_, image) in self.parameterImages {
cacheStats.logImage(image: image, added: false)
}
}
#endif
}
func logAddedImage(image: UIImage) {
#if DEBUG
cacheStats.with { cacheStats in
cacheStats.logImage(image: image, added: true)
}
#endif
}
}
private final class PresentationsResourceAnyCacheHolder {
var objects: [Int32: AnyObject] = [:]
var parameterObjects: [PresentationResourceParameterKey: AnyObject] = [:]
}
public final class PresentationsResourceCache {
private let imageCache = Atomic<PresentationsResourceCacheHolder>(value: PresentationsResourceCacheHolder())
private let objectCache = Atomic<PresentationsResourceAnyCacheHolder>(value: PresentationsResourceAnyCacheHolder())
public func image(_ key: Int32, _ theme: PresentationTheme, _ generate: (PresentationTheme) -> UIImage?) -> UIImage? {
let result = self.imageCache.with { holder -> UIImage? in
return holder.images[key]
}
if let result = result {
return result
} else {
if let image = generate(theme) {
self.imageCache.with { holder -> Void in
holder.images[key] = image
holder.logAddedImage(image: image)
}
return image
} else {
return nil
}
}
}
public func parameterImage(_ key: PresentationResourceParameterKey, _ theme: PresentationTheme, _ generate: (PresentationTheme) -> UIImage?) -> UIImage? {
let result = self.imageCache.with { holder -> UIImage? in
return holder.parameterImages[key]
}
if let result = result {
return result
} else {
if let image = generate(theme) {
self.imageCache.with { holder -> Void in
holder.parameterImages[key] = image
holder.logAddedImage(image: image)
}
return image
} else {
return nil
}
}
}
public func object(_ key: Int32, _ theme: PresentationTheme, _ generate: (PresentationTheme) -> AnyObject?) -> AnyObject? {
let result = self.objectCache.with { holder -> AnyObject? in
return holder.objects[key]
}
if let result = result {
return result
} else {
if let object = generate(theme) {
self.objectCache.with { holder -> Void in
holder.objects[key] = object
}
return object
} else {
return nil
}
}
}
public func parameterObject(_ key: PresentationResourceParameterKey, _ theme: PresentationTheme, _ generate: (PresentationTheme) -> AnyObject?) -> AnyObject? {
let result = self.objectCache.with { holder -> AnyObject? in
return holder.parameterObjects[key]
}
if let result = result {
return result
} else {
if let object = generate(theme) {
self.objectCache.with { holder -> Void in
holder.parameterObjects[key] = object
}
return object
} else {
return nil
}
}
}
}
@@ -0,0 +1,412 @@
import Foundation
import UIKit
public struct PresentationResources {
}
public enum PresentationResourceKey: Int32 {
case rootNavigationIndefiniteActivity
case rootTabContactsIcon
case rootTabContactsSelectedIcon
case rootTabChatsIcon
case rootTabChatsSelectedIcon
case rootTabSettingsIcon
case rootTabSettingsSelectedIcon
case navigationComposeIcon
case navigationCallIcon
case navigationInfoIcon
case navigationShareIcon
case navigationSearchIcon
case navigationCompactSearchIcon
case navigationCompactSearchWhiteIcon
case navigationCompactTagsSearchIcon
case navigationCompactTagsSearchWhiteIcon
case navigationCalendarIcon
case navigationMoreIcon
case navigationMoreCircledIcon
case navigationAddIcon
case navigationPlayerCloseButton
case navigationQrCodeIcon
case navigationLiveLocationIcon
case navigationPlayerRateActiveIcon
case navigationPlayerRateInactiveIcon
case navigationPlayerMaximizedRateActiveIcon
case navigationPlayerMaximizedRateInactiveIcon
case itemListDownArrow
case itemListDisclosureArrow
case disclosureOptionArrowsImage
case itemListDisclosureLocked
case itemListCheckIcon
case itemListSecondaryCheckIcon
case itemListDisabledCheckIcon
case itemListPlusIcon
case itemListRoundPlusIcon
case itemListAccentDeleteIcon
case itemListDeleteIcon
case itemListDeleteIndicatorIcon
case itemListReorderIndicatorIcon
case itemListLinkIcon
case itemListAddPersonIcon
case itemListCreateGroupIcon
case itemListAddExceptionIcon
case itemListAddPhoneIcon
case itemListAddPhotoIcon
case itemListClearInputIcon
case itemListStickerItemUnreadDot
case itemListVerifiedPeerIcon
case itemListCloudFetchIcon
case itemListCloseIconImage
case itemListRemoveIconImage
case itemListMakeVisibleIcon
case itemListMakeInvisibleIcon
case itemListEditThemeIcon
case itemListCornersTop
case itemListCornersBottom
case itemListCornersBoth
case itemListCornersTopGlass
case itemListCornersBottomGlass
case itemListCornersBothGlass
case itemListKnob
case itemListBlockAccentIcon
case itemListBlockDestructiveIcon
case itemListAddDeviceIcon
case itemListResetIcon
case itemListImageIcon
case itemListCloudIcon
case itemListTopicArrowIcon
case itemListAddBoostsIcon
case itemListPremiumIcon
case itemListRoundTopupIcon
case itemListRoundWithdrawIcon
case itemListStatsIcon
case statsReactionsIcon
case statsForwardsIcon
case itemListVoiceCallIcon
case itemListVideoCallIcon
case chatListLockTopUnlockedImage
case chatListLockBottomUnlockedImage
case chatListPending
case chatListClockFrame
case chatListClockMin
case chatListSingleCheck
case chatListDoubleCheck
case chatListBadgeBackgroundActive
case chatListBadgeBackgroundInactive
case chatListBadgeBackgroundMention
case chatListBadgeBackgroundInactiveMention
case chatListBadgeBackgroundPinned
case chatListMutedIcon
case chatListVerifiedIcon
case chatListPremiumIcon
case chatListScamRegularIcon
case chatListScamOutgoingIcon
case chatListScamServiceIcon
case chatListFakeRegularIcon
case chatListFakeOutgoingIcon
case chatListFakeServiceIcon
case chatListSecretIcon
case chatListStatusLockIcon
case chatListTopicArrowIcon
case chatListRecentStatusOnlineIcon
case chatListRecentStatusOnlineHighlightedIcon
case chatListRecentStatusOnlinePinnedIcon
case chatListRecentStatusOnlinePanelIcon
case chatListRecentStatusVoiceChatIcon
case chatListRecentStatusVoiceChatHighlightedIcon
case chatListRecentStatusVoiceChatPinnedIcon
case chatListRecentStatusVoiceChatPanelIcon
case chatListForwardedIcon
case chatListStoryReplyIcon
case chatListGiftIcon
case chatListLocationIcon
case chatListGeneralTopicIcon
case chatListGeneralTopicTemplateIcon
case chatListNewTopicTemplateIcon
case chatListGeneralTopicSmallIcon
case searchAdIcon
case chatTitleLockIcon
case chatTitleMuteIcon
case chatPanelLockIcon
case chatPanelBoostIcon
case chatBubbleVerticalLineIncomingImage
case chatBubbleVerticalLineOutgoingImage
case chatBubbleArrowFreeImage
case chatBubbleArrowIncomingImage
case chatBubbleArrowOutgoingImage
case chatBubbleCheckBubbleFullImage
case chatBubbleBubblePartialImage
case checkBubbleMediaFullImage
case checkBubbleMediaPartialImage
case chatBubbleConsumableContentIncomingIcon
case chatBubbleConsumableContentOutgoingIcon
case chatMediaConsumableContentIcon
case chatBubbleMediaOverlayControlSecret
case chatInstantVideoWithWallpaperBackgroundImage
case chatInstantVideoWithoutWallpaperBackgroundImage
case chatActionPhotoWithWallpaperBackgroundImage
case chatActionPhotoWithoutWallpaperBackgroundImage
case chatUnreadBarBackgroundImage
case chatBubbleFileCloudFetchMediaIcon
case chatBubbleFileCloudFetchIncomingIcon
case chatBubbleFileCloudFetchOutgoingIcon
case chatBubbleFileCloudFetchedIncomingIcon
case chatBubbleFileCloudFetchedOutgoingIcon
case chatBubbleTodoDotIncomingIcon
case chatBubbleTodoDotOutgoingIcon
case chatBubbleTodoCheckIncomingIcon
case chatBubbleTodoCheckOutgoingIcon
case chatServiceMessageTodoCompletedIcon
case chatServiceMessageTodoIncompletedIcon
case chatServiceMessageTodoAppendedIcon
case chatBubbleReplyThumbnailPlayImage
case chatBubbleDeliveryFailedIcon
case chatInfoItemBackgroundImageWithWallpaper
case chatInfoItemBackgroundImageWithoutWallpaper
case chatInputPanelCloseIconImage
case chatInputPanelPinnedListIconImage
case chatInputPanelEncircledCloseIconImage
case chatInputPanelVerticalSeparatorLineImage
case chatInputPanelForwardIconImage
case chatInputPanelReplyIconImage
case chatInputPanelEditIconImage
case chatInputPanelWebpageIconImage
case chatInputMediaPanelAddPackButtonImage
case chatInputMediaPanelAddedPackButtonImage
case chatInputMediaPanelGridSetupImage
case chatInputMediaPanelGridDismissImage
case chatInputButtonPanelButtonHighlightImage
case chatInputButtonPanelButtonShadowImage
case chatInputTextFieldBackgroundImage
case chatInputTextFieldClearImage
case chatInputPanelSendIconImage
case chatInputPanelSendButtonImage
case chatInputPanelApplyIconImage
case chatInputPanelApplyButtonImage
case chatInputPanelScheduleIconImage
case chatInputPanelScheduleButtonImage
case chatInputPanelVoiceButtonImage
case chatInputPanelVideoButtonImage
case chatInputPanelExpandButtonImage
case chatInputPanelVoiceActiveButtonImage
case chatInputPanelVideoActiveButtonImage
case chatInputPanelAttachmentButtonImage
case chatInputPanelEditAttachmentButtonImage
case chatInputPanelMediaRecordingDotImage
case chatInputPanelMediaRecordingCancelArrowImage
case chatInputTextFieldStickersImage
case chatInputTextFieldInputButtonsImage
case chatInputTextFieldKeyboardImage
case chatInputTextFieldCommandsImage
case chatInputTextFieldSilentPostOnImage
case chatInputTextFieldSilentPostOffImage
case chatInputTextFieldTimerImage
case chatInputTextFieldScheduleImage
case chatInputTextFieldGiftImage
case chatInputTextFieldSuggestPostImage
case chatInputSearchPanelUpImage
case chatInputSearchPanelUpDisabledImage
case chatInputSearchPanelDownImage
case chatInputSearchPanelDownDisabledImage
case chatInputSearchPanelCalendarImage
case chatInputSearchPanelMembersImage
case chatHistoryNavigationButtonBackground
case chatHistoryNavigationButtonImage
case chatHistoryNavigationUpButtonImage
case chatHistoryMentionsButtonImage
case chatHistoryReactionsButtonImage
case chatHistoryNavigationButtonBadgeImage
case chatMessageAttachedContentButtonIncoming
case chatMessageAttachedContentHighlightedButtonIncoming
case chatMessageAttachedContentButtonOutgoing
case chatMessageAttachedContentHighlightedButtonOutgoing
case chatMessageAttachedContentButtonIconInstantIncoming
case chatMessageAttachedContentHighlightedButtonIconInstantIncomingWithWallpaper
case chatMessageAttachedContentHighlightedButtonIconInstantIncomingWithoutWallpaper
case chatMessageAttachedContentButtonIconInstantOutgoing
case chatMessageAttachedContentHighlightedButtonIconInstantOutgoingWithWallpaper
case chatMessageAttachedContentHighlightedButtonIconInstantOutgoingWithoutWallpaper
case chatMessageAttachedContentButtonIconLinkIncoming
case chatMessageAttachedContentHighlightedButtonIconLinkIncomingWithWallpaper
case chatMessageAttachedContentHighlightedButtonIconLinkIncomingWithoutWallpaper
case chatMessageAttachedContentButtonIconLinkOutgoing
case chatMessageAttachedContentHighlightedButtonIconLinkOutgoingWithWallpaper
case chatMessageAttachedContentHighlightedButtonIconLinkOutgoingWithoutWallpaper
case chatMessageAttachedContentButtonIconBidIncoming
case chatMessageAttachedContentButtonIconBidOutgoing
case chatCommandPanelArrowImage
case sharedMediaFileDownloadStartIcon
case sharedMediaFileDownloadPauseIcon
case sharedMediaInstantViewIcon
case chatInfoCallButtonImage
case chatInstantMessageInfoBackgroundImage
case chatInstantMessageMuteIconImage
case chatBubbleIncomingCallButtonImage
case chatBubbleOutgoingCallButtonImage
case chatBubbleIncomingVideoCallButtonImage
case chatBubbleOutgoingVideoCallButtonImage
case callListOutgoingIcon
case callListOutgoingVideoIcon
case callListInfoButton
case genericSearchBarLoupeImage
case genericSearchBar
case inAppNotificationBackground
case groupInfoAdminsIcon
case groupInfoPermissionsIcon
case groupInfoMembersIcon
case emptyChatListCheckIcon
case chatFreeCommentButtonIcon
case chatFreeNavigateButtonIcon
case chatFreeShareButtonIcon
case chatFreeCloseButtonIcon
case chatFreeMoreButtonIcon
case chatKeyboardActionButtonMessageIcon
case chatKeyboardActionButtonLinkIcon
case chatKeyboardActionButtonShareIcon
case chatKeyboardActionButtonPhoneIcon
case chatKeyboardActionButtonLocationIcon
case chatKeyboardActionButtonPaymentIcon
case chatKeyboardActionButtonProfileIcon
case chatKeyboardActionButtonAddToChatIcon
case chatKeyboardActionButtonWebAppIcon
case chatGeneralThreadIcon
case chatGeneralThreadIncomingIcon
case chatGeneralThreadOutgoingIcon
case chatGeneralThreadFreeIcon
case uploadToneIcon
case storyViewListLikeIcon
case navigationPostStoryIcon
case navigationSortIcon
case chatReplyBackgroundTemplateIncomingImage
case chatReplyBackgroundTemplateOutgoingDashedImage
case chatReplyServiceBackgroundTemplateImage
case chatBubbleCloseIcon
case chatEmptyStateStarIcon
case chatPlaceholderStarIcon
case chatUserInfoWarningIcon
case avatarPremiumLockBadgeBackground
case avatarPremiumLockBadge
case shareAvatarPremiumLockBadgeBackground
case shareAvatarPremiumLockBadge
case shareAvatarStarsLockBadgeBackground
case shareAvatarStarsLockBadgeInnerBackground
case sharedLinkIcon
case hideIconImage
case peerStatusLockedImage
case expandDownArrowImage
case expandSmallDownArrowImage
case callListCallIcon
case chatFreeNavigateToThreadButtonIcon
case messageButtonsPostReject
case messageButtonsPostApprove
case messageButtonsPostEdit
}
public enum ChatExpiredStoryIndicatorType: Hashable {
case incoming
case outgoing
case free
}
public enum PresentationResourceParameterKey: Hashable {
case chatOutgoingFullCheck(CGFloat)
case chatOutgoingPartialCheck(CGFloat)
case chatMediaFullCheck(CGFloat)
case chatMediaPartialCheck(CGFloat)
case chatFreeFullCheck(CGFloat, Bool)
case chatFreePartialCheck(CGFloat, Bool)
case chatListBadgeBackgroundActive(CGFloat)
case chatListBadgeBackgroundActiveProvisional(CGFloat)
case chatListBadgeBackgroundInactive(CGFloat)
case chatListBadgeBackgroundInactiveProvisional(CGFloat)
case chatListBadgeBackgroundMention(CGFloat)
case badgeBackgroundReactions(CGFloat)
case badgeBackgroundInactiveReactions(CGFloat)
case chatListBadgeBackgroundInactiveMention(CGFloat)
case chatListBadgeBackgroundPinned(CGFloat)
case badgeBackgroundBorder(CGFloat)
case chatBubbleMediaCorner(incoming: Bool, mainRadius: CGFloat, inset: CGFloat)
case chatPrincipalThemeEssentialGraphics(hasWallpaper: Bool, bubbleCorners: PresentationChatBubbleCorners)
case chatPrincipalThemeAdditionalGraphics(isCustomWallpaper: Bool, bubbleCorners: PresentationChatBubbleCorners)
case chatBubbleLamp(incoming: Bool)
case chatPsaInfo(color: UInt32)
case chatMessageCommentsIcon(incoming: Bool)
case chatMessageCommentsArrowIcon(incoming: Bool)
case chatMessageCommentsUnreadDotIcon(incoming: Bool)
case chatMessageRepliesIcon(incoming: Bool)
case chatEntityKeyboardLock(color: UInt32)
case chatInputMediaPanelGridDismissImage(color: UInt32)
case statusAutoremoveIcon(isActive: Bool)
case chatExpiredStoryIndicatorIcon(type: ChatExpiredStoryIndicatorType)
case chatReplyStoryIndicatorIcon(type: ChatExpiredStoryIndicatorType)
}
@@ -0,0 +1,24 @@
import Foundation
import UIKit
import Display
import AppBundle
public struct PresentationResourcesCallList {
public static func outgoingIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.callListOutgoingIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Call List/OutgoingIcon"), color: theme.list.disclosureArrowColor)
})
}
public static func outgoingVideoIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.callListOutgoingVideoIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Call List/OutgoingVideoIcon"), color: theme.list.disclosureArrowColor)
})
}
public static func infoButton(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.callListInfoButton.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Call List/InfoButton"), color: theme.list.itemAccentColor)
})
}
}
@@ -0,0 +1,595 @@
import Foundation
import UIKit
import Display
import AppBundle
import PresentationStrings
private func generateStatusCheckImage(theme: PresentationTheme, single: Bool) -> UIImage? {
return generateImage(CGSize(width: single ? 13.0 : 18.0, height: 13.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.translateBy(x: 1.0, y: 2.0)
context.setStrokeColor(theme.chatList.checkmarkColor.cgColor)
context.setLineWidth(1.32)
context.setLineCap(.round)
context.setLineJoin(.round)
let _ = try? drawSvgPath(context, path: "M0,4.48 L3.59439858,7.93062264 L3.59439858,7.93062264 C3.63384129,7.96848764 3.69651158,7.96720866 3.73437658,7.92776595 C3.7346472,7.92748405 3.73491615,7.92720055 3.73518342,7.92691547 L11.1666667,0 S ")
if !single {
let _ = try? drawSvgPath(context, path: "M7.33333333,8 L14.8333333,0 S ")
}
})
}
private func generateBadgeBackgroundImage(theme: PresentationTheme, diameter: CGFloat, active: Bool, reaction: Bool, provisional: Bool, icon: UIImage? = nil) -> UIImage? {
return generateImage(CGSize(width: diameter, height: diameter), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
if active {
if reaction {
context.setFillColor(theme.chatList.reactionBadgeActiveBackgroundColor.cgColor)
} else if provisional {
context.setFillColor(theme.chatList.unreadBadgeActiveBackgroundColor.withMultipliedAlpha(0.1).cgColor)
} else {
context.setFillColor(theme.chatList.unreadBadgeActiveBackgroundColor.cgColor)
}
} else {
if provisional {
context.setFillColor(theme.chatList.unreadBadgeInactiveBackgroundColor.withMultipliedAlpha(0.2).cgColor)
} else {
context.setFillColor(theme.chatList.unreadBadgeInactiveBackgroundColor.cgColor)
}
}
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
if let icon = icon, let cgImage = icon.cgImage {
context.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((size.width - icon.size.width) / 2.0), y: floor((size.height - icon.size.height) / 2.0)), size: icon.size))
}
})?.stretchableImage(withLeftCapWidth: Int(diameter / 2.0), topCapHeight: Int(diameter / 2.0))
}
private func generateClockFrameImage(color: UIColor) -> UIImage? {
return generateImage(CGSize(width: 11.0, height: 11.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(color.cgColor)
context.setFillColor(color.cgColor)
let strokeWidth: CGFloat = 1.0
context.setLineWidth(strokeWidth)
context.strokeEllipse(in: CGRect(x: strokeWidth / 2.0, y: strokeWidth / 2.0, width: size.width - strokeWidth, height: size.height - strokeWidth))
context.fill(CGRect(x: (11.0 - strokeWidth) / 2.0, y: strokeWidth * 3.0, width: strokeWidth, height: 11.0 / 2.0 - strokeWidth * 3.0))
})
}
private func generateClockMinImage(color: UIColor) -> UIImage? {
return generateImage(CGSize(width: 11.0, height: 11.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(color.cgColor)
let strokeWidth: CGFloat = 1.0
context.fill(CGRect(x: (11.0 - strokeWidth) / 2.0, y: (11.0 - strokeWidth) / 2.0, width: 11.0 / 2.0 - strokeWidth, height: strokeWidth))
})
}
public enum RecentStatusOnlineIconState {
case regular
case highlighted
case pinned
case panel
}
public enum ScamIconType {
case regular
case outgoing
case service
}
public struct PresentationResourcesChatList {
public static func pendingImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatListPending.rawValue, { theme in
return generateImage(CGSize(width: 12.0, height: 14.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.translateBy(x: 0.0, y: 1.0)
context.setStrokeColor(theme.chatList.pendingIndicatorColor.cgColor)
let lineWidth: CGFloat = 0.99
context.setLineWidth(lineWidth)
context.strokeEllipse(in: CGRect(origin: CGPoint(x: lineWidth / 2.0, y: lineWidth / 2.0), size: CGSize(width: 12.0 - lineWidth, height: 12.0 - lineWidth)))
context.setLineCap(.round)
let _ = try? drawSvgPath(context, path: "M6.01830142,3 L6.01830142,6.23251697 L4.5,7.81306587 S ")
})
})
}
public static func singleCheckImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatListSingleCheck.rawValue, { theme in
return generateStatusCheckImage(theme: theme, single: true)
})
}
public static func doubleCheckImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatListDoubleCheck.rawValue, { theme in
return generateStatusCheckImage(theme: theme, single: false)
})
}
public static func clockFrameImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatListClockFrame.rawValue, { theme in
return generateClockFrameImage(color: theme.chatList.pendingIndicatorColor)
})
}
public static func clockMinImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatListClockMin.rawValue, { theme in
return generateClockMinImage(color: theme.chatList.pendingIndicatorColor)
})
}
public static func lockTopUnlockedImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatListLockTopUnlockedImage.rawValue, { theme in
return generateImage(CGSize(width: 7.0, height: 6.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(theme.rootController.navigationBar.primaryTextColor.cgColor)
context.setStrokeColor(theme.rootController.navigationBar.primaryTextColor.cgColor)
context.setLineWidth(1.5)
context.addPath(UIBezierPath(roundedRect: CGRect(x: 0.75, y: 0.75, width: 5.5, height: 12.0), cornerRadius: 2.5).cgPath)
context.strokePath()
context.setBlendMode(.clear)
context.setFillColor(UIColor.clear.cgColor)
context.fill(CGRect(x: 4.0, y: 5.33, width: 3.0, height: 2.0))
})
})
}
public static func lockBottomUnlockedImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatListLockBottomUnlockedImage.rawValue, { theme in
return generateImage(CGSize(width: 10.0, height: 8.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(theme.rootController.navigationBar.primaryTextColor.cgColor)
context.addPath(UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: 10.0, height: 8.0), cornerRadius: 1.5).cgPath)
context.fillPath()
})
})
}
public static func recentStatusOnlineIcon(_ theme: PresentationTheme, state: RecentStatusOnlineIconState, voiceChat: Bool = false) -> UIImage? {
let key: PresentationResourceKey
switch state {
case .regular:
key = voiceChat ? PresentationResourceKey.chatListRecentStatusVoiceChatIcon : PresentationResourceKey.chatListRecentStatusOnlineIcon
case .highlighted:
key = voiceChat ? PresentationResourceKey.chatListRecentStatusVoiceChatHighlightedIcon : PresentationResourceKey.chatListRecentStatusOnlineHighlightedIcon
case .pinned:
key = voiceChat ? PresentationResourceKey.chatListRecentStatusVoiceChatPinnedIcon : PresentationResourceKey.chatListRecentStatusOnlinePinnedIcon
case .panel:
key = voiceChat ? PresentationResourceKey.chatListRecentStatusVoiceChatPanelIcon : PresentationResourceKey.chatListRecentStatusOnlinePanelIcon
}
return theme.image(key.rawValue, { theme in
let size: CGFloat = voiceChat ? 22.0 : 14.0
return generateImage(CGSize(width: size, height: size), rotatedContext: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
switch state {
case .regular:
context.setFillColor(theme.chatList.itemBackgroundColor.cgColor)
case .highlighted:
context.setFillColor(theme.chatList.itemHighlightedBackgroundColor.cgColor)
case .pinned:
context.setFillColor(theme.chatList.pinnedItemBackgroundColor.cgColor)
case .panel:
context.setFillColor(theme.actionSheet.itemBackgroundColor.withAlphaComponent(1.0).cgColor)
}
context.fillEllipse(in: bounds)
context.setFillColor(theme.chatList.onlineDotColor.cgColor)
context.fillEllipse(in: bounds.insetBy(dx: 2.0, dy: 2.0))
})
})
}
public static func badgeBackgroundActive(_ theme: PresentationTheme, diameter: CGFloat) -> UIImage? {
return theme.image(PresentationResourceParameterKey.chatListBadgeBackgroundActive(diameter), { theme in
return generateBadgeBackgroundImage(theme: theme, diameter: diameter, active: true, reaction: false, provisional: false)
})
}
public static func badgeBackgroundActiveProvisional(_ theme: PresentationTheme, diameter: CGFloat) -> UIImage? {
return theme.image(PresentationResourceParameterKey.chatListBadgeBackgroundActiveProvisional(diameter), { theme in
return generateBadgeBackgroundImage(theme: theme, diameter: diameter, active: true, reaction: false, provisional: true)
})
}
public static func badgeBackgroundInactive(_ theme: PresentationTheme, diameter: CGFloat) -> UIImage? {
return theme.image(PresentationResourceParameterKey.chatListBadgeBackgroundInactive(diameter), { theme in
return generateBadgeBackgroundImage(theme: theme, diameter: diameter, active: false, reaction: false, provisional: false)
})
}
public static func badgeBackgroundInactiveProvisional(_ theme: PresentationTheme, diameter: CGFloat) -> UIImage? {
return theme.image(PresentationResourceParameterKey.chatListBadgeBackgroundInactiveProvisional(diameter), { theme in
return generateBadgeBackgroundImage(theme: theme, diameter: diameter, active: false, reaction: false, provisional: true)
})
}
public static func badgeBackgroundMention(_ theme: PresentationTheme, diameter: CGFloat) -> UIImage? {
return theme.image(PresentationResourceParameterKey.chatListBadgeBackgroundMention(diameter), { theme in
return generateBadgeBackgroundImage(theme: theme, diameter: diameter, active: true, reaction: false, provisional: false, icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/MentionBadgeIcon"), color: theme.chatList.unreadBadgeActiveTextColor))
})
}
public static func badgeBackgroundInactiveMention(_ theme: PresentationTheme, diameter: CGFloat) -> UIImage? {
return theme.image(PresentationResourceParameterKey.chatListBadgeBackgroundInactiveMention(diameter), { theme in
return generateBadgeBackgroundImage(theme: theme, diameter: diameter, active: false, reaction: false, provisional: false, icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/MentionBadgeIcon"), color: theme.chatList.unreadBadgeInactiveTextColor))
})
}
public static func badgeBackgroundReactions(_ theme: PresentationTheme, diameter: CGFloat) -> UIImage? {
return theme.image(PresentationResourceParameterKey.badgeBackgroundReactions(diameter), { theme in
return generateBadgeBackgroundImage(theme: theme, diameter: diameter, active: true, reaction: true, provisional: false, icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/ReactionsBadgeIcon"), color: theme.chatList.unreadBadgeActiveTextColor))
})
}
public static func badgeBackgroundInactiveReactions(_ theme: PresentationTheme, diameter: CGFloat) -> UIImage? {
return theme.image(PresentationResourceParameterKey.badgeBackgroundInactiveReactions(diameter), { theme in
return generateBadgeBackgroundImage(theme: theme, diameter: diameter, active: false, reaction: false, provisional: false, icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/ReactionsBadgeIcon"), color: theme.chatList.unreadBadgeInactiveTextColor))
})
}
public static func badgeBackgroundPinned(_ theme: PresentationTheme, diameter: CGFloat) -> UIImage? {
return theme.image(PresentationResourceParameterKey.chatListBadgeBackgroundPinned(diameter), { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/PeerPinnedIcon"), color: theme.chatList.pinnedBadgeColor)
})
}
public static func badgeBackgroundBorder(_ theme: PresentationTheme, diameter: CGFloat) -> UIImage? {
return theme.image(PresentationResourceParameterKey.badgeBackgroundBorder(diameter), { theme in
return generateStretchableFilledCircleImage(diameter: diameter, color: theme.chatList.pinnedItemBackgroundColor.blitOver(theme.chatList.backgroundColor, alpha: 1.0))
})
}
public static func mutedIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatListMutedIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/PeerMutedIcon"), color: theme.chatList.muteIconColor)
})
}
public static func forwardedIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatListForwardedIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/ForwardedIcon"), color: theme.chatList.muteIconColor)
})
}
public static func storyReplyIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatListStoryReplyIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/StoryReplyIcon"), color: theme.chatList.muteIconColor)
})
}
public static func giftIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatListGiftIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/GiftIcon"), color: theme.chatList.muteIconColor)
})
}
public static func locationIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatListLocationIcon.rawValue, { theme in
if let image = UIImage(bundleImageName: "Chat/Attach Menu/Location") {
return generateImage(CGSize(width: 20.0, height: 20.0), contextGenerator: { size, context in
if let cgImage = image.cgImage {
context.clear(CGRect(origin: CGPoint(), size: size))
context.clip(to: CGRect(origin: .zero, size: size), mask: cgImage)
context.setFillColor(theme.chatList.muteIconColor.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
}
})
} else {
return nil
}
})
}
public static func verifiedIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatListVerifiedIcon.rawValue, { theme in
if let backgroundImage = UIImage(bundleImageName: "Chat List/PeerVerifiedIconBackground"), let foregroundImage = UIImage(bundleImageName: "Chat List/PeerVerifiedIconForeground") {
return generateImage(backgroundImage.size, contextGenerator: { size, context in
if let backgroundCgImage = backgroundImage.cgImage, let foregroundCgImage = foregroundImage.cgImage {
context.clear(CGRect(origin: CGPoint(), size: size))
context.saveGState()
context.clip(to: CGRect(origin: .zero, size: size), mask: backgroundCgImage)
context.setFillColor(theme.list.itemCheckColors.fillColor.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
context.restoreGState()
context.clip(to: CGRect(origin: .zero, size: size), mask: foregroundCgImage)
context.setFillColor(theme.list.itemCheckColors.foregroundColor.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
}
}, opaque: false)
} else {
return nil
}
})
}
public static func premiumIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatListPremiumIcon.rawValue, { theme in
if let image = UIImage(bundleImageName: "Chat List/PeerPremiumIcon") {
return generateImage(image.size, contextGenerator: { size, context in
if let cgImage = image.cgImage {
context.clear(CGRect(origin: CGPoint(), size: size))
context.clip(to: CGRect(origin: .zero, size: size), mask: cgImage)
context.setFillColor(theme.list.itemCheckColors.fillColor.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
}
}, opaque: false)
} else {
return nil
}
})
}
public static func scamIcon(_ theme: PresentationTheme, strings: PresentationStrings, type: ScamIconType) -> UIImage? {
let key: PresentationResourceKey
let color: UIColor
switch type {
case .regular:
key = PresentationResourceKey.chatListScamRegularIcon
color = theme.chat.message.incoming.scamColor
case .outgoing:
key = PresentationResourceKey.chatListScamOutgoingIcon
color = theme.chat.message.outgoing.scamColor
case .service:
key = PresentationResourceKey.chatListScamServiceIcon
color = theme.chat.serviceMessage.components.withDefaultWallpaper.scam
}
return theme.image(key.rawValue, { theme in
let titleString = NSAttributedString(string: strings.Message_ScamAccount.uppercased(), font: Font.bold(10.0), textColor: color, paragraphAlignment: .center)
let stringRect = titleString.boundingRect(with: CGSize(width: 100.0, height: 16.0), options: .usesLineFragmentOrigin, context: nil)
return generateImage(CGSize(width: floor(stringRect.width) + 11.0, height: 16.0), contextGenerator: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
context.setFillColor(color.cgColor)
context.setStrokeColor(color.cgColor)
context.setLineWidth(1.0)
context.addPath(UIBezierPath(roundedRect: bounds.insetBy(dx: 0.5, dy: 0.5), cornerRadius: 2.0).cgPath)
context.strokePath()
let titlePath = CGMutablePath()
titlePath.addRect(bounds.offsetBy(dx: 0.0, dy: -2.0 + UIScreenPixel))
let titleFramesetter = CTFramesetterCreateWithAttributedString(titleString as CFAttributedString)
let titleFrame = CTFramesetterCreateFrame(titleFramesetter, CFRangeMake(0, titleString.length), titlePath, nil)
CTFrameDraw(titleFrame, context)
})
})
}
public static func fakeIcon(_ theme: PresentationTheme, strings: PresentationStrings, type: ScamIconType) -> UIImage? {
let key: PresentationResourceKey
let color: UIColor
switch type {
case .regular:
key = PresentationResourceKey.chatListFakeRegularIcon
color = theme.chat.message.incoming.scamColor
case .outgoing:
key = PresentationResourceKey.chatListFakeOutgoingIcon
color = theme.chat.message.outgoing.scamColor
case .service:
key = PresentationResourceKey.chatListFakeServiceIcon
color = theme.chat.serviceMessage.components.withDefaultWallpaper.scam
}
return theme.image(key.rawValue, { theme in
let titleString = NSAttributedString(string: strings.Message_FakeAccount.uppercased(), font: Font.bold(10.0), textColor: color, paragraphAlignment: .center)
let stringRect = titleString.boundingRect(with: CGSize(width: 100.0, height: 16.0), options: .usesLineFragmentOrigin, context: nil)
return generateImage(CGSize(width: floor(stringRect.width) + 11.0, height: 16.0), contextGenerator: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
context.setFillColor(color.cgColor)
context.setStrokeColor(color.cgColor)
context.setLineWidth(1.0)
context.addPath(UIBezierPath(roundedRect: bounds.insetBy(dx: 0.5, dy: 0.5), cornerRadius: 2.0).cgPath)
context.strokePath()
let titlePath = CGMutablePath()
titlePath.addRect(bounds.offsetBy(dx: 0.0, dy: -2.0 + UIScreenPixel))
let titleFramesetter = CTFramesetterCreateWithAttributedString(titleString as CFAttributedString)
let titleFrame = CTFramesetterCreateFrame(titleFramesetter, CFRangeMake(0, titleString.length), titlePath, nil)
CTFrameDraw(titleFrame, context)
})
})
}
public static func secretIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatListSecretIcon.rawValue, { theme in
return generateImage(CGSize(width: 9.0, height: 12.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(theme.chatList.secretIconColor.cgColor)
context.setStrokeColor(theme.chatList.secretIconColor.cgColor)
context.setLineWidth(1.32)
let _ = try? drawSvgPath(context, path: "M4.5,0.66 C3.11560623,0.66 1.99333333,1.78227289 1.99333333,3.16666667 L1.99333333,7.8047619 C1.99333333,9.18915568 3.11560623,10.3114286 4.5,10.3114286 C5.88439377,10.3114286 7.00666667,9.18915568 7.00666667,7.8047619 L7.00666667,3.16666667 C7.00666667,1.78227289 5.88439377,0.66 4.5,0.66 S ")
let _ = try? drawSvgPath(context, path: "M1.32,5.48571429 L7.68,5.48571429 C8.40901587,5.48571429 9,6.07669842 9,6.80571429 L9,10.68 C9,11.4090159 8.40901587,12 7.68,12 L1.32,12 C0.59098413,12 8.92786951e-17,11.4090159 0,10.68 L2.22044605e-16,6.80571429 C1.3276591e-16,6.07669842 0.59098413,5.48571429 1.32,5.48571429 Z ")
})
})
}
public static func statusLockIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatListStatusLockIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/StatusLockIcon"), color: theme.chatList.unreadBadgeInactiveBackgroundColor)
})
}
public static func topicArrowIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatListTopicArrowIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/TopicArrowIcon"), color: theme.chatList.titleColor)
})
}
public static func generalTopicSmallIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatListGeneralTopicSmallIcon.rawValue, { theme in
let image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/GeneralTopicIcon"), color: theme.chatList.unreadBadgeInactiveBackgroundColor)
return generateImage(CGSize(width: 18.0, height: 18.0), contextGenerator: { size, context in
context.clear(CGRect(origin: .zero, size: size))
if let cgImage = image?.cgImage {
context.draw(cgImage, in: CGRect(origin: .zero, size: size))
}
})
})
}
public static func generalTopicIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatListGeneralTopicIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/GeneralTopicIcon"), color: theme.chatList.unreadBadgeInactiveBackgroundColor)
})
}
public static func generalTopicTemplateIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatListGeneralTopicTemplateIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/GeneralTopicIcon"), color: .white)?.withRenderingMode(.alwaysTemplate)
})
}
public static func newTopicTemplateIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatListNewTopicTemplateIcon.rawValue, { theme in
guard let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: .white) else {
return nil
}
let size = CGSize(width: 24.0, height: 24.0)
return generateImage(size, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
UIGraphicsPushContext(context)
defer {
UIGraphicsPopContext()
}
image.draw(in: CGRect(origin: CGPoint(), size: size))
})?.withRenderingMode(.alwaysTemplate)
})
}
public static func statusAutoremoveIcon(_ theme: PresentationTheme, isActive: Bool) -> UIImage? {
return theme.image(PresentationResourceParameterKey.statusAutoremoveIcon(isActive: isActive), { theme in
return generateTintedImage(image: UIImage(bundleImageName: isActive ? "Chat List/StatusIconAutoremoveOn" : "Chat List/StatusIconAutoremoveOff"), color: isActive ? theme.list.itemAccentColor : theme.list.itemSecondaryTextColor)
})
}
public static func avatarPremiumLockBadgeBackground(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.avatarPremiumLockBadgeBackground.rawValue, { theme in
return generateFilledCircleImage(diameter: 17.0, color: .white)?.withRenderingMode(.alwaysTemplate)
})
}
public static func avatarPremiumLockBadge(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.avatarPremiumLockBadge.rawValue, { theme in
return generateImage(CGSize(width: 16.0, height: 16.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.addPath(CGPath(ellipseIn: CGRect(origin: CGPoint(), size: size), transform: nil))
context.clip()
var locations: [CGFloat] = [0.0, 1.0]
let colors: [CGColor] = [UIColor(rgb: 0x9981FF).cgColor, UIColor(rgb: 0xBA6DE9).cgColor]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: 0.0), options: CGGradientDrawingOptions())
context.resetClip()
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/StatusLockIcon"), color: .white), let cgImage = image.cgImage {
let imageSize = image.size.fitted(CGSize(width: 8.0, height: 8.0))
context.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) * 0.5), y: floor((size.height - imageSize.height) * 0.5)), size: imageSize))
}
})
})
}
public static func shareAvatarPremiumLockBadgeBackground(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.shareAvatarPremiumLockBadgeBackground.rawValue, { theme in
return generateFilledCircleImage(diameter: 22.0, color: .white)?.withRenderingMode(.alwaysTemplate)
})
}
public static func shareAvatarStarsLockBadgeBackground(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.shareAvatarStarsLockBadgeBackground.rawValue, { theme in
return generateImage(CGSize(width: 20.0, height: 20.0), contextGenerator: { size, context in
context.clear(CGRect(origin: .zero, size: size))
context.setFillColor(UIColor.white.cgColor)
let rect = CGRect(origin: .zero, size: CGSize(width: 20.0, height: 18.0 + UIScreenPixel)).insetBy(dx: 1.0 - UIScreenPixel, dy: 0.0)
context.addPath(UIBezierPath(roundedRect: rect, cornerRadius: rect.height / 2.0).cgPath)
context.fillPath()
})?.withRenderingMode(.alwaysTemplate).stretchableImage(withLeftCapWidth: 10, topCapHeight: 10)
})
}
public static func shareAvatarStarsLockBadgeInnerBackground(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.shareAvatarStarsLockBadgeInnerBackground.rawValue, { theme in
return generateImage(CGSize(width: 20.0, height: 16.0), contextGenerator: { size, context in
context.clear(CGRect(origin: .zero, size: size))
context.setFillColor(UIColor.white.cgColor)
context.addPath(UIBezierPath(roundedRect: CGRect(origin: .zero, size: CGSize(width: 20.0, height: 15.0)), cornerRadius: 7.5).cgPath)
context.fillPath()
})?.withRenderingMode(.alwaysTemplate).stretchableImage(withLeftCapWidth: 10, topCapHeight: 0)
})
}
public static func shareAvatarPremiumLockBadge(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.shareAvatarPremiumLockBadge.rawValue, { theme in
return generateImage(CGSize(width: 20.0, height: 20.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.addPath(CGPath(ellipseIn: CGRect(origin: CGPoint(), size: size), transform: nil))
context.clip()
var locations: [CGFloat] = [0.0, 1.0]
let colors: [CGColor] = [UIColor(rgb: 0x9981FF).cgColor, UIColor(rgb: 0xBA6DE9).cgColor]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: 0.0), options: CGGradientDrawingOptions())
context.resetClip()
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/StatusLockIcon"), color: .white), let cgImage = image.cgImage {
let imageSize = image.size.fitted(CGSize(width: 14.0, height: 14.0))
context.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) * 0.5), y: floor((size.height - imageSize.height) * 0.5)), size: imageSize))
}
})
})
}
public static func searchAdIcon(_ theme: PresentationTheme, strings: PresentationStrings) -> UIImage? {
return theme.image(PresentationResourceKey.searchAdIcon.rawValue, { theme in
let titleString = NSAttributedString(string: strings.ChatList_Search_Ad, font: Font.regular(11.0), textColor: theme.list.itemAccentColor, paragraphAlignment: .center)
let stringRect = titleString.boundingRect(with: CGSize(width: 200.0, height: 20.0), options: .usesLineFragmentOrigin, context: nil)
return generateImage(CGSize(width: floor(stringRect.width) + 18.0, height: 15.0), rotatedContext: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
context.setFillColor(theme.list.itemAccentColor.withMultipliedAlpha(0.1).cgColor)
context.addPath(UIBezierPath(roundedRect: bounds, cornerRadius: size.height / 2.0).cgPath)
context.fillPath()
context.setFillColor(theme.list.itemAccentColor.cgColor)
let circleSize = CGSize(width: 2.0 - UIScreenPixel, height: 2.0 - UIScreenPixel)
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - 8.0, y: 3.0 + UIScreenPixel), size: circleSize))
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - 8.0, y: 7.0 - UIScreenPixel), size: circleSize))
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - 8.0, y: 10.0), size: circleSize))
let textRect = CGRect(
x: 5.0,
y: (size.height - stringRect.height) / 2.0 - UIScreenPixel,
width: stringRect.width,
height: stringRect.height
)
UIGraphicsPushContext(context)
titleString.draw(in: textRect)
UIGraphicsPopContext()
})
})
}
}
@@ -0,0 +1,490 @@
import Foundation
import UIKit
import Display
import AppBundle
public func generateItemListCheckIcon(color: UIColor) -> UIImage? {
return generateImage(CGSize(width: 12.0, height: 10.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(color.cgColor)
context.setLineWidth(1.98)
context.setLineCap(.round)
context.setLineJoin(.round)
context.translateBy(x: 1.0, y: 1.0)
let _ = try? drawSvgPath(context, path: "M0.215053763,4.36080467 L3.31621263,7.70466293 L3.31621263,7.70466293 C3.35339229,7.74475231 3.41603123,7.74711109 3.45612061,7.70993143 C3.45920681,7.70706923 3.46210733,7.70401312 3.46480451,7.70078171 L9.89247312,0 S ")
})
}
public func generateItemListPlusIcon(_ color: UIColor) -> UIImage? {
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/AddIcon"), color: color)
}
public struct PresentationResourcesItemList {
public static func downArrowImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListDownArrow.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Search/DownButton"), color: theme.list.itemAccentColor)
})
}
public static func disclosureArrowImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListDisclosureArrow.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Item List/DisclosureArrow"), color: theme.list.disclosureArrowColor)
})
}
public static func disclosureOptionArrowsImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.disclosureOptionArrowsImage.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Item List/ContextDisclosureArrow"), color: theme.list.disclosureArrowColor)
})
}
public static func disclosureLockedImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListDisclosureLocked.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/SmallLock"), color: theme.list.disclosureArrowColor)
})
}
public static func checkIconImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListCheckIcon.rawValue, { theme in
return generateItemListCheckIcon(color: theme.list.itemAccentColor)
})
}
public static func secondaryCheckIconImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListSecondaryCheckIcon.rawValue, { theme in
return generateItemListCheckIcon(color: theme.list.itemSecondaryTextColor)
})
}
public static func disabledCheckIconImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListDisabledCheckIcon.rawValue, { theme in
return generateItemListCheckIcon(color: theme.list.itemDisabledTextColor)
})
}
public static func plusIconImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListPlusIcon.rawValue, { theme in
return generateItemListPlusIcon(theme.list.itemAccentColor)
})
}
public static func roundPlusIconImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListRoundPlusIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/AddRoundIcon"), color: theme.list.itemAccentColor)
})
}
public static func accentDeleteIconImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListAccentDeleteIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: theme.list.itemAccentColor)
})
}
public static func deleteIconImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListDeleteIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: theme.list.itemDestructiveColor)
})
}
public static func stickerUnreadDotImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListStickerItemUnreadDot.rawValue, { theme in
return generateFilledCircleImage(diameter: 6.0, color: theme.list.itemAccentColor)
})
}
public static func verifiedPeerIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListVerifiedPeerIcon.rawValue, { theme in
if let backgroundImage = UIImage(bundleImageName: "Chat List/PeerVerifiedIconBackground"), let foregroundImage = UIImage(bundleImageName: "Chat List/PeerVerifiedIconForeground") {
return generateImage(backgroundImage.size, contextGenerator: { size, context in
if let backgroundCgImage = backgroundImage.cgImage, let foregroundCgImage = foregroundImage.cgImage {
context.clear(CGRect(origin: CGPoint(), size: size))
context.saveGState()
context.clip(to: CGRect(origin: .zero, size: size), mask: backgroundCgImage)
context.setFillColor(theme.chatList.unreadBadgeActiveBackgroundColor.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
context.restoreGState()
context.clip(to: CGRect(origin: .zero, size: size), mask: foregroundCgImage)
context.setFillColor(theme.chatList.unreadBadgeActiveTextColor.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
}
}, opaque: false)
} else {
return nil
}
})
}
public static func itemListDeleteIndicatorIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListDeleteIndicatorIcon.rawValue, { theme in
guard let image = generateTintedImage(image: UIImage(bundleImageName: "Item List/RemoveItemIcon"), color: theme.list.itemDestructiveColor) else {
return nil
}
return generateImage(image.size, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(theme.rootController.tabBar.badgeTextColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: 2, y: 2), size: CGSize(width: size.width - 4.0, height: size.height - 4.0)))
context.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: size))
})
})
}
public static func itemListReorderIndicatorIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListReorderIndicatorIcon.rawValue, { theme in
return generateImage(CGSize(width: 17.0, height: 14.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(theme.list.itemBlocksSeparatorColor.cgColor)
let lineHeight = 1.0 + UIScreenPixel
context.addPath(CGPath(roundedRect: CGRect(x: 0.0, y: UIScreenPixel, width: 17.0, height: lineHeight), cornerWidth: lineHeight / 2.0, cornerHeight: lineHeight / 2.0, transform: nil))
context.addPath(CGPath(roundedRect: CGRect(x: 0.0, y: UIScreenPixel + 6.0, width: 17.0, height: lineHeight), cornerWidth: lineHeight / 2.0, cornerHeight: lineHeight / 2.0, transform: nil))
context.addPath(CGPath(roundedRect: CGRect(x: 0.0, y: UIScreenPixel + 12.0, width: 17.0, height: lineHeight), cornerWidth: lineHeight / 2.0, cornerHeight: lineHeight / 2.0, transform: nil))
context.fillPath()
})
})
}
public static func linkIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListLinkIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Contact List/LinkActionIcon"), color: theme.list.itemAccentColor)
})
}
public static func addPersonIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListAddPersonIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Contact List/AddMemberIcon"), color: theme.list.itemAccentColor)
})
}
public static func createGroupIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListCreateGroupIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Location/CreateGroupIcon"), color: theme.list.itemAccentColor)
})
}
public static func addChannelIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListAddExceptionIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Item List/AddChannelIcon"), color: theme.list.itemAccentColor)
})
}
public static func voiceCallIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListVoiceCallIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Info/CallButton"), color: theme.list.itemAccentColor)
})
}
public static func videoCallIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListVideoCallIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Info/VideoCallButton"), color: theme.list.itemAccentColor)
})
}
public static func addPhoneIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListAddPhoneIcon.rawValue, { theme in
guard let image = generateTintedImage(image: UIImage(bundleImageName: "Item List/AddItemIcon"), color: theme.list.itemAccentColor) else {
return nil
}
return generateImage(image.size, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(theme.rootController.tabBar.badgeTextColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: 2, y: 2), size: CGSize(width: size.width - 4.0, height: size.height - 4.0)))
context.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: size))
})
})
}
public static func addPhotoIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListAddPhotoIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Settings/SetAvatar"), color: theme.list.itemAccentColor)
})
}
public static func itemListClearInputIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListClearInputIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: theme.list.inputClearButtonColor)
})
}
public static func cloudFetchIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListCloudFetchIcon.rawValue, { theme in
generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/FileCloudFetch"), color: theme.list.itemAccentColor)
})
}
public static func itemListCloseIconImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListCloseIconImage.rawValue, { theme in
return generateImage(CGSize(width: 12.0, height: 12.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setBlendMode(.copy)
context.setStrokeColor(theme.list.disclosureArrowColor.cgColor)
context.setLineWidth(2.0)
context.setLineCap(.round)
context.move(to: CGPoint(x: 1.0, y: 1.0))
context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - 1.0))
context.strokePath()
context.move(to: CGPoint(x: size.width - 1.0, y: 1.0))
context.addLine(to: CGPoint(x: 1.0, y: size.height - 1.0))
context.strokePath()
})
})
}
public static func itemListRemoveIconImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListRemoveIconImage.rawValue, { theme in
return generateImage(CGSize(width: 15.0, height: 15.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setBlendMode(.copy)
context.setStrokeColor(theme.list.disclosureArrowColor.cgColor)
context.setLineWidth(2.0)
context.setLineCap(.round)
context.move(to: CGPoint(x: 1.0, y: 1.0))
context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - 1.0))
context.strokePath()
context.move(to: CGPoint(x: size.width - 1.0, y: 1.0))
context.addLine(to: CGPoint(x: 1.0, y: size.height - 1.0))
context.strokePath()
})
})
}
public static func makeVisibleIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListMakeVisibleIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Contact List/MakeVisibleIcon"), color: theme.list.itemAccentColor)
})
}
public static func makeInvisibleIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListMakeInvisibleIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Contact List/MakeInvisibleIcon"), color: theme.list.itemDestructiveColor)
})
}
public static func editThemeIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListEditThemeIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Settings/EditTheme"), color: theme.list.itemAccentColor)
})
}
public static func knobImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListKnob.rawValue, { theme in
return generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setShadow(offset: CGSize(width: 0.0, height: -3.0), blur: 12.0, color: UIColor(white: 0.0, alpha: 0.25).cgColor)
context.setFillColor(UIColor.white.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: 6.0, y: 6.0), size: CGSize(width: 28.0, height: 28.0)))
})
})
}
public static func blockAccentIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListBlockAccentIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Item List/Block"), color: theme.list.itemAccentColor)
})
}
public static func blockDestructiveIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListBlockDestructiveIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Item List/Block"), color: theme.list.itemDestructiveColor)
})
}
public static func addDeviceIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListAddDeviceIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Settings/QrIcon"), color: theme.list.itemAccentColor)
})
}
public static func resetIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListResetIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Settings/Reset"), color: theme.list.itemAccentColor)
})
}
public static func imageIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListImageIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Attach Menu/Image"), color: theme.list.itemAccentColor)
})
}
public static func cloudIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListCloudIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Attach Menu/Cloud"), color: theme.list.itemAccentColor)
})
}
public static func addBoostsIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListAddBoostsIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Premium/Gift"), color: theme.list.itemAccentColor)
})
}
public static func premiumIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListPremiumIcon.rawValue, { theme in
return generateImage(CGSize(width: 16.0, height: 16.0), contextGenerator: { size, context in
let bounds = CGRect(origin: .zero, size: size)
context.clear(bounds)
let image = UIImage(bundleImageName: "Item List/PremiumIcon")!
context.clip(to: bounds, mask: image.cgImage!)
let colorsArray: [CGColor] = [
UIColor(rgb: 0x6b93ff).cgColor,
UIColor(rgb: 0x6b93ff).cgColor,
UIColor(rgb: 0x8d77ff).cgColor,
UIColor(rgb: 0xb56eec).cgColor,
UIColor(rgb: 0xb56eec).cgColor
]
var locations: [CGFloat] = [0.0, 0.3, 0.5, 0.7, 1.0]
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: size.height), options: CGGradientDrawingOptions())
})
})
}
public static func cornersImage(_ theme: PresentationTheme, top: Bool, bottom: Bool, glass: Bool = false) -> UIImage? {
if !top && !bottom {
return nil
}
let key: PresentationResourceKey
if top && bottom {
key = glass ? PresentationResourceKey.itemListCornersBothGlass : PresentationResourceKey.itemListCornersBoth
} else if top {
key = glass ? PresentationResourceKey.itemListCornersTopGlass : PresentationResourceKey.itemListCornersTop
} else {
key = glass ? PresentationResourceKey.itemListCornersBottomGlass : PresentationResourceKey.itemListCornersBottom
}
return theme.image(key.rawValue, { theme in
return generateImage(CGSize(width: 56.0, height: 56.0), rotatedContext: { (size, context) in
let bounds = CGRect(origin: CGPoint(), size: size)
context.setFillColor(theme.list.blocksBackgroundColor.cgColor)
context.fill(bounds)
context.setBlendMode(.clear)
var corners: UIRectCorner = []
if top {
corners.insert(.topLeft)
corners.insert(.topRight)
}
if bottom {
corners.insert(.bottomLeft)
corners.insert(.bottomRight)
}
let cornerRadius: CGFloat = glass ? 26.0 : 11.0
let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
context.addPath(path.cgPath)
context.fillPath()
})?.stretchableImage(withLeftCapWidth: 28, topCapHeight: 28)
})
}
public static func uploadToneIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.uploadToneIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Settings/UploadTone"), color: theme.list.itemAccentColor)
})
}
public static func topicArrowDescriptionIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListTopicArrowIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/TopicArrowIcon"), color: theme.list.itemSecondaryTextColor)
})
}
public static func statsReactionsIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.statsReactionsIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chart/Reactions"), color: theme.list.itemSecondaryTextColor)
})
}
public static func statsForwardsIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.statsForwardsIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chart/Forwards"), color: theme.list.itemSecondaryTextColor)
})
}
public static func sharedLinkIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.sharedLinkIcon.rawValue, { theme in
return generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in
UIGraphicsPushContext(context)
defer {
UIGraphicsPopContext()
}
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(theme.list.itemCheckColors.fillColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.list.itemCheckColors.foregroundColor) {
image.draw(at: CGPoint(x: floor((size.width - image.size.width) * 0.5), y: floor((size.height - image.size.height) * 0.5)))
}
})
})
}
public static func hideIconImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.hideIconImage.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/Archive/IconHide"), color: theme.list.itemAccentColor)
})
}
public static func peerStatusLockedImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.peerStatusLockedImage.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/SmallLock"), color: theme.list.itemSecondaryTextColor)
})
}
public static func expandDownArrowImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.expandDownArrowImage.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Item List/ExpandingItemVerticalRegularArrow"), color: .white)?.withRenderingMode(.alwaysTemplate)
})
}
public static func expandSmallDownArrowImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.expandSmallDownArrowImage.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Item List/ExpandingItemVerticalSmallArrow"), color: .white)?.withRenderingMode(.alwaysTemplate)
})
}
public static func itemListRoundTopupIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListRoundTopupIcon.rawValue, { theme in
return generateImage(CGSize(width: 16.0, height: 18.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.translateBy(x: 0.0, y: 2.0 - UIScreenPixel)
context.setFillColor(theme.list.itemCheckColors.foregroundColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.width)))
context.setBlendMode(.clear)
context.addPath(CGPath(roundedRect: CGRect(x: 7.0, y: 3.0, width: 2.0, height: 10.0), cornerWidth: 1.0, cornerHeight: 1.0, transform: nil))
context.addPath(CGPath(roundedRect: CGRect(x: 3.0, y: 7.0, width: 10.0, height: 2.0), cornerWidth: 1.0, cornerHeight: 1.0, transform: nil))
context.fillPath()
})
})
}
public static func itemListRoundWithdrawIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListRoundWithdrawIcon.rawValue, { theme in
return generateImage(CGSize(width: 16.0, height: 18.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.translateBy(x: 0.0, y: 2.0 - UIScreenPixel)
context.setFillColor(theme.list.itemCheckColors.foregroundColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.width)))
context.setBlendMode(.clear)
context.addPath(CGPath(roundedRect: CGRect(x: 3.0, y: 7.0, width: 10.0, height: 2.0), cornerWidth: 1.0, cornerHeight: 1.0, transform: nil))
context.fillPath()
})
})
}
public static func itemListStatsIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListStatsIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Premium/Stars/Stats"), color: .white)?.withRenderingMode(.alwaysTemplate)
})
}
}
@@ -0,0 +1,208 @@
import Foundation
import UIKit
import Display
import AppBundle
private func generateShareButtonImage(theme: PresentationTheme) -> UIImage? {
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/NavigationShare"), color: theme.rootController.navigationBar.accentTextColor)
}
public func generateIndefiniteActivityIndicatorImage(color: UIColor, diameter: CGFloat = 22.0, lineWidth: CGFloat = 2.0) -> UIImage? {
return generateImage(CGSize(width: diameter, height: diameter), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(color.cgColor)
context.setLineWidth(lineWidth)
context.setLineCap(.round)
let cutoutAngle: CGFloat = CGFloat.pi * 30.0 / 180.0
context.addArc(center: CGPoint(x: size.width / 2.0, y: size.height / 2.0), radius: size.width / 2.0 - lineWidth / 2.0, startAngle: 0.0, endAngle: CGFloat.pi * 2.0 - cutoutAngle, clockwise: false)
context.strokePath()
})
}
public func generatePlayerRateIcon(_ color: UIColor) -> UIImage? {
return generateImage(CGSize(width: 19.0, height: 16.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(color.cgColor)
context.setStrokeColor(color.cgColor)
context.setLineWidth(4.0)
context.scaleBy(x: 0.3333, y: 0.3333)
let _ = try? drawSvgPath(context, path: "M15.3637695,32.1972656 L23.7749023,32.1972656 C24.6127972,32.1972656 25.2519509,32.3691389 25.6923828,32.7128906 C26.1328147,33.0566423 26.3530273,33.5239228 26.3530273,34.1147461 C26.3530273,34.6411159 26.1784685,35.0869122 25.8293457,35.4521484 C25.4802229,35.8173846 24.9511754,36 24.2421875,36 L12.3828125,36 C11.5771444,36 10.9487327,35.7771018 10.4975586,35.3312988 C10.0463845,34.8854958 9.82080078,34.3618194 9.82080078,33.7602539 C9.82080078,33.3735332 9.96581886,32.8605989 10.2558594,32.2214355 C10.5458999,31.5822722 10.8627913,31.08008 11.206543,30.7148438 C12.635261,29.2324145 13.9243107,27.9621635 15.0737305,26.9040527 C16.2231503,25.845942 17.0449194,25.1503923 17.5390625,24.8173828 C18.4199263,24.1943328 19.1530732,23.5686067 19.7385254,22.9401855 C20.3239775,22.3117644 20.7697739,21.6672396 21.0759277,21.0065918 C21.3820816,20.345944 21.5351562,19.6987336 21.5351562,19.0649414 C21.5351562,18.377438 21.3713395,17.7624539 21.0437012,17.2199707 C20.7160628,16.6774875 20.2702665,16.2558609 19.7062988,15.9550781 C19.1423312,15.6542954 18.5273471,15.5039062 17.8613281,15.5039062 C16.4540945,15.5039062 15.3476603,16.1215759 14.5419922,17.3569336 C14.4345698,17.5180672 14.2546399,17.9584925 14.0021973,18.6782227 C13.7497546,19.3979528 13.4650895,19.9511699 13.1481934,20.3378906 C12.8312972,20.7246113 12.3667023,20.9179688 11.7543945,20.9179688 C11.2172825,20.9179688 10.7714861,20.7407244 10.4169922,20.3862305 C10.0624982,20.0317365 9.88525391,19.5483429 9.88525391,18.9360352 C9.88525391,18.1948205 10.0517561,17.4213907 10.3847656,16.6157227 C10.7177751,15.8100546 11.2145963,15.0795931 11.8752441,14.4243164 C12.535892,13.7690397 13.3737742,13.2399922 14.388916,12.8371582 C15.4040578,12.4343242 16.5937432,12.2329102 17.9580078,12.2329102 C19.6015707,12.2329102 21.0034122,12.4907201 22.1635742,13.0063477 C22.9155311,13.3500994 23.576169,13.8227509 24.1455078,14.4243164 C24.7148466,15.0258819 25.1579574,15.7214316 25.4748535,16.5109863 C25.7917496,17.3005411 25.9501953,18.1196247 25.9501953,18.9682617 C25.9501953,20.3002996 25.6198764,21.5114692 24.9592285,22.6018066 C24.2985807,23.6921441 23.6245152,24.5461395 22.9370117,25.1638184 C22.2495083,25.7814972 21.0974202,26.75097 19.4807129,28.0722656 C17.8640056,29.3935613 16.7548858,30.4194299 16.1533203,31.1499023 C15.8955065,31.4399429 15.6323256,31.7890605 15.3637695,32.1972656 Z M28.8464425,31.4077148 L34.1315987,23.6894531 L29.6843331,16.8251953 C29.2653857,16.1591764 28.9511799,15.5871606 28.7417062,15.1091309 C28.5322325,14.6311011 28.4274972,14.1718772 28.4274972,13.7314453 C28.4274972,13.2802712 28.6289112,12.8747577 29.0317452,12.5148926 C29.4345793,12.1550275 29.9260294,11.9750977 30.5061105,11.9750977 C31.1721294,11.9750977 31.6904348,12.1711406 32.0610421,12.5632324 C32.4316494,12.9553242 32.9445837,13.6831002 33.5998605,14.746582 L37.1447823,20.4829102 L40.9314034,14.746582 C41.2429284,14.2631812 41.5087949,13.8496111 41.7290109,13.5058594 C41.9492268,13.1621077 42.1613829,12.8774425 42.3654855,12.6518555 C42.569588,12.4262684 42.7978572,12.2570806 43.0502999,12.1442871 C43.3027426,12.0314936 43.5954643,11.9750977 43.9284737,11.9750977 C44.5300392,11.9750977 45.0214894,12.1550275 45.402839,12.5148926 C45.7841885,12.8747577 45.9748605,13.3017553 45.9748605,13.7958984 C45.9748605,14.5156286 45.5612904,15.4931579 44.7341378,16.7285156 L40.0773995,23.6894531 L45.08863,31.4077148 C45.5398041,32.084476 45.8674376,32.6457497 46.0715401,33.0915527 C46.2756427,33.5373557 46.3776925,33.9589824 46.3776925,34.3564453 C46.3776925,34.7324238 46.2863848,35.0761703 46.1037667,35.3876953 C45.9211486,35.6992203 45.6633387,35.9462881 45.3303292,36.1289062 C44.9973197,36.3115244 44.6213469,36.402832 44.2023995,36.402832 C43.7512254,36.402832 43.3698815,36.3088388 43.0583566,36.1208496 C42.7468316,35.9328604 42.4943927,35.6992201 42.3010323,35.4199219 C42.107672,35.1406236 41.7478123,34.5981486 41.2214425,33.7924805 L37.0642159,27.2504883 L32.6491769,33.9858398 C32.3054251,34.5229519 32.0610428,34.8989247 31.9160226,35.1137695 C31.7710023,35.3286144 31.5964435,35.5380849 31.3923409,35.7421875 C31.1882383,35.9462901 30.9465415,36.1074213 30.6672433,36.2255859 C30.387945,36.3437506 30.0603116,36.402832 29.6843331,36.402832 C29.1042521,36.402832 28.623544,36.2255877 28.2421944,35.8710938 C27.8608449,35.5165998 27.670173,35.0009799 27.670173,34.3242188 C27.670173,33.5292929 28.0622589,32.5571347 28.8464425,31.4077148 Z M8,2 C4.6862915,2 2,4.6862915 2,8 L2,40 C2,43.3137085 4.6862915,46 8,46 L48,46 C51.3137085,46 54,43.3137085 54,40 L54,8 C54,4.6862915 51.3137085,2 48,2 L8,2 S")
})
}
public struct PresentationResourcesRootController {
public static func navigationIndefiniteActivityImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.rootNavigationIndefiniteActivity.rawValue, { theme in
generateIndefiniteActivityIndicatorImage(color: theme.rootController.navigationBar.accentTextColor)
})
}
public static func navigationComposeIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationComposeIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/ComposeIcon"), color: theme.rootController.navigationBar.accentTextColor)
})
}
public static func navigationShareIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationShareIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: theme.rootController.navigationBar.accentTextColor)
})
// return theme.image(PresentationResourceKey.navigationShareIcon.rawValue, generateShareButtonImage)
}
public static func navigationCallIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationCallIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Call List/CallIcon"), color: theme.rootController.navigationBar.accentTextColor)
})
}
public static func navigationInfoIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationInfoIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/InfoIcon"), color: theme.rootController.navigationBar.accentTextColor)
})
}
public static func navigationSearchIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationSearchIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/SearchIcon"), color: theme.rootController.navigationBar.accentTextColor)
})
}
public static func navigationCompactSearchIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationCompactSearchIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/SearchIcon"), color: theme.rootController.navigationBar.accentTextColor)
})
}
public static func navigationCompactSearchWhiteIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationCompactSearchWhiteIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/SearchIcon"), color: .white)
})
}
public static func navigationCompactTagsSearchIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationCompactTagsSearchIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/NavigationSearchTagsIcon"), color: theme.rootController.navigationBar.accentTextColor)
})
}
public static func navigationCompactTagsSearchWhiteIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationCompactTagsSearchWhiteIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/NavigationSearchTagsIcon"), color: .white)
})
}
public static func navigationCalendarIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationCalendarIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Search/Calendar"), color: theme.rootController.navigationBar.accentTextColor)
})
}
public static func navigationMoreIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationMoreIcon.rawValue, { theme in
return generateImage(CGSize(width: 30.0, height: 30.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(theme.rootController.navigationBar.accentTextColor.cgColor)
let dotSize: CGFloat = 4.0
context.fillEllipse(in: CGRect(origin: CGPoint(x: 6.0, y: floor((size.height - dotSize) / 2.0)), size: CGSize(width: dotSize, height: dotSize)))
context.fillEllipse(in: CGRect(origin: CGPoint(x: 13.0, y: floor((size.height - dotSize) / 2.0)), size: CGSize(width: dotSize, height: dotSize)))
context.fillEllipse(in: CGRect(origin: CGPoint(x: 20.0, y: floor((size.height - dotSize) / 2.0)), size: CGSize(width: dotSize, height: dotSize)))
})
})
}
public static func navigationMoreCircledIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationMoreCircledIcon.rawValue, { theme in
generateTintedImage(image: UIImage(bundleImageName: "Chat List/NavigationMore"), color: theme.rootController.navigationBar.accentTextColor)
})
}
public static func navigationQrCodeIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationQrCodeIcon.rawValue, { theme in
generateTintedImage(image: UIImage(bundleImageName: "Settings/QrIcon"), color: .white)
})
}
public static func navigationAddIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationAddIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/AddIcon"), color: theme.rootController.navigationBar.accentTextColor)
})
}
public static func navigationPlayerCloseButton(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationPlayerCloseButton.rawValue, { theme in
return generateImage(CGSize(width: 12.0, height: 12.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(theme.rootController.navigationBar.controlColor.cgColor)
context.setLineWidth(2.0)
context.setLineCap(.round)
context.move(to: CGPoint(x: 1.0, y: 1.0))
context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - 1.0))
context.strokePath()
context.move(to: CGPoint(x: size.width - 1.0, y: 1.0))
context.addLine(to: CGPoint(x: 1.0, y: size.height - 1.0))
context.strokePath()
})
})
}
public static func navigationPlayerRateActiveIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationPlayerRateActiveIcon.rawValue, { theme in
return generatePlayerRateIcon(theme.rootController.navigationBar.accentTextColor)
})
}
public static func navigationPlayerRateInactiveIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationPlayerRateInactiveIcon.rawValue, { theme in
return generatePlayerRateIcon(theme.rootController.navigationBar.controlColor)
})
}
public static func navigationPlayerMaximizedRateActiveIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationPlayerMaximizedRateActiveIcon.rawValue, { theme in
return generatePlayerRateIcon(theme.list.itemAccentColor)
})
}
public static func navigationPlayerMaximizedRateInactiveIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationPlayerMaximizedRateInactiveIcon.rawValue, { theme in
return generatePlayerRateIcon(theme.list.itemSecondaryTextColor)
})
}
public static func navigationLiveLocationIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationLiveLocationIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/LiveLocationPanelIcon"), color: theme.rootController.navigationBar.accentTextColor)
})
}
public static func inAppNotificationBackground(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.inAppNotificationBackground.rawValue, { theme in
let inset: CGFloat = 16.0
return generateImage(CGSize(width: 30.0 + inset * 2.0, height: 30.0 + 8.0 * 2.0 + 20.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setShadow(offset: CGSize(width: 0.0, height: -4.0), blur: 40.0, color: UIColor(white: 0.0, alpha: 0.3).cgColor)
context.setFillColor(theme.inAppNotification.fillColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: inset, y: 8.0 * 2.0), size: CGSize(width: 30.0, height: 30.0)))
})?.stretchableImage(withLeftCapWidth: Int(inset) + 15, topCapHeight: 8 * 2 + 15)
})
}
public static func navigationPostStoryIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationPostStoryIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/AddStoryIcon"), color: .white)
})
}
public static func navigationSortIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationSortIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Peer Info/SortIcon"), color: .white)
})
}
public static func callListCallIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.callListCallIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Call List/NewCallListIcon"), color: theme.rootController.navigationBar.accentTextColor)
})
}
}
@@ -0,0 +1,196 @@
import Foundation
import UIKit
import Display
import AppBundle
private func drawBorder(context: CGContext, rect: CGRect) {
context.setLineWidth(UIScreenPixel)
context.setStrokeColor(UIColor(rgb: 0xffffff, alpha: 0.25).cgColor)
let path = CGPath(roundedRect: rect.insetBy(dx: UIScreenPixel / 2.0, dy: UIScreenPixel / 2.0), cornerWidth: 6.0, cornerHeight: 6.0, transform: nil)
context.addPath(path)
context.strokePath()
}
private func addRoundedRectPath(context: CGContext, rect: CGRect, radius: CGFloat) {
context.saveGState()
context.translateBy(x: rect.minX, y: rect.minY)
context.scaleBy(x: radius, y: radius)
let fw = rect.width / radius
let fh = rect.height / radius
context.move(to: CGPoint(x: fw, y: fh / 2.0))
context.addArc(tangent1End: CGPoint(x: fw, y: fh), tangent2End: CGPoint(x: fw/2, y: fh), radius: 1.0)
context.addArc(tangent1End: CGPoint(x: 0, y: fh), tangent2End: CGPoint(x: 0, y: fh/2), radius: 1)
context.addArc(tangent1End: CGPoint(x: 0, y: 0), tangent2End: CGPoint(x: fw/2, y: 0), radius: 1)
context.addArc(tangent1End: CGPoint(x: fw, y: 0), tangent2End: CGPoint(x: fw, y: fh/2), radius: 1)
context.closePath()
context.restoreGState()
}
private func renderIcon(name: String, scaleFactor: CGFloat = 1.0, backgroundColors: [UIColor]? = nil) -> UIImage? {
return generateImage(CGSize(width: 29.0, height: 29.0), contextGenerator: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
if let backgroundColors {
addRoundedRectPath(context: context, rect: CGRect(origin: CGPoint(), size: size), radius: 7.0)
context.clip()
var locations: [CGFloat] = [0.0, 1.0]
let colors: [CGColor] = backgroundColors.map(\.cgColor)
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: size.width, y: size.height), end: CGPoint(x: 0.0, y: 0.0), options: CGGradientDrawingOptions())
context.resetClip()
if let image = generateTintedImage(image: UIImage(bundleImageName: name), color: .white), let cgImage = image.cgImage {
let imageSize = CGSize(width: image.size.width * scaleFactor, height: image.size.height * scaleFactor)
context.draw(cgImage, in: CGRect(origin: CGPoint(x: (bounds.width - imageSize.width) * 0.5, y: (bounds.height - imageSize.height) * 0.5), size: imageSize))
}
} else {
if let image = UIImage(bundleImageName: name), let cgImage = image.cgImage {
let imageSize: CGSize
if scaleFactor == 1.0 {
imageSize = size
} else {
imageSize = CGSize(width: image.size.width * scaleFactor, height: image.size.height * scaleFactor)
}
context.draw(cgImage, in: CGRect(origin: CGPoint(x: (bounds.width - imageSize.width) * 0.5, y: (bounds.height - imageSize.height) * 0.5), size: imageSize))
}
}
})
}
public struct PresentationResourcesSettings {
public static let editProfile = renderIcon(name: "Settings/Menu/EditProfile")
public static let proxy = renderIcon(name: "Settings/Menu/Proxy")
public static let savedMessages = renderIcon(name: "Settings/Menu/SavedMessages")
public static let recentCalls = renderIcon(name: "Settings/Menu/RecentCalls")
public static let devices = renderIcon(name: "Settings/Menu/Sessions")
public static let chatFolders = renderIcon(name: "Settings/Menu/ChatListFilters")
public static let stickers = renderIcon(name: "Settings/Menu/Stickers")
public static let notifications = renderIcon(name: "Settings/Menu/Notifications")
public static let security = renderIcon(name: "Settings/Menu/Security")
public static let dataAndStorage = renderIcon(name: "Settings/Menu/DataAndStorage")
public static let appearance = renderIcon(name: "Settings/Menu/Appearance")
public static let language = renderIcon(name: "Settings/Menu/Language")
public static let deleteAccount = renderIcon(name: "Chat/Info/GroupRemovedIcon")
public static let powerSaving = renderIcon(name: "Settings/Menu/PowerSaving")
public static let stories = renderIcon(name: "Premium/Perk/Stories", scaleFactor: 0.97, backgroundColors: [UIColor(rgb: 0x5856D6)])
public static let premiumGift = renderIcon(name: "Settings/Menu/Gift")
public static let business = renderIcon(name: "Settings/Menu/Business", backgroundColors: [UIColor(rgb: 0xA95CE3), UIColor(rgb: 0xF16B80)])
public static let myProfile = renderIcon(name: "Settings/Menu/Profile")
public static let reactions = renderIcon(name: "Settings/Menu/Reactions")
public static let balance = renderIcon(name: "Settings/Menu/Balance", scaleFactor: 0.97, backgroundColors: [UIColor(rgb: 0x34c759)])
public static let affiliateProgram = renderIcon(name: "Settings/Menu/AffiliateProgram")
public static let earnStars = renderIcon(name: "Settings/Menu/EarnStars")
public static let channelMessages = renderIcon(name: "Chat/Info/ChannelMessages", backgroundColors: [UIColor(rgb: 0x5856D6)])
public static let premium = generateImage(CGSize(width: 29.0, height: 29.0), contextGenerator: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
let path = UIBezierPath(roundedRect: bounds, cornerRadius: 7.0)
context.addPath(path.cgPath)
context.clip()
let colorsArray: [CGColor] = [
UIColor(rgb: 0x6b93ff).cgColor,
UIColor(rgb: 0x6b93ff).cgColor,
UIColor(rgb: 0x8d77ff).cgColor,
UIColor(rgb: 0xb56eec).cgColor,
UIColor(rgb: 0xb56eec).cgColor
]
var locations: [CGFloat] = [0.0, 0.15, 0.5, 0.85, 1.0]
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: size.height), options: CGGradientDrawingOptions())
if let image = generateTintedImage(image: UIImage(bundleImageName: "Premium/ButtonIcon"), color: UIColor(rgb: 0xffffff)), let cgImage = image.cgImage {
context.draw(cgImage, in: CGRect(origin: CGPoint(x: floorToScreenPixels((bounds.width - image.size.width) / 2.0), y: floorToScreenPixels((bounds.height - image.size.height) / 2.0)), size: image.size))
}
drawBorder(context: context, rect: bounds)
})
public static let ton = generateImage(CGSize(width: 29.0, height: 29.0), contextGenerator: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
let path = UIBezierPath(roundedRect: bounds, cornerRadius: 7.0)
context.addPath(path.cgPath)
context.clip()
context.setFillColor(UIColor(rgb: 0x32ade6).cgColor)
context.fill(bounds)
if let image = generateTintedImage(image: UIImage(bundleImageName: "Ads/TonAbout"), color: UIColor(rgb: 0xffffff)), let cgImage = image.cgImage {
context.draw(cgImage, in: CGRect(origin: CGPoint(x: floorToScreenPixels((bounds.width - image.size.width) / 2.0), y: floorToScreenPixels((bounds.height - image.size.height) / 2.0)), size: image.size))
}
drawBorder(context: context, rect: bounds)
})
public static let stars = generateImage(CGSize(width: 29.0, height: 29.0), contextGenerator: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
let path = UIBezierPath(roundedRect: bounds, cornerRadius: 7.0)
context.addPath(path.cgPath)
context.clip()
let colorsArray: [CGColor] = [
UIColor(rgb: 0xfec80f).cgColor,
UIColor(rgb: 0xdd6f12).cgColor
]
var locations: [CGFloat] = [0.0, 1.0]
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: size.height), options: CGGradientDrawingOptions())
if let image = generateTintedImage(image: UIImage(bundleImageName: "Premium/ButtonIcon"), color: UIColor(rgb: 0xffffff)), let cgImage = image.cgImage {
context.draw(cgImage, in: CGRect(origin: CGPoint(x: floorToScreenPixels((bounds.width - image.size.width) / 2.0), y: floorToScreenPixels((bounds.height - image.size.height) / 2.0)), size: image.size))
}
drawBorder(context: context, rect: bounds)
})
public static let bot = generateImage(CGSize(width: 29.0, height: 29.0), contextGenerator: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
let path = UIBezierPath(roundedRect: bounds, cornerRadius: 7.0)
context.addPath(path.cgPath)
context.clip()
context.setFillColor(UIColor(rgb: 0x0088ff).cgColor)
context.fill(bounds)
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Bot"), color: UIColor(rgb: 0xffffff)), let cgImage = image.cgImage {
context.draw(cgImage, in: CGRect(origin: CGPoint(x: floorToScreenPixels((bounds.width - image.size.width) / 2.0), y: floorToScreenPixels((bounds.height - image.size.height) / 2.0)), size: image.size))
}
drawBorder(context: context, rect: bounds)
})
public static let passport = renderIcon(name: "Settings/Menu/Passport")
public static let watch = renderIcon(name: "Settings/Menu/Watch")
public static let support = renderIcon(name: "Settings/Menu/Support")
public static let faq = renderIcon(name: "Settings/Menu/Faq")
public static let tips = renderIcon(name: "Settings/Menu/Tips")
public static let addAccount = renderIcon(name: "Settings/Menu/AddAccount")
public static let setPasscode = renderIcon(name: "Settings/Menu/SetPasscode")
public static let clearCache = renderIcon(name: "Settings/Menu/ClearCache")
public static let changePhoneNumber = renderIcon(name: "Settings/Menu/ChangePhoneNumber")
public static let deleteAddAccount = renderIcon(name: "Settings/Menu/DeleteAddAccount")
public static let deleteSetTwoStepAuth = renderIcon(name: "Settings/Menu/DeleteTwoStepAuth")
public static let deleteSetPasscode = renderIcon(name: "Settings/Menu/FaceId")
public static let deleteChats = renderIcon(name: "Settings/Menu/DeleteChats")
public static let clearSynced = renderIcon(name: "Settings/Menu/ClearSynced")
public static let websites = renderIcon(name: "Settings/Menu/Websites")
}
@@ -0,0 +1,68 @@
import Foundation
import UIKit
import TelegramCore
public extension TelegramWallpaper {
var isEmpty: Bool {
switch self {
case .image:
return false
case .emoticon:
return false
case let .file(file):
if self.isPattern, file.settings.colors.count == 1 && (file.settings.colors[0] == 0xffffff || file.settings.colors[0] == 0xffffffff) {
return true
} else {
return false
}
case let .color(color):
return color == 0xffffff || color == 0xffffffff
default:
return false
}
}
var isColorOrGradient: Bool {
switch self {
case .color, .gradient:
return true
default:
return false
}
}
var isPattern: Bool {
switch self {
case let .file(file):
return file.isPattern || file.file.mimeType == "application/x-tgwallpattern"
default:
return false
}
}
var isBuiltin: Bool {
switch self {
case .builtin:
return true
default:
return false
}
}
var isEmoticon: Bool {
switch self {
case .emoticon:
return true
default:
return false
}
}
var dimensions: CGSize? {
if case let .file(file) = self {
return file.file.dimensions?.cgSize
} else {
return nil
}
}
}