diff --git a/TelegramUI/BlurredImageNode.swift b/TelegramUI/BlurredImageNode.swift index 36a558bd16..6cc43b26cf 100644 --- a/TelegramUI/BlurredImageNode.swift +++ b/TelegramUI/BlurredImageNode.swift @@ -1,13 +1,12 @@ import Foundation import UIKit +import SwiftSignalKit import AsyncDisplayKit import Accelerate private class BlurLayer: CALayer { private static let blurRadiusKey = "blurRadius" - private static let blurLayoutKey = "blurLayout" @NSManaged var blurRadius: CGFloat - @NSManaged private var blurLayout: CGFloat private var fromBlurRadius: CGFloat? var presentationRadius: CGFloat { @@ -23,7 +22,7 @@ private class BlurLayer: CALayer { } override class func needsDisplay(forKey key: String) -> Bool { - if key == blurRadiusKey || key == blurLayoutKey { + if key == blurRadiusKey { return true } return super.needsDisplay(forKey: key) @@ -42,13 +41,6 @@ private class BlurLayer: CALayer { } } - if event == BlurLayer.blurLayoutKey, let action = super.action(forKey: "opacity") as? CABasicAnimation { - action.keyPath = event - action.fromValue = 0 - action.toValue = 1 - return action - } - return super.action(forKey: event) } @@ -58,17 +50,6 @@ private class BlurLayer: CALayer { self.contentsGravity = kCAGravityResizeAspectFill } - func refresh() { - self.fromBlurRadius = nil - } - - func animate() { - UIView.performWithoutAnimation { - self.blurLayout = 0 - } - self.blurLayout = 1 - } - func render(in context: CGContext, for layer: CALayer) { layer.render(in: context) } @@ -79,20 +60,14 @@ class BlurView: UIView { return BlurLayer.self } - private var displayLink: CADisplayLink? private var blurLayer: BlurLayer { return self.layer as! BlurLayer } var image: UIImage? - private let mainQueue = DispatchQueue.main - private let globalQueue: DispatchQueue = { - if #available (iOS 8.0, *) { - return .global(qos: .userInteractive) - } else { - return .global(priority: .high) - } + private let queue: Queue = { + return Queue(name: nil, qos: .userInteractive) }() open var blurRadius: CGFloat { @@ -110,17 +85,6 @@ class BlurView: UIView { self.isUserInteractionEnabled = false } - open override func didMoveToSuperview() { - super.didMoveToSuperview() - - if self.superview == nil { - self.displayLink?.invalidate() - self.displayLink = nil - } else { - self.linkForDisplay() - } - } - private func async(on queue: DispatchQueue, actions: @escaping () -> Void) { queue.async(execute: actions) } @@ -130,9 +94,9 @@ class BlurView: UIView { } private func draw(_ image: UIImage, blurRadius: CGFloat) { - async(on: globalQueue) { [weak self] in + self.queue.async { [weak self] in if let strongSelf = self, let blurredImage = blurredImage(image, radius: blurRadius) { - strongSelf.sync(on: strongSelf.mainQueue) { + Queue.mainQueue().sync { strongSelf.blurLayer.draw(blurredImage) } } @@ -145,22 +109,13 @@ class BlurView: UIView { self.draw(image, blurRadius: blurRadius) } } - - private func linkForDisplay() { - self.displayLink?.invalidate() - self.displayLink = UIScreen.main.displayLink(withTarget: self, selector: #selector(BlurView.displayDidRefresh(_:))) - self.displayLink?.add(to: .main, forMode: RunLoop.Mode(rawValue: "")) - } - - @objc private func displayDidRefresh(_ displayLink: CADisplayLink) { - self.display(self.layer) - } } final class BlurredImageNode: ASDisplayNode { var image: UIImage? { didSet { self.blurView.image = self.image + self.blurView.layer.setNeedsDisplay() } } diff --git a/TelegramUI/ChatController.swift b/TelegramUI/ChatController.swift index 502b271dc6..04939d8cab 100644 --- a/TelegramUI/ChatController.swift +++ b/TelegramUI/ChatController.swift @@ -242,7 +242,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal self.presentationData = (account.applicationContext as! TelegramApplicationContext).currentPresentationData.with { $0 } self.automaticMediaDownloadSettings = (account.applicationContext as! TelegramApplicationContext).currentAutomaticMediaDownloadSettings.with { $0 } - self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, chatWallpaperMode: self.presentationData.chatWallpaperMode, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, fontSize: self.presentationData.fontSize, accountPeerId: account.peerId, mode: mode, chatLocation: chatLocation) + self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, chatWallpaperMode: self.presentationData.chatWallpaperOptions, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, fontSize: self.presentationData.fontSize, accountPeerId: account.peerId, mode: mode, chatLocation: chatLocation) var mediaAccessoryPanelVisibility = MediaAccessoryPanelVisibility.none if case .standard = mode { @@ -1656,7 +1656,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal state = state.updatedTheme(self.presentationData.theme) state = state.updatedStrings(self.presentationData.strings) state = state.updatedDateTimeFormat(self.presentationData.dateTimeFormat) - state = state.updatedChatWallpaper(self.presentationData.chatWallpaper, mode: self.presentationData.chatWallpaperMode) + state = state.updatedChatWallpaper(self.presentationData.chatWallpaper, mode: self.presentationData.chatWallpaperOptions) return state }) } @@ -5419,17 +5419,15 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal } private func openUrlIn(_ url: String) { - if let applicationContext = self.account.applicationContext as? TelegramApplicationContext { - let actionSheet = OpenInActionSheetController(account: self.account, item: .url(url: url), openUrl: { [weak self] url in - if let strongSelf = self, let applicationContext = strongSelf.account.applicationContext as? TelegramApplicationContext, let navigationController = strongSelf.navigationController as? NavigationController { - openExternalUrl(account: strongSelf.account, url: url, forceExternal: true, presentationData: strongSelf.presentationData, applicationContext: applicationContext, navigationController: navigationController, dismissInput: { - self?.chatDisplayNode.dismissInput() - }) - } - }) - self.chatDisplayNode.dismissInput() - self.present(actionSheet, in: .window(.root)) - } + let actionSheet = OpenInActionSheetController(account: self.account, item: .url(url: url), openUrl: { [weak self] url in + if let strongSelf = self, let applicationContext = strongSelf.account.applicationContext as? TelegramApplicationContext, let navigationController = strongSelf.navigationController as? NavigationController { + openExternalUrl(account: strongSelf.account, url: url, forceExternal: true, presentationData: strongSelf.presentationData, applicationContext: applicationContext, navigationController: navigationController, dismissInput: { + self?.chatDisplayNode.dismissInput() + }) + } + }) + self.chatDisplayNode.dismissInput() + self.present(actionSheet, in: .window(.root)) } func avatarPreviewingController(from sourceView: UIView) -> (UIViewController, CGRect)? { @@ -5945,9 +5943,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal if true { inputShortcuts.append(KeyShortcut(input: UIKeyInputUpArrow, action: { [weak self] in - if let strongSelf = self { - - } + })) } } @@ -5978,8 +5974,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal } }), KeyShortcut(input: "W", modifiers: [.command], action: { [weak self] in - if let strongSelf = self { - } + }) ] diff --git a/TelegramUI/ChatControllerBackgroundNode.swift b/TelegramUI/ChatControllerBackgroundNode.swift index d2889081f6..b506533fd8 100644 --- a/TelegramUI/ChatControllerBackgroundNode.swift +++ b/TelegramUI/ChatControllerBackgroundNode.swift @@ -4,32 +4,32 @@ import Display import SwiftSignalKit import Postbox +private let motionAmount: CGFloat = 32.0 + final class ChatBackgroundNode: ASDisplayNode { let contentNode: ASDisplayNode - var parallaxEnabled: Bool = false { + var motionEnabled: Bool = false { didSet { - if oldValue != self.parallaxEnabled { - if self.parallaxEnabled { - let amount = 24.0 - + if oldValue != self.motionEnabled { + if self.motionEnabled { let horizontal = UIInterpolatingMotionEffect(keyPath: "center.x", type: .tiltAlongHorizontalAxis) - horizontal.minimumRelativeValue = -amount - horizontal.maximumRelativeValue = amount + horizontal.minimumRelativeValue = motionAmount + horizontal.maximumRelativeValue = -motionAmount let vertical = UIInterpolatingMotionEffect(keyPath: "center.y", type: .tiltAlongVerticalAxis) - vertical.minimumRelativeValue = -amount - vertical.maximumRelativeValue = amount + vertical.minimumRelativeValue = motionAmount + vertical.maximumRelativeValue = -motionAmount let group = UIMotionEffectGroup() group.motionEffects = [horizontal, vertical] - self.contentNode.view.addMotionEffect(group) } else { for effect in self.contentNode.view.motionEffects { self.contentNode.view.removeMotionEffect(effect) } } + self.updateScale() } } } @@ -40,6 +40,15 @@ final class ChatBackgroundNode: ASDisplayNode { } } + func updateScale() { + if self.motionEnabled { + let scale = (self.frame.width + motionAmount * 2.0) / self.frame.width + self.contentNode.transform = CATransform3DMakeScale(scale, scale, 1.0) + } else { + self.contentNode.transform = CATransform3DIdentity + } + } + override init() { self.contentNode = ASDisplayNode() self.contentNode.contentMode = .scaleAspectFill @@ -53,7 +62,9 @@ final class ChatBackgroundNode: ASDisplayNode { override func layout() { super.layout() - self.contentNode.frame = self.bounds + self.contentNode.bounds = self.bounds + self.contentNode.position = CGPoint(x: self.bounds.midX, y: self.bounds.midY) + self.updateScale() } } @@ -114,11 +125,20 @@ func chatControllerBackgroundImage(wallpaper: TelegramWallpaper, mode: Wallpaper private func serviceColor(for data: Signal) -> Signal { return data |> mapToSignal { data -> Signal in - if data.complete { - let image = UIImage(contentsOfFile: data.path) + if data.complete, let image = UIImage(contentsOfFile: data.path) { + return serviceColor(from: .single(image)) + } + return .complete() + } +} + +func serviceColor(from image: Signal) -> Signal { + return image + |> mapToSignal { image -> Signal in + if let image = image { let context = DrawingContext(size: CGSize(width: 1.0, height: 1.0), scale: 1.0, clear: false) context.withFlippedContext({ context in - if let cgImage = image?.cgImage { + if let cgImage = image.cgImage { context.draw(cgImage, in: CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0)) } }) @@ -187,112 +207,3 @@ func chatServiceBackgroundColor(wallpaper: TelegramWallpaper, postbox: Postbox) } } -func chatBackgroundContrastColor(wallpaper: TelegramWallpaper, postbox: Postbox) -> Signal { - switch wallpaper { - case .builtin: - return .single(UIColor(rgb: 0x888f96)) - case let .color(color): - return .single(contrastingColor(for: UIColor(rgb: UInt32(bitPattern: color)))) - case let .image(representations): - if let largest = largestImageRepresentation(representations) { - return Signal { subscriber in - let fetch = postbox.mediaBox.fetchedResource(largest.resource, parameters: nil).start() - let imageSignal = postbox.mediaBox.resourceData(largest.resource) - |> mapToSignal { data -> Signal in - if data.complete, let image = UIImage(contentsOfFile: data.path) { - return .single(image) - } else { - return .complete() - } - } - let data = backgroundContrastColor(for: imageSignal).start(next: { next in - subscriber.putNext(next) - }, completed: { - subscriber.putCompletion() - }) - return ActionDisposable { - fetch.dispose() - data.dispose() - } - } - } else { - return .single(.white) - } - case let .file(file): - return Signal { subscriber in - let fetch = postbox.mediaBox.fetchedResource(file.file.resource, parameters: nil).start() - let imageSignal = postbox.mediaBox.resourceData(file.file.resource) - |> mapToSignal { data -> Signal in - if data.complete, let image = UIImage(contentsOfFile: data.path) { - return .single(image) - } else { - return .complete() - } - } - let data = backgroundContrastColor(for: imageSignal).start(next: { next in - subscriber.putNext(next) - }, completed: { - subscriber.putCompletion() - }) - return ActionDisposable { - fetch.dispose() - data.dispose() - } - } - } -} - -func backgroundContrastColor(for image: Signal) -> Signal { - return image - |> map { image -> UIColor in - if let image = image { - let context = DrawingContext(size: CGSize(width: 128.0, height: 32.0), scale: 1.0, clear: false) - context.withFlippedContext({ context in - if let cgImage = image.cgImage { - let size = image.size.aspectFilled(CGSize(width: 128.0, height: 128.0)) - context.draw(cgImage, in: CGRect(x: floor((128.0 - size.width) / 2.0), y: 0.0, width: size.width, height: size.height)) - } - }) - - var matching: Int = 0 - let total: Int = Int(context.size.width) * Int(context.size.height) - for y in 0 ..< Int(context.size.height) { - for x in 0 ..< Int(context.size.width) { - var saturation: CGFloat = 0.0 - var brightness: CGFloat = 0.0 - if context.colorAt(CGPoint(x: x, y: y)).getHue(nil, saturation: nil, brightness: &brightness, alpha: nil) { - if brightness > 0.6 { - matching += 1 - } - } - } - } - - if CGFloat(matching) / CGFloat(total) > 0.4 { - return .black - } else { - return .white - } - } else { - return .black - } - } -} - -private func contrastingColor(for color: UIColor) -> UIColor { - var red: CGFloat = 0.0 - var green: CGFloat = 0.0 - var blue: CGFloat = 0.0 - var luminance: CGFloat = 0.0 - - if color.getRed(&red, green: &green, blue: &blue, alpha: nil) { - luminance = red * 0.2126 + green * 0.7152 + blue * 0.0722 - } else if color.getWhite(&luminance, alpha: nil) { - } - - if luminance > 0.6 { - return .black - } else { - return .white - } -} diff --git a/TelegramUI/ChatControllerNode.swift b/TelegramUI/ChatControllerNode.swift index 142a699ca0..529b4ffed8 100644 --- a/TelegramUI/ChatControllerNode.swift +++ b/TelegramUI/ChatControllerNode.swift @@ -239,9 +239,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } self.backgroundNode.image = chatControllerBackgroundImage(wallpaper: chatPresentationInterfaceState.chatWallpaper, mode: chatPresentationInterfaceState.chatWallpaperMode, postbox: account.postbox) - if chatPresentationInterfaceState.chatWallpaperMode.contains(.motion) { - self.backgroundNode.parallaxEnabled = true - } + self.backgroundNode.motionEnabled = chatPresentationInterfaceState.chatWallpaperMode.contains(.motion) + self.historyNode.verticalScrollIndicatorColor = UIColor(white: 0.5, alpha: 0.8) self.addSubnode(self.backgroundNode) @@ -1316,12 +1315,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if self.chatPresentationInterfaceState.chatWallpaper != chatPresentationInterfaceState.chatWallpaper || self.chatPresentationInterfaceState.chatWallpaperMode != chatPresentationInterfaceState.chatWallpaperMode { self.backgroundNode.image = chatControllerBackgroundImage(wallpaper: chatPresentationInterfaceState.chatWallpaper, mode: chatPresentationInterfaceState.chatWallpaperMode, postbox: account.postbox) - - if chatPresentationInterfaceState.chatWallpaperMode.contains(.motion) { - self.backgroundNode.parallaxEnabled = true - } else { - self.backgroundNode.parallaxEnabled = false - } + self.backgroundNode.motionEnabled = chatPresentationInterfaceState.chatWallpaperMode.contains(.motion) } self.historyNode.verticalScrollIndicatorColor = UIColor(white: 0.5, alpha: 0.8) diff --git a/TelegramUI/ChatListController.swift b/TelegramUI/ChatListController.swift index de297d8d6a..a26f27a18e 100644 --- a/TelegramUI/ChatListController.swift +++ b/TelegramUI/ChatListController.swift @@ -529,7 +529,7 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie self.chatListDisplayNode.requestOpenRecentPeerOptions = { [weak self] peer in if let strongSelf = self { - strongSelf.view.endEditing(true) + strongSelf.view.window?.endEditing(true) let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme) actionSheet.setItemGroups([ diff --git a/TelegramUI/ChatListControllerNode.swift b/TelegramUI/ChatListControllerNode.swift index 8a01ee2038..8224ba3225 100644 --- a/TelegramUI/ChatListControllerNode.swift +++ b/TelegramUI/ChatListControllerNode.swift @@ -144,7 +144,7 @@ class ChatListControllerNode: ASDisplayNode { listViewCurve = .Spring(duration: duration) } else { listViewCurve = .Default(duration: duration) - } + } let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, duration: duration, curve: listViewCurve) diff --git a/TelegramUI/ContactListActionItem.swift b/TelegramUI/ContactListActionItem.swift index 440c7ecad6..15f7a33010 100644 --- a/TelegramUI/ContactListActionItem.swift +++ b/TelegramUI/ContactListActionItem.swift @@ -73,8 +73,8 @@ class ContactListActionItem: ListViewItem { func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { async { let node = ContactListActionItemNode() - let (_, _, firstWithHeader) = ContactListActionItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem) - let (layout, apply) = node.asyncLayout()(self, params, firstWithHeader) + let (_, last, firstWithHeader) = ContactListActionItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem) + let (layout, apply) = node.asyncLayout()(self, params, firstWithHeader, last) node.contentSize = layout.contentSize node.insets = layout.insets @@ -93,8 +93,8 @@ class ContactListActionItem: ListViewItem { let makeLayout = nodeValue.asyncLayout() async { - let (_, _, firstWithHeader) = ContactListActionItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem) - let (layout, apply) = makeLayout(self, params, firstWithHeader) + let (_, last, firstWithHeader) = ContactListActionItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem) + let (layout, apply) = makeLayout(self, params, firstWithHeader, last) Queue.mainQueue().async { completion(layout, { _ in apply() @@ -192,11 +192,11 @@ class ContactListActionItemNode: ListViewItemNode { self.addSubnode(self.titleNode) } - func asyncLayout() -> (_ item: ContactListActionItem, _ params: ListViewItemLayoutParams, _ firstWithHeader: Bool) -> (ListViewItemNodeLayout, () -> Void) { + func asyncLayout() -> (_ item: ContactListActionItem, _ params: ListViewItemLayoutParams, _ firstWithHeader: Bool, _ last: Bool) -> (ListViewItemNodeLayout, () -> Void) { let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let currentTheme = self.theme - return { item, params, firstWithHeader in + return { item, params, firstWithHeader, last in var updatedTheme: PresentationTheme? if currentTheme !== item.theme { @@ -233,7 +233,7 @@ class ContactListActionItemNode: ListViewItemNode { let _ = titleApply() var titleOffset = leftInset - var hideBottomStripe: Bool = false + var hideBottomStripe: Bool = last if let image = item.icon.image { var iconFrame: CGRect switch item.icon { @@ -268,7 +268,7 @@ class ContactListActionItemNode: ListViewItemNode { strongSelf.topStripeNode.isHidden = true strongSelf.bottomStripeNode.isHidden = hideBottomStripe - strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height), size: CGSize(width: params.width - leftInset, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight)) strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: titleOffset, y: floor((contentSize.height - titleLayout.size.height) / 2.0)), size: titleLayout.size) diff --git a/TelegramUI/ContactsPeerItem.swift b/TelegramUI/ContactsPeerItem.swift index 38c768f1d5..bd4e139039 100644 --- a/TelegramUI/ContactsPeerItem.swift +++ b/TelegramUI/ContactsPeerItem.swift @@ -281,8 +281,6 @@ class ContactsPeerItem: ListViewItem { } } -private let separatorHeight = 1.0 / UIScreen.main.scale - private let avatarFont: UIFont = UIFont(name: ".SFCompactRounded-Semibold", size: 16.0)! class ContactsPeerItemNode: ItemListRevealOptionsItemNode { @@ -771,6 +769,8 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode { strongSelf.selectionNode = nil } + let separatorHeight = UIScreenPixel + let topHighlightInset: CGFloat = (first || !nodeLayout.insets.top.isZero) ? 0.0 : separatorHeight strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: nodeLayout.contentSize.width, height: nodeLayout.contentSize.height)) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: nodeLayout.size.width, height: nodeLayout.size.height + topHighlightInset)) diff --git a/TelegramUI/CounterContollerTitleView.swift b/TelegramUI/CounterContollerTitleView.swift index 87a740f87c..5dff51f573 100644 --- a/TelegramUI/CounterContollerTitleView.swift +++ b/TelegramUI/CounterContollerTitleView.swift @@ -15,7 +15,7 @@ final class CounterContollerTitleView: UIView { var title: CounterContollerTitle = CounterContollerTitle(title: "", counter: "") { didSet { if self.title != oldValue { - self.titleNode.attributedText = NSAttributedString(string: self.title.title, font: Font.medium(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor) + self.titleNode.attributedText = NSAttributedString(string: self.title.title, font: Font.bold(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor) self.subtitleNode.attributedText = NSAttributedString(string: self.title.counter, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor) self.setNeedsLayout() diff --git a/TelegramUI/DeclareEncodables.swift b/TelegramUI/DeclareEncodables.swift index b381cf2565..c2e38d8ebb 100644 --- a/TelegramUI/DeclareEncodables.swift +++ b/TelegramUI/DeclareEncodables.swift @@ -34,6 +34,7 @@ private var telegramUIDeclaredEncodables: Void = { declareEncodable(RecentWebSearchQueryItem.self, f: { RecentWebSearchQueryItem(decoder: $0) }) declareEncodable(RecentWallpaperSearchQueryItem.self, f: { RecentWallpaperSearchQueryItem(decoder: $0) }) declareEncodable(VoipDerivedState.self, f: { VoipDerivedState(decoder: $0) }) + declareEncodable(PresentationThemeSpecificSettings.self, f: { PresentationThemeSpecificSettings(decoder: $0) }) return }() diff --git a/TelegramUI/FixSearchableListNodeScrolling.swift b/TelegramUI/FixSearchableListNodeScrolling.swift index 4126e09420..b2b11c06a4 100644 --- a/TelegramUI/FixSearchableListNodeScrolling.swift +++ b/TelegramUI/FixSearchableListNodeScrolling.swift @@ -46,3 +46,22 @@ func fixNavigationSearchableListNodeScrolling(_ listNode: ListView, searchNode: } return false } + +func fixNavigationSearchableGridNodeScrolling(_ gridNode: GridNode, searchNode: NavigationBarSearchContentNode) -> Bool { +// if searchNode.expansionProgress > 0.0 && searchNode.expansionProgress < 1.0 { +// let scrollToItem: ListViewScrollToItem +// let targetProgress: CGFloat +// if searchNode.expansionProgress < 0.6 { +// scrollToItem = ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: true, curve: .Default(duration: 0.3), directionHint: .Up) +// targetProgress = 0.0 +// } else { +// scrollToItem = ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: 0.3), directionHint: .Up) +// targetProgress = 1.0 +// } +// searchNode.updateExpansionProgress(targetProgress, animated: true) +// +// listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: scrollToItem, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) +// return true +// } + return false +} diff --git a/TelegramUI/GalleryControllerNode.swift b/TelegramUI/GalleryControllerNode.swift index 726ebed839..f0785848c7 100644 --- a/TelegramUI/GalleryControllerNode.swift +++ b/TelegramUI/GalleryControllerNode.swift @@ -300,6 +300,9 @@ class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGestureRecog func updateDismissTransition(_ value: CGFloat) { } + func updateDistanceFromEquilibrium(_ value: CGFloat) { + } + func scrollViewDidScroll(_ scrollView: UIScrollView) { if self.isDismissed { return @@ -318,6 +321,7 @@ class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGestureRecog } self.updateDismissTransition(transition) + self.updateDistanceFromEquilibrium(distanceFromEquilibrium) if let overlayNode = self.overlayNode { overlayNode.alpha = transition diff --git a/TelegramUI/GalleryItemNode.swift b/TelegramUI/GalleryItemNode.swift index 17229cdcfc..6dd1290b1d 100644 --- a/TelegramUI/GalleryItemNode.swift +++ b/TelegramUI/GalleryItemNode.swift @@ -62,6 +62,9 @@ open class GalleryItemNode: ASDisplayNode { open func centralityUpdated(isCentral: Bool) { } + open func screenFrameUpdated(_ frame: CGRect) { + } + open func activateAsInitial() { } diff --git a/TelegramUI/GalleryPagerNode.swift b/TelegramUI/GalleryPagerNode.swift index ba932819d4..6507eac0e3 100644 --- a/TelegramUI/GalleryPagerNode.swift +++ b/TelegramUI/GalleryPagerNode.swift @@ -307,7 +307,11 @@ final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate { } for i in 0 ..< self.itemNodes.count { - transition.updateFrame(node: self.itemNodes[i], frame: CGRect(origin: CGPoint(x: CGFloat(i) * self.scrollView.bounds.size.width + self.pageGap, y: 0.0), size: CGSize(width: self.scrollView.bounds.size.width - self.pageGap * 2.0, height: self.scrollView.bounds.size.height))) + let node = self.itemNodes[i] + transition.updateFrame(node: node, frame: CGRect(origin: CGPoint(x: CGFloat(i) * self.scrollView.bounds.size.width + self.pageGap, y: 0.0), size: CGSize(width: self.scrollView.bounds.size.width - self.pageGap * 2.0, height: self.scrollView.bounds.size.height))) + + let screenFrame = node.convert(node.bounds, to: self.supernode) + node.screenFrameUpdated(screenFrame) } if resetOffsetToCentralItem { @@ -352,7 +356,11 @@ final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate { let previousCentralCandidateHorizontalOffset = self.scrollView.contentOffset.x - centralItemCandidateNode.frame.minX for i in 0 ..< self.itemNodes.count { - transition.updateFrame(node: self.itemNodes[i], frame: CGRect(origin: CGPoint(x: CGFloat(i) * self.scrollView.bounds.size.width + self.pageGap, y: 0.0), size: CGSize(width: self.scrollView.bounds.size.width - self.pageGap * 2.0, height: self.scrollView.bounds.size.height))) + let node = self.itemNodes[i] + transition.updateFrame(node: node, frame: CGRect(origin: CGPoint(x: CGFloat(i) * self.scrollView.bounds.size.width + self.pageGap, y: 0.0), size: CGSize(width: self.scrollView.bounds.size.width - self.pageGap * 2.0, height: self.scrollView.bounds.size.height))) + + let screenFrame = node.convert(node.bounds, to: self.supernode) + node.screenFrameUpdated(screenFrame) } self.scrollView.contentOffset = CGPoint(x: centralItemCandidateNode.frame.minX + previousCentralCandidateHorizontalOffset, y: 0.0) diff --git a/TelegramUI/ItemListAvatarAndNameItem.swift b/TelegramUI/ItemListAvatarAndNameItem.swift index 58aa5ae325..a4a4865d81 100644 --- a/TelegramUI/ItemListAvatarAndNameItem.swift +++ b/TelegramUI/ItemListAvatarAndNameItem.swift @@ -597,17 +597,17 @@ class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNode, Ite strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2) } switch neighbors.top { - case .sameSection: + case .sameSection(false): strongSelf.topStripeNode.isHidden = true - case .none, .otherSection: + default: strongSelf.topStripeNode.isHidden = false } let bottomStripeInset: CGFloat switch neighbors.bottom { - case .sameSection: + case .sameSection(false): bottomStripeInset = params.leftInset + 16.0 - case .none, .otherSection: + default: bottomStripeInset = 0.0 } diff --git a/TelegramUI/ItemListMultilineInputItem.swift b/TelegramUI/ItemListMultilineInputItem.swift index b2791772f4..08aae66d9d 100644 --- a/TelegramUI/ItemListMultilineInputItem.swift +++ b/TelegramUI/ItemListMultilineInputItem.swift @@ -230,7 +230,7 @@ class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNodeDelega } let bottomStripeInset: CGFloat switch neighbors.bottom { - case .sameSection(true): + case .sameSection(false): bottomStripeInset = leftInset default: bottomStripeInset = 0.0 diff --git a/TelegramUI/ModernCheckNode.swift b/TelegramUI/ModernCheckNode.swift index 9b92c8e220..7905797956 100644 --- a/TelegramUI/ModernCheckNode.swift +++ b/TelegramUI/ModernCheckNode.swift @@ -1,32 +1,164 @@ import Foundation import AsyncDisplayKit import Display +import LegacyComponents + +struct CheckNodeTheme { + let backgroundColor: UIColor + let strokeColor: UIColor + let borderColor: UIColor + let hasShadow: Bool +} + +enum CheckNodeContent { + case check + case counter(Int) +} private final class CheckNodeParameters: NSObject { - let progress: CGFloat + let theme: CheckNodeTheme + let content: CheckNodeContent + let animationProgress: CGFloat + let selected: Bool - init(progress: CGFloat) { - self.progress = progress + init(theme: CheckNodeTheme, content: CheckNodeContent, animationProgress: CGFloat, selected: Bool) { + self.theme = theme + self.content = content + self.animationProgress = animationProgress + self.selected = selected } } class ModernCheckNode: ASDisplayNode { - private var displayProgress: CGFloat = 0.0 - - func setSelected(_ selected: Bool, animated: Bool) { - if animated { - - } else { - self.displayProgress = selected ? 1.0 : 0.0 + private var animationProgress: CGFloat = 0.0 + var theme: CheckNodeTheme { + didSet { + self.setNeedsDisplay() } } + + init(theme: CheckNodeTheme, content: CheckNodeContent = .check) { + self.theme = theme + self.content = content + + super.init() + + self.isOpaque = false + } + + var content: CheckNodeContent { + didSet { + self.setNeedsDisplay() + } + } + + var selected = false + func setSelected(_ selected: Bool, animated: Bool = false) { + self.selected = selected + + if selected && animated { + let animation = POPBasicAnimation() + animation.property = POPAnimatableProperty.property(withName: "progress", initializer: { property in + property?.readBlock = { node, values in + values?.pointee = (node as! ModernCheckNode).animationProgress + } + property?.writeBlock = { node, values in + (node as! ModernCheckNode).animationProgress = values!.pointee + (node as! ModernCheckNode).setNeedsDisplay() + } + property?.threshold = 0.01 + }) as! POPAnimatableProperty + animation.fromValue = 0.0 as NSNumber + animation.toValue = 1.0 as NSNumber + animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) + animation.duration = 0.21 + self.pop_add(animation, forKey: "progress") + } else { + self.animationProgress = selected ? 1.0 : 0.0 + self.setNeedsDisplay() + } + } + + func setHighlighted(_ highlighted: Bool, animated: Bool = false) { + + } override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? { - return CheckNodeParameters(progress: self.displayProgress) + return CheckNodeParameters(theme: self.theme, content: self.content, animationProgress: self.animationProgress, selected: self.selected) } @objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) { + let context = UIGraphicsGetCurrentContext()! + if !isRasterizing { + context.setBlendMode(.copy) + context.setFillColor(UIColor.clear.cgColor) + context.fill(bounds) + } + + if let parameters = parameters as? CheckNodeParameters { + let progress = parameters.animationProgress + let diameter = bounds.width + let center = CGPoint(x: diameter / 2.0, y: diameter / 2.0) + + var borderWidth: CGFloat = 1.5 + if UIScreenScale == 3.0 { + borderWidth = 5.0 / 3.0 + } + + context.setStrokeColor(parameters.theme.borderColor.cgColor) + context.setLineWidth(borderWidth) + context.strokeEllipse(in: bounds.insetBy(dx: borderWidth / 2.0, dy: borderWidth / 2.0)) + + context.setFillColor(parameters.theme.backgroundColor.cgColor) + context.fillEllipse(in: bounds.insetBy(dx: (diameter - borderWidth) * (1.0 - parameters.animationProgress), dy: (diameter - borderWidth) * (1.0 - parameters.animationProgress))) + + let firstSegment: CGFloat = max(0.0, min(1.0, progress * 3.0)) + let s = CGPoint(x: center.x - 4.0, y: center.y + UIScreenPixel) + let p1 = CGPoint(x: 3.0, y: 3.0) + let p2 = CGPoint(x: 5.0, y: -6.0) + + if !firstSegment.isZero { + if firstSegment < 1.0 { + context.move(to: CGPoint(x: s.x + p1.x * firstSegment, y: s.y + p1.y * firstSegment)) + context.addLine(to: s) + } else { + let secondSegment = (progress - 0.33) * 1.5 + context.move(to: CGPoint(x: s.x + p1.x + p2.x * secondSegment, y: s.y + p1.y + p2.y * secondSegment)) + context.addLine(to: CGPoint(x: s.x + p1.x, y: s.y + p1.y)) + context.addLine(to: s) + } + } + + context.setStrokeColor(parameters.theme.strokeColor.cgColor) + if parameters.theme.strokeColor == .clear { + context.setBlendMode(.clear) + } + context.setLineWidth(borderWidth) + context.setLineCap(.round) + context.setLineJoin(.round) + context.setMiterLimit(10.0) + + switch parameters.content { + case .check: + break + case let .counter(number): + break + } + + context.strokePath() + } } + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + super.touchesBegan(touches, with: event) + } + + override func touchesEnded(_ touches: Set, with event: UIEvent?) { + super.touchesEnded(touches, with: event) + } + + override func touchesCancelled(_ touches: Set?, with event: UIEvent?) { + super.touchesCancelled(touches, with: event) + } } diff --git a/TelegramUI/NavigationBarSearchContentNode.swift b/TelegramUI/NavigationBarSearchContentNode.swift index 18eb2247ea..10df92837b 100644 --- a/TelegramUI/NavigationBarSearchContentNode.swift +++ b/TelegramUI/NavigationBarSearchContentNode.swift @@ -52,6 +52,19 @@ class NavigationBarSearchContentNode: NavigationBarContentNode { self.updateExpansionProgress(progress) } + func updateGridVisibleContentOffset(_ offset: GridNodeVisibleContentOffset) { + var progress: CGFloat = 0.0 + switch offset { + case let .known(offset): + progress = max(0.0, (self.nominalHeight - offset)) / self.nominalHeight + case .none: + progress = 1.0 + default: + break + } + self.updateExpansionProgress(progress) + } + func updateExpansionProgress(_ progress: CGFloat, animated: Bool = false) { let newProgress = max(0.0, min(1.0, progress)) if newProgress != self.expansionProgress { diff --git a/TelegramUI/OpenChatMessage.swift b/TelegramUI/OpenChatMessage.swift index e2aacf0a03..454c5a7971 100644 --- a/TelegramUI/OpenChatMessage.swift +++ b/TelegramUI/OpenChatMessage.swift @@ -409,10 +409,10 @@ func openChatWallpaper(account: Account, message: Message, present: @escaping (V if case let .wallpaper(parameter) = resolvedUrl { let source: WallpaperListSource switch parameter { - case let .slug(slug): - source = .slug(slug, content.file) + case let .slug(slug, options): + source = .slug(slug, content.file, options) case let .color(color): - source = .wallpaper(.color(Int32(color.rgb))) + source = .wallpaper(.color(Int32(color.rgb)), nil) } let controller = WallpaperGalleryController(account: account, source: source) diff --git a/TelegramUI/OpenResolvedUrl.swift b/TelegramUI/OpenResolvedUrl.swift index cd49e64f49..f216b62c06 100644 --- a/TelegramUI/OpenResolvedUrl.swift +++ b/TelegramUI/OpenResolvedUrl.swift @@ -198,9 +198,11 @@ func openResolvedUrl(_ resolvedUrl: ResolvedUrl, account: Account, context: Open var controller: OverlayStatusController? let signal: Signal + var options: WallpaperPresentationOptions? switch parameter { - case let .slug(slug): + case let .slug(slug, wallpaperOptions): signal = getWallpaper(account: account, slug: slug) + options = wallpaperOptions controller = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .loading(cancelled: nil)) present(controller!, nil) case let .color(color): @@ -210,7 +212,7 @@ func openResolvedUrl(_ resolvedUrl: ResolvedUrl, account: Account, context: Open let _ = (signal |> deliverOnMainQueue).start(next: { [weak controller] wallpaper in controller?.dismiss() - let galleryController = WallpaperGalleryController(account: account, source: .wallpaper(wallpaper)) + let galleryController = WallpaperGalleryController(account: account, source: .wallpaper(wallpaper, options)) present(galleryController, nil) }, error: { [weak controller] error in controller?.dismiss() diff --git a/TelegramUI/OpenUrl.swift b/TelegramUI/OpenUrl.swift index b0bb2198ca..ff1269249d 100644 --- a/TelegramUI/OpenUrl.swift +++ b/TelegramUI/OpenUrl.swift @@ -547,6 +547,7 @@ public func openExternalUrl(account: Account, context: OpenURLContext = .generic } else if parsedUrl.host == "bg" { if let components = URLComponents(string: "/?" + query) { var parameter: String? + var mode = "" if let queryItems = components.queryItems { for queryItem in queryItems { if let value = queryItem.value { @@ -554,12 +555,14 @@ public func openExternalUrl(account: Account, context: OpenURLContext = .generic parameter = value } else if queryItem.name == "color" { parameter = value + } else if queryItem.name == "mode" { + mode = "?mode=\(value)" } } } } if let parameter = parameter { - convertedUrl = "https://t.me/bg/\(parameter)" + convertedUrl = "https://t.me/bg/\(parameter)\(mode)" } } } diff --git a/TelegramUI/PeerMediaCollectionControllerNode.swift b/TelegramUI/PeerMediaCollectionControllerNode.swift index 96c547c143..4238dbdf49 100644 --- a/TelegramUI/PeerMediaCollectionControllerNode.swift +++ b/TelegramUI/PeerMediaCollectionControllerNode.swift @@ -148,7 +148,7 @@ class PeerMediaCollectionControllerNode: ASDisplayNode { self.historyEmptyNode = PeerMediaCollectionEmptyNode(mode: self.mediaCollectionInterfaceState.mode, theme: self.presentationData.theme, strings: self.presentationData.strings) self.historyEmptyNode.isHidden = true - self.chatPresentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, chatWallpaperMode: self.presentationData.chatWallpaperMode, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, fontSize: self.presentationData.fontSize, accountPeerId: account.peerId, mode: .standard(previewing: false), chatLocation: .peer(self.peerId)) + self.chatPresentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, chatWallpaperMode: self.presentationData.chatWallpaperOptions, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, fontSize: self.presentationData.fontSize, accountPeerId: account.peerId, mode: .standard(previewing: false), chatLocation: .peer(self.peerId)) super.init() diff --git a/TelegramUI/PhotoResources.swift b/TelegramUI/PhotoResources.swift index 98a7c9763e..4086a4cbb2 100644 --- a/TelegramUI/PhotoResources.swift +++ b/TelegramUI/PhotoResources.swift @@ -2196,9 +2196,15 @@ func instantPageImageFile(account: Account, fileReference: FileMediaReference, f } } -private func avatarGalleryPhotoDatas(account: Account, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], alwaysShowThumbnailFirst: Bool = false, autoFetchFullSize: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> { +private func avatarGalleryPhotoDatas(account: Account, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], alwaysShowThumbnailFirst: Bool = false, scaled: Bool = false, autoFetchFullSize: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> { if let smallestRepresentation = smallestImageRepresentation(representations.map({ $0.representation })), let largestRepresentation = largestImageRepresentation(representations.map({ $0.representation })), let smallestIndex = representations.index(where: { $0.representation == smallestRepresentation }), let largestIndex = representations.index(where: { $0.representation == largestRepresentation }) { - let maybeFullSize = account.postbox.mediaBox.resourceData(largestRepresentation.resource) + + let maybeFullSize: Signal + if scaled { + maybeFullSize = account.postbox.mediaBox.cachedResourceRepresentation(largestRepresentation.resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 320.0, height: 320.0), mode: .aspectFit), complete: false, fetch: false) + } else { + maybeFullSize = account.postbox.mediaBox.resourceData(largestRepresentation.resource) + } let decodedThumbnailData = fileReference?.media.immediateThumbnailData.flatMap(decodeTinyThumbnail) let signal = maybeFullSize @@ -2274,8 +2280,8 @@ private func avatarGalleryPhotoDatas(account: Account, fileReference: FileMediaR } } -func chatAvatarGalleryPhoto(account: Account, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], alwaysShowThumbnailFirst: Bool = false, autoFetchFullSize: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - let signal = avatarGalleryPhotoDatas(account: account, fileReference: fileReference, representations: representations, alwaysShowThumbnailFirst: alwaysShowThumbnailFirst, autoFetchFullSize: autoFetchFullSize) +func chatAvatarGalleryPhoto(account: Account, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], alwaysShowThumbnailFirst: Bool = false, scaled: Bool = false, autoFetchFullSize: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { + let signal = avatarGalleryPhotoDatas(account: account, fileReference: fileReference, representations: representations, alwaysShowThumbnailFirst: alwaysShowThumbnailFirst, scaled: scaled, autoFetchFullSize: autoFetchFullSize) return signal |> map { (thumbnailData, fullSizeData, fullSizeComplete) in diff --git a/TelegramUI/PostboxKeys.swift b/TelegramUI/PostboxKeys.swift index 880c9d2f9f..d1aa3ca4fd 100644 --- a/TelegramUI/PostboxKeys.swift +++ b/TelegramUI/PostboxKeys.swift @@ -44,10 +44,12 @@ public struct ApplicationSpecificPreferencesKeys { private enum ApplicationSpecificItemCacheCollectionIdValues: Int8 { case instantPageStoredState = 0 + case themeSpecificSettings = 1 } public struct ApplicationSpecificItemCacheCollectionId { public static let instantPageStoredState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.instantPageStoredState.rawValue) + public static let themeSpecificSettings = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.themeSpecificSettings.rawValue) } private enum ApplicationSpecificOrderedItemListCollectionIdValues: Int32 { diff --git a/TelegramUI/PresentationData.swift b/TelegramUI/PresentationData.swift index 7cf1b64ee1..c019aad04a 100644 --- a/TelegramUI/PresentationData.swift +++ b/TelegramUI/PresentationData.swift @@ -46,7 +46,7 @@ public final class PresentationData: Equatable { public let strings: PresentationStrings public let theme: PresentationTheme public let chatWallpaper: TelegramWallpaper - public let chatWallpaperMode: WallpaperPresentationOptions + public let chatWallpaperOptions: WallpaperPresentationOptions public let volumeControlStatusBarIcons: PresentationVolumeControlStatusBarIcons public let fontSize: PresentationFontSize public let dateTimeFormat: PresentationDateTimeFormat @@ -54,11 +54,11 @@ public final class PresentationData: Equatable { public let nameSortOrder: PresentationPersonNameOrder public let disableAnimations: Bool - public init(strings: PresentationStrings, theme: PresentationTheme, chatWallpaper: TelegramWallpaper, chatWallpaperMode: WallpaperPresentationOptions, volumeControlStatusBarIcons: PresentationVolumeControlStatusBarIcons, fontSize: PresentationFontSize, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, nameSortOrder: PresentationPersonNameOrder, disableAnimations: Bool) { + public init(strings: PresentationStrings, theme: PresentationTheme, chatWallpaper: TelegramWallpaper, chatWallpaperOptions: WallpaperPresentationOptions, volumeControlStatusBarIcons: PresentationVolumeControlStatusBarIcons, fontSize: PresentationFontSize, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, nameSortOrder: PresentationPersonNameOrder, disableAnimations: Bool) { self.strings = strings self.theme = theme self.chatWallpaper = chatWallpaper - self.chatWallpaperMode = chatWallpaperMode + self.chatWallpaperOptions = chatWallpaperOptions self.volumeControlStatusBarIcons = volumeControlStatusBarIcons self.fontSize = fontSize self.dateTimeFormat = dateTimeFormat @@ -68,7 +68,7 @@ public final class PresentationData: Equatable { } public static func ==(lhs: PresentationData, rhs: PresentationData) -> Bool { - return lhs.strings === rhs.strings && lhs.theme === rhs.theme && lhs.chatWallpaper == rhs.chatWallpaper && lhs.chatWallpaperMode == rhs.chatWallpaperMode && lhs.volumeControlStatusBarIcons == rhs.volumeControlStatusBarIcons && lhs.fontSize == rhs.fontSize && lhs.dateTimeFormat == rhs.dateTimeFormat && lhs.disableAnimations == rhs.disableAnimations + return lhs.strings === rhs.strings && lhs.theme === rhs.theme && lhs.chatWallpaper == rhs.chatWallpaper && lhs.chatWallpaperOptions == rhs.chatWallpaperOptions && lhs.volumeControlStatusBarIcons == rhs.volumeControlStatusBarIcons && lhs.fontSize == rhs.fontSize && lhs.dateTimeFormat == rhs.dateTimeFormat && lhs.disableAnimations == rhs.disableAnimations } } @@ -281,7 +281,7 @@ public func currentPresentationDataAndSettings(postbox: Postbox) -> Signal then(chatServiceBackgroundColor(wallpaper: themeSettings.chatWallpaper, postbox: postbox))) - |> mapToSignal { serviceBackgroundColor in - return applicationBindings.applicationInForeground - |> mapToSignal({ inForeground -> Signal in - if inForeground { - return automaticThemeShouldSwitch(themeSettings.automaticThemeSwitchSetting, currentTheme: themeSettings.theme) - |> distinctUntilChanged - |> map { shouldSwitch in - let themeValue: PresentationTheme - let effectiveTheme: PresentationThemeReference - var effectiveChatWallpaper: TelegramWallpaper = themeSettings.chatWallpaper - var effectiveChatWallpaperOptions: WallpaperPresentationOptions = themeSettings.chatWallpaperOptions - - if shouldSwitch { - effectiveTheme = .builtin(themeSettings.automaticThemeSwitchSetting.theme) - switch effectiveChatWallpaper { - case .builtin, .color: - switch themeSettings.automaticThemeSwitchSetting.theme { - case .nightAccent: - effectiveChatWallpaper = .color(0x18222d) - effectiveChatWallpaperOptions = [] - case .nightGrayscale: - effectiveChatWallpaper = .color(0x000000) - effectiveChatWallpaperOptions = [] - default: - break - } - default: - break - } - } else { - effectiveTheme = themeSettings.theme - } - switch effectiveTheme { - case let .builtin(reference): - switch reference { - case .dayClassic: - themeValue = makeDefaultPresentationTheme(serviceBackgroundColor: serviceBackgroundColor) - case .nightGrayscale: - themeValue = defaultDarkPresentationTheme - case .nightAccent: - themeValue = defaultDarkAccentPresentationTheme - case .day: - themeValue = makeDefaultDayPresentationTheme(accentColor: themeSettings.themeAccentColor ?? defaultDayAccentColor) + let themeSpecificSettings = postbox.transaction { transaction -> PresentationThemeSpecificSettings? in + let key = ValueBoxKey(length: 8) + key.setInt64(0, value: themeSettings.theme.index) + if let entry = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: ApplicationSpecificItemCacheCollectionId.themeSpecificSettings, key: key)) as? PresentationThemeSpecificSettings { + return entry + } else { + return nil + } + } + + return themeSpecificSettings + |> mapToSignal { themeSpecificSettings in + let currentWallpaper: TelegramWallpaper + if let themeSpecificSettings = themeSpecificSettings { + currentWallpaper = themeSpecificSettings.chatWallpaper + } else { + currentWallpaper = themeSettings.chatWallpaper + } + + return (.single(UIColor(rgb: 0x000000, alpha: 0.3)) + |> then(chatServiceBackgroundColor(wallpaper: currentWallpaper, postbox: postbox))) + |> mapToSignal { serviceBackgroundColor in + return applicationBindings.applicationInForeground + |> mapToSignal({ inForeground -> Signal in + if inForeground { + return automaticThemeShouldSwitch(themeSettings.automaticThemeSwitchSetting, currentTheme: themeSettings.theme) + |> distinctUntilChanged + |> map { shouldSwitch in + let themeValue: PresentationTheme + let effectiveTheme: PresentationThemeReference + var effectiveChatWallpaper: TelegramWallpaper = currentWallpaper + var effectiveChatWallpaperOptions: WallpaperPresentationOptions = themeSettings.chatWallpaperOptions + + if shouldSwitch { + effectiveTheme = .builtin(themeSettings.automaticThemeSwitchSetting.theme) + switch effectiveChatWallpaper { + case .builtin, .color: + switch themeSettings.automaticThemeSwitchSetting.theme { + case .nightAccent: + effectiveChatWallpaper = .color(0x18222d) + effectiveChatWallpaperOptions = [] + case .nightGrayscale: + effectiveChatWallpaper = .color(0x000000) + effectiveChatWallpaperOptions = [] + default: + break + } + default: + break } - } - - let localizationSettings: LocalizationSettings? - if let current = (view.views[preferencesKey] as! PreferencesView).values[PreferencesKeys.localizationSettings] as? LocalizationSettings { - localizationSettings = current - } else { - localizationSettings = nil - } - - let stringsValue: PresentationStrings - if let localizationSettings = localizationSettings { - stringsValue = PresentationStrings(primaryComponent: PresentationStringsComponent(languageCode: localizationSettings.primaryComponent.languageCode, localizedName: localizationSettings.primaryComponent.localizedName, pluralizationRulesCode: localizationSettings.primaryComponent.customPluralizationCode, dict: dictFromLocalization(localizationSettings.primaryComponent.localization)), secondaryComponent: localizationSettings.secondaryComponent.flatMap({ PresentationStringsComponent(languageCode: $0.languageCode, localizedName: $0.localizedName, pluralizationRulesCode: $0.customPluralizationCode, dict: dictFromLocalization($0.localization)) })) - } else { - stringsValue = defaultPresentationStrings - } - - let dateTimeFormat = currentDateTimeFormat() - let nameDisplayOrder = contactSettings.nameDisplayOrder - let nameSortOrder = currentPersonNameSortOrder() + } else { + effectiveTheme = themeSettings.theme + } + switch effectiveTheme { + case let .builtin(reference): + switch reference { + case .dayClassic: + themeValue = makeDefaultPresentationTheme(serviceBackgroundColor: serviceBackgroundColor) + case .nightGrayscale: + themeValue = defaultDarkPresentationTheme + case .nightAccent: + themeValue = defaultDarkAccentPresentationTheme + case .day: + themeValue = makeDefaultDayPresentationTheme(accentColor: themeSettings.themeAccentColor ?? defaultDayAccentColor) + } + } + + let localizationSettings: LocalizationSettings? + if let current = (view.views[preferencesKey] as! PreferencesView).values[PreferencesKeys.localizationSettings] as? LocalizationSettings { + localizationSettings = current + } else { + localizationSettings = nil + } + + let stringsValue: PresentationStrings + if let localizationSettings = localizationSettings { + stringsValue = PresentationStrings(primaryComponent: PresentationStringsComponent(languageCode: localizationSettings.primaryComponent.languageCode, localizedName: localizationSettings.primaryComponent.localizedName, pluralizationRulesCode: localizationSettings.primaryComponent.customPluralizationCode, dict: dictFromLocalization(localizationSettings.primaryComponent.localization)), secondaryComponent: localizationSettings.secondaryComponent.flatMap({ PresentationStringsComponent(languageCode: $0.languageCode, localizedName: $0.localizedName, pluralizationRulesCode: $0.customPluralizationCode, dict: dictFromLocalization($0.localization)) })) + } else { + stringsValue = defaultPresentationStrings + } + + let dateTimeFormat = currentDateTimeFormat() + let nameDisplayOrder = contactSettings.nameDisplayOrder + let nameSortOrder = currentPersonNameSortOrder() - return PresentationData(strings: stringsValue, theme: themeValue, chatWallpaper: effectiveChatWallpaper, chatWallpaperMode: effectiveChatWallpaperOptions, volumeControlStatusBarIcons: volumeControlStatusBarIcons(), fontSize: themeSettings.fontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, disableAnimations: themeSettings.disableAnimations) + return PresentationData(strings: stringsValue, theme: themeValue, chatWallpaper: effectiveChatWallpaper, chatWallpaperOptions: effectiveChatWallpaperOptions, volumeControlStatusBarIcons: volumeControlStatusBarIcons(), fontSize: themeSettings.fontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, disableAnimations: themeSettings.disableAnimations) + } + } else { + return .complete() } - } else { - return .complete() - } - }) + }) + } } } } @@ -445,5 +465,5 @@ public func defaultPresentationData() -> PresentationData { let nameSortOrder = currentPersonNameSortOrder() let themeSettings = PresentationThemeSettings.defaultSettings - return PresentationData(strings: defaultPresentationStrings, theme: defaultPresentationTheme, chatWallpaper: .builtin, chatWallpaperMode: [], volumeControlStatusBarIcons: volumeControlStatusBarIcons(), fontSize: themeSettings.fontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, disableAnimations: themeSettings.disableAnimations) + return PresentationData(strings: defaultPresentationStrings, theme: defaultPresentationTheme, chatWallpaper: .builtin, chatWallpaperOptions: [], volumeControlStatusBarIcons: volumeControlStatusBarIcons(), fontSize: themeSettings.fontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, disableAnimations: themeSettings.disableAnimations) } diff --git a/TelegramUI/PresentationThemeSettings.swift b/TelegramUI/PresentationThemeSettings.swift index a26a3c9b3a..25acc3d16f 100644 --- a/TelegramUI/PresentationThemeSettings.swift +++ b/TelegramUI/PresentationThemeSettings.swift @@ -56,6 +56,18 @@ public enum PresentationThemeReference: PostboxCoding, Equatable { } } } + + var index: Int64 { + let namespace: Int32 + let id: Int32 + switch self { + case let .builtin(reference): + namespace = 0 + id = reference.rawValue + } + + return (Int64(namespace) << 32) | Int64(bitPattern: UInt64(UInt32(bitPattern: id))) + } } public enum PresentationFontSize: Int32 { @@ -166,7 +178,12 @@ public struct PresentationThemeSettings: PreferencesEntry { public var relatedResources: [MediaResourceId] { switch self.chatWallpaper { case let .image(representations): - return representations.map({ $0.resource.id }) + return representations.map { $0.resource.id } + case let .file(_, _, _, _, _, file): + var resources: [MediaResourceId] = [] + resources.append(file.resource.id) + resources.append(contentsOf: file.previewRepresentations.map { $0.resource.id }) + return resources default: return [] } @@ -223,6 +240,28 @@ public struct PresentationThemeSettings: PreferencesEntry { } } +final class PresentationThemeSpecificSettings: PostboxCoding { + public let chatWallpaper: TelegramWallpaper + public let chatWallpaperOptions: WallpaperPresentationOptions + + init(chatWallpaper: TelegramWallpaper, chatWallpaperOptions: WallpaperPresentationOptions) { + self.chatWallpaper = chatWallpaper + self.chatWallpaperOptions = chatWallpaperOptions + } + + init(decoder: PostboxDecoder) { + self.chatWallpaper = (decoder.decodeObjectForKey("w", decoder: { TelegramWallpaper(decoder: $0) }) as? TelegramWallpaper) ?? .builtin + self.chatWallpaperOptions = WallpaperPresentationOptions(rawValue: decoder.decodeInt32ForKey("o", orElse: 0)) + } + + func encode(_ encoder: PostboxEncoder) { + encoder.encodeObject(self.chatWallpaper, forKey: "w") + encoder.encodeInt32(self.chatWallpaperOptions.rawValue, forKey: "o") + } +} + +private let collectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 100, highWaterItemCount: 200) + public func updatePresentationThemeSettingsInteractively(postbox: Postbox, _ f: @escaping (PresentationThemeSettings) -> PresentationThemeSettings) -> Signal { return postbox.transaction { transaction -> Void in transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.presentationThemeSettings, { entry in @@ -234,5 +273,14 @@ public func updatePresentationThemeSettingsInteractively(postbox: Postbox, _ f: } return f(currentSettings) }) + + if let preferences = transaction.getPreferencesEntry(key: ApplicationSpecificPreferencesKeys.presentationThemeSettings) as? PresentationThemeSettings { + let themeSpecificSettings = PresentationThemeSpecificSettings(chatWallpaper: preferences.chatWallpaper, chatWallpaperOptions: preferences.chatWallpaperOptions) + + let key = ValueBoxKey(length: 8) + key.setInt64(0, value: preferences.theme.index) + let id = ItemCacheEntryId(collectionId: ApplicationSpecificItemCacheCollectionId.themeSpecificSettings, key: key) + transaction.putItemCacheEntry(id: id, entry: themeSpecificSettings, collectionSpec: collectionSpec) + } } } diff --git a/TelegramUI/SettingsThemeWallpaperNode.swift b/TelegramUI/SettingsThemeWallpaperNode.swift index fadf81150f..607b5e93c0 100644 --- a/TelegramUI/SettingsThemeWallpaperNode.swift +++ b/TelegramUI/SettingsThemeWallpaperNode.swift @@ -88,7 +88,7 @@ final class SettingsThemeWallpaperNode: ASDisplayNode { self.backgroundNode.isHidden = true let convertedRepresentations: [ImageRepresentationWithReference] = representations.map({ ImageRepresentationWithReference(representation: $0, reference: .wallpaper(resource: $0.resource)) }) - self.imageNode.setSignal(chatAvatarGalleryPhoto(account: account, representations: convertedRepresentations, autoFetchFullSize: true)) + self.imageNode.setSignal(chatAvatarGalleryPhoto(account: account, representations: convertedRepresentations, scaled: true, autoFetchFullSize: true)) let apply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: largestImageRepresentation(representations)!.dimensions.aspectFilled(size), boundingSize: size, intrinsicInsets: UIEdgeInsets())) apply() case let .file(file): @@ -100,7 +100,7 @@ final class SettingsThemeWallpaperNode: ASDisplayNode { convertedRepresentations.append(ImageRepresentationWithReference(representation: representation, reference: .wallpaper(resource: representation.resource))) } let dimensions = file.file.dimensions ?? CGSize(width: 100.0, height: 100.0) - self.imageNode.setSignal(chatAvatarGalleryPhoto(account: account, fileReference: .standalone(media: file.file), representations: convertedRepresentations, autoFetchFullSize: true)) + self.imageNode.setSignal(chatAvatarGalleryPhoto(account: account, fileReference: .standalone(media: file.file), representations: convertedRepresentations, scaled: true, autoFetchFullSize: true)) let apply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: dimensions.aspectFilled(size), boundingSize: size, intrinsicInsets: UIEdgeInsets())) apply() } diff --git a/TelegramUI/ThemeColorsGridControllerNode.swift b/TelegramUI/ThemeColorsGridControllerNode.swift index a757f0f61a..956ff900a9 100644 --- a/TelegramUI/ThemeColorsGridControllerNode.swift +++ b/TelegramUI/ThemeColorsGridControllerNode.swift @@ -123,7 +123,6 @@ final class ThemeColorsGridControllerNode: ASDisplayNode { controller.apply = { _, _, _ in pop() } - //let controller = WallpaperListPreviewController(account: account, source: .list(wallpapers: wallpapers, central: wallpaper, type: .colors)) strongSelf.present(controller, nil) } } @@ -284,6 +283,13 @@ final class ThemeColorsGridControllerNode: ASDisplayNode { } func scrollToTop() { + let offset = self.gridNode.scrollView.contentOffset.y + self.gridNode.scrollView.contentInset.top + let duration: Double = 0.25 + self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: GridNodeScrollToItem(index: 0, position: .top, transition: .animated(duration: 0.25, curve: .easeInOut), directionHint: .up, adjustForSection: true, adjustForTopInset: true), updateLayout: nil, itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) + + self.backgroundNode.layer.animatePosition(from: self.backgroundNode.layer.position.offsetBy(dx: 0.0, dy: -offset), to: self.backgroundNode.layer.position, duration: duration) + self.separatorNode.layer.animatePosition(from: self.separatorNode.layer.position.offsetBy(dx: 0.0, dy: -offset), to: self.separatorNode.layer.position, duration: duration) + self.customColorItemNode.layer.animatePosition(from: self.customColorItemNode.layer.position.offsetBy(dx: 0.0, dy: -offset), to: self.customColorItemNode.layer.position, duration: duration) } } diff --git a/TelegramUI/ThemeGridController.swift b/TelegramUI/ThemeGridController.swift index 7dae626237..504ca59fed 100644 --- a/TelegramUI/ThemeGridController.swift +++ b/TelegramUI/ThemeGridController.swift @@ -105,20 +105,19 @@ final class ThemeGridController: ViewController { if let strongSelf = self { let controller = WallpaperGalleryController(account: strongSelf.account, source: source) controller.apply = { [weak self, weak controller] wallpaper, mode, cropRect in - + if let strongSelf = self { + strongSelf.uploadCustomWallpaper(wallpaper, mode: mode, cropRect: cropRect, completion: { [weak self, weak controller] in + if let strongSelf = self { + strongSelf.deactivateSearch(animated: false) + strongSelf.controllerNode.scrollToTop(animated: false) + } + if let controller = controller { + controller.dismiss(forceAway: true) + } + }) + } } self?.present(controller, in: .window(.root), with: nil, blockInteraction: true) -// let controller = WallpaperListPreviewController(account: strongSelf.account, source: source) -// controller.apply = { [weak self, weak controller] wallpaper, mode, cropRect in -// if let strongSelf = self { -// strongSelf.uploadCustomWallpaper(wallpaper, mode: mode, cropRect: cropRect) -// if case .wallpaper = wallpaper { -// } else if let controller = controller { -// controller.dismiss() -// } -// } -// } -// self?.present(controller, in: .window(.root), with: nil, blockInteraction: true) } }, presentGallery: { [weak self] in if let strongSelf = self { @@ -130,15 +129,15 @@ final class ThemeGridController: ViewController { let controller = generator(legacyController.context) legacyController.bind(controller: controller) legacyController.deferScreenEdgeGestures = [.top] - controller.selectionBlock = { [weak self, weak legacyController] asset, thumbnailImage in + controller.selectionBlock = { [weak self, weak legacyController] asset, _ in if let strongSelf = self, let asset = asset { - let controller = WallpaperListPreviewController(account: strongSelf.account, source: .asset(asset.backingAsset, thumbnailImage)) + let controller = WallpaperGalleryController(account: strongSelf.account, source: .asset(asset.backingAsset)) controller.apply = { [weak self, weak legacyController, weak controller] wallpaper, mode, cropRect in if let strongSelf = self, let legacyController = legacyController, let controller = controller { strongSelf.uploadCustomWallpaper(wallpaper, mode: mode, cropRect: cropRect, completion: { [weak legacyController, weak controller] in if let legacyController = legacyController, let controller = controller { legacyController.dismiss() - controller.dismiss() + controller.dismiss(forceAway: true) } }) } @@ -181,12 +180,21 @@ final class ThemeGridController: ViewController { let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme) var items: [ActionSheetItem] = [] items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Wallpaper_DeleteConfirmation(Int32(wallpapers.count)), color: .destructive, action: { [weak self, weak actionSheet] in + actionSheet?.dismissAnimated() completed() guard let strongSelf = self else { return } + for wallpaper in wallpapers { + if wallpaper == strongSelf.presentationData.chatWallpaper { + let _ = (updatePresentationThemeSettingsInteractively(postbox: strongSelf.account.postbox, { current in + return PresentationThemeSettings(chatWallpaper: .builtin, chatWallpaperOptions: [], theme: current.theme, themeAccentColor: current.themeAccentColor, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, disableAnimations: current.disableAnimations) + })).start() + break + } + } for wallpaper in wallpapers { let _ = deleteWallpaper(account: strongSelf.account, wallpaper: wallpaper).start() @@ -220,34 +228,30 @@ final class ThemeGridController: ViewController { self?.deactivateSearch(animated: true) } - self.controllerNode.gridNode.scrollingCompleted = { - + self.controllerNode.gridNode.visibleContentOffsetChanged = { [weak self] offset in + if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode { + searchContentNode.updateGridVisibleContentOffset(offset) + } + } + + self.controllerNode.gridNode.scrollingCompleted = { [weak self] in + if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode { + let _ = fixNavigationSearchableGridNodeScrolling(strongSelf.controllerNode.gridNode, searchNode: searchContentNode) + } } -// self.controllerNode.gridNode.scroll = { [weak self] offset in -// if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode { -// searchContentNode.updateListVisibleContentOffset(offset) -// } -// } -// -// self.controllerNode.gridNode.scrollingCompleted = { [weak self] in -// if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode { -// return fixNavigationSearchableListNodeScrolling(listView, searchNode: searchContentNode) -// } else { -// return false -// } -// } self._ready.set(self.controllerNode.ready.get()) self.displayNodeDidLoad() } - private func uploadCustomWallpaper(_ wallpaper: WallpaperEntry, mode: WallpaperPresentationOptions, cropRect: CGRect?, completion: @escaping () -> Void) { + private func uploadCustomWallpaper(_ wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, cropRect: CGRect?, completion: @escaping () -> Void) { let imageSignal: Signal switch wallpaper { case .wallpaper: imageSignal = .complete() - case let .asset(asset, _): + completion() + case let .asset(asset): imageSignal = fetchPhotoLibraryImage(localIdentifier: asset.localIdentifier, thumbnail: false) |> filter { value in return !(value?.1 ?? true) @@ -294,44 +298,53 @@ final class ThemeGridController: ViewController { let finalCropRect: CGRect if let cropRect = cropRect { - finalCropRect = cropRect.insetBy(dx: -16.0, dy: 0.0) + finalCropRect = cropRect } else { - var screenSize = TGScreenSize() - screenSize.width += 32.0 + let screenSize = TGScreenSize() let fittedSize = TGScaleToFit(screenSize, image.size) finalCropRect = CGRect(x: (image.size.width - fittedSize.width) / 2.0, y: (image.size.height - fittedSize.height) / 2.0, width: fittedSize.width, height: fittedSize.height) } - - croppedImage = TGPhotoEditorCrop(image, nil, .up, 0.0, finalCropRect, false, CGSize(width: 2048.0, height: 2048.0), image.size, false) + croppedImage = TGPhotoEditorCrop(image, nil, .up, 0.0, finalCropRect, false, CGSize(width: 1440.0, height: 2960.0), image.size, true) - if let data = UIImageJPEGRepresentation(croppedImage, 0.85) { + let thumbnailDimensions = finalCropRect.size.fitted(CGSize(width: 320.0, height: 320.0)) + let thumbnailImage = generateScaledImage(image: croppedImage, size: thumbnailDimensions, scale: 1.0) + + if let data = UIImageJPEGRepresentation(croppedImage, 0.8), let thumbnailImage = thumbnailImage, let thumbnailData = UIImageJPEGRepresentation(thumbnailImage, 0.4) { + let thumbnailResource = LocalFileMediaResource(fileId: arc4random64()) + self.account.postbox.mediaBox.storeResourceData(thumbnailResource.id, data: thumbnailData) + let resource = LocalFileMediaResource(fileId: arc4random64()) self.account.postbox.mediaBox.storeResourceData(resource.id, data: data) let account = self.account - let updateWallpaper: (TelegramWallpaper) -> Void = { wallpaper in + let updateWallpaper: (TelegramWallpaper) -> Void = { [weak self] wallpaper in let _ = (updatePresentationThemeSettingsInteractively(postbox: account.postbox, { current in return PresentationThemeSettings(chatWallpaper: wallpaper, chatWallpaperOptions: mode, theme: current.theme, themeAccentColor: current.themeAccentColor, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, disableAnimations: current.disableAnimations) })).start() + + if let strongSelf = self, case .file = wallpaper { + strongSelf.controllerNode.updateWallpapers() + } } let apply: () -> Void = { - let wallpaper: TelegramWallpaper = .image([TelegramMediaImageRepresentation(dimensions: croppedImage.size, resource: resource)]) + let wallpaper: TelegramWallpaper = .image([TelegramMediaImageRepresentation(dimensions: thumbnailDimensions, resource: thumbnailResource), TelegramMediaImageRepresentation(dimensions: croppedImage.size, resource: resource)]) updateWallpaper(wallpaper) DispatchQueue.main.async { completion() } -// let _ = uploadWallpaper(account: account, resource: resource).start(next: { status in -// if case let .complete(wallpaper) = status { -// if mode.contains(.blur), case let .file(_, _, _, _, _, file) = wallpaper { -// let _ = account.postbox.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedBlurredWallpaperRepresentation(), complete: true, fetch: true).start(completed: { -// updateWallpaper(wallpaper) -// }) -// } else { -// updateWallpaper(wallpaper) -// } -// } -// }).start() + + let _ = uploadWallpaper(account: account, resource: resource).start(next: { status in + if case let .complete(wallpaper) = status { + if mode.contains(.blur), case let .file(_, _, _, _, _, file) = wallpaper { + let _ = account.postbox.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedBlurredWallpaperRepresentation(), complete: true, fetch: true).start(completed: { + updateWallpaper(wallpaper) + }) + } else { + updateWallpaper(wallpaper) + } + } + }) } if mode.contains(.blur) { diff --git a/TelegramUI/ThemeGridControllerNode.swift b/TelegramUI/ThemeGridControllerNode.swift index cf736c6c2c..bc213e23bc 100644 --- a/TelegramUI/ThemeGridControllerNode.swift +++ b/TelegramUI/ThemeGridControllerNode.swift @@ -39,7 +39,7 @@ struct ThemeGridControllerNodeState: Equatable { var selectedIndices: Set func withUpdatedEditing(_ editing: Bool) -> ThemeGridControllerNodeState { - return ThemeGridControllerNodeState(editing: editing, selectedIndices: self.selectedIndices) + return ThemeGridControllerNodeState(editing: editing, selectedIndices: editing ? self.selectedIndices : Set()) } func withUpdatedSelectedIndices(_ selectedIndices: Set) -> ThemeGridControllerNodeState { @@ -85,8 +85,21 @@ private struct ThemeGridControllerEntry: Comparable, Identifiable { return lhs.index < rhs.index } - var stableId: Int { - return self.index + var stableId: Int64 { + switch self.wallpaper { + case .builtin: + return 0 + case let .color(color): + return (Int64(0) << 32) | Int64(bitPattern: UInt64(UInt32(bitPattern: color))) + case let .file(id, _, _, _, _, _): + return (Int64(1) << 32) | id + case let .image(representations): + if let largest = largestImageRepresentation(representations) { + return (Int64(2) << 32) | Int64(largest.resource.id.hashValue) + } else { + return 0 + } + } } func item(account: Account, interaction: ThemeGridControllerInteraction) -> ThemeGridControllerItem { @@ -157,6 +170,7 @@ final class ThemeGridControllerNode: ASDisplayNode { var requestDeactivateSearch: (() -> Void)? let ready = ValuePromise() + let wallpapersPromise: Promise<[TelegramWallpaper]> private var backgroundNode: ASDisplayNode private var separatorNode: ASDisplayNode @@ -221,6 +235,10 @@ final class ThemeGridControllerNode: ASDisplayNode { self.currentState = ThemeGridControllerNodeState(editing: false, selectedIndices: Set()) self.statePromise = ValuePromise(self.currentState, ignoreRepeated: true) + let wallpapersPromise: Promise<[TelegramWallpaper]> = Promise() + wallpapersPromise.set(telegramWallpapers(postbox: account.postbox, network: account.network)) + self.wallpapersPromise = wallpapersPromise + super.init() self.setViewBlock({ @@ -236,22 +254,19 @@ final class ThemeGridControllerNode: ASDisplayNode { self.gridNode.addSubnode(self.descriptionItemNode) self.addSubnode(self.gridNode) - let wallpapersPromise: Promise<[TelegramWallpaper]> = Promise() - wallpapersPromise.set(telegramWallpapers(postbox: account.postbox, network: account.network)) let previousEntries = Atomic<[ThemeGridControllerEntry]?>(value: nil) - let interaction = ThemeGridControllerInteraction(openWallpaper: { [weak self] wallpaper in if let strongSelf = self, !strongSelf.currentState.editing { let entries = previousEntries.with { $0 } if let entries = entries, !entries.isEmpty { let wallpapers = entries.map { $0.wallpaper } - var mode: WallpaperPresentationOptions? + var options: WallpaperPresentationOptions? if wallpaper == strongSelf.presentationData.chatWallpaper { - mode = strongSelf.presentationData.chatWallpaperMode + options = strongSelf.presentationData.chatWallpaperOptions } - presentPreviewController(.list(wallpapers: wallpapers, central: wallpaper, type: .wallpapers(mode))) + presentPreviewController(.list(wallpapers: wallpapers, central: wallpaper, type: .wallpapers(options))) } } }, toggleWallpaperSelection: { [weak self] index, value in @@ -277,7 +292,9 @@ final class ThemeGridControllerNode: ASDisplayNode { updatedWallpapers.append(entry.wallpaper) } } - wallpapersPromise.set(.single(updatedWallpapers)) + + wallpapersPromise.set(.single(updatedWallpapers) + |> then(telegramWallpapers(postbox: account.postbox, network: account.network))) } }) } @@ -294,18 +311,16 @@ final class ThemeGridControllerNode: ASDisplayNode { var entries: [ThemeGridControllerEntry] = [] var index = 1 - var hasCurrent = false + entries.insert(ThemeGridControllerEntry(index: 0, wallpaper: presentationData.chatWallpaper, selected: true), at: 0) + for wallpaper in wallpapers { let selected = areWallpapersEqual(presentationData.chatWallpaper, wallpaper) - entries.append(ThemeGridControllerEntry(index: index, wallpaper: wallpaper, selected: selected)) - hasCurrent = hasCurrent || selected + if !selected { + entries.append(ThemeGridControllerEntry(index: index, wallpaper: wallpaper, selected: false)) + } index += 1 } - if !hasCurrent { - entries.insert(ThemeGridControllerEntry(index: 0, wallpaper: presentationData.chatWallpaper, selected: true), at: 0) - } - let previous = previousEntries.swap(entries) return (preparedThemeGridEntryTransition(account: account, from: previous ?? [], to: entries, interaction: interaction), previous == nil) } @@ -370,6 +385,10 @@ final class ThemeGridControllerNode: ASDisplayNode { } } + func updateWallpapers() { + self.wallpapersPromise.set(telegramWallpapers(postbox: self.account.postbox, network: self.account.network)) + } + func updatePresentationData(_ presentationData: PresentationData) { self.presentationData = presentationData @@ -586,11 +605,23 @@ final class ThemeGridControllerNode: ASDisplayNode { } } - func scrollToTop() { + func scrollToTop(animated: Bool = true) { if let searchDisplayController = self.searchDisplayController { searchDisplayController.contentNode.scrollToTop() } else { - self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: GridNodeScrollToItem(index: 0, position: .top, transition: .animated(duration: 0.25, curve: .easeInOut), directionHint: .up, adjustForSection: true, adjustForTopInset: true), updateLayout: nil, itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) + let offset = self.gridNode.scrollView.contentOffset.y + self.gridNode.scrollView.contentInset.top + let duration: Double = 0.25 + let transition: ContainedViewLayoutTransition = animated ? .animated(duration: duration, curve: .easeInOut) : .immediate + + self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: GridNodeScrollToItem(index: 0, position: .top, transition: transition, directionHint: .up, adjustForSection: true, adjustForTopInset: true), updateLayout: nil, itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) + + if animated { + self.backgroundNode.layer.animatePosition(from: self.backgroundNode.layer.position.offsetBy(dx: 0.0, dy: -offset), to: self.backgroundNode.layer.position, duration: duration) + self.separatorNode.layer.animatePosition(from: self.separatorNode.layer.position.offsetBy(dx: 0.0, dy: -offset), to: self.separatorNode.layer.position, duration: duration) + self.colorItemNode.layer.animatePosition(from: self.colorItemNode.layer.position.offsetBy(dx: 0.0, dy: -offset), to: self.colorItemNode.layer.position, duration: duration) + self.galleryItemNode.layer.animatePosition(from: self.galleryItemNode.layer.position.offsetBy(dx: 0.0, dy: -offset), to: self.galleryItemNode.layer.position, duration: duration) + self.descriptionItemNode.layer.animatePosition(from: self.descriptionItemNode.layer.position.offsetBy(dx: 0.0, dy: -offset), to: self.descriptionItemNode.layer.position, duration: duration) + } } } } diff --git a/TelegramUI/ThemeSettingsController.swift b/TelegramUI/ThemeSettingsController.swift index 57c35e5616..c7218786ea 100644 --- a/TelegramUI/ThemeSettingsController.swift +++ b/TelegramUI/ThemeSettingsController.swift @@ -254,23 +254,51 @@ public func themeSettingsController(account: Account) -> ViewController { let _ = telegramWallpapers(postbox: account.postbox, network: account.network).start() let arguments = ThemeSettingsControllerArguments(account: account, selectTheme: { index in - let _ = updatePresentationThemeSettingsInteractively(postbox: account.postbox, { current in - let wallpaper: TelegramWallpaper - let theme: PresentationThemeReference - if index == 0 { - wallpaper = .builtin - theme = .builtin(.dayClassic) - } else if index == 1 { - wallpaper = .color(0xffffff) + let theme: PresentationThemeReference + switch index { + case 1: theme = .builtin(.day) - } else if index == 2 { - wallpaper = .color(0x000000) + case 2: theme = .builtin(.nightGrayscale) - } else { - wallpaper = .color(0x18222D) + case 3: theme = .builtin(.nightAccent) + default: + theme = .builtin(.dayClassic) + } + + let _ = (account.postbox.transaction { transaction -> Void in + let wallpaper: TelegramWallpaper + let wallpaperOptions: WallpaperPresentationOptions + + let key = ValueBoxKey(length: 8) + key.setInt64(0, value: theme.index) + if let entry = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: ApplicationSpecificItemCacheCollectionId.themeSpecificSettings, key: key)) as? PresentationThemeSpecificSettings { + wallpaper = entry.chatWallpaper + wallpaperOptions = entry.chatWallpaperOptions + } else { + switch index { + case 1: + wallpaper = .color(0xffffff) + case 2: + wallpaper = .color(0x000000) + case 3: + wallpaper = .color(0x18222d) + default: + wallpaper = .builtin + } + wallpaperOptions = [] } - return PresentationThemeSettings(chatWallpaper: wallpaper, chatWallpaperOptions: [], theme: theme, themeAccentColor: current.themeAccentColor, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, disableAnimations: current.disableAnimations) + + transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.presentationThemeSettings, { entry in + let current: PresentationThemeSettings + if let entry = entry as? PresentationThemeSettings { + current = entry + } else { + current = PresentationThemeSettings.defaultSettings + } + + return PresentationThemeSettings(chatWallpaper: wallpaper, chatWallpaperOptions: wallpaperOptions, theme: theme, themeAccentColor: current.themeAccentColor, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, disableAnimations: current.disableAnimations) + }) }).start() }, selectFontSize: { size in let _ = updatePresentationThemeSettingsInteractively(postbox: account.postbox, { current in diff --git a/TelegramUI/UrlHandling.swift b/TelegramUI/UrlHandling.swift index b9a1bdae87..769db81921 100644 --- a/TelegramUI/UrlHandling.swift +++ b/TelegramUI/UrlHandling.swift @@ -11,7 +11,7 @@ enum ParsedInternalPeerUrlParameter { } enum WallpaperUrlParameter { - case slug(String) + case slug(String, WallpaperPresentationOptions) case color(UIColor) } @@ -180,7 +180,24 @@ func parseInternalUrl(query: String) -> ParsedInternalUrl? { if component.count == 6, component.rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789abcdefABCDEF").inverted) == nil, let color = UIColor(hexString: component) { parameter = .color(color) } else { - parameter = .slug(component) + var options: WallpaperPresentationOptions = [] + if let queryItems = components.queryItems { + for queryItem in queryItems { + if let value = queryItem.value, queryItem.name == "mode" { + for option in value.components(separatedBy: "+") { + switch option.lowercased() { + case "motion": + options.insert(.motion) + case "blur": + options.insert(.blur) + default: + break + } + } + } + } + } + parameter = .slug(component, options) } return .wallpaper(parameter) } else if let value = Int(pathComponents[1]) { diff --git a/TelegramUI/WallpaperGalleryController.swift b/TelegramUI/WallpaperGalleryController.swift index 9817019659..b0da7a0f8f 100644 --- a/TelegramUI/WallpaperGalleryController.swift +++ b/TelegramUI/WallpaperGalleryController.swift @@ -14,16 +14,16 @@ enum WallpaperListType { enum WallpaperListSource { case list(wallpapers: [TelegramWallpaper], central: TelegramWallpaper, type: WallpaperListType) - case wallpaper(TelegramWallpaper) - case slug(String, TelegramMediaFile?) - case asset(PHAsset, UIImage?) + case wallpaper(TelegramWallpaper, WallpaperPresentationOptions?) + case slug(String, TelegramMediaFile?, WallpaperPresentationOptions?) + case asset(PHAsset) case contextResult(ChatContextResult) case customColor(Int32?) } enum WallpaperGalleryEntry: Equatable { case wallpaper(TelegramWallpaper) - case asset(PHAsset, UIImage?) + case asset(PHAsset) case contextResult(ChatContextResult) public static func ==(lhs: WallpaperGalleryEntry, rhs: WallpaperGalleryEntry) -> Bool { @@ -34,8 +34,8 @@ enum WallpaperGalleryEntry: Equatable { } else { return false } - case let .asset(lhsAsset, _): - if case let .asset(rhsAsset, _) = rhs, lhsAsset.localIdentifier == rhsAsset.localIdentifier { + case let .asset(lhsAsset): + if case let .asset(rhsAsset) = rhs, lhsAsset.localIdentifier == rhsAsset.localIdentifier { return true } else { return false @@ -61,6 +61,16 @@ class WallpaperGalleryOverlayNode: ASDisplayNode { } } +class WallpaperGalleryControllerNode: GalleryControllerNode { + override func updateDistanceFromEquilibrium(_ value: CGFloat) { + guard let itemNode = self.pager.centralItemNode() as? WallpaperGalleryItemNode else { + return + } + + itemNode.updateDismissTransition(value) + } +} + class WallpaperGalleryController: ViewController { private var galleryNode: GalleryControllerNode { return self.displayNode as! GalleryControllerNode @@ -81,6 +91,7 @@ class WallpaperGalleryController: ViewController { private var presentationData: PresentationData private var presentationDataDisposable: Disposable? + private var initialOptions: WallpaperPresentationOptions? private var entries: [WallpaperGalleryEntry] = [] private var centralEntryIndex: Int? @@ -93,8 +104,6 @@ class WallpaperGalleryController: ViewController { private var overlayNode: WallpaperGalleryOverlayNode? private var messageNodes: [ListViewItemNode]? - private var blurredButtonNode: WallpaperOptionButtonNode? - private var motionButtonNode: WallpaperOptionButtonNode? private var toolbarNode: WallpaperGalleryToolbarNode? init(account: Account, source: WallpaperListSource) { @@ -106,21 +115,28 @@ class WallpaperGalleryController: ViewController { self.title = self.presentationData.strings.WallpaperPreview_Title self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style + self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) switch source { case let .list(wallpapers, central, type): self.entries = wallpapers.map { .wallpaper($0) } self.centralEntryIndex = wallpapers.index(of: central)! - case let .slug(slug, file): + + if case let .wallpapers(wallpaperOptions) = type, let options = wallpaperOptions { + self.initialOptions = options + } + case let .slug(slug, file, options): if let file = file { self.entries = [.wallpaper(.file(id: 0, accessHash: 0, isCreator: false, isDefault: false, slug: slug, file: file))] self.centralEntryIndex = 0 + self.initialOptions = options } - case let .wallpaper(wallpaper): + case let .wallpaper(wallpaper, options): self.entries = [.wallpaper(wallpaper)] self.centralEntryIndex = 0 - case let .asset(asset, thumbnailImage): - self.entries = [.asset(asset, thumbnailImage)] + self.initialOptions = options + case let .asset(asset): + self.entries = [.asset(asset)] self.centralEntryIndex = 0 case let .contextResult(result): self.entries = [.contextResult(result)] @@ -173,7 +189,7 @@ class WallpaperGalleryController: ViewController { self.centralItemAttributesDisposable.add(self.centralItemAction.get().start(next: { [weak self] barButton in if let strongSelf = self { - strongSelf.navigationItem.rightBarButtonItem = barButton + strongSelf.navigationItem.setRightBarButton(barButton, animated: true) } })) } @@ -196,7 +212,7 @@ class WallpaperGalleryController: ViewController { self.toolbarNode?.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings) } - private func dismiss(forceAway: Bool) { + func dismiss(forceAway: Bool) { let completion: () -> Void = { [weak self] in self?.presentingViewController?.dismiss(animated: false, completion: nil) } @@ -209,11 +225,11 @@ class WallpaperGalleryController: ViewController { if let strongSelf = self { strongSelf.present(controller, in: .window(.root), with: arguments, blockInteraction: true) } - }, dismissController: { [weak self] in + }, dismissController: { [weak self] in self?.dismiss(forceAway: true) - }, replaceRootController: { controller, ready in + }, replaceRootController: { controller, ready in }) - self.displayNode = GalleryControllerNode(controllerInteraction: controllerInteraction, pageGap: 0.0) + self.displayNode = WallpaperGalleryControllerNode(controllerInteraction: controllerInteraction, pageGap: 0.0) self.displayNodeDidLoad() self.galleryNode.statusBar = self.statusBar @@ -231,6 +247,10 @@ class WallpaperGalleryController: ViewController { node.action = { [weak self] in self?.actionPressed() } + + if let (layout, _) = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.2, curve: .easeInOut)) + } } } } @@ -239,6 +259,13 @@ class WallpaperGalleryController: ViewController { self.galleryNode.backgroundNode.isOpaque = false self.galleryNode.isBackgroundExtendedOverNavigationBar = true + switch self.source { + case .asset, .contextResult, .customColor: + self.galleryNode.scrollView.isScrollEnabled = false + default: + break + } + let presentationData = self.account.telegramApplicationContext.currentPresentationData.with { $0 } let toolbarNode = WallpaperGalleryToolbarNode(theme: presentationData.theme, strings: presentationData.strings) let overlayNode = WallpaperGalleryOverlayNode() @@ -254,15 +281,8 @@ class WallpaperGalleryController: ViewController { } toolbarNode.done = { [weak self] in if let strongSelf = self { - var options: WallpaperPresentationOptions = [] - if (strongSelf.blurredButtonNode?.isSelected ?? false) { - options.insert(.blur) - } - if (strongSelf.motionButtonNode?.isSelected ?? false) { - options.insert(.motion) - } - - if let centralItemNode = strongSelf.galleryNode.pager.centralItemNode() { + if let centralItemNode = strongSelf.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode { + let options = centralItemNode.options if !strongSelf.entries.isEmpty { let entry = strongSelf.entries[centralItemNode.index] switch entry { @@ -311,38 +331,12 @@ class WallpaperGalleryController: ViewController { } } - let blurredButtonNode = WallpaperOptionButtonNode(title: presentationData.strings.WallpaperPreview_Blurred) - blurredButtonNode.addTarget(self, action: #selector(self.toggleBlur), forControlEvents: .touchUpInside) - overlayNode.addSubnode(blurredButtonNode) - self.blurredButtonNode = blurredButtonNode - - let motionButtonNode = WallpaperOptionButtonNode(title: presentationData.strings.WallpaperPreview_Motion) - motionButtonNode.addTarget(self, action: #selector(self.toggleMotion), forControlEvents: .touchUpInside) - overlayNode.addSubnode(motionButtonNode) - self.motionButtonNode = motionButtonNode - let ready = self.galleryNode.pager.ready() |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(Void())) |> afterNext { [weak self] _ in self?.didSetReady = true } self._ready.set(ready |> map { true }) } - @objc func toggleBlur() { - if let centralItemNode = self.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode { - let value = !(self.blurredButtonNode?.isSelected ?? false) - self.blurredButtonNode?.setSelected(value, animated: true) - centralItemNode.setBlurEnabled(value, animated: true) - } - } - - @objc func toggleMotion() { - if let centralItemNode = self.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode { - let value = !(self.motionButtonNode?.isSelected ?? false) - self.motionButtonNode?.setSelected(value, animated: true) - centralItemNode.setMotionEnabled(value) - } - } - private func currentEntry() -> WallpaperGalleryEntry? { if let centralItemNode = self.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode { return centralItemNode.entry @@ -363,10 +357,16 @@ class WallpaperGalleryController: ViewController { node.action = { [weak self] in self?.actionPressed() } + + if let (layout, _) = self.validLayout { + self.containerLayoutUpdated(layout, transition: .immediate) + } } } override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + let hadLayout = self.validLayout != nil + super.containerLayoutUpdated(layout, transition: transition) self.galleryNode.frame = CGRect(origin: CGPoint(), size: layout.size) @@ -381,6 +381,25 @@ class WallpaperGalleryController: ViewController { peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_PreviewReplyAuthor, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) peers[otherPeerId] = TelegramUser(id: otherPeerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_PreviewReplyAuthor, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) + let bottomInset = layout.intrinsicInsets.bottom + 49.0 + var optionsAvailable = true + if let centralItemNode = self.galleryNode.pager.centralItemNode() { + if !self.entries.isEmpty { + let entry = self.entries[centralItemNode.index] + switch entry { + case let .wallpaper(wallpaper): + switch wallpaper { + case .color: + optionsAvailable = false + default: + break + } + default: + break + } + } + } + let controllerInteraction = ChatControllerInteraction.default let chatPresentationData = ChatPresentationData(theme: ChatPresentationThemeData(theme: self.presentationData.theme, wallpaper: self.presentationData.chatWallpaper), fontSize: self.presentationData.fontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: false) @@ -444,33 +463,9 @@ class WallpaperGalleryController: ViewController { self.messageNodes = messageNodes } - var bottomInset = layout.intrinsicInsets.bottom + 49.0 - var optionsAvailable = true - if let centralItemNode = self.galleryNode.pager.centralItemNode() { - if !self.entries.isEmpty { - let entry = self.entries[centralItemNode.index] - switch entry { - case let .wallpaper(wallpaper): - switch wallpaper { - case .color: - optionsAvailable = false - default: - break - } - default: - break - } - } - } - transition.updateFrame(node: self.toolbarNode!, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - 49.0 - layout.intrinsicInsets.bottom), size: CGSize(width: layout.size.width, height: 49.0 + layout.intrinsicInsets.bottom))) self.toolbarNode!.updateLayout(size: CGSize(width: layout.size.width, height: 49.0), layout: layout, transition: transition) - let buttonSize = CGSize(width: 100.0, height: 30.0) - transition.updateFrame(node: self.blurredButtonNode!, frame: CGRect(origin: CGPoint(x: layout.size.width / 2.0 - buttonSize.width - 10.0, y: layout.size.height - 49.0 - layout.intrinsicInsets.bottom - 54.0), size: buttonSize)) - - transition.updateFrame(node: self.motionButtonNode!, frame: CGRect(origin: CGPoint(x: layout.size.width / 2.0 + 10.0, y: layout.size.height - 49.0 - layout.intrinsicInsets.bottom - 54.0), size: buttonSize)) - if let messageNodes = self.messageNodes { var bottomOffset: CGFloat = layout.size.height - bottomInset - 9.0 if optionsAvailable { @@ -482,24 +477,27 @@ class WallpaperGalleryController: ViewController { } } - let replace = self.validLayout == nil self.validLayout = (layout, 0.0) - if replace { + if !hadLayout { self.galleryNode.pager.replaceItems(self.entries.map({ WallpaperGalleryItem(account: self.account, entry: $0) }), centralItemIndex: self.centralEntryIndex) + + if let initialOptions = self.initialOptions, let itemNode = self.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode { + itemNode.options = initialOptions + } } } private func actionPressed() { - guard let entry = self.currentEntry(), case let .wallpaper(wallpaper) = entry else { + guard let entry = self.currentEntry(), case let .wallpaper(wallpaper) = entry, let itemNode = self.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode else { return } var options = "" - if (self.blurredButtonNode?.isSelected ?? false) { + if (itemNode.options.contains(.blur)) { options = "?mode=blur" } - if (self.motionButtonNode?.isSelected ?? false) { + if (itemNode.options.contains(.motion)) { if options.isEmpty { options = "?mode=motion" } else { diff --git a/TelegramUI/WallpaperGalleryDecorationNode.swift b/TelegramUI/WallpaperGalleryDecorationNode.swift index 1274d02501..208bfbb804 100644 --- a/TelegramUI/WallpaperGalleryDecorationNode.swift +++ b/TelegramUI/WallpaperGalleryDecorationNode.swift @@ -6,7 +6,7 @@ import Postbox final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { private let backgroundNode: ASDisplayNode - private let checkNode: CheckNode + private let checkNode: ModernCheckNode private let textNode: ASTextNode private var _isSelected: Bool = false @@ -16,18 +16,19 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { } set { self._isSelected = newValue - self.checkNode.setIsChecked(newValue, animated: false) + self.checkNode.setSelected(newValue, animated: false) } } init(title: String) { self.backgroundNode = ASDisplayNode() self.backgroundNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.3) - self.backgroundNode.cornerRadius = 8.0 - self.checkNode = CheckNode(strokeColor: .white, fillColor: .white, foregroundColor: .black, style: .plain) + self.backgroundNode.cornerRadius = 6.0 + + self.checkNode = ModernCheckNode(theme: CheckNodeTheme(backgroundColor: .white, strokeColor: .clear, borderColor: .white, hasShadow: false)) self.checkNode.isUserInteractionEnabled = false self.textNode = ASTextNode() - self.textNode.attributedText = NSAttributedString(string: title, font: Font.regular(13), textColor: .white) + self.textNode.attributedText = NSAttributedString(string: title, font: Font.medium(13), textColor: .white) super.init() @@ -60,9 +61,15 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { } } + var color: UIColor = UIColor(rgb: 0x000000, alpha: 0.3) { + didSet { + self.backgroundNode.backgroundColor = self.color + } + } + func setSelected(_ selected: Bool, animated: Bool = false) { self._isSelected = selected - self.checkNode.setIsChecked(selected, animated: animated) + self.checkNode.setSelected(selected, animated: animated) } func setEnabled(_ enabled: Bool) { @@ -80,8 +87,8 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { self.backgroundNode.frame = self.bounds - let checkSize = CGSize(width: 32.0, height: 32.0) - self.checkNode.frame = CGRect(origin: CGPoint(x: 5.0, y: -1.0), size: checkSize) + let checkSize = CGSize(width: 18.0, height: 18.0) + self.checkNode.frame = CGRect(origin: CGPoint(x: 12.0, y: 6.0), size: checkSize) self.textNode.frame = CGRect(x: 39.0, y: 6.0 + UIScreenPixel, width: 100.0, height: 20.0) } diff --git a/TelegramUI/WallpaperGalleryItem.swift b/TelegramUI/WallpaperGalleryItem.swift index e3825aef7e..73f3e1630e 100644 --- a/TelegramUI/WallpaperGalleryItem.swift +++ b/TelegramUI/WallpaperGalleryItem.swift @@ -6,6 +6,34 @@ import Postbox import TelegramCore import LegacyComponents +private class WallpaperMotionEffect: UIInterpolatingMotionEffect { + var previousValue: CGFloat? + + override func keyPathsAndRelativeValues(forViewerOffset viewerOffset: UIOffset) -> [String : Any]? { + var motionAmplitude: CGFloat = 0.0 + switch self.type { + case .tiltAlongHorizontalAxis: + motionAmplitude = viewerOffset.horizontal + case .tiltAlongVerticalAxis: + motionAmplitude = viewerOffset.vertical + } + + if (motionAmplitude > 0) { + guard let max = (self.maximumRelativeValue as? CGFloat) else { + return nil + } + let value = max * motionAmplitude + return [self.keyPath: value] + } else { + guard let min = (self.minimumRelativeValue as? CGFloat) else { + return nil + } + let value = -(min) * motionAmplitude + return [self.keyPath: value] + } + } +} + class WallpaperGalleryItem: GalleryItem { let account: Account let entry: WallpaperGalleryEntry @@ -32,7 +60,8 @@ class WallpaperGalleryItem: GalleryItem { } } -let progressDiameter: CGFloat = 50.0 +private let progressDiameter: CGFloat = 50.0 +private let motionAmount: CGFloat = 32.0 final class WallpaperGalleryItemNode: GalleryItemNode { private let account: Account @@ -46,15 +75,23 @@ final class WallpaperGalleryItemNode: GalleryItemNode { private let blurredNode: BlurredImageNode let cropNode: WallpaperCropNode + private var blurButtonNode: WallpaperOptionButtonNode + private var motionButtonNode: WallpaperOptionButtonNode + fileprivate let _ready = Promise() private let fetchDisposable = MetaDisposable() private let statusDisposable = MetaDisposable() + private let colorDisposable = MetaDisposable() let subtitle = Promise(nil) let status = Promise(.Local) let actionButton = Promise(nil) + let controlsColor = Promise(UIColor(rgb: 0x000000, alpha: 0.3)) var action: (() -> Void)? + private var validLayout: ContainerViewLayout? + private var validOffset: CGFloat? + init(account: Account) { self.account = account @@ -69,6 +106,10 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.blurredNode = BlurredImageNode() + let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } + self.blurButtonNode = WallpaperOptionButtonNode(title: presentationData.strings.WallpaperPreview_Blurred) + self.motionButtonNode = WallpaperOptionButtonNode(title: presentationData.strings.WallpaperPreview_Motion) + super.init() self.clipsToBounds = true @@ -84,11 +125,18 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.addSubnode(self.wrapperNode) self.addSubnode(self.statusNode) self.addSubnode(self.progressNode) + + self.addSubnode(self.blurButtonNode) + self.addSubnode(self.motionButtonNode) + + self.blurButtonNode.addTarget(self, action: #selector(self.toggleBlur), forControlEvents: .touchUpInside) + self.motionButtonNode.addTarget(self, action: #selector(self.toggleMotion), forControlEvents: .touchUpInside) } deinit { self.fetchDisposable.dispose() self.statusDisposable.dispose() + self.colorDisposable.dispose() } var cropRect: CGRect? { @@ -144,6 +192,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { statusSignal = .single(.Local) subtitleSignal = .single(nil) self.backgroundColor = UIColor(rgb: UInt32(bitPattern: color)) + actionSignal = .single(defaultAction) case let .file(file): let dimensions = file.file.dimensions ?? CGSize(width: 100.0, height: 100.0) contentSize = dimensions @@ -187,7 +236,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { subtitleSignal = .single(nil) } self.cropNode.removeFromSupernode() - case let .asset(asset, _): + case let .asset(asset): let dimensions = CGSize(width: asset.pixelWidth, height: asset.pixelHeight) contentSize = dimensions displaySize = dimensions.dividedByScreenScale().integralFloor @@ -300,31 +349,55 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.subtitle.set(subtitleSignal |> deliverOnMainQueue) self.status.set(statusSignal |> deliverOnMainQueue) self.actionButton.set(actionSignal |> deliverOnMainQueue) + self.controlsColor.set(serviceColor(from: imagePromise.get()) |> deliverOnMainQueue) + self.colorDisposable.set((serviceColor(from: imagePromise.get()) + |> deliverOnMainQueue).start(next: { [weak self] color in + self?.blurButtonNode.color = color + self?.motionButtonNode.color = color + })) } } - func setMotionEnabled(_ enabled: Bool) { - if enabled { - let amount = 24.0 - - let horizontal = UIInterpolatingMotionEffect(keyPath: "center.x", type: .tiltAlongHorizontalAxis) - horizontal.minimumRelativeValue = -amount - horizontal.maximumRelativeValue = amount - - let vertical = UIInterpolatingMotionEffect(keyPath: "center.y", type: .tiltAlongVerticalAxis) - vertical.minimumRelativeValue = -amount - vertical.maximumRelativeValue = amount - - let group = UIMotionEffectGroup() - group.motionEffects = [horizontal, vertical] - self.wrapperNode.view.addMotionEffect(group) - } else { - for effect in self.wrapperNode.view.motionEffects { - self.wrapperNode.view.removeMotionEffect(effect) - } + override func screenFrameUpdated(_ frame: CGRect) { + let offset = -frame.minX + self.validOffset = offset + if let layout = self.validLayout { + self.updateButtonsLayout(layout: layout, offset: CGPoint(x: offset, y: 0.0), transition: .immediate) } } + func updateDismissTransition(_ value: CGFloat) { + if let layout = self.validLayout { + self.updateButtonsLayout(layout: layout, offset: CGPoint(x: 0.0, y: value), transition: .immediate) + } + } + + var options: WallpaperPresentationOptions { + get { + var options: WallpaperPresentationOptions = [] + if self.blurButtonNode.isSelected { + options.insert(.blur) + } + if self.motionButtonNode.isSelected { + options.insert(.motion) + } + return options + } + set { + self.setBlurEnabled(newValue.contains(.blur), animated: false) + self.blurButtonNode.isSelected = newValue.contains(.blur) + + self.setMotionEnabled(newValue.contains(.motion), animated: false) + self.motionButtonNode.isSelected = newValue.contains(.motion) + } + } + + @objc func toggleBlur() { + let value = !self.blurButtonNode.isSelected + self.blurButtonNode.setSelected(value, animated: true) + self.setBlurEnabled(value, animated: true) + } + func setBlurEnabled(_ enabled: Bool, animated: Bool) { let blurRadius: CGFloat = 45.0 @@ -334,8 +407,8 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.blurredNode.frame = self.imageNode.bounds self.imageNode.addSubnode(self.blurredNode) } else { - self.blurredNode.frame = self.imageNode.frame - self.addSubnode(self.blurredNode) + self.blurredNode.frame = self.imageNode.bounds + self.imageNode.addSubnode(self.blurredNode) } } @@ -364,14 +437,68 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } } - override func visibilityUpdated(isVisible: Bool) { - super.visibilityUpdated(isVisible: isVisible) + @objc func toggleMotion() { + let value = !self.motionButtonNode.isSelected + self.motionButtonNode.setSelected(value, animated: true) + self.setMotionEnabled(value, animated: true) + } + + func setMotionEnabled(_ enabled: Bool, animated: Bool) { + if enabled { + let horizontal = WallpaperMotionEffect(keyPath: "center.x", type: .tiltAlongHorizontalAxis) + horizontal.minimumRelativeValue = motionAmount + horizontal.maximumRelativeValue = -motionAmount + + let vertical = WallpaperMotionEffect(keyPath: "center.y", type: .tiltAlongVerticalAxis) + vertical.minimumRelativeValue = motionAmount + vertical.maximumRelativeValue = -motionAmount + + let group = UIMotionEffectGroup() + group.motionEffects = [horizontal, vertical] + self.wrapperNode.view.addMotionEffect(group) + + let scale = (self.frame.width + motionAmount * 2.0) / self.frame.width + if animated { + self.wrapperNode.layer.animateScale(from: 1.0, to: scale, duration: 0.2, removeOnCompletion: false) + } else { + self.wrapperNode.transform = CATransform3DMakeScale(scale, scale, 1.0) + } + } else { + let position = self.wrapperNode.layer.presentation()?.position + + for effect in self.wrapperNode.view.motionEffects { + self.wrapperNode.view.removeMotionEffect(effect) + } + + let scale = (self.frame.width + motionAmount * 2.0) / self.frame.width + if animated { + self.wrapperNode.layer.animateScale(from: scale, to: 1.0, duration: 0.2, removeOnCompletion: false) + if let position = position { + self.wrapperNode.layer.animatePosition(from: position, to: self.wrapperNode.layer.position, duration: 0.2) + } + } else { + self.wrapperNode.transform = CATransform3DIdentity + } + } + } + + func updateButtonsLayout(layout: ContainerViewLayout, offset: CGPoint, transition: ContainedViewLayoutTransition) { + let buttonSize = CGSize(width: 100.0, height: 30.0) + let alpha = 1.0 - min(1.0, max(0.0, abs(offset.y) / 50.0)) + + transition.updateFrame(node: self.blurButtonNode, frame: CGRect(origin: CGPoint(x: floor(layout.size.width / 2.0 - buttonSize.width - 10.0) + offset.x, y: layout.size.height - 49.0 - layout.intrinsicInsets.bottom - 54.0 + offset.y), size: buttonSize)) + transition.updateAlpha(node: self.blurButtonNode, alpha: alpha) + + transition.updateFrame(node: self.motionButtonNode, frame: CGRect(origin: CGPoint(x: ceil(layout.size.width / 2.0 + 10.0) + offset.x, y: layout.size.height - 49.0 - layout.intrinsicInsets.bottom - 54.0 + offset.y), size: buttonSize)) + transition.updateAlpha(node: self.motionButtonNode, alpha: alpha) } override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) - self.wrapperNode.frame = CGRect(origin: CGPoint(), size: layout.size) + self.wrapperNode.bounds = CGRect(origin: CGPoint(), size: layout.size) + self.wrapperNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0) + if self.cropNode.supernode == nil { self.imageNode.frame = self.wrapperNode.bounds self.blurredNode.frame = self.imageNode.frame @@ -389,5 +516,13 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.statusNode.frame = CGRect(x: layout.safeInsets.left + floorToScreenPixels((layout.size.width - layout.safeInsets.left - layout.safeInsets.right - progressDiameter) / 2.0), y: floorToScreenPixels((layout.size.height - progressDiameter) / 2.0), width: progressDiameter, height: progressDiameter) self.progressNode.frame = CGRect(x: layout.safeInsets.left + floorToScreenPixels((layout.size.width - layout.safeInsets.left - layout.safeInsets.right - progressDiameter) / 2.0), y: floorToScreenPixels((layout.size.height - 15.0) / 2.0), width: progressDiameter, height: progressDiameter) + + var offset: CGFloat = 0.0 + if let validOffset = self.validOffset { + offset = validOffset + } + self.updateButtonsLayout(layout: layout, offset: CGPoint(x: offset, y: 0.0), transition: transition) + + self.validLayout = layout } } diff --git a/TelegramUI/WallpaperListPreviewControllerNode.swift b/TelegramUI/WallpaperListPreviewControllerNode.swift index 72d83b5d34..9b47754ca6 100644 --- a/TelegramUI/WallpaperListPreviewControllerNode.swift +++ b/TelegramUI/WallpaperListPreviewControllerNode.swift @@ -232,13 +232,6 @@ private final class WallpaperBackgroundNode: ASDisplayNode { } }) - let controlsColorSignal: Signal - if case let .wallpaper(wallpaper) = wallpaper { - controlsColorSignal = chatBackgroundContrastColor(wallpaper: wallpaper, postbox: account.postbox) - } else { - controlsColorSignal = backgroundContrastColor(for: imagePromise.get()) - } - self.controlsColor.set(.single(.white) |> then(controlsColorSignal)) self.status.set(statusSignal) } @@ -506,20 +499,20 @@ final class WallpaperListPreviewControllerNode: ViewControllerTracingNode { if case let .wallpapers(wallpaperMode) = type, let mode = wallpaperMode { self.segmentedControl.selectedSegmentIndex = Int(clamping: mode.rawValue) } - case let .slug(slug, file): + case let .slug(slug, file, _): if let file = file { let entry = WallpaperEntry.wallpaper(.file(id: 0, accessHash: 0, isCreator: false, isDefault: false, slug: slug, file: file)) self.wallpapers = [entry] self.centralWallpaper = entry } self.ready.set(true) - case let .wallpaper(wallpaper): + case let .wallpaper(wallpaper, _): let entry = WallpaperEntry.wallpaper(wallpaper) self.wallpapers = [entry] self.centralWallpaper = entry self.ready.set(true) - case let .asset(asset, thumbnailImage): - let entry = WallpaperEntry.asset(asset, thumbnailImage) + case let .asset(asset): + let entry = WallpaperEntry.asset(asset, nil) self.wallpapers = [entry] self.centralWallpaper = entry self.ready.set(true)