diff --git a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift index e94d1847b4..b1b978f408 100644 --- a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift +++ b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift @@ -537,11 +537,11 @@ final class LocationPickerControllerNode: ViewControllerTracingNode { } if address != nil { - if foundVenues == nil { + if foundVenues == nil && !state.searchingVenuesAround { displayingPlacesButton = true } else if let previousLocation = foundVenuesLocation { let currentLocation = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude) - if currentLocation.distance(from: previousLocation) > 500 { + if currentLocation.distance(from: previousLocation) > 300 { displayingPlacesButton = true } } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index dd8a0cfa6b..38e36761b4 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -11,6 +11,7 @@ import TelegramUIPreferences import ChatListUI import AccountContext import WallpaperResources +import PresentationDataUtils private func generateMaskImage(color: UIColor) -> UIImage? { return generateImage(CGSize(width: 1.0, height: 80.0), opaque: false, rotatedContext: { size, context in @@ -157,6 +158,8 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate private let mode: ThemeAccentColorControllerMode private var presentationData: PresentationData + private let queue = Queue() + private var state: ThemeColorState private let referenceTimestamp: Int32 @@ -398,8 +401,11 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate } self.stateDisposable = (self.statePromise.get() - |> deliverOn(Queue.concurrentDefaultQueue()) - |> map { state -> (PresentationTheme, (TelegramWallpaper, UIImage?, Signal<(TransformImageArguments) -> DrawingContext?, NoError>?), UIColor, (UIColor, UIColor?)?, [UIColor], Bool) in + |> deliverOn(self.queue) + |> mapToThrottled { next -> Signal in + return .single(next) |> then(.complete() |> delay(0.0166667, queue: self.queue)) + } + |> map { [weak self] state -> (PresentationTheme?, (TelegramWallpaper, UIImage?, Signal<(TransformImageArguments) -> DrawingContext?, NoError>?, (() -> Void)?), UIColor, (UIColor, UIColor?)?, PatternWallpaperArguments, Bool) in let accentColor = state.accentColor var backgroundColors = state.backgroundColors let messagesColors = state.messagesColors @@ -410,6 +416,11 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate var singleBackgroundColor: UIColor? + var updateOnlyWallpaper = false + if state.section == .background && state.preview { + updateOnlyWallpaper = true + } + if let backgroundColors = backgroundColors { if let patternWallpaper = state.patternWallpaper, case let .file(file) = patternWallpaper { let color = Int32(bitPattern: backgroundColors.0.rgb) @@ -453,40 +464,51 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate } let serviceBackgroundColor = serviceColor(for: (wallpaper, wallpaperImage)) - let updatedTheme: PresentationTheme - if let themeReference = mode.themeReference { - updatedTheme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: themeReference, accentColor: accentColor, backgroundColors: backgroundColors, bubbleColors: messagesColors, serviceBackgroundColor: serviceBackgroundColor, preview: true) ?? defaultPresentationTheme - } else if case let .edit(theme, _, _, _, _) = mode { - updatedTheme = customizePresentationTheme(theme, editing: false, accentColor: accentColor, backgroundColors: backgroundColors, bubbleColors: messagesColors) + let updatedTheme: PresentationTheme? + + if !updateOnlyWallpaper { + if let themeReference = mode.themeReference { + updatedTheme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: themeReference, accentColor: accentColor, backgroundColors: backgroundColors, bubbleColors: messagesColors, serviceBackgroundColor: serviceBackgroundColor, preview: true) ?? defaultPresentationTheme + } else if case let .edit(theme, _, _, _, _) = mode { + updatedTheme = customizePresentationTheme(theme, editing: false, accentColor: accentColor, backgroundColors: backgroundColors, bubbleColors: messagesColors) + } else { + updatedTheme = theme + } + + let _ = PresentationResourcesChat.principalGraphics(mediaBox: context.account.postbox.mediaBox, knockoutWallpaper: context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper, theme: updatedTheme!, wallpaper: wallpaper) } else { - updatedTheme = theme + updatedTheme = nil } - - let _ = PresentationResourcesChat.principalGraphics(mediaBox: context.account.postbox.mediaBox, knockoutWallpaper: context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper, theme: updatedTheme, wallpaper: wallpaper) - let patternColors = calcPatternColors(for: state) + let patternArguments = PatternWallpaperArguments(colors: calcPatternColors(for: state), rotation: wallpaper.settings?.rotation ?? 0, preview: state.preview) - return (updatedTheme, (wallpaper, wallpaperImage, wallpaperSignal), serviceBackgroundColor, backgroundColors, patternColors, state.preview) + var wallpaperApply: (() -> Void)? + if let strongSelf = self, case let .file(file) = wallpaper, file.isPattern, let (layout, _, _) = strongSelf.validLayout { + let makeImageLayout = strongSelf.signalBackgroundNode.asyncLayout() + wallpaperApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: wallpaper.dimensions ?? layout.size, boundingSize: layout.size, intrinsicInsets: UIEdgeInsets(), custom: patternArguments)) + } + + return (updatedTheme, (wallpaper, wallpaperImage, wallpaperSignal, wallpaperApply), serviceBackgroundColor, backgroundColors, patternArguments, state.preview) } - |> deliverOnMainQueue).start(next: { [weak self] theme, wallpaperImageAndSignal, serviceBackgroundColor, backgroundColors, patternColors, preview in + |> deliverOnMainQueue).start(next: { [weak self] theme, wallpaperImageAndSignal, serviceBackgroundColor, backgroundColors, patternArguments, preview in guard let strongSelf = self else { return } - let (wallpaper, wallpaperImage, wallpaperSignal) = wallpaperImageAndSignal + let (wallpaper, wallpaperImage, wallpaperSignal, wallpaperApply) = wallpaperImageAndSignal + + if let theme = theme { + strongSelf.theme = theme + strongSelf.themeUpdated?(theme) + strongSelf.themePromise.set(.single(theme)) + strongSelf.colorPanelNode.updateTheme(theme) + strongSelf.toolbarNode.updateThemeAndStrings(theme: theme, strings: strongSelf.presentationData.strings) + strongSelf.chatListBackgroundNode.backgroundColor = theme.chatList.backgroundColor + strongSelf.maskNode.image = generateMaskImage(color: theme.chatList.backgroundColor) + } - strongSelf.theme = theme - strongSelf.themeUpdated?(theme) - strongSelf.themePromise.set(.single(theme)) strongSelf.serviceBackgroundColor = serviceBackgroundColor strongSelf.serviceBackgroundColorPromise.set(.single(serviceBackgroundColor)) - strongSelf.colorPanelNode.updateTheme(theme) - strongSelf.toolbarNode.updateThemeAndStrings(theme: theme, strings: strongSelf.presentationData.strings) - - strongSelf.chatListBackgroundNode.backgroundColor = theme.chatList.backgroundColor - strongSelf.maskNode.image = generateMaskImage(color: theme.chatList.backgroundColor) - - let patternArguments = PatternWallpaperArguments(colors: patternColors, rotation: wallpaper.settings?.rotation ?? 0, preview: preview) if case let .color(value) = wallpaper { strongSelf.backgroundColor = UIColor(rgb: UInt32(bitPattern: value)) strongSelf.immediateBackgroundNode.backgroundColor = UIColor(rgb: UInt32(bitPattern: value)) @@ -506,41 +528,10 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate strongSelf.signalBackgroundNode.isHidden = false if case let .file(file) = wallpaper, let (layout, _, _) = strongSelf.validLayout { - var dispatch = false + wallpaperApply?() + if let previousWallpaper = strongSelf.patternWallpaper, case let .file(previousFile) = previousWallpaper, file.id == previousFile.id { - dispatch = true - } - - if dispatch { - if let _ = strongSelf.patternArgumentsDisposable { - } else { - strongSelf.signalBackgroundNode.contentAnimations = .subsequentUpdates - - let throttledSignal = strongSelf.patternArgumentsPromise.get() - |> mapToThrottled { next -> Signal in - return .single(next) |> then(.complete() |> delay(0.033333, queue: Queue.concurrentDefaultQueue())) - } - - strongSelf.patternArgumentsDisposable = (throttledSignal).start(next: { [weak self] arguments in - if let strongSelf = self { - let makeImageLayout = strongSelf.signalBackgroundNode.asyncLayout() - let imageApply = makeImageLayout(arguments) - let _ = imageApply() - } - }) - } - - strongSelf.patternArgumentsPromise.set(.single(TransformImageArguments(corners: ImageCorners(), imageSize: layout.size, boundingSize: layout.size, intrinsicInsets: UIEdgeInsets(), custom: patternArguments))) } else { - strongSelf.patternArgumentsDisposable?.dispose() - strongSelf.patternArgumentsDisposable = nil - - let makeImageLayout = strongSelf.signalBackgroundNode.asyncLayout() - let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: layout.size, boundingSize: layout.size, intrinsicInsets: UIEdgeInsets(), custom: patternArguments)) - let _ = imageApply() - } - - if !dispatch { strongSelf.signalBackgroundNode.setSignal(wallpaperSignal) strongSelf.patternWallpaper = wallpaper } @@ -557,7 +548,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate strongSelf.patternPanelNode.backgroundColors = backgroundColors } - if let (layout, navigationBarHeight, messagesBottomInset) = strongSelf.validLayout { + if let _ = theme, let (layout, navigationBarHeight, messagesBottomInset) = strongSelf.validLayout { strongSelf.updateChatsLayout(layout: layout, topInset: navigationBarHeight, transition: .immediate) strongSelf.updateMessagesLayout(layout: layout, bottomInset: messagesBottomInset, transition: .immediate) } @@ -954,7 +945,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate transition.updateFrame(node: self.backgroundContainerNode, frame: CGRect(origin: CGPoint(), size: backgroundSize)) let makeImageLayout = self.signalBackgroundNode.asyncLayout() - let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: layout.size, boundingSize: layout.size, intrinsicInsets: UIEdgeInsets(), custom: self.patternArguments)) + let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: self.patternWallpaper?.dimensions ?? layout.size, boundingSize: layout.size, intrinsicInsets: UIEdgeInsets(), custom: self.patternArguments)) let _ = imageApply() transition.updatePosition(node: self.backgroundWrapperNode, position: CGPoint(x: backgroundSize.width / 2.0, y: backgroundSize.height / 2.0)) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift index af994e44b3..dd8b6dd917 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift @@ -384,23 +384,24 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { } } -private func themeSettingsControllerEntries(presentationData: PresentationData, presentationThemeSettings: PresentationThemeSettings, theme: PresentationTheme, themeReference: PresentationThemeReference, themeSpecificAccentColors: [Int64: PresentationThemeAccentColor], availableThemes: [PresentationThemeReference], autoNightSettings: AutomaticThemeSwitchSetting, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, dateTimeFormat: PresentationDateTimeFormat, largeEmoji: Bool, disableAnimations: Bool, availableAppIcons: [PresentationAppIcon], currentAppIconName: String?) -> [ThemeSettingsControllerEntry] { +private func themeSettingsControllerEntries(presentationData: PresentationData, presentationThemeSettings: PresentationThemeSettings, themeReference: PresentationThemeReference, availableThemes: [PresentationThemeReference], availableAppIcons: [PresentationAppIcon], currentAppIconName: String?) -> [ThemeSettingsControllerEntry] { var entries: [ThemeSettingsControllerEntry] = [] + let strings = presentationData.strings let title = presentationData.autoNightModeTriggered ? strings.Appearance_ColorThemeNight.uppercased() : strings.Appearance_ColorTheme.uppercased() entries.append(.themeListHeader(presentationData.theme, title)) - entries.append(.chatPreview(presentationData.theme, theme, wallpaper, presentationData.fontSize, presentationData.strings, dateTimeFormat, presentationData.nameDisplayOrder, [ChatPreviewMessageItem(outgoing: false, reply: (presentationData.strings.Appearance_PreviewReplyAuthor, presentationData.strings.Appearance_PreviewReplyText), text: presentationData.strings.Appearance_PreviewIncomingText), ChatPreviewMessageItem(outgoing: true, reply: nil, text: presentationData.strings.Appearance_PreviewOutgoingText)])) + entries.append(.chatPreview(presentationData.theme, presentationData.theme, presentationData.chatWallpaper, presentationData.fontSize, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, [ChatPreviewMessageItem(outgoing: false, reply: (presentationData.strings.Appearance_PreviewReplyAuthor, presentationData.strings.Appearance_PreviewReplyText), text: presentationData.strings.Appearance_PreviewIncomingText), ChatPreviewMessageItem(outgoing: true, reply: nil, text: presentationData.strings.Appearance_PreviewOutgoingText)])) - entries.append(.themeItem(presentationData.theme, presentationData.strings, availableThemes, themeReference, themeSpecificAccentColors, themeSpecificAccentColors[themeReference.index])) + entries.append(.themeItem(presentationData.theme, presentationData.strings, availableThemes, themeReference, presentationThemeSettings.themeSpecificAccentColors, presentationThemeSettings.themeSpecificAccentColors[themeReference.index])) if case let .builtin(theme) = themeReference { - entries.append(.accentColor(presentationData.theme, themeReference, themeSpecificAccentColors[themeReference.index])) + entries.append(.accentColor(presentationData.theme, themeReference, presentationThemeSettings.themeSpecificAccentColors[themeReference.index])) } entries.append(.wallpaper(presentationData.theme, strings.Settings_ChatBackground)) let autoNightMode: String - switch autoNightSettings.trigger { + switch presentationThemeSettings.automaticThemeSwitchSetting.trigger { case .system: if #available(iOSApplicationExtension 13.0, iOS 13.0, *) { autoNightMode = strings.AutoNightTheme_System @@ -430,8 +431,8 @@ private func themeSettingsControllerEntries(presentationData: PresentationData, } entries.append(.otherHeader(presentationData.theme, strings.Appearance_Other.uppercased())) - entries.append(.largeEmoji(presentationData.theme, strings.Appearance_LargeEmoji, largeEmoji)) - entries.append(.animations(presentationData.theme, strings.Appearance_ReduceMotion, disableAnimations)) + entries.append(.largeEmoji(presentationData.theme, strings.Appearance_LargeEmoji, presentationData.largeEmoji)) + entries.append(.animations(presentationData.theme, strings.Appearance_ReduceMotion, presentationData.disableAnimations)) entries.append(.animationsInfo(presentationData.theme, strings.Appearance_ReduceMotionInfo)) return entries @@ -688,7 +689,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The |> map { presentationData, sharedData, cloudThemes, availableAppIcons, currentAppIconName -> (ItemListControllerState, (ItemListNodeState, Any)) in let settings = (sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings] as? PresentationThemeSettings) ?? PresentationThemeSettings.defaultSettings - let fontSize = settings.fontSize + let fontSize = presentationData.fontSize let dateTimeFormat = presentationData.dateTimeFormat let largeEmoji = presentationData.largeEmoji let disableAnimations = presentationData.disableAnimations @@ -700,9 +701,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The themeReference = settings.theme } - let theme = presentationData.theme let accentColor = settings.themeSpecificAccentColors[themeReference.index] - let wallpaper = presentationData.chatWallpaper let rightNavigationButton = ItemListNavigationButton(content: .icon(.add), style: .regular, enabled: true, action: { moreImpl?() @@ -724,7 +723,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The availableThemes.append(contentsOf: cloudThemes) let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.Appearance_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: themeSettingsControllerEntries(presentationData: presentationData, presentationThemeSettings: settings, theme: theme, themeReference: themeReference, themeSpecificAccentColors: settings.themeSpecificAccentColors, availableThemes: availableThemes, autoNightSettings: settings.automaticThemeSwitchSetting, strings: presentationData.strings, wallpaper: wallpaper, fontSize: fontSize, dateTimeFormat: dateTimeFormat, largeEmoji: largeEmoji, disableAnimations: disableAnimations, availableAppIcons: availableAppIcons, currentAppIconName: currentAppIconName), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: themeSettingsControllerEntries(presentationData: presentationData, presentationThemeSettings: settings, themeReference: themeReference, availableThemes: availableThemes, availableAppIcons: availableAppIcons, currentAppIconName: currentAppIconName), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false) let previousThemeIndex = previousThemeReference.swap(themeReference)?.index let previousAccentColor = previousAccentColor.swap(accentColor) diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperColorPanelNode.swift b/submodules/SettingsUI/Sources/Themes/WallpaperColorPanelNode.swift index c15e0266de..69e24bdec8 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperColorPanelNode.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperColorPanelNode.swift @@ -31,36 +31,22 @@ private func textInputBackgroundImage(fieldColor: UIColor, strokeColor: UIColor, } } -private func generateSwatchImage(theme: PresentationTheme, color: UIColor) -> UIImage? { +private func generateSwatchBorderImage(theme: PresentationTheme) -> UIImage? { + return nil return generateImage(CGSize(width: 21.0, height: 21.0), rotatedContext: { size, context in let bounds = CGRect(origin: CGPoint(), size: size) context.clear(bounds) - - let fillColor = color - var strokeColor: UIColor? - let inputBackgroundColor = theme.chat.inputPanel.inputBackgroundColor - if fillColor.distance(to: inputBackgroundColor) < 200 { - strokeColor = theme.chat.inputPanel.inputStrokeColor - if strokeColor!.distance(to: inputBackgroundColor) < 200 { - strokeColor = theme.chat.inputPanel.inputControlColor - } - } - - context.setFillColor(fillColor.cgColor) context.setLineWidth(1.0) - - context.fillEllipse(in: bounds) - if let strokeColor = strokeColor { - context.setStrokeColor(strokeColor.cgColor) - context.strokeEllipse(in: bounds.insetBy(dx: 1.0, dy: 1.0)) - } + context.setStrokeColor(theme.chat.inputPanel.inputControlColor.cgColor) + context.strokeEllipse(in: bounds.insetBy(dx: 1.0, dy: 1.0)) }) } private class ColorInputFieldNode: ASDisplayNode, UITextFieldDelegate { private var theme: PresentationTheme - private let swatchNode: ASImageNode + private let swatchNode: ASDisplayNode + private let borderNode: ASImageNode private let removeButton: HighlightableButtonNode private let textBackgroundNode: ASImageNode private let selectionNode: ASDisplayNode @@ -123,9 +109,13 @@ private class ColorInputFieldNode: ASDisplayNode, UITextFieldDelegate { self.prefixNode = ASTextNode() self.prefixNode.attributedText = NSAttributedString(string: "#", font: Font.regular(17.0), textColor: self.theme.chat.inputPanel.inputTextColor) - self.swatchNode = ASImageNode() - self.swatchNode.displaysAsynchronously = false - self.swatchNode.displayWithoutProcessing = true + self.swatchNode = ASDisplayNode() + self.swatchNode.cornerRadius = 10.5 + + self.borderNode = ASImageNode() + self.borderNode.displaysAsynchronously = false + self.borderNode.displayWithoutProcessing = true + self.borderNode.image = generateSwatchBorderImage(theme: theme) self.removeButton = HighlightableButtonNode() self.removeButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Settings/ThemeColorRemoveIcon"), color: theme.chat.inputPanel.inputControlColor), for: .normal) @@ -137,6 +127,7 @@ private class ColorInputFieldNode: ASDisplayNode, UITextFieldDelegate { self.addSubnode(self.textFieldNode) self.addSubnode(self.prefixNode) self.addSubnode(self.swatchNode) + self.addSubnode(self.borderNode) self.addSubnode(self.removeButton) self.removeButton.addTarget(self, action: #selector(self.removePressed), forControlEvents: .touchUpInside) @@ -172,6 +163,8 @@ private class ColorInputFieldNode: ASDisplayNode, UITextFieldDelegate { self.textFieldNode.textField.tintColor = self.theme.list.itemAccentColor self.selectionNode.backgroundColor = theme.chat.inputPanel.panelControlAccentColor.withAlphaComponent(0.2) + self.borderNode.image = generateSwatchBorderImage(theme: theme) + self.updateBorderVisibility() } func setColor(_ color: UIColor, isDefault: Bool = false, update: Bool = true, ended: Bool = true) { @@ -186,7 +179,20 @@ private class ColorInputFieldNode: ASDisplayNode, UITextFieldDelegate { if update { self.colorChanged?(color, ended) } - self.swatchNode.image = generateSwatchImage(theme: self.theme, color: color) + self.swatchNode.backgroundColor = color + self.updateBorderVisibility() + } + + private func updateBorderVisibility() { + guard let color = self.swatchNode.backgroundColor else { + return + } + let inputBackgroundColor = self.theme.chat.inputPanel.inputBackgroundColor + if color.distance(to: inputBackgroundColor) < 200 { + self.borderNode.alpha = 1.0 + } else { + self.borderNode.alpha = 0.0 + } } @objc private func removePressed() { @@ -294,7 +300,9 @@ private class ColorInputFieldNode: ASDisplayNode, UITextFieldDelegate { func updateLayout(size: CGSize, condensed: Bool, transition: ContainedViewLayoutTransition) { self.validLayout = (size, condensed) - transition.updateFrame(node: self.swatchNode, frame: CGRect(origin: CGPoint(x: 6.0, y: 6.0), size: CGSize(width: 21.0, height: 21.0))) + let swatchFrame = CGRect(origin: CGPoint(x: 6.0, y: 6.0), size: CGSize(width: 21.0, height: 21.0)) + transition.updateFrame(node: self.swatchNode, frame: swatchFrame) + transition.updateFrame(node: self.borderNode, frame: swatchFrame) let textPadding: CGFloat = condensed ? 31.0 : 37.0 diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperColorPickerNode.swift b/submodules/SettingsUI/Sources/Themes/WallpaperColorPickerNode.swift index 953431e902..cf89d9b0d7 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperColorPickerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperColorPickerNode.swift @@ -5,19 +5,22 @@ import SwiftSignalKit import Display import TelegramPresentationData -private let shadowImage: UIImage = { - return generateImage(CGSize(width: 45.0, height: 45.0), opaque: false, scale: nil, rotatedContext: { size, context in - context.setBlendMode(.clear) - context.setFillColor(UIColor.clear.cgColor) - context.fill(CGRect(origin: CGPoint(), size: size)) +private let knobBackgroundImage: UIImage? = { + return generateImage(CGSize(width: 45.0, height: 45.0), contextGenerator: { size, context in + let bounds = CGRect(origin: CGPoint(), size: size) + context.clear(bounds) + + context.setShadow(offset: CGSize(width: 0.0, height: -1.5), blur: 4.5, color: UIColor(rgb: 0x000000, alpha: 0.4).cgColor) + context.setFillColor(UIColor(rgb: 0x000000, alpha: 0.4).cgColor) + context.fillEllipse(in: bounds.insetBy(dx: 3.0 + UIScreenPixel, dy: 3.0 + UIScreenPixel)) + context.setBlendMode(.normal) - context.setShadow(offset: CGSize(width: 0.0, height: 1.5), blur: 4.5, color: UIColor(rgb: 0x000000, alpha: 0.5).cgColor) - context.setFillColor(UIColor(rgb: 0x000000, alpha: 0.5).cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: 3.0 + UIScreenPixel, dy: 3.0 + UIScreenPixel)) - })! + context.setFillColor(UIColor.white.cgColor) + context.fillEllipse(in: bounds.insetBy(dx: 3.0, dy: 3.0)) + }, opaque: false, scale: nil) }() -private let pointerImage: UIImage = { +private let pointerImage: UIImage? = { return generateImage(CGSize(width: 12.0, height: 55.0), opaque: false, scale: nil, rotatedContext: { size, context in context.setBlendMode(.clear) context.setFillColor(UIColor.clear.cgColor) @@ -43,7 +46,33 @@ private let pointerImage: UIImage = { context.addLine(to: CGPoint(x: size.width - lineWidth / 2.0, y: size.height - lineWidth / 2.0)) context.closePath() context.drawPath(using: .fillStroke) - })! + }) +}() + +private let brightnessMaskImage: UIImage? = { + return generateImage(CGSize(width: 36.0, height: 36.0), opaque: false, scale: nil, rotatedContext: { size, context in + let bounds = CGRect(origin: CGPoint(), size: size) + + context.setFillColor(UIColor.white.cgColor) + context.fill(bounds) + + context.setBlendMode(.clear) + context.setFillColor(UIColor.clear.cgColor) + context.fillEllipse(in: bounds) + })?.stretchableImage(withLeftCapWidth: 18, topCapHeight: 18) +}() + +private let brightnessGradientImage: UIImage? = { + return generateImage(CGSize(width: 160.0, height: 1.0), opaque: false, scale: nil, rotatedContext: { size, context in + let bounds = CGRect(origin: CGPoint(), size: size) + context.clear(bounds) + + let gradientColors = [UIColor.black.withAlphaComponent(0.0), UIColor.black].map { $0.cgColor } as CFArray + var locations: [CGFloat] = [0.0, 1.0] + let colorSpace = CGColorSpaceCreateDeviceRGB() + let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! + context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: 0.0), options: [.drawsBeforeStartLocation, .drawsAfterEndLocation]) + }) }() private final class HSBParameter: NSObject { @@ -63,53 +92,46 @@ private final class WallpaperColorKnobNode: ASDisplayNode { var hsb: (CGFloat, CGFloat, CGFloat) = (0.0, 0.0, 1.0) { didSet { if self.hsb != oldValue { - self.setNeedsDisplay() + let color = UIColor(hue: hsb.0, saturation: hsb.1, brightness: hsb.2, alpha: 1.0) + self.colorNode.backgroundColor = color } } } + private let backgroundNode: ASImageNode + private let colorNode: ASDisplayNode + override init() { + self.backgroundNode = ASImageNode() + self.backgroundNode.displaysAsynchronously = false + self.backgroundNode.displayWithoutProcessing = true + self.backgroundNode.image = knobBackgroundImage + + self.colorNode = ASDisplayNode() + super.init() - self.isOpaque = false - self.displaysAsynchronously = false self.isUserInteractionEnabled = false + + self.addSubnode(self.backgroundNode) + self.addSubnode(self.colorNode) } - override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? { - return HSBParameter(hue: self.hsb.0, saturation: self.hsb.1, value: self.hsb.2) - } - - @objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) { - guard let parameters = parameters as? HSBParameter else { - return - } - let context = UIGraphicsGetCurrentContext()! + override func layout() { + super.layout() - if !isRasterizing { - context.setBlendMode(.copy) - context.setFillColor(UIColor.clear.cgColor) - context.fill(bounds) - } - - context.draw(shadowImage.cgImage!, in: bounds) - - context.setBlendMode(.normal) - context.setFillColor(UIColor.white.cgColor) - context.fillEllipse(in: bounds.insetBy(dx: 3.0, dy: 3.0)) - - let color = UIColor(hue: parameters.hue, saturation: parameters.saturation, brightness: parameters.value, alpha: 1.0) - context.setFillColor(color.cgColor) - - let borderWidth: CGFloat = 7.0 - context.fillEllipse(in: bounds.insetBy(dx: borderWidth - UIScreenPixel, dy: borderWidth - UIScreenPixel)) + self.backgroundNode.frame = self.bounds + self.colorNode.frame = self.bounds.insetBy(dx: 7.0 - UIScreenPixel, dy: 7.0 - UIScreenPixel) + self.colorNode.cornerRadius = self.colorNode.frame.width / 2.0 } } private final class WallpaperColorHueSaturationNode: ASDisplayNode { var value: CGFloat = 1.0 { didSet { - self.setNeedsDisplay() + if self.value != oldValue { + self.setNeedsDisplay() + } } } @@ -121,7 +143,7 @@ private final class WallpaperColorHueSaturationNode: ASDisplayNode { } override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? { - return HSBParameter(hue: 1.0, saturation: 1.0, value: self.value) + return HSBParameter(hue: 1.0, saturation: 1.0, value: 1.0) } @objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) { @@ -147,47 +169,43 @@ private final class WallpaperColorHueSaturationNode: ASDisplayNode { } private final class WallpaperColorBrightnessNode: ASDisplayNode { + private let gradientNode: ASImageNode + private let maskNode: ASImageNode + var hsb: (CGFloat, CGFloat, CGFloat) = (0.0, 1.0, 1.0) { didSet { - self.setNeedsDisplay() + if self.hsb.0 != oldValue.0 || self.hsb.1 != oldValue.1 { + let color = UIColor(hue: hsb.0, saturation: hsb.1, brightness: 1.0, alpha: 1.0) + self.backgroundColor = color + } } } override init() { + self.gradientNode = ASImageNode() + self.gradientNode.displaysAsynchronously = false + self.gradientNode.displayWithoutProcessing = true + self.gradientNode.image = brightnessGradientImage + self.gradientNode.contentMode = .scaleToFill + + self.maskNode = ASImageNode() + self.maskNode.displaysAsynchronously = false + self.maskNode.displayWithoutProcessing = true + self.maskNode.image = brightnessMaskImage + self.maskNode.contentMode = .scaleToFill + super.init() self.isOpaque = true - self.displaysAsynchronously = false + self.addSubnode(self.gradientNode) + self.addSubnode(self.maskNode) } - override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? { - return HSBParameter(hue: self.hsb.0, saturation: self.hsb.1, value: self.hsb.2) - } - - @objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) { - guard let parameters = parameters as? HSBParameter else { - return - } - let context = UIGraphicsGetCurrentContext()! - let colorSpace = CGColorSpaceCreateDeviceRGB() + override func layout() { + super.layout() - context.setFillColor(UIColor(white: parameters.value, alpha: 1.0).cgColor) - context.fill(bounds) - - let path = UIBezierPath(roundedRect: bounds, cornerRadius: bounds.height / 2.0) - context.addPath(path.cgPath) - context.setFillColor(UIColor.white.cgColor) - context.fillPath() - - let innerPath = UIBezierPath(roundedRect: bounds.insetBy(dx: 1.0, dy: 1.0), cornerRadius: bounds.height / 2.0) - context.addPath(innerPath.cgPath) - context.clip() - - let color = UIColor(hue: parameters.hue, saturation: parameters.saturation, brightness: 1.0, alpha: 1.0) - let colors = [color.cgColor, UIColor.black.cgColor] - var locations: [CGFloat] = [0.0, 1.0] - let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)! - context.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: bounds.width, y: 0.0), options: CGGradientDrawingOptions()) + self.gradientNode.frame = self.bounds + self.maskNode.frame = self.bounds } } @@ -250,7 +268,7 @@ final class WallpaperColorPickerNode: ASDisplayNode { } private func update() { - self.backgroundColor = UIColor(white: self.colorHsb.2, alpha: 1.0) + self.backgroundColor = .white self.colorNode.value = self.colorHsb.2 self.brightnessNode.hsb = self.colorHsb self.colorKnobNode.hsb = self.colorHsb diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift index 0e7e024bb3..4424983e46 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift @@ -271,7 +271,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } } - patternArguments = PatternWallpaperArguments(colors: patternColors, rotation: file.settings.rotation) + patternArguments = PatternWallpaperArguments(colors: patternColors, rotation: file.settings.rotation, preview: self.arguments.colorPreview) self.backgroundColor = patternColor.withAlphaComponent(1.0) @@ -295,7 +295,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.colorPreview = self.arguments.colorPreview - signal = patternWallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, representations: convertedRepresentations, mode: self.arguments.colorPreview ? .fastScreen : .screen, autoFetchFullSize: true) + signal = patternWallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, representations: convertedRepresentations, mode: .screen, autoFetchFullSize: true) colorSignal = chatServiceBackgroundColor(wallpaper: wallpaper, mediaBox: context.account.postbox.mediaBox) isBlurrable = false diff --git a/submodules/TelegramPresentationData/Sources/WallpaperUtils.swift b/submodules/TelegramPresentationData/Sources/WallpaperUtils.swift index ae81d73b1a..df80040772 100644 --- a/submodules/TelegramPresentationData/Sources/WallpaperUtils.swift +++ b/submodules/TelegramPresentationData/Sources/WallpaperUtils.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import TelegramCore import SyncCore @@ -46,4 +47,12 @@ public extension TelegramWallpaper { return false } } + + var dimensions: CGSize? { + if case let .file(file) = self { + return file.file.dimensions?.cgSize + } else { + return nil + } + } } diff --git a/submodules/WallpaperResources/Sources/WallpaperResources.swift b/submodules/WallpaperResources/Sources/WallpaperResources.swift index a9d81b1bf4..fd298ea0dd 100644 --- a/submodules/WallpaperResources/Sources/WallpaperResources.swift +++ b/submodules/WallpaperResources/Sources/WallpaperResources.swift @@ -291,7 +291,6 @@ public func wallpaperImage(account: Account, accountManager: AccountManager, fil public enum PatternWallpaperDrawMode { case thumbnail - case fastScreen case screen } @@ -322,8 +321,6 @@ private func patternWallpaperDatas(account: Account, accountManager: AccountMana switch mode { case .thumbnail: size = largestRepresentation.dimensions.cgSize.fitted(CGSize(width: 640.0, height: 640.0)) - case .fastScreen: - size = largestRepresentation.dimensions.cgSize.fitted(CGSize(width: 1280.0, height: 1280.0)) default: size = nil } @@ -407,18 +404,23 @@ public func patternWallpaperImageInternal(thumbnailData: Data?, fullSizeData: Da } var scale: CGFloat = 0.0 - if case .fastScreen = mode { - scale = max(1.0, UIScreenScale - 1.0) - } return .single((thumbnailData, fullSizeData, fullSizeComplete)) |> map { (thumbnailData, fullSizeData, fullSizeComplete) in var fullSizeImage: CGImage? + var scaledSizeImage: CGImage? if let fullSizeData = fullSizeData, fullSizeComplete { let options = NSMutableDictionary() options[kCGImageSourceShouldCache as NSString] = false as NSNumber if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) { fullSizeImage = image + + let options = NSMutableDictionary() + options.setValue(960 as NSNumber, forKey: kCGImageSourceThumbnailMaxPixelSize as String) + options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailFromImageAlways as String) + if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, nil), let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options) { + scaledSizeImage = image + } } } @@ -433,6 +435,7 @@ public func patternWallpaperImageInternal(thumbnailData: Data?, fullSizeData: Da if abs(fittedSize.height - arguments.boundingSize.height).isLessThanOrEqualTo(CGFloat(1.0)) { fittedSize.height = arguments.boundingSize.height } + fittedSize = fittedSize.aspectFilled(arguments.drawingRect.size) let fittedRect = CGRect(origin: CGPoint(x: drawingRect.origin.x + (drawingRect.size.width - fittedSize.width) / 2.0, y: drawingRect.origin.y + (drawingRect.size.height - fittedSize.height) / 2.0), size: fittedSize) @@ -446,7 +449,7 @@ public func patternWallpaperImageInternal(thumbnailData: Data?, fullSizeData: Da let color = combinedColor.withAlphaComponent(1.0) let intensity = combinedColor.alpha - let context = DrawingContext(size: arguments.drawingSize, scale: fullSizeImage == nil ? 1.0 : scale, clear: true) + let context = DrawingContext(size: arguments.drawingSize, scale: fullSizeImage == nil ? 1.0 : scale, clear: !arguments.corners.isEmpty) context.withFlippedContext { c in c.setBlendMode(.copy) @@ -473,10 +476,11 @@ public func patternWallpaperImageInternal(thumbnailData: Data?, fullSizeData: Da c.restoreGState() } - if let fullSizeImage = fullSizeImage { + let image = customArguments.preview ? (scaledSizeImage ?? fullSizeImage) : fullSizeImage + if let image = image { c.setBlendMode(.normal) - c.interpolationQuality = .medium - c.clip(to: fittedRect, mask: fullSizeImage) + c.interpolationQuality = customArguments.preview ? .low : .medium + c.clip(to: fittedRect, mask: image) if colors.count == 1 { c.setFillColor(patternColor(for: color, intensity: intensity, prominent: prominent).cgColor)