diff --git a/NotificationService/Serialization.m b/NotificationService/Serialization.m index 76a2978a00..cd0732944d 100644 --- a/NotificationService/Serialization.m +++ b/NotificationService/Serialization.m @@ -3,7 +3,7 @@ @implementation Serialization - (NSUInteger)currentLayer { - return 107; + return 108; } - (id _Nullable)parseMessage:(NSData * _Nullable)data { diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index a1abc90612..f81ef79b95 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -153,6 +153,11 @@ public enum WallpaperUrlParameter { case gradient(UIColor, UIColor, Int32?) } +public enum ResolvedUrlSettingsSection { + case theme + case devices +} + public enum ResolvedUrl { case externalUrl(String) case peer(PeerId?, ChatControllerInteractionNavigateToPeer) @@ -171,6 +176,7 @@ public enum ResolvedUrl { case wallpaper(WallpaperUrlParameter) case theme(String) case wallet(address: String, amount: Int64?, comment: String?) + case settings(ResolvedUrlSettingsSection) } public enum NavigateToChatKeepStack { diff --git a/submodules/InstantPageUI/Sources/InstantPageLayout.swift b/submodules/InstantPageUI/Sources/InstantPageLayout.swift index ddd2d2eb6a..65fe599451 100644 --- a/submodules/InstantPageUI/Sources/InstantPageLayout.swift +++ b/submodules/InstantPageUI/Sources/InstantPageLayout.swift @@ -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 item: InstantPageItem 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) 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) diff --git a/submodules/LocationUI/Sources/LocationMapNode.swift b/submodules/LocationUI/Sources/LocationMapNode.swift index 7fd7152b2f..eebbdaa76c 100644 --- a/submodules/LocationUI/Sources/LocationMapNode.swift +++ b/submodules/LocationUI/Sources/LocationMapNode.swift @@ -36,12 +36,33 @@ private class PickerAnnotationContainerView: UIView { private class LocationMapView: MKMapView, UIGestureRecognizerDelegate { var customHitTest: ((CGPoint) -> Bool)? + private var allowSelectionChanges = true @objc override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { if let customHitTest = self.customHitTest, customHitTest(gestureRecognizer.location(in: self)) { 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 } } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroControllerNode.swift b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroControllerNode.swift index 08b4282688..ab70359164 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroControllerNode.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroControllerNode.swift @@ -13,6 +13,7 @@ import AuthorizationUI private func generateButtonImage(backgroundColor: UIColor, borderColor: UIColor, highlightColor: UIColor?) -> UIImage? { return generateImage(CGSize(width: 1.0, height: 44.0), contextGenerator: { size, context in let bounds = CGRect(origin: CGPoint(), size: size) + context.clear(bounds) if let highlightColor = highlightColor { context.setFillColor(highlightColor.cgColor) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift index 415f308e4b..a32575de45 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift @@ -150,7 +150,7 @@ final class ThemeAccentColorController: ViewController { } } - let prepare: Signal + let prepare: Signal if let patternWallpaper = state.patternWallpaper, case let .file(file) = patternWallpaper, let backgroundColors = state.backgroundColors { let resource = file.file.resource let representation = CachedPatternWallpaperRepresentation(color: Int32(bitPattern: backgroundColors.0.rgb), bottomColor: backgroundColors.1.flatMap { Int32(bitPattern: $0.rgb) }, intensity: state.patternIntensity, rotation: state.rotation) @@ -167,7 +167,8 @@ final class ThemeAccentColorController: ViewController { prepare = (strongSelf.context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(resource, representation: representation, complete: true, fetch: true) |> filter({ $0.complete }) |> take(1) - |> mapToSignal { _ -> Signal in + |> castError(CreateThemeError.self) + |> mapToSignal { _ -> Signal in return .complete() }) } else { @@ -201,67 +202,116 @@ final class ThemeAccentColorController: ViewController { completion(updatedTheme, settings) }) - } else { - 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 - } - - var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers - var themeSpecificAccentColors = current.themeSpecificAccentColors - var themeSpecificCustomColors = current.themeSpecificCustomColors - var customColors = themeSpecificCustomColors[currentTheme.index]?.colors ?? [] - - var bubbleColors: (Int32, Int32?)? - 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 if case let .colors(theme, create) = strongSelf.mode { + var baseTheme: TelegramBaseTheme + var telegramTheme: TelegramTheme? + if case let .cloud(theme) = theme, let settings = theme.theme.settings { + telegramTheme = theme.theme + baseTheme = settings.baseTheme + } else { + baseTheme = .classic + } + + let accentColor = Int32(bitPattern: state.accentColor.rgb) + var bubbleColors: (Int32, Int32)? + if let messagesColors = state.messagesColors { + if let secondColor = messagesColors.1 { + bubbleColors = (Int32(bitPattern: messagesColors.0.rgb), Int32(bitPattern: secondColor.rgb)) } else { - index = Int32(bitPattern: arc4random()) - exists = false + bubbleColors = (Int32(bitPattern: messagesColors.0.rgb), Int32(bitPattern: messagesColors.0.rgb)) } - - let color = PresentationThemeAccentColor(index: index, baseColor: .custom, accentColor: Int32(bitPattern: state.accentColor.rgb), bubbleColors: bubbleColors) - themeSpecificAccentColors[currentTheme.index] = color - - if exists { - if let index = customColors.firstIndex(where: { $0.index == index }) { - customColors[index] = color - } else { - customColors.append(color) + } + + var wallpaper: TelegramWallpaper? = nil // themeSpecificChatWallpapers[currentTheme.index] + if let coloredWallpaper = coloredWallpaper { + wallpaper = coloredWallpaper + } + + let settings = TelegramThemeSettings(baseTheme: baseTheme, accentColor: accentColor, messageColors: bubbleColors, wallpaper: wallpaper) + + let save: Signal + + 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 { - customColors.append(color) - } - - var wallpaper = themeSpecificChatWallpapers[currentTheme.index] - if let coloredWallpaper = coloredWallpaper { - wallpaper = coloredWallpaper - } - - themeSpecificChatWallpapers[coloredThemeIndex(reference: currentTheme, accentColor: color)] = wallpaper - themeSpecificCustomColors[currentTheme.index] = PresentationThemeCustomColors(colors: customColors) - - 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 - if let strongSelf = self { - strongSelf.completion?() - strongSelf.dismiss() - } - }) + }, error: { error in + }) + } else { + let title = generateThemeName(accentColor: state.accentColor) + let _ = (prepare |> then(createTheme(account: context.account, title: title, resource: nil, thumbnailData: 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() + } + }) + } + }, 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 { - 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 - if let accentColor = themeSpecificAccentColor, let customWallpaper = settings.themeSpecificChatWallpapers[coloredThemeIndex(reference: themeReference, accentColor: accentColor)] { - wallpaper = customWallpaper - } else if let customWallpaper = settings.themeSpecificChatWallpapers[themeReference.index] { - wallpaper = customWallpaper - } else { - 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 + + if case .colors(_, true) = strongSelf.mode { + let themeSpecificAccentColor = settings.themeSpecificAccentColors[themeReference.index] + accentColor = themeSpecificAccentColor?.color ?? defaultDayAccentColor + + if let accentColor = themeSpecificAccentColor, let customWallpaper = settings.themeSpecificChatWallpapers[coloredThemeIndex(reference: themeReference, accentColor: accentColor)] { + wallpaper = customWallpaper + } else if let customWallpaper = settings.themeSpecificChatWallpapers[themeReference.index] { + wallpaper = customWallpaper + } else { + 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 case let .builtin(settings) = wallpaper { - var defaultPatternWallpaper: TelegramWallpaper? + if !wallpaper.isColorOrGradient && !ignoreDefaultWallpaper { + initialWallpaper = wallpaper + } - for wallpaper in wallpapers { - //JqSUrO0-mFIBAAAAWwTvLzoWGQI, 25 - if case let .file(file) = wallpaper, file.slug == "-Xc-np9y2VMCAAAARKr0yNNPYW0" { - defaultPatternWallpaper = wallpaper - break + if let initialBackgroundColor = strongSelf.initialBackgroundColor { + backgroundColors = (initialBackgroundColor, nil) + } else if !ignoreDefaultWallpaper { + extractWallpaperParameters(wallpaper) + } 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 { - backgroundColors = nil - } - - if let bubbleColors = settings.themeSpecificAccentColors[themeReference.index]?.customBubbleColors { - if let bottomColor = bubbleColors.1 { - messageColors = (bubbleColors.0, bottomColor) + 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 customWallpaper = settings.themeSpecificChatWallpapers[themeReference.index] { + 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 { - 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 + let theme = makePresentationTheme(mediaBox: strongSelf.context.sharedContext.accountManager.mediaBox, themeReference: themeReference)! + + accentColor = theme.rootController.navigationBar.accentTextColor + + let wallpaper = theme.chat.defaultWallpaper + extractWallpaperParameters(wallpaper) + + 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 { diff --git a/submodules/SettingsUI/Sources/Themes/ThemeColorPresets.swift b/submodules/SettingsUI/Sources/Themes/ThemeColorPresets.swift new file mode 100644 index 0000000000..712772cf0d --- /dev/null +++ b/submodules/SettingsUI/Sources/Themes/ThemeColorPresets.swift @@ -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) +] diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeGridControllerNode.swift index 181bcab3e2..0fd20ce0fe 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeGridControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeGridControllerNode.swift @@ -15,6 +15,7 @@ import PresentationDataUtils import AccountContext import SearchBarNode import SearchUI +import WallpaperResources struct ThemeGridControllerNodeState: Equatable { let editing: Bool diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift index 8ea90319d2..6b077cf72c 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift @@ -21,6 +21,7 @@ public enum ThemePreviewSource { case settings(PresentationThemeReference, TelegramWallpaper?) case theme(TelegramTheme) case slug(String, TelegramMediaFile) + case themeSettings(String, TelegramThemeSettings) case media(AnyMediaReference) } @@ -63,55 +64,61 @@ public final class ThemePreviewController: ViewController { var hasInstallsCount = false let themeName: String - if case let .theme(theme) = source { - themeName = theme.title - self.theme.set(.single(theme) - |> then( - getTheme(account: context.account, slug: theme.slug) + switch source { + case let .theme(theme): + themeName = theme.title + self.theme.set(.single(theme) + |> then( + getTheme(account: context.account, slug: theme.slug) + |> map(Optional.init) + |> `catch` { _ -> Signal 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) |> `catch` { _ -> Signal in return .single(nil) - } - |> filter { $0 != nil } - )) - hasInstallsCount = true - } else if case let .slug(slug, _) = source { - self.theme.set(getTheme(account: context.account, slug: slug) - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) - }) - themeName = previewTheme.name.string - - self.presentationTheme.set(.single(self.previewTheme) - |> then( - 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 in - guard let data = data, let presentationTheme = makePresentationTheme(data: data) else { - return .complete() + }) + themeName = previewTheme.name.string + + self.presentationTheme.set(.single(self.previewTheme) + |> then( + 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 in + guard let data = data, let presentationTheme = makePresentationTheme(data: data) else { + return .complete() + } + return .single(presentationTheme) } - 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 in + return .single(nil) + }) + themeName = theme.theme.title + hasInstallsCount = true + } else { + self.theme.set(.single(nil)) + themeName = previewTheme.name.string } - )) - hasInstallsCount = true - } else if case let .settings(themeReference, _) = source, case let .cloud(theme) = themeReference { - self.theme.set(getTheme(account: context.account, slug: theme.theme.slug) - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) - }) - themeName = previewTheme.name.string - hasInstallsCount = true - } else { - self.theme.set(.single(nil)) - themeName = previewTheme.name.string + default: + self.theme.set(.single(nil)) + themeName = previewTheme.name.string } var isPreview = false @@ -219,7 +226,7 @@ public final class ThemePreviewController: ViewController { switch self.source { case let .settings(reference, _): theme = .single(reference) - case .theme, .slug: + case .theme, .slug, .themeSettings: theme = combineLatest(self.theme.get() |> take(1), wallpaperPromise.get() |> take(1)) |> mapToSignal { theme, wallpaper -> Signal in if let theme = theme { @@ -440,7 +447,7 @@ public final class ThemePreviewController: ViewController { case let .theme(theme): subject = .url("https://t.me/addtheme/\(theme.slug)") preferredAction = .default - case let .slug(slug, _): + case let .slug(slug, _), let .themeSettings(slug, _): subject = .url("https://t.me/addtheme/\(slug)") preferredAction = .default case let .media(media): diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsAccentColorItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsAccentColorItem.swift index 19d0ffa585..462af36f40 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsAccentColorItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsAccentColorItem.swift @@ -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 { case let .color(_, themeReference, accentColor, selected): 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): return color.baseColor.color case .theme: - return nil + return .clear } } @@ -159,9 +159,9 @@ private class ThemeSettingsAccentColorIconItem: ListViewItem { let color: ThemeSettingsColorOption? let selected: Bool 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.color = color self.selected = selected @@ -307,7 +307,7 @@ private final class ThemeSettingsAccentColorIconItemNode : ListViewItemNode { gesture.cancel() 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) 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) @@ -593,16 +603,18 @@ class ThemeSettingsAccentColorItem: ListViewItem, ItemListItem { var sectionId: ItemListSectionId let theme: PresentationTheme + let generalThemeReference: PresentationThemeReference let themeReference: PresentationThemeReference let colors: [ThemeSettingsAccentColor] let currentColor: ThemeSettingsColorOption? let updated: (ThemeSettingsColorOption?) -> Void - let contextAction: ((PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void)? + let contextAction: ((Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void)? let openColorPicker: (Bool) -> Void 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.generalThemeReference = generalThemeReference self.themeReference = themeReference self.colors = colors self.currentColor = currentColor @@ -654,7 +666,7 @@ private struct ThemeSettingsAccentColorItemNodeTransition { 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 deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } @@ -852,7 +864,7 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode { switch color { case .default: 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): var selected = false if let currentColor = item.currentColor, case let .accentColor(accentColor) = currentColor { @@ -866,9 +878,9 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode { } switch accentColor { case let .accentColor(color): - entries.append(.color(index, item.themeReference, color, selected)) + entries.append(.color(index, item.generalThemeReference, color, selected)) 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): var selected = false @@ -881,7 +893,7 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode { if let currentColor = item.currentColor { selected = currentColor.index == theme.index } - entries.append(.theme(index, item.themeReference, theme, selected)) + entries.append(.theme(index, item.generalThemeReference, theme, selected)) } index += 1 @@ -908,9 +920,9 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode { 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 { - item.contextAction?(item.themeReference, color, node, gesture) + item.contextAction?(selected, item.themeReference, color, node, gesture) } } let openColorPicker: (Bool) -> Void = { [weak self] create in @@ -920,7 +932,7 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode { } 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) strongSelf.enqueueTransition(transition) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift index 2052e713ff..c2a271b38a 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift @@ -81,9 +81,9 @@ private final class ThemeSettingsControllerArguments { let selectAppIcon: (String) -> Void let editTheme: (PresentationCloudTheme) -> 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.selectTheme = selectTheme self.selectFontSize = selectFontSize @@ -133,7 +133,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { case fontSize(PresentationTheme, PresentationFontSize) case chatPreview(PresentationTheme, PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, [ChatPreviewMessageItem]) 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 textSize(PresentationTheme, String, String) case themeItem(PresentationTheme, PresentationStrings, [PresentationThemeReference], PresentationThemeReference, [Int64: PresentationThemeAccentColor], [Int64: TelegramWallpaper], PresentationThemeAccentColor?) @@ -208,8 +208,8 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { } else { return false } - case let .accentColor(lhsTheme, lhsCurrentTheme, lhsCustomColors, lhsThemes, lhsColor): - if case let .accentColor(rhsTheme, rhsCurrentTheme, rhsCustomColors, rhsThemes, rhsColor) = rhs, lhsTheme === rhsTheme, lhsCurrentTheme == rhsCurrentTheme, lhsCustomColors == rhsCustomColors, lhsThemes == rhsThemes, lhsColor == rhsColor { + case let .accentColor(lhsTheme, lhsGeneralTheme, lhsCurrentTheme, lhsCustomColors, lhsThemes, lhsColor): + if case let .accentColor(rhsTheme, rhsGeneralTheme, rhsCurrentTheme, rhsCustomColors, rhsThemes, rhsColor) = rhs, lhsTheme === rhsTheme, lhsCurrentTheme == rhsCurrentTheme, lhsCustomColors == rhsCustomColors, lhsThemes == rhsThemes, lhsColor == rhsColor { return true } else { return false @@ -308,20 +308,13 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: { arguments.openWallpaperSettings() }) - case let .accentColor(theme, currentTheme, customColors, themes, color): + case let .accentColor(theme, generalThemeReference, currentTheme, customColors, themes, color): var colorItems: [ThemeSettingsAccentColor] = [] for theme in themes { 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 colors = PresentationThemeBaseColor.allCases 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)) } - colorItems.append(.preset(PresentationThemeAccentColor(index: 104, baseColor: .preset, accentColor: 0x5a9e29, bubbleColors: (0xdcf8c6, nil), wallpaper: patternWallpaper("R3j69wKskFIBAAAAoUdXWCKMzCM", 0xede6dd, nil, 50, nil)))) - 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(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)))) + for preset in dayClassicColorPresets { + colorItems.append(.preset(preset)) + } } else if name == .day { colorItems.append(.color(.blue)) colors = colors.filter { $0 != .blue } - colorItems.append(.preset(PresentationThemeAccentColor(index: 101, baseColor: .preset, accentColor: 0x007aff, bubbleColors: (0x007aff, 0xff53f4), wallpaper: nil))) - colorItems.append(.preset(PresentationThemeAccentColor(index: 102, baseColor: .preset, accentColor: 0x00b09b, bubbleColors: (0xaee946, 0x00b09b), wallpaper: nil))) - 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))) + for preset in dayColorPresets { + colorItems.append(.preset(preset)) + } } else if name == .night { colorItems.append(.color(.blue)) colors = colors.filter { $0 != .blue } - colorItems.append(.preset(PresentationThemeAccentColor(index: 101, baseColor: .preset, accentColor: 0x007aff, bubbleColors: (0x007aff, 0xff53f4), wallpaper: nil))) - colorItems.append(.preset(PresentationThemeAccentColor(index: 102, baseColor: .preset, accentColor: 0x00b09b, bubbleColors: (0xaee946, 0x00b09b), wallpaper: nil))) - 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))) + for preset in nightColorPresets { + colorItems.append(.preset(preset)) + } } if name != .day { colors = colors.filter { $0 != .black } @@ -371,14 +359,14 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { colorItems.append(contentsOf: colors.map { .color($0) }) 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 { // if let currentColor = currentColor, currentColor.baseColor == .custom { // 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 { switch color { case let .accentColor(color): @@ -389,10 +377,10 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { } else { arguments.selectAccentColor(nil) } - }, contextAction: { theme, color, node, gesture in - arguments.colorContextAction(theme, color, node, gesture) + }, contextAction: { isCurrent, theme, color, node, gesture in + arguments.colorContextAction(isCurrent, theme, color, node, gesture) }, openColorPicker: { create in - arguments.openAccentColorPicker(generalThemeReference, create) + arguments.openAccentColorPicker(currentTheme, create) }, tag: ThemeSettingsEntryTag.accentColor) 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: { @@ -479,7 +467,7 @@ private func themeSettingsControllerEntries(presentationData: PresentationData, 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)) @@ -615,18 +603,27 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The 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 } var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers var themeSpecificAccentColors = current.themeSpecificAccentColors - themeSpecificAccentColors[generalThemeReference.index] = accentColor + themeSpecificAccentColors[generalThemeReference.index] = accentColor?.withUpdatedWallpaper(presetWallpaper) if case let .builtin(theme) = generalThemeReference { - if let wallpaper = presetWallpaper, let color = accentColor { - themeSpecificChatWallpapers[coloredThemeIndex(reference: currentTheme, accentColor: color)] = wallpaper - } else if let wallpaper = current.themeSpecificChatWallpapers[currentTheme.index], wallpaper.isColorOrGradient || wallpaper.isPattern || wallpaper.isBuiltin { + if let wallpaper = current.themeSpecificChatWallpapers[coloredThemeIndex(reference: currentTheme, accentColor: accentColor)], wallpaper.isColorOrGradient || wallpaper.isPattern || wallpaper.isBuiltin { themeSpecificChatWallpapers[currentTheme.index] = nil if let accentColor = accentColor { 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() 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) 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 settings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings) as? PresentationThemeSettings ?? PresentationThemeSettings.defaultSettings var wallpaper: TelegramWallpaper? @@ -849,6 +846,13 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The } return (accentColor, wallpaper) } |> 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 if let wallpaper = wallpaper { effectiveWallpaper = wallpaper @@ -857,16 +861,30 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The if let accentColor = accentColor, case let .theme(themeReference) = accentColor { theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: themeReference) } 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()) } - return chatServiceBackgroundColor(wallpaper: effectiveWallpaper, mediaBox: context.sharedContext.accountManager.mediaBox) + + let wallpaperSignal: Signal + 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 if let accentColor = accentColor, case let .theme(themeReference) = accentColor { return (makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: themeReference, serviceBackgroundColor: serviceBackgroundColor), wallpaper) } 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 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 + 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 { 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 @@ -1118,7 +1229,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The }) }).start() - presentCrossfadeControllerImpl?(cloudTheme == nil) + presentCrossfadeControllerImpl?(cloudTheme == nil || cloudTheme?.settings != nil) } moreImpl = { let presentationData = context.sharedContext.currentPresentationData.with { $0 } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsThemeItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsThemeItem.swift index 2532ef6e4b..e177537d76 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsThemeItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsThemeItem.swift @@ -602,10 +602,7 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode { } let title = themeDisplayName(strings: item.strings, reference: theme) let accentColor = item.themeSpecificAccentColors[theme.index] - var wallpaper: TelegramWallpaper? - if let accentColor = accentColor { - wallpaper = item.themeSpecificChatWallpapers[coloredThemeIndex(reference: theme, accentColor: accentColor)] - } + let wallpaper = accentColor?.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 } diff --git a/submodules/SyncCore/Sources/TelegramMediaWebpage.swift b/submodules/SyncCore/Sources/TelegramMediaWebpage.swift index 9a0ef4d6d9..2e6eba6cbe 100644 --- a/submodules/SyncCore/Sources/TelegramMediaWebpage.swift +++ b/submodules/SyncCore/Sources/TelegramMediaWebpage.swift @@ -1,5 +1,74 @@ 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 let url: String public let displayUrl: String @@ -16,10 +85,10 @@ public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable { public let image: TelegramMediaImage? public let file: TelegramMediaFile? - public let files: [TelegramMediaFile]? + public let attributes: [TelegramMediaWebpageAttribute] 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.displayUrl = displayUrl self.hash = hash @@ -34,7 +103,7 @@ public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable { self.author = author self.image = image self.file = file - self.files = files + self.attributes = attributes self.instantPage = instantPage } @@ -72,7 +141,14 @@ public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable { 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 { self.instantPage = instantPage @@ -142,11 +218,9 @@ public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable { } else { encoder.encodeNil(forKey: "fi") } - if let files = self.files { - encoder.encodeObjectArray(files, forKey: "fis") - } else { - encoder.encodeNil(forKey: "fis") - } + + encoder.encodeObjectArray(self.attributes, forKey: "attr") + if let instantPage = self.instantPage { encoder.encodeObject(instantPage, forKey: "ip") } else { @@ -187,18 +261,14 @@ public func ==(lhs: TelegramMediaWebpageLoadedContent, rhs: TelegramMediaWebpage return false } - if let lhsFiles = lhs.files, let rhsFiles = rhs.files { - if lhsFiles.count != rhsFiles.count { - return false - } else { - for i in 0 ..< lhsFiles.count { - if !lhsFiles[i].isEqual(to: rhsFiles[i]) { - return false - } + if lhs.attributes.count != rhs.attributes.count { + return false + } else { + for i in 0 ..< lhs.attributes.count { + if lhs.attributes[i] != rhs.attributes[i] { + return false } } - } else if (lhs.files == nil) != (rhs.files == nil) { - return false } if lhs.instantPage != rhs.instantPage { diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 2594fb50f0..5755ee2af2 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -306,7 +306,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-350980120] = { return Api.WebPage.parse_webPageEmpty($0) } dict[-981018084] = { return Api.WebPage.parse_webPagePending($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[-190472735] = { return Api.InputBotInlineMessage.parse_inputBotInlineMessageMediaGeo($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[1251549527] = { return Api.InputStickeredMedia.parse_inputStickeredMediaPhoto($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[-123893531] = { return Api.messages.FeaturedStickers.parse_featuredStickers($0) } dict[1375940666] = { return Api.auth.LoginTokenInfo.parse_loginTokenInfo($0) } @@ -1258,6 +1259,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.InputStickeredMedia: _1.serialize(buffer, boxed) + case let _1 as Api.WebPageAttribute: + _1.serialize(buffer, boxed) case let _1 as Api.messages.FeaturedStickers: _1.serialize(buffer, boxed) case let _1 as Api.auth.LoginTokenInfo: diff --git a/submodules/TelegramApi/Sources/Api1.swift b/submodules/TelegramApi/Sources/Api1.swift index bb5222a361..caacbb6606 100644 --- a/submodules/TelegramApi/Sources/Api1.swift +++ b/submodules/TelegramApi/Sources/Api1.swift @@ -9227,7 +9227,7 @@ public extension Api { case webPageEmpty(id: Int64) case webPagePending(id: Int64, date: Int32) 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) { switch self { @@ -9250,9 +9250,9 @@ public extension Api { } 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 { - buffer.appendInt32(-94051982) + buffer.appendInt32(-392411726) } serializeInt32(flags, 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 << 8) != 0 {serializeString(author!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 9) != 0 {document!.serialize(buffer, true)} - if Int(flags) & Int(1 << 11) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(documents!.count)) - for item in documents! { + if Int(flags) & Int(1 << 10) != 0 {cachedPage!.serialize(buffer, true)} + if Int(flags) & Int(1 << 12) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(attributes!.count)) + for item in attributes! { item.serialize(buffer, true) }} - if Int(flags) & Int(1 << 10) != 0 {cachedPage!.serialize(buffer, true)} break } } @@ -9289,8 +9289,8 @@ public extension Api { return ("webPagePending", [("id", id), ("date", date)]) case .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): - 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)]) + 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), ("cachedPage", cachedPage), ("attributes", attributes)]) } } @@ -9361,13 +9361,13 @@ public extension Api { if Int(_1!) & Int(1 << 9) != 0 {if let signature = reader.readInt32() { _17 = Api.parse(reader, signature: signature) as? Api.Document } } - var _18: [Api.Document]? - 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? + var _18: Api.Page? 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 _c2 = _2 != nil @@ -9386,10 +9386,10 @@ public extension Api { let _c15 = (Int(_1!) & Int(1 << 7) == 0) || _15 != nil let _c16 = (Int(_1!) & Int(1 << 8) == 0) || _16 != nil let _c17 = (Int(_1!) & Int(1 << 9) == 0) || _17 != nil - let _c18 = (Int(_1!) & Int(1 << 11) == 0) || _18 != nil - let _c19 = (Int(_1!) & Int(1 << 10) == 0) || _19 != nil + let _c18 = (Int(_1!) & Int(1 << 10) == 0) || _18 != 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 { - 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 { 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 { case phoneCallDiscardReasonMissed diff --git a/submodules/TelegramApi/Sources/Api3.swift b/submodules/TelegramApi/Sources/Api3.swift index a0ac28fdb8..7dadc1cf31 100644 --- a/submodules/TelegramApi/Sources/Api3.swift +++ b/submodules/TelegramApi/Sources/Api3.swift @@ -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) { + public static func createTheme(flags: Int32, slug: String, title: String, document: Api.InputDocument?, settings: Api.InputThemeSettings?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-1683113716) + buffer.appendInt32(-2077048289) serializeInt32(flags, buffer: buffer, boxed: false) serializeString(slug, 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)} 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) diff --git a/submodules/TelegramCore/Sources/AccountManager.swift b/submodules/TelegramCore/Sources/AccountManager.swift index e6caab77e9..05576c23b2 100644 --- a/submodules/TelegramCore/Sources/AccountManager.swift +++ b/submodules/TelegramCore/Sources/AccountManager.swift @@ -150,6 +150,7 @@ private var declaredEncodables: Void = { declareEncodable(SendScheduledMessageImmediatelyAction.self, f: { SendScheduledMessageImmediatelyAction(decoder: $0) }) declareEncodable(WalletCollection.self, f: { WalletCollection(decoder: $0) }) declareEncodable(EmbeddedMediaStickersMessageAttribute.self, f: { EmbeddedMediaStickersMessageAttribute(decoder: $0) }) + declareEncodable(TelegramMediaWebpageAttribute.self, f: { TelegramMediaWebpageAttribute(decoder: $0) }) return }() diff --git a/submodules/TelegramCore/Sources/Serialization.swift b/submodules/TelegramCore/Sources/Serialization.swift index 42be89fba0..f0a22a6eb7 100644 --- a/submodules/TelegramCore/Sources/Serialization.swift +++ b/submodules/TelegramCore/Sources/Serialization.swift @@ -210,7 +210,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 107 + return 108 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramCore/Sources/TelegramMediaWebpage.swift b/submodules/TelegramCore/Sources/TelegramMediaWebpage.swift index 371b2dde24..3c50ba95b5 100644 --- a/submodules/TelegramCore/Sources/TelegramMediaWebpage.swift +++ b/submodules/TelegramCore/Sources/TelegramMediaWebpage.swift @@ -4,13 +4,24 @@ import TelegramApi 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? { switch webpage { case .webPageNotModified: return nil case let .webPagePending(id, date): 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? if let embedWidth = embedWidth, let embedHeight = embedHeight { embedSize = PixelDimensions(width: embedWidth, height: embedHeight) @@ -27,15 +38,15 @@ func telegramMediaWebpageFromApiWebpage(_ webpage: Api.WebPage, url: String?) -> if let document = document { file = telegramMediaFileFromApiDocument(document) } - var files: [TelegramMediaFile]? - if let documents = documents { - files = documents.compactMap(telegramMediaFileFromApiDocument) + var webpageAttributes: [TelegramMediaWebpageAttribute] = [] + if let attributes = attributes { + webpageAttributes = attributes.compactMap(telegramMediaWebpageAttributeFromApiWebpageAttribute) } var instantPage: InstantPage? if let cachedPage = 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: return nil } diff --git a/submodules/TelegramCore/Sources/Themes.swift b/submodules/TelegramCore/Sources/Themes.swift index 4c5b653e1c..7ef73119a3 100644 --- a/submodules/TelegramCore/Sources/Themes.swift +++ b/submodules/TelegramCore/Sources/Themes.swift @@ -279,56 +279,91 @@ public enum CreateThemeResult { case progress(Float) } -public func createTheme(account: Account, title: String, resource: MediaResource, thumbnailData: Data? = nil, settings: TelegramThemeSettings?) -> Signal { +public func createTheme(account: Account, title: String, resource: MediaResource? = nil, thumbnailData: Data? = nil, settings: TelegramThemeSettings?) -> Signal { var flags: Int32 = 0 var inputSettings: Api.InputThemeSettings? + if let _ = resource { + flags |= 1 << 2 + } if let settings = settings { flags |= 1 << 3 inputSettings = settings.apiInputThemeSettings } - return uploadTheme(account: account, resource: resource, thumbnailData: thumbnailData) - |> mapError { _ in return CreateThemeError.generic } - |> mapToSignal { result -> Signal in - switch result { - case let .complete(file): - if let resource = file.resource as? CloudDocumentMediaResource { - 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)) - |> mapError { error in - if error.errorDescription == "THEME_SLUG_INVALID" { - return .slugInvalid - } else if error.errorDescription == "THEME_SLUG_OCCUPIED" { - return .slugOccupied - } - return .generic - } - |> mapToSignal { apiTheme -> Signal 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) + if let resource = resource { + return uploadTheme(account: account, resource: resource, thumbnailData: thumbnailData) + |> mapError { _ in return CreateThemeError.generic } + |> mapToSignal { result -> Signal in + switch result { + case let .complete(file): + if let resource = file.resource as? CloudDocumentMediaResource { + 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)) + |> mapError { error in + if error.errorDescription == "THEME_SLUG_INVALID" { + return .slugInvalid + } else if error.errorDescription == "THEME_SLUG_OCCUPIED" { + return .slugOccupied + } + return .generic + } + |> mapToSignal { apiTheme -> Signal 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) } - |> 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 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) } - case let .progress(progress): - return .single(.progress(progress)) } } } @@ -350,6 +385,7 @@ public func updateTheme(account: Account, accountManager: AccountManager, theme: var inputSettings: Api.InputThemeSettings? if let settings = settings { flags |= 1 << 3 + inputSettings = settings.apiInputThemeSettings } let uploadSignal: Signal if let resource = resource { @@ -379,7 +415,7 @@ public func updateTheme(account: Account, accountManager: AccountManager, theme: 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 if error.errorDescription == "THEME_SLUG_INVALID" { return .slugInvalid diff --git a/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift index 9dc9e64c13..2f33bcbd4e 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift @@ -129,7 +129,7 @@ public func customizeDefaultDarkPresentationTheme(theme: PresentationTheme, edit outgoingSecondaryTextColor = UIColor(rgb: 0xffffff, alpha: 0.5) outgoingLinkTextColor = 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))), infoPrimaryTextColor: 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), mediaDateAndStatusTextColor: UIColor(rgb: 0xffffff), shareButtonFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)), diff --git a/submodules/TelegramPresentationData/Sources/MakePresentationTheme.swift b/submodules/TelegramPresentationData/Sources/MakePresentationTheme.swift index 8a72021977..db56eab238 100644 --- a/submodules/TelegramPresentationData/Sources/MakePresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/MakePresentationTheme.swift @@ -48,7 +48,13 @@ public func makePresentationTheme(mediaBox: MediaBox, themeReference: Presentati return nil } 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) } else { return nil diff --git a/submodules/TelegramPresentationData/Sources/PresentationData.swift b/submodules/TelegramPresentationData/Sources/PresentationData.swift index 3ea831eee4..bade3aa869 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationData.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationData.swift @@ -516,14 +516,14 @@ public func updatedPresentationData(accountManager: AccountManager, applicationI let contactSettings: ContactSynchronizationSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.contactSynchronizationSettings] as? ContactSynchronizationSettings ?? ContactSynchronizationSettings.defaultSettings - let effectiveColors = themeSettings.themeSpecificAccentColors[themeSettings.theme.index] - let themeSpecificWallpaper = (themeSettings.themeSpecificChatWallpapers[coloredThemeIndex(reference: themeSettings.theme, accentColor: effectiveColors)] ?? themeSettings.themeSpecificChatWallpapers[themeSettings.theme.index]) + let currentColors = themeSettings.themeSpecificAccentColors[themeSettings.theme.index] + let themeSpecificWallpaper = (themeSettings.themeSpecificChatWallpapers[coloredThemeIndex(reference: themeSettings.theme, accentColor: currentColors)] ?? themeSettings.themeSpecificChatWallpapers[themeSettings.theme.index]) let currentWallpaper: TelegramWallpaper if let themeSpecificWallpaper = themeSpecificWallpaper { currentWallpaper = themeSpecificWallpaper } else { - let theme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: themeSettings.theme, accentColor: 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 } @@ -537,12 +537,13 @@ public func updatedPresentationData(accountManager: AccountManager, applicationI |> distinctUntilChanged |> map { autoNightModeTriggered in var effectiveTheme: PresentationThemeReference - var effectiveChatWallpaper: TelegramWallpaper = currentWallpaper + var effectiveChatWallpaper = currentWallpaper + var effectiveColors = currentColors var switchedToNightModeWallpaper = false if autoNightModeTriggered { 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]) if let themeSpecificWallpaper = themeSpecificWallpaper { @@ -554,8 +555,7 @@ public func updatedPresentationData(accountManager: AccountManager, applicationI effectiveTheme = themeSettings.theme } - let effectiveColors = themeSettings.themeSpecificAccentColors[effectiveTheme.index] - let themeValue = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: effectiveTheme, accentColor: effectiveColors?.color, bubbleColors: effectiveColors?.customBubbleColors, serviceBackgroundColor: serviceBackgroundColor) ?? defaultPresentationTheme + let themeValue = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: effectiveTheme, accentColor: effectiveColors?.color, bubbleColors: effectiveColors?.customBubbleColors, wallpaper: effectiveColors?.wallpaper, serviceBackgroundColor: serviceBackgroundColor) ?? defaultPresentationTheme if autoNightModeTriggered && !switchedToNightModeWallpaper { switch effectiveChatWallpaper { diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveMediaNode.swift index f7cedeb5c9..8207cb980e 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveMediaNode.swift @@ -322,6 +322,8 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio } else { 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: unboundSize = CGSize(width: 128.0, height: 128.0) } @@ -439,13 +441,15 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio } else { emptyColor = message.effectivelyIncoming(context.account.peerId) ? theme.chat.message.incoming.mediaPlaceholderColor : theme.chat.message.outgoing.mediaPlaceholderColor } - if let wallpaper = media as? WallpaperPreviewMedia, case let .file(_, patternColor, patternBottomColor, rotation, _, _) = wallpaper.content { - var colors: [UIColor] = [] - colors.append(patternColor ?? UIColor(rgb: 0xd6e2ee, alpha: 0.5)) - if let patternBottomColor = patternBottomColor { - colors.append(patternBottomColor) + if let wallpaper = media as? WallpaperPreviewMedia { + if case let .file(_, patternColor, patternBottomColor, rotation, _, _) = wallpaper.content { + var colors: [UIColor] = [] + colors.append(patternColor ?? UIColor(rgb: 0xd6e2ee, alpha: 0.5)) + if let patternBottomColor = patternBottomColor { + colors.append(patternBottomColor) + } + patternArguments = PatternWallpaperArguments(colors: colors, rotation: rotation) } - patternArguments = PatternWallpaperArguments(colors: colors, rotation: rotation) } if mediaUpdated || isSendingUpdated || automaticPlaybackUpdated { @@ -580,7 +584,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio switch wallpaper.content { case let .file(file, _, _, _, 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 { 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" { @@ -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) } } + case let .themeSettings(settings): + return themeImage(account: context.account, accountManager: context.sharedContext.accountManager, source: .settings(settings)) case let .color(color): return solidColorImage(color) case let .gradient(topColor, bottomColor, rotation): @@ -604,6 +610,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio }, cancel: { messageMediaFileCancelInteractiveFetch(context: context, messageId: message.id, file: file) }) + } else if case .themeSettings = wallpaper.content { } else { boundingSize = CGSize(width: boundingSize.width, height: boundingSize.width) } @@ -645,7 +652,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio |> map { resourceStatus -> (MediaResourceStatus, MediaResourceStatus?) in return (resourceStatus, nil) } - case .color, .gradient: + case .themeSettings, .color, .gradient: updatedStatusSignal = .single((.Local, nil)) } } diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageWebpageBubbleContentNode.swift index 544094c941..a8c5eed503 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageWebpageBubbleContentNode.swift @@ -310,21 +310,31 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { } } else if type == "telegram_theme" { var file: TelegramMediaFile? + var settings: TelegramThemeSettings? var isSupported = false - if let contentFiles = webpage.files { - if let filteredFile = contentFiles.filter({ $0.mimeType == themeMimeType }).first { - isSupported = true - file = filteredFile - } else { - file = contentFiles.first + + for attribute in webpage.attributes { + if case let .theme(attribute) = attribute { + if let attributeSettings = attribute.settings { + settings = attributeSettings + 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 file = contentFile } if let file = file { let media = WallpaperPreviewMedia(content: .file(file, nil, nil, nil, true, isSupported)) mediaAndFlags = (media, ChatMessageAttachedContentNodeMediaFlags()) + } else if let settings = settings { + let media = WallpaperPreviewMedia(content: .themeSettings(settings)) + mediaAndFlags = (media, ChatMessageAttachedContentNodeMediaFlags()) } } } diff --git a/submodules/TelegramUI/TelegramUI/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/TelegramUI/ChatRecentActionsControllerNode.swift index a3cc4c128b..ff2333c5a4 100644 --- a/submodules/TelegramUI/TelegramUI/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatRecentActionsControllerNode.swift @@ -812,6 +812,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { break case .wallet: break + case .settings: + break } } })) diff --git a/submodules/TelegramUI/TelegramUI/OpenChatMessage.swift b/submodules/TelegramUI/TelegramUI/OpenChatMessage.swift index 70243dfaa8..f6d45833ca 100644 --- a/submodules/TelegramUI/TelegramUI/OpenChatMessage.swift +++ b/submodules/TelegramUI/TelegramUI/OpenChatMessage.swift @@ -508,20 +508,39 @@ func openChatTheme(context: AccountContext, message: Message, pushController: @e let _ = (context.sharedContext.resolveUrl(account: context.account, url: content.url) |> deliverOnMainQueue).start(next: { resolvedUrl in var file: TelegramMediaFile? - let mimeType = "application/x-tgtheme-ios" - if let contentFiles = content.files, let filteredFile = contentFiles.filter({ $0.mimeType == mimeType }).first { - file = filteredFile - } else if let contentFile = content.file, contentFile.mimeType == mimeType { + var settings: TelegramThemeSettings? + let themeMimeType = "application/x-tgtheme-ios" + + 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 } let displayUnsupportedAlert: () -> Void = { 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) } - if case let .theme(slug) = resolvedUrl, let file = file { - if let path = context.sharedContext.accountManager.mediaBox.completedResourcePath(file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { - if let theme = makePresentationTheme(data: data) { - let controller = ThemePreviewController(context: context, previewTheme: theme, source: .slug(slug, file)) + if case let .theme(slug) = resolvedUrl { + if let file = file { + if let path = context.sharedContext.accountManager.mediaBox.completedResourcePath(file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { + 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) } else { displayUnsupportedAlert() diff --git a/submodules/TelegramUI/TelegramUI/OpenResolvedUrl.swift b/submodules/TelegramUI/TelegramUI/OpenResolvedUrl.swift index 851f0b1a44..1ca413602c 100644 --- a/submodules/TelegramUI/TelegramUI/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/TelegramUI/OpenResolvedUrl.swift @@ -281,12 +281,13 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur case let .theme(slug): let presentationData = context.sharedContext.currentPresentationData.with { $0 } let signal = getTheme(account: context.account, slug: slug) - |> mapToSignal { themeInfo -> Signal<(Data, TelegramTheme), GetThemeError> in - return Signal<(Data, TelegramTheme), GetThemeError> { subscriber in + |> mapToSignal { themeInfo -> Signal<(Data?, TelegramThemeSettings?, TelegramTheme), GetThemeError> in + return Signal<(Data?, TelegramThemeSettings?, TelegramTheme), GetThemeError> { subscriber in let disposables = DisposableSet() - let resource = themeInfo.file?.resource - - if let resource = resource { + if let settings = themeInfo.settings { + subscriber.putNext((nil, settings, themeInfo)) + subscriber.putCompletion() + } else if let resource = themeInfo.file?.resource { 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) @@ -309,7 +310,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur disposables.add(maybeFetched.start(next: { data in if let data = data { - subscriber.putNext((data, themeInfo)) + subscriber.putNext((data, nil, themeInfo)) subscriber.putCompletion() } })) @@ -348,9 +349,16 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur } } |> deliverOnMainQueue).start(next: { dataAndTheme in - if let theme = makePresentationTheme(data: dataAndTheme.0) { - let previewController = ThemePreviewController(context: context, previewTheme: theme, source: .theme(dataAndTheme.1)) - navigationController?.pushViewController(previewController) + if let data = dataAndTheme.0 { + if let theme = makePresentationTheme(data: data) { + 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 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 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 + } } } diff --git a/submodules/TelegramUI/TelegramUI/OpenUrl.swift b/submodules/TelegramUI/TelegramUI/OpenUrl.swift index 6b83ad1959..b7f18394e6 100644 --- a/submodules/TelegramUI/TelegramUI/OpenUrl.swift +++ b/submodules/TelegramUI/TelegramUI/OpenUrl.swift @@ -200,7 +200,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur } let continueHandling: () -> Void = { - let handleRevolvedUrl: (ResolvedUrl) -> Void = { resolved in + let handleResolvedUrl: (ResolvedUrl) -> Void = { resolved in if case let .externalUrl(value) = resolved { context.sharedContext.applicationBindings.openUrl(value) } else { @@ -243,416 +243,435 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur let handleInternalUrl: (String) -> Void = { url in 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? - if parsedUrl.host == "localpeer" { - if let components = URLComponents(string: "/?" + query) { - var peerId: PeerId? - if let queryItems = components.queryItems { - for queryItem in queryItems { - if let value = queryItem.value { - 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 query = parsedUrl.query { + if parsedUrl.host == "localpeer" { + if let components = URLComponents(string: "/?" + query) { + var peerId: PeerId? + if let queryItems = components.queryItems { + for queryItem in queryItems { + if let value = queryItem.value { + 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))) + } } - - let valid: Bool - if parsedUrl.host == "resolve" { - if domain == "telegrampassport" { - valid = true + } 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 { + 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 { - 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("}") { - opaquePayload = Data() - if opaqueNonce.isEmpty { + + if valid { + if let botId = botId, let scope = scope, let publicKey = publicKey, let callbackUrl = callbackUrl { + if scope.hasPrefix("{") && scope.hasSuffix("}") { + opaquePayload = Data() + if opaqueNonce.isEmpty { + return + } + } else if opaquePayload.isEmpty { return } - } else if opaquePayload.isEmpty { - return - } - if case .chat = urlContext { - 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() + if case .chat = urlContext { + 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)) - navigationController.view.window?.endEditing(true) - context.sharedContext.applicationBindings.getWindowHost()?.present(controller, on: .root, blockInteraction: false, completion: {}) + if let navigationController = navigationController { + 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" { - if let components = URLComponents(string: "/?" + query) { - var id: String? - if let queryItems = components.queryItems { - for queryItem in queryItems { - if let value = queryItem.value { - if queryItem.name == "id" { - id = value + } else if parsedUrl.host == "user" { + if let components = URLComponents(string: "/?" + query) { + var id: String? + if let queryItems = components.queryItems { + for queryItem in queryItems { + if let value = queryItem.value { + if queryItem.name == "id" { + id = value + } } } } - } - - if let id = id, !id.isEmpty, let idValue = Int32(id), idValue > 0 { - let _ = (context.account.postbox.transaction { transaction -> Peer? in - 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) + + if let id = id, !id.isEmpty, let idValue = Int32(id), idValue > 0 { + let _ = (context.account.postbox.transaction { transaction -> Peer? in + return transaction.getPeer(PeerId(namespace: Namespaces.Peer.CloudUser, id: idValue)) } - }) - return - } - } - } 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 + |> deliverOnMainQueue).start(next: { peer in + if let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic) { + navigationController?.pushViewController(controller) } - } - if queryItem.name == "token" { - isToken = true - } + }) + return } } - 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)" - } - } - } else if parsedUrl.host == "confirmphone" { - if let components = URLComponents(string: "/?" + query) { - 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 + } 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" { + isToken = true } } } + 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 { - convertedUrl = "https://t.me/confirmphone?phone=\(phone)&hash=\(hash)" - } - } - } else if parsedUrl.host == "bg" { - if let components = URLComponents(string: "/?" + query) { - var parameter: String? - var query: [String] = [] - if let queryItems = components.queryItems { - for queryItem in queryItems { - if let value = queryItem.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)") + } else if parsedUrl.host == "confirmphone" { + if let components = URLComponents(string: "/?" + query) { + 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 let phone = phone, let hash = hash { + convertedUrl = "https://t.me/confirmphone?phone=\(phone)&hash=\(hash)" + } } - var queryString = "" - if !query.isEmpty { - queryString = "?\(query.joined(separator: "&"))" - } - if let parameter = parameter { - convertedUrl = "https://t.me/bg/\(parameter)\(queryString)" - } - } - } else if parsedUrl.host == "addtheme" { - if let components = URLComponents(string: "/?" + query) { - var parameter: String? - if let queryItems = components.queryItems { - for queryItem in queryItems { - if let value = queryItem.value { - if queryItem.name == "slug" { - parameter = value + } else if parsedUrl.host == "bg" { + if let components = URLComponents(string: "/?" + query) { + var parameter: String? + var query: [String] = [] + if let queryItems = components.queryItems { + for queryItem in queryItems { + if let value = queryItem.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)") + } } } } + var queryString = "" + if !query.isEmpty { + queryString = "?\(query.joined(separator: "&"))" + } + if let parameter = parameter { + convertedUrl = "https://t.me/bg/\(parameter)\(queryString)" + } } - if let parameter = parameter { - convertedUrl = "https://t.me/addtheme/\(parameter)" - } - } - } - - if parsedUrl.host == "resolve" { - if let components = URLComponents(string: "/?" + query) { - 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 + } else if parsedUrl.host == "addtheme" { + if let components = URLComponents(string: "/?" + query) { + var parameter: String? + if let queryItems = components.queryItems { + for queryItem in queryItems { + if let value = queryItem.value { + if queryItem.name == "slug" { + parameter = value + } } } } - } - - if let domain = domain { - var result = "https://t.me/\(domain)" - if let post = post, let postValue = Int(post) { - result += "/\(postValue)" + if let parameter = parameter { + convertedUrl = "https://t.me/addtheme/\(parameter)" } - 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) { - var host: String? - if let queryItems = components.queryItems { - for queryItem in queryItems { - if let value = queryItem.value { - if queryItem.name == "host" { - host = value + + if parsedUrl.host == "resolve" { + if let components = URLComponents(string: "/?" + query) { + 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 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 { - 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 == "hostOverride" { + if let components = URLComponents(string: "/?" + query) { + var host: String? + if let queryItems = components.queryItems { + for queryItem in queryItems { + if let value = queryItem.value { + 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)) + } } } } diff --git a/submodules/TelegramUI/TelegramUI/WallpaperPreviewMedia.swift b/submodules/TelegramUI/TelegramUI/WallpaperPreviewMedia.swift index 8773fec6c8..6d6433875d 100644 --- a/submodules/TelegramUI/TelegramUI/WallpaperPreviewMedia.swift +++ b/submodules/TelegramUI/TelegramUI/WallpaperPreviewMedia.swift @@ -8,6 +8,7 @@ enum WallpaperPreviewMediaContent: Equatable { case file(TelegramMediaFile, UIColor?, UIColor?, Int32?, Bool, Bool) case color(UIColor) case gradient(UIColor, UIColor, Int32?) + case themeSettings(TelegramThemeSettings) } final class WallpaperPreviewMedia: Media { diff --git a/submodules/TelegramUI/TelegramUI/WebpagePreviewAccessoryPanelNode.swift b/submodules/TelegramUI/TelegramUI/WebpagePreviewAccessoryPanelNode.swift index 8518d7a1e4..9344843603 100644 --- a/submodules/TelegramUI/TelegramUI/WebpagePreviewAccessoryPanelNode.swift +++ b/submodules/TelegramUI/TelegramUI/WebpagePreviewAccessoryPanelNode.swift @@ -117,7 +117,7 @@ final class WebpagePreviewAccessoryPanelNode: AccessoryPanelNode { } else { 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 } else if let _ = content.image { text = stringForMediaKind(.image, strings: self.strings).0 diff --git a/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift b/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift index 55d4fe6165..2ec4c2b19a 100644 --- a/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift @@ -456,6 +456,7 @@ public struct PresentationThemeAccentColor: PostboxCoding, Equatable { } else { self.bubbleColors = nil } + self.wallpaper = decoder.decodeObjectForKey("w", decoder: { TelegramWallpaper(decoder: $0) }) as? TelegramWallpaper } public func encode(_ encoder: PostboxEncoder) { @@ -477,6 +478,11 @@ public struct PresentationThemeAccentColor: PostboxCoding, Equatable { encoder.encodeNil(forKey: "bt") encoder.encodeNil(forKey: "bb") } + if let wallpaper = self.wallpaper { + encoder.encodeObject(wallpaper, forKey: "w") + } else { + encoder.encodeNil(forKey: "w") + } } public var color: UIColor { @@ -510,6 +516,10 @@ public struct PresentationThemeAccentColor: PostboxCoding, Equatable { 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 { diff --git a/submodules/WallpaperResources/Sources/WallpaperResources.swift b/submodules/WallpaperResources/Sources/WallpaperResources.swift index f7dbca8a4d..8ef113499a 100644 --- a/submodules/WallpaperResources/Sources/WallpaperResources.swift +++ b/submodules/WallpaperResources/Sources/WallpaperResources.swift @@ -863,84 +863,98 @@ public func drawThemeImage(context c: CGContext, theme: PresentationTheme, wallp c.restoreGState() } -public func themeImage(account: Account, accountManager: AccountManager, fileReference: FileMediaReference, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - let isSupportedTheme = fileReference.media.mimeType == "application/x-tgtheme-ios" - let maybeFetched = accountManager.mediaBox.resourceData(fileReference.media.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad) - let data = maybeFetched - |> take(1) - |> mapToSignal { maybeData -> Signal<(Data?, Data?), NoError> in - if maybeData.complete && isSupportedTheme { - let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) - return .single((loadedData, nil)) - } else { - let decodedThumbnailData = fileReference.media.immediateThumbnailData.flatMap(decodeTinyThumbnail) - - let previewRepresentation = fileReference.media.previewRepresentations.first - let fetchedThumbnail: Signal - if let previewRepresentation = previewRepresentation { - fetchedThumbnail = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: fileReference.resourceReference(previewRepresentation.resource)) - } else { - fetchedThumbnail = .complete() - } - - let thumbnailData: Signal - if let previewRepresentation = previewRepresentation { - thumbnailData = Signal { 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) +public enum ThemeImageSource { + case file(FileMediaReference) + case settings(TelegramThemeSettings) +} + +public func themeImage(account: Account, accountManager: AccountManager, source: ThemeImageSource, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { + let theme: Signal<(PresentationTheme?, Data?), NoError> + + switch source { + case let .file(fileReference): + let isSupportedTheme = fileReference.media.mimeType == "application/x-tgtheme-ios" + let maybeFetched = accountManager.mediaBox.resourceData(fileReference.media.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad) + theme = maybeFetched + |> take(1) + |> mapToSignal { maybeData -> Signal<(PresentationTheme?, Data?), NoError> in + if maybeData.complete && isSupportedTheme { + let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) + return .single((loadedData.flatMap { makePresentationTheme(data: $0) }, nil)) + } else { + let decodedThumbnailData = fileReference.media.immediateThumbnailData.flatMap(decodeTinyThumbnail) - return ActionDisposable { - fetchedDisposable.dispose() - thumbnailDisposable.dispose() + let previewRepresentation = fileReference.media.previewRepresentations.first + let fetchedThumbnail: Signal + if let previewRepresentation = previewRepresentation { + fetchedThumbnail = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: fileReference.resourceReference(previewRepresentation.resource)) + } else { + fetchedThumbnail = .complete() } - } - } else { - thumbnailData = .single(decodedThumbnailData) - } - - let reference = fileReference.resourceReference(fileReference.media.resource) - let fullSizeData: Signal - if isSupportedTheme { - 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) + + let thumbnailData: Signal + if let previewRepresentation = previewRepresentation { + thumbnailData = Signal { 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 { + fetchedDisposable.dispose() + thumbnailDisposable.dispose() } - subscriber.putNext(next) - }, error: { error in - subscriber.putError(error) - }, completed: { - subscriber.putCompletion() - }) - return ActionDisposable { - fetch.dispose() - disposable.dispose() + } + } else { + thumbnailData = .single(decodedThumbnailData) + } + + let reference = fileReference.resourceReference(fileReference.media.resource) + let fullSizeData: Signal + if isSupportedTheme { + 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) } - - return thumbnailData |> mapToSignal { thumbnailData in - return fullSizeData |> map { fullSizeData in - return (fullSizeData, thumbnailData) - } - } - } + case let .settings(settings): + 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)) } - |> 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 { return cachedWallpaper(account: account, slug: file.slug, settings: file.settings) |> mapToSignal { wallpaper -> Signal<(PresentationTheme?, UIImage?, Data?), NoError> in @@ -1071,6 +1085,16 @@ public func themeIconImage(account: Account, accountManager: AccountManager, the case .dayClassic: incomingColor = UIColor(rgb: 0xffffff) 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 { topBackgroundColor = UIColor(rgb: 0xd6e2ee) outgoingColor = bubbleColors