Various theme improvements

This commit is contained in:
Ilya Laktyushin 2019-12-14 03:32:42 +04:00
parent eb406e4032
commit e19439a33e
37 changed files with 3981 additions and 3132 deletions

View File

@ -5185,3 +5185,9 @@ Any member of this group will be able to see messages in the channel.";
"Theme.Colors.Proceed" = "Proceed";
"AuthSessions.AddDevice.UrlLoginHint" = "This code can be used to allow someone to log in to your Telegram account.\n\nTo confirm Telegram login, please go to Settings > Devices > Scan QR and scan the code.";
"Appearance.RemoveThemeColor" = "Remove Color";
"WallpaperPreview.PatternTitle" = "Choose Pattern";
"WallpaperPreview.PatternPaternDiscard" = "Discard";
"WallpaperPreview.PatternPaternApply" = "Apply";

View File

@ -7,6 +7,10 @@ public enum TransformImageResizeMode {
case blurBackground
}
public protocol TransformImageCustomArguments {
func serialized() -> NSArray
}
public struct TransformImageArguments: Equatable {
public let corners: ImageCorners
@ -15,15 +19,17 @@ public struct TransformImageArguments: Equatable {
public let intrinsicInsets: UIEdgeInsets
public let resizeMode: TransformImageResizeMode
public let emptyColor: UIColor?
public let custom: TransformImageCustomArguments?
public let scale: CGFloat?
public init(corners: ImageCorners, imageSize: CGSize, boundingSize: CGSize, intrinsicInsets: UIEdgeInsets, resizeMode: TransformImageResizeMode = .fill(.black), emptyColor: UIColor? = nil, scale: CGFloat? = nil) {
public init(corners: ImageCorners, imageSize: CGSize, boundingSize: CGSize, intrinsicInsets: UIEdgeInsets, resizeMode: TransformImageResizeMode = .fill(.black), emptyColor: UIColor? = nil, custom: TransformImageCustomArguments? = nil, scale: CGFloat? = nil) {
self.corners = corners
self.imageSize = imageSize
self.boundingSize = boundingSize
self.intrinsicInsets = intrinsicInsets
self.resizeMode = resizeMode
self.emptyColor = emptyColor
self.custom = custom
self.scale = scale
}
@ -43,6 +49,14 @@ public struct TransformImageArguments: Equatable {
}
public static func ==(lhs: TransformImageArguments, rhs: TransformImageArguments) -> Bool {
return lhs.imageSize == rhs.imageSize && lhs.boundingSize == rhs.boundingSize && lhs.corners == rhs.corners && lhs.emptyColor == rhs.emptyColor
var result = lhs.imageSize == rhs.imageSize && lhs.boundingSize == rhs.boundingSize && lhs.corners == rhs.corners && lhs.emptyColor == rhs.emptyColor
if result {
if let lhsCustom = lhs.custom, let rhsCustom = rhs.custom {
return lhsCustom.serialized().isEqual(rhsCustom.serialized())
} else {
return (lhs.custom != nil) == (rhs.custom != nil)
}
}
return result
}
}

View File

@ -81,7 +81,7 @@ public final class WallpaperBackgroundNode: ASDisplayNode {
}
public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
let isFirstLayout = self.frame.isEmpty
let isFirstLayout = self.contentNode.frame.isEmpty
transition.updatePosition(node: self.contentNode, position: CGPoint(x: size.width / 2.0, y: size.height / 2.0))
transition.updateBounds(node: self.contentNode, bounds: CGRect(origin: CGPoint(), size: size))

View File

@ -148,20 +148,33 @@ public final class CachedPatternWallpaperRepresentation: CachedMediaResourceRepr
public let keepDuration: CachedMediaRepresentationKeepDuration = .general
public let color: Int32
public let bottomColor: Int32?
public let intensity: Int32
public let rotation: Int32?
public var uniqueId: String {
return "pattern-wallpaper-\(self.color)-\(self.intensity)"
var id: String
if let bottomColor = self.bottomColor {
id = "pattern-wallpaper-\(self.color)-\(bottomColor)-\(self.intensity)"
} else {
id = "pattern-wallpaper-\(self.color)-\(self.intensity)"
}
if let rotation = self.rotation, rotation != 0 {
id += "-\(rotation)deg"
}
return id
}
public init(color: Int32, intensity: Int32) {
public init(color: Int32, bottomColor: Int32?, intensity: Int32, rotation: Int32?) {
self.color = color
self.bottomColor = bottomColor
self.intensity = intensity
self.rotation = rotation
}
public func isEqual(to: CachedMediaResourceRepresentation) -> Bool {
if let to = to as? CachedPatternWallpaperRepresentation {
return self.color == to.color && self.intensity == intensity
return self.color == to.color && self.bottomColor == to.bottomColor && self.intensity == intensity && self.rotation == to.rotation
} else {
return false
}

View File

@ -126,11 +126,15 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
self.toolbarNode.cancel = {
dismiss()
}
var dismissed = false
self.toolbarNode.done = { [weak self] in
guard let strongSelf = self else {
return
}
apply(strongSelf.presentationThemeSettings.useSystemFont, strongSelf.presentationThemeSettings.fontSize)
if !dismissed {
dismissed = true
apply(strongSelf.presentationThemeSettings.useSystemFont, strongSelf.presentationThemeSettings.fontSize)
}
}
self.toolbarNode.updateUseSystemFont = { [weak self] value in
guard let strongSelf = self else {

View File

@ -315,6 +315,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
let _ = (previewThemePromise.get()
|> take(1)
|> deliverOnMainQueue).start(next: { theme in
var controllerDismissImpl: (() -> Void)?
let controller = ThemeAccentColorController(context: context, mode: .edit(theme: theme, wallpaper: nil, defaultThemeReference: nil, create: false, completion: { updatedTheme in
updateState { current in
var state = current
@ -322,7 +323,11 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
state.updatedTheme = updatedTheme
return state
}
controllerDismissImpl?()
}))
controllerDismissImpl = { [weak controller] in
controller?.dismiss()
}
pushControllerImpl?(controller)
})
}, openFile: {

View File

@ -30,8 +30,8 @@ private func whiteColorImage(theme: PresentationTheme, color: UIColor) -> Signal
}
final class SettingsThemeWallpaperNode: ASDisplayNode {
private var wallpaper: TelegramWallpaper?
private var color: UIColor?
var wallpaper: TelegramWallpaper?
private var arguments: PatternWallpaperArguments?
let buttonNode = HighlightTrackingButtonNode()
let backgroundNode = ASDisplayNode()
@ -39,7 +39,7 @@ final class SettingsThemeWallpaperNode: ASDisplayNode {
private let statusNode: RadialStatusNode
var pressed: (() -> Void)?
init(overlayBackgroundColor: UIColor = UIColor(white: 0.0, alpha: 0.3)) {
self.imageNode.contentAnimations = [.subsequentUpdates]
@ -63,6 +63,10 @@ final class SettingsThemeWallpaperNode: ASDisplayNode {
self.statusNode.transitionToState(state, animated: animated, completion: {})
}
func setOverlayBackgroundColor(_ color: UIColor) {
self.statusNode.backgroundNodeColor = color
}
func setWallpaper(context: AccountContext, wallpaper: TelegramWallpaper, selected: Bool, size: CGSize, cornerRadius: CGFloat = 0.0, synchronousLoad: Bool = false) {
self.buttonNode.frame = CGRect(origin: CGPoint(), size: size)
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size)
@ -125,6 +129,7 @@ final class SettingsThemeWallpaperNode: ASDisplayNode {
if file.isPattern {
self.backgroundNode.isHidden = false
var patternColors: [UIColor] = []
var patternColor = UIColor(rgb: 0xd6e2ee, alpha: 0.5)
var patternIntensity: CGFloat = 0.5
if let color = file.settings.color {
@ -132,9 +137,15 @@ final class SettingsThemeWallpaperNode: ASDisplayNode {
patternIntensity = CGFloat(intensity) / 100.0
}
patternColor = UIColor(rgb: UInt32(bitPattern: color), alpha: patternIntensity)
patternColors.append(patternColor)
if let bottomColor = file.settings.bottomColor {
patternColors.append(UIColor(rgb: UInt32(bitPattern: bottomColor), alpha: patternIntensity))
}
}
self.backgroundNode.backgroundColor = patternColor
self.color = patternColor
self.arguments = PatternWallpaperArguments(colors: patternColors, rotation: file.settings.rotation)
imageSignal = patternWallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, representations: convertedRepresentations, mode: .thumbnail, autoFetchFullSize: true)
} else {
self.backgroundNode.isHidden = true
@ -144,7 +155,7 @@ final class SettingsThemeWallpaperNode: ASDisplayNode {
self.imageNode.setSignal(imageSignal, attemptSynchronously: synchronousLoad)
let dimensions = file.file.dimensions ?? PixelDimensions(width: 100, height: 100)
let apply = self.imageNode.asyncLayout()(TransformImageArguments(corners: corners, imageSize: dimensions.cgSize.aspectFilled(size), boundingSize: size, intrinsicInsets: UIEdgeInsets(), emptyColor: self.color))
let apply = self.imageNode.asyncLayout()(TransformImageArguments(corners: corners, imageSize: dimensions.cgSize.aspectFilled(size), boundingSize: size, intrinsicInsets: UIEdgeInsets(), custom: self.arguments))
apply()
}
} else if let wallpaper = self.wallpaper {
@ -157,7 +168,7 @@ final class SettingsThemeWallpaperNode: ASDisplayNode {
apply()
case let .file(file):
let dimensions = file.file.dimensions ?? PixelDimensions(width: 100, height: 100)
let apply = self.imageNode.asyncLayout()(TransformImageArguments(corners: corners, imageSize: dimensions.cgSize.aspectFilled(size), boundingSize: size, intrinsicInsets: UIEdgeInsets(), emptyColor: self.color))
let apply = self.imageNode.asyncLayout()(TransformImageArguments(corners: corners, imageSize: dimensions.cgSize.aspectFilled(size), boundingSize: size, intrinsicInsets: UIEdgeInsets(), custom: self.arguments))
apply()
}
}

View File

@ -10,8 +10,9 @@ import TelegramPresentationData
import TelegramUIPreferences
import AccountContext
import PresentationDataUtils
import MediaResources
private let colors: [Int32] = [0x007aff, 0x00c2ed, 0x29b327, 0xeb6ca4, 0xf08200, 0x9472ee, 0xd33213, 0xedb400, 0x6d839e]
private let randomBackgroundColors: [Int32] = [0x007aff, 0x00c2ed, 0x29b327, 0xeb6ca4, 0xf08200, 0x9472ee, 0xd33213, 0xedb400, 0x6d839e]
enum ThemeAccentColorControllerMode {
case colors(themeReference: PresentationThemeReference)
@ -64,7 +65,7 @@ final class ThemeAccentColorController: ViewController {
self.segmentedTitleView = ThemeColorSegmentedTitleView(theme: self.presentationData.theme, strings: self.presentationData.strings, selectedSection: section)
if case .background = mode {
self.initialBackgroundColor = colors.randomElement().flatMap { UIColor(rgb: UInt32(bitPattern: $0)) }
self.initialBackgroundColor = randomBackgroundColors.randomElement().flatMap { UIColor(rgb: UInt32(bitPattern: $0)) }
} else {
self.initialBackgroundColor = nil
}
@ -146,7 +147,35 @@ final class ThemeAccentColorController: ViewController {
completion(updatedTheme)
} else {
let _ = (updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
let prepare: Signal<Void, NoError>
if let patternWallpaper = state.patternWallpaper, case let .file(file) = patternWallpaper, let backgroundColors = state.backgroundColors {
let resource = file.file.resource
let representation = CachedPatternWallpaperRepresentation(color: Int32(bitPattern: backgroundColors.0.rgb), bottomColor: backgroundColors.1.flatMap { Int32(bitPattern: $0.rgb) }, intensity: state.patternIntensity, rotation: state.rotation)
var data: Data?
if let path = strongSelf.context.account.postbox.mediaBox.completedResourcePath(resource), let maybeData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) {
data = maybeData
} else if let path = strongSelf.context.sharedContext.accountManager.mediaBox.completedResourcePath(resource), let maybeData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) {
data = maybeData
}
if let data = data {
strongSelf.context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
prepare = (strongSelf.context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(resource, representation: representation, complete: true, fetch: true)
|> filter({ $0.complete })
|> take(1)
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
})
} else {
prepare = .complete()
}
} else {
prepare = .complete()
}
let _ = (prepare
|> then(updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
let autoNightModeTriggered = context.sharedContext.currentPresentationData.with { $0 }.autoNightModeTriggered
var currentTheme = current.theme
if autoNightModeTriggered {
@ -170,16 +199,21 @@ final class ThemeAccentColorController: ViewController {
var wallpaper = themeSpecificChatWallpapers[currentTheme.index]
if let backgroundColors = state.backgroundColors {
if let bottomColor = backgroundColors.1 {
wallpaper = .gradient(Int32(bitPattern: backgroundColors.0.rgb), Int32(bitPattern: bottomColor.rgb), WallpaperSettings())
let color = Int32(bitPattern: backgroundColors.0.rgb)
let bottomColor = backgroundColors.1.flatMap { Int32(bitPattern: $0.rgb) }
if let patternWallpaper = state.patternWallpaper {
wallpaper = patternWallpaper.withUpdatedSettings(WallpaperSettings(motion: state.motion, color: color, bottomColor: bottomColor, intensity: state.patternIntensity, rotation: state.rotation))
} else if let bottomColor = bottomColor {
wallpaper = .gradient(color, bottomColor, WallpaperSettings(motion: state.motion, rotation: state.rotation))
} else {
wallpaper = .color(Int32(bitPattern: backgroundColors.0.rgb))
wallpaper = .color(color)
}
}
themeSpecificChatWallpapers[currentTheme.index] = wallpaper
return PresentationThemeSettings(theme: current.theme, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
}) |> deliverOnMainQueue).start(completed: { [weak self] in
})) |> deliverOnMainQueue).start(completed: { [weak self] in
if let strongSelf = self {
strongSelf.completion?()
strongSelf.dismiss()
@ -196,6 +230,7 @@ final class ThemeAccentColorController: ViewController {
}
let _ = (self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings])
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] sharedData in
guard let strongSelf = self else {
return
@ -205,8 +240,12 @@ final class ThemeAccentColorController: ViewController {
let accentColor: UIColor
var initialWallpaper: TelegramWallpaper?
let backgroundColors: (UIColor, UIColor?)?
var patternWallpaper: TelegramWallpaper?
var patternIntensity: Int32 = 50
var motion = false
let messageColors: (UIColor, UIColor?)?
var defaultMessagesColor: UIColor?
var rotation: Int32 = 0
var ignoreDefaultWallpaper = false
@ -235,10 +274,28 @@ final class ThemeAccentColorController: ViewController {
if let initialBackgroundColor = strongSelf.initialBackgroundColor {
backgroundColors = (initialBackgroundColor, nil)
} else if !ignoreDefaultWallpaper {
if case let .color(color) = wallpaper {
if case let .file(file) = wallpaper, file.isPattern {
var patternColor = UIColor(rgb: 0xd6e2ee, alpha: 0.5)
var bottomColor: UIColor?
if let color = file.settings.color {
if let intensity = file.settings.intensity {
patternIntensity = intensity
}
patternColor = UIColor(rgb: UInt32(bitPattern: color))
if let bottomColorValue = file.settings.bottomColor {
bottomColor = UIColor(rgb: UInt32(bitPattern: bottomColorValue))
}
}
patternWallpaper = wallpaper
backgroundColors = (patternColor, bottomColor)
motion = file.settings.motion
rotation = file.settings.rotation ?? 0
} else if case let .color(color) = wallpaper {
backgroundColors = (UIColor(rgb: UInt32(bitPattern: color)), nil)
} else if case let .gradient(topColor, bottomColor, _) = wallpaper {
} else if case let .gradient(topColor, bottomColor, settings) = wallpaper {
backgroundColors = (UIColor(rgb: UInt32(bitPattern: topColor)), UIColor(rgb: UInt32(bitPattern: bottomColor)))
motion = settings.motion
rotation = settings.rotation ?? 0
} else {
backgroundColors = nil
}
@ -263,8 +320,9 @@ final class ThemeAccentColorController: ViewController {
accentColor = theme.rootController.navigationBar.accentTextColor
if case let .color(color) = theme.chat.defaultWallpaper {
backgroundColors = (UIColor(rgb: UInt32(bitPattern: color)), nil)
} else if case let .gradient(topColor, bottomColor, _) = theme.chat.defaultWallpaper {
} else if case let .gradient(topColor, bottomColor, settings) = theme.chat.defaultWallpaper {
backgroundColors = (UIColor(rgb: UInt32(bitPattern: topColor)), UIColor(rgb: UInt32(bitPattern: bottomColor)))
rotation = settings.rotation ?? 0
} else {
backgroundColors = nil
}
@ -277,12 +335,12 @@ final class ThemeAccentColorController: ViewController {
messageColors = (topMessageColor, bottomMessageColor)
}
} else {
accentColor = UIColor(rgb: 0x007ee5)
accentColor = defaultDayAccentColor
backgroundColors = nil
messageColors = nil
}
let initialState = ThemeColorState(section: strongSelf.section, accentColor: accentColor, initialWallpaper: initialWallpaper, backgroundColors: backgroundColors, defaultMessagesColor: defaultMessagesColor, messagesColors: messageColors)
let initialState = ThemeColorState(section: strongSelf.section, accentColor: accentColor, initialWallpaper: initialWallpaper, backgroundColors: backgroundColors, patternWallpaper: patternWallpaper, patternIntensity: patternIntensity, motion: motion, defaultMessagesColor: defaultMessagesColor, messagesColors: messageColors, rotation: rotation)
strongSelf.controllerNode.updateState({ _ in
return initialState

View File

@ -10,10 +10,7 @@ import TelegramPresentationData
import TelegramUIPreferences
import ChatListUI
import AccountContext
private func radiansToDegrees(_ radians: CGFloat) -> CGFloat {
return radians * 180.0 / CGFloat.pi
}
import WallpaperResources
private func generateMaskImage(color: UIColor) -> UIImage? {
return generateImage(CGSize(width: 1.0, height: 80.0), opaque: false, rotatedContext: { size, context in
@ -39,38 +36,73 @@ enum ThemeColorSection: Int {
struct ThemeColorState {
fileprivate var section: ThemeColorSection?
fileprivate var colorPanelCollapsed: Bool
fileprivate var displayPatternPanel: Bool
var accentColor: UIColor
var initialWallpaper: TelegramWallpaper?
var backgroundColors: (UIColor, UIColor?)?
fileprivate var preview: Bool
fileprivate var previousPatternWallpaper: TelegramWallpaper?
var patternWallpaper: TelegramWallpaper?
var patternIntensity: Int32
var motion: Bool
var defaultMessagesColor: UIColor?
var messagesColors: (UIColor, UIColor?)?
var rotation: CGFloat
var rotation: Int32
init() {
self.section = nil
self.colorPanelCollapsed = false
self.displayPatternPanel = false
self.accentColor = .clear
self.initialWallpaper = nil
self.backgroundColors = nil
self.preview = false
self.previousPatternWallpaper = nil
self.patternWallpaper = nil
self.patternIntensity = 50
self.motion = false
self.defaultMessagesColor = nil
self.messagesColors = nil
self.rotation = 0.0
self.rotation = 0
}
init(section: ThemeColorSection, accentColor: UIColor, initialWallpaper: TelegramWallpaper?, backgroundColors: (UIColor, UIColor?)?, defaultMessagesColor: UIColor?, messagesColors: (UIColor, UIColor?)?, rotation: CGFloat = 0.0) {
init(section: ThemeColorSection, accentColor: UIColor, initialWallpaper: TelegramWallpaper?, backgroundColors: (UIColor, UIColor?)?, patternWallpaper: TelegramWallpaper?, patternIntensity: Int32, motion: Bool, defaultMessagesColor: UIColor?, messagesColors: (UIColor, UIColor?)?, rotation: Int32 = 0) {
self.section = section
self.colorPanelCollapsed = false
self.displayPatternPanel = false
self.accentColor = accentColor
self.initialWallpaper = initialWallpaper
self.backgroundColors = backgroundColors
self.preview = false
self.previousPatternWallpaper = nil
self.patternWallpaper = patternWallpaper
self.patternIntensity = patternIntensity
self.motion = motion
self.defaultMessagesColor = defaultMessagesColor
self.messagesColors = messagesColors
self.rotation = rotation
}
func areColorsEqual(to otherState: ThemeColorState) -> Bool {
func isEqual(to otherState: ThemeColorState) -> Bool {
if self.accentColor != otherState.accentColor {
return false
}
if self.preview != otherState.preview {
return false
}
if self.patternWallpaper != otherState.patternWallpaper {
return false
}
if self.patternIntensity != otherState.patternIntensity {
return false
}
if self.rotation != otherState.rotation {
return false
}
if let lhsBackgroundColors = self.backgroundColors, let rhsBackgroundColors = otherState.backgroundColors {
if lhsBackgroundColors.0 != rhsBackgroundColors.0 {
return false
@ -103,6 +135,22 @@ struct ThemeColorState {
}
}
private func calcPatternColors(for state: ThemeColorState) -> [UIColor] {
if let backgroundColors = state.backgroundColors {
let patternIntensity = CGFloat(state.patternIntensity) / 100.0
let topPatternColor = backgroundColors.0.withAlphaComponent(patternIntensity)
if let bottomColor = backgroundColors.1 {
let bottomPatternColor = bottomColor.withAlphaComponent(patternIntensity)
return [topPatternColor, bottomPatternColor]
} else {
return [topPatternColor, topPatternColor]
}
} else {
let patternColor = UIColor(rgb: 0xd6e2ee, alpha: 0.5)
return [patternColor, patternColor]
}
}
final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate {
private let context: AccountContext
private var theme: PresentationTheme
@ -115,23 +163,38 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
private let scrollNode: ASScrollNode
private let pageControlBackgroundNode: ASDisplayNode
private let pageControlNode: PageControlNode
private var motionButtonNode: WallpaperOptionButtonNode
private var patternButtonNode: WallpaperOptionButtonNode
private let chatListBackgroundNode: ASDisplayNode
private var chatNodes: [ListViewItemNode]?
private let maskNode: ASImageNode
private let chatBackgroundNode: WallpaperBackgroundNode
private let backgroundContainerNode: ASDisplayNode
private let backgroundWrapperNode: ASDisplayNode
private let immediateBackgroundNode: ASImageNode
private let signalBackgroundNode: TransformImageNode
private let messagesContainerNode: ASDisplayNode
private var dateHeaderNode: ListViewItemHeaderNode?
private var messageNodes: [ListViewItemNode]?
private var colorPanelNode: WallpaperColorPanelNode
private let colorPanelNode: WallpaperColorPanelNode
private let patternPanelNode: WallpaperPatternPanelNode
private let toolbarNode: WallpaperGalleryToolbarNode
private var serviceColorDisposable: Disposable?
private var colorsDisposable: Disposable?
private let colors = Promise<(UIColor, (UIColor, UIColor?)?, (UIColor, UIColor?)?, TelegramWallpaper?, CGFloat)>()
private var stateDisposable: Disposable?
private let statePromise = Promise<ThemeColorState>()
private let themePromise = Promise<PresentationTheme>()
private var wallpaper: TelegramWallpaper
private var serviceBackgroundColor: UIColor?
private let serviceBackgroundColorPromise = Promise<UIColor>()
private var wallpaperDisposable = MetaDisposable()
private var preview = false
private var currentBackgroundColor: UIColor?
private var patternWallpaper: TelegramWallpaper?
private var patternArguments: PatternWallpaperArguments?
private var patternArgumentsValue = Promise<TransformImageArguments>()
private var patternArgumentsDisposable: Disposable?
private var tapGestureRecognizer: UITapGestureRecognizer?
@ -141,8 +204,10 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
var requiresWallpaperChange: Bool {
switch self.wallpaper {
case .image, .file:
case .image, .builtin:
return true
case let .file(file):
return !file.isPattern
default:
return false
}
@ -170,21 +235,24 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
self.pageControlNode = PageControlNode(dotSpacing: 7.0, dotColor: .white, inactiveDotColor: UIColor.white.withAlphaComponent(0.4))
self.motionButtonNode = WallpaperOptionButtonNode(title: self.presentationData.strings.WallpaperPreview_Motion, value: .check(false))
self.patternButtonNode = WallpaperOptionButtonNode(title: self.presentationData.strings.WallpaperPreview_Pattern, value: .check(false))
self.chatListBackgroundNode = ASDisplayNode()
self.chatBackgroundNode = WallpaperBackgroundNode()
self.chatBackgroundNode.displaysAsynchronously = false
self.backgroundContainerNode = ASDisplayNode()
self.backgroundContainerNode.clipsToBounds = true
self.backgroundWrapperNode = ASDisplayNode()
self.immediateBackgroundNode = ASImageNode()
self.signalBackgroundNode = TransformImageNode()
self.signalBackgroundNode.displaysAsynchronously = false
self.messagesContainerNode = ASDisplayNode()
self.messagesContainerNode.clipsToBounds = true
self.messagesContainerNode.transform = CATransform3DMakeScale(1.0, -1.0, 1.0)
if case .color = wallpaper {
} else {
self.chatBackgroundNode.image = chatControllerBackgroundImage(theme: theme, wallpaper: wallpaper, mediaBox: context.sharedContext.accountManager.mediaBox, knockoutMode: false)
self.chatBackgroundNode.motionEnabled = wallpaper.settings?.motion ?? false
}
self.colorPanelNode = WallpaperColorPanelNode(theme: self.theme, strings: self.presentationData.strings)
self.patternPanelNode = WallpaperPatternPanelNode(context: self.context, theme: self.theme, strings: self.presentationData.strings)
let doneButtonType: WallpaperGalleryToolbarDoneButtonType
if case .edit(_, _, _, true, _) = self.mode {
@ -215,45 +283,48 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
self.chatListBackgroundNode.addSubnode(self.maskNode)
self.addSubnode(self.pageControlBackgroundNode)
self.addSubnode(self.pageControlNode)
self.addSubnode(self.motionButtonNode)
self.addSubnode(self.patternButtonNode)
self.addSubnode(self.colorPanelNode)
self.addSubnode(self.patternPanelNode)
self.addSubnode(self.toolbarNode)
self.scrollNode.addSubnode(self.chatListBackgroundNode)
self.scrollNode.addSubnode(self.chatBackgroundNode)
self.scrollNode.addSubnode(self.backgroundContainerNode)
self.scrollNode.addSubnode(self.messagesContainerNode)
self.backgroundContainerNode.addSubnode(self.backgroundWrapperNode)
self.backgroundWrapperNode.addSubnode(self.immediateBackgroundNode)
self.backgroundWrapperNode.addSubnode(self.signalBackgroundNode)
self.motionButtonNode.addTarget(self, action: #selector(self.toggleMotion), forControlEvents: .touchUpInside)
self.patternButtonNode.addTarget(self, action: #selector(self.togglePattern), forControlEvents: .touchUpInside)
self.colorPanelNode.colorsChanged = { [weak self] firstColor, secondColor, _ in
self.colorPanelNode.colorsChanged = { [weak self] firstColor, secondColor, ended in
if let strongSelf = self, let section = strongSelf.state.section {
switch section {
case .accent:
strongSelf.updateState({ current in
var updated = current
strongSelf.updateState({ current in
var updated = current
updated.preview = !ended
switch section {
case .accent:
if let firstColor = firstColor {
updated.accentColor = firstColor
}
return updated
})
case .background:
strongSelf.updateState({ current in
var updated = current
case .background:
if let firstColor = firstColor {
updated.backgroundColors = (firstColor, secondColor)
} else {
updated.backgroundColors = nil
}
return updated
})
case .messages:
strongSelf.updateState({ current in
var updated = current
case .messages:
if let firstColor = firstColor {
updated.messagesColors = (firstColor, secondColor)
} else {
updated.messagesColors = nil
}
return updated
})
}
}
return updated
})
}
}
@ -271,9 +342,9 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
if let strongSelf = self {
strongSelf.updateState({ current in
var updated = current
var newRotation = updated.rotation + CGFloat.pi / 4.0
if newRotation >= CGFloat.pi * 2.0 {
newRotation = 0.0
var newRotation = updated.rotation + 45
if newRotation >= 360 {
newRotation = 0
}
updated.rotation = newRotation
return updated
@ -281,47 +352,106 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
}
}
self.toolbarNode.cancel = {
dismiss()
}
self.toolbarNode.done = { [weak self] in
self.patternPanelNode.patternChanged = { [weak self] wallpaper, intensity, preview in
if let strongSelf = self {
apply(strongSelf.state, strongSelf.serviceBackgroundColor)
strongSelf.updateState({ current in
var updated = current
updated.patternWallpaper = wallpaper
updated.patternIntensity = intensity ?? 50
updated.preview = preview
return updated
}, animated: true)
}
}
self.colorsDisposable = (self.colors.get()
self.toolbarNode.cancel = { [weak self] in
if let strongSelf = self {
if strongSelf.state.displayPatternPanel {
strongSelf.updateState({ current in
var updated = current
updated.displayPatternPanel = false
updated.patternWallpaper = nil
return updated
}, animated: true)
} else {
dismiss()
}
}
}
var dismissed = false
self.toolbarNode.done = { [weak self] in
if let strongSelf = self {
if strongSelf.state.displayPatternPanel {
strongSelf.updateState({ current in
var updated = current
updated.displayPatternPanel = false
return updated
}, animated: true)
} else {
if !dismissed {
dismissed = true
apply(strongSelf.state, strongSelf.serviceBackgroundColor)
}
}
}
}
self.stateDisposable = (self.statePromise.get()
|> deliverOn(Queue.concurrentDefaultQueue())
|> map { accentColor, backgroundColors, messagesColors, initialWallpaper, rotation -> (PresentationTheme, (TelegramWallpaper, UIImage?), UIColor) in
|> map { state -> (PresentationTheme, (TelegramWallpaper, UIImage?, Signal<(TransformImageArguments) -> DrawingContext?, NoError>?), UIColor, UIColor?, [UIColor], Bool) in
let accentColor = state.accentColor
var backgroundColors = state.backgroundColors
let messagesColors = state.messagesColors
var wallpaper: TelegramWallpaper
var wallpaperImage: UIImage?
var backgroundColors = backgroundColors
var wallpaperSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
var singleBackgroundColor: UIColor?
if let backgroundColors = backgroundColors {
if let bottomColor = backgroundColors.1 {
wallpaper = .gradient(Int32(bitPattern: backgroundColors.0.rgb), Int32(bitPattern: bottomColor.rgb), WallpaperSettings(rotation: Int32(radiansToDegrees(rotation))))
wallpaperImage = chatControllerBackgroundImage(theme: nil, wallpaper: wallpaper, mediaBox: context.sharedContext.accountManager.mediaBox, knockoutMode: false)
if let patternWallpaper = state.patternWallpaper, case let .file(file) = patternWallpaper {
let color = Int32(bitPattern: backgroundColors.0.rgb)
let bottomColor = backgroundColors.1.flatMap { Int32(bitPattern: $0.rgb) }
wallpaper = patternWallpaper.withUpdatedSettings(WallpaperSettings(motion: state.motion, color: color, bottomColor: bottomColor, intensity: state.patternIntensity, rotation: state.rotation))
let dimensions = file.file.dimensions ?? PixelDimensions(width: 100, height: 100)
var convertedRepresentations: [ImageRepresentationWithReference] = []
for representation in file.file.previewRepresentations {
convertedRepresentations.append(ImageRepresentationWithReference(representation: representation, reference: .wallpaper(resource: representation.resource)))
}
convertedRepresentations.append(ImageRepresentationWithReference(representation: .init(dimensions: dimensions, resource: file.file.resource), reference: .wallpaper(resource: file.file.resource)))
wallpaperSignal = patternWallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, representations: convertedRepresentations, mode: .screen, autoFetchFullSize: true)
} else if let bottomColor = backgroundColors.1 {
wallpaper = .gradient(Int32(bitPattern: backgroundColors.0.rgb), Int32(bitPattern: bottomColor.rgb), WallpaperSettings(rotation: state.rotation))
wallpaperSignal = gradientImage([backgroundColors.0, bottomColor], rotation: state.rotation)
} else {
wallpaper = .color(Int32(bitPattern: backgroundColors.0.rgb))
}
} else if let themeReference = mode.themeReference, case let .builtin(theme) = themeReference, initialWallpaper == nil {
} else if let themeReference = mode.themeReference, case let .builtin(theme) = themeReference, state.initialWallpaper == nil {
var suggestedWallpaper: TelegramWallpaper
switch theme {
case .dayClassic:
let topColor = accentColor.withMultiplied(hue: 1.010, saturation: 0.414, brightness: 0.957)
let bottomColor = accentColor.withMultiplied(hue: 1.019, saturation: 0.867, brightness: 0.965)
suggestedWallpaper = .gradient(Int32(bitPattern: topColor.rgb), Int32(bitPattern: bottomColor.rgb), WallpaperSettings())
wallpaperSignal = gradientImage([topColor, bottomColor], rotation: state.rotation)
backgroundColors = (topColor, bottomColor)
singleBackgroundColor = bottomColor
case .nightAccent:
let color = accentColor.withMultiplied(hue: 1.024, saturation: 0.573, brightness: 0.18)
suggestedWallpaper = .color(Int32(bitPattern: color.rgb))
backgroundColors = (color, nil)
singleBackgroundColor = color
default:
suggestedWallpaper = .builtin(WallpaperSettings())
}
wallpaperImage = chatControllerBackgroundImage(theme: nil, wallpaper: suggestedWallpaper, mediaBox: context.sharedContext.accountManager.mediaBox, knockoutMode: false)
wallpaper = suggestedWallpaper
} else {
wallpaper = initialWallpaper ?? .builtin(WallpaperSettings())
wallpaper = state.initialWallpaper ?? .builtin(WallpaperSettings())
wallpaperImage = chatControllerBackgroundImage(theme: nil, wallpaper: wallpaper, mediaBox: context.sharedContext.accountManager.mediaBox, knockoutMode: false)
}
let serviceBackgroundColor = serviceColor(for: (wallpaper, wallpaperImage))
@ -336,13 +466,15 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
let _ = PresentationResourcesChat.principalGraphics(mediaBox: context.account.postbox.mediaBox, knockoutWallpaper: context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper, theme: updatedTheme, wallpaper: wallpaper)
return (updatedTheme, (wallpaper, wallpaperImage), serviceBackgroundColor)
let patternColors = calcPatternColors(for: state)
return (updatedTheme, (wallpaper, wallpaperImage, wallpaperSignal), serviceBackgroundColor, singleBackgroundColor, patternColors, state.preview)
}
|> deliverOnMainQueue).start(next: { [weak self] theme, wallpaperAndImage, serviceBackgroundColor in
|> deliverOnMainQueue).start(next: { [weak self] theme, wallpaperImageAndSignal, serviceBackgroundColor, singleBackgroundColor, patternColors, preview in
guard let strongSelf = self else {
return
}
let (wallpaper, wallpaperImage) = wallpaperAndImage
let (wallpaper, wallpaperImage, wallpaperSignal) = wallpaperImageAndSignal
strongSelf.theme = theme
strongSelf.themeUpdated?(theme)
@ -356,15 +488,69 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
strongSelf.chatListBackgroundNode.backgroundColor = theme.chatList.backgroundColor
strongSelf.maskNode.image = generateMaskImage(color: theme.chatList.backgroundColor)
let patternArguments = PatternWallpaperArguments(colors: patternColors, rotation: wallpaper.settings?.rotation ?? 0, preview: preview)
if case let .color(value) = wallpaper {
strongSelf.backgroundColor = UIColor(rgb: UInt32(bitPattern: value))
strongSelf.chatBackgroundNode.backgroundColor = UIColor(rgb: UInt32(bitPattern: value))
strongSelf.chatBackgroundNode.image = nil
strongSelf.backgroundColor = UIColor(rgb: UInt32(bitPattern: value))
strongSelf.immediateBackgroundNode.backgroundColor = UIColor(rgb: UInt32(bitPattern: value))
strongSelf.immediateBackgroundNode.image = nil
strongSelf.immediateBackgroundNode.isHidden = false
strongSelf.signalBackgroundNode.isHidden = true
strongSelf.patternWallpaper = nil
} else if let wallpaperImage = wallpaperImage {
strongSelf.chatBackgroundNode.imageContentMode = .scaleToFill
strongSelf.chatBackgroundNode.image = wallpaperImage
strongSelf.immediateBackgroundNode.image = wallpaperImage
strongSelf.immediateBackgroundNode.isHidden = false
strongSelf.signalBackgroundNode.isHidden = true
strongSelf.patternWallpaper = nil
} else if let wallpaperSignal = wallpaperSignal {
strongSelf.signalBackgroundNode.contentMode = .scaleToFill
strongSelf.immediateBackgroundNode.isHidden = false
strongSelf.signalBackgroundNode.isHidden = false
if case let .file(file) = wallpaper, let (layout, _, _) = strongSelf.validLayout {
var dispatch = false
if let previousWallpaper = strongSelf.patternWallpaper, case let .file(previousFile) = previousWallpaper, file.id == previousFile.id {
dispatch = true
} else {
strongSelf.patternWallpaper = wallpaper
strongSelf.signalBackgroundNode.setSignal(wallpaperSignal)
}
if dispatch {
if let _ = strongSelf.patternArgumentsDisposable {
} else {
let throttledSignal = strongSelf.patternArgumentsValue.get()
|> mapToThrottled { next -> Signal<TransformImageArguments, NoError> in
return .single(next) |> then(.complete() |> delay(0.016667, queue: Queue.concurrentDefaultQueue()))
}
strongSelf.patternArgumentsDisposable = (throttledSignal).start(next: { [weak self] arguments in
if let strongSelf = self {
let makeImageLayout = strongSelf.signalBackgroundNode.asyncLayout()
let imageApply = makeImageLayout(arguments)
let _ = imageApply()
}
})
}
strongSelf.patternArgumentsValue.set(.single(TransformImageArguments(corners: ImageCorners(), imageSize: layout.size, boundingSize: layout.size, intrinsicInsets: UIEdgeInsets(), custom: patternArguments)))
} else {
strongSelf.patternArgumentsDisposable?.dispose()
strongSelf.patternArgumentsDisposable = nil
let makeImageLayout = strongSelf.signalBackgroundNode.asyncLayout()
let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: layout.size, boundingSize: layout.size, intrinsicInsets: UIEdgeInsets(), custom: patternArguments))
let _ = imageApply()
}
} else {
strongSelf.signalBackgroundNode.setSignal(wallpaperSignal)
strongSelf.patternWallpaper = nil
}
}
strongSelf.wallpaper = wallpaper
strongSelf.patternArguments = patternArguments
strongSelf.currentBackgroundColor = singleBackgroundColor
if let (layout, navigationBarHeight, messagesBottomInset) = strongSelf.validLayout {
strongSelf.updateChatsLayout(layout: layout, topInset: navigationBarHeight, transition: .immediate)
@ -380,14 +566,19 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
|> then(self.serviceBackgroundColorPromise.get()))
|> deliverOnMainQueue).start(next: { [weak self] color in
if let strongSelf = self {
strongSelf.patternPanelNode.serviceBackgroundColor = color
strongSelf.pageControlBackgroundNode.backgroundColor = color
strongSelf.patternButtonNode.buttonColor = color
strongSelf.motionButtonNode.buttonColor = color
}
})
}
deinit {
self.colorsDisposable?.dispose()
self.stateDisposable?.dispose()
self.serviceColorDisposable?.dispose()
self.wallpaperDisposable.dispose()
self.patternArgumentsDisposable?.dispose()
}
override func didLoad() {
@ -400,6 +591,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
self.scrollNode.view.delegate = self
self.pageControlNode.setPage(0.0)
self.colorPanelNode.view.disablesInteractiveTransitionGestureRecognizer = true
self.patternPanelNode.view.disablesInteractiveTransitionGestureRecognizer = true
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.chatTapped))
self.scrollNode.view.addGestureRecognizer(tapGestureRecognizer)
@ -421,13 +613,22 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
var animationCurve = ContainedViewLayoutTransitionCurve.easeInOut
var animationDuration: Double = 0.3
let colorsChanged = !previousState.areColorsEqual(to: self.state)
if colorsChanged {
self.colors.set(.single((self.state.accentColor, self.state.backgroundColors, self.state.messagesColors, self.state.initialWallpaper, self.state.rotation)))
let visibleStateChange = !previousState.isEqual(to: self.state)
if visibleStateChange {
self.statePromise.set(.single(self.state))
}
let colorPanelCollapsed = self.state.colorPanelCollapsed
if (previousState.patternWallpaper != nil) != (self.state.patternWallpaper != nil) {
self.patternButtonNode.setSelected(self.state.patternWallpaper != nil, animated: animated)
}
if previousState.motion != self.state.motion {
self.motionButtonNode.setSelected(self.state.motion, animated: animated)
self.setMotionEnabled(self.state.motion, animated: animated)
}
let sectionChanged = previousState.section != self.state.section
if sectionChanged, let section = self.state.section {
self.view.endEditing(true)
@ -437,7 +638,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
var defaultColor: UIColor?
switch section {
case .accent:
firstColor = self.state.accentColor ?? .blue
firstColor = self.state.accentColor ?? defaultDayAccentColor
secondColor = nil
case .background:
if let themeReference = self.mode.themeReference, case let .builtin(theme) = themeReference {
@ -453,7 +654,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
if let backgroundColors = self.state.backgroundColors {
firstColor = backgroundColors.0
secondColor = backgroundColors.1
} else if let image = self.chatBackgroundNode.image, previousState.initialWallpaper != nil {
} else if previousState.initialWallpaper != nil, let image = self.immediateBackgroundNode.image {
firstColor = averageColor(from: image)
secondColor = nil
} else {
@ -461,7 +662,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
secondColor = nil
}
case .messages:
defaultColor = self.state.defaultMessagesColor ?? (self.state.accentColor ?? UIColor(rgb: 0x007ee5))
defaultColor = self.state.defaultMessagesColor ?? (self.state.accentColor ?? defaultDayAccentColor)
if let messagesColors = self.state.messagesColors {
firstColor = messagesColors.0
secondColor = messagesColors.1
@ -472,7 +673,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
}
self.colorPanelNode.updateState({ _ in
return WallpaperColorPanelNodeState(selection: colorPanelCollapsed ? .none : .first, firstColor: firstColor, defaultColor: defaultColor, secondColor: secondColor, secondColorAvailable: self.state.section != .accent)
return WallpaperColorPanelNodeState(selection: colorPanelCollapsed ? .none : .first, firstColor: firstColor, defaultColor: defaultColor, secondColor: secondColor, secondColorAvailable: self.state.section != .accent, preview: false)
}, animated: animated)
needsLayout = true
@ -490,6 +691,29 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
}, animated: animated)
}
if previousState.displayPatternPanel != self.state.displayPatternPanel {
let cancelButtonType: WallpaperGalleryToolbarCancelButtonType
let doneButtonType: WallpaperGalleryToolbarDoneButtonType
if self.state.displayPatternPanel {
doneButtonType = .apply
cancelButtonType = .discard
} else {
if case .edit(_, _, _, true, _) = self.mode {
doneButtonType = .proceed
} else {
doneButtonType = .set
}
cancelButtonType = .cancel
}
self.toolbarNode.cancelButtonType = cancelButtonType
self.toolbarNode.doneButtonType = doneButtonType
animationCurve = .easeInOut
animationDuration = 0.3
needsLayout = true
}
if needsLayout, let (layout, navigationBarHeight, _) = self.validLayout {
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: animated ? .animated(duration: animationDuration, curve: animationCurve) : .immediate)
}
@ -502,6 +726,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
updated.initialWallpaper = nil
}
updated.section = section
updated.displayPatternPanel = false
return updated
}, animated: true)
}
@ -667,22 +892,18 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
let isFirstLayout = self.validLayout == nil
let bounds = CGRect(origin: CGPoint(), size: layout.size)
let chatListPreviewAvailable = self.state.section == .accent
self.scrollNode.frame = bounds
let toolbarHeight = 49.0 + layout.intrinsicInsets.bottom
self.chatListBackgroundNode.frame = CGRect(x: bounds.width, y: 0.0, width: bounds.width, height: bounds.height)
self.scrollNode.view.contentSize = CGSize(width: bounds.width * 2.0, height: bounds.height)
var pageControlAlpha: CGFloat = 1.0
if self.state.section != .accent {
pageControlAlpha = 0.0
}
self.scrollNode.view.isScrollEnabled = pageControlAlpha > 0.0
self.scrollNode.view.isScrollEnabled = chatListPreviewAvailable
var messagesTransition = transition
if !self.scrollNode.view.isScrollEnabled && self.scrollNode.view.contentOffset.x > 0.0 {
if !chatListPreviewAvailable && self.scrollNode.view.contentOffset.x > 0.0 {
var bounds = self.scrollNode.bounds
bounds.origin.x = 0.0
transition.updateBounds(node: scrollNode, bounds: bounds)
@ -690,6 +911,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
self.pageControlNode.setPage(0.0)
}
let toolbarHeight = 49.0 + layout.intrinsicInsets.bottom
transition.updateFrame(node: self.toolbarNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - toolbarHeight), size: CGSize(width: layout.size.width, height: 49.0 + layout.intrinsicInsets.bottom)))
self.toolbarNode.updateLayout(size: CGSize(width: layout.size.width, height: 49.0), layout: layout, transition: transition)
@ -712,14 +934,36 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
transition.updateFrame(node: self.colorPanelNode, frame: colorPanelFrame)
self.colorPanelNode.updateLayout(size: colorPanelFrame.size, transition: transition)
var patternPanelAlpha: CGFloat = self.state.displayPatternPanel ? 1.0 : 0.0
var patternPanelFrame = colorPanelFrame
transition.updateFrame(node: self.patternPanelNode, frame: patternPanelFrame)
self.patternPanelNode.updateLayout(size: patternPanelFrame.size, transition: transition)
self.patternPanelNode.isUserInteractionEnabled = self.state.displayPatternPanel
transition.updateAlpha(node: self.patternPanelNode, alpha: patternPanelAlpha)
self.chatListBackgroundNode.frame = CGRect(x: bounds.width, y: 0.0, width: bounds.width, height: bounds.height)
transition.updateFrame(node: self.messagesContainerNode, frame: CGRect(x: 0.0, y: navigationBarHeight, width: bounds.width, height: bounds.height - bottomInset - navigationBarHeight))
let backgroundSize = CGSize(width: bounds.width, height: bounds.height - (colorPanelHeight - colorPanelOffset))
transition.updateFrame(node: self.chatBackgroundNode, frame: CGRect(origin: CGPoint(), size: backgroundSize))
self.chatBackgroundNode.updateLayout(size: backgroundSize, transition: transition)
transition.updateFrame(node: self.backgroundContainerNode, frame: CGRect(origin: CGPoint(), size: backgroundSize))
let makeImageLayout = self.signalBackgroundNode.asyncLayout()
let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: layout.size, boundingSize: layout.size, intrinsicInsets: UIEdgeInsets(), custom: self.patternArguments))
let _ = imageApply()
transition.updatePosition(node: self.backgroundWrapperNode, position: CGPoint(x: backgroundSize.width / 2.0, y: backgroundSize.height / 2.0))
transition.updateBounds(node: self.backgroundWrapperNode, bounds: CGRect(origin: CGPoint(), size: layout.size))
transition.updateFrame(node: self.immediateBackgroundNode, frame: CGRect(origin: CGPoint(), size: layout.size))
transition.updateFrame(node: self.signalBackgroundNode, frame: CGRect(origin: CGPoint(), size: layout.size))
let displayOptionButtons = self.state.section == .background
var messagesBottomInset: CGFloat = 0.0
if pageControlAlpha > 0.0 {
if displayOptionButtons {
messagesBottomInset = 46.0
} else if chatListPreviewAvailable {
messagesBottomInset = 37.0
}
self.updateChatsLayout(layout: layout, topInset: navigationBarHeight, transition: transition)
@ -727,6 +971,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
self.validLayout = (layout, navigationBarHeight, messagesBottomInset)
let pageControlAlpha: CGFloat = chatListPreviewAvailable ? 1.0 : 0.0
let pageControlSize = self.pageControlNode.measure(CGSize(width: bounds.width, height: 100.0))
let pageControlFrame = CGRect(origin: CGPoint(x: floor((bounds.width - pageControlSize.width) / 2.0), y: layout.size.height - bottomInset - 28.0), size: pageControlSize)
transition.updateFrame(node: self.pageControlNode, frame: pageControlFrame)
@ -735,13 +980,127 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
transition.updateAlpha(node: self.pageControlNode, alpha: pageControlAlpha)
transition.updateAlpha(node: self.pageControlBackgroundNode, alpha: pageControlAlpha)
transition.updateFrame(node: self.maskNode, frame: CGRect(x: 0.0, y: layout.size.height - bottomInset - 80.0, width: bounds.width, height: 80.0))
let patternButtonSize = self.patternButtonNode.measure(layout.size)
let motionButtonSize = self.motionButtonNode.measure(layout.size)
let maxButtonWidth = max(patternButtonSize.width, motionButtonSize.width)
let buttonSize = CGSize(width: maxButtonWidth, height: 30.0)
let leftButtonFrame = CGRect(origin: CGPoint(x: floor(layout.size.width / 2.0 - buttonSize.width - 10.0), y: layout.size.height - bottomInset - 44.0), size: buttonSize)
let centerButtonFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - buttonSize.width) / 2.0), y: layout.size.height - bottomInset - 44.0), size: buttonSize)
let rightButtonFrame = CGRect(origin: CGPoint(x: ceil(layout.size.width / 2.0 + 10.0), y: layout.size.height - bottomInset - 44.0), size: buttonSize)
var hasMotion: Bool = self.state.backgroundColors?.1 != nil || self.state.patternWallpaper != nil || self.state.displayPatternPanel
var patternAlpha: CGFloat = displayOptionButtons ? 1.0 : 0.0
var motionAlpha: CGFloat = displayOptionButtons && hasMotion ? 1.0 : 0.0
var patternFrame = hasMotion ? leftButtonFrame : centerButtonFrame
var motionFrame = hasMotion ? rightButtonFrame : centerButtonFrame
transition.updateFrame(node: self.patternButtonNode, frame: patternFrame)
transition.updateAlpha(node: self.patternButtonNode, alpha: patternAlpha)
transition.updateFrame(node: self.motionButtonNode, frame: motionFrame)
transition.updateAlpha(node: self.motionButtonNode, alpha: motionAlpha)
if isFirstLayout {
self.setMotionEnabled(self.state.motion, animated: false)
}
}
@objc private func chatTapped() {
self.updateState({ current in
var updated = current
updated.colorPanelCollapsed = !updated.colorPanelCollapsed
if updated.displayPatternPanel {
updated.displayPatternPanel = false
} else {
updated.colorPanelCollapsed = !updated.colorPanelCollapsed
}
return updated
}, animated: true)
}
@objc private func toggleMotion() {
self.updateState({ current in
var updated = current
updated.motion = !updated.motion
return updated
}, animated: true)
}
@objc private func togglePattern() {
let wallpaper = self.state.previousPatternWallpaper ?? self.patternPanelNode.wallpapers.first
let backgroundColor = self.currentBackgroundColor
var appeared = false
self.updateState({ current in
var updated = current
if updated.patternWallpaper != nil {
updated.previousPatternWallpaper = updated.patternWallpaper
updated.patternWallpaper = nil
updated.displayPatternPanel = false
} else {
updated.displayPatternPanel = true
if current.patternWallpaper == nil, let wallpaper = wallpaper {
updated.patternWallpaper = wallpaper
if updated.backgroundColors == nil, let color = backgroundColor {
updated.backgroundColors = (color, nil)
}
appeared = true
}
}
return updated
}, animated: true)
if appeared {
self.patternPanelNode.didAppear(initialWallpaper: wallpaper)
}
}
private let motionAmount: CGFloat = 32.0
private func setMotionEnabled(_ enabled: Bool, animated: Bool) {
guard let (layout, _, _) = self.validLayout else {
return
}
if enabled {
let horizontal = UIInterpolatingMotionEffect(keyPath: "center.x", type: .tiltAlongHorizontalAxis)
horizontal.minimumRelativeValue = motionAmount
horizontal.maximumRelativeValue = -motionAmount
let vertical = UIInterpolatingMotionEffect(keyPath: "center.y", type: .tiltAlongVerticalAxis)
vertical.minimumRelativeValue = motionAmount
vertical.maximumRelativeValue = -motionAmount
let group = UIMotionEffectGroup()
group.motionEffects = [horizontal, vertical]
self.backgroundWrapperNode.view.addMotionEffect(group)
let scale = (layout.size.width + motionAmount * 2.0) / layout.size.width
if animated {
self.backgroundWrapperNode.transform = CATransform3DMakeScale(scale, scale, 1.0)
self.backgroundWrapperNode.layer.animateScale(from: 1.0, to: scale, duration: 0.2)
} else {
self.backgroundWrapperNode.transform = CATransform3DMakeScale(scale, scale, 1.0)
}
} else {
let position = self.backgroundWrapperNode.layer.presentation()?.position
for effect in self.backgroundWrapperNode.view.motionEffects {
self.backgroundWrapperNode.view.removeMotionEffect(effect)
}
let scale = (layout.size.width + motionAmount * 2.0) / layout.size.width
if animated {
self.backgroundWrapperNode.transform = CATransform3DIdentity
self.backgroundWrapperNode.layer.animateScale(from: scale, to: 1.0, duration: 0.2)
if let position = position {
self.backgroundWrapperNode.layer.animatePosition(from: position, to: self.backgroundWrapperNode.layer.position, duration: 0.2)
}
} else {
self.backgroundWrapperNode.transform = CATransform3DIdentity
}
}
}
}

View File

@ -17,7 +17,7 @@ import AppBundle
import PresentationDataUtils
public enum ThemePreviewSource {
case settings(PresentationThemeReference)
case settings(PresentationThemeReference, TelegramWallpaper?)
case theme(TelegramTheme)
case slug(String, TelegramMediaFile)
case media(AnyMediaReference)
@ -95,7 +95,7 @@ public final class ThemePreviewController: ViewController {
}
))
hasInstallsCount = true
} else if case let .settings(themeReference) = source, case let .cloud(theme) = themeReference {
} else if case let .settings(themeReference, _) = source, case let .cloud(theme) = themeReference {
self.theme.set(getTheme(account: context.account, slug: theme.theme.slug)
|> map(Optional.init)
|> `catch` { _ -> Signal<TelegramTheme?, NoError> in
@ -171,7 +171,13 @@ public final class ThemePreviewController: ViewController {
if case .settings = self.source {
isPreview = true
}
self.displayNode = ThemePreviewControllerNode(context: self.context, previewTheme: self.previewTheme, dismiss: { [weak self] in
var initialWallpaper: TelegramWallpaper?
if case let .settings(_, currentWallpaper) = self.source, let wallpaper = currentWallpaper {
initialWallpaper = wallpaper
}
self.displayNode = ThemePreviewControllerNode(context: self.context, previewTheme: self.previewTheme, initialWallpaper: initialWallpaper, dismiss: { [weak self] in
if let strongSelf = self {
strongSelf.dismiss()
}
@ -183,7 +189,9 @@ public final class ThemePreviewController: ViewController {
self.displayNodeDidLoad()
let previewTheme = self.previewTheme
if case let .file(file) = previewTheme.chat.defaultWallpaper, file.id == 0 {
if let initialWallpaper = initialWallpaper {
self.controllerNode.wallpaperPromise.set(.single(initialWallpaper))
} else if case let .file(file) = previewTheme.chat.defaultWallpaper, file.id == 0 {
self.controllerNode.wallpaperPromise.set(cachedWallpaper(account: self.context.account, slug: file.slug, settings: file.settings)
|> mapToSignal { wallpaper in
return .single(wallpaper?.wallpaper ?? .color(Int32(bitPattern: previewTheme.chatList.backgroundColor.rgb)))
@ -203,7 +211,7 @@ public final class ThemePreviewController: ViewController {
let disposable = self.applyDisposable
switch self.source {
case let .settings(reference):
case let .settings(reference, _):
theme = .single(reference)
case .theme, .slug:
theme = combineLatest(self.theme.get() |> take(1), wallpaperPromise.get() |> take(1))

View File

@ -65,7 +65,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
private var statusDisposable: Disposable?
private var fetchDisposable = MetaDisposable()
init(context: AccountContext, previewTheme: PresentationTheme, dismiss: @escaping () -> Void, apply: @escaping () -> Void, isPreview: Bool) {
init(context: AccountContext, previewTheme: PresentationTheme, initialWallpaper: TelegramWallpaper?, dismiss: @escaping () -> Void, apply: @escaping () -> Void, isPreview: Bool) {
self.context = context
self.previewTheme = previewTheme
self.isPreview = isPreview
@ -98,8 +98,10 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.instantChatBackgroundNode = WallpaperBackgroundNode()
self.instantChatBackgroundNode.displaysAsynchronously = false
self.instantChatBackgroundNode.image = chatControllerBackgroundImage(theme: previewTheme, wallpaper: previewTheme.chat.defaultWallpaper, mediaBox: context.sharedContext.accountManager.mediaBox, knockoutMode: context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper)
if case .gradient = previewTheme.chat.defaultWallpaper {
let wallpaper = initialWallpaper ?? previewTheme.chat.defaultWallpaper
self.instantChatBackgroundNode.image = chatControllerBackgroundImage(theme: previewTheme, wallpaper: wallpaper, mediaBox: context.sharedContext.accountManager.mediaBox, knockoutMode: context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper)
if case .gradient = wallpaper {
self.instantChatBackgroundNode.imageContentMode = .scaleToFill
}
self.instantChatBackgroundNode.motionEnabled = previewTheme.chat.defaultWallpaper.settings?.motion ?? false
@ -167,8 +169,12 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.toolbarNode.cancel = {
dismiss()
}
var dismissed = false
self.toolbarNode.done = {
apply()
if !dismissed {
dismissed = true
apply()
}
}
if case let .file(file) = self.previewTheme.chat.defaultWallpaper {
@ -255,16 +261,21 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
})
var patternColor: UIColor?
var patternIntensity: CGFloat = 0.5
var patternArguments: PatternWallpaperArguments?
if let color = file.settings.color {
var patternIntensity: CGFloat = 0.5
if let intensity = file.settings.intensity {
patternIntensity = CGFloat(intensity) / 100.0
}
patternColor = UIColor(rgb: UInt32(bitPattern: color), alpha: patternIntensity)
var patternColors = [UIColor(rgb: UInt32(bitPattern: color), alpha: patternIntensity)]
if let bottomColor = file.settings.bottomColor {
patternColors.append(UIColor(rgb: UInt32(bitPattern: bottomColor), alpha: patternIntensity))
}
patternArguments = PatternWallpaperArguments(colors: patternColors, rotation: file.settings.rotation)
}
strongSelf.remoteChatBackgroundNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets(), emptyColor: patternColor))()
strongSelf.remoteChatBackgroundNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets(), custom: patternArguments))()
}
})
}

View File

@ -8,9 +8,10 @@ import SyncCore
import TelegramPresentationData
import TelegramUIPreferences
import ItemListUI
import ContextUI
import PresentationDataUtils
private func generateSwatchImage(theme: PresentationTheme, color: PresentationThemeAccentColor, bubbles: (UIColor, UIColor?)?, selected: Bool, more: Bool) -> UIImage? {
private func generateSwatchImage(theme: PresentationTheme, themeReference: PresentationThemeReference, color: PresentationThemeAccentColor, bubbles: (UIColor, UIColor?)?, selected: Bool, more: Bool) -> UIImage? {
return generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
@ -50,9 +51,17 @@ private func generateSwatchImage(theme: PresentationTheme, color: PresentationTh
context.addEllipse(in: bounds.insetBy(dx: 10.0, dy: 10.0))
context.clip()
if let colors = bubbles {
var colors: (UIColor, UIColor) = (colors.0, colors.1 ?? colors.0)
var colors: (UIColor, UIColor)?
if let customColors = bubbles {
colors = (customColors.0, customColors.1 ?? customColors.0)
} else if case .builtin(.dayClassic) = themeReference {
let hsb = color.color.hsb
let bubbleColor = UIColor(hue: hsb.0, saturation: (hsb.1 > 0.0 && hsb.2 > 0.0) ? 0.14 : 0.0, brightness: 0.79 + hsb.2 * 0.21, alpha: 1.0)
colors = (bubbleColor, bubbleColor)
}
if let colors = colors {
let gradientColors = [colors.0.cgColor, colors.1.cgColor] as CFArray
var locations: [CGFloat] = [0.0, 1.0]
let colorSpace = CGColorSpaceCreateDeviceRGB()
@ -105,17 +114,21 @@ class ThemeSettingsAccentColorItem: ListViewItem, ItemListItem {
var sectionId: ItemListSectionId
let theme: PresentationTheme
let themeReference: PresentationThemeReference
let colors: [ThemeSettingsAccentColor]
let currentColor: PresentationThemeAccentColor?
let updated: (PresentationThemeAccentColor?) -> Void
let contextAction: ((PresentationThemeReference, PresentationThemeAccentColor?, ASDisplayNode, ContextGesture?) -> Void)?
let openColorPicker: () -> Void
let tag: ItemListItemTag?
init(theme: PresentationTheme, sectionId: ItemListSectionId, colors: [ThemeSettingsAccentColor], currentColor: PresentationThemeAccentColor?, updated: @escaping (PresentationThemeAccentColor?) -> Void, openColorPicker: @escaping () -> Void, tag: ItemListItemTag? = nil) {
init(theme: PresentationTheme, sectionId: ItemListSectionId, themeReference: PresentationThemeReference, colors: [ThemeSettingsAccentColor], currentColor: PresentationThemeAccentColor?, updated: @escaping (PresentationThemeAccentColor?) -> Void, contextAction: ((PresentationThemeReference, PresentationThemeAccentColor?, ASDisplayNode, ContextGesture?) -> Void)?, openColorPicker: @escaping () -> Void, tag: ItemListItemTag? = nil) {
self.theme = theme
self.themeReference = themeReference
self.colors = colors
self.currentColor = currentColor
self.updated = updated
self.contextAction = contextAction
self.openColorPicker = openColorPicker
self.tag = tag
self.sectionId = sectionId
@ -156,24 +169,40 @@ class ThemeSettingsAccentColorItem: ListViewItem, ItemListItem {
}
private final class ThemeSettingsAccentColorNode : ASDisplayNode {
private let containerNode: ContextControllerSourceNode
private let iconNode: ASImageNode
private var action: (() -> Void)?
private var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
override init() {
self.containerNode = ContextControllerSourceNode()
self.iconNode = ASImageNode()
self.iconNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 62.0, height: 62.0))
self.iconNode.isLayerBacked = true
super.init()
self.addSubnode(self.iconNode)
self.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.iconNode)
self.containerNode.activated = { [weak self] gesture in
guard let strongSelf = self else {
gesture.cancel()
return
}
strongSelf.contextAction?(strongSelf.containerNode, gesture)
}
}
func setup(theme: PresentationTheme, color: PresentationThemeAccentColor, bubbles: (UIColor, UIColor?)?, selected: Bool, more: Bool, action: @escaping () -> Void) {
self.iconNode.image = generateSwatchImage(theme: theme, color: color, bubbles: bubbles, selected: selected, more: more)
func setup(theme: PresentationTheme, themeReference: PresentationThemeReference, isDefault: Bool, color: PresentationThemeAccentColor, bubbles: (UIColor, UIColor?)?, selected: Bool, more: Bool, action: @escaping () -> Void, contextAction: ((PresentationThemeReference, PresentationThemeAccentColor?, ASDisplayNode, ContextGesture?) -> Void)?) {
self.iconNode.image = generateSwatchImage(theme: theme, themeReference: themeReference, color: color, bubbles: bubbles, selected: selected, more: more)
self.action = {
action()
}
self.contextAction = { node, gesture in
contextAction?(themeReference, isDefault ? nil : color, node, gesture)
}
}
override func didLoad() {
@ -191,7 +220,8 @@ private final class ThemeSettingsAccentColorNode : ASDisplayNode {
override func layout() {
super.layout()
self.iconNode.frame = self.bounds
self.containerNode.frame = self.bounds
self.iconNode.frame = self.containerNode.bounds
}
}
@ -340,10 +370,7 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode {
var updated = false
var selectedNode: ThemeSettingsAccentColorNode?
strongSelf.customNode.frame = CGRect(origin: CGPoint(x: nodeOffset, y: 9.0), size: CGSize(width: 42.0, height: 42.0))
nodeOffset += nodeSize.width + 18.0
var i = 0
for color in item.colors {
let imageNode: ThemeSettingsAccentColorNode
@ -359,10 +386,12 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode {
let selected: Bool
var accentColor: PresentationThemeAccentColor
var itemColor: PresentationThemeAccentColor?
var isDefault = false
switch color {
case .default:
selected = item.currentColor == nil
accentColor = PresentationThemeAccentColor(baseColor: .blue, accentColor: 0x007ee5, bubbleColors: (0xe1ffc7, nil))
isDefault = true
case let .color(color):
selected = item.currentColor?.baseColor == color
if let currentColor = item.currentColor, selected {
@ -377,7 +406,7 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode {
selectedNode = imageNode
}
imageNode.setup(theme: item.theme, color: accentColor, bubbles: accentColor.customBubbleColors, selected: selected, more: true, action: { [weak self, weak imageNode] in
imageNode.setup(theme: item.theme, themeReference: item.themeReference, isDefault: isDefault, color: accentColor, bubbles: accentColor.customBubbleColors, selected: selected, more: true, action: { [weak self, weak imageNode] in
if selected {
item.openColorPicker()
} else {
@ -386,7 +415,7 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode {
if let imageNode = imageNode {
self?.scrollToNode(imageNode, animated: true)
}
})
}, contextAction: item.contextAction)
imageNode.frame = CGRect(origin: CGPoint(x: nodeOffset, y: 10.0), size: nodeSize)
nodeOffset += nodeSize.width + 18.0
@ -394,7 +423,7 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode {
i += 1
}
// strongSelf.customNode.frame = CGRect(origin: CGPoint(x: nodeOffset, y: 9.0), size: CGSize(width: 42.0, height: 42.0))
strongSelf.customNode.frame = CGRect(origin: CGPoint(x: nodeOffset, y: 9.0), size: CGSize(width: 42.0, height: 42.0))
for k in (i ..< strongSelf.colorNodes.count).reversed() {
let node = strongSelf.colorNodes[k]
@ -402,7 +431,7 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode {
node.removeFromSupernode()
}
let contentSize = CGSize(width: strongSelf.colorNodes.last!.frame.maxX + nodeInset, height: strongSelf.scrollNode.frame.height)
let contentSize = CGSize(width: strongSelf.customNode.frame.maxX + nodeInset, height: strongSelf.scrollNode.frame.height)
if strongSelf.scrollNode.view.contentSize != contentSize {
strongSelf.scrollNode.view.contentSize = contentSize
}

View File

@ -79,9 +79,10 @@ private final class ThemeSettingsControllerArguments {
let disableAnimations: (Bool) -> Void
let selectAppIcon: (String) -> Void
let editTheme: (PresentationCloudTheme) -> Void
let contextAction: (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void
let themeContextAction: (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void
let colorContextAction: (PresentationThemeReference, PresentationThemeAccentColor?, ASDisplayNode, ContextGesture?) -> Void
init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, selectFontSize: @escaping (PresentationFontSize) -> Void, openWallpaperSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference) -> Void, openAutoNightTheme: @escaping () -> Void, openTextSize: @escaping () -> Void, toggleLargeEmoji: @escaping (Bool) -> Void, disableAnimations: @escaping (Bool) -> Void, selectAppIcon: @escaping (String) -> Void, editTheme: @escaping (PresentationCloudTheme) -> Void, contextAction: @escaping (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void) {
init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, selectFontSize: @escaping (PresentationFontSize) -> Void, openWallpaperSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference) -> Void, openAutoNightTheme: @escaping () -> Void, openTextSize: @escaping () -> Void, toggleLargeEmoji: @escaping (Bool) -> Void, disableAnimations: @escaping (Bool) -> Void, selectAppIcon: @escaping (String) -> Void, editTheme: @escaping (PresentationCloudTheme) -> Void, themeContextAction: @escaping (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void, colorContextAction: @escaping (PresentationThemeReference, PresentationThemeAccentColor?, ASDisplayNode, ContextGesture?) -> Void) {
self.context = context
self.selectTheme = selectTheme
self.selectFontSize = selectFontSize
@ -94,7 +95,8 @@ private final class ThemeSettingsControllerArguments {
self.disableAnimations = disableAnimations
self.selectAppIcon = selectAppIcon
self.editTheme = editTheme
self.contextAction = contextAction
self.themeContextAction = themeContextAction
self.colorContextAction = colorContextAction
}
}
@ -331,8 +333,10 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
colorItems.append(contentsOf: colors.map { .color($0) })
return ThemeSettingsAccentColorItem(theme: theme, sectionId: self.section, colors: colorItems, currentColor: currentColor, updated: { color in
return ThemeSettingsAccentColorItem(theme: theme, sectionId: self.section, themeReference: currentTheme, colors: colorItems, currentColor: currentColor, updated: { color in
arguments.selectAccentColor(color)
}, contextAction: { theme, color, node, gesture in
arguments.colorContextAction(theme, color, node, gesture)
}, openColorPicker: {
arguments.openAccentColorPicker(currentTheme)
}, tag: ThemeSettingsEntryTag.accentColor)
@ -356,7 +360,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
arguments.selectTheme(theme)
}
}, contextAction: { theme, node, gesture in
arguments.contextAction(theme.index == currentTheme.index, theme, node, gesture)
arguments.themeContextAction(theme.index == currentTheme.index, theme, node, gesture)
})
case let .iconHeader(theme, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
@ -486,7 +490,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
themeSpecificAccentColors[currentTheme.index] = color
if case let .builtin(theme) = currentTheme, theme == .dayClassic || theme == .nightAccent {
if let wallpaper = current.themeSpecificChatWallpapers[currentTheme.index], wallpaper.isColorOrGradient || wallpaper.isBuiltin {
if let wallpaper = current.themeSpecificChatWallpapers[currentTheme.index], wallpaper.isColorOrGradient || wallpaper.isPattern || wallpaper.isBuiltin {
themeSpecificChatWallpapers[currentTheme.index] = nil
}
}
@ -524,20 +528,20 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
}
})
pushControllerImpl?(controller)
}, contextAction: { isCurrent, reference, node, gesture in
let _ = (context.sharedContext.accountManager.transaction { transaction -> PresentationThemeAccentColor? in
}, themeContextAction: { isCurrent, reference, node, gesture in
let _ = (context.sharedContext.accountManager.transaction { transaction -> (PresentationThemeAccentColor?, TelegramWallpaper?) in
let settings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings) as? PresentationThemeSettings ?? PresentationThemeSettings.defaultSettings
return settings.themeSpecificAccentColors[reference.index]
} |> map { accentColor in
return makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: reference, accentColor: accentColor?.color, bubbleColors: accentColor?.customBubbleColors)
return (settings.themeSpecificAccentColors[reference.index], settings.themeSpecificChatWallpapers[reference.index])
} |> map { accentColor, wallpaper in
return (makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: reference, accentColor: accentColor?.color, bubbleColors: accentColor?.customBubbleColors), wallpaper)
}
|> deliverOnMainQueue).start(next: { theme in
|> deliverOnMainQueue).start(next: { theme, wallpaper in
guard let theme = theme else {
return
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let strings = presentationData.strings
let themeController = ThemePreviewController(context: context, previewTheme: theme, source: .settings(reference))
let themeController = ThemePreviewController(context: context, previewTheme: theme, source: .settings(reference, wallpaper))
var items: [ContextMenuItem] = []
if case let .cloud(theme) = reference {
@ -633,6 +637,27 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: themeController, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture)
presentInGlobalOverlayImpl?(contextController, nil)
})
}, colorContextAction: { reference, accentColor, node, gesture in
guard let theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: reference, accentColor: accentColor?.color, bubbleColors: accentColor?.customBubbleColors) else {
return
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let strings = presentationData.strings
let themeController = ThemePreviewController(context: context, previewTheme: theme, source: .settings(reference, nil))
var items: [ContextMenuItem] = []
let removable = accentColor?.accentColor != nil || accentColor?.bubbleColors != nil
if removable {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Appearance_RemoveThemeColor, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
}, action: { c, f in
c.dismiss(completion: {
let controller = ThemeAccentColorController(context: context, mode: .colors(themeReference: reference))
pushControllerImpl?(controller)
})
})))
}
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: themeController, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture)
presentInGlobalOverlayImpl?(contextController, nil)
})
let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings]), cloudThemes.get(), availableAppIcons, currentAppIconName.get())
@ -811,7 +836,7 @@ public final class ThemeSettingsCrossfadeController: ViewController {
private let snapshotView: UIView?
public init() {
self.snapshotView = UIScreen.main.snapshotView(afterScreenUpdates: false)
self.snapshotView = (UIScreen.main as? UIView)?.snapshotContentTree() //UIScreen.main.snapshotView(afterScreenUpdates: false)
super.init(navigationBarPresentationData: nil)

View File

@ -7,6 +7,7 @@ import Postbox
import TelegramCore
import SyncCore
import TelegramPresentationData
import MergeLists
import TelegramUIPreferences
import ItemListUI
import PresentationDataUtils
@ -15,22 +16,134 @@ import AccountContext
import AppBundle
import ContextUI
private var borderImages: [String: UIImage] = [:]
private struct ThemeSettingsThemeEntry: Comparable, Identifiable {
let index: Int
let themeReference: PresentationThemeReference
let title: String
let accentColor: PresentationThemeAccentColor?
var selected: Bool
let theme: PresentationTheme
var stableId: Int64 {
return self.themeReference.index
}
static func ==(lhs: ThemeSettingsThemeEntry, rhs: ThemeSettingsThemeEntry) -> Bool {
if lhs.index != rhs.index {
return false
}
if lhs.themeReference.index != rhs.themeReference.index {
return false
}
if lhs.accentColor != rhs.accentColor {
return false
}
if lhs.title != rhs.title {
return false
}
if lhs.selected != rhs.selected {
return false
}
if lhs.theme !== rhs.theme {
return false
}
return true
}
static func <(lhs: ThemeSettingsThemeEntry, rhs: ThemeSettingsThemeEntry) -> Bool {
return lhs.index < rhs.index
}
func item(context: AccountContext, action: @escaping (PresentationThemeReference) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?) -> ListViewItem {
return ThemeSettingsThemeIconItem(context: context, themeReference: self.themeReference, accentColor: self.accentColor, selected: self.selected, title: self.title, theme: self.theme, action: action, contextAction: contextAction)
}
}
private class ThemeSettingsThemeIconItem: ListViewItem {
let context: AccountContext
let themeReference: PresentationThemeReference
let accentColor: PresentationThemeAccentColor?
let selected: Bool
let title: String
let theme: PresentationTheme
let action: (PresentationThemeReference) -> Void
let contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?
public init(context: AccountContext, themeReference: PresentationThemeReference, accentColor: PresentationThemeAccentColor?, selected: Bool, title: String, theme: PresentationTheme, action: @escaping (PresentationThemeReference) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?) {
self.context = context
self.themeReference = themeReference
self.accentColor = accentColor
self.selected = selected
self.title = title
self.theme = theme
self.action = action
self.contextAction = contextAction
}
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
async {
let node = ThemeSettingsThemeItemIconNode()
let (nodeLayout, apply) = node.asyncLayout()(self, params)
node.insets = nodeLayout.insets
node.contentSize = nodeLayout.contentSize
Queue.mainQueue().async {
completion(node, {
return (nil, { _ in
apply(false)
})
})
}
}
}
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
Queue.mainQueue().async {
assert(node() is ThemeSettingsThemeItemIconNode)
if let nodeValue = node() as? ThemeSettingsThemeItemIconNode {
let layout = nodeValue.asyncLayout()
async {
let (nodeLayout, apply) = layout(self, params)
Queue.mainQueue().async {
completion(nodeLayout, { _ in
apply(animation.isAnimated)
})
}
}
}
}
}
public var selectable = true
public func selected(listView: ListView) {
self.action(self.themeReference)
}
}
private let textFont = Font.regular(12.0)
private let selectedTextFont = Font.bold(12.0)
private var cachedBorderImages: [String: UIImage] = [:]
private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selected: Bool) -> UIImage? {
let key = "\(theme.list.itemBlocksBackgroundColor.hexString)_\(selected ? "s" + theme.list.itemAccentColor.hexString : theme.list.disclosureArrowColor.hexString)"
if let image = borderImages[key] {
if let image = cachedBorderImages[key] {
return image
} else {
let image = generateImage(CGSize(width: 32.0, height: 32.0), rotatedContext: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.setFillColor(theme.list.itemBlocksBackgroundColor.cgColor)
context.fill(bounds)
context.setBlendMode(.clear)
context.fillEllipse(in: bounds.insetBy(dx: 1.0, dy: 1.0))
if selected {
context.fillEllipse(in: bounds.insetBy(dx: 5.0, dy: 5.0))
} else {
context.fillEllipse(in: bounds.insetBy(dx: 1.0, dy: 1.0))
}
context.setBlendMode(.normal)
let lineWidth: CGFloat
if selected {
var accentColor = theme.list.itemAccentColor
@ -43,13 +156,13 @@ private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selec
context.setStrokeColor(theme.list.disclosureArrowColor.withAlphaComponent(0.4).cgColor)
lineWidth = 1.0
}
if bordered || selected {
context.setLineWidth(lineWidth)
context.strokeEllipse(in: bounds.insetBy(dx: 1.0 + lineWidth / 2.0, dy: 1.0 + lineWidth / 2.0))
}
})?.stretchableImage(withLeftCapWidth: 16, topCapHeight: 16)
borderImages[key] = image
cachedBorderImages[key] = image
return image
}
}
@ -80,9 +193,136 @@ private func createThemeImage(theme: PresentationTheme) -> Signal<(TransformImag
}
private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
private let containerNode: ContextControllerSourceNode
private let imageNode: TransformImageNode
private let overlayNode: ASImageNode
private let titleNode: TextNode
var item: ThemeSettingsThemeIconItem?
init() {
self.containerNode = ContextControllerSourceNode()
self.imageNode = TransformImageNode()
self.imageNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 98.0, height: 62.0))
self.imageNode.isLayerBacked = true
self.overlayNode = ASImageNode()
self.overlayNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 100.0, height: 64.0))
self.overlayNode.isLayerBacked = true
self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
self.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.imageNode)
self.containerNode.addSubnode(self.overlayNode)
self.containerNode.addSubnode(self.titleNode)
self.containerNode.activated = { [weak self] gesture in
guard let strongSelf = self, let item = strongSelf.item else {
gesture.cancel()
return
}
item.contextAction?(item.themeReference, strongSelf.containerNode, gesture)
}
}
override func didLoad() {
super.didLoad()
self.layer.sublayerTransform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
}
func asyncLayout() -> (ThemeSettingsThemeIconItem, ListViewItemLayoutParams) -> (ListViewItemNodeLayout, (Bool) -> Void) {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeImageLayout = self.imageNode.asyncLayout()
let currentItem = self.item
return { [weak self] item, params in
var updatedThemeReference = false
var updatedAccentColor = false
var updatedTheme = false
var updatedSelected = false
if currentItem?.themeReference != item.themeReference {
updatedThemeReference = true
}
if currentItem?.accentColor != item.accentColor {
updatedAccentColor = true
}
if currentItem?.theme !== item.theme {
updatedTheme = true
}
if currentItem?.selected != item.selected {
updatedSelected = true
}
let title = NSAttributedString(string: item.title, font: item.selected ? selectedTextFont : textFont, textColor: item.selected ? item.theme.list.itemAccentColor : item.theme.list.itemPrimaryTextColor)
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: title, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let itemLayout = ListViewItemNodeLayout(contentSize: CGSize(width: 116.0, height: 116.0), insets: UIEdgeInsets())
return (itemLayout, { animated in
if let strongSelf = self {
strongSelf.item = item
if case let .cloud(theme) = item.themeReference, theme.theme.file == nil {
if updatedTheme {
strongSelf.imageNode.setSignal(createThemeImage(theme: item.theme))
}
strongSelf.containerNode.isGestureEnabled = false
} else {
if updatedThemeReference || updatedAccentColor {
strongSelf.imageNode.setSignal(themeIconImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, theme: item.themeReference, accentColor: item.accentColor?.color, bubbleColors: item.accentColor?.plainBubbleColors))
}
strongSelf.containerNode.isGestureEnabled = true
}
if updatedTheme || updatedSelected {
strongSelf.overlayNode.image = generateBorderImage(theme: item.theme, bordered: true, selected: item.selected)
}
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: itemLayout.contentSize)
let _ = titleApply()
let imageSize = CGSize(width: 98.0, height: 62.0)
strongSelf.imageNode.frame = CGRect(origin: CGPoint(x: 10.0, y: 14.0), size: imageSize)
let applyLayout = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: .clear))
applyLayout()
strongSelf.overlayNode.frame = CGRect(origin: CGPoint(x: 9.0, y: 13.0), size: CGSize(width: 100.0, height: 64.0))
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 88.0), size: CGSize(width: itemLayout.contentSize.width, height: 16.0))
}
})
}
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
super.animateInsertion(currentTimestamp, duration: duration, short: short)
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
super.animateRemoved(currentTimestamp, duration: duration)
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
}
override func animateAdded(_ currentTimestamp: Double, duration: Double) {
super.animateAdded(currentTimestamp, duration: duration)
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}
class ThemeSettingsThemeItem: ListViewItem, ItemListItem {
var sectionId: ItemListSectionId
let context: AccountContext
let theme: PresentationTheme
let strings: PresentationStrings
@ -93,7 +333,7 @@ class ThemeSettingsThemeItem: ListViewItem, ItemListItem {
let updatedTheme: (PresentationThemeReference) -> Void
let contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?
let tag: ItemListItemTag?
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, themes: [PresentationThemeReference], displayUnsupported: Bool, themeSpecificAccentColors: [Int64: PresentationThemeAccentColor], currentTheme: PresentationThemeReference, updatedTheme: @escaping (PresentationThemeReference) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?, tag: ItemListItemTag? = nil) {
self.context = context
self.theme = theme
@ -107,15 +347,15 @@ class ThemeSettingsThemeItem: ListViewItem, ItemListItem {
self.tag = tag
self.sectionId = sectionId
}
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
async {
let node = ThemeSettingsThemeItemNode()
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
node.contentSize = layout.contentSize
node.insets = layout.insets
Queue.mainQueue().async {
completion(node, {
return (nil, { _ in apply() })
@ -123,12 +363,12 @@ class ThemeSettingsThemeItem: ListViewItem, ItemListItem {
}
}
}
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
Queue.mainQueue().async {
if let nodeValue = node() as? ThemeSettingsThemeItemNode {
let makeLayout = nodeValue.asyncLayout()
async {
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
Queue.mainQueue().async {
@ -142,211 +382,132 @@ class ThemeSettingsThemeItem: ListViewItem, ItemListItem {
}
}
private func areBubbleColorsEqual(_ lhs: (UIColor, UIColor)?, _ rhs: (UIColor, UIColor)?) -> Bool {
if let (lhsTopColor, lhsBottomColor) = lhs, let (rhsTopColor, rhsBottomColor) = rhs {
return lhsTopColor.rgb == rhsTopColor.rgb && lhsBottomColor.rgb == rhsBottomColor.rgb
} else {
return (lhs == nil) == (rhs == nil)
}
private struct ThemeSettingsThemeItemNodeTransition {
let deletions: [ListViewDeleteItem]
let insertions: [ListViewInsertItem]
let updates: [ListViewUpdateItem]
}
private final class ThemeSettingsThemeItemIconNode : ASDisplayNode {
private let containerNode: ContextControllerSourceNode
private let imageNode: TransformImageNode
private let overlayNode: ASImageNode
private let textNode: ASTextNode
private var action: (() -> Void)?
private var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
private func preparedTransition(context: AccountContext, action: @escaping (PresentationThemeReference) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?, from fromEntries: [ThemeSettingsThemeEntry], to toEntries: [ThemeSettingsThemeEntry]) -> ThemeSettingsThemeItemNodeTransition {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
private var theme: PresentationThemeReference?
private var currentTheme: PresentationTheme?
private var accentColor: UIColor?
private var bubbleColors: (UIColor, UIColor)?
private var bordered: Bool?
private var selected: Bool?
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, action: action, contextAction: contextAction), directionHint: .Down) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, action: action, contextAction: contextAction), directionHint: nil) }
override init() {
self.containerNode = ContextControllerSourceNode()
self.imageNode = TransformImageNode()
self.imageNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 98.0, height: 62.0))
self.imageNode.isLayerBacked = true
self.overlayNode = ASImageNode()
self.overlayNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 100.0, height: 64.0))
self.overlayNode.isLayerBacked = true
self.textNode = ASTextNode()
self.textNode.isUserInteractionEnabled = false
self.textNode.displaysAsynchronously = true
super.init()
self.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.imageNode)
self.containerNode.addSubnode(self.overlayNode)
self.containerNode.addSubnode(self.textNode)
self.containerNode.activated = { [weak self] gesture in
guard let strongSelf = self else {
gesture.cancel()
return
}
strongSelf.contextAction?(strongSelf.containerNode, gesture)
}
}
func setup(context: AccountContext, theme: PresentationThemeReference, accentColor: UIColor?, bubbleColors: (UIColor, UIColor)?, currentTheme: PresentationTheme, title: NSAttributedString, bordered: Bool, selected: Bool, action: @escaping () -> Void, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?) {
let updatedTheme = self.currentTheme == nil || currentTheme !== self.currentTheme!
var contextActionEnabled = true
if case let .cloud(theme) = theme, theme.theme.file == nil {
if updatedTheme || accentColor != self.accentColor {
self.imageNode.setSignal(createThemeImage(theme: currentTheme))
self.currentTheme = currentTheme
self.accentColor = accentColor
contextActionEnabled = false
}
} else {
if theme != self.theme || accentColor != self.accentColor || !areBubbleColorsEqual(bubbleColors, self.bubbleColors) {
self.imageNode.setSignal(themeIconImage(account: context.account, accountManager: context.sharedContext.accountManager, theme: theme, accentColor: accentColor, bubbleColors: bubbleColors))
self.theme = theme
self.accentColor = accentColor
self.bubbleColors = bubbleColors
}
}
if updatedTheme || bordered != self.bordered || selected != self.selected {
self.overlayNode.image = generateBorderImage(theme: currentTheme, bordered: bordered, selected: selected)
self.currentTheme = currentTheme
self.bordered = bordered
self.selected = selected
}
self.textNode.attributedText = title
self.action = action
self.contextAction = contextAction
self.containerNode.isGestureEnabled = contextActionEnabled
}
override func didLoad() {
super.didLoad()
let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:)))
recognizer.delaysTouchesBegan = false
recognizer.tapActionAtPoint = { point in
return .waitForSingleTap
}
self.view.addGestureRecognizer(recognizer)
}
@objc private func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
switch recognizer.state {
case .ended:
if let (gesture, _) = recognizer.lastRecognizedGestureAndLocation {
switch gesture {
case .tap:
self.action?()
default:
break
}
}
default:
break
}
}
override func layout() {
super.layout()
let bounds = self.bounds
self.containerNode.frame = CGRect(origin: CGPoint(), size: bounds.size)
let imageSize = CGSize(width: 98.0, height: 62.0)
self.imageNode.frame = CGRect(origin: CGPoint(x: 10.0, y: 14.0), size: imageSize)
let makeLayout = self.imageNode.asyncLayout()
let applyLayout = makeLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: .clear))
applyLayout()
self.overlayNode.frame = CGRect(origin: CGPoint(x: 9.0, y: 13.0), size: CGSize(width: 100.0, height: 64.0))
self.textNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 14.0 + 60.0 + 4.0 + 9.0), size: CGSize(width: bounds.size.width, height: 16.0))
}
return ThemeSettingsThemeItemNodeTransition(deletions: deletions, insertions: insertions, updates: updates)
}
private let textFont = Font.regular(12.0)
private let selectedTextFont = Font.bold(12.0)
private func ensureThemeVisible(listNode: ListView, themeReference: PresentationThemeReference, animated: Bool) {
var resultNode: ThemeSettingsThemeItemIconNode?
listNode.forEachItemNode { node in
if resultNode == nil, let node = node as? ThemeSettingsThemeItemIconNode {
if node.item?.themeReference.index == themeReference.index {
resultNode = node
}
}
}
if let resultNode = resultNode {
listNode.ensureItemNodeVisible(resultNode, animated: animated, overflow: 56.0)
}
}
class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode
private let maskNode: ASImageNode
private let scrollNode: ASScrollNode
private var nodes: [ThemeSettingsThemeItemIconNode] = []
private let listNode: ListView
private var entries: [ThemeSettingsThemeEntry]?
private var enqueuedTransitions: [ThemeSettingsThemeItemNodeTransition] = []
private var initialized = false
private var item: ThemeSettingsThemeItem?
private var layoutParams: ListViewItemLayoutParams?
var tag: ItemListItemTag? {
return self.item?.tag
}
init() {
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true
self.topStripeNode = ASDisplayNode()
self.topStripeNode.isLayerBacked = true
self.bottomStripeNode = ASDisplayNode()
self.bottomStripeNode.isLayerBacked = true
self.maskNode = ASImageNode()
self.scrollNode = ASScrollNode()
self.listNode = ListView()
self.listNode.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.scrollNode)
self.addSubnode(self.listNode)
}
override func didLoad() {
super.didLoad()
self.scrollNode.view.disablesInteractiveTransitionGestureRecognizer = true
self.scrollNode.view.showsHorizontalScrollIndicator = false
self.listNode.view.disablesInteractiveTransitionGestureRecognizer = true
}
private func scrollToNode(_ node: ThemeSettingsThemeItemIconNode, animated: Bool) {
let bounds = self.scrollNode.view.bounds
let frame = node.frame.insetBy(dx: -48.0, dy: 0.0)
private func enqueueTransition(_ transition: ThemeSettingsThemeItemNodeTransition) {
self.enqueuedTransitions.append(transition)
if frame.minX < bounds.minX || frame.maxX > bounds.maxX {
self.scrollNode.view.scrollRectToVisible(frame, animated: animated)
if let _ = self.item {
while !self.enqueuedTransitions.isEmpty {
self.dequeueTransition()
}
}
}
private func dequeueTransition() {
guard let _ = self.item, let transition = self.enqueuedTransitions.first else {
return
}
self.enqueuedTransitions.remove(at: 0)
var options = ListViewDeleteAndInsertOptions()
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in
if let strongSelf = self, let item = strongSelf.item {
if !strongSelf.initialized {
strongSelf.initialized = true
ensureThemeVisible(listNode: strongSelf.listNode, themeReference: item.currentTheme, animated: false)
}
}
})
}
func asyncLayout() -> (_ item: ThemeSettingsThemeItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let currentItem = self.item
return { item, params, neighbors in
let contentSize: CGSize
let insets: UIEdgeInsets
let separatorHeight = UIScreenPixel
contentSize = CGSize(width: params.width, height: 116.0)
insets = itemListNeighborsGroupedInsets(neighbors)
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
let layoutSize = layout.size
return (layout, { [weak self] in
if let strongSelf = self {
let isFirstLayout = currentItem == nil
strongSelf.item = item
strongSelf.layoutParams = params
strongSelf.scrollNode.view.contentInset = UIEdgeInsets(top: 0.0, left: params.leftInset, bottom: 0.0, right: params.rightInset)
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
if strongSelf.backgroundNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
}
@ -359,7 +520,7 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
if strongSelf.maskNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
}
let hasCorners = itemListHasRoundedBlockLayout(params)
var hasTopCorners = false
var hasBottomCorners = false
@ -382,88 +543,53 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
hasBottomCorners = true
strongSelf.bottomStripeNode.isHidden = hasCorners
}
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
var listInsets = UIEdgeInsets()
listInsets.top += params.leftInset + 4.0
listInsets.bottom += params.rightInset + 4.0
strongSelf.scrollNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 2.0), size: CGSize(width: layoutSize.width, height: layoutSize.height))
strongSelf.listNode.bounds = CGRect(x: 0.0, y: 0.0, width: layoutSize.height, height: layoutSize.width)
strongSelf.listNode.position = CGPoint(x: layoutSize.width / 2.0, y: layoutSize.height / 2.0)
strongSelf.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: CGSize(width: layoutSize.height, height: layoutSize.width), insets: listInsets, duration: 0.0, curve: .Default(duration: nil)), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
let nodeInset: CGFloat = 4.0
let nodeSize = CGSize(width: 116.0, height: 112.0)
var nodeOffset = nodeInset
var updated = false
var selectedNode: ThemeSettingsThemeItemIconNode?
var i = 0
var entries: [ThemeSettingsThemeEntry] = []
var index: Int = 0
for theme in item.themes {
if !item.displayUnsupported, case let .cloud(theme) = theme, theme.theme.file == nil {
continue
}
let imageNode: ThemeSettingsThemeItemIconNode
if strongSelf.nodes.count > i {
imageNode = strongSelf.nodes[i]
} else {
imageNode = ThemeSettingsThemeItemIconNode()
strongSelf.nodes.append(imageNode)
strongSelf.scrollNode.addSubnode(imageNode)
updated = true
}
let selected = theme.index == item.currentTheme.index
if selected {
selectedNode = imageNode
}
let name = themeDisplayName(strings: item.strings, reference: theme)
imageNode.setup(context: item.context, theme: theme, accentColor: item.themeSpecificAccentColors[theme.index]?.color, bubbleColors: item.themeSpecificAccentColors[theme.index]?.plainBubbleColors, currentTheme: item.theme, title: NSAttributedString(string: name, font: selected ? selectedTextFont : textFont, textColor: selected ? item.theme.list.itemAccentColor : item.theme.list.itemPrimaryTextColor, paragraphAlignment: .center), bordered: true, selected: selected, action: { [weak self, weak imageNode] in
item.updatedTheme(theme)
if let imageNode = imageNode {
self?.scrollToNode(imageNode, animated: true)
}
}, contextAction: item.contextAction.flatMap {
contextAction in
return { node, gesture in
contextAction(theme, node, gesture)
}
})
imageNode.frame = CGRect(origin: CGPoint(x: nodeOffset, y: 0.0), size: nodeSize)
nodeOffset += nodeSize.width + 2.0
i += 1
let title = themeDisplayName(strings: item.strings, reference: theme)
let accentColor = item.themeSpecificAccentColors[theme.index]
entries.append(ThemeSettingsThemeEntry(index: index, themeReference: theme, title: title, accentColor: accentColor, selected: item.currentTheme.index == theme.index, theme: item.theme))
index += 1
}
for k in (i ..< strongSelf.nodes.count).reversed() {
let node = strongSelf.nodes[k]
strongSelf.nodes.remove(at: k)
node.removeFromSupernode()
}
if let lastNode = strongSelf.nodes.last {
let contentSize = CGSize(width: lastNode.frame.maxX + nodeInset, height: strongSelf.scrollNode.frame.height)
if strongSelf.scrollNode.view.contentSize != contentSize {
strongSelf.scrollNode.view.contentSize = contentSize
let action: (PresentationThemeReference) -> Void = { [weak self, weak item] themeReference in
if let strongSelf = self {
strongSelf.item?.updatedTheme(themeReference)
ensureThemeVisible(listNode: strongSelf.listNode, themeReference: themeReference, animated: true)
}
}
let transition = preparedTransition(context: item.context, action: action, contextAction: item.contextAction, from: strongSelf.entries ?? [], to: entries)
strongSelf.enqueueTransition(transition)
if updated, let selectedNode = selectedNode {
strongSelf.scrollToNode(selectedNode, animated: false)
}
strongSelf.entries = entries
}
})
}
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
}
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
}

View File

@ -325,6 +325,7 @@ struct WallpaperColorPanelNodeState {
var defaultColor: UIColor?
var secondColor: UIColor?
var secondColorAvailable: Bool
var preview: Bool
}
final class WallpaperColorPanelNode: ASDisplayNode {
@ -365,14 +366,14 @@ final class WallpaperColorPanelNode: ASDisplayNode {
self.colorPickerNode = WallpaperColorPickerNode(strings: strings)
self.swapButton = HighlightableButtonNode()
self.swapButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Settings/ThemeColorSwapIcon"), color: theme.chat.inputPanel.panelControlColor), for: .normal)
self.swapButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Settings/ThemeColorRotateIcon"), color: theme.chat.inputPanel.panelControlColor), for: .normal)
self.addButton = HighlightableButtonNode()
self.addButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Settings/ThemeColorAddIcon"), color: theme.chat.inputPanel.panelControlColor), for: .normal)
self.firstColorFieldNode = ColorInputFieldNode(theme: theme)
self.secondColorFieldNode = ColorInputFieldNode(theme: theme)
self.state = WallpaperColorPanelNodeState(selection: .first, firstColor: nil, secondColor: nil, secondColorAvailable: false)
self.state = WallpaperColorPanelNodeState(selection: .first, firstColor: nil, secondColor: nil, secondColorAvailable: false, preview: false)
super.init()
@ -467,6 +468,7 @@ final class WallpaperColorPanelNode: ASDisplayNode {
if let strongSelf = self {
strongSelf.updateState({ current in
var updated = current
updated.preview = true
switch strongSelf.state.selection {
case .first:
updated.firstColor = color
@ -483,6 +485,7 @@ final class WallpaperColorPanelNode: ASDisplayNode {
if let strongSelf = self {
strongSelf.updateState({ current in
var updated = current
updated.preview = false
switch strongSelf.state.selection {
case .first:
updated.firstColor = color
@ -510,6 +513,7 @@ final class WallpaperColorPanelNode: ASDisplayNode {
var updateLayout = updateLayout
let previousFirstColor = self.state.firstColor
let previousSecondColor = self.state.secondColor
let previousPreview = self.state.preview
self.state = f(self.state)
let firstColor: UIColor
@ -554,8 +558,8 @@ final class WallpaperColorPanelNode: ASDisplayNode {
self.updateLayout(size: size, transition: animated ? .animated(duration: 0.3, curve: .easeInOut) : .immediate)
}
if self.state.firstColor?.rgb != previousFirstColor?.rgb || self.state.secondColor?.rgb != previousSecondColor?.rgb {
self.colorsChanged?(firstColorIsDefault ? nil : firstColor, secondColor, updateLayout)
if self.state.firstColor?.rgb != previousFirstColor?.rgb || self.state.secondColor?.rgb != previousSecondColor?.rgb || self.state.preview != previousPreview {
self.colorsChanged?(firstColorIsDefault ? nil : firstColor, secondColor, !self.state.preview)
}
}
@ -679,14 +683,15 @@ final class WallpaperColorPanelNode: ASDisplayNode {
}
@objc private func swapPressed() {
self.updateState({ current in
var updated = current
if let secondColor = current.secondColor {
updated.firstColor = secondColor
updated.secondColor = current.firstColor
}
return updated
})
self.rotate?()
// self.updateState({ current in
// var updated = current
// if let secondColor = current.secondColor {
// updated.firstColor = secondColor
// updated.secondColor = current.firstColor
// }
// return updated
// })
}
@objc private func addPressed() {

View File

@ -358,8 +358,10 @@ public class WallpaperGalleryController: ViewController {
toolbarNode.cancel = { [weak self] in
self?.dismiss(forceAway: true)
}
var dismissed = false
toolbarNode.done = { [weak self] in
if let strongSelf = self {
if let strongSelf = self, !dismissed {
dismissed = true
if let centralItemNode = strongSelf.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode {
let options = centralItemNode.options
if !strongSelf.entries.isEmpty {
@ -380,7 +382,7 @@ public class WallpaperGalleryController: ViewController {
let completion: (TelegramWallpaper) -> Void = { wallpaper in
let baseSettings = wallpaper.settings
let updatedSettings = WallpaperSettings(blur: options.contains(.blur), motion: options.contains(.motion), color: baseSettings?.color, intensity: baseSettings?.intensity)
let updatedSettings = WallpaperSettings(blur: options.contains(.blur), motion: options.contains(.motion), color: baseSettings?.color, bottomColor: baseSettings?.bottomColor, intensity: baseSettings?.intensity)
let wallpaper = wallpaper.withUpdatedSettings(updatedSettings)
let autoNightModeTriggered = strongSelf.presentationData.autoNightModeTriggered
@ -430,7 +432,7 @@ public class WallpaperGalleryController: ViewController {
}
} else if case let .file(file) = wallpaper, let resource = resource {
if file.isPattern, let color = file.settings.color, let intensity = file.settings.intensity {
let representation = CachedPatternWallpaperRepresentation(color: color, intensity: intensity)
let representation = CachedPatternWallpaperRepresentation(color: color, bottomColor: file.settings.bottomColor, intensity: intensity, rotation: file.settings.rotation)
var data: Data?
if let path = strongSelf.context.account.postbox.mediaBox.completedResourcePath(resource), let maybeData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) {

View File

@ -200,7 +200,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
let subtitleSignal: Signal<String?, NoError>
var actionSignal: Signal<UIBarButtonItem?, NoError> = .single(nil)
var colorSignal: Signal<UIColor, NoError> = serviceColor(from: imagePromise.get())
var color: UIColor?
var patternArguments: PatternWallpaperArguments?
let displaySize: CGSize
let contentSize: CGSize
@ -255,14 +255,23 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
convertedRepresentations.append(ImageRepresentationWithReference(representation: .init(dimensions: dimensions, resource: file.file.resource), reference: reference(for: file.file.resource, media: file.file, message: message)))
if file.isPattern {
var patternColors: [UIColor] = []
var patternColor = UIColor(rgb: 0xd6e2ee, alpha: 0.5)
var patternIntensity: CGFloat = 0.5
if let color = file.settings.color {
if let intensity = file.settings.intensity {
patternIntensity = CGFloat(intensity) / 100.0
}
patternColor = UIColor(rgb: UInt32(bitPattern: color), alpha: patternIntensity)
patternColors.append(patternColor)
if let bottomColor = file.settings.bottomColor {
patternColors.append(UIColor(rgb: UInt32(bitPattern: bottomColor), alpha: patternIntensity))
}
}
patternArguments = PatternWallpaperArguments(colors: patternColors, rotation: file.settings.rotation)
self.backgroundColor = patternColor.withAlphaComponent(1.0)
@ -270,7 +279,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
let makeImageLayout = self.imageNode.asyncLayout()
Queue.concurrentDefaultQueue().async {
let apply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets(), emptyColor: patternColor))
let apply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets(), custom: patternArguments))
Queue.mainQueue().async {
if self.colorPreview {
apply()
@ -280,9 +289,8 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
return
} else if let offset = self.validOffset, self.arguments.colorPreview && abs(offset) > 0.0 {
return
}
else {
color = patternColor
} else {
patternArguments = PatternWallpaperArguments(colors: patternColors, rotation: file.settings.rotation)
}
self.colorPreview = self.arguments.colorPreview
@ -443,7 +451,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
}
self.imageNode.setSignal(signal, dispatchOnDisplayLink: false)
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets(), emptyColor: color))()
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets(), custom: patternArguments))()
self.imageNode.imageUpdated = { [weak self] image in
if let strongSelf = self {
var image = isBlurrable ? image : nil
@ -597,7 +605,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
}
}
@objc func toggleMotion() {
@objc private func toggleMotion() {
let value = !self.motionButtonNode.isSelected
self.motionButtonNode.setSelected(value, animated: true)
self.setMotionEnabled(value, animated: true)
@ -607,7 +615,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
return self.patternButtonNode.isSelected
}
@objc func togglePattern() {
@objc private func togglePattern() {
let value = !self.patternButtonNode.isSelected
self.patternButtonNode.setSelected(value, animated: true)

View File

@ -4,14 +4,31 @@ import AsyncDisplayKit
import Display
import TelegramPresentationData
enum WallpaperGalleryToolbarCancelButtonType {
case cancel
case discard
}
enum WallpaperGalleryToolbarDoneButtonType {
case set
case proceed
case apply
}
final class WallpaperGalleryToolbarNode: ASDisplayNode {
private var theme: PresentationTheme
private let doneButtonType: WallpaperGalleryToolbarDoneButtonType
private let strings: PresentationStrings
var cancelButtonType: WallpaperGalleryToolbarCancelButtonType {
didSet {
self.updateThemeAndStrings(theme: self.theme, strings: self.strings)
}
}
var doneButtonType: WallpaperGalleryToolbarDoneButtonType {
didSet {
self.updateThemeAndStrings(theme: self.theme, strings: self.strings)
}
}
private let cancelButton = HighlightableButtonNode()
private let doneButton = HighlightableButtonNode()
@ -21,8 +38,10 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode {
var cancel: (() -> Void)?
var done: (() -> Void)?
init(theme: PresentationTheme, strings: PresentationStrings, doneButtonType: WallpaperGalleryToolbarDoneButtonType) {
init(theme: PresentationTheme, strings: PresentationStrings, cancelButtonType: WallpaperGalleryToolbarCancelButtonType = .cancel, doneButtonType: WallpaperGalleryToolbarDoneButtonType = .set) {
self.theme = theme
self.strings = strings
self.cancelButtonType = cancelButtonType
self.doneButtonType = doneButtonType
super.init()
@ -73,8 +92,24 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode {
self.separatorNode.backgroundColor = theme.rootController.tabBar.separatorColor
self.topSeparatorNode.backgroundColor = theme.rootController.tabBar.separatorColor
self.cancelButton.setTitle(strings.Common_Cancel, with: Font.regular(17.0), with: theme.list.itemPrimaryTextColor, for: [])
self.doneButton.setTitle(self.doneButtonType == .set ? strings.Wallpaper_Set : strings.Theme_Colors_Proceed, with: Font.regular(17.0), with: theme.list.itemPrimaryTextColor, for: [])
let cancelTitle: String
switch self.cancelButtonType {
case .cancel:
cancelTitle = strings.Common_Cancel
case .discard:
cancelTitle = strings.WallpaperPreview_PatternPaternDiscard
}
let doneTitle: String
switch self.doneButtonType {
case .set:
doneTitle = strings.Wallpaper_Set
case .proceed:
doneTitle = strings.Theme_Colors_Proceed
case .apply:
doneTitle = strings.WallpaperPreview_PatternPaternApply
}
self.cancelButton.setTitle(cancelTitle, with: Font.regular(17.0), with: theme.list.itemPrimaryTextColor, for: [])
self.doneButton.setTitle(doneTitle, with: Font.regular(17.0), with: theme.list.itemPrimaryTextColor, for: [])
}
func updateLayout(size: CGSize, layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
@ -89,7 +124,6 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode {
}
@objc func donePressed() {
self.doneButton.isUserInteractionEnabled = false
self.done?()
}
}

View File

@ -43,7 +43,7 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode {
self.backgroundNode = ASDisplayNode()
self.backgroundNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.3)
self.backgroundNode.cornerRadius = 6.0
self.backgroundNode.cornerRadius = 14.0
self.checkNode = ModernCheckNode(theme: CheckNodeTheme(backgroundColor: .white, strokeColor: .clear, borderColor: .white, hasShadow: false))
self.checkNode.isUserInteractionEnabled = false
@ -147,7 +147,7 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode {
override func measure(_ constrainedSize: CGSize) -> CGSize {
let size = self.textNode.measure(constrainedSize)
self.textSize = size
return CGSize(width: ceil(size.width) + 52.0, height: 30.0)
return CGSize(width: ceil(size.width) + 48.0, height: 30.0)
}
override func layout() {
@ -159,42 +159,15 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode {
return
}
let checkSize = CGSize(width: 18.0, height: 18.0)
let padding: CGFloat = 6.0
let spacing: CGFloat = 9.0
let totalWidth = checkSize.width + spacing + textSize.width
let origin = floor((self.bounds.width - totalWidth) / 2.0)
let checkSize = CGSize(width: 18.0, height: 18.0)
self.checkNode.frame = CGRect(origin: CGPoint(x: origin, y: 6.0), size: checkSize)
self.colorNode.frame = CGRect(origin: CGPoint(x: origin, y: 6.0), size: checkSize)
self.checkNode.frame = CGRect(origin: CGPoint(x: padding, y: padding), size: checkSize)
self.colorNode.frame = CGRect(origin: CGPoint(x: padding, y: padding), size: checkSize)
if let textSize = self.textSize {
self.textNode.frame = CGRect(x: origin + checkSize.width + spacing, y: 6.0 + UIScreenPixel, width: textSize.width, height: textSize.height)
}
}
}
final class WallpaperGalleryDecorationNode: ASDisplayNode {
private let dismiss: () -> Void
private let apply: () -> Void
// private var messageNodes: [ListViewItemNode]?
// private var blurredButtonNode: WallpaperOptionButtonNode?
// private var motionButtonNode: WallpaperOptionButtonNode?
// private var toolbarNode: WallpaperGalleryToolbarNode?
init(source: WallpaperListSource, dismiss: @escaping () -> Void, apply: @escaping () -> Void) {
self.dismiss = dismiss
self.apply = apply
super.init()
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let result = super.hitTest(point, with: event)
if result != self.view {
return result
} else {
return nil
self.textNode.frame = CGRect(x: max(padding + checkSize.width + spacing, padding + checkSize.width + floor((self.bounds.width - padding - checkSize.width - textSize.width) / 2.0) - 2.0), y: 6.0 + UIScreenPixel, width: textSize.width, height: textSize.height)
}
}
}

View File

@ -13,42 +13,64 @@ private let itemSize = CGSize(width: 88.0, height: 88.0)
private let inset: CGFloat = 12.0
final class WallpaperPatternPanelNode: ASDisplayNode {
private let theme: PresentationTheme
private let context: AccountContext
private var theme: PresentationTheme
private let backgroundNode: ASDisplayNode
private let topSeparatorNode: ASDisplayNode
private let scrollNode: ASScrollNode
private let labelNode: ASTextNode
private let titleNode: ImmediateTextNode
private let labelNode: ImmediateTextNode
private var sliderView: TGPhotoEditorSliderView?
private var disposable: Disposable?
private var wallpapers: [TelegramWallpaper] = []
var wallpapers: [TelegramWallpaper] = []
private var currentWallpaper: TelegramWallpaper?
var patternChanged: ((TelegramWallpaper, Int32?, Bool) -> Void)?
var serviceBackgroundColor: UIColor = UIColor(rgb: 0x748698) {
didSet {
guard let nodes = self.scrollNode.subnodes else {
return
}
for case let node as SettingsThemeWallpaperNode in nodes {
node.setOverlayBackgroundColor(self.serviceBackgroundColor.withAlphaComponent(0.4))
}
}
}
private var validLayout: CGSize?
var patternChanged: ((TelegramWallpaper?, Int32?, Bool) -> Void)?
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings) {
self.context = context
self.theme = theme
self.backgroundNode = ASDisplayNode()
self.backgroundNode.backgroundColor = theme.chat.inputPanel.panelBackgroundColor
self.backgroundNode.backgroundColor = self.theme.chat.inputPanel.panelBackgroundColor
self.topSeparatorNode = ASDisplayNode()
self.topSeparatorNode.backgroundColor = theme.chat.inputPanel.panelSeparatorColor
self.topSeparatorNode.backgroundColor = self.theme.chat.inputPanel.panelSeparatorColor
self.scrollNode = ASScrollNode()
self.labelNode = ASTextNode()
self.titleNode = ImmediateTextNode()
self.titleNode.attributedText = NSAttributedString(string: strings.WallpaperPreview_PatternTitle, font: Font.bold(17.0), textColor: theme.rootController.navigationBar.primaryTextColor)
self.labelNode = ImmediateTextNode()
self.labelNode.attributedText = NSAttributedString(string: strings.WallpaperPreview_PatternIntensity, font: Font.regular(14.0), textColor: theme.rootController.navigationBar.primaryTextColor)
super.init()
self.allowsGroupOpacity = true
self.addSubnode(self.backgroundNode)
self.addSubnode(self.topSeparatorNode)
self.addSubnode(self.scrollNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.labelNode)
self.disposable = ((telegramWallpapers(postbox: context.account.postbox, network: context.account.network)
@ -63,46 +85,8 @@ final class WallpaperPatternPanelNode: ASDisplayNode {
}
|> deliverOnMainQueue).start(next: { [weak self] wallpapers in
if let strongSelf = self {
if let subnodes = strongSelf.scrollNode.subnodes {
for node in subnodes {
node.removeFromSupernode()
}
}
var selected = true
for wallpaper in wallpapers {
let node = SettingsThemeWallpaperNode(overlayBackgroundColor: UIColor(rgb: 0x748698, alpha: 0.4))
node.clipsToBounds = true
node.cornerRadius = 5.0
var updatedWallpaper = wallpaper
if case let .file(file) = updatedWallpaper {
let settings = WallpaperSettings(blur: false, motion: false, color: 0xd6e2ee, intensity: 100)
updatedWallpaper = .file(id: file.id, accessHash: file.accessHash, isCreator: file.isCreator, isDefault: file.isDefault, isPattern: file.isPattern, isDark: file.isDark, slug: file.slug, file: file.file, settings: settings)
}
node.setWallpaper(context: context, wallpaper: updatedWallpaper, selected: selected, size: itemSize)
node.pressed = { [weak self, weak node] in
if let strongSelf = self {
strongSelf.currentWallpaper = updatedWallpaper
if let sliderView = strongSelf.sliderView {
strongSelf.patternChanged?(updatedWallpaper, Int32(sliderView.value), false)
}
if let subnodes = strongSelf.scrollNode.subnodes {
for case let subnode as SettingsThemeWallpaperNode in subnodes {
subnode.setSelected(node === subnode, animated: true)
}
}
}
}
strongSelf.scrollNode.addSubnode(node)
selected = false
}
strongSelf.scrollNode.view.contentSize = CGSize(width: (itemSize.width + inset) * CGFloat(wallpapers.count) + inset, height: 112.0)
strongSelf.layoutItemNodes(transition: .immediate)
strongSelf.wallpapers = wallpapers
strongSelf.updateWallpapers()
}
}))
}
@ -135,6 +119,66 @@ final class WallpaperPatternPanelNode: ASDisplayNode {
self.sliderView = sliderView
}
func updateWallpapers() {
guard let subnodes = self.scrollNode.subnodes else {
return
}
for node in subnodes {
node.removeFromSupernode()
}
var selected = true
for wallpaper in wallpapers {
let node = SettingsThemeWallpaperNode(overlayBackgroundColor: UIColor(rgb: 0x748698, alpha: 0.4))
node.clipsToBounds = true
node.cornerRadius = 5.0
var updatedWallpaper = wallpaper
if case let .file(file) = updatedWallpaper {
let settings = WallpaperSettings(blur: false, motion: false, color: 0xd6e2ee, intensity: 100)
updatedWallpaper = .file(id: file.id, accessHash: file.accessHash, isCreator: file.isCreator, isDefault: file.isDefault, isPattern: file.isPattern, isDark: file.isDark, slug: file.slug, file: file.file, settings: settings)
}
node.setWallpaper(context: self.context, wallpaper: updatedWallpaper, selected: selected, size: itemSize)
node.pressed = { [weak self, weak node] in
if let strongSelf = self {
strongSelf.currentWallpaper = updatedWallpaper
if let sliderView = strongSelf.sliderView {
strongSelf.patternChanged?(updatedWallpaper, Int32(sliderView.value), false)
}
if let subnodes = strongSelf.scrollNode.subnodes {
for case let subnode as SettingsThemeWallpaperNode in subnodes {
subnode.setSelected(node === subnode, animated: true)
}
}
}
}
self.scrollNode.addSubnode(node)
selected = false
}
self.scrollNode.view.contentSize = CGSize(width: (itemSize.width + inset) * CGFloat(wallpapers.count) + inset, height: 112.0)
self.layoutItemNodes(transition: .immediate)
}
func updateTheme(_ theme: PresentationTheme) {
self.theme = theme
self.backgroundNode.backgroundColor = self.theme.chat.inputPanel.panelBackgroundColor
self.topSeparatorNode.backgroundColor = self.theme.chat.inputPanel.panelSeparatorColor
self.sliderView?.backColor = self.theme.list.disclosureArrowColor
self.sliderView?.trackColor = self.theme.list.itemAccentColor
self.titleNode.attributedText = NSAttributedString(string: self.labelNode.attributedText?.string ?? "", font: Font.bold(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor)
self.labelNode.attributedText = NSAttributedString(string: self.labelNode.attributedText?.string ?? "", font: Font.regular(14.0), textColor: self.theme.rootController.navigationBar.primaryTextColor)
if let size = self.validLayout {
self.updateLayout(size: size, transition: .immediate)
}
}
@objc func sliderValueChanged() {
guard let sliderView = self.sliderView else {
return
@ -145,37 +189,46 @@ final class WallpaperPatternPanelNode: ASDisplayNode {
}
}
func didAppear() {
if let wallpaper = self.wallpapers.first {
func didAppear(initialWallpaper: TelegramWallpaper? = nil) {
var wallpaper = initialWallpaper ?? self.wallpapers.first
if let wallpaper = wallpaper {
self.currentWallpaper = wallpaper
self.sliderView?.value = 40.0
self.scrollNode.view.contentOffset = CGPoint()
var selected = true
if let subnodes = self.scrollNode.subnodes {
for case let subnode as SettingsThemeWallpaperNode in subnodes {
subnode.setSelected(selected, animated: false)
selected = false
subnode.setSelected(wallpaper == subnode.wallpaper, animated: false)
}
}
if let wallpaper = self.currentWallpaper, let sliderView = self.sliderView {
if initialWallpaper == nil, let wallpaper = self.currentWallpaper, let sliderView = self.sliderView {
self.patternChanged?(wallpaper, Int32(sliderView.value), false)
}
}
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
let separatorHeight = UIScreenPixel
self.validLayout = size
transition.updateFrame(node: self.backgroundNode, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
transition.updateFrame(node: self.topSeparatorNode, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: separatorHeight))
transition.updateFrame(node: self.scrollNode, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: 114.0))
transition.updateFrame(node: self.topSeparatorNode, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: UIScreenPixel))
let labelSize = self.labelNode.measure(self.bounds.size)
transition.updateFrame(node: labelNode, frame: CGRect(origin: CGPoint(x: 14.0, y: 128.0), size: labelSize))
let titleSize = self.titleNode.updateLayout(self.bounds.size)
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((self.bounds.width - titleSize.width) / 2.0), y: 19.0), size: titleSize))
self.sliderView?.frame = CGRect(origin: CGPoint(x: 15.0, y: 136.0), size: CGSize(width: size.width - 15.0 * 2.0, height: 44.0))
let scrollViewFrame = CGRect(x: 0.0, y: 52.0, width: size.width, height: 114.0)
transition.updateFrame(node: self.scrollNode, frame: scrollViewFrame)
let labelSize = self.labelNode.updateLayout(self.bounds.size)
var combinedHeight = labelSize.height + 34.0
var originY: CGFloat = scrollViewFrame.maxY + floor((size.height - scrollViewFrame.maxY - combinedHeight) / 2.0)
transition.updateFrame(node: self.labelNode, frame: CGRect(origin: CGPoint(x: 14.0, y: originY), size: labelSize))
self.sliderView?.frame = CGRect(origin: CGPoint(x: 15.0, y: originY + 8.0), size: CGSize(width: size.width - 15.0 * 2.0, height: 44.0))
self.layoutItemNodes(transition: transition)
}

View File

@ -4,13 +4,15 @@ public struct WallpaperSettings: PostboxCoding, Equatable {
public let blur: Bool
public let motion: Bool
public let color: Int32?
public let bottomColor: Int32?
public let intensity: Int32?
public let rotation: Int32?
public init(blur: Bool = false, motion: Bool = false, color: Int32? = nil, intensity: Int32? = nil, rotation: Int32? = nil) {
public init(blur: Bool = false, motion: Bool = false, color: Int32? = nil, bottomColor: Int32? = nil, intensity: Int32? = nil, rotation: Int32? = nil) {
self.blur = blur
self.motion = motion
self.color = color
self.bottomColor = bottomColor
self.intensity = intensity
self.rotation = rotation
}
@ -19,6 +21,7 @@ public struct WallpaperSettings: PostboxCoding, Equatable {
self.blur = decoder.decodeInt32ForKey("b", orElse: 0) != 0
self.motion = decoder.decodeInt32ForKey("m", orElse: 0) != 0
self.color = decoder.decodeOptionalInt32ForKey("c")
self.bottomColor = decoder.decodeOptionalInt32ForKey("bc")
self.intensity = decoder.decodeOptionalInt32ForKey("i")
self.rotation = decoder.decodeOptionalInt32ForKey("r")
}
@ -31,6 +34,11 @@ public struct WallpaperSettings: PostboxCoding, Equatable {
} else {
encoder.encodeNil(forKey: "c")
}
if let bottomColor = self.bottomColor {
encoder.encodeInt32(bottomColor, forKey: "bc")
} else {
encoder.encodeNil(forKey: "bc")
}
if let intensity = self.intensity {
encoder.encodeInt32(intensity, forKey: "i")
} else {
@ -42,6 +50,28 @@ public struct WallpaperSettings: PostboxCoding, Equatable {
encoder.encodeNil(forKey: "r")
}
}
public static func ==(lhs: WallpaperSettings, rhs: WallpaperSettings) -> Bool {
if lhs.blur != rhs.blur {
return false
}
if lhs.motion != rhs.motion {
return false
}
if lhs.color != rhs.color {
return false
}
if lhs.bottomColor != rhs.bottomColor {
return false
}
if lhs.intensity != rhs.intensity {
return false
}
if lhs.rotation != rhs.rotation {
return false
}
return true
}
}
public enum TelegramWallpaper: OrderedItemListEntryContents, Equatable {
@ -144,7 +174,7 @@ public enum TelegramWallpaper: OrderedItemListEntryContents, Equatable {
return false
}
case let .file(lhsId, _, lhsIsCreator, lhsIsDefault, lhsIsPattern, lhsIsDark, lhsSlug, lhsFile, lhsSettings):
if case let .file(rhsId, _, rhsIsCreator, rhsIsDefault, rhsIsPattern, rhsIsDark, rhsSlug, rhsFile, rhsSettings) = rhs, lhsId == rhsId, lhsIsCreator == rhsIsCreator, lhsIsDefault == rhsIsDefault, lhsIsPattern == rhsIsPattern, lhsIsDark == rhsIsDark, lhsSlug == rhsSlug, lhsFile == rhsFile, lhsSettings == rhsSettings {
if case let .file(rhsId, _, rhsIsCreator, rhsIsDefault, rhsIsPattern, rhsIsDark, rhsSlug, rhsFile, rhsSettings) = rhs, lhsId == rhsId, lhsIsCreator == rhsIsCreator, lhsIsDefault == rhsIsDefault, lhsIsPattern == rhsIsPattern, lhsIsDark == rhsIsDark, lhsSlug == rhsSlug, lhsFile.id == rhsFile.id, lhsSettings == rhsSettings {
return true
} else {
return false

View File

@ -11,7 +11,7 @@ import AppBundle
private var backgroundImageForWallpaper: (TelegramWallpaper, Bool, UIImage)?
public func chatControllerBackgroundImage(theme: PresentationTheme?, wallpaper initialWallpaper: TelegramWallpaper, mediaBox: MediaBox, composed: Bool = true, knockoutMode: 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 {
@ -28,9 +28,10 @@ public func chatControllerBackgroundImage(theme: PresentationTheme?, wallpaper i
}
var backgroundImage: UIImage?
if composed && wallpaper == backgroundImageForWallpaper?.0, (wallpaper.settings?.blur ?? false) == backgroundImageForWallpaper?.1 {
if cached && composed && wallpaper == backgroundImageForWallpaper?.0, (wallpaper.settings?.blur ?? false) == backgroundImageForWallpaper?.1 {
backgroundImage = backgroundImageForWallpaper?.2
} else {
var succeed = true
switch wallpaper {
case .builtin:
if let filePath = getAppBundle().path(forResource: "ChatWallpaperBuiltin0", ofType: "jpg") {
@ -41,14 +42,18 @@ public func chatControllerBackgroundImage(theme: PresentationTheme?, wallpaper i
context.setFillColor(UIColor(rgb: UInt32(bitPattern: color)).withAlphaComponent(1.0).cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
})
case let .gradient(topColor, bottomColor, _):
backgroundImage = generateImage(CGSize(width: 1.0, height: 1280.0), rotatedContext: { size, context in
case let .gradient(topColor, bottomColor, settings):
backgroundImage = generateImage(CGSize(width: 640.0, height: 1280.0), rotatedContext: { size, context in
let gradientColors = [UIColor(rgb: UInt32(bitPattern: topColor)).cgColor, UIColor(rgb: UInt32(bitPattern: bottomColor)).cgColor] as CFArray
var locations: [CGFloat] = [0.0, 1.0]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
context.translateBy(x: 320.0, y: 640.0)
context.rotate(by: CGFloat(settings.rotation ?? 0) * CGFloat.pi / 180.0)
context.translateBy(x: -320.0, y: -640.0)
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
})
case let .image(representations, settings):
@ -63,13 +68,14 @@ public func chatControllerBackgroundImage(theme: PresentationTheme?, wallpaper i
backgroundImage = image
}
if backgroundImage == nil, let path = mediaBox.completedResourcePath(largest.resource) {
succeed = false
backgroundImage = UIImage(contentsOfFile: path)?.precomposed()
}
}
case let .file(file):
if file.isPattern, let color = file.settings.color, let intensity = file.settings.intensity {
var image: UIImage?
let _ = mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedPatternWallpaperRepresentation(color: color, intensity: intensity), complete: true, fetch: true, attemptSynchronously: true).start(next: { data in
let _ = mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedPatternWallpaperRepresentation(color: color, bottomColor: file.settings.bottomColor, intensity: intensity, rotation: file.settings.rotation), complete: true, fetch: true, attemptSynchronously: true).start(next: { data in
if data.complete {
image = UIImage(contentsOfFile: data.path)?.precomposed()
}
@ -86,11 +92,12 @@ public func chatControllerBackgroundImage(theme: PresentationTheme?, wallpaper i
backgroundImage = image
}
if backgroundImage == nil, let path = mediaBox.completedResourcePath(file.file.resource) {
succeed = false
backgroundImage = UIImage(contentsOfFile: path)?.precomposed()
}
}
}
if let backgroundImage = backgroundImage, composed {
if let backgroundImage = backgroundImage, composed && succeed {
backgroundImageForWallpaper = (wallpaper, (wallpaper.settings?.blur ?? false), backgroundImage)
}
}

View File

@ -190,6 +190,7 @@ public func customizeDefaultDayTheme(theme: PresentationTheme, editing: Bool, ac
accentTextColor: accentColor,
accentControlColor: accentColor,
mediaActiveControlColor: accentColor,
fileTitleColor: accentColor,
polls: chat.message.incoming.polls.withUpdated(
radioProgress: accentColor,
highlight: accentColor?.withAlphaComponent(0.12),
@ -229,7 +230,7 @@ public func customizeDefaultDayTheme(theme: PresentationTheme, editing: Bool, ac
fileTitleColor: outgoingFileTitleColor,
fileDescriptionColor: outgoingFileDescriptionColor,
fileDurationColor: outgoingFileDurationColor,
mediaPlaceholderColor: day ? accentColor?.withMultipliedBrightnessBy(0.95) : nil,
mediaPlaceholderColor: day ? accentColor?.withMultipliedBrightnessBy(0.95) : outgoingMediaPlaceholderColor,
polls: chat.message.outgoing.polls.withUpdated(radioButton: outgoingPollsButtonColor, radioProgress: outgoingPollsProgressColor, highlight: outgoingPollsProgressColor?.withAlphaComponent(0.12), separator: outgoingPollsButtonColor, bar: outgoingPollsProgressColor),
actionButtonsFillColor: chat.message.outgoing.actionButtonsFillColor.withUpdated(withWallpaper: serviceBackgroundColor),
actionButtonsStrokeColor: day ? chat.message.outgoing.actionButtonsStrokeColor.withUpdated(withoutWallpaper: accentColor) : nil,

View File

@ -396,8 +396,18 @@ public func serviceColor(for wallpaper: (TelegramWallpaper, UIImage?)) -> UIColo
} else {
return UIColor(rgb: 0x000000, alpha: 0.3)
}
case .file:
if let image = wallpaper.1 {
case let .file(file):
if file.isPattern {
if let color = file.settings.color {
var mixedColor = UIColor(rgb: UInt32(bitPattern: color))
if let bottomColor = file.settings.bottomColor {
mixedColor = mixedColor.mixedWith(UIColor(rgb: UInt32(bitPattern: bottomColor)), 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)
@ -458,7 +468,11 @@ public func chatServiceBackgroundColor(wallpaper: TelegramWallpaper, mediaBox: M
case let .file(file):
if file.isPattern {
if let color = file.settings.color {
return .single(serviceColor(with: UIColor(rgb: UInt32(bitPattern: color))))
var mixedColor = UIColor(rgb: UInt32(bitPattern: color))
if let bottomColor = file.settings.bottomColor {
mixedColor = mixedColor.mixedWith(UIColor(rgb: UInt32(bitPattern: bottomColor)), alpha: 0.5)
}
return .single(serviceColor(with: mixedColor))
} else {
return .single(UIColor(rgb: 0x000000, alpha: 0.3))
}

View File

@ -29,6 +29,15 @@ public extension TelegramWallpaper {
}
}
var isPattern: Bool {
switch self {
case let .file(file):
return file.isPattern
default:
return false
}
}
var isBuiltin: Bool {
switch self {
case .builtin:

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "ic_input_change.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@ -487,7 +487,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, presentationData.dateTimeFormat, message, wallpaper, .full, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
refineContentImageLayout = refineLayout
if case let .file(_, _, isTheme, _) = wallpaper.content, isTheme {
if case let .file(_, _, _, _, isTheme, _) = wallpaper.content, isTheme {
skipStandardStatus = true
}
}

View File

@ -303,7 +303,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
unboundSize = CGSize(width: floor(dimensions.cgSize.width * 0.5), height: floor(dimensions.cgSize.height * 0.5))
} else if let wallpaper = media as? WallpaperPreviewMedia {
switch wallpaper.content {
case let .file(file, _, isTheme, isSupported):
case let .file(file, _, _, _, isTheme, isSupported):
if let thumbnail = file.previewRepresentations.first, var dimensions = file.dimensions {
let dimensionsVertical = dimensions.width < dimensions.height
let thumbnailVertical = thumbnail.dimensions.width < thumbnail.dimensions.height
@ -433,13 +433,19 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
var onlyFullSizeVideoThumbnail: Bool?
var emptyColor: UIColor
var patternArguments: PatternWallpaperArguments?
if isSticker {
emptyColor = .clear
} else {
emptyColor = message.effectivelyIncoming(context.account.peerId) ? theme.chat.message.incoming.mediaPlaceholderColor : theme.chat.message.outgoing.mediaPlaceholderColor
}
if let wallpaper = media as? WallpaperPreviewMedia, case let .file(_, patternColor, _, _) = wallpaper.content {
emptyColor = patternColor ?? UIColor(rgb: 0xd6e2ee, alpha: 0.5)
if let wallpaper = media as? WallpaperPreviewMedia, case let .file(_, patternColor, patternBottomColor, rotation, _, _) = wallpaper.content {
var colors: [UIColor] = []
colors.append(patternColor ?? UIColor(rgb: 0xd6e2ee, alpha: 0.5))
if let patternBottomColor = patternBottomColor {
colors.append(patternBottomColor)
}
patternArguments = PatternWallpaperArguments(colors: colors, rotation: rotation)
}
if mediaUpdated || isSendingUpdated || automaticPlaybackUpdated {
@ -572,7 +578,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
} else if let wallpaper = media as? WallpaperPreviewMedia {
updateImageSignal = { synchronousLoad in
switch wallpaper.content {
case let .file(file, _, isTheme, _):
case let .file(file, _, _, _, isTheme, _):
if isTheme {
return themeImage(account: context.account, accountManager: context.sharedContext.accountManager, fileReference: FileMediaReference.message(message: MessageReference(message), media: file))
} else {
@ -585,12 +591,12 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
}
case let .color(color):
return solidColorImage(color)
case let .gradient(topColor, bottomColor):
return gradientImage([topColor, bottomColor])
case let .gradient(topColor, bottomColor, rotation):
return gradientImage([topColor, bottomColor], rotation: rotation ?? 0)
}
}
if case let .file(file, _, _, _) = wallpaper.content {
if case let .file(file, _, _, _, _, _) = wallpaper.content {
updatedFetchControls = FetchControls(fetch: { manual in
if let strongSelf = self {
strongSelf.fetchDisposable.set(messageMediaFileInteractiveFetched(context: context, message: message, file: file, userInitiated: manual).start())
@ -634,7 +640,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
}
} else if let wallpaper = media as? WallpaperPreviewMedia {
switch wallpaper.content {
case let .file(file, _, _, _):
case let .file(file, _, _, _, _, _):
updatedStatusSignal = messageMediaFileStatus(context: context, messageId: message.id, file: file)
|> map { resourceStatus -> (MediaResourceStatus, MediaResourceStatus?) in
return (resourceStatus, nil)
@ -645,7 +651,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
}
}
let arguments = TransformImageArguments(corners: corners, imageSize: drawingSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), resizeMode: isInlinePlayableVideo ? .fill(.black) : .blurBackground, emptyColor: emptyColor)
let arguments = TransformImageArguments(corners: corners, imageSize: drawingSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), resizeMode: isInlinePlayableVideo ? .fill(.black) : .blurBackground, emptyColor: emptyColor, custom: patternArguments)
let imageFrame = CGRect(origin: CGPoint(x: -arguments.insets.left, y: -arguments.insets.top), size: arguments.drawingSize)

View File

@ -251,7 +251,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
if let wallpaper = parseWallpaperUrl(webpage.url), case let .slug(_, _, color, intensity) = wallpaper {
patternColor = color?.withAlphaComponent(CGFloat(intensity ?? 50) / 100.0)
}
let media = WallpaperPreviewMedia(content: .file(file, patternColor, false, false))
let media = WallpaperPreviewMedia(content: .file(file, patternColor, nil, 0, false, false))
mediaAndFlags = (media, [.preferMediaAspectFilled])
if let fileSize = file.size {
badge = dataSizeString(fileSize, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator)
@ -285,7 +285,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
let components = text.replacingOccurrences(of: "#", with: "").components(separatedBy: "-")
if components.count == 2, let topColorCode = components.first, let bottomColorCode = components.last {
if let topColor = UIColor(hexString: topColorCode), let bottomColor = UIColor(hexString: bottomColorCode) {
let media = WallpaperPreviewMedia(content: .gradient(topColor, bottomColor))
let media = WallpaperPreviewMedia(content: .gradient(topColor, bottomColor, 0))
mediaAndFlags = (media, ChatMessageAttachedContentNodeMediaFlags())
}
} else if components.count == 1, let colorCode = components.first {
@ -311,7 +311,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
file = contentFile
}
if let file = file {
let media = WallpaperPreviewMedia(content: .file(file, nil, true, isSupported))
let media = WallpaperPreviewMedia(content: .file(file, nil, nil, nil, true, isSupported))
mediaAndFlags = (media, ChatMessageAttachedContentNodeMediaFlags())
}
}

View File

@ -82,7 +82,7 @@ public func fetchCachedResourceRepresentation(account: Account, resource: MediaR
if !data.complete {
return .complete()
}
return fetchCachedPatternWallpaperRepresentation(account: account, resource: resource, resourceData: data, representation: representation)
return fetchCachedPatternWallpaperRepresentation(resource: resource, resourceData: data, representation: representation)
}
} else if let representation = representation as? CachedAlbumArtworkRepresentation {
return account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false))
@ -452,21 +452,66 @@ private func fetchCachedPatternWallpaperRepresentation(resource: MediaResource,
let size = CGSize(width: image.size.width * image.scale, height: image.size.height * image.scale)
let backgroundColor = UIColor(rgb: UInt32(bitPattern: representation.color))
let foregroundColor = patternColor(for: backgroundColor, intensity: CGFloat(representation.intensity) / 100.0)
var colors: [UIColor] = []
if let bottomColor = representation.bottomColor {
colors.append(UIColor(rgb: UInt32(bitPattern: bottomColor)))
}
colors.append(UIColor(rgb: UInt32(bitPattern: representation.color)))
let intensity = CGFloat(representation.intensity) / 100.0
let colorImage = generateImage(size, contextGenerator: { size, c in
let rect = CGRect(origin: CGPoint(), size: size)
c.setBlendMode(.copy)
c.setFillColor(backgroundColor.cgColor)
c.fill(rect)
if colors.count == 1, let color = colors.first {
c.setFillColor(color.cgColor)
c.fill(rect)
} else {
let gradientColors = colors.map { $0.cgColor } as CFArray
let delta: CGFloat = 1.0 / (CGFloat(colors.count) - 1.0)
var locations: [CGFloat] = []
for i in 0 ..< colors.count {
locations.append(delta * CGFloat(i))
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
c.saveGState()
c.translateBy(x: rect.width / 2.0, y: rect.height / 2.0)
c.rotate(by: CGFloat(representation.rotation ?? 0) * CGFloat.pi / -180.0)
c.translateBy(x: -rect.width / 2.0, y: -rect.height / 2.0)
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: rect.height), options: CGGradientDrawingOptions())
c.restoreGState()
}
c.setBlendMode(.normal)
if let cgImage = image.cgImage {
c.clip(to: rect, mask: cgImage)
}
c.setFillColor(foregroundColor.cgColor)
c.fill(rect)
if colors.count == 1, let color = colors.first {
c.setFillColor(patternColor(for: color, intensity: intensity).cgColor)
c.fill(rect)
} else {
let gradientColors = colors.map { patternColor(for: $0, intensity: intensity).cgColor } as CFArray
let delta: CGFloat = 1.0 / (CGFloat(colors.count) - 1.0)
var locations: [CGFloat] = []
for i in 0 ..< colors.count {
locations.append(delta * CGFloat(i))
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
c.translateBy(x: rect.width / 2.0, y: rect.height / 2.0)
c.rotate(by: CGFloat(representation.rotation ?? 0) * CGFloat.pi / -180.0)
c.translateBy(x: -rect.width / 2.0, y: -rect.height / 2.0)
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: rect.height), options: CGGradientDrawingOptions())
}
}, scale: 1.0)
if let colorImage = colorImage, let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) {
@ -554,89 +599,6 @@ private func fetchCachedBlurredWallpaperRepresentation(account: Account, resourc
}) |> runOn(Queue.concurrentDefaultQueue())
}
private func fetchCachedPatternWallpaperMaskRepresentation(account: Account, resource: MediaResource, resourceData: MediaResourceData, representation: CachedPatternWallpaperMaskRepresentation) -> Signal<CachedMediaResourceRepresentationResult, NoError> {
return Signal({ subscriber in
if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) {
if let image = UIImage(data: data) {
let path = NSTemporaryDirectory() + "\(arc4random64())"
let url = URL(fileURLWithPath: path)
let size = representation.size != nil ? image.size.aspectFitted(representation.size!) : CGSize(width: image.size.width * image.scale, height: image.size.height * image.scale)
let alphaImage = generateImage(size, contextGenerator: { size, context in
context.setFillColor(UIColor.black.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
context.clip(to: CGRect(origin: CGPoint(), size: size), mask: image.cgImage!)
context.setFillColor(UIColor.white.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
}, scale: 1.0)
if let alphaImage = alphaImage, let alphaDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) {
CGImageDestinationSetProperties(alphaDestination, [:] as CFDictionary)
let colorQuality: Float = 0.87
let options = NSMutableDictionary()
options.setObject(colorQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString)
CGImageDestinationAddImage(alphaDestination, alphaImage.cgImage!, options as CFDictionary)
if CGImageDestinationFinalize(alphaDestination) {
subscriber.putNext(.temporaryPath(path))
subscriber.putCompletion()
}
}
}
}
return EmptyDisposable
}) |> runOn(Queue.concurrentDefaultQueue())
}
private func fetchCachedPatternWallpaperRepresentation(account: Account, resource: MediaResource, resourceData: MediaResourceData, representation: CachedPatternWallpaperRepresentation) -> Signal<CachedMediaResourceRepresentationResult, NoError> {
return Signal({ subscriber in
if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) {
if let image = UIImage(data: data) {
let path = NSTemporaryDirectory() + "\(arc4random64())"
let url = URL(fileURLWithPath: path)
let size = CGSize(width: image.size.width * image.scale, height: image.size.height * image.scale)
let backgroundColor = UIColor(rgb: UInt32(bitPattern: representation.color))
let foregroundColor = patternColor(for: backgroundColor, intensity: CGFloat(representation.intensity) / 100.0)
let colorImage = generateImage(size, contextGenerator: { size, c in
let rect = CGRect(origin: CGPoint(), size: size)
c.setBlendMode(.copy)
c.setFillColor(backgroundColor.cgColor)
c.fill(rect)
c.setBlendMode(.normal)
if let cgImage = image.cgImage {
c.clip(to: rect, mask: cgImage)
}
c.setFillColor(foregroundColor.cgColor)
c.fill(rect)
}, scale: 1.0)
if let colorImage = colorImage, let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) {
CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary)
let colorQuality: Float = 0.9
let options = NSMutableDictionary()
options.setObject(colorQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString)
CGImageDestinationAddImage(colorDestination, colorImage.cgImage!, options as CFDictionary)
if CGImageDestinationFinalize(colorDestination) {
subscriber.putNext(.temporaryPath(path))
subscriber.putCompletion()
}
}
}
}
return EmptyDisposable
}) |> runOn(Queue.concurrentDefaultQueue())
}
public enum FetchAlbumArtworkError {
case moreDataNeeded(Int)
}

View File

@ -167,7 +167,7 @@ public func upgradedAccounts(accountManager: AccountManager, rootPath: String, e
let _ = accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: true, fetch: true).start()
if file.isPattern {
if let color = file.settings.color, let intensity = file.settings.intensity {
let _ = accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedPatternWallpaperRepresentation(color: color, intensity: intensity), complete: true, fetch: true).start()
let _ = accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedPatternWallpaperRepresentation(color: color, bottomColor: file.settings.bottomColor, intensity: intensity, rotation: file.settings.rotation), complete: true, fetch: true).start()
}
} else {
if file.settings.blur {

View File

@ -5,9 +5,9 @@ import TelegramCore
import SyncCore
enum WallpaperPreviewMediaContent: Equatable {
case file(TelegramMediaFile, UIColor?, Bool, Bool)
case file(TelegramMediaFile, UIColor?, UIColor?, Int32?, Bool, Bool)
case color(UIColor)
case gradient(UIColor, UIColor)
case gradient(UIColor, UIColor, Int32?)
}
final class WallpaperPreviewMedia: Media {

View File

@ -96,7 +96,7 @@ public struct PresentationCloudTheme: PostboxCoding, Equatable {
return false
}
if lhs.resolvedWallpaper != rhs.resolvedWallpaper {
return false
return false
}
return true
}

View File

@ -295,6 +295,26 @@ public enum PatternWallpaperDrawMode {
case screen
}
public struct PatternWallpaperArguments: TransformImageCustomArguments {
let colors: [UIColor]
let rotation: Int32?
let preview: Bool
public init(colors: [UIColor], rotation: Int32?, preview: Bool = false) {
self.colors = colors
self.rotation = rotation
self.preview = preview
}
public func serialized() -> NSArray {
let array = NSMutableArray()
array.addObjects(from: self.colors)
array.add(NSNumber(value: self.rotation ?? 0))
array.add(NSNumber(value: self.preview))
return array
}
}
private func patternWallpaperDatas(account: Account, accountManager: AccountManager, representations: [ImageRepresentationWithReference], mode: PatternWallpaperDrawMode, autoFetchFullSize: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> {
if let smallestRepresentation = smallestImageRepresentation(representations.map({ $0.representation })), let largestRepresentation = largestImageRepresentation(representations.map({ $0.representation })), let smallestIndex = representations.firstIndex(where: { $0.representation == smallestRepresentation }), let largestIndex = representations.firstIndex(where: { $0.representation == largestRepresentation }) {
@ -394,6 +414,8 @@ public func patternWallpaperImageInternal(thumbnailData: Data?, fullSizeData: Da
return .single((thumbnailData, fullSizeData, fullSizeComplete))
|> map { (thumbnailData, fullSizeData, fullSizeComplete) in
return { arguments in
var scale = scale
let drawingRect = arguments.drawingRect
var fittedSize = arguments.imageSize
if abs(fittedSize.width - arguments.boundingSize.width).isLessThanOrEqualTo(CGFloat(1.0)) {
@ -414,35 +436,69 @@ public func patternWallpaperImageInternal(thumbnailData: Data?, fullSizeData: Da
}
}
if let combinedColor = arguments.emptyColor {
if let customArguments = arguments.custom as? PatternWallpaperArguments, let combinedColor = customArguments.colors.first {
if customArguments.preview {
scale = max(1.0, UIScreenScale - 1.0)
}
let combinedColors = customArguments.colors
let colors = combinedColors.reversed().map { $0.withAlphaComponent(1.0) }
let color = combinedColor.withAlphaComponent(1.0)
let intensity = combinedColor.alpha
if fullSizeImage == nil {
let context = DrawingContext(size: arguments.drawingSize, scale: 1.0, clear: true)
context.withFlippedContext { c in
c.setBlendMode(.copy)
c.setFillColor(color.cgColor)
c.fill(arguments.drawingRect)
}
addCorners(context, arguments: arguments)
return context
}
let context = DrawingContext(size: arguments.drawingSize, scale: scale, clear: true)
let context = DrawingContext(size: arguments.drawingSize, scale: fullSizeImage == nil ? 1.0 : scale, clear: true)
context.withFlippedContext { c in
c.setBlendMode(.copy)
c.setFillColor(color.cgColor)
c.fill(arguments.drawingRect)
if colors.count == 1 {
c.setFillColor(color.cgColor)
c.fill(arguments.drawingRect)
} else {
let gradientColors = colors.map { $0.cgColor } as CFArray
let delta: CGFloat = 1.0 / (CGFloat(colors.count) - 1.0)
var locations: [CGFloat] = []
for i in 0 ..< colors.count {
locations.append(delta * CGFloat(i))
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
c.saveGState()
c.translateBy(x: arguments.drawingSize.width / 2.0, y: arguments.drawingSize.height / 2.0)
c.rotate(by: CGFloat(customArguments.rotation ?? 0) * CGFloat.pi / -180.0)
c.translateBy(x: -arguments.drawingSize.width / 2.0, y: -arguments.drawingSize.height / 2.0)
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: arguments.drawingSize.height), options: CGGradientDrawingOptions())
c.restoreGState()
}
if let fullSizeImage = fullSizeImage {
c.setBlendMode(.normal)
c.interpolationQuality = .medium
c.clip(to: fittedRect, mask: fullSizeImage)
c.setFillColor(patternColor(for: color, intensity: intensity, prominent: prominent).cgColor)
c.fill(arguments.drawingRect)
if colors.count == 1 {
c.setFillColor(patternColor(for: color, intensity: intensity, prominent: prominent).cgColor)
c.fill(arguments.drawingRect)
c.fill(arguments.drawingRect)
} else {
let gradientColors = colors.map { patternColor(for: $0, intensity: intensity, prominent: prominent).cgColor } as CFArray
let delta: CGFloat = 1.0 / (CGFloat(colors.count) - 1.0)
var locations: [CGFloat] = []
for i in 0 ..< colors.count {
locations.append(delta * CGFloat(i))
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
c.translateBy(x: arguments.drawingSize.width / 2.0, y: arguments.drawingSize.height / 2.0)
c.rotate(by: CGFloat(customArguments.rotation ?? 0) * CGFloat.pi / -180.0)
c.translateBy(x: -arguments.drawingSize.width / 2.0, y: -arguments.drawingSize.height / 2.0)
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: arguments.drawingSize.height), options: CGGradientDrawingOptions())
}
}
}
@ -490,7 +546,7 @@ public func solidColorImage(_ color: UIColor) -> Signal<(TransformImageArguments
})
}
public func gradientImage(_ colors: [UIColor]) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
public func gradientImage(_ colors: [UIColor], rotation: Int32 = 0) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
guard !colors.isEmpty else {
return .complete()
}
@ -515,6 +571,10 @@ public func gradientImage(_ colors: [UIColor]) -> Signal<(TransformImageArgument
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
c.translateBy(x: arguments.drawingSize.width / 2.0, y: arguments.drawingSize.height / 2.0)
c.rotate(by: CGFloat(rotation) * CGFloat.pi / 180.0)
c.translateBy(x: -arguments.drawingSize.width / 2.0, y: -arguments.drawingSize.height / 2.0)
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: arguments.drawingSize.height), options: CGGradientDrawingOptions())
}
@ -893,7 +953,7 @@ public func themeImage(account: Account, accountManager: AccountManager, fileRef
let _ = accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: true, fetch: true).start()
if file.isPattern, let color = file.settings.color, let intensity = file.settings.intensity {
return accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedPatternWallpaperRepresentation(color: color, intensity: intensity), complete: true, fetch: true)
return accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedPatternWallpaperRepresentation(color: color, bottomColor: file.settings.bottomColor, intensity: intensity, rotation: file.settings.rotation), complete: true, fetch: true)
|> mapToSignal { data in
if data.complete, let data = try? Data(contentsOf: URL(fileURLWithPath: data.path)), let image = UIImage(data: data) {
return .single((theme, image, thumbnailData))
@ -1085,7 +1145,7 @@ public func themeIconImage(account: Account, accountManager: AccountManager, the
let _ = accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: true, fetch: true).start()
if file.isPattern, let color = file.settings.color, let intensity = file.settings.intensity {
return accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedPatternWallpaperRepresentation(color: color, intensity: intensity), complete: true, fetch: true)
return accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedPatternWallpaperRepresentation(color: color, bottomColor: file.settings.bottomColor, intensity: intensity, rotation: file.settings.rotation), complete: true, fetch: true)
|> mapToSignal { _ in
return .complete()
}