mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various theme improvements
This commit is contained in:
parent
eb406e4032
commit
e19439a33e
@ -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";
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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: {
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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))()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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?()
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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))
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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:
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Settings/ThemeColorSwapIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Settings/ThemeColorSwapIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "ic_input_change.pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Settings/ThemeColorSwapIcon.imageset/ic_input_change.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Settings/ThemeColorSwapIcon.imageset/ic_input_change.pdf
vendored
Normal file
Binary file not shown.
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
Binary file not shown.
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -96,7 +96,7 @@ public struct PresentationCloudTheme: PostboxCoding, Equatable {
|
||||
return false
|
||||
}
|
||||
if lhs.resolvedWallpaper != rhs.resolvedWallpaper {
|
||||
return false
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user