Update themes

This commit is contained in:
Ilya Laktyushin 2019-12-24 23:03:40 +03:00
parent efacf13a9e
commit e27d764dae
33 changed files with 1396 additions and 789 deletions

View File

@ -3,7 +3,7 @@
@implementation Serialization @implementation Serialization
- (NSUInteger)currentLayer { - (NSUInteger)currentLayer {
return 107; return 108;
} }
- (id _Nullable)parseMessage:(NSData * _Nullable)data { - (id _Nullable)parseMessage:(NSData * _Nullable)data {

View File

@ -153,6 +153,11 @@ public enum WallpaperUrlParameter {
case gradient(UIColor, UIColor, Int32?) case gradient(UIColor, UIColor, Int32?)
} }
public enum ResolvedUrlSettingsSection {
case theme
case devices
}
public enum ResolvedUrl { public enum ResolvedUrl {
case externalUrl(String) case externalUrl(String)
case peer(PeerId?, ChatControllerInteractionNavigateToPeer) case peer(PeerId?, ChatControllerInteractionNavigateToPeer)
@ -171,6 +176,7 @@ public enum ResolvedUrl {
case wallpaper(WallpaperUrlParameter) case wallpaper(WallpaperUrlParameter)
case theme(String) case theme(String)
case wallet(address: String, amount: Int64?, comment: String?) case wallet(address: String, amount: Int64?, comment: String?)
case settings(ResolvedUrlSettingsSection)
} }
public enum NavigateToChatKeepStack { public enum NavigateToChatKeepStack {

View File

@ -628,7 +628,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
let frame = CGRect(origin: CGPoint(x: floor((boundingWidth - size.width) / 2.0), y: 0.0), size: size) let frame = CGRect(origin: CGPoint(x: floor((boundingWidth - size.width) / 2.0), y: 0.0), size: size)
let item: InstantPageItem let item: InstantPageItem
if let url = url, let coverId = coverId, let image = media[coverId] as? TelegramMediaImage { if let url = url, let coverId = coverId, let image = media[coverId] as? TelegramMediaImage {
let loadedContent = TelegramMediaWebpageLoadedContent(url: url, displayUrl: url, hash: 0, type: "video", websiteName: nil, title: nil, text: nil, embedUrl: url, embedType: "video", embedSize: PixelDimensions(size), duration: nil, author: nil, image: image, file: nil, files: nil, instantPage: nil) let loadedContent = TelegramMediaWebpageLoadedContent(url: url, displayUrl: url, hash: 0, type: "video", websiteName: nil, title: nil, text: nil, embedUrl: url, embedType: "video", embedSize: PixelDimensions(size), duration: nil, author: nil, image: image, file: nil, attributes: [], instantPage: nil)
let content = TelegramMediaWebpageContent.Loaded(loadedContent) let content = TelegramMediaWebpageContent.Loaded(loadedContent)
item = InstantPageImageItem(frame: frame, webPage: webpage, media: InstantPageMedia(index: embedIndex, media: TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.LocalWebpage, id: -1), content: content), url: nil, caption: nil, credit: nil), attributes: [], interactive: true, roundCorners: false, fit: false) item = InstantPageImageItem(frame: frame, webPage: webpage, media: InstantPageMedia(index: embedIndex, media: TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.LocalWebpage, id: -1), content: content), url: nil, caption: nil, credit: nil), attributes: [], interactive: true, roundCorners: false, fit: false)

View File

@ -36,12 +36,33 @@ private class PickerAnnotationContainerView: UIView {
private class LocationMapView: MKMapView, UIGestureRecognizerDelegate { private class LocationMapView: MKMapView, UIGestureRecognizerDelegate {
var customHitTest: ((CGPoint) -> Bool)? var customHitTest: ((CGPoint) -> Bool)?
private var allowSelectionChanges = true
@objc override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { @objc override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if let customHitTest = self.customHitTest, customHitTest(gestureRecognizer.location(in: self)) { if let customHitTest = self.customHitTest, customHitTest(gestureRecognizer.location(in: self)) {
return false return false
} }
return true return self.allowSelectionChanges
}
public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
let pointInside = super.point(inside: point, with: event)
if !pointInside {
return pointInside
}
for annotation in self.annotations(in: self.visibleMapRect) where annotation is LocationPinAnnotation {
guard let view = self.view(for: annotation as! MKAnnotation) else {
continue
}
if view.frame.insetBy(dx: -16.0, dy: -16.0).contains(point) {
self.allowSelectionChanges = true
return true
}
}
self.allowSelectionChanges = false
return pointInside
} }
} }

View File

@ -13,6 +13,7 @@ import AuthorizationUI
private func generateButtonImage(backgroundColor: UIColor, borderColor: UIColor, highlightColor: UIColor?) -> UIImage? { private func generateButtonImage(backgroundColor: UIColor, borderColor: UIColor, highlightColor: UIColor?) -> UIImage? {
return generateImage(CGSize(width: 1.0, height: 44.0), contextGenerator: { size, context in return generateImage(CGSize(width: 1.0, height: 44.0), contextGenerator: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size) let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
if let highlightColor = highlightColor { if let highlightColor = highlightColor {
context.setFillColor(highlightColor.cgColor) context.setFillColor(highlightColor.cgColor)

View File

@ -150,7 +150,7 @@ final class ThemeAccentColorController: ViewController {
} }
} }
let prepare: Signal<Void, NoError> let prepare: Signal<CreateThemeResult, CreateThemeError>
if let patternWallpaper = state.patternWallpaper, case let .file(file) = patternWallpaper, let backgroundColors = state.backgroundColors { if let patternWallpaper = state.patternWallpaper, case let .file(file) = patternWallpaper, let backgroundColors = state.backgroundColors {
let resource = file.file.resource 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) let representation = CachedPatternWallpaperRepresentation(color: Int32(bitPattern: backgroundColors.0.rgb), bottomColor: backgroundColors.1.flatMap { Int32(bitPattern: $0.rgb) }, intensity: state.patternIntensity, rotation: state.rotation)
@ -167,7 +167,8 @@ final class ThemeAccentColorController: ViewController {
prepare = (strongSelf.context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(resource, representation: representation, complete: true, fetch: true) prepare = (strongSelf.context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(resource, representation: representation, complete: true, fetch: true)
|> filter({ $0.complete }) |> filter({ $0.complete })
|> take(1) |> take(1)
|> mapToSignal { _ -> Signal<Void, NoError> in |> castError(CreateThemeError.self)
|> mapToSignal { _ -> Signal<CreateThemeResult, CreateThemeError> in
return .complete() return .complete()
}) })
} else { } else {
@ -201,67 +202,116 @@ final class ThemeAccentColorController: ViewController {
completion(updatedTheme, settings) completion(updatedTheme, settings)
}) })
} else { } else if case let .colors(theme, create) = strongSelf.mode {
let _ = (prepare var baseTheme: TelegramBaseTheme
|> then(updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in var telegramTheme: TelegramTheme?
let autoNightModeTriggered = context.sharedContext.currentPresentationData.with { $0 }.autoNightModeTriggered if case let .cloud(theme) = theme, let settings = theme.theme.settings {
var currentTheme = current.theme telegramTheme = theme.theme
if autoNightModeTriggered { baseTheme = settings.baseTheme
currentTheme = current.automaticThemeSwitchSetting.theme } else {
} baseTheme = .classic
}
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
var themeSpecificAccentColors = current.themeSpecificAccentColors let accentColor = Int32(bitPattern: state.accentColor.rgb)
var themeSpecificCustomColors = current.themeSpecificCustomColors var bubbleColors: (Int32, Int32)?
var customColors = themeSpecificCustomColors[currentTheme.index]?.colors ?? [] if let messagesColors = state.messagesColors {
if let secondColor = messagesColors.1 {
var bubbleColors: (Int32, Int32?)? bubbleColors = (Int32(bitPattern: messagesColors.0.rgb), Int32(bitPattern: secondColor.rgb))
if let messagesColors = state.messagesColors {
if let secondColor = messagesColors.1 {
bubbleColors = (Int32(bitPattern: messagesColors.0.rgb), Int32(bitPattern: secondColor.rgb))
} else {
bubbleColors = (Int32(bitPattern: messagesColors.0.rgb), nil)
}
}
let index: Int32
var exists: Bool
if let initialIndex = initialAccentColor?.index, initialIndex != -1 {
index = initialIndex
exists = true
} else { } else {
index = Int32(bitPattern: arc4random()) bubbleColors = (Int32(bitPattern: messagesColors.0.rgb), Int32(bitPattern: messagesColors.0.rgb))
exists = false
} }
}
let color = PresentationThemeAccentColor(index: index, baseColor: .custom, accentColor: Int32(bitPattern: state.accentColor.rgb), bubbleColors: bubbleColors)
themeSpecificAccentColors[currentTheme.index] = color var wallpaper: TelegramWallpaper? = nil // themeSpecificChatWallpapers[currentTheme.index]
if let coloredWallpaper = coloredWallpaper {
if exists { wallpaper = coloredWallpaper
if let index = customColors.firstIndex(where: { $0.index == index }) { }
customColors[index] = color
} else { let settings = TelegramThemeSettings(baseTheme: baseTheme, accentColor: accentColor, messageColors: bubbleColors, wallpaper: wallpaper)
customColors.append(color)
let save: Signal<Void, NoError>
if !create, let theme = telegramTheme {
let _ = (prepare |> then(updateTheme(account: context.account, accountManager: context.sharedContext.accountManager, theme: theme, title: theme.title, slug: theme.slug, resource: nil, settings: settings))
|> deliverOnMainQueue).start(next: { next in
if case let .result(resultTheme) = next {
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start()
let _ = (updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
// if let resource = resultTheme.file?.resource, let data = themeData {
// context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
// }
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: wallpaper))
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
themeSpecificChatWallpapers[themeReference.index] = nil
return PresentationThemeSettings(theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificCustomColors: current.themeSpecificCustomColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
}) |> deliverOnMainQueue).start(completed: {
if let strongSelf = self {
strongSelf.completion?()
strongSelf.dismiss()
}
})
} }
} else { }, error: { error in
customColors.append(color) })
} } else {
let title = generateThemeName(accentColor: state.accentColor)
var wallpaper = themeSpecificChatWallpapers[currentTheme.index] let _ = (prepare |> then(createTheme(account: context.account, title: title, resource: nil, thumbnailData: nil, settings: settings))
if let coloredWallpaper = coloredWallpaper { |> deliverOnMainQueue).start(next: { next in
wallpaper = coloredWallpaper if case let .result(resultTheme) = next {
} let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start()
let _ = (updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
themeSpecificChatWallpapers[coloredThemeIndex(reference: currentTheme, accentColor: color)] = wallpaper // if let resource = resultTheme.file?.resource, let data = themeData {
themeSpecificCustomColors[currentTheme.index] = PresentationThemeCustomColors(colors: customColors) // context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
// }
return PresentationThemeSettings(theme: current.theme, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificCustomColors: themeSpecificCustomColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
})) |> deliverOnMainQueue).start(completed: { [weak self] in let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: wallpaper))
if let strongSelf = self {
strongSelf.completion?() var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
strongSelf.dismiss() themeSpecificChatWallpapers[themeReference.index] = nil
}
}) return PresentationThemeSettings(theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificCustomColors: current.themeSpecificCustomColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
}) |> deliverOnMainQueue).start(completed: {
if let strongSelf = self {
strongSelf.completion?()
strongSelf.dismiss()
}
})
}
}, error: { error in
})
}
// let _ = (prepare
// |> then(updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
// let autoNightModeTriggered = context.sharedContext.currentPresentationData.with { $0 }.autoNightModeTriggered
// var currentTheme = current.theme
// if autoNightModeTriggered {
// currentTheme = current.automaticThemeSwitchSetting.theme
// }
//
//
//
// if create {
//
// } else {
//
// }
//
// var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
//
//
// themeSpecificChatWallpapers[coloredThemeIndex(reference: currentTheme, accentColor: nil)] = wallpaper
//
// return PresentationThemeSettings(theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificCustomColors: current.themeSpecificCustomColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
// })) |> deliverOnMainQueue).start(completed: { [weak self] in
// if let strongSelf = self {
// strongSelf.completion?()
// strongSelf.dismiss()
// }
// })
} }
} }
}) })
@ -326,69 +376,116 @@ final class ThemeAccentColorController: ViewController {
} }
if let themeReference = strongSelf.mode.themeReference { if let themeReference = strongSelf.mode.themeReference {
let themeSpecificAccentColor = settings.themeSpecificAccentColors[themeReference.index]
if case .colors(_, true) = strongSelf.mode {
} else if themeSpecificAccentColor?.baseColor == .custom {
strongSelf.initialAccentColor = themeSpecificAccentColor
}
accentColor = themeSpecificAccentColor?.color ?? defaultDayAccentColor
var wallpaper: TelegramWallpaper var wallpaper: TelegramWallpaper
if let accentColor = themeSpecificAccentColor, let customWallpaper = settings.themeSpecificChatWallpapers[coloredThemeIndex(reference: themeReference, accentColor: accentColor)] {
wallpaper = customWallpaper if case .colors(_, true) = strongSelf.mode {
} else if let customWallpaper = settings.themeSpecificChatWallpapers[themeReference.index] { let themeSpecificAccentColor = settings.themeSpecificAccentColors[themeReference.index]
wallpaper = customWallpaper accentColor = themeSpecificAccentColor?.color ?? defaultDayAccentColor
} else {
let theme = makePresentationTheme(mediaBox: strongSelf.context.sharedContext.accountManager.mediaBox, themeReference: themeReference, accentColor: nil) ?? defaultPresentationTheme if let accentColor = themeSpecificAccentColor, let customWallpaper = settings.themeSpecificChatWallpapers[coloredThemeIndex(reference: themeReference, accentColor: accentColor)] {
if case let .builtin(themeName) = themeReference { wallpaper = customWallpaper
if case .dayClassic = themeName, settings.themeSpecificAccentColors[themeReference.index] != nil { } else if let customWallpaper = settings.themeSpecificChatWallpapers[themeReference.index] {
ignoreDefaultWallpaper = true wallpaper = customWallpaper
} else if case .nightAccent = themeName { } else {
ignoreDefaultWallpaper = true let theme = makePresentationTheme(mediaBox: strongSelf.context.sharedContext.accountManager.mediaBox, themeReference: themeReference, accentColor: nil) ?? defaultPresentationTheme
if case let .builtin(themeName) = themeReference {
if case .dayClassic = themeName, settings.themeSpecificAccentColors[themeReference.index] != nil {
ignoreDefaultWallpaper = true
} else if case .nightAccent = themeName {
ignoreDefaultWallpaper = true
}
}
wallpaper = theme.chat.defaultWallpaper
}
if case let .builtin(settings) = wallpaper {
var defaultPatternWallpaper: TelegramWallpaper?
for wallpaper in wallpapers {
//JqSUrO0-mFIBAAAAWwTvLzoWGQI, 25
if case let .file(file) = wallpaper, file.slug == "-Xc-np9y2VMCAAAARKr0yNNPYW0" {
defaultPatternWallpaper = wallpaper
break
}
}
if let defaultPatternWallpaper = defaultPatternWallpaper {
wallpaper = defaultPatternWallpaper.withUpdatedSettings(WallpaperSettings(blur: settings.blur, motion: settings.motion, color: 0xd6e2ee, bottomColor: nil, intensity: 40, rotation: nil))
} }
} }
wallpaper = theme.chat.defaultWallpaper if !wallpaper.isColorOrGradient && !ignoreDefaultWallpaper {
} initialWallpaper = wallpaper
}
if case let .builtin(settings) = wallpaper {
var defaultPatternWallpaper: TelegramWallpaper?
for wallpaper in wallpapers { if let initialBackgroundColor = strongSelf.initialBackgroundColor {
//JqSUrO0-mFIBAAAAWwTvLzoWGQI, 25 backgroundColors = (initialBackgroundColor, nil)
if case let .file(file) = wallpaper, file.slug == "-Xc-np9y2VMCAAAARKr0yNNPYW0" { } else if !ignoreDefaultWallpaper {
defaultPatternWallpaper = wallpaper extractWallpaperParameters(wallpaper)
break } else {
backgroundColors = nil
}
if let bubbleColors = settings.themeSpecificAccentColors[themeReference.index]?.customBubbleColors {
if let bottomColor = bubbleColors.1 {
messageColors = (bubbleColors.0, bottomColor)
} else {
messageColors = (bubbleColors.0, nil)
}
} else {
if let themeReference = strongSelf.mode.themeReference, themeReference == .builtin(.dayClassic), settings.themeSpecificAccentColors[themeReference.index] == nil {
messageColors = (UIColor(rgb: 0xe1ffc7), nil)
} else {
messageColors = nil
} }
} }
if let defaultPatternWallpaper = defaultPatternWallpaper {
wallpaper = defaultPatternWallpaper.withUpdatedSettings(WallpaperSettings(blur: settings.blur, motion: settings.motion, color: 0xd6e2ee, bottomColor: nil, intensity: 40, rotation: nil))
}
}
if !wallpaper.isColorOrGradient && !ignoreDefaultWallpaper {
initialWallpaper = wallpaper
}
if let initialBackgroundColor = strongSelf.initialBackgroundColor {
backgroundColors = (initialBackgroundColor, nil)
} else if !ignoreDefaultWallpaper {
extractWallpaperParameters(wallpaper)
} else { } else {
backgroundColors = nil let presentationTheme = makePresentationTheme(mediaBox: strongSelf.context.sharedContext.accountManager.mediaBox, themeReference: themeReference)!
} if case let .cloud(theme) = themeReference, let themeSettings = theme.theme.settings {
accentColor = UIColor(rgb: UInt32(bitPattern: themeSettings.accentColor))
if let bubbleColors = settings.themeSpecificAccentColors[themeReference.index]?.customBubbleColors {
if let bottomColor = bubbleColors.1 { if let customWallpaper = settings.themeSpecificChatWallpapers[themeReference.index] {
messageColors = (bubbleColors.0, bottomColor) wallpaper = customWallpaper
} else {
wallpaper = presentationTheme.chat.defaultWallpaper
}
extractWallpaperParameters(wallpaper)
if !wallpaper.isColorOrGradient {
initialWallpaper = wallpaper
}
if let colors = themeSettings.messageColors {
let topMessageColor = UIColor(rgb: UInt32(bitPattern: colors.top))
let bottomMessageColor = UIColor(rgb: UInt32(bitPattern: colors.bottom))
if topMessageColor.rgb == bottomMessageColor.rgb {
messageColors = (topMessageColor, nil)
} else {
messageColors = (topMessageColor, bottomMessageColor)
}
} else {
messageColors = nil
}
} else { } else {
messageColors = (bubbleColors.0, nil) let theme = makePresentationTheme(mediaBox: strongSelf.context.sharedContext.accountManager.mediaBox, themeReference: themeReference)!
}
} else { accentColor = theme.rootController.navigationBar.accentTextColor
if let themeReference = strongSelf.mode.themeReference, themeReference == .builtin(.dayClassic), settings.themeSpecificAccentColors[themeReference.index] == nil {
messageColors = (UIColor(rgb: 0xe1ffc7), nil) let wallpaper = theme.chat.defaultWallpaper
} else { extractWallpaperParameters(wallpaper)
messageColors = nil
if !wallpaper.isColorOrGradient {
initialWallpaper = wallpaper
}
let topMessageColor = theme.chat.message.outgoing.bubble.withWallpaper.fill
let bottomMessageColor = theme.chat.message.outgoing.bubble.withWallpaper.gradientFill
if topMessageColor.rgb == bottomMessageColor.rgb {
messageColors = (topMessageColor, nil)
} else {
messageColors = (topMessageColor, bottomMessageColor)
}
} }
} }
} else if case let .edit(theme, wallpaper, _, _, _) = strongSelf.mode { } else if case let .edit(theme, wallpaper, _, _, _) = strongSelf.mode {

View File

@ -0,0 +1,31 @@
import Foundation
import Postbox
import SyncCore
import TelegramUIPreferences
private func patternWallpaper(slug: String, topColor: Int32, bottomColor: Int32?, intensity: Int32?, rotation: Int32?) -> TelegramWallpaper {
return TelegramWallpaper.file(id: 0, accessHash: 0, isCreator: false, isDefault: true, isPattern: true, isDark: false, slug: slug, file: TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], immediateThumbnailData: nil, mimeType: "", size: nil, attributes: []), settings: WallpaperSettings(color: topColor, bottomColor: bottomColor, intensity: intensity ?? 50, rotation: rotation))
}
var dayClassicColorPresets: [PresentationThemeAccentColor] = [
PresentationThemeAccentColor(index: 106, baseColor: .preset, accentColor: 0xf55783, bubbleColors: (0xd6f5ff, nil), wallpaper: patternWallpaper(slug: "p-pXcflrmFIBAAAAvXYQk-mCwZU", topColor: 0xfce3ec, bottomColor: nil, intensity: 40, rotation: nil)),
PresentationThemeAccentColor(index: 102, baseColor: .preset, accentColor: 0xff5fa9, bubbleColors: (0xfff4d7, nil), wallpaper: patternWallpaper(slug: "51nnTjx8mFIBAAAAaFGJsMIvWkk", topColor: 0xf6b594, bottomColor: 0xebf6cd, intensity: 46, rotation: 45)),
PresentationThemeAccentColor(index: 104, baseColor: .preset, accentColor: 0x5a9e29, bubbleColors: (0xdcf8c6, nil), wallpaper: patternWallpaper(slug: "R3j69wKskFIBAAAAoUdXWCKMzCM", topColor: 0xede6dd, bottomColor: nil, intensity: 50, rotation: nil)),
PresentationThemeAccentColor(index: 101, baseColor: .preset, accentColor: 0x7e5fe5, bubbleColors: (0xf5e2ff, nil), wallpaper: patternWallpaper(slug: "nQcFYJe1mFIBAAAAcI95wtIK0fk", topColor: 0xfcccf4, bottomColor: 0xae85f0, intensity: 54, rotation: nil)),
PresentationThemeAccentColor(index: 103, baseColor: .preset, accentColor: 0x199972, bubbleColors: (0xfffec7, nil), wallpaper: patternWallpaper(slug: "fqv01SQemVIBAAAApND8LDRUhRU", topColor: 0xc1e7cb, bottomColor: nil, intensity: 50, rotation: nil)),
PresentationThemeAccentColor(index: 105, baseColor: .preset, accentColor: 0x009eee, bubbleColors: (0x94fff9, 0xccffc7), wallpaper: patternWallpaper(slug: "p-pXcflrmFIBAAAAvXYQk-mCwZU", topColor: 0xffbca6, bottomColor: 0xff63bd, intensity: 57, rotation: 225))
]
var dayColorPresets: [PresentationThemeAccentColor] = [
PresentationThemeAccentColor(index: 101, baseColor: .preset, accentColor: 0x007aff, bubbleColors: (0x007aff, 0xff53f4), wallpaper: nil),
PresentationThemeAccentColor(index: 102, baseColor: .preset, accentColor: 0x00b09b, bubbleColors: (0xaee946, 0x00b09b), wallpaper: nil),
PresentationThemeAccentColor(index: 103, baseColor: .preset, accentColor: 0xd33213, bubbleColors: (0xf9db00, 0xd33213), wallpaper: nil),
PresentationThemeAccentColor(index: 104, baseColor: .preset, accentColor: 0xea8ced, bubbleColors: (0xea8ced, 0x00c2ed), wallpaper: nil)
]
var nightColorPresets: [PresentationThemeAccentColor] = [
PresentationThemeAccentColor(index: 101, baseColor: .preset, accentColor: 0x007aff, bubbleColors: (0x007aff, 0xff53f4), wallpaper: nil),
PresentationThemeAccentColor(index: 102, baseColor: .preset, accentColor: 0x00b09b, bubbleColors: (0xaee946, 0x00b09b), wallpaper: nil),
PresentationThemeAccentColor(index: 103, baseColor: .preset, accentColor: 0xd33213, bubbleColors: (0xf9db00, 0xd33213), wallpaper: nil),
PresentationThemeAccentColor(index: 104, baseColor: .preset, accentColor: 0xea8ced, bubbleColors: (0xea8ced, 0x00c2ed), wallpaper: nil)
]

View File

@ -15,6 +15,7 @@ import PresentationDataUtils
import AccountContext import AccountContext
import SearchBarNode import SearchBarNode
import SearchUI import SearchUI
import WallpaperResources
struct ThemeGridControllerNodeState: Equatable { struct ThemeGridControllerNodeState: Equatable {
let editing: Bool let editing: Bool

View File

@ -21,6 +21,7 @@ public enum ThemePreviewSource {
case settings(PresentationThemeReference, TelegramWallpaper?) case settings(PresentationThemeReference, TelegramWallpaper?)
case theme(TelegramTheme) case theme(TelegramTheme)
case slug(String, TelegramMediaFile) case slug(String, TelegramMediaFile)
case themeSettings(String, TelegramThemeSettings)
case media(AnyMediaReference) case media(AnyMediaReference)
} }
@ -63,55 +64,61 @@ public final class ThemePreviewController: ViewController {
var hasInstallsCount = false var hasInstallsCount = false
let themeName: String let themeName: String
if case let .theme(theme) = source { switch source {
themeName = theme.title case let .theme(theme):
self.theme.set(.single(theme) themeName = theme.title
|> then( self.theme.set(.single(theme)
getTheme(account: context.account, slug: theme.slug) |> then(
getTheme(account: context.account, slug: theme.slug)
|> map(Optional.init)
|> `catch` { _ -> Signal<TelegramTheme?, NoError> in
return .single(nil)
}
|> filter { $0 != nil }
))
hasInstallsCount = true
case let .slug(slug, _), let .themeSettings(slug, _):
self.theme.set(getTheme(account: context.account, slug: slug)
|> map(Optional.init) |> map(Optional.init)
|> `catch` { _ -> Signal<TelegramTheme?, NoError> in |> `catch` { _ -> Signal<TelegramTheme?, NoError> in
return .single(nil) return .single(nil)
} })
|> filter { $0 != nil } themeName = previewTheme.name.string
))
hasInstallsCount = true self.presentationTheme.set(.single(self.previewTheme)
} else if case let .slug(slug, _) = source { |> then(
self.theme.set(getTheme(account: context.account, slug: slug) self.theme.get()
|> map(Optional.init) |> mapToSignal { theme in
|> `catch` { _ -> Signal<TelegramTheme?, NoError> in if let file = theme?.file {
return .single(nil) return telegramThemeData(account: context.account, accountManager: context.sharedContext.accountManager, resource: file.resource)
}) |> mapToSignal { data -> Signal<PresentationTheme, NoError> in
themeName = previewTheme.name.string guard let data = data, let presentationTheme = makePresentationTheme(data: data) else {
return .complete()
self.presentationTheme.set(.single(self.previewTheme) }
|> then( return .single(presentationTheme)
self.theme.get()
|> mapToSignal { theme in
if let file = theme?.file {
return telegramThemeData(account: context.account, accountManager: context.sharedContext.accountManager, resource: file.resource)
|> mapToSignal { data -> Signal<PresentationTheme, NoError> in
guard let data = data, let presentationTheme = makePresentationTheme(data: data) else {
return .complete()
} }
return .single(presentationTheme) } else {
return .complete()
} }
} else {
return .complete()
} }
))
hasInstallsCount = true
case let .settings(themeReference, _):
if case let .cloud(theme) = themeReference {
self.theme.set(getTheme(account: context.account, slug: theme.theme.slug)
|> map(Optional.init)
|> `catch` { _ -> Signal<TelegramTheme?, NoError> in
return .single(nil)
})
themeName = theme.theme.title
hasInstallsCount = true
} else {
self.theme.set(.single(nil))
themeName = previewTheme.name.string
} }
)) default:
hasInstallsCount = true self.theme.set(.single(nil))
} else if case let .settings(themeReference, _) = source, case let .cloud(theme) = themeReference { themeName = previewTheme.name.string
self.theme.set(getTheme(account: context.account, slug: theme.theme.slug)
|> map(Optional.init)
|> `catch` { _ -> Signal<TelegramTheme?, NoError> in
return .single(nil)
})
themeName = previewTheme.name.string
hasInstallsCount = true
} else {
self.theme.set(.single(nil))
themeName = previewTheme.name.string
} }
var isPreview = false var isPreview = false
@ -219,7 +226,7 @@ public final class ThemePreviewController: ViewController {
switch self.source { switch self.source {
case let .settings(reference, _): case let .settings(reference, _):
theme = .single(reference) theme = .single(reference)
case .theme, .slug: case .theme, .slug, .themeSettings:
theme = combineLatest(self.theme.get() |> take(1), wallpaperPromise.get() |> take(1)) theme = combineLatest(self.theme.get() |> take(1), wallpaperPromise.get() |> take(1))
|> mapToSignal { theme, wallpaper -> Signal<PresentationThemeReference?, NoError> in |> mapToSignal { theme, wallpaper -> Signal<PresentationThemeReference?, NoError> in
if let theme = theme { if let theme = theme {
@ -440,7 +447,7 @@ public final class ThemePreviewController: ViewController {
case let .theme(theme): case let .theme(theme):
subject = .url("https://t.me/addtheme/\(theme.slug)") subject = .url("https://t.me/addtheme/\(theme.slug)")
preferredAction = .default preferredAction = .default
case let .slug(slug, _): case let .slug(slug, _), let .themeSettings(slug, _):
subject = .url("https://t.me/addtheme/\(slug)") subject = .url("https://t.me/addtheme/\(slug)")
preferredAction = .default preferredAction = .default
case let .media(media): case let .media(media):

View File

@ -73,7 +73,7 @@ private enum ThemeSettingsColorEntry: Comparable, Identifiable {
} }
} }
func item(action: @escaping (ThemeSettingsColorOption?, Bool) -> Void, contextAction: ((ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void)?, openColorPicker: @escaping (Bool) -> Void) -> ListViewItem { func item(action: @escaping (ThemeSettingsColorOption?, Bool) -> Void, contextAction: ((ThemeSettingsColorOption?, Bool, ASDisplayNode, ContextGesture?) -> Void)?, openColorPicker: @escaping (Bool) -> Void) -> ListViewItem {
switch self { switch self {
case let .color(_, themeReference, accentColor, selected): case let .color(_, themeReference, accentColor, selected):
return ThemeSettingsAccentColorIconItem(themeReference: themeReference, color: accentColor.flatMap { .accentColor($0) }, selected: selected, action: action, contextAction: contextAction) return ThemeSettingsAccentColorIconItem(themeReference: themeReference, color: accentColor.flatMap { .accentColor($0) }, selected: selected, action: action, contextAction: contextAction)
@ -107,7 +107,7 @@ enum ThemeSettingsColorOption: Equatable {
case let .accentColor(color): case let .accentColor(color):
return color.baseColor.color return color.baseColor.color
case .theme: case .theme:
return nil return .clear
} }
} }
@ -159,9 +159,9 @@ private class ThemeSettingsAccentColorIconItem: ListViewItem {
let color: ThemeSettingsColorOption? let color: ThemeSettingsColorOption?
let selected: Bool let selected: Bool
let action: (ThemeSettingsColorOption?, Bool) -> Void let action: (ThemeSettingsColorOption?, Bool) -> Void
let contextAction: ((ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void)? let contextAction: ((ThemeSettingsColorOption?, Bool, ASDisplayNode, ContextGesture?) -> Void)?
public init(themeReference: PresentationThemeReference, color: ThemeSettingsColorOption?, selected: Bool, action: @escaping (ThemeSettingsColorOption?, Bool) -> Void, contextAction: ((ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void)?) { public init(themeReference: PresentationThemeReference, color: ThemeSettingsColorOption?, selected: Bool, action: @escaping (ThemeSettingsColorOption?, Bool) -> Void, contextAction: ((ThemeSettingsColorOption?, Bool, ASDisplayNode, ContextGesture?) -> Void)?) {
self.themeReference = themeReference self.themeReference = themeReference
self.color = color self.color = color
self.selected = selected self.selected = selected
@ -307,7 +307,7 @@ private final class ThemeSettingsAccentColorIconItemNode : ListViewItemNode {
gesture.cancel() gesture.cancel()
return return
} }
item.contextAction?(item.color, strongSelf.containerNode, gesture) item.contextAction?(item.color, item.selected, strongSelf.containerNode, gesture)
} }
} }
@ -386,6 +386,16 @@ private final class ThemeSettingsAccentColorIconItemNode : ListViewItemNode {
topColor = UIColor(rgb: 0xe1ffc7) topColor = UIColor(rgb: 0xe1ffc7)
bottomColor = topColor bottomColor = topColor
} }
} else if case .builtin(.nightAccent) = item.themeReference {
if let accentColor = item.color?.accentColor {
bottomColor = accentColor.withMultiplied(hue: 1.019, saturation: 0.731, brightness: 0.59)
topColor = bottomColor!.withMultiplied(hue: 0.966, saturation: 0.61, brightness: 0.98)
} else {
fillColor = UIColor(rgb: 0x2ea6ff)
strokeColor = fillColor
topColor = UIColor(rgb: 0x466f95)
bottomColor = topColor
}
} }
strongSelf.fillNode.image = generateFillImage(color: fillColor ?? .clear) strongSelf.fillNode.image = generateFillImage(color: fillColor ?? .clear)
@ -593,16 +603,18 @@ class ThemeSettingsAccentColorItem: ListViewItem, ItemListItem {
var sectionId: ItemListSectionId var sectionId: ItemListSectionId
let theme: PresentationTheme let theme: PresentationTheme
let generalThemeReference: PresentationThemeReference
let themeReference: PresentationThemeReference let themeReference: PresentationThemeReference
let colors: [ThemeSettingsAccentColor] let colors: [ThemeSettingsAccentColor]
let currentColor: ThemeSettingsColorOption? let currentColor: ThemeSettingsColorOption?
let updated: (ThemeSettingsColorOption?) -> Void let updated: (ThemeSettingsColorOption?) -> Void
let contextAction: ((PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void)? let contextAction: ((Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void)?
let openColorPicker: (Bool) -> Void let openColorPicker: (Bool) -> Void
let tag: ItemListItemTag? let tag: ItemListItemTag?
init(theme: PresentationTheme, sectionId: ItemListSectionId, themeReference: PresentationThemeReference, colors: [ThemeSettingsAccentColor], currentColor: ThemeSettingsColorOption?, updated: @escaping (ThemeSettingsColorOption?) -> Void, contextAction: ((PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void)?, openColorPicker: @escaping (Bool) -> Void, tag: ItemListItemTag? = nil) { init(theme: PresentationTheme, sectionId: ItemListSectionId, generalThemeReference: PresentationThemeReference, themeReference: PresentationThemeReference, colors: [ThemeSettingsAccentColor], currentColor: ThemeSettingsColorOption?, updated: @escaping (ThemeSettingsColorOption?) -> Void, contextAction: ((Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void)?, openColorPicker: @escaping (Bool) -> Void, tag: ItemListItemTag? = nil) {
self.theme = theme self.theme = theme
self.generalThemeReference = generalThemeReference
self.themeReference = themeReference self.themeReference = themeReference
self.colors = colors self.colors = colors
self.currentColor = currentColor self.currentColor = currentColor
@ -654,7 +666,7 @@ private struct ThemeSettingsAccentColorItemNodeTransition {
let crossfade: Bool let crossfade: Bool
} }
private func preparedTransition(action: @escaping (ThemeSettingsColorOption?, Bool) -> Void, contextAction: ((ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void)?, openColorPicker: @escaping (Bool) -> Void, from fromEntries: [ThemeSettingsColorEntry], to toEntries: [ThemeSettingsColorEntry], crossfade: Bool) -> ThemeSettingsAccentColorItemNodeTransition { private func preparedTransition(action: @escaping (ThemeSettingsColorOption?, Bool) -> Void, contextAction: ((ThemeSettingsColorOption?, Bool, ASDisplayNode, ContextGesture?) -> Void)?, openColorPicker: @escaping (Bool) -> Void, from fromEntries: [ThemeSettingsColorEntry], to toEntries: [ThemeSettingsColorEntry], crossfade: Bool) -> ThemeSettingsAccentColorItemNodeTransition {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
@ -852,7 +864,7 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode {
switch color { switch color {
case .default: case .default:
let selected = item.currentColor == nil let selected = item.currentColor == nil
entries.append(.color(index, item.themeReference, nil, selected)) entries.append(.color(index, item.generalThemeReference, nil, selected))
case let .color(color): case let .color(color):
var selected = false var selected = false
if let currentColor = item.currentColor, case let .accentColor(accentColor) = currentColor { if let currentColor = item.currentColor, case let .accentColor(accentColor) = currentColor {
@ -866,9 +878,9 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode {
} }
switch accentColor { switch accentColor {
case let .accentColor(color): case let .accentColor(color):
entries.append(.color(index, item.themeReference, color, selected)) entries.append(.color(index, item.generalThemeReference, color, selected))
case let .theme(theme): case let .theme(theme):
entries.append(.theme(index, item.themeReference, theme, selected)) entries.append(.theme(index, item.generalThemeReference, theme, selected))
} }
case let .preset(color), let .custom(color): case let .preset(color), let .custom(color):
var selected = false var selected = false
@ -881,7 +893,7 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode {
if let currentColor = item.currentColor { if let currentColor = item.currentColor {
selected = currentColor.index == theme.index selected = currentColor.index == theme.index
} }
entries.append(.theme(index, item.themeReference, theme, selected)) entries.append(.theme(index, item.generalThemeReference, theme, selected))
} }
index += 1 index += 1
@ -908,9 +920,9 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode {
ensureColorVisible(listNode: strongSelf.listNode, accentColor: color, animated: true) ensureColorVisible(listNode: strongSelf.listNode, accentColor: color, animated: true)
} }
} }
let contextAction: ((ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void)? = { [weak item] color, node, gesture in let contextAction: ((ThemeSettingsColorOption?, Bool, ASDisplayNode, ContextGesture?) -> Void)? = { [weak item] color, selected, node, gesture in
if let strongSelf = self, let item = strongSelf.item { if let strongSelf = self, let item = strongSelf.item {
item.contextAction?(item.themeReference, color, node, gesture) item.contextAction?(selected, item.themeReference, color, node, gesture)
} }
} }
let openColorPicker: (Bool) -> Void = { [weak self] create in let openColorPicker: (Bool) -> Void = { [weak self] create in
@ -920,7 +932,7 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode {
} }
let previousEntries = strongSelf.entries ?? [] let previousEntries = strongSelf.entries ?? []
let crossfade = previousEntries.count != entries.count || (currentItem != nil && currentItem?.themeReference.index != item.themeReference.index) let crossfade = previousEntries.count != entries.count || (currentItem != nil && (currentItem?.generalThemeReference.index != item.generalThemeReference.index))
let transition = preparedTransition(action: action, contextAction: contextAction, openColorPicker: openColorPicker, from: previousEntries, to: entries, crossfade: crossfade) let transition = preparedTransition(action: action, contextAction: contextAction, openColorPicker: openColorPicker, from: previousEntries, to: entries, crossfade: crossfade)
strongSelf.enqueueTransition(transition) strongSelf.enqueueTransition(transition)

View File

@ -81,9 +81,9 @@ private final class ThemeSettingsControllerArguments {
let selectAppIcon: (String) -> Void let selectAppIcon: (String) -> Void
let editTheme: (PresentationCloudTheme) -> Void let editTheme: (PresentationCloudTheme) -> Void
let themeContextAction: (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void let themeContextAction: (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void
let colorContextAction: (PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void let colorContextAction: (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void
init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, selectFontSize: @escaping (PresentationFontSize) -> Void, openWallpaperSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference, Bool) -> 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, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void) { init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, selectFontSize: @escaping (PresentationFontSize) -> Void, openWallpaperSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference, Bool) -> 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 (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void) {
self.context = context self.context = context
self.selectTheme = selectTheme self.selectTheme = selectTheme
self.selectFontSize = selectFontSize self.selectFontSize = selectFontSize
@ -133,7 +133,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
case fontSize(PresentationTheme, PresentationFontSize) case fontSize(PresentationTheme, PresentationFontSize)
case chatPreview(PresentationTheme, PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, [ChatPreviewMessageItem]) case chatPreview(PresentationTheme, PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, [ChatPreviewMessageItem])
case wallpaper(PresentationTheme, String) case wallpaper(PresentationTheme, String)
case accentColor(PresentationTheme, PresentationThemeReference, PresentationThemeCustomColors?, [PresentationThemeReference], ThemeSettingsColorOption?) case accentColor(PresentationTheme, PresentationThemeReference, PresentationThemeReference, PresentationThemeCustomColors?, [PresentationThemeReference], ThemeSettingsColorOption?)
case autoNightTheme(PresentationTheme, String, String) case autoNightTheme(PresentationTheme, String, String)
case textSize(PresentationTheme, String, String) case textSize(PresentationTheme, String, String)
case themeItem(PresentationTheme, PresentationStrings, [PresentationThemeReference], PresentationThemeReference, [Int64: PresentationThemeAccentColor], [Int64: TelegramWallpaper], PresentationThemeAccentColor?) case themeItem(PresentationTheme, PresentationStrings, [PresentationThemeReference], PresentationThemeReference, [Int64: PresentationThemeAccentColor], [Int64: TelegramWallpaper], PresentationThemeAccentColor?)
@ -208,8 +208,8 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .accentColor(lhsTheme, lhsCurrentTheme, lhsCustomColors, lhsThemes, lhsColor): case let .accentColor(lhsTheme, lhsGeneralTheme, lhsCurrentTheme, lhsCustomColors, lhsThemes, lhsColor):
if case let .accentColor(rhsTheme, rhsCurrentTheme, rhsCustomColors, rhsThemes, rhsColor) = rhs, lhsTheme === rhsTheme, lhsCurrentTheme == rhsCurrentTheme, lhsCustomColors == rhsCustomColors, lhsThemes == rhsThemes, lhsColor == rhsColor { if case let .accentColor(rhsTheme, rhsGeneralTheme, rhsCurrentTheme, rhsCustomColors, rhsThemes, rhsColor) = rhs, lhsTheme === rhsTheme, lhsCurrentTheme == rhsCurrentTheme, lhsCustomColors == rhsCustomColors, lhsThemes == rhsThemes, lhsColor == rhsColor {
return true return true
} else { } else {
return false return false
@ -308,20 +308,13 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: {
arguments.openWallpaperSettings() arguments.openWallpaperSettings()
}) })
case let .accentColor(theme, currentTheme, customColors, themes, color): case let .accentColor(theme, generalThemeReference, currentTheme, customColors, themes, color):
var colorItems: [ThemeSettingsAccentColor] = [] var colorItems: [ThemeSettingsAccentColor] = []
for theme in themes { for theme in themes {
colorItems.append(.theme(theme)) colorItems.append(.theme(theme))
} }
let generalThemeReference: PresentationThemeReference
if case let .cloud(theme) = currentTheme, let settings = theme.theme.settings {
generalThemeReference = .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme))
} else {
generalThemeReference = currentTheme
}
var defaultColor: PresentationThemeAccentColor? = PresentationThemeAccentColor(baseColor: .blue) var defaultColor: PresentationThemeAccentColor? = PresentationThemeAccentColor(baseColor: .blue)
var colors = PresentationThemeBaseColor.allCases var colors = PresentationThemeBaseColor.allCases
colors = colors.filter { $0 != .custom && $0 != .preset } colors = colors.filter { $0 != .custom && $0 != .preset }
@ -334,28 +327,23 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
return TelegramWallpaper.file(id: 0, accessHash: 0, isCreator: false, isDefault: true, isPattern: true, isDark: false, slug: slug, file: TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], immediateThumbnailData: nil, mimeType: "", size: nil, attributes: []), settings: WallpaperSettings(color: topColor, bottomColor: bottomColor, intensity: intensity ?? 50, rotation: rotation)) return TelegramWallpaper.file(id: 0, accessHash: 0, isCreator: false, isDefault: true, isPattern: true, isDark: false, slug: slug, file: TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], immediateThumbnailData: nil, mimeType: "", size: nil, attributes: []), settings: WallpaperSettings(color: topColor, bottomColor: bottomColor, intensity: intensity ?? 50, rotation: rotation))
} }
colorItems.append(.preset(PresentationThemeAccentColor(index: 104, baseColor: .preset, accentColor: 0x5a9e29, bubbleColors: (0xdcf8c6, nil), wallpaper: patternWallpaper("R3j69wKskFIBAAAAoUdXWCKMzCM", 0xede6dd, nil, 50, nil)))) for preset in dayClassicColorPresets {
colorItems.append(.preset(PresentationThemeAccentColor(index: 106, baseColor: .preset, accentColor: 0xf55783, bubbleColors: (0xd6f5ff, nil), wallpaper: patternWallpaper("p-pXcflrmFIBAAAAvXYQk-mCwZU", 0xfce3ec, nil, 40, nil)))) colorItems.append(.preset(preset))
colorItems.append(.preset(PresentationThemeAccentColor(index: 101, baseColor: .preset, accentColor: 0x7e5fe5, bubbleColors: (0xf5e2ff, nil), wallpaper: patternWallpaper("nQcFYJe1mFIBAAAAcI95wtIK0fk", 0xfcccf4, 0xae85f0, 54, nil)))) }
colorItems.append(.preset(PresentationThemeAccentColor(index: 102, baseColor: .preset, accentColor: 0xff5fa9, bubbleColors: (0xfff4d7, nil), wallpaper: patternWallpaper("51nnTjx8mFIBAAAAaFGJsMIvWkk", 0xf6b594, 0xebf6cd, 46, 45))))
colorItems.append(.preset(PresentationThemeAccentColor(index: 103, baseColor: .preset, accentColor: 0x199972, bubbleColors: (0xfffec7, nil), wallpaper: patternWallpaper("fqv01SQemVIBAAAApND8LDRUhRU", 0xc1e7cb, nil, 50, nil))))
colorItems.append(.preset(PresentationThemeAccentColor(index: 105, baseColor: .preset, accentColor: 0x009eee, bubbleColors: (0x94fff9, 0xccffc7), wallpaper: patternWallpaper("p-pXcflrmFIBAAAAvXYQk-mCwZU", 0xffbca6, 0xff63bd, 57, 225))))
} else if name == .day { } else if name == .day {
colorItems.append(.color(.blue)) colorItems.append(.color(.blue))
colors = colors.filter { $0 != .blue } colors = colors.filter { $0 != .blue }
colorItems.append(.preset(PresentationThemeAccentColor(index: 101, baseColor: .preset, accentColor: 0x007aff, bubbleColors: (0x007aff, 0xff53f4), wallpaper: nil))) for preset in dayColorPresets {
colorItems.append(.preset(PresentationThemeAccentColor(index: 102, baseColor: .preset, accentColor: 0x00b09b, bubbleColors: (0xaee946, 0x00b09b), wallpaper: nil))) colorItems.append(.preset(preset))
colorItems.append(.preset(PresentationThemeAccentColor(index: 103, baseColor: .preset, accentColor: 0xd33213, bubbleColors: (0xf9db00, 0xd33213), wallpaper: nil))) }
colorItems.append(.preset(PresentationThemeAccentColor(index: 104, baseColor: .preset, accentColor: 0xea8ced, bubbleColors: (0xea8ced, 0x00c2ed), wallpaper: nil)))
} else if name == .night { } else if name == .night {
colorItems.append(.color(.blue)) colorItems.append(.color(.blue))
colors = colors.filter { $0 != .blue } colors = colors.filter { $0 != .blue }
colorItems.append(.preset(PresentationThemeAccentColor(index: 101, baseColor: .preset, accentColor: 0x007aff, bubbleColors: (0x007aff, 0xff53f4), wallpaper: nil))) for preset in nightColorPresets {
colorItems.append(.preset(PresentationThemeAccentColor(index: 102, baseColor: .preset, accentColor: 0x00b09b, bubbleColors: (0xaee946, 0x00b09b), wallpaper: nil))) colorItems.append(.preset(preset))
colorItems.append(.preset(PresentationThemeAccentColor(index: 103, baseColor: .preset, accentColor: 0xd33213, bubbleColors: (0xf9db00, 0xd33213), wallpaper: nil))) }
colorItems.append(.preset(PresentationThemeAccentColor(index: 104, baseColor: .preset, accentColor: 0xea8ced, bubbleColors: (0xea8ced, 0x00c2ed), wallpaper: nil)))
} }
if name != .day { if name != .day {
colors = colors.filter { $0 != .black } colors = colors.filter { $0 != .black }
@ -371,14 +359,14 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
colorItems.append(contentsOf: colors.map { .color($0) }) colorItems.append(contentsOf: colors.map { .color($0) })
if let customColors = customColors { if let customColors = customColors {
colorItems.insert(contentsOf: customColors.colors.reversed().map { .custom($0) }, at: 0) // colorItems.insert(contentsOf: customColors.colors.reversed().map { .custom($0) }, at: 0)
} else { } else {
// if let currentColor = currentColor, currentColor.baseColor == .custom { // if let currentColor = currentColor, currentColor.baseColor == .custom {
// colorItems.insert(.custom(currentColor), at: 0) // colorItems.insert(.custom(currentColor), at: 0)
// } // }
} }
return ThemeSettingsAccentColorItem(theme: theme, sectionId: self.section, themeReference: currentTheme, colors: colorItems, currentColor: currentColor, updated: { color in return ThemeSettingsAccentColorItem(theme: theme, sectionId: self.section, generalThemeReference: generalThemeReference, themeReference: currentTheme, colors: colorItems, currentColor: currentColor, updated: { color in
if let color = color { if let color = color {
switch color { switch color {
case let .accentColor(color): case let .accentColor(color):
@ -389,10 +377,10 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
} else { } else {
arguments.selectAccentColor(nil) arguments.selectAccentColor(nil)
} }
}, contextAction: { theme, color, node, gesture in }, contextAction: { isCurrent, theme, color, node, gesture in
arguments.colorContextAction(theme, color, node, gesture) arguments.colorContextAction(isCurrent, theme, color, node, gesture)
}, openColorPicker: { create in }, openColorPicker: { create in
arguments.openAccentColorPicker(generalThemeReference, create) arguments.openAccentColorPicker(currentTheme, create)
}, tag: ThemeSettingsEntryTag.accentColor) }, tag: ThemeSettingsEntryTag.accentColor)
case let .autoNightTheme(theme, text, value): case let .autoNightTheme(theme, text, value):
return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
@ -479,7 +467,7 @@ private func themeSettingsControllerEntries(presentationData: PresentationData,
colorOption = .theme(themeReference) colorOption = .theme(themeReference)
} }
entries.append(.accentColor(presentationData.theme, themeReference, presentationThemeSettings.themeSpecificCustomColors[themeReference.index], colorThemes, colorOption)) entries.append(.accentColor(presentationData.theme, generalThemeReference, themeReference, presentationThemeSettings.themeSpecificCustomColors[generalThemeReference.index], colorThemes, colorOption))
} }
entries.append(.wallpaper(presentationData.theme, strings.Settings_ChatBackground)) entries.append(.wallpaper(presentationData.theme, strings.Settings_ChatBackground))
@ -615,18 +603,27 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
generalThemeReference = currentTheme generalThemeReference = currentTheme
} }
guard let theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: generalThemeReference, accentColor: accentColor?.color) else { currentTheme = generalThemeReference
var updatedTheme = current.theme
var updatedAutomaticThemeSwitchSetting = current.automaticThemeSwitchSetting
if autoNightModeTriggered {
var updatedAutomaticThemeSwitchSetting = current.automaticThemeSwitchSetting
updatedAutomaticThemeSwitchSetting.theme = generalThemeReference
} else {
updatedTheme = generalThemeReference
}
guard let theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: generalThemeReference, accentColor: accentColor?.color, wallpaper: presetWallpaper) else {
return current return current
} }
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
var themeSpecificAccentColors = current.themeSpecificAccentColors var themeSpecificAccentColors = current.themeSpecificAccentColors
themeSpecificAccentColors[generalThemeReference.index] = accentColor themeSpecificAccentColors[generalThemeReference.index] = accentColor?.withUpdatedWallpaper(presetWallpaper)
if case let .builtin(theme) = generalThemeReference { if case let .builtin(theme) = generalThemeReference {
if let wallpaper = presetWallpaper, let color = accentColor { if let wallpaper = current.themeSpecificChatWallpapers[coloredThemeIndex(reference: currentTheme, accentColor: accentColor)], wallpaper.isColorOrGradient || wallpaper.isPattern || wallpaper.isBuiltin {
themeSpecificChatWallpapers[coloredThemeIndex(reference: currentTheme, accentColor: color)] = wallpaper
} else if let wallpaper = current.themeSpecificChatWallpapers[currentTheme.index], wallpaper.isColorOrGradient || wallpaper.isPattern || wallpaper.isBuiltin {
themeSpecificChatWallpapers[currentTheme.index] = nil themeSpecificChatWallpapers[currentTheme.index] = nil
if let accentColor = accentColor { if let accentColor = accentColor {
themeSpecificChatWallpapers[coloredThemeIndex(reference: currentTheme, accentColor: accentColor)] = nil themeSpecificChatWallpapers[coloredThemeIndex(reference: currentTheme, accentColor: accentColor)] = nil
@ -634,7 +631,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
} }
} }
return PresentationThemeSettings(theme: current.theme, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificCustomColors: current.themeSpecificCustomColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) return PresentationThemeSettings(theme: updatedTheme, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificCustomColors: current.themeSpecificCustomColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, automaticThemeSwitchSetting: updatedAutomaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
}).start() }).start()
presentCrossfadeControllerImpl?(true) presentCrossfadeControllerImpl?(true)
@ -828,7 +825,7 @@ 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) let contextController = ContextController(account: context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: themeController, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture)
presentInGlobalOverlayImpl?(contextController, nil) presentInGlobalOverlayImpl?(contextController, nil)
}) })
}, colorContextAction: { reference, accentColor, node, gesture in }, colorContextAction: { isCurrent, reference, accentColor, node, gesture in
let _ = (context.sharedContext.accountManager.transaction { transaction -> (ThemeSettingsColorOption?, TelegramWallpaper?) in let _ = (context.sharedContext.accountManager.transaction { transaction -> (ThemeSettingsColorOption?, TelegramWallpaper?) in
let settings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings) as? PresentationThemeSettings ?? PresentationThemeSettings.defaultSettings let settings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings) as? PresentationThemeSettings ?? PresentationThemeSettings.defaultSettings
var wallpaper: TelegramWallpaper? var wallpaper: TelegramWallpaper?
@ -849,6 +846,13 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
} }
return (accentColor, wallpaper) return (accentColor, wallpaper)
} |> mapToSignal { accentColor, wallpaper -> Signal<(PresentationTheme?, TelegramWallpaper?), NoError> in } |> mapToSignal { accentColor, wallpaper -> Signal<(PresentationTheme?, TelegramWallpaper?), NoError> in
let generalThemeReference: PresentationThemeReference
if let accentColor = accentColor, case let .cloud(theme) = reference, let settings = theme.theme.settings {
generalThemeReference = .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme))
} else {
generalThemeReference = reference
}
let effectiveWallpaper: TelegramWallpaper let effectiveWallpaper: TelegramWallpaper
if let wallpaper = wallpaper { if let wallpaper = wallpaper {
effectiveWallpaper = wallpaper effectiveWallpaper = wallpaper
@ -857,16 +861,30 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
if let accentColor = accentColor, case let .theme(themeReference) = accentColor { if let accentColor = accentColor, case let .theme(themeReference) = accentColor {
theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: themeReference) theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: themeReference)
} else { } else {
theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: reference, accentColor: accentColor?.accentColor, bubbleColors: accentColor?.customBubbleColors) theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: generalThemeReference, accentColor: accentColor?.accentColor, bubbleColors: accentColor?.customBubbleColors)
} }
effectiveWallpaper = theme?.chat.defaultWallpaper ?? .builtin(WallpaperSettings()) effectiveWallpaper = theme?.chat.defaultWallpaper ?? .builtin(WallpaperSettings())
} }
return chatServiceBackgroundColor(wallpaper: effectiveWallpaper, mediaBox: context.sharedContext.accountManager.mediaBox)
let wallpaperSignal: Signal<TelegramWallpaper, NoError>
if case let .file(file) = effectiveWallpaper, file.id == 0 {
wallpaperSignal = cachedWallpaper(account: context.account, slug: file.slug, settings: file.settings)
|> map { cachedWallpaper in
return cachedWallpaper?.wallpaper ?? effectiveWallpaper
}
} else {
wallpaperSignal = .single(effectiveWallpaper)
}
return wallpaperSignal
|> mapToSignal { wallpaper in
return chatServiceBackgroundColor(wallpaper: wallpaper, mediaBox: context.sharedContext.accountManager.mediaBox)
}
|> map { serviceBackgroundColor in |> map { serviceBackgroundColor in
if let accentColor = accentColor, case let .theme(themeReference) = accentColor { if let accentColor = accentColor, case let .theme(themeReference) = accentColor {
return (makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: themeReference, serviceBackgroundColor: serviceBackgroundColor), wallpaper) return (makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: themeReference, serviceBackgroundColor: serviceBackgroundColor), wallpaper)
} else { } else {
return (makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: reference, accentColor: accentColor?.accentColor, bubbleColors: accentColor?.customBubbleColors, serviceBackgroundColor: serviceBackgroundColor), wallpaper) return (makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: generalThemeReference, accentColor: accentColor?.accentColor, bubbleColors: accentColor?.customBubbleColors, serviceBackgroundColor: serviceBackgroundColor), wallpaper)
} }
} }
} }
@ -882,6 +900,99 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
if let accentColor = accentColor { if let accentColor = accentColor {
if case let .accentColor(color) = accentColor, color.baseColor != .custom { if case let .accentColor(color) = accentColor, color.baseColor != .custom {
} else if case let .theme(theme) = accentColor, case let .cloud(cloudTheme) = theme {
if cloudTheme.theme.isCreator {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Appearance_EditTheme, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ApplyTheme"), color: theme.contextMenu.primaryColor) }, action: { c, f in
let controller = editThemeController(context: context, mode: .edit(cloudTheme), navigateToChat: { peerId in
if let navigationController = getNavigationControllerImpl?() {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId)))
}
})
c.dismiss(completion: {
pushControllerImpl?(controller)
})
})))
} else {
items.append(.action(ContextMenuActionItem(text: strings.Theme_Context_ChangeColors, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ApplyTheme"), color: theme.contextMenu.primaryColor) }, action: { c, f in
guard let theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: reference, preview: false) else {
return
}
let resolvedWallpaper: Signal<TelegramWallpaper, NoError>
if case let .file(file) = theme.chat.defaultWallpaper, file.id == 0 {
resolvedWallpaper = cachedWallpaper(account: context.account, slug: file.slug, settings: file.settings)
|> map { cachedWallpaper -> TelegramWallpaper in
return cachedWallpaper?.wallpaper ?? theme.chat.defaultWallpaper
}
} else {
resolvedWallpaper = .single(theme.chat.defaultWallpaper)
}
let _ = (resolvedWallpaper
|> deliverOnMainQueue).start(next: { wallpaper in
let controller = ThemeAccentColorController(context: context, mode: .edit(theme: theme, wallpaper: wallpaper, defaultThemeReference: nil, create: true, completion: { result, settings in
let controller = editThemeController(context: context, mode: .create(result, nil), navigateToChat: { peerId in
if let navigationController = getNavigationControllerImpl?() {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId)))
}
})
updateControllersImpl?({ controllers in
var controllers = controllers
controllers = controllers.filter { controller in
if controller is ThemeAccentColorController {
return false
}
return true
}
controllers.append(controller)
return controllers
})
}))
c.dismiss(completion: {
pushControllerImpl?(controller)
})
})
})))
}
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Appearance_ShareTheme, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor) }, action: { c, f in
c.dismiss(completion: {
let controller = ShareController(context: context, subject: .url("https://t.me/addtheme/\(cloudTheme.theme.slug)"), preferredAction: .default)
presentControllerImpl?(controller, nil)
})
})))
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Appearance_RemoveTheme, 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 actionSheet = ActionSheetController(presentationData: presentationData)
var items: [ActionSheetItem] = []
items.append(ActionSheetButtonItem(title: presentationData.strings.Appearance_RemoveThemeConfirmation, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
let _ = (cloudThemes.get() |> delay(0.5, queue: Queue.mainQueue())
|> take(1)
|> deliverOnMainQueue).start(next: { themes in
if isCurrent, let currentThemeIndex = themes.firstIndex(where: { $0.id == cloudTheme.theme.id }) {
let previousThemeIndex = themes.prefix(upTo: currentThemeIndex).reversed().firstIndex(where: { $0.file != nil })
let newTheme: PresentationThemeReference
if let previousThemeIndex = previousThemeIndex {
newTheme = .cloud(PresentationCloudTheme(theme: themes[themes.index(before: previousThemeIndex.base)], resolvedWallpaper: nil))
} else {
newTheme = .builtin(.nightAccent)
}
selectThemeImpl?(newTheme)
}
let _ = deleteThemeInteractively(account: context.account, accountManager: context.sharedContext.accountManager, theme: cloudTheme.theme).start()
})
}))
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])])
presentControllerImpl?(actionSheet, nil)
})
})))
} else { } else {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Appearance_ShareThemeColor, textColor: .primary, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor) items.append(.action(ContextMenuActionItem(text: presentationData.strings.Appearance_ShareThemeColor, textColor: .primary, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor)
}, action: { c, f in }, action: { c, f in
@ -1118,7 +1229,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
}) })
}).start() }).start()
presentCrossfadeControllerImpl?(cloudTheme == nil) presentCrossfadeControllerImpl?(cloudTheme == nil || cloudTheme?.settings != nil)
} }
moreImpl = { moreImpl = {
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }

View File

@ -602,10 +602,7 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
} }
let title = themeDisplayName(strings: item.strings, reference: theme) let title = themeDisplayName(strings: item.strings, reference: theme)
let accentColor = item.themeSpecificAccentColors[theme.index] let accentColor = item.themeSpecificAccentColors[theme.index]
var wallpaper: TelegramWallpaper? let wallpaper = accentColor?.wallpaper
if let accentColor = accentColor {
wallpaper = item.themeSpecificChatWallpapers[coloredThemeIndex(reference: theme, accentColor: accentColor)]
}
entries.append(ThemeSettingsThemeEntry(index: index, themeReference: theme, title: title, accentColor: accentColor, selected: item.currentTheme.index == theme.index, theme: item.theme, wallpaper: wallpaper)) entries.append(ThemeSettingsThemeEntry(index: index, themeReference: theme, title: title, accentColor: accentColor, selected: item.currentTheme.index == theme.index, theme: item.theme, wallpaper: wallpaper))
index += 1 index += 1
} }

View File

@ -1,5 +1,74 @@
import Postbox import Postbox
private enum TelegramMediaWebpageAttributeTypes: Int32 {
case unsupported
case theme
}
public enum TelegramMediaWebpageAttribute: PostboxCoding, Equatable {
case unsupported
case theme(TelegraMediaWebpageThemeAttribute)
public init(decoder: PostboxDecoder) {
switch decoder.decodeInt32ForKey("r", orElse: 0) {
case TelegramMediaWebpageAttributeTypes.theme.rawValue:
self = .theme(decoder.decodeObjectForKey("a", decoder: { TelegraMediaWebpageThemeAttribute(decoder: $0) }) as! TelegraMediaWebpageThemeAttribute)
default:
self = .unsupported
}
}
public func encode(_ encoder: PostboxEncoder) {
switch self {
case .unsupported:
encoder.encodeInt32(TelegramMediaWebpageAttributeTypes.unsupported.rawValue, forKey: "r")
case let .theme(attribute):
encoder.encodeInt32(TelegramMediaWebpageAttributeTypes.theme.rawValue, forKey: "r")
encoder.encodeObject(attribute, forKey: "a")
}
}
}
public final class TelegraMediaWebpageThemeAttribute: PostboxCoding, Equatable {
public static func == (lhs: TelegraMediaWebpageThemeAttribute, rhs: TelegraMediaWebpageThemeAttribute) -> Bool {
if lhs.settings != rhs.settings {
return false
}
if lhs.files.count != rhs.files.count {
return false
} else {
for i in 0 ..< lhs.files.count {
if !lhs.files[i].isEqual(to: rhs.files[i]) {
return false
}
}
}
return true
}
public let files: [TelegramMediaFile]
public let settings: TelegramThemeSettings?
public init(files: [TelegramMediaFile], settings: TelegramThemeSettings?) {
self.files = files
self.settings = settings
}
public init(decoder: PostboxDecoder) {
self.files = decoder.decodeObjectArrayForKey("files")
self.settings = decoder.decodeObjectForKey("settings", decoder: { TelegramThemeSettings(decoder: $0) }) as? TelegramThemeSettings
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeObjectArray(self.files, forKey: "files")
if let settings = self.settings {
encoder.encodeObject(settings, forKey: "settings")
} else {
encoder.encodeNil(forKey: "settings")
}
}
}
public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable { public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable {
public let url: String public let url: String
public let displayUrl: String public let displayUrl: String
@ -16,10 +85,10 @@ public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable {
public let image: TelegramMediaImage? public let image: TelegramMediaImage?
public let file: TelegramMediaFile? public let file: TelegramMediaFile?
public let files: [TelegramMediaFile]? public let attributes: [TelegramMediaWebpageAttribute]
public let instantPage: InstantPage? public let instantPage: InstantPage?
public init(url: String, displayUrl: String, hash: Int32, type: String?, websiteName: String?, title: String?, text: String?, embedUrl: String?, embedType: String?, embedSize: PixelDimensions?, duration: Int?, author: String?, image: TelegramMediaImage?, file: TelegramMediaFile?, files: [TelegramMediaFile]?, instantPage: InstantPage?) { public init(url: String, displayUrl: String, hash: Int32, type: String?, websiteName: String?, title: String?, text: String?, embedUrl: String?, embedType: String?, embedSize: PixelDimensions?, duration: Int?, author: String?, image: TelegramMediaImage?, file: TelegramMediaFile?, attributes: [TelegramMediaWebpageAttribute], instantPage: InstantPage?) {
self.url = url self.url = url
self.displayUrl = displayUrl self.displayUrl = displayUrl
self.hash = hash self.hash = hash
@ -34,7 +103,7 @@ public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable {
self.author = author self.author = author
self.image = image self.image = image
self.file = file self.file = file
self.files = files self.attributes = attributes
self.instantPage = instantPage self.instantPage = instantPage
} }
@ -72,7 +141,14 @@ public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable {
self.file = nil self.file = nil
} }
self.files = decoder.decodeOptionalObjectArrayWithDecoderForKey("fis") var effectiveAttributes: [TelegramMediaWebpageAttribute] = []
if let attributes = decoder.decodeObjectArrayWithDecoderForKey("attr") as [TelegramMediaWebpageAttribute]? {
effectiveAttributes.append(contentsOf: attributes)
}
if let legacyFiles = decoder.decodeOptionalObjectArrayWithDecoderForKey("fis") as [TelegramMediaFile]? {
effectiveAttributes.append(.theme(TelegraMediaWebpageThemeAttribute(files: legacyFiles, settings: nil)))
}
self.attributes = effectiveAttributes
if let instantPage = decoder.decodeObjectForKey("ip", decoder: { InstantPage(decoder: $0) }) as? InstantPage { if let instantPage = decoder.decodeObjectForKey("ip", decoder: { InstantPage(decoder: $0) }) as? InstantPage {
self.instantPage = instantPage self.instantPage = instantPage
@ -142,11 +218,9 @@ public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable {
} else { } else {
encoder.encodeNil(forKey: "fi") encoder.encodeNil(forKey: "fi")
} }
if let files = self.files {
encoder.encodeObjectArray(files, forKey: "fis") encoder.encodeObjectArray(self.attributes, forKey: "attr")
} else {
encoder.encodeNil(forKey: "fis")
}
if let instantPage = self.instantPage { if let instantPage = self.instantPage {
encoder.encodeObject(instantPage, forKey: "ip") encoder.encodeObject(instantPage, forKey: "ip")
} else { } else {
@ -187,18 +261,14 @@ public func ==(lhs: TelegramMediaWebpageLoadedContent, rhs: TelegramMediaWebpage
return false return false
} }
if let lhsFiles = lhs.files, let rhsFiles = rhs.files { if lhs.attributes.count != rhs.attributes.count {
if lhsFiles.count != rhsFiles.count { return false
return false } else {
} else { for i in 0 ..< lhs.attributes.count {
for i in 0 ..< lhsFiles.count { if lhs.attributes[i] != rhs.attributes[i] {
if !lhsFiles[i].isEqual(to: rhsFiles[i]) { return false
return false
}
} }
} }
} else if (lhs.files == nil) != (rhs.files == nil) {
return false
} }
if lhs.instantPage != rhs.instantPage { if lhs.instantPage != rhs.instantPage {

View File

@ -306,7 +306,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-350980120] = { return Api.WebPage.parse_webPageEmpty($0) } dict[-350980120] = { return Api.WebPage.parse_webPageEmpty($0) }
dict[-981018084] = { return Api.WebPage.parse_webPagePending($0) } dict[-981018084] = { return Api.WebPage.parse_webPagePending($0) }
dict[-2054908813] = { return Api.WebPage.parse_webPageNotModified($0) } dict[-2054908813] = { return Api.WebPage.parse_webPageNotModified($0) }
dict[-94051982] = { return Api.WebPage.parse_webPage($0) } dict[-392411726] = { return Api.WebPage.parse_webPage($0) }
dict[1036876423] = { return Api.InputBotInlineMessage.parse_inputBotInlineMessageText($0) } dict[1036876423] = { return Api.InputBotInlineMessage.parse_inputBotInlineMessageText($0) }
dict[-190472735] = { return Api.InputBotInlineMessage.parse_inputBotInlineMessageMediaGeo($0) } dict[-190472735] = { return Api.InputBotInlineMessage.parse_inputBotInlineMessageMediaGeo($0) }
dict[1262639204] = { return Api.InputBotInlineMessage.parse_inputBotInlineMessageGame($0) } dict[1262639204] = { return Api.InputBotInlineMessage.parse_inputBotInlineMessageGame($0) }
@ -600,6 +600,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1118798639] = { return Api.InputThemeSettings.parse_inputThemeSettings($0) } dict[-1118798639] = { return Api.InputThemeSettings.parse_inputThemeSettings($0) }
dict[1251549527] = { return Api.InputStickeredMedia.parse_inputStickeredMediaPhoto($0) } dict[1251549527] = { return Api.InputStickeredMedia.parse_inputStickeredMediaPhoto($0) }
dict[70813275] = { return Api.InputStickeredMedia.parse_inputStickeredMediaDocument($0) } dict[70813275] = { return Api.InputStickeredMedia.parse_inputStickeredMediaDocument($0) }
dict[1421174295] = { return Api.WebPageAttribute.parse_webPageAttributeTheme($0) }
dict[82699215] = { return Api.messages.FeaturedStickers.parse_featuredStickersNotModified($0) } dict[82699215] = { return Api.messages.FeaturedStickers.parse_featuredStickersNotModified($0) }
dict[-123893531] = { return Api.messages.FeaturedStickers.parse_featuredStickers($0) } dict[-123893531] = { return Api.messages.FeaturedStickers.parse_featuredStickers($0) }
dict[1375940666] = { return Api.auth.LoginTokenInfo.parse_loginTokenInfo($0) } dict[1375940666] = { return Api.auth.LoginTokenInfo.parse_loginTokenInfo($0) }
@ -1258,6 +1259,8 @@ public struct Api {
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.InputStickeredMedia: case let _1 as Api.InputStickeredMedia:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.WebPageAttribute:
_1.serialize(buffer, boxed)
case let _1 as Api.messages.FeaturedStickers: case let _1 as Api.messages.FeaturedStickers:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.auth.LoginTokenInfo: case let _1 as Api.auth.LoginTokenInfo:

View File

@ -9227,7 +9227,7 @@ public extension Api {
case webPageEmpty(id: Int64) case webPageEmpty(id: Int64)
case webPagePending(id: Int64, date: Int32) case webPagePending(id: Int64, date: Int32)
case webPageNotModified case webPageNotModified
case webPage(flags: Int32, id: Int64, url: String, displayUrl: String, hash: Int32, type: String?, siteName: String?, title: String?, description: String?, photo: Api.Photo?, embedUrl: String?, embedType: String?, embedWidth: Int32?, embedHeight: Int32?, duration: Int32?, author: String?, document: Api.Document?, documents: [Api.Document]?, cachedPage: Api.Page?) case webPage(flags: Int32, id: Int64, url: String, displayUrl: String, hash: Int32, type: String?, siteName: String?, title: String?, description: String?, photo: Api.Photo?, embedUrl: String?, embedType: String?, embedWidth: Int32?, embedHeight: Int32?, duration: Int32?, author: String?, document: Api.Document?, cachedPage: Api.Page?, attributes: [Api.WebPageAttribute]?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
@ -9250,9 +9250,9 @@ public extension Api {
} }
break break
case .webPage(let flags, let id, let url, let displayUrl, let hash, let type, let siteName, let title, let description, let photo, let embedUrl, let embedType, let embedWidth, let embedHeight, let duration, let author, let document, let documents, let cachedPage): case .webPage(let flags, let id, let url, let displayUrl, let hash, let type, let siteName, let title, let description, let photo, let embedUrl, let embedType, let embedWidth, let embedHeight, let duration, let author, let document, let cachedPage, let attributes):
if boxed { if boxed {
buffer.appendInt32(-94051982) buffer.appendInt32(-392411726)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(id, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false)
@ -9271,12 +9271,12 @@ public extension Api {
if Int(flags) & Int(1 << 7) != 0 {serializeInt32(duration!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 7) != 0 {serializeInt32(duration!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 8) != 0 {serializeString(author!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 8) != 0 {serializeString(author!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 9) != 0 {document!.serialize(buffer, true)} if Int(flags) & Int(1 << 9) != 0 {document!.serialize(buffer, true)}
if Int(flags) & Int(1 << 11) != 0 {buffer.appendInt32(481674261) if Int(flags) & Int(1 << 10) != 0 {cachedPage!.serialize(buffer, true)}
buffer.appendInt32(Int32(documents!.count)) if Int(flags) & Int(1 << 12) != 0 {buffer.appendInt32(481674261)
for item in documents! { buffer.appendInt32(Int32(attributes!.count))
for item in attributes! {
item.serialize(buffer, true) item.serialize(buffer, true)
}} }}
if Int(flags) & Int(1 << 10) != 0 {cachedPage!.serialize(buffer, true)}
break break
} }
} }
@ -9289,8 +9289,8 @@ public extension Api {
return ("webPagePending", [("id", id), ("date", date)]) return ("webPagePending", [("id", id), ("date", date)])
case .webPageNotModified: case .webPageNotModified:
return ("webPageNotModified", []) return ("webPageNotModified", [])
case .webPage(let flags, let id, let url, let displayUrl, let hash, let type, let siteName, let title, let description, let photo, let embedUrl, let embedType, let embedWidth, let embedHeight, let duration, let author, let document, let documents, let cachedPage): case .webPage(let flags, let id, let url, let displayUrl, let hash, let type, let siteName, let title, let description, let photo, let embedUrl, let embedType, let embedWidth, let embedHeight, let duration, let author, let document, let cachedPage, let attributes):
return ("webPage", [("flags", flags), ("id", id), ("url", url), ("displayUrl", displayUrl), ("hash", hash), ("type", type), ("siteName", siteName), ("title", title), ("description", description), ("photo", photo), ("embedUrl", embedUrl), ("embedType", embedType), ("embedWidth", embedWidth), ("embedHeight", embedHeight), ("duration", duration), ("author", author), ("document", document), ("documents", documents), ("cachedPage", cachedPage)]) return ("webPage", [("flags", flags), ("id", id), ("url", url), ("displayUrl", displayUrl), ("hash", hash), ("type", type), ("siteName", siteName), ("title", title), ("description", description), ("photo", photo), ("embedUrl", embedUrl), ("embedType", embedType), ("embedWidth", embedWidth), ("embedHeight", embedHeight), ("duration", duration), ("author", author), ("document", document), ("cachedPage", cachedPage), ("attributes", attributes)])
} }
} }
@ -9361,13 +9361,13 @@ public extension Api {
if Int(_1!) & Int(1 << 9) != 0 {if let signature = reader.readInt32() { if Int(_1!) & Int(1 << 9) != 0 {if let signature = reader.readInt32() {
_17 = Api.parse(reader, signature: signature) as? Api.Document _17 = Api.parse(reader, signature: signature) as? Api.Document
} } } }
var _18: [Api.Document]? var _18: Api.Page?
if Int(_1!) & Int(1 << 11) != 0 {if let _ = reader.readInt32() {
_18 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self)
} }
var _19: Api.Page?
if Int(_1!) & Int(1 << 10) != 0 {if let signature = reader.readInt32() { if Int(_1!) & Int(1 << 10) != 0 {if let signature = reader.readInt32() {
_19 = Api.parse(reader, signature: signature) as? Api.Page _18 = Api.parse(reader, signature: signature) as? Api.Page
} }
var _19: [Api.WebPageAttribute]?
if Int(_1!) & Int(1 << 12) != 0 {if let _ = reader.readInt32() {
_19 = Api.parseVector(reader, elementSignature: 0, elementType: Api.WebPageAttribute.self)
} } } }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
@ -9386,10 +9386,10 @@ public extension Api {
let _c15 = (Int(_1!) & Int(1 << 7) == 0) || _15 != nil let _c15 = (Int(_1!) & Int(1 << 7) == 0) || _15 != nil
let _c16 = (Int(_1!) & Int(1 << 8) == 0) || _16 != nil let _c16 = (Int(_1!) & Int(1 << 8) == 0) || _16 != nil
let _c17 = (Int(_1!) & Int(1 << 9) == 0) || _17 != nil let _c17 = (Int(_1!) & Int(1 << 9) == 0) || _17 != nil
let _c18 = (Int(_1!) & Int(1 << 11) == 0) || _18 != nil let _c18 = (Int(_1!) & Int(1 << 10) == 0) || _18 != nil
let _c19 = (Int(_1!) & Int(1 << 10) == 0) || _19 != nil let _c19 = (Int(_1!) & Int(1 << 12) == 0) || _19 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 { if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 {
return Api.WebPage.webPage(flags: _1!, id: _2!, url: _3!, displayUrl: _4!, hash: _5!, type: _6, siteName: _7, title: _8, description: _9, photo: _10, embedUrl: _11, embedType: _12, embedWidth: _13, embedHeight: _14, duration: _15, author: _16, document: _17, documents: _18, cachedPage: _19) return Api.WebPage.webPage(flags: _1!, id: _2!, url: _3!, displayUrl: _4!, hash: _5!, type: _6, siteName: _7, title: _8, description: _9, photo: _10, embedUrl: _11, embedType: _12, embedWidth: _13, embedHeight: _14, duration: _15, author: _16, document: _17, cachedPage: _18, attributes: _19)
} }
else { else {
return nil return nil
@ -17150,6 +17150,56 @@ public extension Api {
} }
} }
}
public enum WebPageAttribute: TypeConstructorDescription {
case webPageAttributeTheme(flags: Int32, documents: [Api.Document]?, settings: Api.ThemeSettings?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .webPageAttributeTheme(let flags, let documents, let settings):
if boxed {
buffer.appendInt32(1421174295)
}
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(documents!.count))
for item in documents! {
item.serialize(buffer, true)
}}
if Int(flags) & Int(1 << 1) != 0 {settings!.serialize(buffer, true)}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .webPageAttributeTheme(let flags, let documents, let settings):
return ("webPageAttributeTheme", [("flags", flags), ("documents", documents), ("settings", settings)])
}
}
public static func parse_webPageAttributeTheme(_ reader: BufferReader) -> WebPageAttribute? {
var _1: Int32?
_1 = reader.readInt32()
var _2: [Api.Document]?
if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self)
} }
var _3: Api.ThemeSettings?
if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.ThemeSettings
} }
let _c1 = _1 != nil
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
if _c1 && _c2 && _c3 {
return Api.WebPageAttribute.webPageAttributeTheme(flags: _1!, documents: _2, settings: _3)
}
else {
return nil
}
}
} }
public enum PhoneCallDiscardReason: TypeConstructorDescription { public enum PhoneCallDiscardReason: TypeConstructorDescription {
case phoneCallDiscardReasonMissed case phoneCallDiscardReasonMissed

View File

@ -5925,13 +5925,13 @@ public extension Api {
}) })
} }
public static func createTheme(flags: Int32, slug: String, title: String, document: Api.InputDocument, settings: Api.InputThemeSettings?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Theme>) { public static func createTheme(flags: Int32, slug: String, title: String, document: Api.InputDocument?, settings: Api.InputThemeSettings?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Theme>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(-1683113716) buffer.appendInt32(-2077048289)
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(slug, buffer: buffer, boxed: false) serializeString(slug, buffer: buffer, boxed: false)
serializeString(title, buffer: buffer, boxed: false) serializeString(title, buffer: buffer, boxed: false)
document.serialize(buffer, true) if Int(flags) & Int(1 << 2) != 0 {document!.serialize(buffer, true)}
if Int(flags) & Int(1 << 3) != 0 {settings!.serialize(buffer, true)} if Int(flags) & Int(1 << 3) != 0 {settings!.serialize(buffer, true)}
return (FunctionDescription(name: "account.createTheme", parameters: [("flags", flags), ("slug", slug), ("title", title), ("document", document), ("settings", settings)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Theme? in return (FunctionDescription(name: "account.createTheme", parameters: [("flags", flags), ("slug", slug), ("title", title), ("document", document), ("settings", settings)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Theme? in
let reader = BufferReader(buffer) let reader = BufferReader(buffer)

View File

@ -150,6 +150,7 @@ private var declaredEncodables: Void = {
declareEncodable(SendScheduledMessageImmediatelyAction.self, f: { SendScheduledMessageImmediatelyAction(decoder: $0) }) declareEncodable(SendScheduledMessageImmediatelyAction.self, f: { SendScheduledMessageImmediatelyAction(decoder: $0) })
declareEncodable(WalletCollection.self, f: { WalletCollection(decoder: $0) }) declareEncodable(WalletCollection.self, f: { WalletCollection(decoder: $0) })
declareEncodable(EmbeddedMediaStickersMessageAttribute.self, f: { EmbeddedMediaStickersMessageAttribute(decoder: $0) }) declareEncodable(EmbeddedMediaStickersMessageAttribute.self, f: { EmbeddedMediaStickersMessageAttribute(decoder: $0) })
declareEncodable(TelegramMediaWebpageAttribute.self, f: { TelegramMediaWebpageAttribute(decoder: $0) })
return return
}() }()

View File

@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
public class Serialization: NSObject, MTSerialization { public class Serialization: NSObject, MTSerialization {
public func currentLayer() -> UInt { public func currentLayer() -> UInt {
return 107 return 108
} }
public func parseMessage(_ data: Data!) -> Any! { public func parseMessage(_ data: Data!) -> Any! {

View File

@ -4,13 +4,24 @@ import TelegramApi
import SyncCore import SyncCore
func telegramMediaWebpageAttributeFromApiWebpageAttribute(_ attribute: Api.WebPageAttribute) -> TelegramMediaWebpageAttribute? {
switch attribute {
case let .webPageAttributeTheme(flags, documents, settings):
var files: [TelegramMediaFile] = []
if let documents = documents {
files = documents.compactMap { telegramMediaFileFromApiDocument($0) }
}
return .theme(TelegraMediaWebpageThemeAttribute(files: files, settings: settings.flatMap { TelegramThemeSettings(apiThemeSettings: $0) }))
}
}
func telegramMediaWebpageFromApiWebpage(_ webpage: Api.WebPage, url: String?) -> TelegramMediaWebpage? { func telegramMediaWebpageFromApiWebpage(_ webpage: Api.WebPage, url: String?) -> TelegramMediaWebpage? {
switch webpage { switch webpage {
case .webPageNotModified: case .webPageNotModified:
return nil return nil
case let .webPagePending(id, date): case let .webPagePending(id, date):
return TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), content: .Pending(date, url)) return TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), content: .Pending(date, url))
case let .webPage(_, id, url, displayUrl, hash, type, siteName, title, description, photo, embedUrl, embedType, embedWidth, embedHeight, duration, author, document, documents, cachedPage): case let .webPage(_, id, url, displayUrl, hash, type, siteName, title, description, photo, embedUrl, embedType, embedWidth, embedHeight, duration, author, document, cachedPage, attributes):
var embedSize: PixelDimensions? var embedSize: PixelDimensions?
if let embedWidth = embedWidth, let embedHeight = embedHeight { if let embedWidth = embedWidth, let embedHeight = embedHeight {
embedSize = PixelDimensions(width: embedWidth, height: embedHeight) embedSize = PixelDimensions(width: embedWidth, height: embedHeight)
@ -27,15 +38,15 @@ func telegramMediaWebpageFromApiWebpage(_ webpage: Api.WebPage, url: String?) ->
if let document = document { if let document = document {
file = telegramMediaFileFromApiDocument(document) file = telegramMediaFileFromApiDocument(document)
} }
var files: [TelegramMediaFile]? var webpageAttributes: [TelegramMediaWebpageAttribute] = []
if let documents = documents { if let attributes = attributes {
files = documents.compactMap(telegramMediaFileFromApiDocument) webpageAttributes = attributes.compactMap(telegramMediaWebpageAttributeFromApiWebpageAttribute)
} }
var instantPage: InstantPage? var instantPage: InstantPage?
if let cachedPage = cachedPage { if let cachedPage = cachedPage {
instantPage = InstantPage(apiPage: cachedPage) instantPage = InstantPage(apiPage: cachedPage)
} }
return TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), content: .Loaded(TelegramMediaWebpageLoadedContent(url: url, displayUrl: displayUrl, hash: hash, type: type, websiteName: siteName, title: title, text: description, embedUrl: embedUrl, embedType: embedType, embedSize: embedSize, duration: webpageDuration, author: author, image: image, file: file, files: files, instantPage: instantPage))) return TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), content: .Loaded(TelegramMediaWebpageLoadedContent(url: url, displayUrl: displayUrl, hash: hash, type: type, websiteName: siteName, title: title, text: description, embedUrl: embedUrl, embedType: embedType, embedSize: embedSize, duration: webpageDuration, author: author, image: image, file: file, attributes: webpageAttributes, instantPage: instantPage)))
case .webPageEmpty: case .webPageEmpty:
return nil return nil
} }

View File

@ -279,56 +279,91 @@ public enum CreateThemeResult {
case progress(Float) case progress(Float)
} }
public func createTheme(account: Account, title: String, resource: MediaResource, thumbnailData: Data? = nil, settings: TelegramThemeSettings?) -> Signal<CreateThemeResult, CreateThemeError> { public func createTheme(account: Account, title: String, resource: MediaResource? = nil, thumbnailData: Data? = nil, settings: TelegramThemeSettings?) -> Signal<CreateThemeResult, CreateThemeError> {
var flags: Int32 = 0 var flags: Int32 = 0
var inputSettings: Api.InputThemeSettings? var inputSettings: Api.InputThemeSettings?
if let _ = resource {
flags |= 1 << 2
}
if let settings = settings { if let settings = settings {
flags |= 1 << 3 flags |= 1 << 3
inputSettings = settings.apiInputThemeSettings inputSettings = settings.apiInputThemeSettings
} }
return uploadTheme(account: account, resource: resource, thumbnailData: thumbnailData) if let resource = resource {
|> mapError { _ in return CreateThemeError.generic } return uploadTheme(account: account, resource: resource, thumbnailData: thumbnailData)
|> mapToSignal { result -> Signal<CreateThemeResult, CreateThemeError> in |> mapError { _ in return CreateThemeError.generic }
switch result { |> mapToSignal { result -> Signal<CreateThemeResult, CreateThemeError> in
case let .complete(file): switch result {
if let resource = file.resource as? CloudDocumentMediaResource { case let .complete(file):
return account.network.request(Api.functions.account.createTheme(flags: flags, slug: "", title: title, document: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference)), settings: inputSettings)) if let resource = file.resource as? CloudDocumentMediaResource {
|> mapError { error in return account.network.request(Api.functions.account.createTheme(flags: flags, slug: "", title: title, document: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference)), settings: inputSettings))
if error.errorDescription == "THEME_SLUG_INVALID" { |> mapError { error in
return .slugInvalid if error.errorDescription == "THEME_SLUG_INVALID" {
} else if error.errorDescription == "THEME_SLUG_OCCUPIED" { return .slugInvalid
return .slugOccupied } else if error.errorDescription == "THEME_SLUG_OCCUPIED" {
} return .slugOccupied
return .generic }
} return .generic
|> mapToSignal { apiTheme -> Signal<CreateThemeResult, CreateThemeError> in }
if let theme = TelegramTheme(apiTheme: apiTheme) { |> mapToSignal { apiTheme -> Signal<CreateThemeResult, CreateThemeError> in
return account.postbox.transaction { transaction -> CreateThemeResult in if let theme = TelegramTheme(apiTheme: apiTheme) {
let entries = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudThemes) return account.postbox.transaction { transaction -> CreateThemeResult in
var items = entries.map { $0.contents as! TelegramTheme } let entries = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudThemes)
items.insert(theme, at: 0) var items = entries.map { $0.contents as! TelegramTheme }
var updatedEntries: [OrderedItemListEntry] = [] items.insert(theme, at: 0)
for item in items { var updatedEntries: [OrderedItemListEntry] = []
var intValue = Int32(updatedEntries.count) for item in items {
let id = MemoryBuffer(data: Data(bytes: &intValue, count: 4)) var intValue = Int32(updatedEntries.count)
updatedEntries.append(OrderedItemListEntry(id: id, contents: item)) let id = MemoryBuffer(data: Data(bytes: &intValue, count: 4))
} updatedEntries.append(OrderedItemListEntry(id: id, contents: item))
transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.CloudThemes, items: updatedEntries) }
return .result(theme) transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.CloudThemes, items: updatedEntries)
return .result(theme)
}
|> castError(CreateThemeError.self)
} else {
return .fail(.generic)
} }
|> castError(CreateThemeError.self)
} else {
return .fail(.generic)
} }
} }
else {
return .fail(.generic)
}
case let .progress(progress):
return .single(.progress(progress))
}
}
} else {
return account.network.request(Api.functions.account.createTheme(flags: flags, slug: "", title: title, document: .inputDocumentEmpty, settings: inputSettings))
|> mapError { error in
if error.errorDescription == "THEME_SLUG_INVALID" {
return .slugInvalid
} else if error.errorDescription == "THEME_SLUG_OCCUPIED" {
return .slugOccupied
} }
else { return .generic
}
|> mapToSignal { apiTheme -> Signal<CreateThemeResult, CreateThemeError> in
if let theme = TelegramTheme(apiTheme: apiTheme) {
return account.postbox.transaction { transaction -> CreateThemeResult in
let entries = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudThemes)
var items = entries.map { $0.contents as! TelegramTheme }
items.insert(theme, at: 0)
var updatedEntries: [OrderedItemListEntry] = []
for item in items {
var intValue = Int32(updatedEntries.count)
let id = MemoryBuffer(data: Data(bytes: &intValue, count: 4))
updatedEntries.append(OrderedItemListEntry(id: id, contents: item))
}
transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.CloudThemes, items: updatedEntries)
return .result(theme)
}
|> castError(CreateThemeError.self)
} else {
return .fail(.generic) return .fail(.generic)
} }
case let .progress(progress):
return .single(.progress(progress))
} }
} }
} }
@ -350,6 +385,7 @@ public func updateTheme(account: Account, accountManager: AccountManager, theme:
var inputSettings: Api.InputThemeSettings? var inputSettings: Api.InputThemeSettings?
if let settings = settings { if let settings = settings {
flags |= 1 << 3 flags |= 1 << 3
inputSettings = settings.apiInputThemeSettings
} }
let uploadSignal: Signal<UploadThemeResult?, UploadThemeError> let uploadSignal: Signal<UploadThemeResult?, UploadThemeError>
if let resource = resource { if let resource = resource {
@ -379,7 +415,7 @@ public func updateTheme(account: Account, accountManager: AccountManager, theme:
inputDocument = nil inputDocument = nil
} }
return account.network.request(Api.functions.account.updateTheme(flags: flags, format: telegramThemeFormat, theme: .inputTheme(id: theme.id, accessHash: theme.accessHash), slug: slug, title: title, document: inputDocument, settings: nil)) return account.network.request(Api.functions.account.updateTheme(flags: flags, format: telegramThemeFormat, theme: .inputTheme(id: theme.id, accessHash: theme.accessHash), slug: slug, title: title, document: inputDocument, settings: inputSettings))
|> mapError { error in |> mapError { error in
if error.errorDescription == "THEME_SLUG_INVALID" { if error.errorDescription == "THEME_SLUG_INVALID" {
return .slugInvalid return .slugInvalid

View File

@ -129,7 +129,7 @@ public func customizeDefaultDarkPresentationTheme(theme: PresentationTheme, edit
outgoingSecondaryTextColor = UIColor(rgb: 0xffffff, alpha: 0.5) outgoingSecondaryTextColor = UIColor(rgb: 0xffffff, alpha: 0.5)
outgoingLinkTextColor = UIColor(rgb: 0xffffff) outgoingLinkTextColor = UIColor(rgb: 0xffffff)
outgoingScamColor = UIColor(rgb: 0xffffff) outgoingScamColor = UIColor(rgb: 0xffffff)
outgoingCheckColor = UIColor(rgb: 0xffffff, alpha: 0.5) outgoingCheckColor = UIColor(rgb: 0xffffff)
} }
} }
@ -392,7 +392,7 @@ public func makeDefaultDarkPresentationTheme(preview: Bool) -> PresentationTheme
freeform: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x1f1f1f), highlightedFill: UIColor(rgb: 0x2a2a2a), stroke: UIColor(rgb: 0x1f1f1f)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x1f1f1f), highlightedFill: UIColor(rgb: 0x2a2a2a), stroke: UIColor(rgb: 0x1f1f1f))), freeform: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x1f1f1f), highlightedFill: UIColor(rgb: 0x2a2a2a), stroke: UIColor(rgb: 0x1f1f1f)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x1f1f1f), highlightedFill: UIColor(rgb: 0x2a2a2a), stroke: UIColor(rgb: 0x1f1f1f))),
infoPrimaryTextColor: UIColor(rgb: 0xffffff), infoPrimaryTextColor: UIColor(rgb: 0xffffff),
infoLinkTextColor: UIColor(rgb: 0xffffff), infoLinkTextColor: UIColor(rgb: 0xffffff),
outgoingCheckColor: UIColor(rgb: 0xffffff, alpha: 0.5), outgoingCheckColor: UIColor(rgb: 0xffffff),
mediaDateAndStatusFillColor: UIColor(white: 0.0, alpha: 0.5), mediaDateAndStatusFillColor: UIColor(white: 0.0, alpha: 0.5),
mediaDateAndStatusTextColor: UIColor(rgb: 0xffffff), mediaDateAndStatusTextColor: UIColor(rgb: 0xffffff),
shareButtonFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)), shareButtonFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)),

View File

@ -48,7 +48,13 @@ public func makePresentationTheme(mediaBox: MediaBox, themeReference: Presentati
return nil return nil
} }
case let .cloud(info): case let .cloud(info):
if let file = info.theme.file, let path = mediaBox.completedResourcePath(file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead), let loadedTheme = makePresentationTheme(data: data, themeReference: themeReference, resolvedWallpaper: info.resolvedWallpaper) { if let settings = info.theme.settings {
if let loadedTheme = makePresentationTheme(mediaBox: mediaBox, themeReference: .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)), accentColor: accentColor ?? UIColor(rgb: UInt32(bitPattern: settings.accentColor)), backgroundColors: nil, bubbleColors: bubbleColors ?? settings.messageColors.flatMap { (UIColor(rgb: UInt32(bitPattern: $0.top)), UIColor(rgb: UInt32(bitPattern: $0.bottom))) }, wallpaper: wallpaper ?? settings.wallpaper, serviceBackgroundColor: serviceBackgroundColor, preview: preview) {
theme = loadedTheme
} else {
return nil
}
} else if let file = info.theme.file, let path = mediaBox.completedResourcePath(file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead), let loadedTheme = makePresentationTheme(data: data, themeReference: themeReference, resolvedWallpaper: info.resolvedWallpaper) {
theme = customizePresentationTheme(loadedTheme, editing: false, accentColor: accentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, wallpaper: wallpaper) theme = customizePresentationTheme(loadedTheme, editing: false, accentColor: accentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, wallpaper: wallpaper)
} else { } else {
return nil return nil

View File

@ -516,14 +516,14 @@ public func updatedPresentationData(accountManager: AccountManager, applicationI
let contactSettings: ContactSynchronizationSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.contactSynchronizationSettings] as? ContactSynchronizationSettings ?? ContactSynchronizationSettings.defaultSettings let contactSettings: ContactSynchronizationSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.contactSynchronizationSettings] as? ContactSynchronizationSettings ?? ContactSynchronizationSettings.defaultSettings
let effectiveColors = themeSettings.themeSpecificAccentColors[themeSettings.theme.index] let currentColors = themeSettings.themeSpecificAccentColors[themeSettings.theme.index]
let themeSpecificWallpaper = (themeSettings.themeSpecificChatWallpapers[coloredThemeIndex(reference: themeSettings.theme, accentColor: effectiveColors)] ?? themeSettings.themeSpecificChatWallpapers[themeSettings.theme.index]) let themeSpecificWallpaper = (themeSettings.themeSpecificChatWallpapers[coloredThemeIndex(reference: themeSettings.theme, accentColor: currentColors)] ?? themeSettings.themeSpecificChatWallpapers[themeSettings.theme.index])
let currentWallpaper: TelegramWallpaper let currentWallpaper: TelegramWallpaper
if let themeSpecificWallpaper = themeSpecificWallpaper { if let themeSpecificWallpaper = themeSpecificWallpaper {
currentWallpaper = themeSpecificWallpaper currentWallpaper = themeSpecificWallpaper
} else { } else {
let theme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: themeSettings.theme, accentColor: effectiveColors?.color, bubbleColors: effectiveColors?.customBubbleColors) ?? defaultPresentationTheme let theme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: themeSettings.theme, accentColor: currentColors?.color, bubbleColors: currentColors?.customBubbleColors, wallpaper: currentColors?.wallpaper) ?? defaultPresentationTheme
currentWallpaper = theme.chat.defaultWallpaper currentWallpaper = theme.chat.defaultWallpaper
} }
@ -537,12 +537,13 @@ public func updatedPresentationData(accountManager: AccountManager, applicationI
|> distinctUntilChanged |> distinctUntilChanged
|> map { autoNightModeTriggered in |> map { autoNightModeTriggered in
var effectiveTheme: PresentationThemeReference var effectiveTheme: PresentationThemeReference
var effectiveChatWallpaper: TelegramWallpaper = currentWallpaper var effectiveChatWallpaper = currentWallpaper
var effectiveColors = currentColors
var switchedToNightModeWallpaper = false var switchedToNightModeWallpaper = false
if autoNightModeTriggered { if autoNightModeTriggered {
let automaticTheme = themeSettings.automaticThemeSwitchSetting.theme let automaticTheme = themeSettings.automaticThemeSwitchSetting.theme
let effectiveColors = themeSettings.themeSpecificAccentColors[automaticTheme.index] effectiveColors = themeSettings.themeSpecificAccentColors[automaticTheme.index]
let themeSpecificWallpaper = (themeSettings.themeSpecificChatWallpapers[coloredThemeIndex(reference: automaticTheme, accentColor: effectiveColors)] ?? themeSettings.themeSpecificChatWallpapers[automaticTheme.index]) let themeSpecificWallpaper = (themeSettings.themeSpecificChatWallpapers[coloredThemeIndex(reference: automaticTheme, accentColor: effectiveColors)] ?? themeSettings.themeSpecificChatWallpapers[automaticTheme.index])
if let themeSpecificWallpaper = themeSpecificWallpaper { if let themeSpecificWallpaper = themeSpecificWallpaper {
@ -554,8 +555,7 @@ public func updatedPresentationData(accountManager: AccountManager, applicationI
effectiveTheme = themeSettings.theme effectiveTheme = themeSettings.theme
} }
let effectiveColors = themeSettings.themeSpecificAccentColors[effectiveTheme.index] let themeValue = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: effectiveTheme, accentColor: effectiveColors?.color, bubbleColors: effectiveColors?.customBubbleColors, wallpaper: effectiveColors?.wallpaper, serviceBackgroundColor: serviceBackgroundColor) ?? defaultPresentationTheme
let themeValue = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: effectiveTheme, accentColor: effectiveColors?.color, bubbleColors: effectiveColors?.customBubbleColors, serviceBackgroundColor: serviceBackgroundColor) ?? defaultPresentationTheme
if autoNightModeTriggered && !switchedToNightModeWallpaper { if autoNightModeTriggered && !switchedToNightModeWallpaper {
switch effectiveChatWallpaper { switch effectiveChatWallpaper {

View File

@ -322,6 +322,8 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
} else { } else {
unboundSize = CGSize(width: 54.0, height: 54.0) unboundSize = CGSize(width: 54.0, height: 54.0)
} }
case .themeSettings:
unboundSize = CGSize(width: 160.0, height: 240.0).fitted(CGSize(width: 240.0, height: 240.0))
case .color, .gradient: case .color, .gradient:
unboundSize = CGSize(width: 128.0, height: 128.0) unboundSize = CGSize(width: 128.0, height: 128.0)
} }
@ -439,13 +441,15 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
} else { } else {
emptyColor = message.effectivelyIncoming(context.account.peerId) ? theme.chat.message.incoming.mediaPlaceholderColor : theme.chat.message.outgoing.mediaPlaceholderColor 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, patternBottomColor, rotation, _, _) = wallpaper.content { if let wallpaper = media as? WallpaperPreviewMedia {
var colors: [UIColor] = [] if case let .file(_, patternColor, patternBottomColor, rotation, _, _) = wallpaper.content {
colors.append(patternColor ?? UIColor(rgb: 0xd6e2ee, alpha: 0.5)) var colors: [UIColor] = []
if let patternBottomColor = patternBottomColor { colors.append(patternColor ?? UIColor(rgb: 0xd6e2ee, alpha: 0.5))
colors.append(patternBottomColor) if let patternBottomColor = patternBottomColor {
colors.append(patternBottomColor)
}
patternArguments = PatternWallpaperArguments(colors: colors, rotation: rotation)
} }
patternArguments = PatternWallpaperArguments(colors: colors, rotation: rotation)
} }
if mediaUpdated || isSendingUpdated || automaticPlaybackUpdated { if mediaUpdated || isSendingUpdated || automaticPlaybackUpdated {
@ -580,7 +584,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
switch wallpaper.content { switch wallpaper.content {
case let .file(file, _, _, _, isTheme, _): case let .file(file, _, _, _, isTheme, _):
if isTheme { if isTheme {
return themeImage(account: context.account, accountManager: context.sharedContext.accountManager, fileReference: FileMediaReference.message(message: MessageReference(message), media: file)) return themeImage(account: context.account, accountManager: context.sharedContext.accountManager, source: .file(FileMediaReference.message(message: MessageReference(message), media: file)))
} else { } else {
let representations: [ImageRepresentationWithReference] = file.previewRepresentations.map({ ImageRepresentationWithReference(representation: $0, reference: AnyMediaReference.message(message: MessageReference(message), media: file).resourceReference($0.resource)) }) let representations: [ImageRepresentationWithReference] = file.previewRepresentations.map({ ImageRepresentationWithReference(representation: $0, reference: AnyMediaReference.message(message: MessageReference(message), media: file).resourceReference($0.resource)) })
if file.mimeType == "image/png" { if file.mimeType == "image/png" {
@ -589,6 +593,8 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
return wallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, fileReference: FileMediaReference.message(message: MessageReference(message), media: file), representations: representations, alwaysShowThumbnailFirst: false, thumbnail: true, autoFetchFullSize: true) return wallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, fileReference: FileMediaReference.message(message: MessageReference(message), media: file), representations: representations, alwaysShowThumbnailFirst: false, thumbnail: true, autoFetchFullSize: true)
} }
} }
case let .themeSettings(settings):
return themeImage(account: context.account, accountManager: context.sharedContext.accountManager, source: .settings(settings))
case let .color(color): case let .color(color):
return solidColorImage(color) return solidColorImage(color)
case let .gradient(topColor, bottomColor, rotation): case let .gradient(topColor, bottomColor, rotation):
@ -604,6 +610,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
}, cancel: { }, cancel: {
messageMediaFileCancelInteractiveFetch(context: context, messageId: message.id, file: file) messageMediaFileCancelInteractiveFetch(context: context, messageId: message.id, file: file)
}) })
} else if case .themeSettings = wallpaper.content {
} else { } else {
boundingSize = CGSize(width: boundingSize.width, height: boundingSize.width) boundingSize = CGSize(width: boundingSize.width, height: boundingSize.width)
} }
@ -645,7 +652,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|> map { resourceStatus -> (MediaResourceStatus, MediaResourceStatus?) in |> map { resourceStatus -> (MediaResourceStatus, MediaResourceStatus?) in
return (resourceStatus, nil) return (resourceStatus, nil)
} }
case .color, .gradient: case .themeSettings, .color, .gradient:
updatedStatusSignal = .single((.Local, nil)) updatedStatusSignal = .single((.Local, nil))
} }
} }

View File

@ -310,21 +310,31 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
} }
} else if type == "telegram_theme" { } else if type == "telegram_theme" {
var file: TelegramMediaFile? var file: TelegramMediaFile?
var settings: TelegramThemeSettings?
var isSupported = false var isSupported = false
if let contentFiles = webpage.files {
if let filteredFile = contentFiles.filter({ $0.mimeType == themeMimeType }).first { for attribute in webpage.attributes {
isSupported = true if case let .theme(attribute) = attribute {
file = filteredFile if let attributeSettings = attribute.settings {
} else { settings = attributeSettings
file = contentFiles.first isSupported = true
} else if let filteredFile = attribute.files.filter({ $0.mimeType == themeMimeType }).first {
file = filteredFile
isSupported = true
}
} }
} else if let contentFile = webpage.file { }
if !isSupported, let contentFile = webpage.file {
isSupported = true isSupported = true
file = contentFile file = contentFile
} }
if let file = file { if let file = file {
let media = WallpaperPreviewMedia(content: .file(file, nil, nil, nil, true, isSupported)) let media = WallpaperPreviewMedia(content: .file(file, nil, nil, nil, true, isSupported))
mediaAndFlags = (media, ChatMessageAttachedContentNodeMediaFlags()) mediaAndFlags = (media, ChatMessageAttachedContentNodeMediaFlags())
} else if let settings = settings {
let media = WallpaperPreviewMedia(content: .themeSettings(settings))
mediaAndFlags = (media, ChatMessageAttachedContentNodeMediaFlags())
} }
} }
} }

View File

@ -812,6 +812,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
break break
case .wallet: case .wallet:
break break
case .settings:
break
} }
} }
})) }))

View File

@ -508,20 +508,39 @@ func openChatTheme(context: AccountContext, message: Message, pushController: @e
let _ = (context.sharedContext.resolveUrl(account: context.account, url: content.url) let _ = (context.sharedContext.resolveUrl(account: context.account, url: content.url)
|> deliverOnMainQueue).start(next: { resolvedUrl in |> deliverOnMainQueue).start(next: { resolvedUrl in
var file: TelegramMediaFile? var file: TelegramMediaFile?
let mimeType = "application/x-tgtheme-ios" var settings: TelegramThemeSettings?
if let contentFiles = content.files, let filteredFile = contentFiles.filter({ $0.mimeType == mimeType }).first { let themeMimeType = "application/x-tgtheme-ios"
file = filteredFile
} else if let contentFile = content.file, contentFile.mimeType == mimeType { for attribute in content.attributes {
if case let .theme(attribute) = attribute {
if let attributeSettings = attribute.settings {
settings = attributeSettings
} else if let filteredFile = attribute.files.filter({ $0.mimeType == themeMimeType }).first {
file = filteredFile
}
}
}
if file == nil && settings == nil, let contentFile = content.file, contentFile.mimeType == themeMimeType {
file = contentFile file = contentFile
} }
let displayUnsupportedAlert: () -> Void = { let displayUnsupportedAlert: () -> Void = {
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
present(textAlertController(context: context, title: nil, text: presentationData.strings.Theme_Unsupported, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) present(textAlertController(context: context, title: nil, text: presentationData.strings.Theme_Unsupported, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
} }
if case let .theme(slug) = resolvedUrl, let file = file { if case let .theme(slug) = resolvedUrl {
if let path = context.sharedContext.accountManager.mediaBox.completedResourcePath(file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { if let file = file {
if let theme = makePresentationTheme(data: data) { if let path = context.sharedContext.accountManager.mediaBox.completedResourcePath(file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) {
let controller = ThemePreviewController(context: context, previewTheme: theme, source: .slug(slug, file)) if let theme = makePresentationTheme(data: data) {
let controller = ThemePreviewController(context: context, previewTheme: theme, source: .slug(slug, file))
pushController(controller)
} else {
displayUnsupportedAlert()
}
}
} else if let settings = settings {
if let theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)), accentColor: UIColor(rgb: UInt32(bitPattern: settings.accentColor)), backgroundColors: nil, bubbleColors: settings.messageColors.flatMap { (UIColor(rgb: UInt32(bitPattern: $0.top)), UIColor(rgb: UInt32(bitPattern: $0.bottom))) }, wallpaper: settings.wallpaper, serviceBackgroundColor: nil, preview: false) {
let controller = ThemePreviewController(context: context, previewTheme: theme, source: .themeSettings(slug, settings))
pushController(controller) pushController(controller)
} else { } else {
displayUnsupportedAlert() displayUnsupportedAlert()

View File

@ -281,12 +281,13 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
case let .theme(slug): case let .theme(slug):
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let signal = getTheme(account: context.account, slug: slug) let signal = getTheme(account: context.account, slug: slug)
|> mapToSignal { themeInfo -> Signal<(Data, TelegramTheme), GetThemeError> in |> mapToSignal { themeInfo -> Signal<(Data?, TelegramThemeSettings?, TelegramTheme), GetThemeError> in
return Signal<(Data, TelegramTheme), GetThemeError> { subscriber in return Signal<(Data?, TelegramThemeSettings?, TelegramTheme), GetThemeError> { subscriber in
let disposables = DisposableSet() let disposables = DisposableSet()
let resource = themeInfo.file?.resource if let settings = themeInfo.settings {
subscriber.putNext((nil, settings, themeInfo))
if let resource = resource { subscriber.putCompletion()
} else if let resource = themeInfo.file?.resource {
disposables.add(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: .standalone(resource: resource)).start()) disposables.add(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: .standalone(resource: resource)).start())
let maybeFetched = context.sharedContext.accountManager.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: false) let maybeFetched = context.sharedContext.accountManager.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: false)
@ -309,7 +310,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
disposables.add(maybeFetched.start(next: { data in disposables.add(maybeFetched.start(next: { data in
if let data = data { if let data = data {
subscriber.putNext((data, themeInfo)) subscriber.putNext((data, nil, themeInfo))
subscriber.putCompletion() subscriber.putCompletion()
} }
})) }))
@ -348,9 +349,16 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
} }
} }
|> deliverOnMainQueue).start(next: { dataAndTheme in |> deliverOnMainQueue).start(next: { dataAndTheme in
if let theme = makePresentationTheme(data: dataAndTheme.0) { if let data = dataAndTheme.0 {
let previewController = ThemePreviewController(context: context, previewTheme: theme, source: .theme(dataAndTheme.1)) if let theme = makePresentationTheme(data: data) {
navigationController?.pushViewController(previewController) let previewController = ThemePreviewController(context: context, previewTheme: theme, source: .theme(dataAndTheme.2))
navigationController?.pushViewController(previewController)
}
} else if let settings = dataAndTheme.1 {
if let theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)), accentColor: UIColor(rgb: UInt32(bitPattern: settings.accentColor)), backgroundColors: nil, bubbleColors: settings.messageColors.flatMap { (UIColor(rgb: UInt32(bitPattern: $0.top)), UIColor(rgb: UInt32(bitPattern: $0.bottom))) }, wallpaper: settings.wallpaper) {
let previewController = ThemePreviewController(context: context, previewTheme: theme, source: .theme(dataAndTheme.2))
navigationController?.pushViewController(previewController)
}
} }
}, error: { error in }, error: { error in
let errorText: String let errorText: String
@ -368,5 +376,51 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
context.sharedContext.openWallet(context: context, walletContext: .send(address: address, amount: amount, comment: comment)) { c in context.sharedContext.openWallet(context: context, walletContext: .send(address: address, amount: amount, comment: comment)) { c in
navigationController?.pushViewController(c) navigationController?.pushViewController(c)
} }
case let .settings(section):
dismissInput()
switch section {
case .theme:
if let navigationController = navigationController {
let controller = themeSettingsController(context: context)
controller.navigationPresentation = .modal
var controllers = navigationController.viewControllers
controllers = controllers.filter { !($0 is ThemeSettingsController) }
controllers.append(controller)
navigationController.setViewControllers(controllers, animated: true)
}
case .devices:
if let navigationController = navigationController {
let activeSessions = deferred { () -> Signal<(ActiveSessionsContext, Int, WebSessionsContext), NoError> in
let activeSessionsContext = ActiveSessionsContext(account: context.account)
let webSessionsContext = WebSessionsContext(account: context.account)
let otherSessionCount = activeSessionsContext.state
|> map { state -> Int in
return state.sessions.filter({ !$0.isCurrent }).count
}
|> distinctUntilChanged
return otherSessionCount
|> map { value in
return (activeSessionsContext, value, webSessionsContext)
}
}
let _ = (activeSessions
|> take(1)
|> deliverOnMainQueue).start(next: { activeSessionsContext, count, webSessionsContext in
let controller = recentSessionsController(context: context, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext, websitesOnly: false)
controller.navigationPresentation = .modal
var controllers = navigationController.viewControllers
controllers = controllers.filter { !($0 is RecentSessionsController) }
controllers.append(controller)
navigationController.setViewControllers(controllers, animated: true)
})
}
break
}
} }
} }

View File

@ -200,7 +200,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
} }
let continueHandling: () -> Void = { let continueHandling: () -> Void = {
let handleRevolvedUrl: (ResolvedUrl) -> Void = { resolved in let handleResolvedUrl: (ResolvedUrl) -> Void = { resolved in
if case let .externalUrl(value) = resolved { if case let .externalUrl(value) = resolved {
context.sharedContext.applicationBindings.openUrl(value) context.sharedContext.applicationBindings.openUrl(value)
} else { } else {
@ -243,416 +243,435 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
let handleInternalUrl: (String) -> Void = { url in let handleInternalUrl: (String) -> Void = { url in
let _ = (context.sharedContext.resolveUrl(account: context.account, url: url) let _ = (context.sharedContext.resolveUrl(account: context.account, url: url)
|> deliverOnMainQueue).start(next: handleRevolvedUrl) |> deliverOnMainQueue).start(next: handleResolvedUrl)
} }
if let scheme = parsedUrl.scheme, (scheme == "tg" || scheme == context.sharedContext.applicationBindings.appSpecificScheme), let query = parsedUrl.query { if let scheme = parsedUrl.scheme, (scheme == "tg" || scheme == context.sharedContext.applicationBindings.appSpecificScheme) {
var convertedUrl: String? var convertedUrl: String?
if parsedUrl.host == "localpeer" { if let query = parsedUrl.query {
if let components = URLComponents(string: "/?" + query) { if parsedUrl.host == "localpeer" {
var peerId: PeerId? if let components = URLComponents(string: "/?" + query) {
if let queryItems = components.queryItems { var peerId: PeerId?
for queryItem in queryItems { if let queryItems = components.queryItems {
if let value = queryItem.value { for queryItem in queryItems {
if queryItem.name == "id", let intValue = Int64(value) { if let value = queryItem.value {
peerId = PeerId(intValue) if queryItem.name == "id", let intValue = Int64(value) {
} peerId = PeerId(intValue)
}
}
}
if let peerId = peerId, let navigationController = navigationController {
context.sharedContext.applicationBindings.dismissNativeController()
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId)))
}
}
} else if parsedUrl.host == "join" {
if let components = URLComponents(string: "/?" + query) {
var invite: String?
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "invite" {
invite = value
}
}
}
}
if let invite = invite {
convertedUrl = "https://t.me/joinchat/\(invite)"
}
}
} else if parsedUrl.host == "addstickers" {
if let components = URLComponents(string: "/?" + query) {
var set: String?
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "set" {
set = value
}
}
}
}
if let set = set {
convertedUrl = "https://t.me/addstickers/\(set)"
}
}
} else if parsedUrl.host == "setlanguage" {
if let components = URLComponents(string: "/?" + query) {
var lang: String?
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "lang" {
lang = value
}
}
}
}
if let lang = lang {
convertedUrl = "https://t.me/setlanguage/\(lang)"
}
}
} else if parsedUrl.host == "msg" {
if let components = URLComponents(string: "/?" + query) {
var sharePhoneNumber: String?
var shareText: String?
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "to" {
sharePhoneNumber = value
} else if queryItem.name == "text" {
shareText = value
}
}
}
}
if sharePhoneNumber != nil || shareText != nil {
handleRevolvedUrl(.share(url: nil, text: shareText, to: sharePhoneNumber))
return
}
}
} else if parsedUrl.host == "msg_url" {
if let components = URLComponents(string: "/?" + query) {
var shareUrl: String?
var shareText: String?
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "url" {
shareUrl = value
} else if queryItem.name == "text" {
shareText = value
}
}
}
}
if let shareUrl = shareUrl {
var resultUrl = "https://t.me/share/url?url=\(urlEncodedStringFromString(shareUrl))"
if let shareText = shareText {
resultUrl += "&text=\(urlEncodedStringFromString(shareText))"
}
convertedUrl = resultUrl
}
}
} else if parsedUrl.host == "socks" || parsedUrl.host == "proxy" {
if let components = URLComponents(string: "/?" + query) {
var server: String?
var port: String?
var user: String?
var pass: String?
var secret: String?
var secretHost: String?
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "server" || queryItem.name == "proxy" {
server = value
} else if queryItem.name == "port" {
port = value
} else if queryItem.name == "user" {
user = value
} else if queryItem.name == "pass" {
pass = value
} else if queryItem.name == "secret" {
secret = value
} else if queryItem.name == "host" {
secretHost = value
}
}
}
}
if let server = server, !server.isEmpty, let port = port, let _ = Int32(port) {
var result = "https://t.me/proxy?proxy=\(server)&port=\(port)"
if let user = user {
result += "&user=\((user as NSString).addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryValueAllowed) ?? "")"
if let pass = pass {
result += "&pass=\((pass as NSString).addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryValueAllowed) ?? "")"
}
}
if let secret = secret {
result += "&secret=\((secret as NSString).addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryValueAllowed) ?? "")"
}
if let secretHost = secretHost?.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryValueAllowed) {
result += "&host=\(secretHost)"
}
convertedUrl = result
}
}
} else if parsedUrl.host == "passport" || parsedUrl.host == "resolve" {
if let components = URLComponents(string: "/?" + query) {
var domain: String?
var botId: Int32?
var scope: String?
var publicKey: String?
var callbackUrl: String?
var opaquePayload = Data()
var opaqueNonce = Data()
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "domain" {
domain = value
} else if queryItem.name == "bot_id" {
botId = Int32(value)
} else if queryItem.name == "scope" {
scope = value
} else if queryItem.name == "public_key" {
publicKey = value
} else if queryItem.name == "callback_url" {
callbackUrl = value
} else if queryItem.name == "payload" {
if let data = value.data(using: .utf8) {
opaquePayload = data
}
} else if queryItem.name == "nonce" {
if let data = value.data(using: .utf8) {
opaqueNonce = data
} }
} }
} }
} }
if let peerId = peerId, let navigationController = navigationController {
context.sharedContext.applicationBindings.dismissNativeController()
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId)))
}
} }
} else if parsedUrl.host == "join" {
let valid: Bool if let components = URLComponents(string: "/?" + query) {
if parsedUrl.host == "resolve" { var invite: String?
if domain == "telegrampassport" { if let queryItems = components.queryItems {
valid = true for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "invite" {
invite = value
}
}
}
}
if let invite = invite {
convertedUrl = "https://t.me/joinchat/\(invite)"
}
}
} else if parsedUrl.host == "addstickers" {
if let components = URLComponents(string: "/?" + query) {
var set: String?
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "set" {
set = value
}
}
}
}
if let set = set {
convertedUrl = "https://t.me/addstickers/\(set)"
}
}
} else if parsedUrl.host == "setlanguage" {
if let components = URLComponents(string: "/?" + query) {
var lang: String?
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "lang" {
lang = value
}
}
}
}
if let lang = lang {
convertedUrl = "https://t.me/setlanguage/\(lang)"
}
}
} else if parsedUrl.host == "msg" {
if let components = URLComponents(string: "/?" + query) {
var sharePhoneNumber: String?
var shareText: String?
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "to" {
sharePhoneNumber = value
} else if queryItem.name == "text" {
shareText = value
}
}
}
}
if sharePhoneNumber != nil || shareText != nil {
handleResolvedUrl(.share(url: nil, text: shareText, to: sharePhoneNumber))
return
}
}
} else if parsedUrl.host == "msg_url" {
if let components = URLComponents(string: "/?" + query) {
var shareUrl: String?
var shareText: String?
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "url" {
shareUrl = value
} else if queryItem.name == "text" {
shareText = value
}
}
}
}
if let shareUrl = shareUrl {
var resultUrl = "https://t.me/share/url?url=\(urlEncodedStringFromString(shareUrl))"
if let shareText = shareText {
resultUrl += "&text=\(urlEncodedStringFromString(shareText))"
}
convertedUrl = resultUrl
}
}
} else if parsedUrl.host == "socks" || parsedUrl.host == "proxy" {
if let components = URLComponents(string: "/?" + query) {
var server: String?
var port: String?
var user: String?
var pass: String?
var secret: String?
var secretHost: String?
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "server" || queryItem.name == "proxy" {
server = value
} else if queryItem.name == "port" {
port = value
} else if queryItem.name == "user" {
user = value
} else if queryItem.name == "pass" {
pass = value
} else if queryItem.name == "secret" {
secret = value
} else if queryItem.name == "host" {
secretHost = value
}
}
}
}
if let server = server, !server.isEmpty, let port = port, let _ = Int32(port) {
var result = "https://t.me/proxy?proxy=\(server)&port=\(port)"
if let user = user {
result += "&user=\((user as NSString).addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryValueAllowed) ?? "")"
if let pass = pass {
result += "&pass=\((pass as NSString).addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryValueAllowed) ?? "")"
}
}
if let secret = secret {
result += "&secret=\((secret as NSString).addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryValueAllowed) ?? "")"
}
if let secretHost = secretHost?.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryValueAllowed) {
result += "&host=\(secretHost)"
}
convertedUrl = result
}
}
} else if parsedUrl.host == "passport" || parsedUrl.host == "resolve" {
if let components = URLComponents(string: "/?" + query) {
var domain: String?
var botId: Int32?
var scope: String?
var publicKey: String?
var callbackUrl: String?
var opaquePayload = Data()
var opaqueNonce = Data()
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "domain" {
domain = value
} else if queryItem.name == "bot_id" {
botId = Int32(value)
} else if queryItem.name == "scope" {
scope = value
} else if queryItem.name == "public_key" {
publicKey = value
} else if queryItem.name == "callback_url" {
callbackUrl = value
} else if queryItem.name == "payload" {
if let data = value.data(using: .utf8) {
opaquePayload = data
}
} else if queryItem.name == "nonce" {
if let data = value.data(using: .utf8) {
opaqueNonce = data
}
}
}
}
}
let valid: Bool
if parsedUrl.host == "resolve" {
if domain == "telegrampassport" {
valid = true
} else {
valid = false
}
} else { } else {
valid = false valid = true
} }
} else {
valid = true if valid {
} if let botId = botId, let scope = scope, let publicKey = publicKey, let callbackUrl = callbackUrl {
if scope.hasPrefix("{") && scope.hasSuffix("}") {
if valid { opaquePayload = Data()
if let botId = botId, let scope = scope, let publicKey = publicKey, let callbackUrl = callbackUrl { if opaqueNonce.isEmpty {
if scope.hasPrefix("{") && scope.hasSuffix("}") { return
opaquePayload = Data() }
if opaqueNonce.isEmpty { } else if opaquePayload.isEmpty {
return return
} }
} else if opaquePayload.isEmpty { if case .chat = urlContext {
return return
} }
if case .chat = urlContext { let controller = SecureIdAuthController(context: context, mode: .form(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: botId), scope: scope, publicKey: publicKey, callbackUrl: callbackUrl, opaquePayload: opaquePayload, opaqueNonce: opaqueNonce))
return
}
let controller = SecureIdAuthController(context: context, mode: .form(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: botId), scope: scope, publicKey: publicKey, callbackUrl: callbackUrl, opaquePayload: opaquePayload, opaqueNonce: opaqueNonce))
if let navigationController = navigationController {
context.sharedContext.applicationBindings.dismissNativeController()
navigationController.view.window?.endEditing(true) if let navigationController = navigationController {
context.sharedContext.applicationBindings.getWindowHost()?.present(controller, on: .root, blockInteraction: false, completion: {}) context.sharedContext.applicationBindings.dismissNativeController()
navigationController.view.window?.endEditing(true)
context.sharedContext.applicationBindings.getWindowHost()?.present(controller, on: .root, blockInteraction: false, completion: {})
}
} }
return
} }
return
} }
} } else if parsedUrl.host == "user" {
} else if parsedUrl.host == "user" { if let components = URLComponents(string: "/?" + query) {
if let components = URLComponents(string: "/?" + query) { var id: String?
var id: String? if let queryItems = components.queryItems {
if let queryItems = components.queryItems { for queryItem in queryItems {
for queryItem in queryItems { if let value = queryItem.value {
if let value = queryItem.value { if queryItem.name == "id" {
if queryItem.name == "id" { id = value
id = value }
} }
} }
} }
}
if let id = id, !id.isEmpty, let idValue = Int32(id), idValue > 0 {
if let id = id, !id.isEmpty, let idValue = Int32(id), idValue > 0 { let _ = (context.account.postbox.transaction { transaction -> Peer? in
let _ = (context.account.postbox.transaction { transaction -> Peer? in return transaction.getPeer(PeerId(namespace: Namespaces.Peer.CloudUser, id: idValue))
return transaction.getPeer(PeerId(namespace: Namespaces.Peer.CloudUser, id: idValue))
}
|> deliverOnMainQueue).start(next: { peer in
if let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic) {
navigationController?.pushViewController(controller)
} }
}) |> deliverOnMainQueue).start(next: { peer in
return if let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic) {
} navigationController?.pushViewController(controller)
}
} else if parsedUrl.host == "login" {
if let components = URLComponents(string: "/?" + query) {
var code: String?
var isToken: Bool = false
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "code" {
code = value
} }
} })
if queryItem.name == "token" { return
isToken = true
}
} }
} }
if isToken { } else if parsedUrl.host == "login" {
context.sharedContext.presentGlobalController(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.AuthSessions_AddDevice_UrlLoginHint, actions: [ if let components = URLComponents(string: "/?" + query) {
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: { var code: String?
}), var isToken: Bool = false
], parseMarkdown: true), nil) if let queryItems = components.queryItems {
return for queryItem in queryItems {
} if let value = queryItem.value {
if let code = code { if queryItem.name == "code" {
convertedUrl = "https://t.me/login/\(code)" code = value
} }
} }
} else if parsedUrl.host == "confirmphone" { if queryItem.name == "token" {
if let components = URLComponents(string: "/?" + query) { isToken = true
var phone: String?
var hash: String?
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "phone" {
phone = value
} else if queryItem.name == "hash" {
hash = value
} }
} }
} }
if isToken {
context.sharedContext.presentGlobalController(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.AuthSessions_AddDevice_UrlLoginHint, actions: [
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
}),
], parseMarkdown: true), nil)
return
}
if let code = code {
convertedUrl = "https://t.me/login/\(code)"
}
} }
if let phone = phone, let hash = hash { } else if parsedUrl.host == "confirmphone" {
convertedUrl = "https://t.me/confirmphone?phone=\(phone)&hash=\(hash)" if let components = URLComponents(string: "/?" + query) {
} var phone: String?
} var hash: String?
} else if parsedUrl.host == "bg" { if let queryItems = components.queryItems {
if let components = URLComponents(string: "/?" + query) { for queryItem in queryItems {
var parameter: String? if let value = queryItem.value {
var query: [String] = [] if queryItem.name == "phone" {
if let queryItems = components.queryItems { phone = value
for queryItem in queryItems { } else if queryItem.name == "hash" {
if let value = queryItem.value { hash = value
if queryItem.name == "slug" { }
parameter = value
} else if queryItem.name == "color" {
parameter = value
} else if queryItem.name == "gradient" {
parameter = value
} else if queryItem.name == "mode" {
query.append("mode=\(value)")
} else if queryItem.name == "bg_color" {
query.append("bg_color=\(value)")
} else if queryItem.name == "intensity" {
query.append("intensity=\(value)")
} else if queryItem.name == "rotation" {
query.append("rotation=\(value)")
} }
} }
} }
if let phone = phone, let hash = hash {
convertedUrl = "https://t.me/confirmphone?phone=\(phone)&hash=\(hash)"
}
} }
var queryString = "" } else if parsedUrl.host == "bg" {
if !query.isEmpty { if let components = URLComponents(string: "/?" + query) {
queryString = "?\(query.joined(separator: "&"))" var parameter: String?
} var query: [String] = []
if let parameter = parameter { if let queryItems = components.queryItems {
convertedUrl = "https://t.me/bg/\(parameter)\(queryString)" for queryItem in queryItems {
} if let value = queryItem.value {
} if queryItem.name == "slug" {
} else if parsedUrl.host == "addtheme" { parameter = value
if let components = URLComponents(string: "/?" + query) { } else if queryItem.name == "color" {
var parameter: String? parameter = value
if let queryItems = components.queryItems { } else if queryItem.name == "gradient" {
for queryItem in queryItems { parameter = value
if let value = queryItem.value { } else if queryItem.name == "mode" {
if queryItem.name == "slug" { query.append("mode=\(value)")
parameter = value } else if queryItem.name == "bg_color" {
query.append("bg_color=\(value)")
} else if queryItem.name == "intensity" {
query.append("intensity=\(value)")
} else if queryItem.name == "rotation" {
query.append("rotation=\(value)")
}
} }
} }
} }
var queryString = ""
if !query.isEmpty {
queryString = "?\(query.joined(separator: "&"))"
}
if let parameter = parameter {
convertedUrl = "https://t.me/bg/\(parameter)\(queryString)"
}
} }
if let parameter = parameter { } else if parsedUrl.host == "addtheme" {
convertedUrl = "https://t.me/addtheme/\(parameter)" if let components = URLComponents(string: "/?" + query) {
} var parameter: String?
} if let queryItems = components.queryItems {
} for queryItem in queryItems {
if let value = queryItem.value {
if parsedUrl.host == "resolve" { if queryItem.name == "slug" {
if let components = URLComponents(string: "/?" + query) { parameter = value
var domain: String? }
var start: String?
var startGroup: String?
var game: String?
var post: String?
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "domain" {
domain = value
} else if queryItem.name == "start" {
start = value
} else if queryItem.name == "startgroup" {
startGroup = value
} else if queryItem.name == "game" {
game = value
} else if queryItem.name == "post" {
post = value
} }
} }
} }
} if let parameter = parameter {
convertedUrl = "https://t.me/addtheme/\(parameter)"
if let domain = domain {
var result = "https://t.me/\(domain)"
if let post = post, let postValue = Int(post) {
result += "/\(postValue)"
} }
if let start = start {
result += "?start=\(start)"
} else if let startGroup = startGroup {
result += "?startgroup=\(startGroup)"
} else if let game = game {
result += "?game=\(game)"
}
convertedUrl = result
} }
} }
} else if parsedUrl.host == "hostOverride" {
if let components = URLComponents(string: "/?" + query) { if parsedUrl.host == "resolve" {
var host: String? if let components = URLComponents(string: "/?" + query) {
if let queryItems = components.queryItems { var domain: String?
for queryItem in queryItems { var start: String?
if let value = queryItem.value { var startGroup: String?
if queryItem.name == "host" { var game: String?
host = value var post: String?
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "domain" {
domain = value
} else if queryItem.name == "start" {
start = value
} else if queryItem.name == "startgroup" {
startGroup = value
} else if queryItem.name == "game" {
game = value
} else if queryItem.name == "post" {
post = value
}
} }
} }
} }
if let domain = domain {
var result = "https://t.me/\(domain)"
if let post = post, let postValue = Int(post) {
result += "/\(postValue)"
}
if let start = start {
result += "?start=\(start)"
} else if let startGroup = startGroup {
result += "?startgroup=\(startGroup)"
} else if let game = game {
result += "?game=\(game)"
}
convertedUrl = result
}
} }
if let host = host { } else if parsedUrl.host == "hostOverride" {
let _ = updateNetworkSettingsInteractively(postbox: context.account.postbox, network: context.account.network, { settings in if let components = URLComponents(string: "/?" + query) {
var settings = settings var host: String?
settings.backupHostOverride = host if let queryItems = components.queryItems {
return settings for queryItem in queryItems {
}).start() if let value = queryItem.value {
return if queryItem.name == "host" {
host = value
}
}
}
}
if let host = host {
let _ = updateNetworkSettingsInteractively(postbox: context.account.postbox, network: context.account.network, { settings in
var settings = settings
settings.backupHostOverride = host
return settings
}).start()
return
}
}
}
} else {
if parsedUrl.host == "settings" {
if let path = parsedUrl.pathComponents.last {
var section: ResolvedUrlSettingsSection?
switch path {
case "theme":
section = .theme
case "devices":
section = .devices
default:
break
}
if let section = section {
handleResolvedUrl(.settings(section))
}
} }
} }
} }

View File

@ -8,6 +8,7 @@ enum WallpaperPreviewMediaContent: Equatable {
case file(TelegramMediaFile, UIColor?, UIColor?, Int32?, Bool, Bool) case file(TelegramMediaFile, UIColor?, UIColor?, Int32?, Bool, Bool)
case color(UIColor) case color(UIColor)
case gradient(UIColor, UIColor, Int32?) case gradient(UIColor, UIColor, Int32?)
case themeSettings(TelegramThemeSettings)
} }
final class WallpaperPreviewMedia: Media { final class WallpaperPreviewMedia: Media {

View File

@ -117,7 +117,7 @@ final class WebpagePreviewAccessoryPanelNode: AccessoryPanelNode {
} else { } else {
text = stringForMediaKind(mediaKind, strings: self.strings).0 text = stringForMediaKind(mediaKind, strings: self.strings).0
} }
} else if let files = content.files, content.type == "telegram_theme" { } else if content.type == "telegram_theme" {
text = strings.Message_Theme text = strings.Message_Theme
} else if let _ = content.image { } else if let _ = content.image {
text = stringForMediaKind(.image, strings: self.strings).0 text = stringForMediaKind(.image, strings: self.strings).0

View File

@ -456,6 +456,7 @@ public struct PresentationThemeAccentColor: PostboxCoding, Equatable {
} else { } else {
self.bubbleColors = nil self.bubbleColors = nil
} }
self.wallpaper = decoder.decodeObjectForKey("w", decoder: { TelegramWallpaper(decoder: $0) }) as? TelegramWallpaper
} }
public func encode(_ encoder: PostboxEncoder) { public func encode(_ encoder: PostboxEncoder) {
@ -477,6 +478,11 @@ public struct PresentationThemeAccentColor: PostboxCoding, Equatable {
encoder.encodeNil(forKey: "bt") encoder.encodeNil(forKey: "bt")
encoder.encodeNil(forKey: "bb") encoder.encodeNil(forKey: "bb")
} }
if let wallpaper = self.wallpaper {
encoder.encodeObject(wallpaper, forKey: "w")
} else {
encoder.encodeNil(forKey: "w")
}
} }
public var color: UIColor { public var color: UIColor {
@ -510,6 +516,10 @@ public struct PresentationThemeAccentColor: PostboxCoding, Equatable {
return nil return nil
} }
} }
public func withUpdatedWallpaper(_ wallpaper: TelegramWallpaper?) -> PresentationThemeAccentColor {
return PresentationThemeAccentColor(index: self.index, baseColor: self.baseColor, accentColor: self.accentColor, bubbleColors: self.bubbleColors, wallpaper: wallpaper)
}
} }
public struct PresentationThemeSettings: PreferencesEntry { public struct PresentationThemeSettings: PreferencesEntry {

View File

@ -863,84 +863,98 @@ public func drawThemeImage(context c: CGContext, theme: PresentationTheme, wallp
c.restoreGState() c.restoreGState()
} }
public func themeImage(account: Account, accountManager: AccountManager, fileReference: FileMediaReference, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { public enum ThemeImageSource {
let isSupportedTheme = fileReference.media.mimeType == "application/x-tgtheme-ios" case file(FileMediaReference)
let maybeFetched = accountManager.mediaBox.resourceData(fileReference.media.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad) case settings(TelegramThemeSettings)
let data = maybeFetched }
|> take(1)
|> mapToSignal { maybeData -> Signal<(Data?, Data?), NoError> in public func themeImage(account: Account, accountManager: AccountManager, source: ThemeImageSource, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
if maybeData.complete && isSupportedTheme { let theme: Signal<(PresentationTheme?, Data?), NoError>
let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: [])
return .single((loadedData, nil)) switch source {
} else { case let .file(fileReference):
let decodedThumbnailData = fileReference.media.immediateThumbnailData.flatMap(decodeTinyThumbnail) let isSupportedTheme = fileReference.media.mimeType == "application/x-tgtheme-ios"
let maybeFetched = accountManager.mediaBox.resourceData(fileReference.media.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad)
let previewRepresentation = fileReference.media.previewRepresentations.first theme = maybeFetched
let fetchedThumbnail: Signal<FetchResourceSourceType, FetchResourceError> |> take(1)
if let previewRepresentation = previewRepresentation { |> mapToSignal { maybeData -> Signal<(PresentationTheme?, Data?), NoError> in
fetchedThumbnail = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: fileReference.resourceReference(previewRepresentation.resource)) if maybeData.complete && isSupportedTheme {
} else { let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: [])
fetchedThumbnail = .complete() return .single((loadedData.flatMap { makePresentationTheme(data: $0) }, nil))
} } else {
let decodedThumbnailData = fileReference.media.immediateThumbnailData.flatMap(decodeTinyThumbnail)
let thumbnailData: Signal<Data?, NoError>
if let previewRepresentation = previewRepresentation {
thumbnailData = Signal<Data?, NoError> { subscriber in
let fetchedDisposable = fetchedThumbnail.start()
let thumbnailDisposable = account.postbox.mediaBox.resourceData(previewRepresentation.resource).start(next: { next in
let data = next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: [])
if let data = data, data.count > 0 {
subscriber.putNext(data)
} else {
subscriber.putNext(decodedThumbnailData)
}
}, error: subscriber.putError, completed: subscriber.putCompletion)
return ActionDisposable { let previewRepresentation = fileReference.media.previewRepresentations.first
fetchedDisposable.dispose() let fetchedThumbnail: Signal<FetchResourceSourceType, FetchResourceError>
thumbnailDisposable.dispose() if let previewRepresentation = previewRepresentation {
fetchedThumbnail = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: fileReference.resourceReference(previewRepresentation.resource))
} else {
fetchedThumbnail = .complete()
} }
}
} else { let thumbnailData: Signal<Data?, NoError>
thumbnailData = .single(decodedThumbnailData) if let previewRepresentation = previewRepresentation {
} thumbnailData = Signal<Data?, NoError> { subscriber in
let fetchedDisposable = fetchedThumbnail.start()
let reference = fileReference.resourceReference(fileReference.media.resource) let thumbnailDisposable = account.postbox.mediaBox.resourceData(previewRepresentation.resource).start(next: { next in
let fullSizeData: Signal<Data?, NoError> let data = next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: [])
if isSupportedTheme { if let data = data, data.count > 0 {
fullSizeData = Signal { subscriber in subscriber.putNext(data)
let fetch = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: reference).start() } else {
let disposable = (account.postbox.mediaBox.resourceData(reference.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: false) subscriber.putNext(decodedThumbnailData)
|> map { data -> Data? in }
return data.complete ? try? Data(contentsOf: URL(fileURLWithPath: data.path)) : nil }, error: subscriber.putError, completed: subscriber.putCompletion)
}).start(next: { next in
if let data = next { return ActionDisposable {
accountManager.mediaBox.storeResourceData(reference.resource.id, data: data) fetchedDisposable.dispose()
thumbnailDisposable.dispose()
} }
subscriber.putNext(next) }
}, error: { error in } else {
subscriber.putError(error) thumbnailData = .single(decodedThumbnailData)
}, completed: { }
subscriber.putCompletion()
}) let reference = fileReference.resourceReference(fileReference.media.resource)
return ActionDisposable { let fullSizeData: Signal<Data?, NoError>
fetch.dispose() if isSupportedTheme {
disposable.dispose() fullSizeData = Signal { subscriber in
let fetch = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: reference).start()
let disposable = (account.postbox.mediaBox.resourceData(reference.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: false)
|> map { data -> Data? in
return data.complete ? try? Data(contentsOf: URL(fileURLWithPath: data.path)) : nil
}).start(next: { next in
if let data = next {
accountManager.mediaBox.storeResourceData(reference.resource.id, data: data)
}
subscriber.putNext(next)
}, error: { error in
subscriber.putError(error)
}, completed: {
subscriber.putCompletion()
})
return ActionDisposable {
fetch.dispose()
disposable.dispose()
}
}
} else {
fullSizeData = .single(nil)
}
return thumbnailData |> mapToSignal { thumbnailData in
return fullSizeData |> map { fullSizeData in
return (fullSizeData.flatMap { makePresentationTheme(data: $0) }, thumbnailData)
}
} }
} }
} else {
fullSizeData = .single(nil)
} }
case let .settings(settings):
return thumbnailData |> mapToSignal { thumbnailData in theme = .single((makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)), accentColor: UIColor(rgb: UInt32(bitPattern: settings.accentColor)), backgroundColors: nil, bubbleColors: settings.messageColors.flatMap { (UIColor(rgb: UInt32(bitPattern: $0.top)), UIColor(rgb: UInt32(bitPattern: $0.bottom))) }, wallpaper: settings.wallpaper, serviceBackgroundColor: nil, preview: false), nil))
return fullSizeData |> map { fullSizeData in
return (fullSizeData, thumbnailData)
}
}
}
} }
|> mapToSignal { (fullSizeData, thumbnailData) -> Signal<(PresentationTheme?, UIImage?, Data?), NoError> in
if let fullSizeData = fullSizeData, let theme = makePresentationTheme(data: fullSizeData) { let data = theme
|> mapToSignal { (theme, thumbnailData) -> Signal<(PresentationTheme?, UIImage?, Data?), NoError> in
if let theme = theme {
if case let .file(file) = theme.chat.defaultWallpaper, file.id == 0 { if case let .file(file) = theme.chat.defaultWallpaper, file.id == 0 {
return cachedWallpaper(account: account, slug: file.slug, settings: file.settings) return cachedWallpaper(account: account, slug: file.slug, settings: file.settings)
|> mapToSignal { wallpaper -> Signal<(PresentationTheme?, UIImage?, Data?), NoError> in |> mapToSignal { wallpaper -> Signal<(PresentationTheme?, UIImage?, Data?), NoError> in
@ -1071,6 +1085,16 @@ public func themeIconImage(account: Account, accountManager: AccountManager, the
case .dayClassic: case .dayClassic:
incomingColor = UIColor(rgb: 0xffffff) incomingColor = UIColor(rgb: 0xffffff)
if let accentColor = accentColor { if let accentColor = accentColor {
if let wallpaper = wallpaper, case let .file(file) = wallpaper {
topBackgroundColor = file.settings.color.flatMap { UIColor(rgb: UInt32(bitPattern: $0)) } ?? UIColor(rgb: 0xd6e2ee)
bottomBackgroundColor = file.settings.bottomColor.flatMap { UIColor(rgb: UInt32(bitPattern: $0)) }
} else {
if let bubbleColors = bubbleColors {
topBackgroundColor = UIColor(rgb: 0xd6e2ee)
} else {
topBackgroundColor = accentColor.withMultiplied(hue: 1.019, saturation: 0.867, brightness: 0.965)
}
}
if let bubbleColors = bubbleColors { if let bubbleColors = bubbleColors {
topBackgroundColor = UIColor(rgb: 0xd6e2ee) topBackgroundColor = UIColor(rgb: 0xd6e2ee)
outgoingColor = bubbleColors outgoingColor = bubbleColors