diff --git a/Telegram-iOS.xcworkspace/contents.xcworkspacedata b/Telegram-iOS.xcworkspace/contents.xcworkspacedata
index b294c9cb24..176a24b7fc 100644
--- a/Telegram-iOS.xcworkspace/contents.xcworkspacedata
+++ b/Telegram-iOS.xcworkspace/contents.xcworkspacedata
@@ -551,6 +551,9 @@
+
+
{ get }
+ var updateTransitionWhenPresentedAsModal: ((CGFloat, ContainedViewLayoutTransition) -> Void)? { get set }
func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations
var deferScreenEdgeGestures: UIRectEdge { get }
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition)
func updateToInterfaceOrientation(_ orientation: UIInterfaceOrientation)
+ func updateModalTransition(_ value: CGFloat, transition: ContainedViewLayoutTransition)
func viewWillAppear(_ animated: Bool)
func viewWillDisappear(_ animated: Bool)
diff --git a/submodules/Display/Display/NavigationController.swift b/submodules/Display/Display/NavigationController.swift
index e5245c5089..92ec8f82e5 100644
--- a/submodules/Display/Display/NavigationController.swift
+++ b/submodules/Display/Display/NavigationController.swift
@@ -124,6 +124,8 @@ public enum MasterDetailLayoutBlackout : Equatable {
open class NavigationController: UINavigationController, ContainableController, UIGestureRecognizerDelegate {
public var isOpaqueWhenInOverlay: Bool = true
public var blocksBackgroundWhenInOverlay: Bool = true
+ public var isModalWhenInOverlay: Bool = false
+ public var updateTransitionWhenPresentedAsModal: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
private let _ready = Promise(true)
open var ready: Promise {
@@ -551,7 +553,7 @@ open class NavigationController: UINavigationController, ContainableController,
self.controllerView.containerView.addSubview(record.controller.view)
record.controller.setIgnoreAppearanceMethodInvocations(false)
- if let _ = previousControllers.index(where: { $0.controller === record.controller }) {
+ if let _ = previousControllers.firstIndex(where: { $0.controller === record.controller }) {
//previousControllers[index].transition = .appearance
let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.controllerView.containerView, topView: previousController.view, topNavigationBar: (previousController as? ViewController)?.navigationBar, bottomView: record.controller.view, bottomNavigationBar: (record.controller as? ViewController)?.navigationBar)
self.navigationTransitionCoordinator = navigationTransitionCoordinator
@@ -571,7 +573,7 @@ open class NavigationController: UINavigationController, ContainableController,
}
})
} else {
- if let index = self._viewControllers.index(where: { $0.controller === previousController }) {
+ if let index = self._viewControllers.firstIndex(where: { $0.controller === previousController }) {
self._viewControllers[index].transition = .appearance
}
let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Push, container: self.controllerView.containerView, topView: record.controller.view, topNavigationBar: (record.controller as? ViewController)?.navigationBar, bottomView: previousController.view, bottomNavigationBar: (previousController as? ViewController)?.navigationBar)
@@ -580,7 +582,7 @@ open class NavigationController: UINavigationController, ContainableController,
self.controllerView.inTransition = true
navigationTransitionCoordinator.animateCompletion(0.0, completion: { [weak self] in
if let strongSelf = self {
- if let index = strongSelf._viewControllers.index(where: { $0.controller === previousController }) {
+ if let index = strongSelf._viewControllers.firstIndex(where: { $0.controller === previousController }) {
strongSelf._viewControllers[index].transition = .none
}
strongSelf.navigationTransitionCoordinator = nil
@@ -713,7 +715,7 @@ open class NavigationController: UINavigationController, ContainableController,
self.loadView()
}
self.validLayout = layout
- transition.updateFrame(view: self.view, frame: CGRect(origin: self.view.frame.origin, size: layout.size))
+ //transition.updateFrame(view: self.view, frame: CGRect(origin: self.view.frame.origin, size: layout.size))
self.updateControllerLayouts(previousControllers: self._viewControllers, layout: layout, transition: transition)
@@ -732,6 +734,33 @@ open class NavigationController: UINavigationController, ContainableController,
}
}
+ private var modalTransition: CGFloat = 0.0
+
+ public func updateModalTransition(_ value: CGFloat, transition: ContainedViewLayoutTransition) {
+ if self.modalTransition == value {
+ return
+ }
+ let scale = (self.view.bounds.width - 20.0 * 2.0) / self.view.bounds.width
+ let cornerRadius = value * 10.0 / scale
+ switch transition {
+ case let .animated(duration, curve):
+ let previous = self.displayNode.layer.cornerRadius
+ self.displayNode.layer.cornerRadius = cornerRadius
+ if !cornerRadius.isZero {
+ self.displayNode.clipsToBounds = true
+ }
+ self.displayNode.layer.animate(from: previous as NSNumber, to: cornerRadius as NSNumber, keyPath: "cornerRadius", timingFunction: curve.timingFunction, duration: duration, completion: { [weak self] _ in
+ if cornerRadius.isZero {
+ self?.displayNode.clipsToBounds = false
+ }
+ })
+ case .immediate:
+ self.displayNode.layer.cornerRadius = cornerRadius
+ self.displayNode.clipsToBounds = !cornerRadius.isZero
+ }
+ self.modalTransition = value
+ }
+
public func updateToInterfaceOrientation(_ orientation: UIInterfaceOrientation) {
for record in self._viewControllers {
if let controller = record.controller as? ContainableController {
diff --git a/submodules/Display/Display/PresentationContext.swift b/submodules/Display/Display/PresentationContext.swift
index 6421425520..3f81254d34 100644
--- a/submodules/Display/Display/PresentationContext.swift
+++ b/submodules/Display/Display/PresentationContext.swift
@@ -34,9 +34,8 @@ public final class PresentationContext {
}
}
- weak var volumeControlStatusBarNodeView: UIView?
-
var updateIsInteractionBlocked: ((Bool) -> Void)?
+ var updateHasBlocked: ((Bool) -> Void)?
var updateHasOpaqueOverlay: ((Bool) -> Void)?
private(set) var hasOpaqueOverlay: Bool = false {
@@ -47,6 +46,9 @@ public final class PresentationContext {
}
}
+ private var modalPresentationValue: CGFloat = 0.0
+ var updateModalTransition: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
+
private var layout: ContainerViewLayout?
private var ready: Bool {
@@ -120,6 +122,18 @@ public final class PresentationContext {
}
}
+ private func layoutForController(containerLayout: ContainerViewLayout, controller: ContainableController) -> (ContainerViewLayout, CGRect) {
+ if controller.isModalWhenInOverlay {
+ let topInset = (containerLayout.statusBarHeight ?? 0.0) + 20.0
+ var updatedLayout = containerLayout
+ updatedLayout.statusBarHeight = nil
+ updatedLayout.size.height -= topInset
+ return (updatedLayout, CGRect(origin: CGPoint(x: 0.0, y: topInset), size: updatedLayout.size))
+ } else {
+ return (containerLayout, CGRect(origin: CGPoint(), size: containerLayout.size))
+ }
+ }
+
public func present(_ controller: ContainableController, on level: PresentationSurfaceLevel, blockInteraction: Bool = false, completion: @escaping () -> Void) {
let controllerReady = controller.ready.get()
|> filter({ $0 })
@@ -140,8 +154,9 @@ public final class PresentationContext {
controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: orientations, compactSize: orientations)
}
}
- controller.view.frame = CGRect(origin: CGPoint(), size: initialLayout.size)
- controller.containerLayoutUpdated(initialLayout, transition: .immediate)
+ let (controllerLayout, controllerFrame) = self.layoutForController(containerLayout: initialLayout, controller: controller)
+ controller.view.frame = controllerFrame
+ controller.containerLayoutUpdated(controllerLayout, transition: .immediate)
var blockInteractionToken: Int?
if blockInteraction {
blockInteractionToken = self.addBlockInteraction()
@@ -170,37 +185,32 @@ public final class PresentationContext {
}
strongSelf.controllers.insert((controller, level), at: insertIndex ?? strongSelf.controllers.count)
if let view = strongSelf.view, let layout = strongSelf.layout {
+ let (updatedControllerLayout, updatedControllerFrame) = strongSelf.layoutForController(containerLayout: layout, controller: controller)
+
(controller as? UIViewController)?.navigation_setDismiss({ [weak controller] in
if let strongSelf = self, let controller = controller {
strongSelf.dismiss(controller)
}
}, rootController: nil)
(controller as? UIViewController)?.setIgnoreAppearanceMethodInvocations(true)
- if layout != initialLayout {
- controller.view.frame = CGRect(origin: CGPoint(), size: layout.size)
+ if updatedControllerLayout != controllerLayout {
+ controller.view.frame = updatedControllerFrame
if let topLevelSubview = strongSelf.topLevelSubview(for: level) {
view.insertSubview(controller.view, belowSubview: topLevelSubview)
} else {
- if let volumeControlStatusBarNodeView = strongSelf.volumeControlStatusBarNodeView {
- view.insertSubview(controller.view, belowSubview: volumeControlStatusBarNodeView)
- } else {
- view.addSubview(controller.view)
- }
+ view.addSubview(controller.view)
}
- controller.containerLayoutUpdated(layout, transition: .immediate)
+ controller.containerLayoutUpdated(updatedControllerLayout, transition: .immediate)
} else {
if let topLevelSubview = strongSelf.topLevelSubview(for: level) {
view.insertSubview(controller.view, belowSubview: topLevelSubview)
} else {
- if let volumeControlStatusBarNodeView = strongSelf.volumeControlStatusBarNodeView {
- view.insertSubview(controller.view, belowSubview: volumeControlStatusBarNodeView)
- } else {
- view.addSubview(controller.view)
- }
+ view.addSubview(controller.view)
}
}
(controller as? UIViewController)?.setIgnoreAppearanceMethodInvocations(false)
view.layer.invalidateUpTheTree()
+ strongSelf.updateViews()
controller.viewWillAppear(false)
if let controller = controller as? PresentableController {
controller.viewDidAppear(completion: { [weak self] in
@@ -211,7 +221,6 @@ public final class PresentationContext {
strongSelf.notifyAccessibilityScreenChanged()
}
}
- strongSelf.updateViews()
}
}))
} else {
@@ -225,7 +234,7 @@ public final class PresentationContext {
}
private func dismiss(_ controller: ContainableController) {
- if let index = self.controllers.index(where: { $0.0 === controller }) {
+ if let index = self.controllers.firstIndex(where: { $0.0 === controller }) {
self.controllers.remove(at: index)
controller.viewWillDisappear(false)
controller.view.removeFromSuperview()
@@ -242,7 +251,9 @@ public final class PresentationContext {
self.readyChanged(wasReady: wasReady)
} else if self.ready {
for (controller, _) in self.controllers {
- controller.containerLayoutUpdated(layout, transition: transition)
+ let (controllerLayout, controllerFrame) = self.layoutForController(containerLayout: layout, controller: controller)
+ controller.view.frame = controllerFrame
+ controller.containerLayoutUpdated(controllerLayout, transition: transition)
}
}
}
@@ -262,14 +273,11 @@ public final class PresentationContext {
if let topLevelSubview = self.topLevelSubview {
view.insertSubview(controller.view, belowSubview: topLevelSubview)
} else {
- if let volumeControlStatusBarNodeView = self.volumeControlStatusBarNodeView {
- view.insertSubview(controller.view, belowSubview: volumeControlStatusBarNodeView)
- } else {
- view.addSubview(controller.view)
- }
+ view.addSubview(controller.view)
}
- controller.view.frame = CGRect(origin: CGPoint(), size: layout.size)
- controller.containerLayoutUpdated(layout, transition: .immediate)
+ let (controllerLayout, controllerFrame) = self.layoutForController(containerLayout: layout, controller: controller)
+ controller.view.frame = controllerFrame
+ controller.containerLayoutUpdated(controllerLayout, transition: .immediate)
if let controller = controller as? PresentableController {
controller.viewDidAppear(completion: { [weak self] in
self?.notifyAccessibilityScreenChanged()
@@ -291,10 +299,18 @@ public final class PresentationContext {
}
}
+ private weak var currentModalController: ContainableController?
+
private func updateViews() {
self.hasOpaqueOverlay = self.currentlyBlocksBackgroundWhenInOverlay
+ var modalController: ContainableController?
var topHasOpaque = false
for (controller, _) in self.controllers.reversed() {
+ if controller.isModalWhenInOverlay {
+ if modalController == nil {
+ modalController = controller
+ }
+ }
if topHasOpaque {
controller.displayNode.accessibilityElementsHidden = true
} else {
@@ -304,16 +320,47 @@ public final class PresentationContext {
controller.displayNode.accessibilityElementsHidden = false
}
}
+
+ if self.currentModalController !== modalController {
+ if let currentModalController = self.currentModalController {
+ currentModalController.updateTransitionWhenPresentedAsModal = nil
+ if #available(iOSApplicationExtension 11.0, *) {
+ currentModalController.displayNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner]
+ }
+ currentModalController.displayNode.layer.cornerRadius = 0.0
+ }
+ self.currentModalController = modalController
+ if let modalController = modalController {
+ if #available(iOSApplicationExtension 11.0, *) {
+ modalController.displayNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
+ }
+ modalController.displayNode.layer.cornerRadius = 10.0
+ modalController.updateTransitionWhenPresentedAsModal = { [weak self, weak modalController] value, transition in
+ guard let strongSelf = self, let modalController = modalController, modalController === strongSelf.currentModalController else {
+ return
+ }
+ if strongSelf.modalPresentationValue != value {
+ strongSelf.modalPresentationValue = value
+ strongSelf.updateModalTransition?(value, transition)
+ }
+ }
+ } else {
+ if self.modalPresentationValue != 0.0 {
+ self.modalPresentationValue = 0.0
+ self.updateModalTransition?(0.0, .animated(duration: 0.3, curve: .spring))
+ }
+ }
+ }
}
private func notifyAccessibilityScreenChanged() {
UIAccessibility.post(notification: UIAccessibility.Notification.screenChanged, argument: nil)
}
- func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
+ func hitTest(view: UIView, point: CGPoint, with event: UIEvent?) -> UIView? {
for (controller, _) in self.controllers.reversed() {
if controller.isViewLoaded {
- if let result = controller.view.hitTest(point, with: event) {
+ if let result = controller.view.hitTest(view.convert(point, to: controller.view), with: event) {
return result
}
}
diff --git a/submodules/Display/Display/StatusBarHost.swift b/submodules/Display/Display/StatusBarHost.swift
index 35a7299d4e..a6653246e3 100644
--- a/submodules/Display/Display/StatusBarHost.swift
+++ b/submodules/Display/Display/StatusBarHost.swift
@@ -3,7 +3,7 @@ import SwiftSignalKit
public protocol StatusBarHost {
var statusBarFrame: CGRect { get }
- var statusBarStyle: UIStatusBarStyle { get set }
+ var statusBarStyle: UIStatusBarStyle { get }
var statusBarWindow: UIView? { get }
var statusBarView: UIView? { get }
@@ -11,4 +11,6 @@ public protocol StatusBarHost {
var keyboardView: UIView? { get }
var handleVolumeControl: Signal { get }
+
+ func setStatusBarStyle(_ style: UIStatusBarStyle, animated: Bool)
}
diff --git a/submodules/Display/Display/StatusBarManager.swift b/submodules/Display/Display/StatusBarManager.swift
index b051951bfd..1bcb1efe08 100644
--- a/submodules/Display/Display/StatusBarManager.swift
+++ b/submodules/Display/Display/StatusBarManager.swift
@@ -76,7 +76,7 @@ class StatusBarManager {
private let volumeControlStatusBarNode: VolumeControlStatusBarNode
private var surfaces: [StatusBarSurface] = []
- private var validParams: (withSafeInsets: Bool, forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool)?
+ private var validParams: (withSafeInsets: Bool, forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool, UIStatusBarStyle?)?
var inCallNavigate: (() -> Void)?
@@ -111,8 +111,8 @@ class StatusBarManager {
self?.volumeControlStatusBarNode.allowsGroupOpacity = false
})
}
- if let (withSafeInsets, forceInCallStatusBarText, forceHiddenBySystemWindows) = self.validParams {
- self.updateSurfaces(self.surfaces, withSafeInsets: withSafeInsets, forceInCallStatusBarText: forceInCallStatusBarText, forceHiddenBySystemWindows: forceHiddenBySystemWindows, animated: false, alphaTransition: .animated(duration: 0.2, curve: .easeInOut))
+ if let (withSafeInsets, forceInCallStatusBarText, forceHiddenBySystemWindows, forceAppearance) = self.validParams {
+ self.updateSurfaces(self.surfaces, withSafeInsets: withSafeInsets, forceInCallStatusBarText: forceInCallStatusBarText, forceHiddenBySystemWindows: forceHiddenBySystemWindows, animated: false, forceAppearance: forceAppearance, alphaTransition: .animated(duration: 0.2, curve: .easeInOut))
}
}
@@ -126,24 +126,24 @@ class StatusBarManager {
}
})
self.volumeTimer = nil
- if let (withSafeInsets, forceInCallStatusBarText, forceHiddenBySystemWindows) = self.validParams {
- self.updateSurfaces(self.surfaces, withSafeInsets: withSafeInsets, forceInCallStatusBarText: forceInCallStatusBarText, forceHiddenBySystemWindows: forceHiddenBySystemWindows, animated: false, alphaTransition: .animated(duration: 0.2, curve: .easeInOut))
+ if let (withSafeInsets, forceInCallStatusBarText, forceHiddenBySystemWindows, forceAppearance) = self.validParams {
+ self.updateSurfaces(self.surfaces, withSafeInsets: withSafeInsets, forceInCallStatusBarText: forceInCallStatusBarText, forceHiddenBySystemWindows: forceHiddenBySystemWindows, animated: false, forceAppearance: forceAppearance, alphaTransition: .animated(duration: 0.2, curve: .easeInOut))
}
}
- func updateState(surfaces: [StatusBarSurface], withSafeInsets: Bool, forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool, animated: Bool) {
+ func updateState(surfaces: [StatusBarSurface], withSafeInsets: Bool, forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool, forceAppearance: UIStatusBarStyle?, animated: Bool) {
let previousSurfaces = self.surfaces
self.surfaces = surfaces
- self.updateSurfaces(previousSurfaces, withSafeInsets: withSafeInsets, forceInCallStatusBarText: forceInCallStatusBarText, forceHiddenBySystemWindows: forceHiddenBySystemWindows, animated: animated, alphaTransition: .immediate)
+ self.updateSurfaces(previousSurfaces, withSafeInsets: withSafeInsets, forceInCallStatusBarText: forceInCallStatusBarText, forceHiddenBySystemWindows: forceHiddenBySystemWindows, animated: animated, forceAppearance: forceAppearance, alphaTransition: .immediate)
}
- private func updateSurfaces(_ previousSurfaces: [StatusBarSurface], withSafeInsets: Bool, forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool, animated: Bool, alphaTransition: ContainedViewLayoutTransition) {
+ private func updateSurfaces(_ previousSurfaces: [StatusBarSurface], withSafeInsets: Bool, forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool, animated: Bool, forceAppearance: UIStatusBarStyle?, alphaTransition: ContainedViewLayoutTransition) {
let statusBarFrame = self.host.statusBarFrame
guard let statusBarView = self.host.statusBarView else {
return
}
- self.validParams = (withSafeInsets, forceInCallStatusBarText, forceHiddenBySystemWindows)
+ self.validParams = (withSafeInsets, forceInCallStatusBarText, forceHiddenBySystemWindows, forceAppearance)
if self.host.statusBarWindow?.isUserInteractionEnabled != (forceInCallStatusBarText == nil) {
self.host.statusBarWindow?.isUserInteractionEnabled = (forceInCallStatusBarText == nil)
@@ -278,6 +278,17 @@ class StatusBarManager {
}
self.volumeControlStatusBarNode.isDark = isDark
+ if let forceAppearance = forceAppearance {
+ let style: StatusBarStyle
+ switch forceAppearance {
+ case .lightContent:
+ style = .White
+ default:
+ style = .Black
+ }
+ globalStatusBar = (style, 1.0, 0.0)
+ }
+
if let globalStatusBar = globalStatusBar, !forceHiddenBySystemWindows {
let statusBarStyle: UIStatusBarStyle
if forceInCallStatusBarText != nil {
@@ -286,7 +297,7 @@ class StatusBarManager {
statusBarStyle = globalStatusBar.0 == .Black ? .default : .lightContent
}
if self.host.statusBarStyle != statusBarStyle {
- self.host.statusBarStyle = statusBarStyle
+ self.host.setStatusBarStyle(statusBarStyle, animated: animated)
}
if let statusBarWindow = self.host.statusBarWindow {
alphaTransition.updateAlpha(layer: statusBarView.layer, alpha: globalStatusBar.1)
diff --git a/submodules/Display/Display/ViewController.swift b/submodules/Display/Display/ViewController.swift
index 3d7dc74efb..f2a54e65a8 100644
--- a/submodules/Display/Display/ViewController.swift
+++ b/submodules/Display/Display/ViewController.swift
@@ -88,6 +88,14 @@ open class ViewControllerPresentationArguments {
public final var isOpaqueWhenInOverlay: Bool = false
public final var blocksBackgroundWhenInOverlay: Bool = false
public final var automaticallyControlPresentationContextLayout: Bool = true
+ public final var isModalWhenInOverlay: Bool = false {
+ didSet {
+ if self.isNodeLoaded {
+ self.displayNode.clipsToBounds = true
+ }
+ }
+ }
+ public var updateTransitionWhenPresentedAsModal: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
public func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations {
return self.supportedOrientations
@@ -190,7 +198,7 @@ open class ViewControllerPresentationArguments {
open var visualNavigationInsetHeight: CGFloat {
if let navigationBar = self.navigationBar {
- var height = navigationBar.frame.maxY
+ let height = navigationBar.frame.maxY
if let contentNode = navigationBar.contentNode, case .expansion = contentNode.mode {
//height += contentNode.height
}
@@ -233,7 +241,7 @@ open class ViewControllerPresentationArguments {
private func updateScrollToTopView() {
if self.scrollToTop != nil {
if let displayNode = self._displayNode , self.scrollToTopView == nil {
- let scrollToTopView = ScrollToTopView(frame: CGRect(x: 0.0, y: -1.0, width: displayNode.frame.size.width, height: 1.0))
+ let scrollToTopView = ScrollToTopView(frame: CGRect(x: 0.0, y: -1.0, width: displayNode.bounds.size.width, height: 1.0))
scrollToTopView.action = { [weak self] in
if let scrollToTop = self?.scrollToTop {
scrollToTop()
@@ -293,16 +301,16 @@ open class ViewControllerPresentationArguments {
private func updateNavigationBarLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0
- let navigationBarHeight: CGFloat = max(20.0, statusBarHeight) + (self.navigationBar?.contentHeight ?? 44.0)
+ let navigationBarHeight: CGFloat = statusBarHeight + (self.navigationBar?.contentHeight ?? 44.0)
let navigationBarOffset: CGFloat
if statusBarHeight.isZero {
- navigationBarOffset = -20.0
+ navigationBarOffset = 0.0
} else {
navigationBarOffset = 0.0
}
var navigationBarFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationBarOffset), size: CGSize(width: layout.size.width, height: navigationBarHeight))
if layout.statusBarHeight == nil {
- navigationBarFrame.size.height = (self.navigationBar?.contentHeight ?? 44.0) + 20.0
+ //navigationBarFrame.size.height = (self.navigationBar?.contentHeight ?? 44.0) + 20.0
}
if !self.displayNavigationBar {
@@ -332,7 +340,7 @@ open class ViewControllerPresentationArguments {
if !self.isViewLoaded {
self.loadView()
}
- transition.updateFrame(node: self.displayNode, frame: CGRect(origin: self.view.frame.origin, size: layout.size))
+ //transition.updateFrame(node: self.displayNode, frame: CGRect(origin: self.view.frame.origin, size: layout.size))
if let _ = layout.statusBarHeight {
self.statusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 40.0))
}
@@ -348,6 +356,10 @@ open class ViewControllerPresentationArguments {
}
}
+ open func updateModalTransition(_ value: CGFloat, transition: ContainedViewLayoutTransition) {
+
+ }
+
open func navigationStackConfigurationUpdated(next: [ViewController]) {
}
@@ -377,6 +389,10 @@ open class ViewControllerPresentationArguments {
self.blocksBackgroundWhenInOverlay = true
self.isOpaqueWhenInOverlay = true
}
+
+ if self.isModalWhenInOverlay {
+ self.displayNode.clipsToBounds = true
+ }
}
public func requestLayout(transition: ContainedViewLayoutTransition) {
@@ -580,7 +596,7 @@ private func traceViewVisibility(view: UIView, rect: CGRect) -> Bool {
if view.window == nil {
return false
}
- if let index = siblings.index(where: { $0 === view.layer }) {
+ if let index = siblings.firstIndex(where: { $0 === view.layer }) {
let viewFrame = view.convert(rect, to: superview)
for i in (index + 1) ..< siblings.count {
if siblings[i].frame.contains(viewFrame) {
diff --git a/submodules/Display/Display/WindowContent.swift b/submodules/Display/Display/WindowContent.swift
index 181fd6f4ca..cf0342181e 100644
--- a/submodules/Display/Display/WindowContent.swift
+++ b/submodules/Display/Display/WindowContent.swift
@@ -344,10 +344,6 @@ public class Window1 {
private var isInteractionBlocked = false
- /*private var accessibilityElements: [Any]? {
- return self.viewController?.view.accessibilityElements
- }*/
-
public init(hostView: WindowHostView, statusBarHost: StatusBarHost?) {
self.hostView = hostView
@@ -377,6 +373,8 @@ public class Window1 {
self.presentationContext = PresentationContext()
self.overlayPresentationContext = GlobalOverlayPresentationContext(statusBarHost: statusBarHost, parentView: self.hostView.aboveStatusBarView)
+ self.presentationContextContainerView = UIView()
+
self.presentationContext.updateIsInteractionBlocked = { [weak self] value in
self?.isInteractionBlocked = value
}
@@ -385,6 +383,10 @@ public class Window1 {
self?._rootController?.displayNode.accessibilityElementsHidden = value
}
+ self.presentationContext.updateModalTransition = { [weak self] value, transition in
+ self?.updateModalTransition(value, transition: transition)
+ }
+
self.hostView.present = { [weak self] controller, level, blockInteraction, completion in
self?.present(controller, on: level, blockInteraction: blockInteraction, completion: completion)
}
@@ -440,12 +442,7 @@ public class Window1 {
})
}
- /*self.hostView.getAccessibilityElements = { [weak self] in
- return self?.accessibilityElements
- }*/
-
- self.presentationContext.view = self.hostView.containerView
- self.presentationContext.volumeControlStatusBarNodeView = self.volumeControlStatusBarNode.view
+ self.presentationContext.view = self.presentationContextContainerView
self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout, hasOnScreenNavigation: self.hostView.hasOnScreenNavigation), transition: .immediate)
self.overlayPresentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout, hasOnScreenNavigation: self.hostView.hasOnScreenNavigation), transition: .immediate)
@@ -567,6 +564,8 @@ public class Window1 {
self.windowPanRecognizer = recognizer
self.hostView.containerView.addGestureRecognizer(recognizer)
+ self.hostView.containerView.addSubview(self.presentationContextContainerView)
+
self.hostView.containerView.addSubview(self.volumeControlStatusBar)
self.hostView.containerView.addSubview(self.volumeControlStatusBarNode.view)
}
@@ -668,7 +667,7 @@ public class Window1 {
}
}
- if let result = self.presentationContext.hitTest(point, with: event) {
+ if let result = self.presentationContext.hitTest(view: self.hostView.containerView, point: point, with: event) {
return result
}
return self.viewController?.view.hitTest(point, with: event)
@@ -697,7 +696,19 @@ public class Window1 {
if let rootController = self._rootController {
if !self.windowLayout.size.width.isZero && !self.windowLayout.size.height.isZero {
- rootController.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout, hasOnScreenNavigation: self.hostView.hasOnScreenNavigation), transition: .immediate)
+ let rootLayout = containedLayoutForWindowLayout(self.windowLayout, hasOnScreenNavigation: self.hostView.hasOnScreenNavigation)
+ var positionOffset: CGFloat = 0.0
+ if !self.appliedModalLevel.isZero {
+ let scale = ((rootLayout.size.width - 20.0 * 2.0) / rootLayout.size.width)
+ rootController.displayNode.transform = CATransform3DMakeScale(scale, scale, 1.0)
+ let transformedUpperBound = (self.windowLayout.size.height - rootLayout.size.height * scale) / 2.0
+ let targetBound = (self.windowLayout.statusBarHeight ?? 0.0) + 20.0 - 10.0
+ positionOffset = targetBound - transformedUpperBound
+ }
+ rootController.displayNode.position = CGPoint(x: self.windowLayout.size.width / 2.0, y: self.windowLayout.size.height / 2.0 + positionOffset)
+ rootController.displayNode.bounds = CGRect(origin: CGPoint(), size: rootLayout.size)
+ rootController.containerLayoutUpdated(rootLayout, transition: .immediate)
+ rootController.updateModalTransition(self.appliedModalLevel, transition: .immediate)
}
self.hostView.containerView.insertSubview(rootController.view, at: 0)
@@ -707,6 +718,26 @@ public class Window1 {
}
}
+ private func insertContentViewAtTop(_ view: UIView) {
+ if let dimView = self.dimView {
+ self.hostView.containerView.insertSubview(view, belowSubview: dimView)
+ } else {
+ self.hostView.containerView.insertSubview(view, belowSubview: self.presentationContextContainerView)
+ }
+ }
+
+ private func insertCoveringView(_ view: UIView) {
+ self.hostView.containerView.insertSubview(view, belowSubview: self.volumeControlStatusBarNode.view)
+ }
+
+ private func insertDimView(_ view: UIView) {
+ if let coveringView = self.coveringView {
+ self.hostView.containerView.insertSubview(view, belowSubview: coveringView)
+ } else {
+ self.hostView.containerView.insertSubview(view, belowSubview: self.presentationContextContainerView)
+ }
+ }
+
private var _topLevelOverlayControllers: [ContainableController] = []
public var topLevelOverlayControllers: [ContainableController] {
get {
@@ -721,18 +752,17 @@ public class Window1 {
let layout = containedLayoutForWindowLayout(self.windowLayout, hasOnScreenNavigation: self.hostView.hasOnScreenNavigation)
for controller in self._topLevelOverlayControllers {
controller.containerLayoutUpdated(layout, transition: .immediate)
-
- if let coveringView = self.coveringView {
- self.hostView.containerView.insertSubview(controller.view, belowSubview: coveringView)
- } else {
- self.hostView.containerView.insertSubview(controller.view, belowSubview: self.volumeControlStatusBarNode.view)
- }
+ self.insertContentViewAtTop(controller.view)
}
self.presentationContext.topLevelSubview = self._topLevelOverlayControllers.first?.view
}
}
+ private var dimView: UIView? = nil
+
+ private let presentationContextContainerView: UIView
+
public var coveringView: WindowCoveringView? {
didSet {
if self.coveringView !== oldValue {
@@ -746,7 +776,7 @@ public class Window1 {
coveringView.layer.removeAnimation(forKey: "opacity")
coveringView.layer.allowsGroupOpacity = false
coveringView.alpha = 1.0
- self.hostView.containerView.insertSubview(coveringView, belowSubview: self.volumeControlStatusBarNode.view)
+ self.insertCoveringView(coveringView)
if !self.windowLayout.size.width.isZero {
coveringView.frame = CGRect(origin: CGPoint(), size: self.windowLayout.size)
coveringView.updateLayout(self.windowLayout.size)
@@ -756,6 +786,8 @@ public class Window1 {
}
}
+ private var appliedModalLevel: CGFloat = 0.0
+
private func layoutSubviews() {
var hasPreview = false
var updatedHasPreview = false
@@ -771,11 +803,19 @@ public class Window1 {
updatedHasPreview = true
}
- if self.tracingStatusBarsInvalidated || updatedHasPreview, let statusBarManager = statusBarManager, let keyboardManager = keyboardManager {
+ var modalLevelUpdated = false
+ if self.appliedModalLevel != self.modalTransition {
+ modalLevelUpdated = self.appliedModalLevel.isZero != self.modalTransition.isZero
+ self.appliedModalLevel = self.modalTransition
+ }
+
+ if self.tracingStatusBarsInvalidated || updatedHasPreview || modalLevelUpdated, let statusBarManager = statusBarManager, let keyboardManager = keyboardManager {
self.tracingStatusBarsInvalidated = false
if self.statusBarHidden {
- statusBarManager.updateState(surfaces: [], withSafeInsets: false, forceInCallStatusBarText: nil, forceHiddenBySystemWindows: false, animated: false)
+ statusBarManager.updateState(surfaces: [], withSafeInsets: false, forceInCallStatusBarText: nil, forceHiddenBySystemWindows: false, forceAppearance: nil, animated: false)
+ } else if !self.modalTransition.isZero || self.isInTransitionOutOfModal {
+ statusBarManager.updateState(surfaces: [], withSafeInsets: false, forceInCallStatusBarText: nil, forceHiddenBySystemWindows: false, forceAppearance: self.isInTransitionOutOfModal ? .default : .lightContent, animated: modalLevelUpdated)
} else {
var statusBarSurfaces: [StatusBarSurface] = []
for layers in self.hostView.containerView.layer.traceableLayerSurfaces(withTag: WindowTracingTags.statusBar) {
@@ -796,7 +836,7 @@ public class Window1 {
}
}
self.cachedWindowSubviewCount = self.hostView.containerView.window?.subviews.count ?? 0
- statusBarManager.updateState(surfaces: statusBarSurfaces, withSafeInsets: !self.windowLayout.safeInsets.top.isZero, forceInCallStatusBarText: self.forceInCallStatusBarText, forceHiddenBySystemWindows: hasPreview, animated: animatedUpdate)
+ statusBarManager.updateState(surfaces: statusBarSurfaces, withSafeInsets: !self.windowLayout.safeInsets.top.isZero, forceInCallStatusBarText: self.forceInCallStatusBarText, forceHiddenBySystemWindows: hasPreview, forceAppearance: nil, animated: animatedUpdate || modalLevelUpdated)
}
var keyboardSurfaces: [KeyboardSurface] = []
@@ -969,13 +1009,31 @@ public class Window1 {
let childLayoutUpdated = self.updatedContainerLayout != childLayout
self.updatedContainerLayout = childLayout
+ updatingLayout.transition.updateFrame(view: self.presentationContextContainerView, frame: CGRect(origin: CGPoint(), size: self.windowLayout.size))
+ if let dimView = self.dimView {
+ updatingLayout.transition.updateFrame(view: dimView, frame: CGRect(origin: CGPoint(), size: self.windowLayout.size))
+ }
+
if childLayoutUpdated {
var rootLayout = childLayout
let rootTransition = updatingLayout.transition
if self.presentationContext.isCurrentlyOpaque {
rootLayout.inputHeight = nil
}
- self._rootController?.containerLayoutUpdated(rootLayout, transition: rootTransition)
+ var positionOffset: CGFloat = 0.0
+ if let rootController = self._rootController {
+ if !self.modalTransition.isZero {
+ let scale = (rootLayout.size.width - 20.0 * 2.0) / rootLayout.size.width
+ rootTransition.updateTransformScale(node: rootController.displayNode, scale: scale)
+
+ let transformedUpperBound = (self.windowLayout.size.height - rootLayout.size.height * scale) / 2.0
+ let targetBound = (self.windowLayout.statusBarHeight ?? 0.0) + 20.0 - 10.0
+ positionOffset = targetBound - transformedUpperBound
+ }
+ rootTransition.updatePosition(node: rootController.displayNode, position: CGPoint(x: self.windowLayout.size.width / 2.0, y: self.windowLayout.size.height / 2.0 + positionOffset))
+ rootTransition.updateBounds(node: rootController.displayNode, bounds: CGRect(origin: CGPoint(), size: rootLayout.size))
+ rootController.containerLayoutUpdated(rootLayout, transition: rootTransition)
+ }
self.presentationContext.containerLayoutUpdated(childLayout, transition: updatingLayout.transition)
self.overlayPresentationContext.containerLayoutUpdated(childLayout, transition: updatingLayout.transition)
@@ -1008,6 +1066,62 @@ public class Window1 {
}
}
+ private var modalTransition: CGFloat = 0.0
+ private var defaultBackgroundColor: UIColor?
+ private var isInTransitionOutOfModal: Bool = false
+
+ private func updateModalTransition(_ value: CGFloat, transition: ContainedViewLayoutTransition) {
+ self.modalTransition = value
+ if !value.isZero {
+ if self.dimView == nil {
+ let dimView = UIView()
+ dimView.frame = CGRect(origin: CGPoint(), size: self.windowLayout.size)
+ dimView.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
+ self.dimView = dimView
+ self.insertDimView(dimView)
+ dimView.alpha = 0.0
+ transition.updateAlpha(layer: dimView.layer, alpha: 1.0)
+
+ self.defaultBackgroundColor = self.hostView.containerView.backgroundColor
+ self.hostView.containerView.backgroundColor = .black
+
+ var positionOffset: CGFloat = 0.0
+ if let rootController = self._rootController {
+ let rootSize = rootController.displayNode.bounds.size
+ let scale = (rootSize.width - 20.0 * 2.0) / rootSize.width
+ transition.updateTransformScale(node: rootController.displayNode, scale: scale)
+
+ let transformedUpperBound = (self.windowLayout.size.height - rootSize.height * scale) / 2.0
+ let targetBound = (self.windowLayout.statusBarHeight ?? 0.0) + 20.0 - 10.0
+ positionOffset = targetBound - transformedUpperBound
+
+ transition.updatePosition(node: rootController.displayNode, position: CGPoint(x: self.windowLayout.size.width / 2.0, y: self.windowLayout.size.height / 2.0 + positionOffset))
+ rootController.updateModalTransition(value, transition: transition)
+ }
+ self.layoutSubviews()
+ }
+ } else {
+ if let dimView = self.dimView {
+ self.dimView = nil
+ self.isInTransitionOutOfModal = true
+ transition.updateAlpha(layer: dimView.layer, alpha: 0.0, completion: { [weak self, weak dimView] _ in
+ dimView?.removeFromSuperview()
+ if let strongSelf = self {
+ strongSelf.hostView.containerView.backgroundColor = strongSelf.defaultBackgroundColor
+ strongSelf.isInTransitionOutOfModal = false
+ strongSelf.layoutSubviews()
+ }
+ })
+ if let rootController = self._rootController {
+ transition.updateTransformScale(node: rootController.displayNode, scale: 1.0)
+ transition.updatePosition(node: rootController.displayNode, position: CGPoint(x: self.windowLayout.size.width / 2.0, y: self.windowLayout.size.height / 2.0))
+ rootController.updateModalTransition(0.0, transition: transition)
+ }
+ self.layoutSubviews()
+ }
+ }
+ }
+
public func present(_ controller: ContainableController, on level: PresentationSurfaceLevel, blockInteraction: Bool = false, completion: @escaping () -> Void = {}) {
self.presentationContext.present(controller, on: level, blockInteraction: blockInteraction, completion: completion)
}
diff --git a/submodules/ItemListUI/Sources/ItemListController.swift b/submodules/ItemListUI/Sources/ItemListController.swift
index 81344e6c05..25ebf981ea 100644
--- a/submodules/ItemListUI/Sources/ItemListController.swift
+++ b/submodules/ItemListUI/Sources/ItemListController.swift
@@ -230,6 +230,7 @@ open class ItemListController: ViewController, KeyShor
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: theme), strings: NavigationBarStrings(presentationStrings: strings)))
self.isOpaqueWhenInOverlay = true
+ self.isModalWhenInOverlay = true
self.blocksBackgroundWhenInOverlay = true
self.statusBar.statusBarStyle = theme.rootController.statusBarStyle.style
@@ -478,6 +479,7 @@ open class ItemListController: ViewController, KeyShor
presentationArguments.completion?()
completion()
})
+ self.updateTransitionWhenPresentedAsModal?(1.0, .animated(duration: 0.5, curve: .spring))
} else {
completion()
}
@@ -506,6 +508,7 @@ open class ItemListController: ViewController, KeyShor
if !self.isDismissed {
self.isDismissed = true
(self.displayNode as! ItemListControllerNode).animateOut(completion: completion)
+ self.updateTransitionWhenPresentedAsModal?(0.0, .animated(duration: 0.2, curve: .easeInOut))
}
}
diff --git a/submodules/MessageReactionListUI/Info.plist b/submodules/MessageReactionListUI/Info.plist
new file mode 100644
index 0000000000..e1fe4cfb7b
--- /dev/null
+++ b/submodules/MessageReactionListUI/Info.plist
@@ -0,0 +1,22 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+
+
diff --git a/submodules/MessageReactionListUI/MessageReactionListUI_Xcode.xcodeproj/project.pbxproj b/submodules/MessageReactionListUI/MessageReactionListUI_Xcode.xcodeproj/project.pbxproj
new file mode 100644
index 0000000000..63e8bf9027
--- /dev/null
+++ b/submodules/MessageReactionListUI/MessageReactionListUI_Xcode.xcodeproj/project.pbxproj
@@ -0,0 +1,571 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 50;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ D072F36823154C230009E66F /* MessageReactionListUI.h in Headers */ = {isa = PBXBuildFile; fileRef = D072F36623154C230009E66F /* MessageReactionListUI.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D072F37423154E150009E66F /* AsyncDisplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D072F37323154E150009E66F /* AsyncDisplayKit.framework */; };
+ D072F37623154E180009E66F /* SwiftSignalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D072F37523154E180009E66F /* SwiftSignalKit.framework */; };
+ D072F37823154E1B0009E66F /* Display.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D072F37723154E1B0009E66F /* Display.framework */; };
+ D072F37A23154E1E0009E66F /* Postbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D072F37923154E1E0009E66F /* Postbox.framework */; };
+ D072F37C23154E220009E66F /* TelegramCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D072F37B23154E220009E66F /* TelegramCore.framework */; };
+ D072F37E23154E290009E66F /* AccountContext.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D072F37D23154E290009E66F /* AccountContext.framework */; };
+ D072F38023154E390009E66F /* MessageReactionListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D072F37F23154E390009E66F /* MessageReactionListController.swift */; };
+ D072F38223154EA90009E66F /* TelegramPresentationData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D072F38123154EA90009E66F /* TelegramPresentationData.framework */; };
+ D072F38623156B450009E66F /* MessageReactionCategoryNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D072F38523156B450009E66F /* MessageReactionCategoryNode.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ D072F36323154C230009E66F /* MessageReactionListUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MessageReactionListUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ D072F36623154C230009E66F /* MessageReactionListUI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MessageReactionListUI.h; sourceTree = ""; };
+ D072F36723154C230009E66F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ D072F37323154E150009E66F /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ D072F37523154E180009E66F /* SwiftSignalKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftSignalKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ D072F37723154E1B0009E66F /* Display.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Display.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ D072F37923154E1E0009E66F /* Postbox.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Postbox.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ D072F37B23154E220009E66F /* TelegramCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TelegramCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ D072F37D23154E290009E66F /* AccountContext.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AccountContext.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ D072F37F23154E390009E66F /* MessageReactionListController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageReactionListController.swift; sourceTree = ""; };
+ D072F38123154EA90009E66F /* TelegramPresentationData.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TelegramPresentationData.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ D072F38523156B450009E66F /* MessageReactionCategoryNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageReactionCategoryNode.swift; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ D072F36023154C230009E66F /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ D072F38223154EA90009E66F /* TelegramPresentationData.framework in Frameworks */,
+ D072F37E23154E290009E66F /* AccountContext.framework in Frameworks */,
+ D072F37C23154E220009E66F /* TelegramCore.framework in Frameworks */,
+ D072F37A23154E1E0009E66F /* Postbox.framework in Frameworks */,
+ D072F37823154E1B0009E66F /* Display.framework in Frameworks */,
+ D072F37623154E180009E66F /* SwiftSignalKit.framework in Frameworks */,
+ D072F37423154E150009E66F /* AsyncDisplayKit.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ D072F35923154C230009E66F = {
+ isa = PBXGroup;
+ children = (
+ D072F36723154C230009E66F /* Info.plist */,
+ D072F36523154C230009E66F /* Sources */,
+ D072F36423154C230009E66F /* Products */,
+ D072F37223154E150009E66F /* Frameworks */,
+ );
+ sourceTree = "";
+ };
+ D072F36423154C230009E66F /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ D072F36323154C230009E66F /* MessageReactionListUI.framework */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ D072F36523154C230009E66F /* Sources */ = {
+ isa = PBXGroup;
+ children = (
+ D072F36623154C230009E66F /* MessageReactionListUI.h */,
+ D072F37F23154E390009E66F /* MessageReactionListController.swift */,
+ D072F38523156B450009E66F /* MessageReactionCategoryNode.swift */,
+ );
+ path = Sources;
+ sourceTree = "";
+ };
+ D072F37223154E150009E66F /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ D072F38123154EA90009E66F /* TelegramPresentationData.framework */,
+ D072F37D23154E290009E66F /* AccountContext.framework */,
+ D072F37B23154E220009E66F /* TelegramCore.framework */,
+ D072F37923154E1E0009E66F /* Postbox.framework */,
+ D072F37723154E1B0009E66F /* Display.framework */,
+ D072F37523154E180009E66F /* SwiftSignalKit.framework */,
+ D072F37323154E150009E66F /* AsyncDisplayKit.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+ D072F35E23154C230009E66F /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ D072F36823154C230009E66F /* MessageReactionListUI.h in Headers */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXNativeTarget section */
+ D072F36223154C230009E66F /* MessageReactionListUI */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = D072F36B23154C230009E66F /* Build configuration list for PBXNativeTarget "MessageReactionListUI" */;
+ buildPhases = (
+ D072F35E23154C230009E66F /* Headers */,
+ D072F35F23154C230009E66F /* Sources */,
+ D072F36023154C230009E66F /* Frameworks */,
+ D072F36123154C230009E66F /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = MessageReactionListUI;
+ productName = MessageReactionListUI;
+ productReference = D072F36323154C230009E66F /* MessageReactionListUI.framework */;
+ productType = "com.apple.product-type.framework";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ D072F35A23154C230009E66F /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ DefaultBuildSystemTypeForWorkspace = Latest;
+ LastUpgradeCheck = 1030;
+ ORGANIZATIONNAME = "Telegram Messenger LLP";
+ TargetAttributes = {
+ D072F36223154C230009E66F = {
+ CreatedOnToolsVersion = 10.3;
+ LastSwiftMigration = 1030;
+ };
+ };
+ };
+ buildConfigurationList = D072F35D23154C230009E66F /* Build configuration list for PBXProject "MessageReactionListUI_Xcode" */;
+ compatibilityVersion = "Xcode 9.3";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ );
+ mainGroup = D072F35923154C230009E66F;
+ productRefGroup = D072F36423154C230009E66F /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ D072F36223154C230009E66F /* MessageReactionListUI */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ D072F36123154C230009E66F /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ D072F35F23154C230009E66F /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ D072F38623156B450009E66F /* MessageReactionCategoryNode.swift in Sources */,
+ D072F38023154E390009E66F /* MessageReactionListController.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+ D072F36923154C230009E66F /* DebugAppStoreLLC */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = DebugAppStoreLLC;
+ };
+ D072F36A23154C230009E66F /* ReleaseAppStoreLLC */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ VALIDATE_PRODUCT = YES;
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = ReleaseAppStoreLLC;
+ };
+ D072F36C23154C230009E66F /* DebugAppStoreLLC */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_IDENTITY = "";
+ CODE_SIGN_STYLE = Manual;
+ DEFINES_MODULE = YES;
+ DEVELOPMENT_TEAM = "";
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ INFOPLIST_FILE = Info.plist;
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MACH_O_TYPE = staticlib;
+ PRODUCT_BUNDLE_IDENTIFIER = org.telegram.MessageReactionListUI;
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SKIP_INSTALL = YES;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = DebugAppStoreLLC;
+ };
+ D072F36D23154C230009E66F /* ReleaseAppStoreLLC */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_IDENTITY = "";
+ CODE_SIGN_STYLE = Manual;
+ DEFINES_MODULE = YES;
+ DEVELOPMENT_TEAM = "";
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ INFOPLIST_FILE = Info.plist;
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MACH_O_TYPE = staticlib;
+ PRODUCT_BUNDLE_IDENTIFIER = org.telegram.MessageReactionListUI;
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SKIP_INSTALL = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = ReleaseAppStoreLLC;
+ };
+ D072F36E23154C450009E66F /* DebugHockeyapp */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = DebugHockeyapp;
+ };
+ D072F36F23154C450009E66F /* DebugHockeyapp */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_IDENTITY = "";
+ CODE_SIGN_STYLE = Manual;
+ DEFINES_MODULE = YES;
+ DEVELOPMENT_TEAM = "";
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ INFOPLIST_FILE = Info.plist;
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MACH_O_TYPE = staticlib;
+ PRODUCT_BUNDLE_IDENTIFIER = org.telegram.MessageReactionListUI;
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SKIP_INSTALL = YES;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = DebugHockeyapp;
+ };
+ D072F37023154C530009E66F /* ReleaseHockeyappInternal */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ VALIDATE_PRODUCT = YES;
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = ReleaseHockeyappInternal;
+ };
+ D072F37123154C530009E66F /* ReleaseHockeyappInternal */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_IDENTITY = "";
+ CODE_SIGN_STYLE = Manual;
+ DEFINES_MODULE = YES;
+ DEVELOPMENT_TEAM = "";
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ INFOPLIST_FILE = Info.plist;
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MACH_O_TYPE = staticlib;
+ PRODUCT_BUNDLE_IDENTIFIER = org.telegram.MessageReactionListUI;
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SKIP_INSTALL = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = ReleaseHockeyappInternal;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ D072F35D23154C230009E66F /* Build configuration list for PBXProject "MessageReactionListUI_Xcode" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ D072F36923154C230009E66F /* DebugAppStoreLLC */,
+ D072F36E23154C450009E66F /* DebugHockeyapp */,
+ D072F36A23154C230009E66F /* ReleaseAppStoreLLC */,
+ D072F37023154C530009E66F /* ReleaseHockeyappInternal */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = ReleaseAppStoreLLC;
+ };
+ D072F36B23154C230009E66F /* Build configuration list for PBXNativeTarget "MessageReactionListUI" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ D072F36C23154C230009E66F /* DebugAppStoreLLC */,
+ D072F36F23154C450009E66F /* DebugHockeyapp */,
+ D072F36D23154C230009E66F /* ReleaseAppStoreLLC */,
+ D072F37123154C530009E66F /* ReleaseHockeyappInternal */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = ReleaseAppStoreLLC;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = D072F35A23154C230009E66F /* Project object */;
+}
diff --git a/submodules/MessageReactionListUI/Sources/MessageReactionCategoryNode.swift b/submodules/MessageReactionListUI/Sources/MessageReactionCategoryNode.swift
new file mode 100644
index 0000000000..379ef364c6
--- /dev/null
+++ b/submodules/MessageReactionListUI/Sources/MessageReactionCategoryNode.swift
@@ -0,0 +1,103 @@
+import Foundation
+import AsyncDisplayKit
+import Display
+import TelegramPresentationData
+import TelegramCore
+
+final class MessageReactionCategoryNode: ASDisplayNode {
+ let category: MessageReactionListCategory
+ private let action: () -> Void
+
+ private let buttonNode: HighlightableButtonNode
+ private let highlightedBackgroundNode: ASImageNode
+ private let iconNode: ASImageNode
+ private let emojiNode: ImmediateTextNode
+ private let countNode: ImmediateTextNode
+
+ var isSelected = false {
+ didSet {
+ self.highlightedBackgroundNode.alpha = self.isSelected ? 1.0 : 0.0
+ }
+ }
+
+ init(theme: PresentationTheme, category: MessageReactionListCategory, count: Int, action: @escaping () -> Void) {
+ self.category = category
+ self.action = action
+
+ self.buttonNode = HighlightableButtonNode()
+
+ self.highlightedBackgroundNode = ASImageNode()
+ self.highlightedBackgroundNode.displaysAsynchronously = false
+ self.highlightedBackgroundNode.displayWithoutProcessing = true
+ self.highlightedBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: UIColor(rgb: 0xe6e6e8))
+ self.highlightedBackgroundNode.alpha = 1.0
+
+ self.iconNode = ASImageNode()
+
+ self.emojiNode = ImmediateTextNode()
+ self.emojiNode.displaysAsynchronously = false
+ let emojiText: String
+ switch category {
+ case .all:
+ emojiText = ""
+ self.iconNode.image = PresentationResourcesChat.chatInputTextFieldTimerImage(theme)
+ case let .reaction(value):
+ emojiText = value
+ }
+ self.emojiNode.attributedText = NSAttributedString(string: emojiText, font: Font.regular(18.0), textColor: .black)
+
+ self.countNode = ImmediateTextNode()
+ self.countNode.displaysAsynchronously = false
+ self.countNode.attributedText = NSAttributedString(string: "\(count)", font: Font.regular(16.0), textColor: .black)
+
+ super.init()
+
+ self.addSubnode(self.highlightedBackgroundNode)
+ self.addSubnode(self.iconNode)
+ self.addSubnode(self.emojiNode)
+ self.addSubnode(self.countNode)
+ self.addSubnode(self.buttonNode)
+
+ self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
+ }
+
+ func updateLayout() -> CGSize {
+ let sideInset: CGFloat = 6.0
+ let spacing: CGFloat = 2.0
+ let emojiSize = self.emojiNode.updateLayout(CGSize(width: 100.0, height: 100.0))
+ let iconSize = self.iconNode.image?.size ?? CGSize()
+ let countSize = self.countNode.updateLayout(CGSize(width: 100.0, height: 100.0))
+
+ let height: CGFloat = 60.0
+ let backgroundHeight: CGFloat = 36.0
+
+ self.emojiNode.frame = CGRect(origin: CGPoint(x: sideInset, y: floor((height - emojiSize.height) / 2.0)), size: emojiSize)
+ self.iconNode.frame = CGRect(origin: CGPoint(x: sideInset, y: floor((height - iconSize.height) / 2.0)), size: iconSize)
+
+ let iconFrame: CGRect
+ if self.iconNode.image != nil {
+ iconFrame = self.iconNode.frame
+ } else {
+ iconFrame = self.emojiNode.frame
+ }
+
+ self.countNode.frame = CGRect(origin: CGPoint(x: iconFrame.maxX + spacing, y: floor((height - countSize.height) / 2.0)), size: countSize)
+ let contentWidth = sideInset * 2.0 + spacing + iconFrame.width + countSize.width
+ self.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: floor((height - backgroundHeight) / 2.0)), size: CGSize(width: contentWidth, height: backgroundHeight))
+
+ let size = CGSize(width: contentWidth, height: height)
+ self.buttonNode.frame = CGRect(origin: CGPoint(), size: size)
+ return size
+ }
+
+ @objc private func buttonPressed() {
+ self.action()
+ }
+
+ override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
+ if self.buttonNode.frame.contains(point) {
+ return self.buttonNode.view
+ }
+ return nil
+ }
+}
diff --git a/submodules/MessageReactionListUI/Sources/MessageReactionListController.swift b/submodules/MessageReactionListUI/Sources/MessageReactionListController.swift
new file mode 100644
index 0000000000..c61df42470
--- /dev/null
+++ b/submodules/MessageReactionListUI/Sources/MessageReactionListController.swift
@@ -0,0 +1,418 @@
+import Foundation
+import Display
+import AccountContext
+import TelegramPresentationData
+import Postbox
+import TelegramCore
+import SwiftSignalKit
+import MergeLists
+import ItemListPeerItem
+
+public final class MessageReactionListController: ViewController {
+ private let context: AccountContext
+ private let messageId: MessageId
+ private let presentatonData: PresentationData
+ private let initialReactions: [MessageReaction]
+
+ private var controllerNode: MessageReactionListControllerNode {
+ return self.displayNode as! MessageReactionListControllerNode
+ }
+
+ private var animatedIn: Bool = false
+
+ private let _ready = Promise()
+ override public var ready: Promise {
+ return self._ready
+ }
+
+ public init(context: AccountContext, messageId: MessageId, initialReactions: [MessageReaction]) {
+ self.context = context
+ self.messageId = messageId
+ self.presentatonData = context.sharedContext.currentPresentationData.with { $0 }
+ self.initialReactions = initialReactions
+
+ super.init(navigationBarPresentationData: nil)
+
+ self.statusBar.statusBarStyle = .Ignore
+ }
+
+ required public init(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override public func loadDisplayNode() {
+ self.displayNode = MessageReactionListControllerNode(context: self.context, presentatonData: self.presentatonData, messageId: messageId, initialReactions: initialReactions, dismiss: { [weak self] in
+ self?.dismiss()
+ })
+
+ super.displayNodeDidLoad()
+
+ self._ready.set(self.controllerNode.isReady.get())
+ }
+
+ override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
+ super.containerLayoutUpdated(layout, transition: transition)
+
+ self.controllerNode.containerLayoutUpdated(layout: layout, transition: transition)
+ }
+
+ override public func viewDidAppear(_ animated: Bool) {
+ super.viewDidAppear(animated)
+
+ if !self.animatedIn {
+ self.animatedIn = true
+ self.controllerNode.animateIn()
+ }
+ }
+
+ override public func dismiss(completion: (() -> Void)? = nil) {
+ self.controllerNode.animateOut(completion: { [weak self] in
+ self?.presentingViewController?.dismiss(animated: false, completion: nil)
+ completion?()
+ })
+ }
+}
+
+private struct MessageReactionListTransaction {
+ let deletions: [ListViewDeleteItem]
+ let insertions: [ListViewInsertItem]
+ let updates: [ListViewUpdateItem]
+}
+
+private struct MessageReactionListEntry: Comparable, Identifiable {
+ let index: Int
+ let item: MessageReactionListCategoryItem
+
+ var stableId: PeerId {
+ return self.item.peer.id
+ }
+
+ static func <(lhs: MessageReactionListEntry, rhs: MessageReactionListEntry) -> Bool {
+ return lhs.index < rhs.index
+ }
+
+ func item(context: AccountContext, presentationData: PresentationData) -> ListViewItem {
+ return ItemListPeerItem(theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, account: context.account, peer: self.item.peer, height: .peerList, nameStyle: .distinctBold, presence: nil, text: .none, label: .text(self.item.reaction), editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: false, sectionId: 0, action: {
+
+ }, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, noInsets: true, tag: nil)
+ }
+}
+
+private func preparedTransition(from fromEntries: [MessageReactionListEntry], to toEntries: [MessageReactionListEntry], context: AccountContext, presentationData: PresentationData) -> MessageReactionListTransaction {
+ let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
+
+ let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
+ let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData), directionHint: nil) }
+ let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData), directionHint: nil) }
+
+ return MessageReactionListTransaction(deletions: deletions, insertions: insertions, updates: updates)
+}
+
+private let headerHeight: CGFloat = 60.0
+private let itemHeight: CGFloat = 50.0
+
+private func topInsetForLayout(layout: ContainerViewLayout, itemCount: Int) -> CGFloat {
+ let contentHeight = CGFloat(itemCount) * itemHeight
+ let minimumItemHeights: CGFloat = contentHeight
+
+ return max(layout.size.height - layout.intrinsicInsets.bottom - minimumItemHeights, headerHeight)
+}
+
+private final class MessageReactionListControllerNode: ViewControllerTracingNode {
+ private let context: AccountContext
+ private let presentatonData: PresentationData
+ private let dismiss: () -> Void
+
+ private let listContext: MessageReactionListContext
+
+ private let dimNode: ASDisplayNode
+ private let backgroundNode: ASDisplayNode
+ private let contentHeaderContainerNode: ASDisplayNode
+ private let contentHeaderContainerBackgroundNode: ASImageNode
+ private var categoryItemNodes: [MessageReactionCategoryNode] = []
+ private let categoryScrollNode: ASScrollNode
+ private let listNode: ListView
+
+ private var validLayout: ContainerViewLayout?
+
+ private var currentCategory: MessageReactionListCategory = .all
+ private var currentState: MessageReactionListState?
+
+ private var enqueuedTransactions: [MessageReactionListTransaction] = []
+
+ private let disposable = MetaDisposable()
+
+ let isReady = Promise()
+
+ private var forceHeaderTransition: ContainedViewLayoutTransition?
+
+ init(context: AccountContext, presentatonData: PresentationData, messageId: MessageId, initialReactions: [MessageReaction], dismiss: @escaping () -> Void) {
+ self.context = context
+ self.presentatonData = presentatonData
+ self.dismiss = dismiss
+
+ self.dimNode = ASDisplayNode()
+ self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
+
+ self.backgroundNode = ASDisplayNode()
+ self.backgroundNode.backgroundColor = self.presentatonData.theme.actionSheet.opaqueItemBackgroundColor
+
+ self.contentHeaderContainerNode = ASDisplayNode()
+ self.contentHeaderContainerBackgroundNode = ASImageNode()
+ self.contentHeaderContainerBackgroundNode.displaysAsynchronously = false
+
+ self.categoryScrollNode = ASScrollNode()
+ self.contentHeaderContainerBackgroundNode.displayWithoutProcessing = true
+ self.contentHeaderContainerBackgroundNode.image = generateImage(CGSize(width: 10.0, height: 10.0), rotatedContext: { size, context in
+ context.clear(CGRect(origin: CGPoint(), size: size))
+ context.setFillColor(presentatonData.theme.rootController.navigationBar.backgroundColor.cgColor)
+ context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
+ context.fill(CGRect(origin: CGPoint(x: 0.0, y: size.height / 2.0), size: CGSize(width: size.width, height: size.height / 2.0)))
+ })?.stretchableImage(withLeftCapWidth: 5, topCapHeight: 5)
+
+ self.listNode = ListView()
+ self.listNode.limitHitTestToNodes = true
+
+ self.listContext = MessageReactionListContext(postbox: self.context.account.postbox, network: self.context.account.network, messageId: messageId, initialReactions: initialReactions)
+
+ super.init()
+
+ self.addSubnode(self.dimNode)
+ self.addSubnode(self.backgroundNode)
+
+ self.listNode.stackFromBottom = false
+ self.addSubnode(self.listNode)
+
+ self.addSubnode(self.contentHeaderContainerNode)
+ self.contentHeaderContainerNode.addSubnode(self.contentHeaderContainerBackgroundNode)
+ self.contentHeaderContainerNode.addSubnode(self.categoryScrollNode)
+
+ self.listNode.updateFloatingHeaderOffset = { [weak self] offset, listTransition in
+ guard let strongSelf = self, let layout = strongSelf.validLayout else {
+ return
+ }
+
+ let transition = strongSelf.forceHeaderTransition ?? listTransition
+ strongSelf.forceHeaderTransition = nil
+
+ let topOffset = offset
+ transition.updateFrame(node: strongSelf.contentHeaderContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topOffset - headerHeight), size: CGSize(width: layout.size.width, height: headerHeight)))
+ transition.updateFrame(node: strongSelf.contentHeaderContainerBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: headerHeight)))
+ transition.updateFrame(node: strongSelf.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topOffset - headerHeight / 2.0), size: CGSize(width: layout.size.width, height: layout.size.height + 300.0)))
+ }
+
+ self.disposable.set((self.listContext.state
+ |> deliverOnMainQueue).start(next: { [weak self] state in
+ self?.updateState(state)
+ }))
+ }
+
+ deinit {
+ self.disposable.dispose()
+ }
+
+ override func didLoad() {
+ super.didLoad()
+
+ self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimNodeTapGesture)))
+ }
+
+ func containerLayoutUpdated(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
+ let isFirstLayout = self.validLayout == nil
+ self.validLayout = layout
+
+ transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
+
+ //transition.updateBounds(node: self.listNode, bounds: CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height))
+ //transition.updatePosition(node: self.listNode, position: CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0))
+
+ self.listNode.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)
+ self.listNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0)
+
+ var currentCategoryItemCount = 0
+ if let currentState = self.currentState {
+ for (category, categoryState) in currentState.states {
+ if category == self.currentCategory {
+ currentCategoryItemCount = categoryState.count
+ break
+ }
+ }
+ }
+
+ var insets = UIEdgeInsets()
+ insets.top = topInsetForLayout(layout: layout, itemCount: currentCategoryItemCount)
+ insets.bottom = layout.intrinsicInsets.bottom
+
+ var duration: Double = 0.0
+ var curve: UInt = 0
+ switch transition {
+ case .immediate:
+ break
+ case let .animated(animationDuration, animationCurve):
+ duration = animationDuration
+ switch animationCurve {
+ case .easeInOut, .custom:
+ break
+ case .spring:
+ curve = 7
+ }
+ }
+
+ let listViewCurve: ListViewAnimationCurve
+ if curve == 7 {
+ listViewCurve = .Spring(duration: duration)
+ } else {
+ listViewCurve = .Default(duration: duration)
+ }
+
+ self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, duration: duration, curve: listViewCurve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
+
+ let sideInset: CGFloat = 12.0
+ let spacing: CGFloat = 6.0
+ var leftX = sideInset
+ for itemNode in self.categoryItemNodes {
+ let itemSize = itemNode.updateLayout()
+ itemNode.frame = CGRect(origin: CGPoint(x: leftX, y: 0.0), size: itemSize)
+ leftX += spacing + itemSize.width
+ }
+ leftX += sideInset
+ self.categoryScrollNode.view.contentSize = CGSize(width: leftX, height: 60.0)
+ self.categoryScrollNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 60.0))
+
+ if isFirstLayout {
+ while !self.enqueuedTransactions.isEmpty {
+ self.dequeueTransaction()
+ }
+ }
+ }
+
+ func animateIn() {
+ self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
+ self.dimNode.layer.animatePosition(from: CGPoint(x: self.dimNode.position.x, y: self.dimNode.position.y - self.layer.bounds.size.height), to: self.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in
+ })
+ self.layer.animatePosition(from: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), to: self.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in
+ })
+ }
+
+ func animateOut(completion: @escaping () -> Void) {
+ self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
+ self.dimNode.layer.animatePosition(from: self.dimNode.position, to: CGPoint(x: self.dimNode.position.x, y: self.dimNode.position.y - self.layer.bounds.size.height), duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false)
+ self.layer.animatePosition(from: self.layer.position, to: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, completion: { _ in
+ completion()
+ })
+ }
+
+ func updateState(_ state: MessageReactionListState) {
+ if self.currentState != state {
+ self.currentState = state
+
+ self.updateItems()
+
+ if let validLayout = self.validLayout {
+ self.containerLayoutUpdated(layout: validLayout, transition: .immediate)
+ }
+ }
+ }
+
+ private var currentEntries: [MessageReactionListEntry]?
+ private func updateItems() {
+ var entries: [MessageReactionListEntry] = []
+
+ var index = 0
+ let states = self.currentState?.states ?? []
+ for (category, categoryState) in states {
+ if self.categoryItemNodes.count <= index {
+ let itemNode = MessageReactionCategoryNode(theme: self.presentatonData.theme, category: category, count: categoryState.count, action: { [weak self] in
+ self?.setCategory(category)
+ })
+ self.categoryItemNodes.append(itemNode)
+ self.categoryScrollNode.addSubnode(itemNode)
+ if category == self.currentCategory {
+ itemNode.isSelected = true
+ } else {
+ itemNode.isSelected = false
+ }
+ }
+
+ if category == self.currentCategory {
+ for item in categoryState.items {
+ entries.append(MessageReactionListEntry(index: entries.count, item: item))
+ }
+ if !categoryState.items.isEmpty {
+ self.isReady.set(.single(true))
+ }
+ }
+ index += 1
+ }
+ let transaction = preparedTransition(from: self.currentEntries ?? [], to: entries, context: self.context, presentationData: self.presentatonData)
+ self.currentEntries = entries
+
+ self.enqueuedTransactions.append(transaction)
+ self.dequeueTransaction()
+ }
+
+ func setCategory(_ category: MessageReactionListCategory) {
+ if self.currentCategory != category {
+ self.currentCategory = category
+
+ for itemNode in self.categoryItemNodes {
+ itemNode.isSelected = category == itemNode.category
+ }
+
+ self.forceHeaderTransition = .animated(duration: 0.3, curve: .spring)
+ if let validLayout = self.validLayout {
+ self.containerLayoutUpdated(layout: validLayout, transition: .animated(duration: 0.3, curve: .spring))
+ }
+
+ self.updateItems()
+ }
+ }
+
+ private func dequeueTransaction() {
+ guard let layout = self.validLayout, let transaction = self.enqueuedTransactions.first else {
+ return
+ }
+
+ self.enqueuedTransactions.remove(at: 0)
+
+ var options = ListViewDeleteAndInsertOptions()
+ options.insert(.Synchronous)
+ //options.insert(.AnimateTopItemPosition)
+ //options.insert(.AnimateCrossfade)
+ options.insert(.PreferSynchronousResourceLoading)
+
+ var currentCategoryItemCount = 0
+ if let currentState = self.currentState {
+ for (category, categoryState) in currentState.states {
+ if category == self.currentCategory {
+ currentCategoryItemCount = categoryState.count
+ break
+ }
+ }
+ }
+
+ var insets = UIEdgeInsets()
+ insets.top = topInsetForLayout(layout: layout, itemCount: currentCategoryItemCount)
+ insets.bottom = layout.intrinsicInsets.bottom
+
+ let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: self.listNode.bounds.size, insets: insets, duration: 0.0, curve: .Default(duration: nil))
+
+ self.listNode.transaction(deleteIndices: transaction.deletions, insertIndicesAndItems: transaction.insertions, updateIndicesAndItems: transaction.updates, options: options, updateSizeAndInsets: updateSizeAndInsets, updateOpaqueState: nil, completion: { _ in
+ })
+ }
+
+ @objc private func dimNodeTapGesture(_ recognizer: UITapGestureRecognizer) {
+ if case .ended = recognizer.state {
+ self.dismiss()
+ }
+ }
+
+ override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
+ for itemNode in self.categoryItemNodes {
+ if let result = itemNode.hitTest(self.view.convert(point, to: itemNode.view), with: event) {
+ return result
+ }
+ }
+ return super.hitTest(point, with: event)
+ }
+}
diff --git a/submodules/MessageReactionListUI/Sources/MessageReactionListUI.h b/submodules/MessageReactionListUI/Sources/MessageReactionListUI.h
new file mode 100644
index 0000000000..6460ffc97e
--- /dev/null
+++ b/submodules/MessageReactionListUI/Sources/MessageReactionListUI.h
@@ -0,0 +1,19 @@
+//
+// MessageReactionListUI.h
+// MessageReactionListUI
+//
+// Created by Peter on 8/27/19.
+// Copyright © 2019 Telegram Messenger LLP. All rights reserved.
+//
+
+#import
+
+//! Project version number for MessageReactionListUI.
+FOUNDATION_EXPORT double MessageReactionListUIVersionNumber;
+
+//! Project version string for MessageReactionListUI.
+FOUNDATION_EXPORT const unsigned char MessageReactionListUIVersionString[];
+
+// In this header, you should import all the public headers of your framework using statements like #import
+
+
diff --git a/submodules/TelegramCore/TelegramCore/MessageReactionList.swift b/submodules/TelegramCore/TelegramCore/MessageReactionList.swift
new file mode 100644
index 0000000000..85a6b34d8b
--- /dev/null
+++ b/submodules/TelegramCore/TelegramCore/MessageReactionList.swift
@@ -0,0 +1,216 @@
+import Foundation
+#if os(macOS)
+import PostboxMac
+import SwiftSignalKitMac
+import MtProtoKitMac
+import TelegramApiMac
+#else
+import Postbox
+import SwiftSignalKit
+import TelegramApi
+#if BUCK
+import MtProtoKit
+#else
+import MtProtoKitDynamic
+#endif
+#endif
+
+public enum MessageReactionListCategory: Hashable {
+ case all
+ case reaction(String)
+}
+
+public final class MessageReactionListCategoryItem: Equatable {
+ public let peer: Peer
+ public let reaction: String
+
+ init(peer: Peer, reaction: String) {
+ self.peer = peer
+ self.reaction = reaction
+ }
+
+ public static func ==(lhs: MessageReactionListCategoryItem, rhs: MessageReactionListCategoryItem) -> Bool {
+ if lhs.peer.id != rhs.peer.id {
+ return false
+ }
+ if lhs.reaction != rhs.reaction {
+ return false
+ }
+ return true
+ }
+}
+
+public struct MessageReactionListCategoryState: Equatable {
+ public var count: Int
+ public var completed: Bool
+ public var items: [MessageReactionListCategoryItem]
+ public var loadingMore: Bool
+ fileprivate var nextOffset: String?
+}
+
+private enum LoadReactionsError {
+ case generic
+}
+
+private final class MessageReactionCategoryContext {
+ private let postbox: Postbox
+ private let network: Network
+ private let messageId: MessageId
+ private let category: MessageReactionListCategory
+ private var state: MessageReactionListCategoryState
+ var statePromise: ValuePromise
+
+ private let loadingDisposable = MetaDisposable()
+
+ init(postbox: Postbox, network: Network, messageId: MessageId, category: MessageReactionListCategory, initialState: MessageReactionListCategoryState) {
+ self.postbox = postbox
+ self.network = network
+ self.messageId = messageId
+ self.category = category
+ self.state = initialState
+ self.statePromise = ValuePromise(initialState)
+ }
+
+ deinit {
+ self.loadingDisposable.dispose()
+ }
+
+ func loadMore() {
+ if self.state.completed || self.state.loadingMore {
+ return
+ }
+ self.state.loadingMore = true
+ self.statePromise.set(self.state)
+
+ var flags: Int32 = 0
+ var reaction: String?
+ switch self.category {
+ case .all:
+ break
+ case let .reaction(value):
+ flags |= 1 << 0
+ reaction = value
+ }
+ let messageId = self.messageId
+ let offset = self.state.nextOffset
+ let request = self.postbox.transaction { transaction -> Api.InputPeer? in
+ let inputPeer = transaction.getPeer(messageId.peerId).flatMap(apiInputPeer)
+ return inputPeer
+ }
+ |> introduceError(LoadReactionsError.self)
+ |> mapToSignal { inputPeer -> Signal in
+ guard let inputPeer = inputPeer else {
+ return .fail(.generic)
+ }
+ return self.network.request(Api.functions.messages.getMessageReactionsList(flags: flags, peer: inputPeer, id: messageId.id, reaction: reaction, offset: offset, limit: 64))
+ |> mapError { _ -> LoadReactionsError in
+ return .generic
+ }
+ }
+ self.loadingDisposable.set((request
+ |> deliverOnMainQueue).start(next: { [weak self] result in
+ guard let strongSelf = self else {
+ return
+ }
+ let currentState = strongSelf.state
+ let _ = (strongSelf.postbox.transaction { transaction -> MessageReactionListCategoryState in
+ var mergedItems = currentState.items
+ var currentIds = Set(mergedItems.lazy.map { $0.peer.id })
+ switch result {
+ case let .messageReactionsList(_, count, reactions, users, nextOffset):
+ var peers: [Peer] = []
+ for user in users {
+ let parsedUser = TelegramUser(user: user)
+ peers.append(parsedUser)
+ }
+ updatePeers(transaction: transaction, peers: peers, update: { _, updated in updated })
+ for reaction in reactions {
+ switch reaction {
+ case let .messageUserReaction(userId, reaction):
+ if let peer = transaction.getPeer(PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)) {
+ if !currentIds.contains(peer.id) {
+ currentIds.insert(peer.id)
+ mergedItems.append(MessageReactionListCategoryItem(peer: peer, reaction: reaction))
+ }
+ }
+ }
+ }
+ return MessageReactionListCategoryState(count: max(mergedItems.count, Int(count)), completed: nextOffset == nil, items: mergedItems, loadingMore: false, nextOffset: nextOffset)
+ }
+ }
+ |> deliverOnMainQueue).start(next: { state in
+ guard let strongSelf = self else {
+ return
+ }
+ strongSelf.state = state
+ strongSelf.statePromise.set(state)
+ })
+ }, error: { _ in
+
+ }))
+ }
+}
+
+public struct MessageReactionListState: Equatable {
+ public var states: [(MessageReactionListCategory, MessageReactionListCategoryState)]
+
+ public static func ==(lhs: MessageReactionListState, rhs: MessageReactionListState) -> Bool {
+ if lhs.states.count != rhs.states.count {
+ return false
+ }
+ for i in 0 ..< lhs.states.count {
+ if lhs.states[i].0 != rhs.states[i].0 {
+ return false
+ }
+ if lhs.states[i].1 != rhs.states[i].1 {
+ return false
+ }
+ }
+ return true
+ }
+}
+
+public final class MessageReactionListContext {
+ private let postbox: Postbox
+ private let network: Network
+
+ private var categoryContexts: [MessageReactionListCategory: MessageReactionCategoryContext] = [:]
+
+ private let _state = Promise()
+ public var state: Signal {
+ return self._state.get()
+ }
+
+ public init(postbox: Postbox, network: Network, messageId: MessageId, initialReactions: [MessageReaction]) {
+ self.postbox = postbox
+ self.network = network
+
+ var allState = MessageReactionListCategoryState(count: 0, completed: false, items: [], loadingMore: false, nextOffset: nil)
+ var signals: [Signal<(MessageReactionListCategory, MessageReactionListCategoryState), NoError>] = []
+ for reaction in initialReactions {
+ allState.count += Int(reaction.count)
+ let context = MessageReactionCategoryContext(postbox: postbox, network: network, messageId: messageId, category: .reaction(reaction.value), initialState: MessageReactionListCategoryState(count: Int(reaction.count), completed: false, items: [], loadingMore: false, nextOffset: nil))
+ signals.append(context.statePromise.get() |> map { value -> (MessageReactionListCategory, MessageReactionListCategoryState) in
+ return (.reaction(reaction.value), value)
+ })
+ self.categoryContexts[.reaction(reaction.value)] = context
+ context.loadMore()
+ }
+ let allContext = MessageReactionCategoryContext(postbox: postbox, network: network, messageId: messageId, category: .all, initialState: allState)
+ signals.insert(allContext.statePromise.get() |> map { value -> (MessageReactionListCategory, MessageReactionListCategoryState) in
+ return (.all, value)
+ }, at: 0)
+ self.categoryContexts[.all] = allContext
+
+ self._state.set(combineLatest(queue: .mainQueue(), signals)
+ |> map { states in
+ return MessageReactionListState(states: states)
+ })
+
+ allContext.loadMore()
+ }
+
+ public func loadMore(category: MessageReactionListCategory) {
+ self.categoryContexts[category]?.loadMore()
+ }
+}
diff --git a/submodules/TelegramCore/TelegramCore_Xcode.xcodeproj/project.pbxproj b/submodules/TelegramCore/TelegramCore_Xcode.xcodeproj/project.pbxproj
index 63bc08ccc6..e470875ec7 100644
--- a/submodules/TelegramCore/TelegramCore_Xcode.xcodeproj/project.pbxproj
+++ b/submodules/TelegramCore/TelegramCore_Xcode.xcodeproj/project.pbxproj
@@ -450,6 +450,8 @@
D07047B81F3DF2CD00F6A8D4 /* ManagedConsumePersonalMessagesActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07047B61F3DF2CD00F6A8D4 /* ManagedConsumePersonalMessagesActions.swift */; };
D07047BA1F3DF75500F6A8D4 /* ConsumePersonalMessageAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07047B91F3DF75500F6A8D4 /* ConsumePersonalMessageAction.swift */; };
D07047BB1F3DF75500F6A8D4 /* ConsumePersonalMessageAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07047B91F3DF75500F6A8D4 /* ConsumePersonalMessageAction.swift */; };
+ D072F357231542740009E66F /* MessageReactionList.swift in Sources */ = {isa = PBXBuildFile; fileRef = D072F356231542740009E66F /* MessageReactionList.swift */; };
+ D072F358231542740009E66F /* MessageReactionList.swift in Sources */ = {isa = PBXBuildFile; fileRef = D072F356231542740009E66F /* MessageReactionList.swift */; };
D073CE5D1DCB97F6007511FD /* ForwardSourceInfoAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D073CE5C1DCB97F6007511FD /* ForwardSourceInfoAttribute.swift */; };
D073CE601DCB9D14007511FD /* OutgoingMessageInfoAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D073CE5F1DCB9D14007511FD /* OutgoingMessageInfoAttribute.swift */; };
D073CE6A1DCBCF17007511FD /* ViewCountMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0CE71D6224AD00955575 /* ViewCountMessageAttribute.swift */; };
@@ -1042,6 +1044,7 @@
D07047B31F3DF1FE00F6A8D4 /* ConsumablePersonalMentionMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsumablePersonalMentionMessageAttribute.swift; sourceTree = ""; };
D07047B61F3DF2CD00F6A8D4 /* ManagedConsumePersonalMessagesActions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedConsumePersonalMessagesActions.swift; sourceTree = ""; };
D07047B91F3DF75500F6A8D4 /* ConsumePersonalMessageAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsumePersonalMessageAction.swift; sourceTree = ""; };
+ D072F356231542740009E66F /* MessageReactionList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageReactionList.swift; sourceTree = ""; };
D073CE5C1DCB97F6007511FD /* ForwardSourceInfoAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForwardSourceInfoAttribute.swift; sourceTree = ""; };
D073CE5F1DCB9D14007511FD /* OutgoingMessageInfoAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutgoingMessageInfoAttribute.swift; sourceTree = ""; };
D0750C8F22B2FD8300BE5F6E /* PeerAccessHash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerAccessHash.swift; sourceTree = ""; };
@@ -1606,6 +1609,7 @@
D01AC91C1DD5DA5E00E8160F /* RequestMessageActionCallback.swift */,
D0AB262A21C3CE80008F6685 /* Polls.swift */,
D0329EA122FC5A7C00F9F071 /* MessageReactions.swift */,
+ D072F356231542740009E66F /* MessageReactionList.swift */,
D01AC9201DD5E7E500E8160F /* RequestEditMessage.swift */,
D0DC354D1DE368F7000195EB /* RequestChatContextResults.swift */,
D0DC354F1DE36900000195EB /* ChatContextResult.swift */,
@@ -2453,6 +2457,7 @@
D054649120738653002ECC1E /* SecureIdIDCardValue.swift in Sources */,
D018EE052045E95000CBB130 /* CheckPeerChatServiceActions.swift in Sources */,
D0F3A8A51E82C94C00B4C64C /* SynchronizeableChatInputState.swift in Sources */,
+ D072F357231542740009E66F /* MessageReactionList.swift in Sources */,
D03B0CD71D62245300955575 /* TelegramGroup.swift in Sources */,
D02609BF20C6EC08006C34AC /* Crypto.m in Sources */,
D0B8438C1DA7CF50005F29E1 /* BotInfo.swift in Sources */,
@@ -2783,6 +2788,7 @@
D05A32E81E6F0B5C002760B4 /* RecentAccountSession.swift in Sources */,
D0F7B1E31E045C7B007EB8A5 /* RichText.swift in Sources */,
D0575C2E22B922DF00A71A0E /* DeleteAccount.swift in Sources */,
+ D072F358231542740009E66F /* MessageReactionList.swift in Sources */,
D0FA8BB11E1FEC7E001E855B /* SecretChatEncryptionConfig.swift in Sources */,
D0B418AA1D7E0597004562A4 /* Download.swift in Sources */,
D001F3F41E128A1C007A8C60 /* UpdatesApiUtils.swift in Sources */,
diff --git a/submodules/TelegramUI/TelegramUI/AppDelegate.swift b/submodules/TelegramUI/TelegramUI/AppDelegate.swift
index faf983cda1..7cadafdf72 100644
--- a/submodules/TelegramUI/TelegramUI/AppDelegate.swift
+++ b/submodules/TelegramUI/TelegramUI/AppDelegate.swift
@@ -57,12 +57,13 @@ private class ApplicationStatusBarHost: StatusBarHost {
return self.application.statusBarFrame
}
var statusBarStyle: UIStatusBarStyle {
- get {
- return self.application.statusBarStyle
- } set(value) {
- self.application.setStatusBarStyle(value, animated: false)
- }
+ return self.application.statusBarStyle
}
+
+ func setStatusBarStyle(_ style: UIStatusBarStyle, animated: Bool) {
+ self.application.setStatusBarStyle(style, animated: animated)
+ }
+
var statusBarWindow: UIView? {
return self.application.value(forKey: "statusBarWindow") as? UIView
}
@@ -237,7 +238,7 @@ final class SharedApplicationContext {
let (window, hostView, aboveStatusbarWindow) = nativeWindowHostView()
self.mainWindow = Window1(hostView: hostView, statusBarHost: statusBarHost)
self.aboveStatusbarWindow = aboveStatusbarWindow
- window.backgroundColor = UIColor.white
+ hostView.containerView.backgroundColor = UIColor.white
self.window = window
self.nativeWindow = window
@@ -656,7 +657,7 @@ final class SharedApplicationContext {
}
|> deliverOnMainQueue
|> mapToSignal { accountManager, initialPresentationDataAndSettings -> Signal<(SharedApplicationContext, LoggingSettings), NoError> in
- self.window?.backgroundColor = initialPresentationDataAndSettings.presentationData.theme.chatList.backgroundColor
+ self.mainWindow?.hostView.containerView.backgroundColor = initialPresentationDataAndSettings.presentationData.theme.chatList.backgroundColor
let legacyBasePath = appGroupUrl.path
let legacyCache = LegacyCache(path: legacyBasePath + "/Caches")
diff --git a/submodules/TelegramUI/TelegramUI/ChatController.swift b/submodules/TelegramUI/TelegramUI/ChatController.swift
index 4a367294dc..2a55b64dab 100644
--- a/submodules/TelegramUI/TelegramUI/ChatController.swift
+++ b/submodules/TelegramUI/TelegramUI/ChatController.swift
@@ -44,6 +44,7 @@ import PeerInfoUI
import RaiseToListen
import UrlHandling
import ReactionSelectionNode
+import MessageReactionListUI
public enum ChatControllerPeekActions {
case standard
@@ -286,6 +287,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
private var updateSlowmodeStatusDisposable = MetaDisposable()
private var updateSlowmodeStatusTimerValue: Int32?
+
+ private var isDismissed = false
public override var customData: Any? {
return self.chatLocation
@@ -343,6 +346,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
super.init(context: context, navigationBarPresentationData: navigationBarPresentationData, mediaAccessoryPanelVisibility: mediaAccessoryPanelVisibility, locationBroadcastPanelSource: locationBroadcastPanelSource)
self.blocksBackgroundWhenInOverlay = true
+ if let subject = subject, case .scheduledMessages = subject {
+ self.isModalWhenInOverlay = true
+ }
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
@@ -1499,8 +1505,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let strongSelf = self {
strongSelf.chatDisplayNode.sendCurrentMessage(scheduleTime: scheduleTime)
if !strongSelf.presentationInterfaceState.isScheduledMessages {
- let controller = ChatControllerImpl(context: strongSelf.context, chatLocation: strongSelf.chatLocation, subject: .scheduledMessages)
- (strongSelf.navigationController as? NavigationController)?.pushViewController(controller)
+ strongSelf.openScheduledMessages()
}
}
})
@@ -1581,6 +1586,28 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
let _ = updateMessageReactionsInteractively(postbox: strongSelf.context.account.postbox, messageId: messageId, reaction: reaction).start()
+ }, openMessageReactions: { [weak self] messageId in
+ guard let strongSelf = self else {
+ return
+ }
+ let _ = (strongSelf.context.account.postbox.transaction { transaction -> Message? in
+ return transaction.getMessage(messageId)
+ }
+ |> deliverOnMainQueue).start(next: { message in
+ guard let strongSelf = self, let message = message else {
+ return
+ }
+ var initialReactions: [MessageReaction] = []
+ for attribute in message.attributes {
+ if let attribute = attribute as? ReactionsMessageAttribute {
+ initialReactions = attribute.reactions
+ }
+ }
+
+ if !initialReactions.isEmpty {
+ strongSelf.present(MessageReactionListController(context: strongSelf.context, messageId: message.id, initialReactions: initialReactions), in: .window(.root))
+ }
+ })
}, requestMessageUpdate: { [weak self] id in
if let strongSelf = self {
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id)
@@ -3856,8 +3883,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}, openScheduledMessages: { [weak self] in
if let strongSelf = self {
- let controller = ChatControllerImpl(context: strongSelf.context, chatLocation: strongSelf.chatLocation, subject: .scheduledMessages)
- (strongSelf.navigationController as? NavigationController)?.pushViewController(controller)
+ strongSelf.openScheduledMessages()
}
}, statuses: ChatPanelInterfaceInteractionStatuses(editingMessage: self.editingMessage.get(), startingBot: self.startingBot.get(), unblockingPeer: self.unblockingPeer.get(), searching: self.searching.get(), loadingMessage: self.loadingMessage.get()))
@@ -4213,6 +4239,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
}
+
+ if let subject = self.subject, case .scheduledMessages = subject {
+ self.chatDisplayNode.animateIn()
+ self.updateTransitionWhenPresentedAsModal?(1.0, .animated(duration: 0.5, curve: .spring))
+ }
}
override public func viewWillDisappear(_ animated: Bool) {
@@ -4571,7 +4602,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
//
// }
- if let button = leftNavigationButtonForChatInterfaceState(updatedChatPresentationInterfaceState, strings: updatedChatPresentationInterfaceState.strings, currentButton: self.leftNavigationButton, target: self, selector: #selector(self.leftNavigationButtonAction)) {
+ if let button = leftNavigationButtonForChatInterfaceState(updatedChatPresentationInterfaceState, subject: self.subject, strings: updatedChatPresentationInterfaceState.strings, currentButton: self.leftNavigationButton, target: self, selector: #selector(self.leftNavigationButtonAction)) {
if self.leftNavigationButton != button {
var animated = transition.isAnimated
if let currentButton = self.leftNavigationButton?.action, currentButton == button.action {
@@ -4671,115 +4702,117 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
private func navigationButtonAction(_ action: ChatNavigationButtonAction) {
switch action {
- case .cancelMessageSelection:
- self.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } })
- case .clearHistory:
- if case let .peer(peerId) = self.chatLocation {
- guard let peer = self.presentationInterfaceState.renderedPeer, let chatPeer = peer.peers[peer.peerId], let mainPeer = peer.chatMainPeer else {
+ case .cancelMessageSelection:
+ self.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } })
+ case .clearHistory:
+ if case let .peer(peerId) = self.chatLocation {
+ guard let peer = self.presentationInterfaceState.renderedPeer, let chatPeer = peer.peers[peer.peerId], let mainPeer = peer.chatMainPeer else {
+ return
+ }
+
+ let text: String
+ if peerId == self.context.account.peerId {
+ text = self.presentationData.strings.Conversation_ClearSelfHistory
+ } else if peerId.namespace == Namespaces.Peer.SecretChat {
+ text = self.presentationData.strings.Conversation_ClearSecretHistory
+ } else if peerId.namespace == Namespaces.Peer.CloudGroup || peerId.namespace == Namespaces.Peer.CloudChannel {
+ text = self.presentationData.strings.Conversation_ClearGroupHistory
+ } else {
+ text = self.presentationData.strings.Conversation_ClearPrivateHistory
+ }
+
+ var canRemoveGlobally = false
+ let limitsConfiguration = self.context.currentLimitsConfiguration.with { $0 }
+ if peerId.namespace == Namespaces.Peer.CloudUser && peerId != self.context.account.peerId {
+ if limitsConfiguration.maxMessageRevokeIntervalInPrivateChats == LimitsConfiguration.timeIntervalForever {
+ canRemoveGlobally = true
+ }
+ }
+ if let user = chatPeer as? TelegramUser, user.botInfo != nil {
+ canRemoveGlobally = false
+ }
+
+ let account = self.context.account
+
+ let beginClear: (InteractiveHistoryClearingType) -> Void = { [weak self] type in
+ guard let strongSelf = self else {
return
}
+ strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } })
+ strongSelf.chatDisplayNode.historyNode.historyAppearsCleared = true
- let text: String
- if peerId == self.context.account.peerId {
- text = self.presentationData.strings.Conversation_ClearSelfHistory
- } else if peerId.namespace == Namespaces.Peer.SecretChat {
- text = self.presentationData.strings.Conversation_ClearSecretHistory
- } else if peerId.namespace == Namespaces.Peer.CloudGroup || peerId.namespace == Namespaces.Peer.CloudChannel {
- text = self.presentationData.strings.Conversation_ClearGroupHistory
+ let statusText: String
+ if strongSelf.presentationInterfaceState.isScheduledMessages {
+ statusText = strongSelf.presentationData.strings.Undo_ScheduledMessagesCleared
+ } else if case .forEveryone = type {
+ statusText = strongSelf.presentationData.strings.Undo_ChatClearedForBothSides
} else {
- text = self.presentationData.strings.Conversation_ClearPrivateHistory
+ statusText = strongSelf.presentationData.strings.Undo_ChatCleared
}
- var canRemoveGlobally = false
- let limitsConfiguration = self.context.currentLimitsConfiguration.with { $0 }
- if peerId.namespace == Namespaces.Peer.CloudUser && peerId != self.context.account.peerId {
- if limitsConfiguration.maxMessageRevokeIntervalInPrivateChats == LimitsConfiguration.timeIntervalForever {
- canRemoveGlobally = true
- }
- }
- if let user = chatPeer as? TelegramUser, user.botInfo != nil {
- canRemoveGlobally = false
- }
-
- let account = self.context.account
-
- let beginClear: (InteractiveHistoryClearingType) -> Void = { [weak self] type in
- guard let strongSelf = self else {
- return
- }
- strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } })
- strongSelf.chatDisplayNode.historyNode.historyAppearsCleared = true
-
- let statusText: String
- if strongSelf.presentationInterfaceState.isScheduledMessages {
- statusText = strongSelf.presentationData.strings.Undo_ScheduledMessagesCleared
- } else if case .forEveryone = type {
- statusText = strongSelf.presentationData.strings.Undo_ChatClearedForBothSides
- } else {
- statusText = strongSelf.presentationData.strings.Undo_ChatCleared
- }
-
- strongSelf.present(UndoOverlayController(context: strongSelf.context, content: .removedChat(text: statusText), elevatedLayout: true, action: { shouldCommit in
- if shouldCommit {
- let _ = clearHistoryInteractively(postbox: account.postbox, peerId: peerId, type: type).start(completed: {
- self?.chatDisplayNode.historyNode.historyAppearsCleared = false
- })
- } else {
+ strongSelf.present(UndoOverlayController(context: strongSelf.context, content: .removedChat(text: statusText), elevatedLayout: true, action: { shouldCommit in
+ if shouldCommit {
+ let _ = clearHistoryInteractively(postbox: account.postbox, peerId: peerId, type: type).start(completed: {
self?.chatDisplayNode.historyNode.historyAppearsCleared = false
- }
- }), in: .window(.root))
- }
-
- let actionSheet = ActionSheetController(presentationTheme: self.presentationData.theme)
- var items: [ActionSheetItem] = []
-
- if self.presentationInterfaceState.isScheduledMessages {
- items.append(ActionSheetButtonItem(title: self.presentationData.strings.ScheduledMessages_ClearAllConfirmation, color: .destructive, action: { [weak actionSheet] in
- actionSheet?.dismissAnimated()
- beginClear(.scheduledMessages)
- }))
- } else if canRemoveGlobally {
- items.append(DeleteChatPeerActionSheetItem(context: self.context, peer: mainPeer, chatPeer: chatPeer, action: .clearHistory, strings: self.presentationData.strings))
- items.append(ActionSheetButtonItem(title: self.presentationData.strings.ChatList_DeleteForEveryone(mainPeer.compactDisplayTitle).0, color: .destructive, action: { [weak actionSheet] in
- beginClear(.forEveryone)
- actionSheet?.dismissAnimated()
- }))
- items.append(ActionSheetButtonItem(title: self.presentationData.strings.ChatList_DeleteForCurrentUser, color: .destructive, action: { [weak actionSheet] in
- actionSheet?.dismissAnimated()
- beginClear(.forLocalPeer)
- }))
- } else {
- items.append(ActionSheetTextItem(title: text))
- items.append(ActionSheetButtonItem(title: self.presentationData.strings.Conversation_ClearAll, color: .destructive, action: { [weak actionSheet] in
- actionSheet?.dismissAnimated()
- beginClear(.forLocalPeer)
- }))
- }
+ })
+ } else {
+ self?.chatDisplayNode.historyNode.historyAppearsCleared = false
+ }
+ }), in: .window(.root))
+ }
+
+ let actionSheet = ActionSheetController(presentationTheme: self.presentationData.theme)
+ var items: [ActionSheetItem] = []
+
+ if self.presentationInterfaceState.isScheduledMessages {
+ items.append(ActionSheetButtonItem(title: self.presentationData.strings.ScheduledMessages_ClearAllConfirmation, color: .destructive, action: { [weak actionSheet] in
+ actionSheet?.dismissAnimated()
+ beginClear(.scheduledMessages)
+ }))
+ } else if canRemoveGlobally {
+ items.append(DeleteChatPeerActionSheetItem(context: self.context, peer: mainPeer, chatPeer: chatPeer, action: .clearHistory, strings: self.presentationData.strings))
+ items.append(ActionSheetButtonItem(title: self.presentationData.strings.ChatList_DeleteForEveryone(mainPeer.compactDisplayTitle).0, color: .destructive, action: { [weak actionSheet] in
+ beginClear(.forEveryone)
+ actionSheet?.dismissAnimated()
+ }))
+ items.append(ActionSheetButtonItem(title: self.presentationData.strings.ChatList_DeleteForCurrentUser, color: .destructive, action: { [weak actionSheet] in
+ actionSheet?.dismissAnimated()
+ beginClear(.forLocalPeer)
+ }))
+ } else {
+ items.append(ActionSheetTextItem(title: text))
+ items.append(ActionSheetButtonItem(title: self.presentationData.strings.Conversation_ClearAll, color: .destructive, action: { [weak actionSheet] in
+ actionSheet?.dismissAnimated()
+ beginClear(.forLocalPeer)
+ }))
+ }
- actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
- ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
- actionSheet?.dismissAnimated()
- })
- ])])
-
- self.chatDisplayNode.dismissInput()
- self.present(actionSheet, in: .window(.root))
- }
- case .openChatInfo:
- switch self.chatLocationInfoData {
- case let .peer(peerView):
- self.navigationActionDisposable.set((peerView.get()
- |> take(1)
- |> deliverOnMainQueue).start(next: { [weak self] peerView in
- if let strongSelf = self, let peer = peerView.peers[peerView.peerId], peer.restrictionText(platform: "ios") == nil && !strongSelf.presentationInterfaceState.isNotAccessible {
- if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic) {
- (strongSelf.navigationController as? NavigationController)?.pushViewController(infoController)
- }
+ actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
+ ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
+ actionSheet?.dismissAnimated()
+ })
+ ])])
+
+ self.chatDisplayNode.dismissInput()
+ self.present(actionSheet, in: .window(.root))
+ }
+ case .openChatInfo:
+ switch self.chatLocationInfoData {
+ case let .peer(peerView):
+ self.navigationActionDisposable.set((peerView.get()
+ |> take(1)
+ |> deliverOnMainQueue).start(next: { [weak self] peerView in
+ if let strongSelf = self, let peer = peerView.peers[peerView.peerId], peer.restrictionText(platform: "ios") == nil && !strongSelf.presentationInterfaceState.isNotAccessible {
+ if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic) {
+ (strongSelf.navigationController as? NavigationController)?.pushViewController(infoController)
}
- }))
- }
- case .search:
- self.interfaceInteraction?.beginMessageSearch(.everything, "")
+ }
+ }))
+ }
+ case .search:
+ self.interfaceInteraction?.beginMessageSearch(.everything, "")
+ case .dismiss:
+ self.dismiss()
}
}
@@ -4944,8 +4977,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let strongSelf = self {
done(time)
if !strongSelf.presentationInterfaceState.isScheduledMessages {
- let controller = ChatControllerImpl(context: strongSelf.context, chatLocation: strongSelf.chatLocation, subject: .scheduledMessages)
- (strongSelf.navigationController as? NavigationController)?.pushViewController(controller)
+ strongSelf.openScheduledMessages()
}
}
})
@@ -4986,8 +5018,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let strongSelf = self {
done(time)
if !strongSelf.presentationInterfaceState.isScheduledMessages {
- let controller = ChatControllerImpl(context: strongSelf.context, chatLocation: strongSelf.chatLocation, subject: .scheduledMessages)
- (strongSelf.navigationController as? NavigationController)?.pushViewController(controller)
+ strongSelf.openScheduledMessages()
}
}
})
@@ -5171,8 +5202,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let strongSelf = self {
done(time)
if !strongSelf.presentationInterfaceState.isScheduledMessages {
- let controller = ChatControllerImpl(context: strongSelf.context, chatLocation: strongSelf.chatLocation, subject: .scheduledMessages)
- (strongSelf.navigationController as? NavigationController)?.pushViewController(controller)
+ strongSelf.openScheduledMessages()
}
}
})
@@ -7535,4 +7565,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.updateSlowmodeStatusDisposable.set(nil)
}
}
+
+ private func openScheduledMessages() {
+ let controller = ChatControllerImpl(context: self.context, chatLocation: self.chatLocation, subject: .scheduledMessages)
+ self.present(controller, in: .window(.root))
+ }
+
+ override public func dismiss(completion: (() -> Void)? = nil) {
+ if !self.isDismissed {
+ self.isDismissed = true
+ self.chatDisplayNode.animateOut(completion: { [weak self] in
+ self?.presentingViewController?.dismiss(animated: false, completion: nil)
+ })
+ self.updateTransitionWhenPresentedAsModal?(0.0, .animated(duration: 0.2, curve: .easeInOut))
+ }
+ }
}
diff --git a/submodules/TelegramUI/TelegramUI/ChatControllerInteraction.swift b/submodules/TelegramUI/TelegramUI/ChatControllerInteraction.swift
index c36a16e51d..e1a7ce7cbc 100644
--- a/submodules/TelegramUI/TelegramUI/ChatControllerInteraction.swift
+++ b/submodules/TelegramUI/TelegramUI/ChatControllerInteraction.swift
@@ -95,6 +95,7 @@ public final class ChatControllerInteraction {
let editScheduledMessagesTime: ([MessageId]) -> Void
let performTextSelectionAction: (UInt32, String, TextSelectionAction) -> Void
let updateMessageReaction: (MessageId, String) -> Void
+ let openMessageReactions: (MessageId) -> Void
let requestMessageUpdate: (MessageId) -> Void
let cancelInteractiveKeyboardGestures: () -> Void
@@ -109,7 +110,7 @@ public final class ChatControllerInteraction {
var searchTextHighightState: String?
var seenOneTimeAnimatedMedia = Set()
- init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, TapLongTapOrDoubleTapGestureRecognizer?) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendCurrentMessage: @escaping (Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool, sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, chatControllerNode: @escaping () -> ASDisplayNode?, reactionContainerNode: @escaping () -> ReactionSelectionParentNode?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOption: @escaping (MessageId, Data) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, scheduleCurrentMessage: @escaping () -> Void, sendScheduledMessagesNow: @escaping ([MessageId]) -> Void, editScheduledMessagesTime: @escaping ([MessageId]) -> Void, performTextSelectionAction: @escaping (UInt32, String, TextSelectionAction) -> Void, updateMessageReaction: @escaping (MessageId, String) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings) {
+ init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, TapLongTapOrDoubleTapGestureRecognizer?) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendCurrentMessage: @escaping (Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool, sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, chatControllerNode: @escaping () -> ASDisplayNode?, reactionContainerNode: @escaping () -> ReactionSelectionParentNode?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOption: @escaping (MessageId, Data) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, scheduleCurrentMessage: @escaping () -> Void, sendScheduledMessagesNow: @escaping ([MessageId]) -> Void, editScheduledMessagesTime: @escaping ([MessageId]) -> Void, performTextSelectionAction: @escaping (UInt32, String, TextSelectionAction) -> Void, updateMessageReaction: @escaping (MessageId, String) -> Void, openMessageReactions: @escaping (MessageId) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings) {
self.openMessage = openMessage
self.openPeer = openPeer
self.openPeerMention = openPeerMention
@@ -158,6 +159,7 @@ public final class ChatControllerInteraction {
self.editScheduledMessagesTime = editScheduledMessagesTime
self.performTextSelectionAction = performTextSelectionAction
self.updateMessageReaction = updateMessageReaction
+ self.openMessageReactions = openMessageReactions
self.requestMessageUpdate = requestMessageUpdate
self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures
@@ -193,6 +195,7 @@ public final class ChatControllerInteraction {
}, editScheduledMessagesTime: { _ in
}, performTextSelectionAction: { _, _, _ in
}, updateMessageReaction: { _, _ in
+ }, openMessageReactions: { _ in
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
diff --git a/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift b/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift
index 9a77946636..1c2438c721 100644
--- a/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift
+++ b/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift
@@ -2100,4 +2100,16 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
}
}
+
+ func animateIn(completion: (() -> Void)? = nil) {
+ self.layer.animatePosition(from: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), to: self.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in
+ completion?()
+ })
+ }
+
+ func animateOut(completion: (() -> Void)? = nil) {
+ self.layer.animatePosition(from: self.layer.position, to: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, completion: { [weak self] _ in
+ completion?()
+ })
+ }
}
diff --git a/submodules/TelegramUI/TelegramUI/ChatInterfaceStateNavigationButtons.swift b/submodules/TelegramUI/TelegramUI/ChatInterfaceStateNavigationButtons.swift
index 8fc3cd71c8..aee93f6cd7 100644
--- a/submodules/TelegramUI/TelegramUI/ChatInterfaceStateNavigationButtons.swift
+++ b/submodules/TelegramUI/TelegramUI/ChatInterfaceStateNavigationButtons.swift
@@ -3,12 +3,14 @@ import UIKit
import Postbox
import TelegramCore
import TelegramPresentationData
+import AccountContext
enum ChatNavigationButtonAction {
case openChatInfo
case clearHistory
case cancelMessageSelection
case search
+ case dismiss
}
struct ChatNavigationButton: Equatable {
@@ -20,7 +22,7 @@ struct ChatNavigationButton: Equatable {
}
}
-func leftNavigationButtonForChatInterfaceState(_ presentationInterfaceState: ChatPresentationInterfaceState, strings: PresentationStrings, currentButton: ChatNavigationButton?, target: Any?, selector: Selector?) -> ChatNavigationButton? {
+func leftNavigationButtonForChatInterfaceState(_ presentationInterfaceState: ChatPresentationInterfaceState, subject: ChatControllerSubject?, strings: PresentationStrings, currentButton: ChatNavigationButton?, target: Any?, selector: Selector?) -> ChatNavigationButton? {
if let _ = presentationInterfaceState.interfaceState.selectionState {
if let currentButton = currentButton, currentButton.action == .clearHistory {
return currentButton
@@ -45,6 +47,13 @@ func leftNavigationButtonForChatInterfaceState(_ presentationInterfaceState: Cha
}
}
}
+ if let subject = subject, case .scheduledMessages = subject {
+ if let currentButton = currentButton, currentButton.action == .dismiss {
+ return currentButton
+ } else {
+ return ChatNavigationButton(action: .dismiss, buttonItem: UIBarButtonItem(title: strings.Common_Done, style: .plain, target: target, action: selector))
+ }
+ }
return nil
}
diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageDateAndStatusNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageDateAndStatusNode.swift
index bf13645ce6..a9203282a4 100644
--- a/submodules/TelegramUI/TelegramUI/ChatMessageDateAndStatusNode.swift
+++ b/submodules/TelegramUI/TelegramUI/ChatMessageDateAndStatusNode.swift
@@ -98,10 +98,13 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
private var impressionIcon: ASImageNode?
private var reactionNodes: [StatusReactionNode] = []
private var reactionCountNode: TextNode?
+ private var reactionButtonNode: HighlightTrackingButtonNode?
private var type: ChatMessageDateAndStatusType?
private var theme: ChatPresentationThemeData?
+ var openReactions: (() -> Void)?
+
override init() {
self.dateNode = TextNode()
self.dateNode.isUserInteractionEnabled = false
@@ -109,8 +112,6 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
super.init()
- self.isUserInteractionEnabled = false
-
self.addSubnode(self.dateNode)
}
@@ -561,7 +562,8 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
}
- node.frame = CGRect(origin: CGPoint(x: reactionOffset + 1, y: backgroundInsets.top + 1.0 + offset), size: layout.size)
+ node.frame = CGRect(origin: CGPoint(x: reactionOffset + 1.0, y: backgroundInsets.top + 1.0 + offset), size: layout.size)
+ reactionOffset += 1.0 + layout.size.width
} else if let reactionCountNode = strongSelf.reactionCountNode {
strongSelf.reactionCountNode = nil
if animated {
@@ -572,6 +574,27 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
reactionCountNode.removeFromSupernode()
}
}
+
+ if !strongSelf.reactionNodes.isEmpty {
+ if strongSelf.reactionButtonNode == nil {
+ let reactionButtonNode = HighlightTrackingButtonNode()
+ strongSelf.reactionButtonNode = reactionButtonNode
+ strongSelf.addSubnode(reactionButtonNode)
+ reactionButtonNode.addTarget(strongSelf, action: #selector(strongSelf.reactionButtonPressed), forControlEvents: .touchUpInside)
+ reactionButtonNode.highligthedChanged = { [weak strongSelf] highlighted in
+ guard let strongSelf = strongSelf else {
+ return
+ }
+ if highlighted {
+ strongSelf.reactionButtonPressed()
+ }
+ }
+ }
+ strongSelf.reactionButtonNode?.frame = CGRect(origin: CGPoint(x: leftInset - reactionInset + backgroundInsets.left - 5.0, y: backgroundInsets.top + 1.0 + offset - 5.0), size: CGSize(width: reactionOffset + 5.0 * 2.0, height: 20.0))
+ } else if let reactionButtonNode = strongSelf.reactionButtonNode {
+ strongSelf.reactionButtonNode = nil
+ reactionButtonNode.removeFromSupernode()
+ }
}
})
}
@@ -605,4 +628,17 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
}
return nil
}
+
+ @objc private func reactionButtonPressed() {
+ self.openReactions?()
+ }
+
+ override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
+ if let reactionButtonNode = self.reactionButtonNode {
+ if reactionButtonNode.frame.contains(point) {
+ return reactionButtonNode.view
+ }
+ }
+ return nil
+ }
}
diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageTextBubbleContentNode.swift
index 28e189b189..4a2c5d3a67 100644
--- a/submodules/TelegramUI/TelegramUI/ChatMessageTextBubbleContentNode.swift
+++ b/submodules/TelegramUI/TelegramUI/ChatMessageTextBubbleContentNode.swift
@@ -65,6 +65,13 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
self.textAccessibilityOverlayNode.openUrl = { [weak self] url in
self?.item?.controllerInteraction.openUrl(url, false, false)
}
+
+ self.statusNode.openReactions = { [weak self] in
+ guard let strongSelf = self, let item = strongSelf.item else {
+ return
+ }
+ item.controllerInteraction.openMessageReactions(item.message.id)
+ }
}
required init?(coder aDecoder: NSCoder) {
diff --git a/submodules/TelegramUI/TelegramUI/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/TelegramUI/ChatRecentActionsControllerNode.swift
index 3cdbbaf05d..dcc4f15462 100644
--- a/submodules/TelegramUI/TelegramUI/ChatRecentActionsControllerNode.swift
+++ b/submodules/TelegramUI/TelegramUI/ChatRecentActionsControllerNode.swift
@@ -411,6 +411,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, editScheduledMessagesTime: { _ in
}, performTextSelectionAction: { _, _, _ in
}, updateMessageReaction: { _, _ in
+ }, openMessageReactions: { _ in
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,
diff --git a/submodules/TelegramUI/TelegramUI/OverlayPlayerControllerNode.swift b/submodules/TelegramUI/TelegramUI/OverlayPlayerControllerNode.swift
index 76554ee22c..f3cd2f7bfe 100644
--- a/submodules/TelegramUI/TelegramUI/OverlayPlayerControllerNode.swift
+++ b/submodules/TelegramUI/TelegramUI/OverlayPlayerControllerNode.swift
@@ -113,6 +113,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
}, editScheduledMessagesTime: { _ in
}, performTextSelectionAction: { _, _, _ in
}, updateMessageReaction: { _, _ in
+ }, openMessageReactions: { _ in
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false))
diff --git a/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift b/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift
index 4702f224f8..f22b6c8c5d 100644
--- a/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift
+++ b/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift
@@ -287,6 +287,7 @@ public class PeerMediaCollectionController: TelegramBaseController {
}, editScheduledMessagesTime: { _ in
}, performTextSelectionAction: { _, _, _ in
}, updateMessageReaction: { _, _ in
+ }, openMessageReactions: { _ in
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
diff --git a/submodules/TelegramUI/TelegramUI_Xcode.xcodeproj/project.pbxproj b/submodules/TelegramUI/TelegramUI_Xcode.xcodeproj/project.pbxproj
index 994c9fab79..e46f7751ad 100644
--- a/submodules/TelegramUI/TelegramUI_Xcode.xcodeproj/project.pbxproj
+++ b/submodules/TelegramUI/TelegramUI_Xcode.xcodeproj/project.pbxproj
@@ -178,6 +178,7 @@
D06BB8821F58994B0084FC30 /* LegacyInstantVideoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06BB8811F58994B0084FC30 /* LegacyInstantVideoController.swift */; };
D06E0F8E1F79ABFB003CF3DD /* ChatLoadingNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06E0F8D1F79ABFB003CF3DD /* ChatLoadingNode.swift */; };
D06F1EA41F6C0A5D00FE8B74 /* ChatHistorySearchContainerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06F1EA31F6C0A5D00FE8B74 /* ChatHistorySearchContainerNode.swift */; };
+ D072F38423155EAF0009E66F /* MessageReactionListUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D072F38323155EAF0009E66F /* MessageReactionListUI.framework */; };
D0750C7822B2A13300BE5F6E /* UniversalMediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0750C7722B2A13300BE5F6E /* UniversalMediaPlayer.framework */; };
D0750C7A22B2A14300BE5F6E /* DeviceAccess.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0750C7922B2A14300BE5F6E /* DeviceAccess.framework */; };
D0750C7C22B2A14300BE5F6E /* TelegramPresentationData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0750C7B22B2A14300BE5F6E /* TelegramPresentationData.framework */; };
@@ -812,6 +813,7 @@
D06BB8811F58994B0084FC30 /* LegacyInstantVideoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyInstantVideoController.swift; sourceTree = ""; };
D06E0F8D1F79ABFB003CF3DD /* ChatLoadingNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatLoadingNode.swift; sourceTree = ""; };
D06F1EA31F6C0A5D00FE8B74 /* ChatHistorySearchContainerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatHistorySearchContainerNode.swift; sourceTree = ""; };
+ D072F38323155EAF0009E66F /* MessageReactionListUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MessageReactionListUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D073CE621DCBBE5D007511FD /* MessageSent.caf */ = {isa = PBXFileReference; lastKnownFileType = file; name = MessageSent.caf; path = TelegramUI/Sounds/MessageSent.caf; sourceTree = ""; };
D073CE641DCBC26B007511FD /* ServiceSoundManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceSoundManager.swift; sourceTree = ""; };
D073CE701DCBF23F007511FD /* DeclareEncodables.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeclareEncodables.swift; sourceTree = ""; };
@@ -1169,6 +1171,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ D072F38423155EAF0009E66F /* MessageReactionListUI.framework in Frameworks */,
D03E495D230868DF0049C28B /* PersistentStringHash.framework in Frameworks */,
D03E493C2308679D0049C28B /* InstantPageCache.framework in Frameworks */,
D03E4910230866280049C28B /* GridMessageSelectionNode.framework in Frameworks */,
@@ -1773,6 +1776,7 @@
D08D45281D5E340200A7428A /* Frameworks */ = {
isa = PBXGroup;
children = (
+ D072F38323155EAF0009E66F /* MessageReactionListUI.framework */,
D03E495C230868DF0049C28B /* PersistentStringHash.framework */,
D03E493B2308679D0049C28B /* InstantPageCache.framework */,
D03E490F230866280049C28B /* GridMessageSelectionNode.framework */,