diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 8c6579dc49..0f35cb28f3 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -9149,7 +9149,7 @@ Sorry for the inconvenience."; "WallpaperPreview.ChatBottomText" = "Enjoy the view."; "Conversation.Theme.SetPhotoWallpaper" = "Choose Background from Photos"; -"Conversation.Theme.SetColorWallpaper" = "Choose Color as a Background"; +"Conversation.Theme.SetColorWallpaper" = "Set a Color as a Background"; "Conversation.Theme.OtherOptions" = "Other Options..."; "Conversation.Theme.ChooseWallpaperTitle" = "Choose Background"; @@ -9158,3 +9158,11 @@ Sorry for the inconvenience."; "Conversation.Theme.SetCustomColor" = "Set Custom"; "Appearance.ShowNextMediaOnTap" = "Show Next Media on Tap"; + +"WebApp.LaunchMoreInfo" = "More about this bot"; +"WebApp.LaunchConfirmation" = "To launch this web app, you will connect to its website."; + +"WallpaperPreview.PreviewInNightMode" = "Preview this background in night mode."; +"WallpaperPreview.PreviewInDayMode" = "Preview this background in day mode."; + +"Conversation.Theme.ApplyBackground" = "Set as Background"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 3173913004..0869ada5a6 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -756,6 +756,7 @@ public protocol SharedAccountContext: AnyObject { var currentInAppNotificationSettings: Atomic { get } var currentMediaInputSettings: Atomic { get } var currentStickerSettings: Atomic { get } + var currentMediaDisplaySettings: Atomic { get } var energyUsageSettings: EnergyUsageSettings { get } diff --git a/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift b/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift index a4f1b421d6..c293f8802c 100644 --- a/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift +++ b/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift @@ -1370,7 +1370,7 @@ public final class CalendarMessageScreen: ViewController { if self.selectionState?.dayRange == nil { if let selectionToolbarNode = self.selectionToolbarNode { let toolbarFrame = selectionToolbarNode.view.convert(selectionToolbarNode.bounds, to: self.view) - self.controller?.present(TooltipScreen(account: self.context.account, text: self.presentationData.strings.MessageCalendar_EmptySelectionTooltip, style: .default, icon: .none, location: .point(toolbarFrame.insetBy(dx: 0.0, dy: 10.0), .bottom), shouldDismissOnTouch: { point in + self.controller?.present(TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: self.presentationData.strings.MessageCalendar_EmptySelectionTooltip, style: .default, icon: .none, location: .point(toolbarFrame.insetBy(dx: 0.0, dy: 10.0), .bottom), shouldDismissOnTouch: { point in return .dismiss(consume: false) }), in: .current) } diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 33c098db5c..5bba1511fe 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -2079,7 +2079,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 8.0), size: CGSize()) - parentController.present(TooltipScreen(account: strongSelf.context.account, text: text, icon: .chatListPress, location: .point(location, .bottom), shouldDismissOnTouch: { point in + parentController.present(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: text, icon: .chatListPress, location: .point(location, .bottom), shouldDismissOnTouch: { point in guard let strongSelf = self, let parentController = strongSelf.parent as? TabBarController else { return .dismiss(consume: false) } diff --git a/submodules/Display/Source/NavigationBar.swift b/submodules/Display/Source/NavigationBar.swift index 234eb1e768..d033d54219 100644 --- a/submodules/Display/Source/NavigationBar.swift +++ b/submodules/Display/Source/NavigationBar.swift @@ -138,6 +138,7 @@ public final class NavigationBackgroundNode: ASDisplayNode { private var _color: UIColor private var enableBlur: Bool + private var enableSaturation: Bool public var effectView: UIVisualEffectView? private let backgroundNode: ASDisplayNode @@ -152,9 +153,10 @@ public final class NavigationBackgroundNode: ASDisplayNode { } } - public init(color: UIColor, enableBlur: Bool = true) { + public init(color: UIColor, enableBlur: Bool = true, enableSaturation: Bool = true) { self._color = .clear self.enableBlur = enableBlur + self.enableSaturation = enableSaturation self.backgroundNode = ASDisplayNode() @@ -195,10 +197,12 @@ public final class NavigationBackgroundNode: ASDisplayNode { if let sublayer = effectView.layer.sublayers?[0], let filters = sublayer.filters { sublayer.backgroundColor = nil sublayer.isOpaque = false - let allowedKeys: [String] = [ - "colorSaturate", + var allowedKeys: [String] = [ "gaussianBlur" ] + if self.enableSaturation { + allowedKeys.append("colorSaturate") + } sublayer.filters = filters.filter { filter in guard let filter = filter as? NSObject else { return true @@ -225,14 +229,16 @@ public final class NavigationBackgroundNode: ASDisplayNode { } } - public func updateColor(color: UIColor, enableBlur: Bool? = nil, forceKeepBlur: Bool = false, transition: ContainedViewLayoutTransition) { + public func updateColor(color: UIColor, enableBlur: Bool? = nil, enableSaturation: Bool? = nil, forceKeepBlur: Bool = false, transition: ContainedViewLayoutTransition) { let effectiveEnableBlur = enableBlur ?? self.enableBlur - - if self._color.isEqual(color) && self.enableBlur == effectiveEnableBlur { + let effectiveEnableSaturation = enableSaturation ?? self.enableSaturation + + if self._color.isEqual(color) && self.enableBlur == effectiveEnableBlur && self.enableSaturation == effectiveEnableSaturation { return } self._color = color self.enableBlur = effectiveEnableBlur + self.enableSaturation = effectiveEnableSaturation if sharedIsReduceTransparencyEnabled { transition.updateBackgroundColor(node: self.backgroundNode, color: self._color.withAlphaComponent(1.0)) diff --git a/submodules/GalleryUI/Sources/GalleryController.swift b/submodules/GalleryUI/Sources/GalleryController.swift index b1fa7a8a98..5823fa9537 100644 --- a/submodules/GalleryUI/Sources/GalleryController.swift +++ b/submodules/GalleryUI/Sources/GalleryController.swift @@ -1211,7 +1211,9 @@ public class GalleryController: ViewController, StandalonePresentableController, }) } }) - self.displayNode = GalleryControllerNode(controllerInteraction: controllerInteraction) + + let disableTapNavigation = !(self.context.sharedContext.currentMediaDisplaySettings.with { $0 }.showNextMediaOnTap) + self.displayNode = GalleryControllerNode(controllerInteraction: controllerInteraction, disableTapNavigation: disableTapNavigation) self.displayNodeDidLoad() self.galleryNode.statusBar = self.statusBar diff --git a/submodules/ImageBlur/Sources/ImageBlur.swift b/submodules/ImageBlur/Sources/ImageBlur.swift index 05fb8da1be..1340bddc0b 100644 --- a/submodules/ImageBlur/Sources/ImageBlur.swift +++ b/submodules/ImageBlur/Sources/ImageBlur.swift @@ -39,6 +39,7 @@ public func blurredImage(_ image: UIImage, radius: CGFloat, iterations: Int = 3) let source = CFDataGetBytePtr(providerData) memcpy(inBuffer.data, source, bytes) + for _ in 0 ..< iterations { vImageBoxConvolve_ARGB8888(&inBuffer, &outBuffer, tempData, 0, 0, boxSize, boxSize, nil, vImage_Flags(kvImageEdgeExtend)) diff --git a/submodules/LocationUI/Sources/LocationViewControllerNode.swift b/submodules/LocationUI/Sources/LocationViewControllerNode.swift index 879371f8bc..2a70da2a89 100644 --- a/submodules/LocationUI/Sources/LocationViewControllerNode.swift +++ b/submodules/LocationUI/Sources/LocationViewControllerNode.swift @@ -829,7 +829,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan text = strongSelf.presentationData.strings.Location_ProximityTip(EnginePeer(peer).compactDisplayTitle).string } - strongSelf.interaction.present(TooltipScreen(account: strongSelf.context.account, text: text, icon: nil, location: .point(location.offsetBy(dx: -9.0, dy: 0.0), .right), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in + strongSelf.interaction.present(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: text, icon: nil, location: .point(location.offsetBy(dx: -9.0, dy: 0.0), .right), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in return .dismiss(consume: false) })) }) diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index c06f852960..97c3a39a9d 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -385,32 +385,40 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { override func didLoad() { super.didLoad() + guard let controller = self.controller else { + return + } + self.gridNode.scrollView.alwaysBounceVertical = true self.gridNode.scrollView.showsVerticalScrollIndicator = false - let selectionGesture = MediaPickerGridSelectionGesture() - selectionGesture.delegate = self - selectionGesture.began = { [weak self] in - self?.controller?.cancelPanGesture() - } - selectionGesture.updateIsScrollEnabled = { [weak self] isEnabled in - self?.gridNode.scrollView.isScrollEnabled = isEnabled - } - selectionGesture.itemAt = { [weak self] point in - if let strongSelf = self, let itemNode = strongSelf.gridNode.itemNodeAtPoint(point) as? MediaPickerGridItemNode, let selectableItem = itemNode.selectableItem { - return (selectableItem, strongSelf.controller?.interaction?.selectionState?.isIdentifierSelected(selectableItem.uniqueIdentifier) ?? false) - } else { - return nil + if case let .assets(_, mode) = controller.subject, case .wallpaper = mode { + + } else { + let selectionGesture = MediaPickerGridSelectionGesture() + selectionGesture.delegate = self + selectionGesture.began = { [weak self] in + self?.controller?.cancelPanGesture() } - } - selectionGesture.updateSelection = { [weak self] asset, selected in - if let strongSelf = self { - strongSelf.controller?.interaction?.selectionState?.setItem(asset, selected: selected, animated: true, sender: nil) + selectionGesture.updateIsScrollEnabled = { [weak self] isEnabled in + self?.gridNode.scrollView.isScrollEnabled = isEnabled } + selectionGesture.itemAt = { [weak self] point in + if let strongSelf = self, let itemNode = strongSelf.gridNode.itemNodeAtPoint(point) as? MediaPickerGridItemNode, let selectableItem = itemNode.selectableItem { + return (selectableItem, strongSelf.controller?.interaction?.selectionState?.isIdentifierSelected(selectableItem.uniqueIdentifier) ?? false) + } else { + return nil + } + } + selectionGesture.updateSelection = { [weak self] asset, selected in + if let strongSelf = self { + strongSelf.controller?.interaction?.selectionState?.setItem(asset, selected: selected, animated: true, sender: nil) + } + } + selectionGesture.sideInset = 44.0 + self.gridNode.view.addGestureRecognizer(selectionGesture) + self.selectionGesture = selectionGesture } - selectionGesture.sideInset = 44.0 - self.gridNode.view.addGestureRecognizer(selectionGesture) - self.selectionGesture = selectionGesture if let controller = self.controller, case let .assets(collection, _) = controller.subject, collection != nil { self.gridNode.view.interactiveTransitionGestureRecognizerTest = { point -> Bool in @@ -741,7 +749,11 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } if let customSelection = controller.customSelection { + self.openingMedia = true customSelection(fetchResult[index]) + Queue.mainQueue().after(0.3) { + self.openingMedia = false + } return } @@ -2010,18 +2022,19 @@ public func wallpaperMediaPickerController( context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer, - canDelete: Bool, - completion: @escaping (PHAsset) -> Void = { _ in } + completion: @escaping (PHAsset) -> Void = { _ in }, + openColors: @escaping () -> Void ) -> ViewController { let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: { return nil }) + //controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) controller.requestController = { [weak controller] _, present in let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let mediaPickerController = MediaPickerScreen(context: context, peer: nil, threadTitle: nil, chatLocation: nil, bannedSendPhotos: nil, bannedSendVideos: nil, subject: .assets(nil, .wallpaper), mainButtonState: canDelete ? AttachmentMainButtonState(text: presentationData.strings.Conversation_Theme_ResetWallpaper, font: .regular, background: .color(.clear), textColor: presentationData.theme.actionSheet.destructiveActionTextColor, isVisible: true, progress: .none, isEnabled: true) : nil, mainButtonAction: canDelete ? { - let _ = context.engine.themes.setChatWallpaper(peerId: peer.id, wallpaper: nil).start() + let mediaPickerController = MediaPickerScreen(context: context, peer: nil, threadTitle: nil, chatLocation: nil, bannedSendPhotos: nil, bannedSendVideos: nil, subject: .assets(nil, .wallpaper), mainButtonState: AttachmentMainButtonState(text: presentationData.strings.Conversation_Theme_SetColorWallpaper, font: .regular, background: .color(.clear), textColor: presentationData.theme.actionSheet.controlAccentColor, isVisible: true, progress: .none, isEnabled: true), mainButtonAction: { controller?.dismiss(animated: true) - } : nil) + openColors() + }) mediaPickerController.customSelection = completion present(mediaPickerController, mediaPickerController.mediaPickerContext) } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift index 1e06aaf916..b5a667f6c0 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift @@ -669,12 +669,8 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present entries.append(.everybody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenEverybody, state.setting == .everybody)) entries.append(.contacts(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenContacts, state.setting == .contacts)) - switch kind { - case .presence, .voiceCalls, .forwards, .phoneNumber, .voiceMessages, .profilePhoto: - entries.append(.nobody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenNobody, state.setting == .nobody)) - case .groupInvitations: - break - } + entries.append(.nobody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenNobody, state.setting == .nobody)) + let phoneLink = "https://t.me/+\(phoneNumber)" if let settingInfoText = settingInfoText { entries.append(.settingInfo(presentationData.theme, settingInfoText, phoneLink)) diff --git a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift index a3831910cd..bc08631acd 100644 --- a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift +++ b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift @@ -25,9 +25,9 @@ func presentCustomWallpaperPicker(context: AccountContext, present: @escaping (V controller.selectionBlock = { [weak legacyController] asset, _ in if let asset = asset { let controller = WallpaperGalleryController(context: context, source: .asset(asset.backingAsset)) - controller.apply = { [weak legacyController, weak controller] wallpaper, mode, cropRect in + controller.apply = { [weak legacyController, weak controller] wallpaper, mode, cropRect, brightness in if let legacyController = legacyController, let controller = controller { - uploadCustomWallpaper(context: context, wallpaper: wallpaper, mode: mode, cropRect: cropRect, completion: { [weak legacyController, weak controller] in + uploadCustomWallpaper(context: context, wallpaper: wallpaper, mode: mode, cropRect: cropRect, brightness: brightness, completion: { [weak legacyController, weak controller] in if let legacyController = legacyController, let controller = controller { legacyController.dismiss() controller.dismiss(forceAway: true) @@ -47,7 +47,7 @@ func presentCustomWallpaperPicker(context: AccountContext, present: @escaping (V }) } -func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, cropRect: CGRect?, completion: @escaping () -> Void) { +func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, cropRect: CGRect?, brightness: CGFloat?, completion: @escaping () -> Void) { let imageSignal: Signal switch wallpaper { case let .wallpaper(wallpaper, _): @@ -131,12 +131,12 @@ func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperGalleryE if let data = croppedImage.jpegData(compressionQuality: 0.8), let thumbnailImage = thumbnailImage, let thumbnailData = thumbnailImage.jpegData(compressionQuality: 0.4) { let thumbnailResource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - context.sharedContext.accountManager.mediaBox.storeResourceData(thumbnailResource.id, data: thumbnailData) - context.account.postbox.mediaBox.storeResourceData(thumbnailResource.id, data: thumbnailData) + context.sharedContext.accountManager.mediaBox.storeResourceData(thumbnailResource.id, data: thumbnailData, synchronous: true) + context.account.postbox.mediaBox.storeResourceData(thumbnailResource.id, data: thumbnailData, synchronous: true) let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data) - context.account.postbox.mediaBox.storeResourceData(resource.id, data: data) + context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) + context.account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) let autoNightModeTriggered = context.sharedContext.currentPresentationData.with {$0 }.autoNightModeTriggered let accountManager = context.sharedContext.accountManager @@ -196,14 +196,14 @@ func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperGalleryE }).start() } -public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, cropRect: CGRect?, brightnessMultiplier: CGFloat?, peerId: PeerId, completion: @escaping () -> Void) { +public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, cropRect: CGRect?, brightness: CGFloat?, peerId: PeerId, completion: @escaping () -> Void) { let imageSignal: Signal switch wallpaper { case let .wallpaper(wallpaper, _): switch wallpaper { case let .file(file): if let path = context.account.postbox.mediaBox.completedResourcePath(file.file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { - context.sharedContext.accountManager.mediaBox.storeResourceData(file.file.resource.id, data: data) + context.sharedContext.accountManager.mediaBox.storeResourceData(file.file.resource.id, data: data, synchronous: true) let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: true, fetch: true).start() let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedBlurredWallpaperRepresentation(), complete: true, fetch: true).start() } @@ -211,7 +211,7 @@ public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: Wallpa for representation in representations { let resource = representation.resource if let path = context.account.postbox.mediaBox.completedResourcePath(resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { - context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data) + context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: true, fetch: true).start() } } @@ -276,7 +276,7 @@ public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: Wallpa croppedImage = TGPhotoEditorCrop(image, nil, .up, 0.0, finalCropRect, false, CGSize(width: 1440.0, height: 2960.0), image.size, true) if mode.contains(.blur) { - croppedImage = blurredImage(croppedImage, radius: 20.0)! + croppedImage = blurredImage(croppedImage, radius: 30.0)! } let thumbnailDimensions = finalCropRect.size.fitted(CGSize(width: 320.0, height: 320.0)) @@ -291,7 +291,12 @@ public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: Wallpa context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) context.account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) - let settings = WallpaperSettings(blur: mode.contains(.blur), motion: mode.contains(.motion), colors: [], intensity: nil) + var intensity: Int32? + if let brightness { + intensity = max(1, Int32(brightness * 100.0)) + } + + let settings = WallpaperSettings(blur: mode.contains(.blur), motion: mode.contains(.motion), colors: [], intensity: intensity) let temporaryWallpaper: TelegramWallpaper = .image([TelegramMediaImageRepresentation(dimensions: PixelDimensions(thumbnailDimensions), resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false), TelegramMediaImageRepresentation(dimensions: PixelDimensions(croppedImage.size), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)], settings) let _ = context.account.postbox.transaction({ transaction in @@ -308,7 +313,7 @@ public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: Wallpa completion() } - let _ = uploadWallpaper(account: context.account, resource: resource, settings: WallpaperSettings(blur: false, motion: mode.contains(.motion), colors: [], intensity: nil), forChat: true).start(next: { status in + let _ = uploadWallpaper(account: context.account, resource: resource, settings: WallpaperSettings(blur: false, motion: mode.contains(.motion), colors: [], intensity: intensity), forChat: true).start(next: { status in if case let .complete(wallpaper) = status { if case let .file(file) = wallpaper { context.account.postbox.mediaBox.copyResourceData(from: resource.id, to: file.file.resource.id, synchronous: true) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift index 41b777cc59..74405b65f1 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift @@ -119,7 +119,11 @@ final class ThemeAccentColorController: ViewController { } else { self.navigationItem.titleView = self.segmentedTitleView } - self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView()) + if case .peer = resultMode { + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) + } else { + self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView()) + } } required init(coder aDecoder: NSCoder) { @@ -129,6 +133,10 @@ final class ThemeAccentColorController: ViewController { deinit { self.applyDisposable.dispose() } + + @objc private func cancelPressed() { + self.dismiss() + } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) @@ -169,6 +177,12 @@ final class ThemeAccentColorController: ViewController { } } + if case let .peer(peer) = strongSelf.resultMode { + let _ = strongSelf.context.engine.themes.setChatWallpaper(peerId: peer.id, wallpaper: coloredWallpaper).start() + strongSelf.completion?() + return + } + let prepareWallpaper: Signal if let patternWallpaper = state.patternWallpaper, case let .file(file) = patternWallpaper, !state.backgroundColors.isEmpty { let resource = file.file.resource diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index 9ead4f5c37..12d8f61d15 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -152,6 +152,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate private let context: AccountContext private var theme: PresentationTheme private let mode: ThemeAccentColorControllerMode + private let resultMode: ThemeAccentColorController.ResultMode private var presentationData: PresentationData private let animationCache: AnimationCache @@ -227,6 +228,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate init(context: AccountContext, mode: ThemeAccentColorControllerMode, resultMode: ThemeAccentColorController.ResultMode, theme: PresentationTheme, wallpaper: TelegramWallpaper, dismiss: @escaping () -> Void, apply: @escaping (ThemeColorState, UIColor?) -> Void, ready: Promise) { self.context = context self.mode = mode + self.resultMode = resultMode self.state = ThemeColorState() self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.theme = theme @@ -766,6 +768,8 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate } else { if case .edit(_, _, _, _, _, true, _) = self.mode { doneButtonType = .proceed + } else if case .peer = self.resultMode { + doneButtonType = .setPeer } else { doneButtonType = .set } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift index a4b30a3610..36a5493534 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift @@ -11,8 +11,8 @@ import TelegramUIPreferences import AccountContext import AttachmentUI -private func availableGradients(theme: PresentationTheme) -> [[UInt32]] { - if theme.overallDarkAppearance { +private func availableGradients(dark: Bool) -> [[UInt32]] { + if dark { return [ [0x1e3557, 0x151a36, 0x1c4352, 0x2a4541] as [UInt32], [0x1d223f, 0x1d1832, 0x1b2943, 0x141631] as [UInt32], @@ -39,8 +39,8 @@ private func availableGradients(theme: PresentationTheme) -> [[UInt32]] { } } -private func availableColors(theme: PresentationTheme) -> [UInt32] { - if theme.overallDarkAppearance { +private func availableColors(dark: Bool) -> [UInt32] { + if dark { return [ 0x1D2D3C, 0x111B26, @@ -149,6 +149,8 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina fileprivate let mainButtonStatePromise = Promise(nil) var pushController: (ViewController) -> Void = { _ in } + var dismissControllers: (() -> Void)? + var openGallery: (() -> Void)? public init(context: AccountContext, mode: Mode = .default, canDelete: Bool = false) { self.context = context @@ -191,9 +193,7 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina self?.push(controller) } - if canDelete { - self.mainButtonStatePromise.set(.single(AttachmentMainButtonState(text: self.presentationData.strings.Conversation_Theme_ResetWallpaper, font: .regular, background: .color(.clear), textColor: self.presentationData.theme.actionSheet.destructiveActionTextColor, isVisible: true, progress: .none, isEnabled: true))) - } + self.mainButtonStatePromise.set(.single(AttachmentMainButtonState(text: self.presentationData.strings.Conversation_Theme_SetPhotoWallpaper, font: .regular, background: .color(.clear), textColor: self.presentationData.theme.actionSheet.controlAccentColor, isVisible: true, progress: .none, isEnabled: true))) } required public init(coder aDecoder: NSCoder) { @@ -240,23 +240,28 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina } let controller = ThemeAccentColorController(context: strongSelf.context, mode: .background(themeReference: themeReference), resultMode: strongSelf.mode.colorPickerMode) - controller.completion = { [weak self] in - if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController { - var controllers = navigationController.viewControllers - controllers = controllers.filter { controller in - if controller is ThemeColorsGridController { - return false + controller.completion = { [weak self, weak controller] in + if let strongSelf = self { + if let dismissControllers = strongSelf.dismissControllers { + dismissControllers() + controller?.dismiss(animated: true) + } else if let navigationController = strongSelf.navigationController as? NavigationController { + var controllers = navigationController.viewControllers + controllers = controllers.filter { controller in + if controller is ThemeColorsGridController { + return false + } + return true } - return true - } - navigationController.setViewControllers(controllers, animated: false) - controllers = controllers.filter { controller in - if controller is ThemeAccentColorController { - return false + navigationController.setViewControllers(controllers, animated: false) + controllers = controllers.filter { controller in + if controller is ThemeAccentColorController { + return false + } + return true } - return true + navigationController.setViewControllers(controllers, animated: true) } - navigationController.setViewControllers(controllers, animated: true) } } strongSelf.pushController(controller) @@ -264,7 +269,11 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina } public override func loadDisplayNode() { - self.displayNode = ThemeColorsGridControllerNode(context: self.context, presentationData: self.presentationData, controller: self, gradients: availableGradients(theme: self.presentationData.theme), colors: availableColors(theme: self.presentationData.theme), push: { [weak self] controller in + var dark = false + if case .default = self.mode { + dark = self.presentationData.theme.overallDarkAppearance + } + self.displayNode = ThemeColorsGridControllerNode(context: self.context, presentationData: self.presentationData, controller: self, gradients: availableGradients(dark: dark), colors: availableColors(dark: dark), push: { [weak self] controller in self?.pushController(controller) }, pop: { [weak self] in if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController { @@ -321,12 +330,8 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina } @objc fileprivate func mainButtonPressed() { - guard case let .peer(peer) = self.mode else { - return - } - - let _ = self.context.engine.themes.setChatWallpaper(peerId: peer.id, wallpaper: nil).start() self.dismiss(animated: true) + self.openGallery?() } public var requestAttachmentMenuExpansion: () -> Void = {} @@ -379,15 +384,26 @@ private final class ThemeColorsGridContext: AttachmentMediaPickerContext { } -public func standaloneColorPickerController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer, canDelete: Bool, push: @escaping (ViewController) -> Void) -> ViewController { +public func standaloneColorPickerController( + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + peer: EnginePeer, + push: @escaping (ViewController) -> Void, + openGallery: @escaping () -> Void +) -> ViewController { let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: { return nil }) + //controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) controller.requestController = { _, present in - let colorPickerController = ThemeColorsGridController(context: context, mode: .peer(peer), canDelete: canDelete) + let colorPickerController = ThemeColorsGridController(context: context, mode: .peer(peer)) colorPickerController.pushController = { controller in push(controller) } + colorPickerController.dismissControllers = { [weak controller] in + controller?.dismiss(animated: true) + } + colorPickerController.openGallery = openGallery present(colorPickerController, colorPickerController.mediaPickerContext) } return controller diff --git a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift index 94bbdcb6d1..387b773374 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift @@ -132,27 +132,33 @@ final class ThemeColorsGridControllerNode: ASDisplayNode { self.addSubnode(self.gridNode) let previousEntries = Atomic<[ThemeColorsGridControllerEntry]?>(value: nil) - - let dismissControllers = { [weak self] in - if let self, let navigationController = self.controller?.navigationController as? NavigationController { - let controllers = navigationController.viewControllers.filter({ controller in - if controller is ThemeColorsGridController || controller is WallpaperGalleryController { - return false - } - return true - }) - navigationController.setViewControllers(controllers, animated: true) - } - } - + let interaction = ThemeColorsGridControllerInteraction(openWallpaper: { [weak self] wallpaper in if let strongSelf = self { let entries = previousEntries.with { $0 } if let entries = entries, !entries.isEmpty { let wallpapers = entries.map { $0.wallpaper } let controller = WallpaperGalleryController(context: context, source: .list(wallpapers: wallpapers, central: wallpaper, type: .colors), mode: strongSelf.controller?.mode.galleryMode ?? .default) + + let dismissControllers = { [weak self, weak controller] in + if let self { + if let dismissControllers = self.controller?.dismissControllers { + dismissControllers() + controller?.dismiss(animated: true) + } else if let navigationController = self.controller?.navigationController as? NavigationController { + let controllers = navigationController.viewControllers.filter({ controller in + if controller is ThemeColorsGridController || controller is WallpaperGalleryController { + return false + } + return true + }) + navigationController.setViewControllers(controllers, animated: true) + } + } + } + controller.navigationPresentation = .modal - controller.apply = { [weak self] wallpaper, _, _ in + controller.apply = { [weak self] wallpaper, _, _, _ in if let strongSelf = self, let mode = strongSelf.controller?.mode, case let .peer(peer) = mode, case let .wallpaper(wallpaperValue, _) = wallpaper { let _ = (strongSelf.context.engine.themes.setChatWallpaper(peerId: peer.id, wallpaper: wallpaperValue) |> deliverOnMainQueue).start(completed: { diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift b/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift index 14658adb61..cef1f6070c 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift @@ -120,9 +120,9 @@ public final class ThemeGridController: ViewController { self.displayNode = ThemeGridControllerNode(context: self.context, presentationData: self.presentationData, presentPreviewController: { [weak self] source in if let strongSelf = self { let controller = WallpaperGalleryController(context: strongSelf.context, source: source) - controller.apply = { [weak self, weak controller] wallpaper, options, cropRect in + controller.apply = { [weak self, weak controller] wallpaper, options, cropRect, brightness in if let strongSelf = self { - uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, completion: { [weak self, weak controller] in + uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, brightness: brightness, completion: { [weak self, weak controller] in if let strongSelf = self { strongSelf.deactivateSearch(animated: false) strongSelf.controllerNode.scrollToTop(animated: false) @@ -148,9 +148,9 @@ public final class ThemeGridController: ViewController { return } let controller = WallpaperGalleryController(context: strongSelf.context, source: .asset(asset)) - controller.apply = { [weak self, weak controller] wallpaper, options, cropRect in + controller.apply = { [weak self, weak controller] wallpaper, options, cropRect, brightness in if let strongSelf = self, let controller = controller { - uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, completion: { [weak controller] in + uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, brightness: brightness, completion: { [weak controller] in if let controller = controller { controller.dismiss(forceAway: true) } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift index 93eab9cfd5..0e3fd9f894 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift @@ -58,13 +58,13 @@ private final class ThemeSettingsControllerArguments { let openBubbleSettings: () -> Void let openPowerSavingSettings: () -> Void let openStickersAndEmoji: () -> Void - let disableAnimations: (Bool) -> Void + let toggleShowNextMediaOnTap: (Bool) -> Void let selectAppIcon: (PresentationAppIcon) -> Void let editTheme: (PresentationCloudTheme) -> Void let themeContextAction: (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void let colorContextAction: (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void - init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, openThemeSettings: @escaping () -> Void, openWallpaperSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference, Bool) -> Void, toggleNightTheme: @escaping (Bool) -> Void, openAutoNightTheme: @escaping () -> Void, openTextSize: @escaping () -> Void, openBubbleSettings: @escaping () -> Void, openPowerSavingSettings: @escaping () -> Void, openStickersAndEmoji: @escaping () -> Void, disableAnimations: @escaping (Bool) -> Void, selectAppIcon: @escaping (PresentationAppIcon) -> Void, editTheme: @escaping (PresentationCloudTheme) -> Void, themeContextAction: @escaping (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void, colorContextAction: @escaping (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void) { + init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, openThemeSettings: @escaping () -> Void, openWallpaperSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference, Bool) -> Void, toggleNightTheme: @escaping (Bool) -> Void, openAutoNightTheme: @escaping () -> Void, openTextSize: @escaping () -> Void, openBubbleSettings: @escaping () -> Void, openPowerSavingSettings: @escaping () -> Void, openStickersAndEmoji: @escaping () -> Void, toggleShowNextMediaOnTap: @escaping (Bool) -> Void, selectAppIcon: @escaping (PresentationAppIcon) -> 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.openThemeSettings = openThemeSettings @@ -77,7 +77,7 @@ private final class ThemeSettingsControllerArguments { self.openBubbleSettings = openBubbleSettings self.openPowerSavingSettings = openPowerSavingSettings self.openStickersAndEmoji = openStickersAndEmoji - self.disableAnimations = disableAnimations + self.toggleShowNextMediaOnTap = toggleShowNextMediaOnTap self.selectAppIcon = selectAppIcon self.editTheme = editTheme self.themeContextAction = themeContextAction @@ -128,7 +128,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { case powerSaving case stickersAndEmoji case otherHeader(PresentationTheme, String) - case animations(PresentationTheme, String, Bool) + case showNextMediaOnTap(PresentationTheme, String, Bool) case animationsInfo(PresentationTheme, String) var section: ItemListSectionId { @@ -143,7 +143,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { return ThemeSettingsControllerSection.icon.rawValue case .powerSaving, .stickersAndEmoji: return ThemeSettingsControllerSection.message.rawValue - case .otherHeader, .animations, .animationsInfo: + case .otherHeader, .showNextMediaOnTap, .animationsInfo: return ThemeSettingsControllerSection.other.rawValue } } @@ -178,7 +178,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { return 12 case .otherHeader: return 13 - case .animations: + case .showNextMediaOnTap: return 14 case .animationsInfo: return 15 @@ -271,8 +271,8 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { } else { return false } - case let .animations(lhsTheme, lhsTitle, lhsValue): - if case let .animations(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue { + case let .showNextMediaOnTap(lhsTheme, lhsTitle, lhsValue): + if case let .showNextMediaOnTap(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue { return true } else { return false @@ -343,9 +343,9 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { }) case let .otherHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) - case let .animations(_, title, value): + case let .showNextMediaOnTap(_, title, value): return ItemListSwitchItem(presentationData: presentationData, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in - arguments.disableAnimations(value) + arguments.toggleShowNextMediaOnTap(value) }, tag: ThemeSettingsEntryTag.animations) case let .animationsInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) @@ -353,7 +353,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { } } -private func themeSettingsControllerEntries(presentationData: PresentationData, presentationThemeSettings: PresentationThemeSettings, themeReference: PresentationThemeReference, availableThemes: [PresentationThemeReference], availableAppIcons: [PresentationAppIcon], currentAppIconName: String?, isPremium: Bool, chatThemes: [PresentationThemeReference], animatedEmojiStickers: [String: [StickerPackItem]]) -> [ThemeSettingsControllerEntry] { +private func themeSettingsControllerEntries(presentationData: PresentationData, presentationThemeSettings: PresentationThemeSettings, mediaSettings: MediaDisplaySettings, themeReference: PresentationThemeReference, availableThemes: [PresentationThemeReference], availableAppIcons: [PresentationAppIcon], currentAppIconName: String?, isPremium: Bool, chatThemes: [PresentationThemeReference], animatedEmojiStickers: [String: [StickerPackItem]]) -> [ThemeSettingsControllerEntry] { var entries: [ThemeSettingsControllerEntry] = [] let strings = presentationData.strings @@ -404,8 +404,7 @@ private func themeSettingsControllerEntries(presentationData: PresentationData, } entries.append(.otherHeader(presentationData.theme, strings.Appearance_Other.uppercased())) - entries.append(.animations(presentationData.theme, strings.Appearance_ReduceMotion, presentationData.reduceMotion)) - entries.append(.animationsInfo(presentationData.theme, strings.Appearance_ReduceMotionInfo)) + entries.append(.showNextMediaOnTap(presentationData.theme, strings.Appearance_ShowNextMediaOnTap, mediaSettings.showNextMediaOnTap)) return entries } @@ -522,9 +521,9 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The pushControllerImpl?(installedStickerPacksController(context: context, mode: .general, archivedPacks: archivedStickerPacks, updatedPacks: { _ in })) }) - }, disableAnimations: { reduceMotion in - let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in - return current.withUpdatedReduceMotion(reduceMotion) + }, toggleShowNextMediaOnTap: { value in + let _ = updateMediaDisplaySettingsInteractively(accountManager: context.sharedContext.accountManager, { current in + return current.withUpdatedShowNextMediaOnTap(value) }).start() }, selectAppIcon: { icon in let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) @@ -1000,10 +999,11 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The }) }) - let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings, SharedDataKeys.chatThemes]), cloudThemes.get(), availableAppIcons, currentAppIconName.get(), removedThemeIndexesPromise.get(), animatedEmojiStickers, context.account.postbox.peerView(id: context.account.peerId)) + let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings, SharedDataKeys.chatThemes, ApplicationSpecificSharedDataKeys.mediaDisplaySettings]), cloudThemes.get(), availableAppIcons, currentAppIconName.get(), removedThemeIndexesPromise.get(), animatedEmojiStickers, context.account.postbox.peerView(id: context.account.peerId)) |> map { presentationData, sharedData, cloudThemes, availableAppIcons, currentAppIconName, removedThemeIndexes, animatedEmojiStickers, peerView -> (ItemListControllerState, (ItemListNodeState, Any)) in let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings]?.get(PresentationThemeSettings.self) ?? PresentationThemeSettings.defaultSettings - + let mediaSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.mediaDisplaySettings]?.get(MediaDisplaySettings.self) ?? MediaDisplaySettings.defaultSettings + let isPremium = peerView.peers[peerView.peerId]?.isPremium ?? false let themeReference: PresentationThemeReference @@ -1041,7 +1041,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The chatThemes.insert(.builtin(.dayClassic), at: 0) let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.Appearance_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: themeSettingsControllerEntries(presentationData: presentationData, presentationThemeSettings: settings, themeReference: themeReference, availableThemes: availableThemes, availableAppIcons: availableAppIcons, currentAppIconName: currentAppIconName, isPremium: isPremium, chatThemes: chatThemes, animatedEmojiStickers: animatedEmojiStickers), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: themeSettingsControllerEntries(presentationData: presentationData, presentationThemeSettings: settings, mediaSettings: mediaSettings, themeReference: themeReference, availableThemes: availableThemes, availableAppIcons: availableAppIcons, currentAppIconName: currentAppIconName, isPremium: isPremium, chatThemes: chatThemes, animatedEmojiStickers: animatedEmojiStickers), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false) return (controllerState, (listState, arguments)) } diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift index e0e357ec39..3dc081ba42 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift @@ -176,7 +176,7 @@ public class WallpaperGalleryController: ViewController { private let context: AccountContext private let source: WallpaperListSource private let mode: Mode - public var apply: ((WallpaperGalleryEntry, WallpaperPresentationOptions, CGRect?) -> Void)? + public var apply: ((WallpaperGalleryEntry, WallpaperPresentationOptions, CGRect?, CGFloat?) -> Void)? private let _ready = Promise() override public var ready: Promise { @@ -437,6 +437,12 @@ public class WallpaperGalleryController: ViewController { } let toolbarNode = WallpaperGalleryToolbarNode(theme: presentationData.theme, strings: presentationData.strings, doneButtonType: doneButtonType) + switch self.source { + case .asset, .contextResult: + toolbarNode.dark = false + default: + toolbarNode.dark = true + } self.toolbarNode = toolbarNode overlayNode.addSubnode(toolbarNode) @@ -453,7 +459,7 @@ public class WallpaperGalleryController: ViewController { let entry = strongSelf.entries[centralItemNode.index] if case .peer = strongSelf.mode { - strongSelf.apply?(entry, options, centralItemNode.cropRect) + strongSelf.apply?(entry, options, centralItemNode.cropRect, centralItemNode.brightness) return } @@ -611,7 +617,7 @@ public class WallpaperGalleryController: ViewController { break } - strongSelf.apply?(entry, options, centralItemNode.cropRect) + strongSelf.apply?(entry, options, centralItemNode.cropRect, centralItemNode.brightness) } } } diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift index a63e7ff887..77098e8839 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift @@ -19,6 +19,8 @@ import WallpaperResources import AppBundle import WallpaperBackgroundNode import TextFormat +import TooltipUI +import TelegramNotices struct WallpaperGalleryItemArguments { let colorPreview: Bool @@ -83,7 +85,7 @@ private func reference(for resource: MediaResource, media: Media, message: Messa final class WallpaperGalleryItemNode: GalleryItemNode { private let context: AccountContext - private let presentationData: PresentationData + private var presentationData: PresentationData var entry: WallpaperGalleryEntry? var source: WallpaperListSource? @@ -95,23 +97,28 @@ final class WallpaperGalleryItemNode: GalleryItemNode { let wrapperNode: ASDisplayNode let imageNode: TransformImageNode let nativeNode: WallpaperBackgroundNode + let brightnessNode: ASDisplayNode private let statusNode: RadialStatusNode private let blurredNode: BlurredImageNode let cropNode: WallpaperCropNode - private var cancelButtonNode: WallpaperNavigationButtonNode - private var shareButtonNode: WallpaperNavigationButtonNode + private let cancelButtonNode: WallpaperNavigationButtonNode + private let shareButtonNode: WallpaperNavigationButtonNode + private let dayNightButtonNode: WallpaperNavigationButtonNode - private var blurButtonNode: WallpaperOptionButtonNode - private var motionButtonNode: WallpaperOptionButtonNode - private var patternButtonNode: WallpaperOptionButtonNode - private var colorsButtonNode: WallpaperOptionButtonNode - private var playButtonNode: WallpaperNavigationButtonNode + private let blurButtonNode: WallpaperOptionButtonNode + private let motionButtonNode: WallpaperOptionButtonNode + private let patternButtonNode: WallpaperOptionButtonNode + private let colorsButtonNode: WallpaperOptionButtonNode + private let playButtonNode: WallpaperNavigationButtonNode + private let sliderNode: WallpaperSliderNode private let messagesContainerNode: ASDisplayNode private var messageNodes: [ListViewItemNode]? private var validMessages: [String]? + private let serviceBackgroundNode: NavigationBackgroundNode + fileprivate let _ready = Promise() private let fetchDisposable = MetaDisposable() private let statusDisposable = MetaDisposable() @@ -135,10 +142,16 @@ final class WallpaperGalleryItemNode: GalleryItemNode { private var isReadyDisposable: Disposable? + private var isDarkAppearance: Bool = false + private var didChangeAppearance: Bool = false + private var darkAppearanceIntensity: CGFloat = 0.8 + init(context: AccountContext) { self.context = context self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.isDarkAppearance = self.presentationData.theme.overallDarkAppearance + self.wrapperNode = ASDisplayNode() self.imageNode = TransformImageNode() self.imageNode.contentAnimations = .subsequentUpdates @@ -149,6 +162,8 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.statusNode.isUserInteractionEnabled = false self.blurredNode = BlurredImageNode() + self.brightnessNode = ASDisplayNode() + self.brightnessNode.alpha = 0.0 self.messagesContainerNode = ASDisplayNode() self.messagesContainerNode.transform = CATransform3DMakeScale(1.0, -1.0, 1.0) @@ -160,12 +175,24 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.motionButtonNode.setEnabled(false) self.patternButtonNode = WallpaperOptionButtonNode(title: self.presentationData.strings.WallpaperPreview_Pattern, value: .check(false)) self.patternButtonNode.setEnabled(false) + + self.serviceBackgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x333333, alpha: 0.33)) + self.serviceBackgroundNode.isHidden = true + + var sliderValueChangedImpl: ((CGFloat) -> Void)? + self.sliderNode = WallpaperSliderNode(minValue: 0.0, maxValue: 1.0, value: 0.7, valueChanged: { value, _ in + sliderValueChangedImpl?(value) + }) self.colorsButtonNode = WallpaperOptionButtonNode(title: self.presentationData.strings.WallpaperPreview_WallpaperColors, value: .colors(false, [.clear])) - self.cancelButtonNode = WallpaperNavigationButtonNode(content: .text(self.presentationData.strings.Common_Cancel)) - self.shareButtonNode = WallpaperNavigationButtonNode(content: .icon(image: UIImage(bundleImageName: "Chat/Links/Share"), size: CGSize(width: 28.0, height: 28.0))) - + self.cancelButtonNode = WallpaperNavigationButtonNode(content: .text(self.presentationData.strings.Common_Cancel), dark: true) + self.cancelButtonNode.enableSaturation = true + self.shareButtonNode = WallpaperNavigationButtonNode(content: .icon(image: UIImage(bundleImageName: "Chat/Links/Share"), size: CGSize(width: 28.0, height: 28.0)), dark: true) + self.shareButtonNode.enableSaturation = true + self.dayNightButtonNode = WallpaperNavigationButtonNode(content: .dayNight(isNight: self.isDarkAppearance), dark: true) + self.dayNightButtonNode.enableSaturation = true + self.playButtonPlayImage = generateImage(CGSize(width: 48.0, height: 48.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.setFillColor(UIColor.white.cgColor) @@ -193,7 +220,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.playButtonRotateImage = generateTintedImage(image: UIImage(bundleImageName: "Settings/ThemeColorRotateIcon"), color: .white) - self.playButtonNode = WallpaperNavigationButtonNode(content: .icon(image: self.playButtonPlayImage, size: CGSize(width: 48.0, height: 48.0))) + self.playButtonNode = WallpaperNavigationButtonNode(content: .icon(image: self.playButtonPlayImage, size: CGSize(width: 48.0, height: 48.0)), dark: true) super.init() @@ -217,6 +244,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.addSubnode(self.wrapperNode) //self.addSubnode(self.statusNode) + self.addSubnode(self.serviceBackgroundNode) self.addSubnode(self.messagesContainerNode) self.addSubnode(self.blurButtonNode) @@ -224,8 +252,12 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.addSubnode(self.patternButtonNode) self.addSubnode(self.colorsButtonNode) self.addSubnode(self.playButtonNode) + self.addSubnode(self.sliderNode) self.addSubnode(self.cancelButtonNode) self.addSubnode(self.shareButtonNode) + self.addSubnode(self.dayNightButtonNode) + + self.imageNode.addSubnode(self.brightnessNode) self.blurButtonNode.addTarget(self, action: #selector(self.toggleBlur), forControlEvents: .touchUpInside) self.motionButtonNode.addTarget(self, action: #selector(self.toggleMotion), forControlEvents: .touchUpInside) @@ -234,6 +266,13 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.playButtonNode.addTarget(self, action: #selector(self.togglePlay), forControlEvents: .touchUpInside) self.cancelButtonNode.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside) self.shareButtonNode.addTarget(self, action: #selector(self.actionPressed), forControlEvents: .touchUpInside) + self.dayNightButtonNode.addTarget(self, action: #selector(self.dayNightPressed), forControlEvents: .touchUpInside) + + sliderValueChangedImpl = { [weak self] value in + if let self { + self.updateIntensity(transition: .immediate) + } + } } deinit { @@ -255,6 +294,18 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } } + var brightness: CGFloat? { + guard let entry = self.entry else { + return nil + } + switch entry { + case .asset, .contextResult: + return self.sliderNode.value + default: + return nil + } + } + override func ready() -> Signal { return self._ready.get() } @@ -263,6 +314,173 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.action?() } + private func switchTheme() { + if let messageNodes = self.messageNodes { + for messageNode in messageNodes.prefix(2) { + if let snapshotView = messageNode.view.snapshotContentTree() { + messageNode.view.addSubview(snapshotView) + snapshotView.frame = messageNode.bounds + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.35, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } + } + } + let themeSettings = self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings]) + |> map { sharedData -> PresentationThemeSettings in + let themeSettings: PresentationThemeSettings + if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings]?.get(PresentationThemeSettings.self) { + themeSettings = current + } else { + themeSettings = PresentationThemeSettings.defaultSettings + } + return themeSettings + } + + let _ = (themeSettings + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] themeSettings in + guard let strongSelf = self else { + return + } + var presentationData = strongSelf.presentationData + + let lightTheme: PresentationTheme + let lightWallpaper: TelegramWallpaper + + let darkTheme: PresentationTheme + let darkWallpaper: TelegramWallpaper + + if !strongSelf.isDarkAppearance { + darkTheme = presentationData.theme + darkWallpaper = presentationData.chatWallpaper + + var currentColors = themeSettings.themeSpecificAccentColors[themeSettings.theme.index] + if let colors = currentColors, colors.baseColor == .theme { + currentColors = nil + } + + let themeSpecificWallpaper = (themeSettings.themeSpecificChatWallpapers[coloredThemeIndex(reference: themeSettings.theme, accentColor: currentColors)] ?? themeSettings.themeSpecificChatWallpapers[themeSettings.theme.index]) + + if let themeSpecificWallpaper = themeSpecificWallpaper { + lightWallpaper = themeSpecificWallpaper + } else { + let theme = makePresentationTheme(mediaBox: strongSelf.context.sharedContext.accountManager.mediaBox, themeReference: themeSettings.theme, accentColor: currentColors?.color, bubbleColors: currentColors?.customBubbleColors ?? [], wallpaper: currentColors?.wallpaper, baseColor: currentColors?.baseColor, preview: true) ?? defaultPresentationTheme + lightWallpaper = theme.chat.defaultWallpaper + } + + var preferredBaseTheme: TelegramBaseTheme? + if let baseTheme = themeSettings.themePreferredBaseTheme[themeSettings.theme.index], [.classic, .day].contains(baseTheme) { + preferredBaseTheme = baseTheme + } + + lightTheme = makePresentationTheme(mediaBox: strongSelf.context.sharedContext.accountManager.mediaBox, themeReference: themeSettings.theme, baseTheme: preferredBaseTheme, accentColor: currentColors?.color, bubbleColors: currentColors?.customBubbleColors ?? [], wallpaper: currentColors?.wallpaper, baseColor: currentColors?.baseColor, serviceBackgroundColor: defaultServiceBackgroundColor) ?? defaultPresentationTheme + } else { + lightTheme = presentationData.theme + lightWallpaper = presentationData.chatWallpaper + + let automaticTheme = themeSettings.automaticThemeSwitchSetting.theme + let effectiveColors = themeSettings.themeSpecificAccentColors[automaticTheme.index] + let themeSpecificWallpaper = (themeSettings.themeSpecificChatWallpapers[coloredThemeIndex(reference: automaticTheme, accentColor: effectiveColors)] ?? themeSettings.themeSpecificChatWallpapers[automaticTheme.index]) + + var preferredBaseTheme: TelegramBaseTheme? + if let baseTheme = themeSettings.themePreferredBaseTheme[automaticTheme.index], [.night, .tinted].contains(baseTheme) { + preferredBaseTheme = baseTheme + } else { + preferredBaseTheme = .night + } + + darkTheme = makePresentationTheme(mediaBox: strongSelf.context.sharedContext.accountManager.mediaBox, themeReference: automaticTheme, baseTheme: preferredBaseTheme, accentColor: effectiveColors?.color, bubbleColors: effectiveColors?.customBubbleColors ?? [], wallpaper: effectiveColors?.wallpaper, baseColor: effectiveColors?.baseColor, serviceBackgroundColor: defaultServiceBackgroundColor) ?? defaultPresentationTheme + + if let themeSpecificWallpaper = themeSpecificWallpaper { + darkWallpaper = themeSpecificWallpaper + } else { + switch lightWallpaper { + case .builtin, .color, .gradient: + darkWallpaper = darkTheme.chat.defaultWallpaper + case .file: + if lightWallpaper.isPattern { + darkWallpaper = darkTheme.chat.defaultWallpaper + } else { + darkWallpaper = lightWallpaper + } + default: + darkWallpaper = lightWallpaper + } + } + } + + if strongSelf.isDarkAppearance { + darkTheme.forceSync = true + Queue.mainQueue().after(1.0, { + darkTheme.forceSync = false + }) + presentationData = presentationData.withUpdated(theme: darkTheme).withUpdated(chatWallpaper: darkWallpaper) + } else { + lightTheme.forceSync = true + Queue.mainQueue().after(1.0, { + lightTheme.forceSync = false + }) + presentationData = presentationData.withUpdated(theme: lightTheme).withUpdated(chatWallpaper: lightWallpaper) + } + + strongSelf.presentationData = presentationData + strongSelf.nativeNode.updateBubbleTheme(bubbleTheme: presentationData.theme, bubbleCorners: presentationData.chatBubbleCorners) + + if let (layout, _) = strongSelf.validLayout { + strongSelf.updateMessagesLayout(layout: layout, offset: CGPoint(), transition: .animated(duration: 0.3, curve: .easeInOut)) + } + }) + } + + @objc private func dayNightPressed() { + self.isDarkAppearance = !self.isDarkAppearance + self.dayNightButtonNode.setIsNight(self.isDarkAppearance) + + if let layout = self.validLayout?.0 { + let offset = CGPoint(x: self.validOffset ?? 0.0, y: 0.0) + let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) + self.updateButtonsLayout(layout: layout, offset: offset, transition: transition) + self.updateMessagesLayout(layout: layout, offset: offset, transition: transition) + + if !self.didChangeAppearance { + self.didChangeAppearance = true + self.animateIntensityChange(delay: 0.15) + } else { + self.updateIntensity(transition: .animated(duration: 0.3, curve: .easeInOut)) + } + } + + self.switchTheme() + } + + private func animateIntensityChange(delay: Double) { + let targetValue: CGFloat = self.sliderNode.value + self.sliderNode.internalUpdateLayout(size: self.sliderNode.frame.size, value: 1.0) + self.sliderNode.ignoreUpdates = true + Queue.mainQueue().after(delay, { + self.brightnessNode.backgroundColor = UIColor(rgb: 0x000000) + self.brightnessNode.layer.compositingFilter = nil + + self.sliderNode.ignoreUpdates = false + let transition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .easeInOut) + self.sliderNode.animateValue(from: 1.0, to: targetValue, transition: transition) + self.updateIntensity(transition: transition) + }) + } + + private func updateIntensity(transition: ContainedViewLayoutTransition) { + let value = self.isDarkAppearance ? self.sliderNode.value : 1.0 + if value < 1.0 { + self.brightnessNode.backgroundColor = UIColor(rgb: 0x000000) + self.brightnessNode.layer.compositingFilter = nil + transition.updateAlpha(node: self.brightnessNode, alpha: 1.0 - value) + } else { + self.brightnessNode.layer.compositingFilter = nil + transition.updateAlpha(node: self.brightnessNode, alpha: 0.0) + } + } + @objc private func cancelPressed() { self.dismiss() } @@ -281,6 +499,8 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } } + var showPreviewTooltip = false + if self.entry != entry || self.arguments.colorPreview != previousArguments.colorPreview { let previousEntry = self.entry self.entry = entry @@ -308,7 +528,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { let progressAction = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: presentationData.theme.rootController.navigationBar.accentTextColor)) var isBlurrable = true - + self.nativeNode.updateBubbleTheme(bubbleTheme: presentationData.theme, bubbleCorners: presentationData.chatBubbleCorners) switch entry { @@ -345,7 +565,9 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } case .asset: self.nativeNode._internalUpdateIsSettingUpWallpaper() - self.nativeNode.isHidden = true + + //self.nativeNode.update(wallpaper: .color(0xff000000)) + self.nativeNode.isHidden = false self.patternButtonNode.isSelected = false self.playButtonNode.setIcon(self.playButtonRotateImage) default: @@ -353,7 +575,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.patternButtonNode.isSelected = false self.playButtonNode.setIcon(self.playButtonRotateImage) } - + var canShare = false switch entry { case let .wallpaper(wallpaper, message): @@ -526,6 +748,8 @@ final class WallpaperGalleryItemNode: GalleryItemNode { subtitleSignal = .single(nil) colorSignal = .single(UIColor(rgb: 0x000000, alpha: 0.3)) self.wrapperNode.addSubnode(self.cropNode) + showPreviewTooltip = true + self.serviceBackgroundNode.isHidden = false case let .contextResult(result): var imageDimensions: CGSize? var imageResource: TelegramMediaResource? @@ -580,6 +804,8 @@ final class WallpaperGalleryItemNode: GalleryItemNode { colorSignal = .single(UIColor(rgb: 0x000000, alpha: 0.3)) subtitleSignal = .single(nil) self.wrapperNode.addSubnode(self.cropNode) + showPreviewTooltip = true + self.serviceBackgroundNode.isHidden = false } self.contentSize = contentSize @@ -590,6 +816,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.wrapperNode.addSubnode(self.imageNode) self.wrapperNode.addSubnode(self.nativeNode) } else { + self.wrapperNode.insertSubnode(self.nativeNode, at: 0) self.imageNode.contentMode = .scaleToFill } @@ -609,6 +836,14 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } strongSelf.blurredNode.image = image imagePromise.set(.single(image)) + + if case .asset = entry, let image, let data = image.jpegData(compressionQuality: 0.5) { + let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) + strongSelf.context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) + + let wallpaper: TelegramWallpaper = .image([TelegramMediaImageRepresentation(dimensions: PixelDimensions(image.size), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)], WallpaperSettings()) + strongSelf.nativeNode.update(wallpaper: wallpaper) + } } } self.fetchDisposable.set(fetchSignal.start()) @@ -660,6 +895,18 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.updateButtonsLayout(layout: layout, offset: CGPoint(), transition: .immediate) self.updateMessagesLayout(layout: layout, offset: CGPoint(), transition: .immediate) } + + if showPreviewTooltip { + Queue.mainQueue().after(0.35) { + self.maybePresentPreviewTooltip() + } + if self.isDarkAppearance && !self.didChangeAppearance { + Queue.mainQueue().justDispatch { + self.didChangeAppearance = true + self.animateIntensityChange(delay: 0.35) + } + } + } } override func screenFrameUpdated(_ frame: CGRect) { @@ -721,7 +968,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } func setBlurEnabled(_ enabled: Bool, animated: Bool) { - let blurRadius: CGFloat = 45.0 + let blurRadius: CGFloat = 30.0 var animated = animated if animated, let (layout, _) = self.validLayout { @@ -732,13 +979,8 @@ final class WallpaperGalleryItemNode: GalleryItemNode { if enabled { if self.blurredNode.supernode == nil { - if self.cropNode.supernode != nil { - self.blurredNode.frame = self.imageNode.bounds - self.imageNode.addSubnode(self.blurredNode) - } else { - self.blurredNode.frame = self.imageNode.bounds - self.imageNode.addSubnode(self.blurredNode) - } + self.blurredNode.frame = self.imageNode.bounds + self.imageNode.insertSubnode(self.blurredNode, at: 0) } if animated { @@ -914,12 +1156,17 @@ final class WallpaperGalleryItemNode: GalleryItemNode { let buttonSize = CGSize(width: maxButtonWidth, height: 30.0) let alpha = 1.0 - min(1.0, max(0.0, abs(offset.y) / 50.0)) - let additionalYOffset: CGFloat = 0.0 - /*if self.patternButtonNode.isSelected { - additionalYOffset = -235.0 - } else if self.colorsButtonNode.isSelected { - additionalYOffset = -235.0 - }*/ + var additionalYOffset: CGFloat = 0.0 + if let source = self.source { + switch source { + case .asset, .contextResult: + if self.isDarkAppearance { + additionalYOffset -= 44.0 + } + default: + break + } + } let buttonSpacing: CGFloat = 18.0 @@ -937,13 +1184,26 @@ final class WallpaperGalleryItemNode: GalleryItemNode { var motionFrame = centerButtonFrame var motionAlpha: CGFloat = 0.0 - + var colorsFrame = CGRect(origin: CGPoint(x: rightButtonFrame.maxX - colorsButtonSize.width, y: rightButtonFrame.minY), size: colorsButtonSize) var colorsAlpha: CGFloat = 0.0 let playFrame = CGRect(origin: CGPoint(x: centerButtonFrame.midX - playButtonSize.width / 2.0, y: centerButtonFrame.midY - playButtonSize.height / 2.0), size: playButtonSize) var playAlpha: CGFloat = 0.0 + let sliderSize = CGSize(width: 268.0, height: 30.0) + var sliderFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - sliderSize.width) / 2.0) + offset.x, y: layout.size.height - toolbarHeight - layout.intrinsicInsets.bottom - 52.0 + offset.y), size: sliderSize) + var sliderAlpha: CGFloat = 0.0 + var sliderScale: CGFloat = 0.2 + if !additionalYOffset.isZero { + sliderAlpha = 1.0 + sliderScale = 1.0 + } else { + sliderFrame = sliderFrame.offsetBy(dx: 0.0, dy: 22.0) + } + + var dayNightHidden = true + let cancelSize = self.cancelButtonNode.measure(layout.size) let cancelFrame = CGRect(origin: CGPoint(x: 16.0 + offset.x, y: 16.0), size: cancelSize) @@ -958,11 +1218,13 @@ final class WallpaperGalleryItemNode: GalleryItemNode { blurFrame = leftButtonFrame motionAlpha = 1.0 motionFrame = rightButtonFrame + dayNightHidden = false case .contextResult: blurAlpha = 1.0 blurFrame = leftButtonFrame motionAlpha = 1.0 motionFrame = rightButtonFrame + dayNightHidden = false case let .wallpaper(wallpaper, _): switch wallpaper { case .builtin: @@ -1047,17 +1309,21 @@ final class WallpaperGalleryItemNode: GalleryItemNode { transition.updateAlpha(node: self.playButtonNode, alpha: playAlpha * alpha) transition.updateSublayerTransformScale(node: self.playButtonNode, scale: max(0.1, playAlpha)) + transition.updateFrameAsPositionAndBounds(node: self.sliderNode, frame: sliderFrame) + transition.updateAlpha(node: self.sliderNode, alpha: sliderAlpha * alpha) + transition.updateTransformScale(node: self.sliderNode, scale: sliderScale) + self.sliderNode.updateLayout(size: sliderFrame.size) + transition.updateFrame(node: self.cancelButtonNode, frame: cancelFrame) transition.updateFrame(node: self.shareButtonNode, frame: shareFrame) + transition.updateFrame(node: self.dayNightButtonNode, frame: shareFrame) + + self.dayNightButtonNode.isHidden = dayNightHidden } private func updateMessagesLayout(layout: ContainerViewLayout, offset: CGPoint, transition: ContainedViewLayoutTransition) { - let bottomInset: CGFloat = 132.0 + var bottomInset: CGFloat = 132.0 - if self.patternButtonNode.isSelected || self.colorsButtonNode.isSelected { - //bottomInset = 350.0 - } - var items: [ListViewItem] = [] let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)) let otherPeerId = self.context.account.peerId @@ -1125,6 +1391,9 @@ final class WallpaperGalleryItemNode: GalleryItemNode { case .asset, .contextResult: topMessageText = presentationData.strings.WallpaperPreview_CropTopText bottomMessageText = presentationData.strings.WallpaperPreview_CropBottomText + if self.isDarkAppearance { + bottomInset += 44.0 + } case .customColor: topMessageText = presentationData.strings.WallpaperPreview_CustomColorTopText bottomMessageText = presentationData.strings.WallpaperPreview_CustomColorBottomText @@ -1139,7 +1408,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } } - let theme = self.presentationData.theme.withUpdated(preview: true) + let theme = self.presentationData.theme let message1 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: bottomMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message1], theme: theme, strings: self.presentationData.strings, wallpaper: currentWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.nativeNode, availableReactions: nil, isCentered: false)) @@ -1157,18 +1426,15 @@ final class WallpaperGalleryItemNode: GalleryItemNode { let params = ListViewItemLayoutParams(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, availableHeight: layout.size.height) if let messageNodes = self.messageNodes { - if self.validMessages != [topMessageText, bottomMessageText] { - self.validMessages = [topMessageText, bottomMessageText] - for i in 0 ..< items.count { - items[i].updateNode(async: { f in f() }, node: { return messageNodes[i] }, params: params, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], animation: .None) { layout, apply in - let nodeFrame = CGRect(origin: messageNodes[i].frame.origin, size: CGSize(width: layout.size.width, height: layout.size.height)) + for i in 0 ..< items.count { + items[i].updateNode(async: { f in f() }, node: { return messageNodes[i] }, params: params, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], animation: .None) { layout, apply in + let nodeFrame = CGRect(origin: messageNodes[i].frame.origin, size: CGSize(width: layout.size.width, height: layout.size.height)) - messageNodes[i].contentSize = layout.contentSize - messageNodes[i].insets = layout.insets - messageNodes[i].frame = nodeFrame + messageNodes[i].contentSize = layout.contentSize + messageNodes[i].insets = layout.insets + messageNodes[i].frame = nodeFrame - apply(ListViewItemApply(isOnScreen: true)) - } + apply(ListViewItemApply(isOnScreen: true)) } } } else { @@ -1187,7 +1453,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } self.messageNodes = messageNodes } - + let alpha = 1.0 - min(1.0, max(0.0, abs(offset.y) / 50.0)) if let messageNodes = self.messageNodes { @@ -1199,6 +1465,15 @@ final class WallpaperGalleryItemNode: GalleryItemNode { transition.updateAlpha(node: itemNode, alpha: alpha) } } + + if let _ = serviceMessageText, let messageNodes = self.messageNodes, let node = messageNodes.last { + if let backgroundNode = node.subnodes?.first?.subnodes?.first?.subnodes?.first?.subnodes?.first, let backdropNode = node.subnodes?.first?.subnodes?.first?.subnodes?.first?.subnodes?.last?.subnodes?.last?.subnodes?.first { + backdropNode.isHidden = true + let serviceBackgroundFrame = backgroundNode.view.convert(backgroundNode.bounds, to: self.view).offsetBy(dx: 0.0, dy: -1.0).insetBy(dx: 0.0, dy: -1.0) + transition.updateFrame(node: self.serviceBackgroundNode, frame: serviceBackgroundFrame) + self.serviceBackgroundNode.update(size: serviceBackgroundFrame.size, cornerRadius: serviceBackgroundFrame.height / 2.0, transition: transition) + } + } } override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { @@ -1236,7 +1511,18 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.cropNode.zoom(to: CGRect(x: (contentSize.width - fittedSize.width) / 2.0, y: (contentSize.height - fittedSize.height) / 2.0, width: fittedSize.width, height: fittedSize.height)) } self.blurredNode.frame = self.imageNode.bounds + + let displayMode: WallpaperDisplayMode + if case .regular = layout.metrics.widthClass { + displayMode = .aspectFit + } else { + displayMode = .aspectFill + } + + self.nativeNode.frame = self.wrapperNode.bounds + self.nativeNode.updateLayout(size: self.nativeNode.bounds.size, displayMode: displayMode, transition: .immediate) } + self.brightnessNode.frame = self.imageNode.bounds let additionalYOffset: CGFloat = 0.0 @@ -1255,4 +1541,41 @@ final class WallpaperGalleryItemNode: GalleryItemNode { func animateWallpaperAppeared() { self.nativeNode.animateEvent(transition: .animated(duration: 2.0, curve: .spring), extendAnimation: true) } + + private var displayedPreviewTooltip = false + private func maybePresentPreviewTooltip() { + guard !self.displayedPreviewTooltip else { + return + } + + let frame = self.dayNightButtonNode.view.convert(self.dayNightButtonNode.bounds, to: self.view) + let currentTimestamp = Int32(Date().timeIntervalSince1970) + + let isDark = self.isDarkAppearance + + let signal: Signal<(Int32, Int32), NoError> + if isDark { + signal = ApplicationSpecificNotice.getChatWallpaperLightPreviewTip(accountManager: self.context.sharedContext.accountManager) + } else { + signal = ApplicationSpecificNotice.getChatWallpaperDarkPreviewTip(accountManager: self.context.sharedContext.accountManager) + } + + let _ = (signal + |> deliverOnMainQueue).start(next: { [weak self] count, timestamp in + if let strongSelf = self, (count < 2 && currentTimestamp > timestamp + 24 * 60 * 60) { + strongSelf.displayedPreviewTooltip = true + + let controller = TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: isDark ? strongSelf.presentationData.strings.WallpaperPreview_PreviewInDayMode : strongSelf.presentationData.strings.WallpaperPreview_PreviewInNightMode, style: .customBlur(UIColor(rgb: 0x333333, alpha: 0.33)), icon: nil, location: .point(frame.offsetBy(dx: 1.0, dy: 6.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in + return .dismiss(consume: false) + }) + strongSelf.galleryController()?.present(controller, in: .current) + + if isDark { + let _ = ApplicationSpecificNotice.incrementChatWallpaperLightPreviewTip(accountManager: strongSelf.context.sharedContext.accountManager, timestamp: currentTimestamp).start() + } else { + let _ = ApplicationSpecificNotice.incrementChatWallpaperDarkPreviewTip(accountManager: strongSelf.context.sharedContext.accountManager, timestamp: currentTimestamp).start() + } + } + }) + } } diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryToolbarNode.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryToolbarNode.swift index 13c47595c0..af9b56b74b 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryToolbarNode.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryToolbarNode.swift @@ -17,39 +17,6 @@ enum WallpaperGalleryToolbarDoneButtonType { case none } -final class WallpaperLightButtonBackgroundNode: ASDisplayNode { - private let backgroundNode: NavigationBackgroundNode - private let overlayNode: ASDisplayNode - private let lightNode: ASDisplayNode - - override init() { - self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x000000, alpha: 0.01), enableBlur: true) - self.overlayNode = ASDisplayNode() - self.overlayNode.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.55) - self.overlayNode.layer.compositingFilter = "overlayBlendMode" - - self.lightNode = ASDisplayNode() - self.lightNode.backgroundColor = UIColor(rgb: 0xf2f2f2, alpha: 0.3) - - super.init() - - self.clipsToBounds = true - - self.addSubnode(self.backgroundNode) - self.addSubnode(self.overlayNode) - //self.addSubnode(self.lightNode) - } - - func updateLayout(size: CGSize) { - let frame = CGRect(origin: .zero, size: size) - self.backgroundNode.frame = frame - self.overlayNode.frame = frame - self.lightNode.frame = frame - - self.backgroundNode.update(size: size, transition: .immediate) - } -} - final class WallpaperGalleryToolbarNode: ASDisplayNode { private var theme: PresentationTheme private let strings: PresentationStrings @@ -65,8 +32,23 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode { } } + var dark: Bool { + didSet { + if self.dark != oldValue { + self.doneButtonBackgroundNode.removeFromSupernode() + if self.dark { + self.doneButtonBackgroundNode = WallpaperOptionBackgroundNode(enableSaturation: true) + } else { + self.doneButtonBackgroundNode = WallpaperLightButtonBackgroundNode() + } + self.doneButtonBackgroundNode.cornerRadius = 14.0 + self.insertSubnode(self.doneButtonBackgroundNode, at: 0) + } + } + } + private let doneButton = HighlightTrackingButtonNode() - private let doneButtonBackgroundNode: WallpaperLightButtonBackgroundNode + private var doneButtonBackgroundNode: ASDisplayNode private let doneButtonTitleNode: ImmediateTextNode @@ -81,6 +63,7 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode { self.strings = strings self.cancelButtonType = cancelButtonType self.doneButtonType = doneButtonType + self.dark = false self.doneButtonBackgroundNode = WallpaperLightButtonBackgroundNode() self.doneButtonBackgroundNode.cornerRadius = 14.0 @@ -106,7 +89,7 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode { super.init() self.addSubnode(self.doneButtonBackgroundNode) - self.doneButtonBackgroundNode.addSubnode(self.doneButtonTitleNode) + self.addSubnode(self.doneButtonTitleNode) self.addSubnode(self.doneButtonSolidBackgroundNode) self.addSubnode(self.doneButtonSolidTitleNode) @@ -196,14 +179,18 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode { let doneFrame = CGRect(origin: CGPoint(x: inset, y: 2.0), size: CGSize(width: size.width - inset * 2.0, height: buttonHeight)) self.doneButton.frame = doneFrame self.doneButtonBackgroundNode.frame = doneFrame - self.doneButtonBackgroundNode.updateLayout(size: doneFrame.size) + if let backgroundNode = self.doneButtonBackgroundNode as? WallpaperOptionBackgroundNode { + backgroundNode.updateLayout(size: doneFrame.size) + } else if let backgroundNode = self.doneButtonBackgroundNode as? WallpaperLightButtonBackgroundNode { + backgroundNode.updateLayout(size: doneFrame.size) + } self.doneButtonSolidBackgroundNode.frame = doneFrame let doneTitleSize = self.doneButtonTitleNode.updateLayout(doneFrame.size) - self.doneButtonTitleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((doneFrame.width - doneTitleSize.width) / 2.0), y: floorToScreenPixels((doneFrame.height - doneTitleSize.height) / 2.0)), size: doneTitleSize) + self.doneButtonTitleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((doneFrame.width - doneTitleSize.width) / 2.0), y: floorToScreenPixels((doneFrame.height - doneTitleSize.height) / 2.0)), size: doneTitleSize).offsetBy(dx: doneFrame.minX, dy: doneFrame.minY) let _ = self.doneButtonSolidTitleNode.updateLayout(doneFrame.size) - self.doneButtonSolidTitleNode.frame = self.doneButtonTitleNode.frame.offsetBy(dx: doneFrame.minX, dy: doneFrame.minY) + self.doneButtonSolidTitleNode.frame = self.doneButtonTitleNode.frame } @objc func cancelPressed() { diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift b/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift index 5ccd6243de..ab8bc1b3b4 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift @@ -5,6 +5,7 @@ import AsyncDisplayKit import SwiftSignalKit import Postbox import CheckNode +import AnimationUI enum WallpaperOptionButtonValue { case check(Bool) @@ -34,28 +35,103 @@ private func generateColorsImage(diameter: CGFloat, colors: [UIColor]) -> UIImag }) } +final class WallpaperLightButtonBackgroundNode: ASDisplayNode { + private let backgroundNode: NavigationBackgroundNode + private let overlayNode: ASDisplayNode + private let lightNode: ASDisplayNode + + override init() { + self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x333333, alpha: 0.3), enableBlur: true, enableSaturation: false) + self.overlayNode = ASDisplayNode() + self.overlayNode.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.75) + self.overlayNode.layer.compositingFilter = "overlayBlendMode" + + self.lightNode = ASDisplayNode() + self.lightNode.backgroundColor = UIColor(rgb: 0xf2f2f2, alpha: 0.2) + + super.init() + + self.clipsToBounds = true + + self.addSubnode(self.backgroundNode) + self.addSubnode(self.overlayNode) + self.addSubnode(self.lightNode) + } + + func updateLayout(size: CGSize) { + let frame = CGRect(origin: .zero, size: size) + self.backgroundNode.frame = frame + self.overlayNode.frame = frame + self.lightNode.frame = frame + + self.backgroundNode.update(size: size, transition: .immediate) + } +} + +final class WallpaperOptionBackgroundNode: ASDisplayNode { + private let backgroundNode: NavigationBackgroundNode + + init(enableSaturation: Bool = false) { + self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x333333, alpha: 0.3), enableBlur: true, enableSaturation: enableSaturation) + + super.init() + + self.clipsToBounds = true + self.isUserInteractionEnabled = false + + self.addSubnode(self.backgroundNode) + } + + func updateLayout(size: CGSize) { + let frame = CGRect(origin: .zero, size: size) + self.backgroundNode.frame = frame + + self.backgroundNode.update(size: size, transition: .immediate) + } +} + final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { enum Content { case icon(image: UIImage?, size: CGSize) case text(String) + case dayNight(isNight: Bool) } + var enableSaturation: Bool = false + private let content: Content + var dark: Bool { + didSet { + if self.dark != oldValue { + self.backgroundNode.removeFromSupernode() + if self.dark { + self.backgroundNode = WallpaperOptionBackgroundNode(enableSaturation: self.enableSaturation) + } else { + self.backgroundNode = WallpaperLightButtonBackgroundNode() + } + self.insertSubnode(self.backgroundNode, at: 0) + } + } + } - private let backgroundNode: WallpaperLightButtonBackgroundNode - + private var backgroundNode: ASDisplayNode private let iconNode: ASImageNode - private let textNode: ImmediateTextNode + private var animationNode: AnimationNode? func setIcon(_ image: UIImage?) { self.iconNode.image = generateTintedImage(image: image, color: .white) } - init(content: Content) { + init(content: Content, dark: Bool) { self.content = content + self.dark = dark - self.backgroundNode = WallpaperLightButtonBackgroundNode() + if dark { + self.backgroundNode = WallpaperOptionBackgroundNode() + } else { + self.backgroundNode = WallpaperLightButtonBackgroundNode() + } self.iconNode = ASImageNode() self.iconNode.displaysAsynchronously = false @@ -68,6 +144,12 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { case let .icon(icon, _): title = "" self.iconNode.image = generateTintedImage(image: icon, color: .white) + case let .dayNight(isNight): + title = "" + let animationNode = AnimationNode(animation: isNight ? "anim_sun_reverse" : "anim_sun", colors: [:], scale: 1.0) + animationNode.speed = 1.66 + animationNode.isUserInteractionEnabled = false + self.animationNode = animationNode } self.textNode = ImmediateTextNode() @@ -79,26 +161,53 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { self.addSubnode(self.iconNode) self.addSubnode(self.textNode) + if let animationNode = self.animationNode { + self.addSubnode(animationNode) + } + self.highligthedChanged = { [weak self] highlighted in if let strongSelf = self { if highlighted { strongSelf.backgroundNode.layer.removeAnimation(forKey: "opacity") strongSelf.backgroundNode.alpha = 0.4 + + strongSelf.iconNode.layer.removeAnimation(forKey: "opacity") + strongSelf.iconNode.alpha = 0.4 + + strongSelf.textNode.layer.removeAnimation(forKey: "opacity") + strongSelf.textNode.alpha = 0.4 + +// if let animationNode = strongSelf.animationNode { +// animationNode.layer.removeAnimation(forKey: "opacity") +// animationNode.alpha = 0.4 +// } } else { strongSelf.backgroundNode.alpha = 1.0 strongSelf.backgroundNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + + strongSelf.iconNode.alpha = 1.0 + strongSelf.iconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + + strongSelf.textNode.alpha = 1.0 + strongSelf.textNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + +// if let animationNode = strongSelf.animationNode { +// animationNode.alpha = 1.0 +// animationNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) +// } } } } } + func setIsNight(_ isNight: Bool) { + self.animationNode?.setAnimation(name: !isNight ? "anim_sun_reverse" : "anim_sun", colors: [:]) + self.animationNode?.speed = 1.66 + self.animationNode?.playOnce() + } + var buttonColor: UIColor = UIColor(rgb: 0x000000, alpha: 0.3) { didSet { -// if self.buttonColor == UIColor(rgb: 0x000000, alpha: 0.3) { -// self.backgroundNode.updateColor(color: UIColor(rgb: 0xf2f2f2, alpha: 0.75), transition: .immediate) -// } else { -// self.backgroundNode.updateColor(color: self.buttonColor, transition: .immediate) -// } } } @@ -111,6 +220,8 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { return CGSize(width: ceil(size.width) + 16.0, height: 28.0) case let .icon(_, size): return size + case .dayNight: + return CGSize(width: 28.0, height: 28.0) } } @@ -119,7 +230,11 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { let size = self.bounds.size self.backgroundNode.frame = self.bounds - self.backgroundNode.updateLayout(size: self.backgroundNode.bounds.size) + if let backgroundNode = self.backgroundNode as? WallpaperOptionBackgroundNode { + backgroundNode.updateLayout(size: self.backgroundNode.bounds.size) + } else if let backgroundNode = self.backgroundNode as? WallpaperLightButtonBackgroundNode { + backgroundNode.updateLayout(size: self.backgroundNode.bounds.size) + } self.backgroundNode.cornerRadius = size.height / 2.0 self.iconNode.frame = self.bounds @@ -127,12 +242,17 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { if let textSize = self.textSize { self.textNode.frame = CGRect(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: floorToScreenPixels((size.height - textSize.height) / 2.0), width: textSize.width, height: textSize.height) } + + if let animationNode = self.animationNode { + animationNode.bounds = CGRect(origin: .zero, size: CGSize(width: 24.0, height: 24.0)) + animationNode.position = CGPoint(x: 14.0, y: 14.0) + } } } final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { - private let backgroundNode: NavigationBackgroundNode + private let backgroundNode: WallpaperOptionBackgroundNode private let checkNode: CheckNode private let colorNode: ASImageNode @@ -172,9 +292,8 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { self._value = value self.title = title - self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x000000, alpha: 0.01)) - self.backgroundNode.cornerRadius = 14.0 - + self.backgroundNode = WallpaperOptionBackgroundNode() + self.checkNode = CheckNode(theme: CheckNodeTheme(backgroundColor: .white, strokeColor: .clear, borderColor: .white, overlayBorder: false, hasInset: false, hasShadow: false, borderWidth: 1.5)) self.checkNode.isUserInteractionEnabled = false @@ -186,6 +305,9 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { super.init() + self.clipsToBounds = true + self.cornerRadius = 14.0 + switch value { case let .check(selected): self.checkNode.isHidden = false @@ -202,6 +324,7 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { } self.addSubnode(self.backgroundNode) + self.addSubnode(self.checkNode) self.addSubnode(self.textNode) self.addSubnode(self.colorNode) @@ -227,11 +350,6 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { var buttonColor: UIColor = UIColor(rgb: 0x000000, alpha: 0.3) { didSet { -// if self.buttonColor == UIColor(rgb: 0x000000, alpha: 0.3) { -// self.backgroundNode.updateColor(color: UIColor(rgb: 0xf2f2f2, alpha: 0.75), transition: .immediate) -// } else { -// self.backgroundNode.updateColor(color: self.buttonColor, transition: .immediate) -// } } } @@ -322,7 +440,7 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { super.layout() self.backgroundNode.frame = self.bounds - self.backgroundNode.update(size: self.backgroundNode.bounds.size, cornerRadius: 15.0, transition: .immediate) + self.backgroundNode.updateLayout(size: self.backgroundNode.bounds.size) guard let _ = self.textSize else { return @@ -340,3 +458,159 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { } } } + +final class WallpaperSliderNode: ASDisplayNode { + let minValue: CGFloat + let maxValue: CGFloat + var value: CGFloat = 1.0 { + didSet { + if let size = self.validLayout { + self.updateLayout(size: size) + } + } + } + + private let backgroundNode: NavigationBackgroundNode + + private let foregroundNode: ASDisplayNode + private let foregroundLightNode: ASDisplayNode + private let leftIconNode: ASImageNode + private let rightIconNode: ASImageNode + + private let valueChanged: (CGFloat, Bool) -> Void + + private let hapticFeedback = HapticFeedback() + + private var validLayout: CGSize? + + init(minValue: CGFloat, maxValue: CGFloat, value: CGFloat, valueChanged: @escaping (CGFloat, Bool) -> Void) { + self.minValue = minValue + self.maxValue = maxValue + self.value = value + self.valueChanged = valueChanged + + self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x333333, alpha: 0.3), enableBlur: true, enableSaturation: false) + + self.foregroundNode = ASDisplayNode() + self.foregroundNode.clipsToBounds = true + self.foregroundNode.cornerRadius = 3.0 + self.foregroundNode.isAccessibilityElement = false + self.foregroundNode.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.75) + self.foregroundNode.layer.compositingFilter = "overlayBlendMode" + self.foregroundNode.isUserInteractionEnabled = false + + self.foregroundLightNode = ASDisplayNode() + self.foregroundLightNode.clipsToBounds = true + self.foregroundLightNode.cornerRadius = 3.0 + self.foregroundLightNode.backgroundColor = UIColor(rgb: 0xf2f2f2, alpha: 0.2) + + self.leftIconNode = ASImageNode() + self.leftIconNode.displaysAsynchronously = false + self.leftIconNode.image = UIImage(bundleImageName: "Settings/WallpaperBrightnessMin") + self.leftIconNode.contentMode = .center + + self.rightIconNode = ASImageNode() + self.rightIconNode.displaysAsynchronously = false + self.rightIconNode.image = UIImage(bundleImageName: "Settings/WallpaperBrightnessMax") + self.rightIconNode.contentMode = .center + + super.init() + + self.clipsToBounds = true + self.cornerRadius = 15.0 + self.isUserInteractionEnabled = true + + self.addSubnode(self.backgroundNode) + + self.addSubnode(self.foregroundNode) + self.addSubnode(self.foregroundLightNode) + + self.addSubnode(self.leftIconNode) + self.addSubnode(self.rightIconNode) + } + + override func didLoad() { + super.didLoad() + + let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) + self.view.addGestureRecognizer(panGestureRecognizer) + + let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) + self.view.addGestureRecognizer(tapGestureRecognizer) + } + + var ignoreUpdates = false + func animateValue(from: CGFloat, to: CGFloat, transition: ContainedViewLayoutTransition = .immediate) { + guard let size = self.validLayout else { + return + } + self.internalUpdateLayout(size: size, value: from) + self.internalUpdateLayout(size: size, value: to, transition: transition) + } + + func internalUpdateLayout(size: CGSize, value: CGFloat, transition: ContainedViewLayoutTransition = .immediate) { + self.validLayout = size + + transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: .zero, size: size)) + self.backgroundNode.update(size: size, transition: transition) + + if let icon = self.leftIconNode.image { + transition.updateFrame(node: self.leftIconNode, frame: CGRect(origin: CGPoint(x: 7.0, y: floorToScreenPixels((size.height - icon.size.height) / 2.0)), size: icon.size)) + } + + if let icon = self.rightIconNode.image { + transition.updateFrame(node: self.rightIconNode, frame: CGRect(origin: CGPoint(x: size.width - icon.size.width - 6.0, y: floorToScreenPixels((size.height - icon.size.height) / 2.0)), size: icon.size)) + } + + let range = self.maxValue - self.minValue + let value = (value - self.minValue) / range + let foregroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: value * size.width, height: size.height)) + transition.updateFrame(node: self.foregroundNode, frame: foregroundFrame) + transition.updateFrame(node: self.foregroundLightNode, frame: foregroundFrame) + } + + func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition = .immediate) { + guard !self.ignoreUpdates else { + return + } + self.internalUpdateLayout(size: size, value: self.value, transition: transition) + } + + @objc private func panGesture(_ gestureRecognizer: UIPanGestureRecognizer) { + let range = self.maxValue - self.minValue + switch gestureRecognizer.state { + case .began: + break + case .changed: + let previousValue = self.value + + let translation: CGFloat = gestureRecognizer.translation(in: gestureRecognizer.view).x + let delta = translation / self.bounds.width * range + self.value = max(self.minValue, min(self.maxValue, self.value + delta)) + gestureRecognizer.setTranslation(CGPoint(), in: gestureRecognizer.view) + + if self.value == 0.0 && previousValue != 0.0 { + self.hapticFeedback.impact(.soft) + } else if self.value == 1.0 && previousValue != 1.0 { + self.hapticFeedback.impact(.soft) + } + if abs(previousValue - self.value) >= 0.001 { + self.valueChanged(self.value, false) + } + case .ended: + let translation: CGFloat = gestureRecognizer.translation(in: gestureRecognizer.view).x + let delta = translation / self.bounds.width * range + self.value = max(self.minValue, min(self.maxValue, self.value + delta)) + self.valueChanged(self.value, true) + default: + break + } + } + + @objc private func tapGesture(_ gestureRecognizer: UITapGestureRecognizer) { + let range = self.maxValue - self.minValue + let location = gestureRecognizer.location(in: gestureRecognizer.view) + self.value = max(self.minValue, min(self.maxValue, self.minValue + location.x / self.bounds.width * range)) + self.valueChanged(self.value, true) + } +} diff --git a/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift b/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift index 87222f20e4..de6f7c7965 100644 --- a/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift +++ b/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift @@ -526,7 +526,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi let _ = (ApplicationSpecificNotice.incrementAudioRateOptionsTip(accountManager: self.context.sharedContext.accountManager) |> deliverOnMainQueue).start(next: { [weak self] value in if let strongSelf = self, let controller = strongSelf.getController?(), value == 2 { - let tooltipController = TooltipScreen(account: strongSelf.context.account, text: strongSelf.strings.Conversation_AudioRateOptionsTooltip, style: .default, icon: nil, location: .point(frame.offsetBy(dx: 0.0, dy: 4.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in + let tooltipController = TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: strongSelf.strings.Conversation_AudioRateOptionsTooltip, style: .default, icon: nil, location: .point(frame.offsetBy(dx: 0.0, dy: 4.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in return .dismiss(consume: false) }) controller.present(tooltipController, in: .window(.root)) diff --git a/submodules/TelegramCallsUI/Sources/CallControllerNode.swift b/submodules/TelegramCallsUI/Sources/CallControllerNode.swift index f1d8d74b9c..25cc58eee2 100644 --- a/submodules/TelegramCallsUI/Sources/CallControllerNode.swift +++ b/submodules/TelegramCallsUI/Sources/CallControllerNode.swift @@ -760,8 +760,8 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro }) else { return } - - self.present?(TooltipScreen(account: self.account, text: self.presentationData.strings.Call_CameraOrScreenTooltip, style: .light, icon: nil, location: .point(location.offsetBy(dx: 0.0, dy: -14.0), .bottom), displayDuration: .custom(5.0), shouldDismissOnTouch: { _ in + + self.present?(TooltipScreen(account: self.account, sharedContext: self.sharedContext, text: self.presentationData.strings.Call_CameraOrScreenTooltip, style: .light, icon: nil, location: .point(location.offsetBy(dx: 0.0, dy: -14.0), .bottom), displayDuration: .custom(5.0), shouldDismissOnTouch: { _ in return .dismiss(consume: false) })) } diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index ccd9a5bfee..b6020e48de 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -2310,7 +2310,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController } else { text = presentationData.strings.VoiceChat_RecordingInProgress } - strongSelf.controller?.present(TooltipScreen(account: strongSelf.context.account, text: text, icon: nil, location: .point(location.offsetBy(dx: 1.0, dy: 0.0), .top), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in + strongSelf.controller?.present(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: text, icon: nil, location: .point(location.offsetBy(dx: 1.0, dy: 0.0), .top), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in return .dismiss(consume: true) }), in: .window(.root)) } @@ -3507,7 +3507,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController if !callState.subscribedToScheduled { let location = self.actionButton.view.convert(self.actionButton.bounds, to: self.view).center let point = CGRect(origin: CGPoint(x: location.x - 5.0, y: location.y - 5.0 - 68.0), size: CGSize(width: 10.0, height: 10.0)) - self.controller?.present(TooltipScreen(account: self.context.account, text: self.presentationData.strings.VoiceChat_ReminderNotify, style: .gradient(UIColor(rgb: 0x262c5a), UIColor(rgb: 0x5d2835)), icon: nil, location: .point(point, .bottom), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in + self.controller?.present(TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: self.presentationData.strings.VoiceChat_ReminderNotify, style: .gradient(UIColor(rgb: 0x262c5a), UIColor(rgb: 0x5d2835)), icon: nil, location: .point(point, .bottom), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in return .dismiss(consume: false) }), in: .window(.root)) } @@ -6411,7 +6411,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController point.origin.y += 32.0 } } - self.controller?.present(TooltipScreen(account: self.context.account, text: self.presentationData.strings.VoiceChat_UnmuteSuggestion, style: .gradient(UIColor(rgb: 0x1d446c), UIColor(rgb: 0x193e63)), icon: nil, location: .point(point, position), displayDuration: .custom(8.0), shouldDismissOnTouch: { _ in + self.controller?.present(TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: self.presentationData.strings.VoiceChat_UnmuteSuggestion, style: .gradient(UIColor(rgb: 0x1d446c), UIColor(rgb: 0x193e63)), icon: nil, location: .point(point, position), displayDuration: .custom(8.0), shouldDismissOnTouch: { _ in return .dismiss(consume: false) }), in: .window(.root)) } diff --git a/submodules/TelegramNotices/Sources/Notices.swift b/submodules/TelegramNotices/Sources/Notices.swift index b5e2bb1294..8995477dff 100644 --- a/submodules/TelegramNotices/Sources/Notices.swift +++ b/submodules/TelegramNotices/Sources/Notices.swift @@ -170,6 +170,8 @@ private enum ApplicationSpecificGlobalNotice: Int32 { case audioRateOptionsTip = 36 case translationSuggestion = 37 case sendWhenOnlineTip = 38 + case chatWallpaperLightPreviewTip = 39 + case chatWallpaperDarkPreviewTip = 40 var key: ValueBoxKey { let v = ValueBoxKey(length: 4) @@ -332,6 +334,14 @@ private struct ApplicationSpecificNoticeKeys { return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatSpecificThemeDarkPreviewTip.key) } + static func chatWallpaperLightPreviewTip() -> NoticeEntryKey { + return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatWallpaperLightPreviewTip.key) + } + + static func chatWallpaperDarkPreviewTip() -> NoticeEntryKey { + return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatWallpaperDarkPreviewTip.key) + } + static func chatForwardOptionsTip() -> NoticeEntryKey { return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatForwardOptionsTip.key) } @@ -1109,6 +1119,60 @@ public struct ApplicationSpecificNotice { } } + public static func getChatWallpaperLightPreviewTip(accountManager: AccountManager) -> Signal<(Int32, Int32), NoError> { + return accountManager.transaction { transaction -> (Int32, Int32) in + if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatWallpaperLightPreviewTip())?.get(ApplicationSpecificTimestampAndCounterNotice.self) { + return (value.counter, value.timestamp) + } else { + return (0, 0) + } + } + } + + public static func incrementChatWallpaperLightPreviewTip(accountManager: AccountManager, count: Int = 1, timestamp: Int32) -> Signal { + return accountManager.transaction { transaction -> Int in + var currentValue: Int32 = 0 + if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatWallpaperLightPreviewTip())?.get(ApplicationSpecificTimestampAndCounterNotice.self) { + currentValue = value.counter + } + let previousValue = currentValue + currentValue += Int32(count) + + if let entry = CodableEntry(ApplicationSpecificTimestampAndCounterNotice(counter: currentValue, timestamp: timestamp)) { + transaction.setNotice(ApplicationSpecificNoticeKeys.chatWallpaperLightPreviewTip(), entry) + } + + return Int(previousValue) + } + } + + public static func getChatWallpaperDarkPreviewTip(accountManager: AccountManager) -> Signal<(Int32, Int32), NoError> { + return accountManager.transaction { transaction -> (Int32, Int32) in + if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatWallpaperDarkPreviewTip())?.get(ApplicationSpecificTimestampAndCounterNotice.self) { + return (value.counter, value.timestamp) + } else { + return (0, 0) + } + } + } + + public static func incrementChatWallpaperDarkPreviewTip(accountManager: AccountManager, count: Int = 1, timestamp: Int32) -> Signal { + return accountManager.transaction { transaction -> Int in + var currentValue: Int32 = 0 + if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatWallpaperDarkPreviewTip())?.get(ApplicationSpecificTimestampAndCounterNotice.self) { + currentValue = value.counter + } + let previousValue = currentValue + currentValue += Int32(count) + + if let entry = CodableEntry(ApplicationSpecificTimestampAndCounterNotice(counter: currentValue, timestamp: timestamp)) { + transaction.setNotice(ApplicationSpecificNoticeKeys.chatWallpaperDarkPreviewTip(), entry) + } + + return Int(previousValue) + } + } + public static func getChatForwardOptionsTip(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Int32 in if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatForwardOptionsTip())?.get(ApplicationSpecificCounterNotice.self) { diff --git a/submodules/TelegramPresentationData/Sources/PresentationData.swift b/submodules/TelegramPresentationData/Sources/PresentationData.swift index 3458593c55..002043cccf 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationData.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationData.swift @@ -218,16 +218,18 @@ public final class InitialPresentationDataAndSettings { public let callListSettings: CallListSettings public let inAppNotificationSettings: InAppNotificationSettings public let mediaInputSettings: MediaInputSettings + public let mediaDisplaySettings: MediaDisplaySettings public let stickerSettings: StickerSettings public let experimentalUISettings: ExperimentalUISettings - public init(presentationData: PresentationData, automaticMediaDownloadSettings: MediaAutoDownloadSettings, autodownloadSettings: AutodownloadSettings, callListSettings: CallListSettings, inAppNotificationSettings: InAppNotificationSettings, mediaInputSettings: MediaInputSettings, stickerSettings: StickerSettings, experimentalUISettings: ExperimentalUISettings) { + public init(presentationData: PresentationData, automaticMediaDownloadSettings: MediaAutoDownloadSettings, autodownloadSettings: AutodownloadSettings, callListSettings: CallListSettings, inAppNotificationSettings: InAppNotificationSettings, mediaInputSettings: MediaInputSettings, mediaDisplaySettings: MediaDisplaySettings, stickerSettings: StickerSettings, experimentalUISettings: ExperimentalUISettings) { self.presentationData = presentationData self.automaticMediaDownloadSettings = automaticMediaDownloadSettings self.autodownloadSettings = autodownloadSettings self.callListSettings = callListSettings self.inAppNotificationSettings = inAppNotificationSettings self.mediaInputSettings = mediaInputSettings + self.mediaDisplaySettings = mediaDisplaySettings self.stickerSettings = stickerSettings self.experimentalUISettings = experimentalUISettings } @@ -242,6 +244,7 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm +1.000000 1.000000 1.000000 scn +9.140625 16.273438 m +9.140625 16.648438 8.828125 16.960938 8.453125 16.960938 c +8.085938 16.960938 7.773438 16.648438 7.773438 16.273438 c +7.773438 14.632812 l +7.773438 14.265625 8.085938 13.953125 8.453125 13.953125 c +8.828125 13.953125 9.140625 14.265625 9.140625 14.632812 c +9.140625 16.273438 l +h +12.312500 13.296875 m +12.054688 13.031250 12.054688 12.601562 12.312500 12.335938 c +12.578125 12.078125 13.015625 12.070312 13.281250 12.335938 c +14.445312 13.500000 l +14.710938 13.765625 14.710938 14.210938 14.445312 14.468750 c +14.187500 14.734375 13.750000 14.734375 13.492188 14.468750 c +12.312500 13.296875 l +h +3.625000 12.335938 m +3.890625 12.078125 4.328125 12.078125 4.585938 12.335938 c +4.851562 12.585938 4.851562 13.039062 4.593750 13.296875 c +3.429688 14.468750 l +3.179688 14.726562 2.734375 14.734375 2.468750 14.468750 c +2.210938 14.210938 2.210938 13.765625 2.460938 13.507812 c +3.625000 12.335938 l +h +8.453125 12.468750 m +6.273438 12.468750 4.468750 10.664062 4.468750 8.476562 c +4.468750 6.296875 6.273438 4.492188 8.453125 4.492188 c +10.625000 4.492188 12.429688 6.296875 12.429688 8.476562 c +12.429688 10.664062 10.625000 12.468750 8.453125 12.468750 c +h +16.226562 7.796875 m +16.601562 7.796875 16.914062 8.109375 16.914062 8.476562 c +16.914062 8.843750 16.601562 9.156250 16.226562 9.156250 c +14.593750 9.156250 l +14.226562 9.156250 13.914062 8.843750 13.914062 8.476562 c +13.914062 8.109375 14.226562 7.796875 14.593750 7.796875 c +16.226562 7.796875 l +h +0.679688 9.156250 m +0.312500 9.156250 0.000000 8.843750 0.000000 8.476562 c +0.000000 8.109375 0.312500 7.796875 0.679688 7.796875 c +2.312500 7.796875 l +2.687500 7.796875 3.000000 8.109375 3.000000 8.476562 c +3.000000 8.843750 2.687500 9.156250 2.312500 9.156250 c +0.679688 9.156250 l +h +13.273438 4.609375 m +13.015625 4.875000 12.578125 4.875000 12.312500 4.609375 c +12.054688 4.351562 12.054688 3.914062 12.312500 3.648438 c +13.492188 2.476562 l +13.750000 2.218750 14.187500 2.226562 14.445312 2.484375 c +14.710938 2.750000 14.710938 3.187500 14.445312 3.445312 c +13.273438 4.609375 l +h +2.460938 3.453125 m +2.203125 3.195312 2.203125 2.757812 2.453125 2.492188 c +2.710938 2.234375 3.156250 2.226562 3.421875 2.484375 c +4.585938 3.648438 l +4.851562 3.906250 4.851562 4.343750 4.593750 4.609375 c +4.335938 4.867188 3.890625 4.867188 3.625000 4.609375 c +2.460938 3.453125 l +h +9.140625 2.320312 m +9.140625 2.695312 8.828125 3.007812 8.453125 3.007812 c +8.085938 3.007812 7.773438 2.695312 7.773438 2.320312 c +7.773438 0.679688 l +7.773438 0.312500 8.085938 0.000000 8.453125 0.000000 c +8.828125 0.000000 9.140625 0.312500 9.140625 0.679688 c +9.140625 2.320312 l +h +f +n +Q + +endstream +endobj + +3 0 obj + 2760 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 16.914062 16.960938 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000002850 00000 n +0000002873 00000 n +0000003046 00000 n +0000003120 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +3179 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Settings/WallpaperBrightnessMin.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Settings/WallpaperBrightnessMin.imageset/Contents.json new file mode 100644 index 0000000000..68cb6916eb --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Settings/WallpaperBrightnessMin.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "brightness_min.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Settings/WallpaperBrightnessMin.imageset/brightness_min.pdf b/submodules/TelegramUI/Images.xcassets/Settings/WallpaperBrightnessMin.imageset/brightness_min.pdf new file mode 100644 index 0000000000..ec3ccfc3af --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Settings/WallpaperBrightnessMin.imageset/brightness_min.pdf @@ -0,0 +1,198 @@ +%PDF-1.7 + +1 0 obj + << /Length 2 0 R >> +stream +1.154297 0 0.087402 -0.137207 1.066895 1.066895 d1 + +endstream +endobj + +2 0 obj + 51 +endobj + +3 0 obj + [ 1.154297 ] +endobj + +4 0 obj + << /Length 5 0 R >> +stream +/CIDInit /ProcSet findresource begin +12 dict begin +begincmap +/CIDSystemInfo +<< /Registry (FigmaPDF) + /Ordering (FigmaPDF) + /Supplement 0 +>> def +/CMapName /A-B-C def +/CMapType 2 def +1 begincodespacerange +<00> +endcodespacerange +1 beginbfchar +<00> +endbfchar +endcmap +CMapName currentdict /CMap defineresource pop +end +end +endstream +endobj + +5 0 obj + 336 +endobj + +6 0 obj + << /Subtype /Type3 + /CharProcs << /C0 1 0 R >> + /Encoding << /Type /Encoding + /Differences [ 0 /C0 ] + >> + /Widths 3 0 R + /FontBBox [ 0.000000 0.000000 0.000000 0.000000 ] + /FontMatrix [ 1.000000 0.000000 0.000000 1.000000 0.000000 0.000000 ] + /Type /Font + /ToUnicode 4 0 R + /FirstChar 0 + /LastChar 0 + /Resources << >> + >> +endobj + +7 0 obj + << /Font << /F1 6 0 R >> >> +endobj + +8 0 obj + << /Length 9 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 -1.653809 1.365723 cm +1.000000 1.000000 1.000000 scn +0.342773 0.692383 m +h +9.000000 11.612793 m +9.468750 11.612793 9.856934 12.000977 9.856934 12.469727 c +9.856934 12.938477 9.468750 13.326660 9.000000 13.326660 c +8.531250 13.326660 8.143066 12.938477 8.143066 12.469727 c +8.143066 12.000977 8.531250 11.612793 9.000000 11.612793 c +h +4.407715 9.715820 m +4.883789 9.715820 5.264648 10.104004 5.264648 10.572754 c +5.264648 11.041504 4.883789 11.429688 4.407715 11.429688 c +3.938965 11.429688 3.550781 11.041504 3.550781 10.572754 c +3.550781 10.104004 3.938965 9.715820 4.407715 9.715820 c +h +13.592285 9.715820 m +14.061035 9.715820 14.449219 10.104004 14.449219 10.572754 c +14.449219 11.041504 14.061035 11.429688 13.592285 11.429688 c +13.123535 11.429688 12.735352 11.041504 12.735352 10.572754 c +12.735352 10.104004 13.123535 9.715820 13.592285 9.715820 c +h +9.000000 2.252441 m +11.036133 2.252441 12.735352 3.944336 12.735352 5.980469 c +12.735352 8.023926 11.036133 9.715820 9.000000 9.715820 c +6.963867 9.715820 5.271973 8.023926 5.271973 5.980469 c +5.271973 3.944336 6.963867 2.252441 9.000000 2.252441 c +h +2.510742 5.123535 m +2.979492 5.123535 3.367676 5.511719 3.367676 5.980469 c +3.367676 6.456543 2.979492 6.837402 2.510742 6.837402 c +2.041992 6.837402 1.653809 6.456543 1.653809 5.980469 c +1.653809 5.511719 2.041992 5.123535 2.510742 5.123535 c +h +15.489258 5.123535 m +15.958008 5.123535 16.346191 5.511719 16.346191 5.980469 c +16.346191 6.456543 15.958008 6.837402 15.489258 6.837402 c +15.020508 6.837402 14.632324 6.456543 14.632324 5.980469 c +14.632324 5.511719 15.020508 5.123535 15.489258 5.123535 c +h +13.592285 0.531250 m +14.061035 0.531250 14.449219 0.919434 14.449219 1.388184 c +14.449219 1.864258 14.061035 2.245117 13.592285 2.245117 c +13.123535 2.245117 12.735352 1.864258 12.735352 1.388184 c +12.735352 0.919434 13.123535 0.531250 13.592285 0.531250 c +h +4.407715 0.531250 m +4.883789 0.531250 5.264648 0.919434 5.264648 1.388184 c +5.264648 1.864258 4.883789 2.245117 4.407715 2.245117 c +3.938965 2.245117 3.550781 1.864258 3.550781 1.388184 c +3.550781 0.919434 3.938965 0.531250 4.407715 0.531250 c +h +9.000000 -1.365723 m +9.468750 -1.365723 9.856934 -0.977539 9.856934 -0.508789 c +9.856934 -0.032715 9.468750 0.348145 9.000000 0.348145 c +8.531250 0.348145 8.143066 -0.032715 8.143066 -0.508789 c +8.143066 -0.977539 8.531250 -1.365723 9.000000 -1.365723 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 -1.653809 1.365723 cm +BT +15.000000 0.000000 0.000000 15.000000 0.342773 0.692383 Tm +/F1 1.000000 Tf +[ (\000) ] TJ +ET +Q + +endstream +endobj + +9 0 obj + 2605 +endobj + +10 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 14.692383 14.692383 ] + /Resources 7 0 R + /Contents 8 0 R + /Parent 11 0 R + >> +endobj + +11 0 obj + << /Kids [ 10 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +12 0 obj + << /Pages 11 0 R + /Type /Catalog + >> +endobj + +xref +0 13 +0000000000 65535 f +0000000010 00000 n +0000000117 00000 n +0000000138 00000 n +0000000169 00000 n +0000000561 00000 n +0000000583 00000 n +0000000995 00000 n +0000001041 00000 n +0000003702 00000 n +0000003725 00000 n +0000003900 00000 n +0000003976 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 12 0 R + /Size 13 +>> +startxref +4037 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/ChatBotStartInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatBotStartInputPanelNode.swift index e0989b05dc..79689abe9f 100644 --- a/submodules/TelegramUI/Sources/ChatBotStartInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatBotStartInputPanelNode.swift @@ -130,7 +130,7 @@ final class ChatBotStartInputPanelNode: ChatInputPanelNode { tooltipController.location = .point(location, .bottom) } } else { - let controller = TooltipScreen(account: context.account, text: self.strings.Bot_TapToUse, icon: .downArrows, location: .point(location, .bottom), displayDuration: .infinite, shouldDismissOnTouch: { _ in + let controller = TooltipScreen(account: context.account, sharedContext: context.sharedContext, text: self.strings.Bot_TapToUse, icon: .downArrows, location: .point(location, .bottom), displayDuration: .infinite, shouldDismissOnTouch: { _ in return .ignore }) controller.alwaysVisible = true diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index bfbc4c4d9e..710f40a519 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -676,14 +676,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return false } - if strongSelf.presentVoiceMessageDiscardAlert(action: { [weak self] in - if let strongSelf = self { - Queue.mainQueue().after(0.1, { - let _ = strongSelf.controllerInteraction?.openMessage(message, mode) - }) + let displayVoiceMessageDiscardAlert: () -> Bool = { + if strongSelf.presentVoiceMessageDiscardAlert(action: { [weak self] in + if let strongSelf = self { + Queue.mainQueue().after(0.1, { + let _ = strongSelf.controllerInteraction?.openMessage(message, mode) + }) + } + }, performAction: false) { + return false } - }, performAction: false) { - return false + return true } strongSelf.commitPurposefulAction() @@ -696,6 +699,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G for media in message.media { if media is TelegramMediaMap { + if !displayVoiceMessageDiscardAlert() { + return false + } isLocation = true } if let file = media as? TelegramMediaFile, file.isInstantVideo { @@ -707,12 +713,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia { switch extendedMedia { case .preview: - strongSelf.controllerInteraction?.openCheckoutOrReceipt(message.id) - return true + if displayVoiceMessageDiscardAlert() { + strongSelf.controllerInteraction?.openCheckoutOrReceipt(message.id) + return true + } else { + return false + } case .full: break } } else if let action = media as? TelegramMediaAction { + if !displayVoiceMessageDiscardAlert() { + return false + } switch action.action { case .pinnedMessageUpdated: for attribute in message.attributes { @@ -845,7 +858,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } strongSelf.chatDisplayNode.dismissInput() let wallpaperPreviewController = WallpaperGalleryController(context: strongSelf.context, source: .wallpaper(wallpaper, nil, [], nil, nil, nil), mode: .peer(EnginePeer(peer), true)) - wallpaperPreviewController.apply = { wallpaper, options, _ in + wallpaperPreviewController.apply = { wallpaper, options, _, _ in let _ = (strongSelf.context.engine.themes.setExistingChatWallpaper(messageId: message.id, settings: nil) |> deliverOnMainQueue).start(completed: { [weak wallpaperPreviewController] in wallpaperPreviewController?.dismiss() @@ -4264,12 +4277,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if value { openWebView() } else { - strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: strongSelf.presentationData.strings.WebApp_OpenWebViewAlertTitle, text: strongSelf.presentationData.strings.WebApp_OpenWebViewAlertText(botName).string, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { }), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: { - if let strongSelf = self { - let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peer.id).start() - openWebView() - } - })], parseMarkdown: true), in: .window(.root), with: nil) + let controller = webAppLaunchConfirmationController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: EnginePeer(peer), commit: { + let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peer.id).start() + openWebView() + }, showMore: nil) + strongSelf.present(controller, in: .window(.root)) } }) }, activateAdAction: { [weak self] messageId in @@ -5840,6 +5852,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let strongSelf = self { let (themeEmoticonPreview, darkAppearancePreview) = themeEmoticonAndDarkAppearance + var chatWallpaper = chatWallpaper + let previousTheme = strongSelf.presentationData.theme let previousStrings = strongSelf.presentationData.strings let previousChatWallpaper = strongSelf.presentationData.chatWallpaper @@ -5847,7 +5861,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var themeEmoticon = themeEmoticon if let themeEmoticonPreview = themeEmoticonPreview { if !themeEmoticonPreview.isEmpty { - themeEmoticon = themeEmoticonPreview + if themeEmoticon != themeEmoticonPreview { + chatWallpaper = nil + themeEmoticon = themeEmoticonPreview + } } else { themeEmoticon = nil } @@ -5858,7 +5875,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var presentationData = presentationData var useDarkAppearance = presentationData.theme.overallDarkAppearance - + if let themeEmoticon = themeEmoticon, let theme = chatThemes.first(where: { $0.emoticon?.strippedEmoji == themeEmoticon.strippedEmoji }) { if let darkAppearancePreview = darkAppearancePreview { useDarkAppearance = darkAppearancePreview @@ -5897,7 +5914,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G lightWallpaper = theme.chat.defaultWallpaper } - lightTheme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: themeSettings.theme, accentColor: currentColors?.color, bubbleColors: currentColors?.customBubbleColors ?? [], wallpaper: currentColors?.wallpaper, baseColor: currentColors?.baseColor, serviceBackgroundColor: defaultServiceBackgroundColor) ?? defaultPresentationTheme + var preferredBaseTheme: TelegramBaseTheme? + if let baseTheme = themeSettings.themePreferredBaseTheme[themeSettings.theme.index], [.classic, .day].contains(baseTheme) { + preferredBaseTheme = baseTheme + } + + lightTheme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: themeSettings.theme, baseTheme: preferredBaseTheme, accentColor: currentColors?.color, bubbleColors: currentColors?.customBubbleColors ?? [], wallpaper: currentColors?.wallpaper, baseColor: currentColors?.baseColor, serviceBackgroundColor: defaultServiceBackgroundColor) ?? defaultPresentationTheme } else { lightTheme = presentationData.theme lightWallpaper = presentationData.chatWallpaper @@ -5906,7 +5928,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let effectiveColors = themeSettings.themeSpecificAccentColors[automaticTheme.index] let themeSpecificWallpaper = (themeSettings.themeSpecificChatWallpapers[coloredThemeIndex(reference: automaticTheme, accentColor: effectiveColors)] ?? themeSettings.themeSpecificChatWallpapers[automaticTheme.index]) - darkTheme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: automaticTheme, accentColor: effectiveColors?.color, bubbleColors: effectiveColors?.customBubbleColors ?? [], wallpaper: effectiveColors?.wallpaper, baseColor: effectiveColors?.baseColor, serviceBackgroundColor: defaultServiceBackgroundColor) ?? defaultPresentationTheme + var preferredBaseTheme: TelegramBaseTheme? + if let baseTheme = themeSettings.themePreferredBaseTheme[automaticTheme.index], [.night, .tinted].contains(baseTheme) { + preferredBaseTheme = baseTheme + } else { + preferredBaseTheme = .night + } + + darkTheme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: automaticTheme, baseTheme: preferredBaseTheme, accentColor: effectiveColors?.color, bubbleColors: effectiveColors?.customBubbleColors ?? [], wallpaper: effectiveColors?.wallpaper, baseColor: effectiveColors?.baseColor, serviceBackgroundColor: defaultServiceBackgroundColor) ?? defaultPresentationTheme if let themeSpecificWallpaper = themeSpecificWallpaper { darkWallpaper = themeSpecificWallpaper @@ -5945,7 +5974,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G presentationData = presentationData.withUpdated(chatWallpaper: chatWallpaper) } - let isFirstTime = !strongSelf.didSetPresentationData strongSelf.presentationData = presentationData strongSelf.didSetPresentationData = true @@ -10942,6 +10970,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return false } + if strongSelf.effectiveNavigationController?.topViewController !== strongSelf { + return false + } + if strongSelf.presentationInterfaceState.inputTextPanelState.mediaRecordingState != nil { return false } @@ -12765,7 +12797,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.presentAttachmentMenu(subject: .bot(id: botId, payload: payload, justInstalled: justInstalled)) } - public func presentBotApp(botApp: BotApp, payload: String?) { + public func presentBotApp(botApp: BotApp, botPeer: EnginePeer, payload: String?) { guard let peerId = self.chatLocation.peerId else { return } @@ -12813,6 +12845,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } + let botAddress = botPeer.addressName ?? "" + self.messageActionCallbackDisposable.set(((self.context.engine.messages.requestAppWebView(peerId: peerId, appReference: .id(id: botApp.id, accessHash: botApp.accessHash), payload: payload, themeParams: generateWebAppThemeParams(self.presentationData.theme), allowWrite: false) |> afterDisposed { updateProgress() @@ -12824,6 +12858,22 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let params = WebAppParameters(peerId: peerId, botId: peerId, botName: botApp.title, url: url, queryId: 0, payload: payload, buttonText: "", keepAliveSignal: nil, fromMenu: false, fromAttachMenu: false, isInline: false, isSimple: false) let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url in self?.openUrl(url, concealed: true, forceExternal: true) + }, requestSwitchInline: { [weak self] query, chatTypes, completion in + if let strongSelf = self { + if let chatTypes { + let controller = strongSelf.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: strongSelf.context, filter: [.excludeRecent, .doNotSearchMessages], requestPeerType: chatTypes, hasContactSelector: false, hasCreation: false)) + controller.peerSelected = { [weak self, weak controller] peer, _ in + if let strongSelf = self { + completion() + controller?.dismiss() + strongSelf.controllerInteraction?.activateSwitchInline(peer.id, "@\(botAddress) \(query)") + } + } + strongSelf.push(controller) + } else { + strongSelf.controllerInteraction?.activateSwitchInline(peerId, "@\(botAddress) \(query)") + } + } }, completion: { [weak self] in self?.chatDisplayNode.historyNode.scrollToEndOfHistory() }, getNavigationController: { [weak self] in @@ -14315,7 +14365,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } - let tooltipScreen = TooltipScreen(account: self.context.account, text: solution.text, textEntities: solution.entities, icon: .info, location: .top, shouldDismissOnTouch: { point in + let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: solution.text, textEntities: solution.entities, icon: .info, location: .top, shouldDismissOnTouch: { point in return .ignore }, openActiveTextItem: { [weak self] item, action in guard let strongSelf = self else { @@ -14408,7 +14458,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } - let tooltipScreen = TooltipScreen(account: self.context.account, text: psaText, textEntities: psaEntities, icon: .info, location: .top, displayDuration: .custom(10.0), shouldDismissOnTouch: { point in + let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: psaText, textEntities: psaEntities, icon: .info, location: .top, displayDuration: .custom(10.0), shouldDismissOnTouch: { point in return .ignore }, openActiveTextItem: { [weak self] item, action in guard let strongSelf = self else { @@ -14522,7 +14572,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } - let tooltipScreen = TooltipScreen(account: self.context.account, text: psaText, textEntities: psaEntities, icon: .info, location: .top, displayDuration: .custom(10.0), shouldDismissOnTouch: { point in + let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: psaText, textEntities: psaEntities, icon: .info, location: .top, displayDuration: .custom(10.0), shouldDismissOnTouch: { point in return .ignore }, openActiveTextItem: { [weak self] item, action in guard let strongSelf = self else { @@ -17173,7 +17223,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G })) } - private func openResolved(result: ResolvedUrl, sourceMessageId: MessageId?, forceExternal: Bool = false) { + private func openResolved(result: ResolvedUrl, sourceMessageId: MessageId?, forceExternal: Bool = false, concealed: Bool = false) { guard let peerId = self.chatLocation.peerId else { return } @@ -17230,7 +17280,28 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), attachBotStart: attachBotStart)) } case let .withBotApp(botAppStart): - strongSelf.presentBotApp(botApp: botAppStart.botApp, payload: botAppStart.payload) + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId.id)) + |> deliverOnMainQueue).start(next: { [weak self] peer in + if let strongSelf = self, let peer { + let openBotApp = { [weak self] in + if let strongSelf = self { + strongSelf.presentBotApp(botApp: botAppStart.botApp, botPeer: peerId, payload: botAppStart.payload) + } + } + if concealed { + let controller = webAppLaunchConfirmationController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: peer, commit: { + openBotApp() + }, showMore: { [weak self] in + if let strongSelf = self { + strongSelf.openResolved(result: .peer(peer._asPeer(), .info), sourceMessageId: nil) + } + }) + strongSelf.present(controller, in: .window(.root)) + } else { + openBotApp() + } + } + }) default: break } @@ -17256,7 +17327,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G openUserGeneratedUrl(context: self.context, peerId: self.peerView?.peerId, url: url, concealed: concealed, skipUrlAuth: skipUrlAuth, skipConcealedAlert: skipConcealedAlert, present: { [weak self] c in self?.present(c, in: .window(.root)) }, openResolved: { [weak self] resolved in - self?.openResolved(result: resolved, sourceMessageId: message?.id, forceExternal: forceExternal) + self?.openResolved(result: resolved, sourceMessageId: message?.id, forceExternal: forceExternal, concealed: concealed) }) }, performAction: true) } @@ -18485,22 +18556,25 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } - - let selectedEmoticon: String? = themeEmoticon + var canResetWallpaper = false + if let cachedUserData = strongSelf.peerView?.cachedData as? CachedUserData { + canResetWallpaper = cachedUserData.wallpaper != nil + } let controller = ChatThemeScreen( context: context, updatedPresentationData: strongSelf.updatedPresentationData, animatedEmojiStickers: animatedEmojiStickers, - initiallySelectedEmoticon: selectedEmoticon, + initiallySelectedEmoticon: themeEmoticon, peerName: strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer.flatMap(EnginePeer.init)?.compactDisplayTitle ?? "", + canResetWallpaper: canResetWallpaper, previewTheme: { [weak self] emoticon, dark in if let strongSelf = self { strongSelf.presentCrossfadeSnapshot() strongSelf.themeEmoticonAndDarkAppearancePreviewPromise.set(.single((emoticon, dark))) } }, - changeWallpaper: { + changeWallpaper: { [weak self] in guard let strongSelf = self, let peerId else { return } @@ -18519,60 +18593,64 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G navigationController.setViewControllers(controllers, animated: true) } } - - var canDelete = false - if let cachedUserData = strongSelf.peerView?.cachedData as? CachedUserData { - canDelete = cachedUserData.wallpaper != nil - } - let controller = wallpaperMediaPickerController( - context: strongSelf.context, - updatedPresentationData: strongSelf.updatedPresentationData, - peer: EnginePeer(peer), - canDelete: canDelete, - completion: { asset in - guard let strongSelf = self else { - return - } - let controller = WallpaperGalleryController(context: strongSelf.context, source: .asset(asset), mode: .peer(EnginePeer(peer), false)) - controller.navigationPresentation = .modal - controller.apply = { [weak self] wallpaper, options, cropRect in - if let strongSelf = self { - uploadCustomPeerWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, brightnessMultiplier: nil, peerId: peerId, completion: { - dismissControllers() - }) - } - } - strongSelf.push(controller) + var openWallpaperPickerImpl: (() -> Void)? + let openWallpaperPicker = { [weak self] in + guard let strongSelf = self else { + return } - ) - controller.navigationPresentation = .flatModal - strongSelf.push(controller) + let controller = wallpaperMediaPickerController( + context: strongSelf.context, + updatedPresentationData: strongSelf.updatedPresentationData, + peer: EnginePeer(peer), + completion: { [weak self] asset in + guard let strongSelf = self else { + return + } + let controller = WallpaperGalleryController(context: strongSelf.context, source: .asset(asset), mode: .peer(EnginePeer(peer), false)) + controller.navigationPresentation = .modal + controller.apply = { [weak self] wallpaper, options, cropRect, brightness in + if let strongSelf = self { + uploadCustomPeerWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, brightness: brightness, peerId: peerId, completion: { + dismissControllers() + }) + } + } + strongSelf.push(controller) + }, + openColors: { [weak self] in + guard let strongSelf = self else { + return + } + let controller = standaloneColorPickerController(context: strongSelf.context, peer: EnginePeer(peer), push: { [weak self] controller in + if let strongSelf = self { + strongSelf.push(controller) + } + }, openGallery: { + openWallpaperPickerImpl?() + }) + controller.navigationPresentation = .flatModal + strongSelf.push(controller) + } + ) + controller.navigationPresentation = .flatModal + strongSelf.push(controller) + } + openWallpaperPickerImpl = openWallpaperPicker + openWallpaperPicker() }, - changeColor: { - guard let strongSelf = self else { + resetWallpaper: { [weak self] in + guard let strongSelf = self, let peerId else { return } - if let themeController = strongSelf.themeScreen { - strongSelf.themeScreen = nil - themeController.dimTapped() - } - - var canDelete = false - if let cachedUserData = strongSelf.peerView?.cachedData as? CachedUserData { - canDelete = cachedUserData.wallpaper != nil - } - let controller = standaloneColorPickerController(context: strongSelf.context, peer: EnginePeer(peer), canDelete: canDelete, push: { [weak self] controller in - if let strongSelf = self { - strongSelf.push(controller) - } - }) - controller.navigationPresentation = .flatModal - strongSelf.push(controller) + let _ = strongSelf.context.engine.themes.setChatWallpaper(peerId: peerId, wallpaper: nil).start() }, completion: { [weak self] emoticon in guard let strongSelf = self, let peerId else { return } + if canResetWallpaper { + let _ = context.engine.themes.setChatWallpaper(peerId: peerId, wallpaper: nil).start() + } strongSelf.themeEmoticonAndDarkAppearancePreviewPromise.set(.single((emoticon ?? "", nil))) let _ = context.engine.themes.setChatTheme(peerId: peerId, emoticon: emoticon).start(completed: { [weak self] in if let strongSelf = self { @@ -18581,6 +18659,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } ) + controller.navigationPresentation = .flatModal controller.passthroughHitTestImpl = { [weak self] _ in if let strongSelf = self { return strongSelf.chatDisplayNode.historyNode.view @@ -18596,7 +18675,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.chatDisplayNode.historyNode.tapped = { [weak controller] in controller?.dimTapped() } - strongSelf.present(controller, in: .window(.root)) + strongSelf.push(controller) strongSelf.themeScreen = controller }) } @@ -18630,7 +18709,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }) { let isEmoji = actions[0].0.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks - strongSelf.presentInGlobalOverlay(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: isEmoji ? presentationData.strings.EmojiPackActionInfo_RemovedTitle : presentationData.strings.StickerPackActionInfo_RemovedTitle, text: isEmoji ? presentationData.strings.EmojiPackActionInfo_MultipleRemovedText(Int32(actions.count)) : presentationData.strings.StickerPackActionInfo_MultipleRemovedText(Int32(actions.count)), undo: true, info: actions[0].0, topItem: actions[0].1.first, context: context), elevatedLayout: true, animateInAsReplacement: false, action: { action in if case .undo = action { var itemsAndIndices: [(StickerPackCollectionInfo, [StickerPackItem], Int)] = actions.compactMap { action -> (StickerPackCollectionInfo, [StickerPackItem], Int)? in @@ -18650,7 +18728,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } else if let (info, items, action) = actions.first { let isEmoji = info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks - switch action { case .add: strongSelf.presentInGlobalOverlay(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: isEmoji ? presentationData.strings.EmojiPackActionInfo_AddedTitle : presentationData.strings.StickerPackActionInfo_AddedTitle, text: isEmoji ? presentationData.strings.EmojiPackActionInfo_AddedText(info.title).string : presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: items.first, context: context), elevatedLayout: true, animateInAsReplacement: false, action: { _ in diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index a695150d1c..e92874376b 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -1472,43 +1472,47 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } else { transition.animatePosition(layer: self.startButton.layer, from: CGPoint(x: 0.0, y: 80.0), to: CGPoint(), additive: true) } - } - - if let context = self.context { - let absoluteFrame = self.startButton.view.convert(self.startButton.bounds, to: nil) - let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 1.0), size: CGSize()) - - if let tooltipController = self.tooltipController { - if self.view.window != nil { - tooltipController.location = .point(location, .bottom) - } - } else { - let controller = TooltipScreen(account: context.account, text: interfaceState.strings.Bot_TapToUse, icon: .downArrows, location: .point(location, .bottom), displayDuration: .infinite, shouldDismissOnTouch: { _ in - return .ignore - }) - controller.alwaysVisible = true - self.tooltipController = controller + if let context = self.context { + let absoluteFrame = self.startButton.view.convert(self.startButton.bounds, to: nil) + let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 1.0), size: CGSize()) - let delay: Double - if case .regular = metrics.widthClass { - delay = 0.1 + if let tooltipController = self.tooltipController { + if self.view.window != nil { + tooltipController.location = .point(location, .bottom) + } } else { - delay = 0.35 + let controller = TooltipScreen(account: context.account, sharedContext: context.sharedContext, text: interfaceState.strings.Bot_TapToUse, icon: .downArrows, location: .point(location, .bottom), displayDuration: .infinite, shouldDismissOnTouch: { _ in + return .ignore + }) + controller.alwaysVisible = true + self.tooltipController = controller + + let delay: Double + if case .regular = metrics.widthClass { + delay = 0.1 + } else { + delay = 0.35 + } + Queue.mainQueue().after(delay, { + let absoluteFrame = self.startButton.view.convert(self.startButton.bounds, to: nil) + let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 1.0), size: CGSize()) + controller.location = .point(location, .bottom) + self.interfaceInteraction?.presentControllerInCurrent(controller, nil) + }) } - Queue.mainQueue().after(delay, { - let absoluteFrame = self.startButton.view.convert(self.startButton.bounds, to: nil) - let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 1.0), size: CGSize()) - controller.location = .point(location, .bottom) - self.interfaceInteraction?.presentControllerInCurrent(controller, nil) - }) + } + } else { + if hasMenuButton && !self.animatingTransition { + self.menuButton.isHidden = true } } } else if !self.startButton.isHidden { if hasMenuButton { self.animateBotButtonOutToMenu(transition: transition) } else { - transition.animatePosition(node: self.startButton, to: CGPoint(x: 0.0, y: 80.0), additive: true, completion: { _ in + transition.animatePosition(node: self.startButton, to: CGPoint(x: 0.0, y: 80.0), removeOnCompletion: false, additive: true, completion: { _ in self.startButton.isHidden = true + self.startButton.layer.removeAllAnimations() }) } } diff --git a/submodules/TelegramUI/Sources/ChatThemeScreen.swift b/submodules/TelegramUI/Sources/ChatThemeScreen.swift index dd015c8b58..035a156600 100644 --- a/submodules/TelegramUI/Sources/ChatThemeScreen.swift +++ b/submodules/TelegramUI/Sources/ChatThemeScreen.swift @@ -554,9 +554,10 @@ final class ChatThemeScreen: ViewController { private let animatedEmojiStickers: [String: [StickerPackItem]] private let initiallySelectedEmoticon: String? private let peerName: String + let canResetWallpaper: Bool private let previewTheme: (String?, Bool?) -> Void fileprivate let changeWallpaper: () -> Void - fileprivate let changeColor: () -> Void + fileprivate let resetWallpaper: () -> Void private let completion: (String?) -> Void private var presentationData: PresentationData @@ -578,9 +579,10 @@ final class ChatThemeScreen: ViewController { animatedEmojiStickers: [String: [StickerPackItem]], initiallySelectedEmoticon: String?, peerName: String, + canResetWallpaper: Bool, previewTheme: @escaping (String?, Bool?) -> Void, changeWallpaper: @escaping () -> Void, - changeColor: @escaping () -> Void, + resetWallpaper: @escaping () -> Void, completion: @escaping (String?) -> Void ) { self.context = context @@ -588,15 +590,18 @@ final class ChatThemeScreen: ViewController { self.animatedEmojiStickers = animatedEmojiStickers self.initiallySelectedEmoticon = initiallySelectedEmoticon self.peerName = peerName + self.canResetWallpaper = canResetWallpaper self.previewTheme = previewTheme self.changeWallpaper = changeWallpaper - self.changeColor = changeColor + self.resetWallpaper = resetWallpaper self.completion = completion super.init(navigationBarPresentationData: nil) self.statusBar.statusBarStyle = .Ignore + self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) + self.blocksBackgroundWhenInOverlay = true self.presentationDataDisposable = (updatedPresentationData.signal @@ -634,20 +639,20 @@ final class ChatThemeScreen: ViewController { guard let strongSelf = self else { return } - strongSelf.dismiss() + strongSelf.dismiss(animated: true) if strongSelf.initiallySelectedEmoticon == nil && emoticon == nil { } else { strongSelf.completion(emoticon) } } self.controllerNode.dismiss = { [weak self] in - self?.presentingViewController?.dismiss(animated: false, completion: nil) + self?.dismiss(animated: false) } self.controllerNode.cancel = { [weak self] in guard let strongSelf = self else { return } - strongSelf.dismiss() + strongSelf.dismiss(animated: true) strongSelf.previewTheme(nil, nil) } } @@ -667,15 +672,22 @@ final class ChatThemeScreen: ViewController { } } - override public func dismiss(completion: (() -> Void)? = nil) { + override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { self.forEachController({ controller in if let controller = controller as? TooltipScreen { controller.dismiss() } return true }) - - self.controllerNode.animateOut(completion: completion) + + if flag { + self.controllerNode.animateOut(completion: { + super.dismiss(animated: flag, completion: completion) + completion?() + }) + } else { + super.dismiss(animated: flag, completion: completion) + } self.dismissed?() } @@ -889,6 +901,8 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega return } + let selectedEmoticon = selectedEmoticon?.strippedEmoji + let isFirstTime = strongSelf.entries == nil let presentationData = strongSelf.presentationData @@ -898,7 +912,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega guard let emoticon = theme.emoticon else { continue } - entries.append(ThemeSettingsThemeEntry(index: entries.count, emoticon: emoticon, emojiFile: animatedEmojiStickers[emoticon]?.first?.file, themeReference: .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: nil)), nightMode: isDarkAppearance, selected: selectedEmoticon == theme.emoticon, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil)) + entries.append(ThemeSettingsThemeEntry(index: entries.count, emoticon: emoticon, emojiFile: animatedEmojiStickers[emoticon]?.first?.file, themeReference: .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: nil)), nightMode: isDarkAppearance, selected: selectedEmoticon == theme.emoticon?.strippedEmoji, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil)) } let action: (String?) -> Void = { [weak self] emoticon in @@ -981,7 +995,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega var scrollToItem: ListViewScrollToItem? if !self.initialized { if let index = transition.entries.firstIndex(where: { entry in - return entry.emoticon == self.initiallySelectedEmoticon + return entry.emoticon?.strippedEmoji == self.initiallySelectedEmoticon?.strippedEmoji }) { scrollToItem = ListViewScrollToItem(index: index, position: .bottom(-57.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Down) self.initialized = true @@ -1013,18 +1027,22 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega } private func updateButtons() { + let canResetWallpaper = self.controller?.canResetWallpaper ?? false + let doneButtonTitle: String let otherButtonTitle: String var accentButtonTheme = true - if self.selectedEmoticon == self.initiallySelectedEmoticon { + var destructiveOtherButton = false + if self.selectedEmoticon?.strippedEmoji == self.initiallySelectedEmoticon?.strippedEmoji { doneButtonTitle = self.presentationData.strings.Conversation_Theme_SetPhotoWallpaper - otherButtonTitle = self.presentationData.strings.Conversation_Theme_SetColorWallpaper + otherButtonTitle = canResetWallpaper ? self.presentationData.strings.Conversation_Theme_ResetWallpaper : self.presentationData.strings.Common_Cancel accentButtonTheme = false + destructiveOtherButton = canResetWallpaper } else if self.selectedEmoticon == nil && self.initiallySelectedEmoticon != nil { doneButtonTitle = self.presentationData.strings.Conversation_Theme_Reset otherButtonTitle = self.presentationData.strings.Conversation_Theme_OtherOptions } else { - doneButtonTitle = self.presentationData.strings.Conversation_Theme_Apply + doneButtonTitle = self.presentationData.strings.Conversation_Theme_ApplyBackground otherButtonTitle = self.presentationData.strings.Conversation_Theme_OtherOptions } @@ -1040,7 +1058,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega } self.doneButton.updateTheme(buttonTheme) - self.otherButton.setTitle(otherButtonTitle, with: Font.regular(17.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal) + self.otherButton.setTitle(otherButtonTitle, with: Font.regular(17.0), with: destructiveOtherButton ? self.presentationData.theme.actionSheet.destructiveActionTextColor : self.presentationData.theme.actionSheet.controlAccentColor, for: .normal) } private var switchThemeIconAnimator: DisplayLinkAnimator? @@ -1099,15 +1117,20 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega } @objc func otherButtonPressed() { - if self.selectedEmoticon != self.initiallySelectedEmoticon { + if self.selectedEmoticon?.strippedEmoji != self.initiallySelectedEmoticon?.strippedEmoji { self.setEmoticon(self.initiallySelectedEmoticon) } else { - self.controller?.changeColor() + if self.controller?.canResetWallpaper == true { + self.controller?.resetWallpaper() + self.cancelButtonPressed() + } else { + self.cancelButtonPressed() + } } } func dimTapped() { - if self.selectedEmoticon == self.initiallySelectedEmoticon { + if self.selectedEmoticon?.strippedEmoji == self.initiallySelectedEmoticon?.strippedEmoji { self.cancelButtonPressed() } else { let alertController = textAlertController(context: self.context, updatedPresentationData: (self.presentationData, .single(self.presentationData)), title: nil, text: self.presentationData.strings.Conversation_Theme_DismissAlert, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Conversation_Theme_DismissAlertApply, action: { [weak self] in @@ -1229,7 +1252,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega if let strongSelf = self, count < 2 && currentTimestamp > timestamp + 24 * 60 * 60 { strongSelf.displayedPreviewTooltip = true - strongSelf.present?(TooltipScreen(account: strongSelf.context.account, text: isDark ? strongSelf.presentationData.strings.Conversation_Theme_PreviewLight(strongSelf.peerName).string : strongSelf.presentationData.strings.Conversation_Theme_PreviewDark(strongSelf.peerName).string, style: .default, icon: nil, location: .point(frame.offsetBy(dx: 3.0, dy: 6.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in + strongSelf.present?(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: isDark ? strongSelf.presentationData.strings.Conversation_Theme_PreviewLight(strongSelf.peerName).string : strongSelf.presentationData.strings.Conversation_Theme_PreviewDark(strongSelf.peerName).string, style: .default, icon: nil, location: .point(frame.offsetBy(dx: 3.0, dy: 6.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in return .dismiss(consume: false) })) diff --git a/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift b/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift index bea052c9e8..b2170a46ff 100644 --- a/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift +++ b/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift @@ -398,9 +398,9 @@ private func fetchCachedBlurredWallpaperRepresentation(resource: MediaResource, let path = NSTemporaryDirectory() + "\(Int64.random(in: Int64.min ... Int64.max))" let url = URL(fileURLWithPath: path) - if let colorImage = blurredImage(image, radius: 20.0), let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) { + if let colorImage = blurredImage(image, radius: 30.0), let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) { CGImageDestinationSetProperties(colorDestination, NSDictionary() as CFDictionary) - + let colorQuality: Float = 0.5 let options = NSMutableDictionary() @@ -447,7 +447,7 @@ private func fetchCachedBlurredWallpaperRepresentation(account: Account, resourc let path = NSTemporaryDirectory() + "\(Int64.random(in: Int64.min ... Int64.max))" let url = URL(fileURLWithPath: path) - if let colorImage = blurredImage(image, radius: 20.0), let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) { + if let colorImage = blurredImage(image, radius: 30.0), let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) { CGImageDestinationSetProperties(colorDestination, NSDictionary() as CFDictionary) let colorQuality: Float = 0.5 diff --git a/submodules/TelegramUI/Sources/NavigateToChatController.swift b/submodules/TelegramUI/Sources/NavigateToChatController.swift index 396aba0eab..4776b1c213 100644 --- a/submodules/TelegramUI/Sources/NavigateToChatController.swift +++ b/submodules/TelegramUI/Sources/NavigateToChatController.swift @@ -132,8 +132,8 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam if let attachBotStart = params.attachBotStart { controller.presentAttachmentBot(botId: attachBotStart.botId, payload: attachBotStart.payload, justInstalled: attachBotStart.justInstalled) } - if let botAppStart = params.botAppStart { - controller.presentBotApp(botApp: botAppStart.botApp, payload: botAppStart.payload) + if let botAppStart = params.botAppStart, case let .peer(peer) = params.chatLocation { + controller.presentBotApp(botApp: botAppStart.botApp, botPeer: peer, payload: botAppStart.payload) } } else { controller = ChatControllerImpl(context: params.context, chatLocation: params.chatLocation.asChatLocation, chatLocationContextHolder: params.chatLocationContextHolder, subject: params.subject, botStart: params.botStart, attachBotStart: params.attachBotStart, botAppStart: params.botAppStart, peekData: params.peekData, peerNearbyData: params.peerNearbyData, chatListFilter: params.chatListFilter, chatNavigationStack: params.chatNavigationStack) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 3ae82617db..afc7f249cf 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -8467,7 +8467,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate return } let buttonFrame = buttonNode.view.convert(buttonNode.bounds, to: self.view) - controller.present(TooltipScreen(account: self.context.account, text: self.presentationData.strings.SharedMedia_CalendarTooltip, style: .default, icon: .none, location: .point(buttonFrame.insetBy(dx: 0.0, dy: 5.0), .top), shouldDismissOnTouch: { point in + controller.present(TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: self.presentationData.strings.SharedMedia_CalendarTooltip, style: .default, icon: .none, location: .point(buttonFrame.insetBy(dx: 0.0, dy: 5.0), .top), shouldDismissOnTouch: { point in return .dismiss(consume: false) }), in: .current) } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 2d8bcb82c5..43d8e09c8e 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -162,6 +162,9 @@ public final class SharedAccountContextImpl: SharedAccountContext { public let currentMediaInputSettings: Atomic private var mediaInputSettingsDisposable: Disposable? + public let currentMediaDisplaySettings: Atomic + private var mediaDisplaySettingsDisposable: Disposable? + public let currentStickerSettings: Atomic private var stickerSettingsDisposable: Disposable? @@ -241,6 +244,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { self.currentAutomaticMediaDownloadSettings = initialPresentationDataAndSettings.automaticMediaDownloadSettings self.currentAutodownloadSettings = Atomic(value: initialPresentationDataAndSettings.autodownloadSettings) self.currentMediaInputSettings = Atomic(value: initialPresentationDataAndSettings.mediaInputSettings) + self.currentMediaDisplaySettings = Atomic(value: initialPresentationDataAndSettings.mediaDisplaySettings) self.currentStickerSettings = Atomic(value: initialPresentationDataAndSettings.stickerSettings) self.currentInAppNotificationSettings = Atomic(value: initialPresentationDataAndSettings.inAppNotificationSettings) @@ -359,6 +363,15 @@ public final class SharedAccountContextImpl: SharedAccountContext { } }) + self.mediaDisplaySettingsDisposable = (self.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.mediaDisplaySettings]) + |> deliverOnMainQueue).start(next: { [weak self] sharedData in + if let strongSelf = self { + if let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.mediaDisplaySettings]?.get(MediaDisplaySettings.self) { + let _ = strongSelf.currentMediaDisplaySettings.swap(settings) + } + } + }) + self.stickerSettingsDisposable = (self.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings]) |> deliverOnMainQueue).start(next: { [weak self] sharedData in if let strongSelf = self { @@ -895,6 +908,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { self.currentAutodownloadSettingsDisposable.dispose() self.inAppNotificationSettingsDisposable?.dispose() self.mediaInputSettingsDisposable?.dispose() + self.mediaDisplaySettingsDisposable?.dispose() self.callDisposable?.dispose() self.groupCallDisposable?.dispose() self.callStateDisposable?.dispose() diff --git a/submodules/TelegramUIPreferences/Sources/MediaDisplaySettings.swift b/submodules/TelegramUIPreferences/Sources/MediaDisplaySettings.swift new file mode 100644 index 0000000000..2e59eab787 --- /dev/null +++ b/submodules/TelegramUIPreferences/Sources/MediaDisplaySettings.swift @@ -0,0 +1,50 @@ +import Foundation +import Postbox +import TelegramCore +import SwiftSignalKit + +public struct MediaDisplaySettings: Codable, Equatable { + public let showNextMediaOnTap: Bool + + public static var defaultSettings: MediaDisplaySettings { + return MediaDisplaySettings(showNextMediaOnTap: true) + } + + public init(showNextMediaOnTap: Bool) { + self.showNextMediaOnTap = showNextMediaOnTap + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: StringCodingKey.self) + + self.showNextMediaOnTap = (try container.decode(Int32.self, forKey: "showNextMediaOnTap")) != 0 + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: StringCodingKey.self) + + try container.encode((self.showNextMediaOnTap ? 1 : 0) as Int32, forKey: "showNextMediaOnTap") + } + + public static func ==(lhs: MediaDisplaySettings, rhs: MediaDisplaySettings) -> Bool { + return lhs.showNextMediaOnTap == rhs.showNextMediaOnTap + } + + public func withUpdatedShowNextMediaOnTap(_ showNextMediaOnTap: Bool) -> MediaDisplaySettings { + return MediaDisplaySettings(showNextMediaOnTap: showNextMediaOnTap) + } +} + +public func updateMediaDisplaySettingsInteractively(accountManager: AccountManager, _ f: @escaping (MediaDisplaySettings) -> MediaDisplaySettings) -> Signal { + return accountManager.transaction { transaction -> Void in + transaction.updateSharedData(ApplicationSpecificSharedDataKeys.mediaDisplaySettings, { entry in + let currentSettings: MediaDisplaySettings + if let entry = entry?.get(MediaDisplaySettings.self) { + currentSettings = entry + } else { + currentSettings = MediaDisplaySettings.defaultSettings + } + return PreferencesEntry(f(currentSettings)) + }) + } +} diff --git a/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift b/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift index f53c296037..5ac78a3560 100644 --- a/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift +++ b/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift @@ -39,6 +39,7 @@ private enum ApplicationSpecificSharedDataKeyValues: Int32 { case intentsSettings = 17 case translationSettings = 18 case drawingSettings = 19 + case mediaDisplaySettings = 20 } public struct ApplicationSpecificSharedDataKeys { @@ -62,6 +63,7 @@ public struct ApplicationSpecificSharedDataKeys { public static let intentsSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.intentsSettings.rawValue) public static let translationSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.translationSettings.rawValue) public static let drawingSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.drawingSettings.rawValue) + public static let mediaDisplaySettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.mediaDisplaySettings.rawValue) } private enum ApplicationSpecificItemCacheCollectionIdValues: Int8 { diff --git a/submodules/TooltipUI/Sources/TooltipScreen.swift b/submodules/TooltipUI/Sources/TooltipScreen.swift index 0d0b017547..36e6eaacc7 100644 --- a/submodules/TooltipUI/Sources/TooltipScreen.swift +++ b/submodules/TooltipUI/Sources/TooltipScreen.swift @@ -11,6 +11,7 @@ import TelegramCore import TextFormat import Postbox import UrlEscaping +import AccountContext public protocol TooltipCustomContentNode: ASDisplayNode { func animateIn() @@ -127,12 +128,11 @@ private final class TooltipScreenNode: ViewControllerTracingNode { private let backgroundContainerNode: ASDisplayNode private let backgroundClipNode: ASDisplayNode private let backgroundMaskNode: ASDisplayNode - private var effectView: UIView? + private var effectNode: NavigationBackgroundNode? private var gradientNode: ASDisplayNode? private var arrowGradientNode: ASDisplayNode? private let arrowNode: ASImageNode private let arrowContainer: ASDisplayNode - private var arrowEffectView: UIView? private let animatedStickerNode: AnimatedStickerNode private var downArrowsNode: DownArrowsIconNode? private let textNode: ImmediateTextNode @@ -143,7 +143,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode { private var validLayout: ContainerViewLayout? - init(account: Account, text: String, textEntities: [MessageTextEntity], style: TooltipScreen.Style, icon: TooltipScreen.Icon?, customContentNode: TooltipCustomContentNode? = nil, location: TooltipScreen.Location, displayDuration: TooltipScreen.DisplayDuration, inset: CGFloat = 13.0, shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, requestDismiss: @escaping () -> Void, openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)?) { + init(account: Account, sharedContext: SharedAccountContext, text: String, textEntities: [MessageTextEntity], style: TooltipScreen.Style, icon: TooltipScreen.Icon?, customContentNode: TooltipCustomContentNode? = nil, location: TooltipScreen.Location, displayDuration: TooltipScreen.DisplayDuration, inset: CGFloat = 13.0, shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, requestDismiss: @escaping () -> Void, openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)?) { self.tooltipStyle = style self.icon = icon self.customContentNode = customContentNode @@ -210,9 +210,16 @@ private final class TooltipScreenNode: ViewControllerTracingNode { self.arrowContainer = ASDisplayNode() + let theme = sharedContext.currentPresentationData.with { $0 }.theme let fontSize: CGFloat if case .top = location { - self.effectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark)) + let backgroundColor: UIColor + if theme.overallDarkAppearance { + backgroundColor = theme.rootController.navigationBar.blurredBackgroundColor + } else { + backgroundColor = UIColor(rgb: 0x000000, alpha: 0.6) + } + self.effectNode = NavigationBackgroundNode(color: backgroundColor) self.backgroundMaskNode.addSubnode(self.backgroundClipNode) self.backgroundClipNode.clipsToBounds = true if case let .point(_, arrowPosition) = location, case .right = arrowPosition { @@ -258,20 +265,28 @@ private final class TooltipScreenNode: ViewControllerTracingNode { maskLayer.frame = CGRect(origin: CGPoint(), size: arrowSize) self.arrowContainer.layer.mask = maskLayer } else { - let effect: UIBlurEffect - if case .light = style { - effect = UIBlurEffect(style: .light) + var enableSaturation = true + let backgroundColor: UIColor + if case let .customBlur(color) = style { + backgroundColor = color + enableSaturation = false + } else if case .light = style { + backgroundColor = theme.rootController.navigationBar.blurredBackgroundColor } else { - effect = UIBlurEffect(style: .dark) + if theme.overallDarkAppearance { + backgroundColor = theme.rootController.navigationBar.blurredBackgroundColor + } else { + backgroundColor = UIColor(rgb: 0x000000, alpha: 0.6) + } } - self.effectView = UIVisualEffectView(effect: effect) + self.effectNode = NavigationBackgroundNode(color: backgroundColor, enableBlur: true, enableSaturation: enableSaturation) self.backgroundMaskNode.addSubnode(self.backgroundClipNode) self.backgroundClipNode.clipsToBounds = true if case let .point(_, arrowPosition) = location, case .right = arrowPosition { self.backgroundClipNode.cornerRadius = 8.5 } else { - self.backgroundClipNode.cornerRadius = 14.0 + self.backgroundClipNode.cornerRadius = 12.5 } if #available(iOS 13.0, *) { self.backgroundClipNode.layer.cornerCurve = .continuous @@ -318,8 +333,8 @@ private final class TooltipScreenNode: ViewControllerTracingNode { if let gradientNode = self.gradientNode { self.backgroundContainerNode.addSubnode(gradientNode) self.containerNode.addSubnode(self.arrowContainer) - } else if let effectView = self.effectView { - self.backgroundContainerNode.view.addSubview(effectView) + } else if let effectNode = self.effectNode { + self.backgroundContainerNode.addSubnode(effectNode) self.backgroundContainerNode.layer.mask = self.backgroundMaskNode.layer } self.containerNode.addSubnode(self.textNode) @@ -430,7 +445,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode { let backgroundHeight: CGFloat switch self.tooltipStyle { - case .default, .gradient: + case .default, .gradient, .customBlur: backgroundHeight = max(animationSize.height, textSize.height) + contentVerticalInset * 2.0 case .light: backgroundHeight = max(28.0, max(animationSize.height, textSize.height) + 4.0 * 2.0) @@ -472,8 +487,10 @@ private final class TooltipScreenNode: ViewControllerTracingNode { transition.updateFrame(node: self.backgroundMaskNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size).insetBy(dx: -10.0, dy: -10.0)) transition.updateFrame(node: self.backgroundClipNode, frame: CGRect(origin: CGPoint(x: 10.0, y: 10.0), size: backgroundFrame.size)) - if let effectView = self.effectView { - transition.updateFrame(view: effectView, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size).insetBy(dx: -10.0, dy: -10.0)) + if let effectNode = self.effectNode { + let effectFrame = CGRect(origin: CGPoint(), size: backgroundFrame.size).insetBy(dx: -10.0, dy: -10.0) + transition.updateFrame(node: effectNode, frame: effectFrame) + effectNode.update(size: effectFrame.size, transition: transition) } if let gradientNode = self.gradientNode { transition.updateFrame(node: gradientNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size)) @@ -501,7 +518,6 @@ private final class TooltipScreenNode: ViewControllerTracingNode { let arrowBounds = CGRect(origin: CGPoint(), size: arrowSize) self.arrowNode.frame = arrowBounds - self.arrowEffectView?.frame = arrowBounds self.arrowGradientNode?.frame = CGRect(origin: CGPoint(x: -arrowFrame.minX + backgroundFrame.minX, y: 0.0), size: backgroundFrame.size) case .right: arrowFrame = CGRect(origin: CGPoint(x: backgroundFrame.width + arrowSize.height, y: rect.midY), size: CGSize(width: arrowSize.height, height: arrowSize.width)) @@ -512,12 +528,10 @@ private final class TooltipScreenNode: ViewControllerTracingNode { let arrowBounds = CGRect(origin: .zero, size: arrowSize) self.arrowNode.frame = arrowBounds - self.arrowEffectView?.frame = arrowBounds self.arrowGradientNode?.frame = arrowBounds } } else { self.arrowNode.isHidden = true - self.arrowEffectView?.isHidden = true } transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: contentInset + animationSize.width + animationSpacing, y: floor((backgroundHeight - textSize.height) / 2.0)), size: textSize)) @@ -672,10 +686,12 @@ public final class TooltipScreen: ViewController { public enum Style { case `default` case light + case customBlur(UIColor) case gradient(UIColor, UIColor) } private let account: Account + private let sharedContext: SharedAccountContext public let text: String public let textEntities: [MessageTextEntity] private let style: TooltipScreen.Style @@ -707,8 +723,9 @@ public final class TooltipScreen: ViewController { public var alwaysVisible = false - public init(account: Account, text: String, textEntities: [MessageTextEntity] = [], style: TooltipScreen.Style = .default, icon: TooltipScreen.Icon?, customContentNode: TooltipCustomContentNode? = nil, location: TooltipScreen.Location, displayDuration: DisplayDuration = .default, inset: CGFloat = 13.0, shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)? = nil) { + public init(account: Account, sharedContext: SharedAccountContext, text: String, textEntities: [MessageTextEntity] = [], style: TooltipScreen.Style = .default, icon: TooltipScreen.Icon?, customContentNode: TooltipCustomContentNode? = nil, location: TooltipScreen.Location, displayDuration: DisplayDuration = .default, inset: CGFloat = 13.0, shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)? = nil) { self.account = account + self.sharedContext = sharedContext self.text = text self.textEntities = textEntities self.style = style @@ -776,7 +793,7 @@ public final class TooltipScreen: ViewController { } override public func loadDisplayNode() { - self.displayNode = TooltipScreenNode(account: self.account, text: self.text, textEntities: self.textEntities, style: self.style, icon: self.icon, customContentNode: self.customContentNode, location: self.location, displayDuration: self.displayDuration, inset: self.inset, shouldDismissOnTouch: self.shouldDismissOnTouch, requestDismiss: { [weak self] in + self.displayNode = TooltipScreenNode(account: self.account, sharedContext: self.sharedContext, text: self.text, textEntities: self.textEntities, style: self.style, icon: self.icon, customContentNode: self.customContentNode, location: self.location, displayDuration: self.displayDuration, inset: self.inset, shouldDismissOnTouch: self.shouldDismissOnTouch, requestDismiss: { [weak self] in guard let strongSelf = self else { return } diff --git a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift index 0095b1c454..af8c9bc9f0 100644 --- a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift +++ b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift @@ -743,6 +743,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode private var gradientBackgroundNode: GradientBackgroundNode? private var outgoingBubbleGradientBackgroundNode: GradientBackgroundNode? private let patternImageLayer: EffectImageLayer + private let dimLayer: SimpleLayer private var isGeneratingPatternImage: Bool = false private let bakedBackgroundView: UIImageView @@ -862,6 +863,9 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode self.bakedBackgroundView = UIImageView() self.bakedBackgroundView.isHidden = true + self.dimLayer = SimpleLayer() + self.dimLayer.backgroundColor = UIColor.black.cgColor + super.init() if #available(iOS 12.0, *) { @@ -885,6 +889,8 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode self.contentNode.frame = self.bounds self.addSubnode(self.contentNode) self.layer.addSublayer(self.patternImageLayer) + + self.layer.addSublayer(self.dimLayer) } deinit { @@ -892,6 +898,19 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode self.wallpaperDisposable.dispose() self.imageDisposable.dispose() } + + private func updateDimming() { + guard let wallpaper = self.wallpaper, let theme = self.bubbleTheme else { + return + } + var dimAlpha: Float = 0.0 + if case let .file(file) = wallpaper, !file.isPattern { + if let intensity = file.settings.intensity, intensity < 100, theme.overallDarkAppearance == true { + dimAlpha = 1.0 - max(0.0, min(1.0, Float(intensity) / 100.0)) + } + } + self.dimLayer.opacity = dimAlpha + } func update(wallpaper: TelegramWallpaper) { if self.wallpaper == wallpaper { @@ -915,7 +934,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode gradientColors = file.settings.colors gradientAngle = file.settings.rotation ?? 0 } - + var scheduleLoopingEvent = false if gradientColors.count >= 3 { let mappedColors = gradientColors.map { color -> UIColor in @@ -1032,6 +1051,8 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode self.animateEvent(transition: .animated(duration: 0.7, curve: .linear), extendAnimation: false) } } + + self.updateDimming() } func _internalUpdateIsSettingUpWallpaper() { @@ -1304,6 +1325,8 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode transition.updateFrame(node: outgoingBackgroundNode, frame: CGRect(origin: CGPoint(), size: size)) outgoingBackgroundNode.update(rect: CGRect(origin: CGPoint(), size: size), within: size, transition: transition) } + + transition.updateFrame(layer: self.dimLayer, frame: CGRect(origin: CGPoint(), size: size)) self.loadPatternForSizeIfNeeded(size: size, displayMode: displayMode, transition: transition) @@ -1378,6 +1401,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode } self.updateBubbles() + self.updateDimming() } } diff --git a/submodules/WallpaperResources/Sources/WallpaperResources.swift b/submodules/WallpaperResources/Sources/WallpaperResources.swift index a4028d56ee..5cf2e4f506 100644 --- a/submodules/WallpaperResources/Sources/WallpaperResources.swift +++ b/submodules/WallpaperResources/Sources/WallpaperResources.swift @@ -144,7 +144,11 @@ public func wallpaperDatas(account: Account, accountManager: AccountManager Void + + override var dismissOnOutsideTap: Bool { + return self.isUserInteractionEnabled + } + + init(context: AccountContext, theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, peer: EnginePeer, title: String, text: String, showMore: Bool, actions: [TextAlertAction], morePressed: @escaping () -> Void) { + self.strings = strings + self.title = title + self.text = text + self.showMore = showMore + self.morePressed = morePressed + + self.titleNode = ImmediateTextNode() + self.titleNode.displaysAsynchronously = false + self.titleNode.maximumNumberOfLines = 1 + self.titleNode.textAlignment = .center + + self.textNode = ASTextNode() + self.textNode.displaysAsynchronously = false + self.textNode.maximumNumberOfLines = 0 + + self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0)) + + self.moreButton = HighlightableButtonNode() + + self.arrowNode = ASImageNode() + self.arrowNode.displaysAsynchronously = false + self.arrowNode.displayWithoutProcessing = true + self.arrowNode.isHidden = !showMore + self.arrowNode.contentMode = .scaleAspectFit + + self.actionNodesSeparator = ASDisplayNode() + self.actionNodesSeparator.isLayerBacked = true + + self.actionNodes = actions.map { action -> TextAlertContentActionNode in + return TextAlertContentActionNode(theme: theme, action: action) + } + + var actionVerticalSeparators: [ASDisplayNode] = [] + if actions.count > 1 { + for _ in 0 ..< actions.count - 1 { + let separatorNode = ASDisplayNode() + separatorNode.isLayerBacked = true + actionVerticalSeparators.append(separatorNode) + } + } + self.actionVerticalSeparators = actionVerticalSeparators + + super.init() + + self.addSubnode(self.titleNode) + self.addSubnode(self.textNode) + self.addSubnode(self.avatarNode) + self.addSubnode(self.moreButton) + self.moreButton.addSubnode(self.arrowNode) + + self.addSubnode(self.actionNodesSeparator) + + for actionNode in self.actionNodes { + self.addSubnode(actionNode) + } + + for separatorNode in self.actionVerticalSeparators { + self.addSubnode(separatorNode) + } + + self.updateTheme(theme) + + self.avatarNode.setPeer(context: context, theme: ptheme, peer: peer) + + self.moreButton.addTarget(self, action: #selector(self.moreButtonPressed), forControlEvents: .touchUpInside) + } + + @objc private func moreButtonPressed() { + self.morePressed() + } + + override func updateTheme(_ theme: AlertControllerTheme) { + self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.semibold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) + self.textNode.attributedText = NSAttributedString(string: self.text, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center) + + self.moreButton.setAttributedTitle(NSAttributedString(string: self.strings.WebApp_LaunchMoreInfo, font: Font.regular(13.0), textColor: theme.accentColor), for: .normal) + self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Peer Info/AlertArrow"), color: theme.accentColor) + + self.actionNodesSeparator.backgroundColor = theme.separatorColor + for actionNode in self.actionNodes { + actionNode.updateTheme(theme) + } + for separatorNode in self.actionVerticalSeparators { + separatorNode.backgroundColor = theme.separatorColor + } + + if let size = self.validLayout { + _ = self.updateLayout(size: size, transition: .immediate) + } + } + + override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + var size = size + size.width = min(size.width, 270.0) + + self.validLayout = size + + var origin: CGPoint = CGPoint(x: 0.0, y: 20.0) + + let avatarSize = CGSize(width: 60.0, height: 60.0) + self.avatarNode.updateSize(size: avatarSize) + + let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0), y: origin.y), size: avatarSize) + transition.updateFrame(node: self.avatarNode, frame: avatarFrame) + + origin.y += avatarSize.height + 17.0 + + if let arrowImage = self.arrowNode.image { + let arrowFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - arrowImage.size.width) / 2.0), y: origin.y + floorToScreenPixels((avatarSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size) + transition.updateFrame(node: self.arrowNode, frame: arrowFrame) + } + + let titleSize = self.titleNode.updateLayout(CGSize(width: size.width - 32.0, height: size.height)) + transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize)) + origin.y += titleSize.height + 6.0 + + if self.showMore { + let moreButtonSize = self.moreButton.measure(CGSize(width: size.width - 32.0, height: size.height)) + transition.updateFrame(node: self.moreButton, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - moreButtonSize.width) / 2.0) - 5.0, y: origin.y), size: moreButtonSize)) + transition.updateFrame(node: self.arrowNode, frame: CGRect(origin: CGPoint(x: moreButtonSize.width + 3.0, y: 4.0), size: CGSize(width: 9.0, height: 9.0))) + origin.y += moreButtonSize.height + 22.0 + } + + let textSize = self.textNode.measure(CGSize(width: size.width - 32.0, height: size.height)) + transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)) + + let actionButtonHeight: CGFloat = 44.0 + var minActionsWidth: CGFloat = 0.0 + let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) + let actionTitleInsets: CGFloat = 8.0 + + var effectiveActionLayout = TextAlertContentActionLayout.horizontal + for actionNode in self.actionNodes { + let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) + if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { + effectiveActionLayout = .vertical + } + switch effectiveActionLayout { + case .horizontal: + minActionsWidth += actionTitleSize.width + actionTitleInsets + case .vertical: + minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) + } + } + + let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0) + + let contentWidth = max(size.width, minActionsWidth) + + var actionsHeight: CGFloat = 0.0 + switch effectiveActionLayout { + case .horizontal: + actionsHeight = actionButtonHeight + case .vertical: + actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) + } + + var resultSize = CGSize(width: contentWidth, height: avatarSize.height + titleSize.height + textSize.height + actionsHeight + 25.0 + insets.top + insets.bottom) + if self.showMore { + resultSize.height += 37.0 + } + + transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) + + var actionOffset: CGFloat = 0.0 + let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) + var separatorIndex = -1 + var nodeIndex = 0 + for actionNode in self.actionNodes { + if separatorIndex >= 0 { + let separatorNode = self.actionVerticalSeparators[separatorIndex] + switch effectiveActionLayout { + case .horizontal: + transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) + case .vertical: + transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) + } + } + separatorIndex += 1 + + let currentActionWidth: CGFloat + switch effectiveActionLayout { + case .horizontal: + if nodeIndex == self.actionNodes.count - 1 { + currentActionWidth = resultSize.width - actionOffset + } else { + currentActionWidth = actionWidth + } + case .vertical: + currentActionWidth = resultSize.width + } + + let actionNodeFrame: CGRect + switch effectiveActionLayout { + case .horizontal: + actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) + actionOffset += currentActionWidth + case .vertical: + actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) + actionOffset += actionButtonHeight + } + + transition.updateFrame(node: actionNode, frame: actionNodeFrame) + + nodeIndex += 1 + } + + return resultSize + } +} + +public func webAppLaunchConfirmationController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peer: EnginePeer, commit: @escaping () -> Void, showMore: (() -> Void)?) -> AlertController { + let theme = defaultDarkColorPresentationTheme + let presentationData: PresentationData + if let updatedPresentationData { + presentationData = updatedPresentationData.initial + } else { + presentationData = context.sharedContext.currentPresentationData.with { $0 } + } + let strings = presentationData.strings + + var dismissImpl: ((Bool) -> Void)? + var contentNode: WebAppLaunchConfirmationAlertContentNode? + let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { + dismissImpl?(true) + }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { + dismissImpl?(true) + commit() + })] + + let title = peer.compactDisplayTitle + let text = presentationData.strings.WebApp_LaunchConfirmation + + contentNode = WebAppLaunchConfirmationAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, peer: peer, title: title, text: text, showMore: showMore != nil, actions: actions, morePressed: { + dismissImpl?(true) + showMore?() + }) + + let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode!) + dismissImpl = { [weak controller] animated in + if animated { + controller?.dismissAnimated() + } else { + controller?.dismiss() + } + } + return controller +}