mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-16 19:30:29 +00:00
Reaction list
This commit is contained in:
parent
2442653541
commit
6784f1bc02
@ -551,6 +551,9 @@
|
|||||||
<FileRef
|
<FileRef
|
||||||
location = "group:submodules/SettingsUI/SettingsUI_Xcode.xcodeproj">
|
location = "group:submodules/SettingsUI/SettingsUI_Xcode.xcodeproj">
|
||||||
</FileRef>
|
</FileRef>
|
||||||
|
<FileRef
|
||||||
|
location = "group:/Users/peter/build/telegram-temp/telegram-ios/submodules/MessageReactionListUI/MessageReactionListUI_Xcode.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
</Group>
|
</Group>
|
||||||
<Group
|
<Group
|
||||||
location = "container:"
|
location = "container:"
|
||||||
|
|||||||
@ -513,6 +513,7 @@ public func createPollController(context: AccountContext, peerId: PeerId, comple
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
controller.isOpaqueWhenInOverlay = true
|
controller.isOpaqueWhenInOverlay = true
|
||||||
|
controller.isModalWhenInOverlay = true
|
||||||
controller.blocksBackgroundWhenInOverlay = true
|
controller.blocksBackgroundWhenInOverlay = true
|
||||||
controller.experimentalSnapScrollToItem = true
|
controller.experimentalSnapScrollToItem = true
|
||||||
|
|
||||||
|
|||||||
@ -11,14 +11,17 @@ public protocol ContainableController: class {
|
|||||||
var displayNode: ASDisplayNode { get }
|
var displayNode: ASDisplayNode { get }
|
||||||
var isViewLoaded: Bool { get }
|
var isViewLoaded: Bool { get }
|
||||||
var isOpaqueWhenInOverlay: Bool { get }
|
var isOpaqueWhenInOverlay: Bool { get }
|
||||||
|
var isModalWhenInOverlay: Bool { get }
|
||||||
var blocksBackgroundWhenInOverlay: Bool { get }
|
var blocksBackgroundWhenInOverlay: Bool { get }
|
||||||
var ready: Promise<Bool> { get }
|
var ready: Promise<Bool> { get }
|
||||||
|
var updateTransitionWhenPresentedAsModal: ((CGFloat, ContainedViewLayoutTransition) -> Void)? { get set }
|
||||||
|
|
||||||
func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations
|
func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations
|
||||||
var deferScreenEdgeGestures: UIRectEdge { get }
|
var deferScreenEdgeGestures: UIRectEdge { get }
|
||||||
|
|
||||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition)
|
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition)
|
||||||
func updateToInterfaceOrientation(_ orientation: UIInterfaceOrientation)
|
func updateToInterfaceOrientation(_ orientation: UIInterfaceOrientation)
|
||||||
|
func updateModalTransition(_ value: CGFloat, transition: ContainedViewLayoutTransition)
|
||||||
|
|
||||||
func viewWillAppear(_ animated: Bool)
|
func viewWillAppear(_ animated: Bool)
|
||||||
func viewWillDisappear(_ animated: Bool)
|
func viewWillDisappear(_ animated: Bool)
|
||||||
|
|||||||
@ -124,6 +124,8 @@ public enum MasterDetailLayoutBlackout : Equatable {
|
|||||||
open class NavigationController: UINavigationController, ContainableController, UIGestureRecognizerDelegate {
|
open class NavigationController: UINavigationController, ContainableController, UIGestureRecognizerDelegate {
|
||||||
public var isOpaqueWhenInOverlay: Bool = true
|
public var isOpaqueWhenInOverlay: Bool = true
|
||||||
public var blocksBackgroundWhenInOverlay: Bool = true
|
public var blocksBackgroundWhenInOverlay: Bool = true
|
||||||
|
public var isModalWhenInOverlay: Bool = false
|
||||||
|
public var updateTransitionWhenPresentedAsModal: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
|
||||||
|
|
||||||
private let _ready = Promise<Bool>(true)
|
private let _ready = Promise<Bool>(true)
|
||||||
open var ready: Promise<Bool> {
|
open var ready: Promise<Bool> {
|
||||||
@ -551,7 +553,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||||||
self.controllerView.containerView.addSubview(record.controller.view)
|
self.controllerView.containerView.addSubview(record.controller.view)
|
||||||
record.controller.setIgnoreAppearanceMethodInvocations(false)
|
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
|
//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)
|
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
|
self.navigationTransitionCoordinator = navigationTransitionCoordinator
|
||||||
@ -571,7 +573,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} 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
|
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)
|
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
|
self.controllerView.inTransition = true
|
||||||
navigationTransitionCoordinator.animateCompletion(0.0, completion: { [weak self] in
|
navigationTransitionCoordinator.animateCompletion(0.0, completion: { [weak self] in
|
||||||
if let strongSelf = self {
|
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._viewControllers[index].transition = .none
|
||||||
}
|
}
|
||||||
strongSelf.navigationTransitionCoordinator = nil
|
strongSelf.navigationTransitionCoordinator = nil
|
||||||
@ -713,7 +715,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||||||
self.loadView()
|
self.loadView()
|
||||||
}
|
}
|
||||||
self.validLayout = layout
|
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)
|
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) {
|
public func updateToInterfaceOrientation(_ orientation: UIInterfaceOrientation) {
|
||||||
for record in self._viewControllers {
|
for record in self._viewControllers {
|
||||||
if let controller = record.controller as? ContainableController {
|
if let controller = record.controller as? ContainableController {
|
||||||
|
|||||||
@ -34,9 +34,8 @@ public final class PresentationContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
weak var volumeControlStatusBarNodeView: UIView?
|
|
||||||
|
|
||||||
var updateIsInteractionBlocked: ((Bool) -> Void)?
|
var updateIsInteractionBlocked: ((Bool) -> Void)?
|
||||||
|
var updateHasBlocked: ((Bool) -> Void)?
|
||||||
|
|
||||||
var updateHasOpaqueOverlay: ((Bool) -> Void)?
|
var updateHasOpaqueOverlay: ((Bool) -> Void)?
|
||||||
private(set) var hasOpaqueOverlay: Bool = false {
|
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 layout: ContainerViewLayout?
|
||||||
|
|
||||||
private var ready: Bool {
|
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) {
|
public func present(_ controller: ContainableController, on level: PresentationSurfaceLevel, blockInteraction: Bool = false, completion: @escaping () -> Void) {
|
||||||
let controllerReady = controller.ready.get()
|
let controllerReady = controller.ready.get()
|
||||||
|> filter({ $0 })
|
|> filter({ $0 })
|
||||||
@ -140,8 +154,9 @@ public final class PresentationContext {
|
|||||||
controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: orientations, compactSize: orientations)
|
controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: orientations, compactSize: orientations)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
controller.view.frame = CGRect(origin: CGPoint(), size: initialLayout.size)
|
let (controllerLayout, controllerFrame) = self.layoutForController(containerLayout: initialLayout, controller: controller)
|
||||||
controller.containerLayoutUpdated(initialLayout, transition: .immediate)
|
controller.view.frame = controllerFrame
|
||||||
|
controller.containerLayoutUpdated(controllerLayout, transition: .immediate)
|
||||||
var blockInteractionToken: Int?
|
var blockInteractionToken: Int?
|
||||||
if blockInteraction {
|
if blockInteraction {
|
||||||
blockInteractionToken = self.addBlockInteraction()
|
blockInteractionToken = self.addBlockInteraction()
|
||||||
@ -170,37 +185,32 @@ public final class PresentationContext {
|
|||||||
}
|
}
|
||||||
strongSelf.controllers.insert((controller, level), at: insertIndex ?? strongSelf.controllers.count)
|
strongSelf.controllers.insert((controller, level), at: insertIndex ?? strongSelf.controllers.count)
|
||||||
if let view = strongSelf.view, let layout = strongSelf.layout {
|
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
|
(controller as? UIViewController)?.navigation_setDismiss({ [weak controller] in
|
||||||
if let strongSelf = self, let controller = controller {
|
if let strongSelf = self, let controller = controller {
|
||||||
strongSelf.dismiss(controller)
|
strongSelf.dismiss(controller)
|
||||||
}
|
}
|
||||||
}, rootController: nil)
|
}, rootController: nil)
|
||||||
(controller as? UIViewController)?.setIgnoreAppearanceMethodInvocations(true)
|
(controller as? UIViewController)?.setIgnoreAppearanceMethodInvocations(true)
|
||||||
if layout != initialLayout {
|
if updatedControllerLayout != controllerLayout {
|
||||||
controller.view.frame = CGRect(origin: CGPoint(), size: layout.size)
|
controller.view.frame = updatedControllerFrame
|
||||||
if let topLevelSubview = strongSelf.topLevelSubview(for: level) {
|
if let topLevelSubview = strongSelf.topLevelSubview(for: level) {
|
||||||
view.insertSubview(controller.view, belowSubview: topLevelSubview)
|
view.insertSubview(controller.view, belowSubview: topLevelSubview)
|
||||||
} else {
|
} else {
|
||||||
if let volumeControlStatusBarNodeView = strongSelf.volumeControlStatusBarNodeView {
|
view.addSubview(controller.view)
|
||||||
view.insertSubview(controller.view, belowSubview: volumeControlStatusBarNodeView)
|
|
||||||
} else {
|
|
||||||
view.addSubview(controller.view)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
controller.containerLayoutUpdated(layout, transition: .immediate)
|
controller.containerLayoutUpdated(updatedControllerLayout, transition: .immediate)
|
||||||
} else {
|
} else {
|
||||||
if let topLevelSubview = strongSelf.topLevelSubview(for: level) {
|
if let topLevelSubview = strongSelf.topLevelSubview(for: level) {
|
||||||
view.insertSubview(controller.view, belowSubview: topLevelSubview)
|
view.insertSubview(controller.view, belowSubview: topLevelSubview)
|
||||||
} else {
|
} else {
|
||||||
if let volumeControlStatusBarNodeView = strongSelf.volumeControlStatusBarNodeView {
|
view.addSubview(controller.view)
|
||||||
view.insertSubview(controller.view, belowSubview: volumeControlStatusBarNodeView)
|
|
||||||
} else {
|
|
||||||
view.addSubview(controller.view)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(controller as? UIViewController)?.setIgnoreAppearanceMethodInvocations(false)
|
(controller as? UIViewController)?.setIgnoreAppearanceMethodInvocations(false)
|
||||||
view.layer.invalidateUpTheTree()
|
view.layer.invalidateUpTheTree()
|
||||||
|
strongSelf.updateViews()
|
||||||
controller.viewWillAppear(false)
|
controller.viewWillAppear(false)
|
||||||
if let controller = controller as? PresentableController {
|
if let controller = controller as? PresentableController {
|
||||||
controller.viewDidAppear(completion: { [weak self] in
|
controller.viewDidAppear(completion: { [weak self] in
|
||||||
@ -211,7 +221,6 @@ public final class PresentationContext {
|
|||||||
strongSelf.notifyAccessibilityScreenChanged()
|
strongSelf.notifyAccessibilityScreenChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
strongSelf.updateViews()
|
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
@ -225,7 +234,7 @@ public final class PresentationContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func dismiss(_ controller: ContainableController) {
|
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)
|
self.controllers.remove(at: index)
|
||||||
controller.viewWillDisappear(false)
|
controller.viewWillDisappear(false)
|
||||||
controller.view.removeFromSuperview()
|
controller.view.removeFromSuperview()
|
||||||
@ -242,7 +251,9 @@ public final class PresentationContext {
|
|||||||
self.readyChanged(wasReady: wasReady)
|
self.readyChanged(wasReady: wasReady)
|
||||||
} else if self.ready {
|
} else if self.ready {
|
||||||
for (controller, _) in self.controllers {
|
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 {
|
if let topLevelSubview = self.topLevelSubview {
|
||||||
view.insertSubview(controller.view, belowSubview: topLevelSubview)
|
view.insertSubview(controller.view, belowSubview: topLevelSubview)
|
||||||
} else {
|
} else {
|
||||||
if let volumeControlStatusBarNodeView = self.volumeControlStatusBarNodeView {
|
view.addSubview(controller.view)
|
||||||
view.insertSubview(controller.view, belowSubview: volumeControlStatusBarNodeView)
|
|
||||||
} else {
|
|
||||||
view.addSubview(controller.view)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
controller.view.frame = CGRect(origin: CGPoint(), size: layout.size)
|
let (controllerLayout, controllerFrame) = self.layoutForController(containerLayout: layout, controller: controller)
|
||||||
controller.containerLayoutUpdated(layout, transition: .immediate)
|
controller.view.frame = controllerFrame
|
||||||
|
controller.containerLayoutUpdated(controllerLayout, transition: .immediate)
|
||||||
if let controller = controller as? PresentableController {
|
if let controller = controller as? PresentableController {
|
||||||
controller.viewDidAppear(completion: { [weak self] in
|
controller.viewDidAppear(completion: { [weak self] in
|
||||||
self?.notifyAccessibilityScreenChanged()
|
self?.notifyAccessibilityScreenChanged()
|
||||||
@ -291,10 +299,18 @@ public final class PresentationContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private weak var currentModalController: ContainableController?
|
||||||
|
|
||||||
private func updateViews() {
|
private func updateViews() {
|
||||||
self.hasOpaqueOverlay = self.currentlyBlocksBackgroundWhenInOverlay
|
self.hasOpaqueOverlay = self.currentlyBlocksBackgroundWhenInOverlay
|
||||||
|
var modalController: ContainableController?
|
||||||
var topHasOpaque = false
|
var topHasOpaque = false
|
||||||
for (controller, _) in self.controllers.reversed() {
|
for (controller, _) in self.controllers.reversed() {
|
||||||
|
if controller.isModalWhenInOverlay {
|
||||||
|
if modalController == nil {
|
||||||
|
modalController = controller
|
||||||
|
}
|
||||||
|
}
|
||||||
if topHasOpaque {
|
if topHasOpaque {
|
||||||
controller.displayNode.accessibilityElementsHidden = true
|
controller.displayNode.accessibilityElementsHidden = true
|
||||||
} else {
|
} else {
|
||||||
@ -304,16 +320,47 @@ public final class PresentationContext {
|
|||||||
controller.displayNode.accessibilityElementsHidden = false
|
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() {
|
private func notifyAccessibilityScreenChanged() {
|
||||||
UIAccessibility.post(notification: UIAccessibility.Notification.screenChanged, argument: nil)
|
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() {
|
for (controller, _) in self.controllers.reversed() {
|
||||||
if controller.isViewLoaded {
|
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
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import SwiftSignalKit
|
|||||||
|
|
||||||
public protocol StatusBarHost {
|
public protocol StatusBarHost {
|
||||||
var statusBarFrame: CGRect { get }
|
var statusBarFrame: CGRect { get }
|
||||||
var statusBarStyle: UIStatusBarStyle { get set }
|
var statusBarStyle: UIStatusBarStyle { get }
|
||||||
var statusBarWindow: UIView? { get }
|
var statusBarWindow: UIView? { get }
|
||||||
var statusBarView: UIView? { get }
|
var statusBarView: UIView? { get }
|
||||||
|
|
||||||
@ -11,4 +11,6 @@ public protocol StatusBarHost {
|
|||||||
var keyboardView: UIView? { get }
|
var keyboardView: UIView? { get }
|
||||||
|
|
||||||
var handleVolumeControl: Signal<Bool, NoError> { get }
|
var handleVolumeControl: Signal<Bool, NoError> { get }
|
||||||
|
|
||||||
|
func setStatusBarStyle(_ style: UIStatusBarStyle, animated: Bool)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -76,7 +76,7 @@ class StatusBarManager {
|
|||||||
private let volumeControlStatusBarNode: VolumeControlStatusBarNode
|
private let volumeControlStatusBarNode: VolumeControlStatusBarNode
|
||||||
|
|
||||||
private var surfaces: [StatusBarSurface] = []
|
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)?
|
var inCallNavigate: (() -> Void)?
|
||||||
|
|
||||||
@ -111,8 +111,8 @@ class StatusBarManager {
|
|||||||
self?.volumeControlStatusBarNode.allowsGroupOpacity = false
|
self?.volumeControlStatusBarNode.allowsGroupOpacity = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if let (withSafeInsets, forceInCallStatusBarText, forceHiddenBySystemWindows) = self.validParams {
|
if let (withSafeInsets, forceInCallStatusBarText, forceHiddenBySystemWindows, forceAppearance) = self.validParams {
|
||||||
self.updateSurfaces(self.surfaces, withSafeInsets: withSafeInsets, forceInCallStatusBarText: forceInCallStatusBarText, forceHiddenBySystemWindows: forceHiddenBySystemWindows, animated: false, alphaTransition: .animated(duration: 0.2, curve: .easeInOut))
|
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
|
self.volumeTimer = nil
|
||||||
if let (withSafeInsets, forceInCallStatusBarText, forceHiddenBySystemWindows) = self.validParams {
|
if let (withSafeInsets, forceInCallStatusBarText, forceHiddenBySystemWindows, forceAppearance) = self.validParams {
|
||||||
self.updateSurfaces(self.surfaces, withSafeInsets: withSafeInsets, forceInCallStatusBarText: forceInCallStatusBarText, forceHiddenBySystemWindows: forceHiddenBySystemWindows, animated: false, alphaTransition: .animated(duration: 0.2, curve: .easeInOut))
|
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
|
let previousSurfaces = self.surfaces
|
||||||
self.surfaces = 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
|
let statusBarFrame = self.host.statusBarFrame
|
||||||
guard let statusBarView = self.host.statusBarView else {
|
guard let statusBarView = self.host.statusBarView else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.validParams = (withSafeInsets, forceInCallStatusBarText, forceHiddenBySystemWindows)
|
self.validParams = (withSafeInsets, forceInCallStatusBarText, forceHiddenBySystemWindows, forceAppearance)
|
||||||
|
|
||||||
if self.host.statusBarWindow?.isUserInteractionEnabled != (forceInCallStatusBarText == nil) {
|
if self.host.statusBarWindow?.isUserInteractionEnabled != (forceInCallStatusBarText == nil) {
|
||||||
self.host.statusBarWindow?.isUserInteractionEnabled = (forceInCallStatusBarText == nil)
|
self.host.statusBarWindow?.isUserInteractionEnabled = (forceInCallStatusBarText == nil)
|
||||||
@ -278,6 +278,17 @@ class StatusBarManager {
|
|||||||
}
|
}
|
||||||
self.volumeControlStatusBarNode.isDark = isDark
|
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 {
|
if let globalStatusBar = globalStatusBar, !forceHiddenBySystemWindows {
|
||||||
let statusBarStyle: UIStatusBarStyle
|
let statusBarStyle: UIStatusBarStyle
|
||||||
if forceInCallStatusBarText != nil {
|
if forceInCallStatusBarText != nil {
|
||||||
@ -286,7 +297,7 @@ class StatusBarManager {
|
|||||||
statusBarStyle = globalStatusBar.0 == .Black ? .default : .lightContent
|
statusBarStyle = globalStatusBar.0 == .Black ? .default : .lightContent
|
||||||
}
|
}
|
||||||
if self.host.statusBarStyle != statusBarStyle {
|
if self.host.statusBarStyle != statusBarStyle {
|
||||||
self.host.statusBarStyle = statusBarStyle
|
self.host.setStatusBarStyle(statusBarStyle, animated: animated)
|
||||||
}
|
}
|
||||||
if let statusBarWindow = self.host.statusBarWindow {
|
if let statusBarWindow = self.host.statusBarWindow {
|
||||||
alphaTransition.updateAlpha(layer: statusBarView.layer, alpha: globalStatusBar.1)
|
alphaTransition.updateAlpha(layer: statusBarView.layer, alpha: globalStatusBar.1)
|
||||||
|
|||||||
@ -88,6 +88,14 @@ open class ViewControllerPresentationArguments {
|
|||||||
public final var isOpaqueWhenInOverlay: Bool = false
|
public final var isOpaqueWhenInOverlay: Bool = false
|
||||||
public final var blocksBackgroundWhenInOverlay: Bool = false
|
public final var blocksBackgroundWhenInOverlay: Bool = false
|
||||||
public final var automaticallyControlPresentationContextLayout: Bool = true
|
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 {
|
public func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations {
|
||||||
return self.supportedOrientations
|
return self.supportedOrientations
|
||||||
@ -190,7 +198,7 @@ open class ViewControllerPresentationArguments {
|
|||||||
|
|
||||||
open var visualNavigationInsetHeight: CGFloat {
|
open var visualNavigationInsetHeight: CGFloat {
|
||||||
if let navigationBar = self.navigationBar {
|
if let navigationBar = self.navigationBar {
|
||||||
var height = navigationBar.frame.maxY
|
let height = navigationBar.frame.maxY
|
||||||
if let contentNode = navigationBar.contentNode, case .expansion = contentNode.mode {
|
if let contentNode = navigationBar.contentNode, case .expansion = contentNode.mode {
|
||||||
//height += contentNode.height
|
//height += contentNode.height
|
||||||
}
|
}
|
||||||
@ -233,7 +241,7 @@ open class ViewControllerPresentationArguments {
|
|||||||
private func updateScrollToTopView() {
|
private func updateScrollToTopView() {
|
||||||
if self.scrollToTop != nil {
|
if self.scrollToTop != nil {
|
||||||
if let displayNode = self._displayNode , self.scrollToTopView == 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
|
scrollToTopView.action = { [weak self] in
|
||||||
if let scrollToTop = self?.scrollToTop {
|
if let scrollToTop = self?.scrollToTop {
|
||||||
scrollToTop()
|
scrollToTop()
|
||||||
@ -293,16 +301,16 @@ open class ViewControllerPresentationArguments {
|
|||||||
|
|
||||||
private func updateNavigationBarLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
private func updateNavigationBarLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0
|
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
|
let navigationBarOffset: CGFloat
|
||||||
if statusBarHeight.isZero {
|
if statusBarHeight.isZero {
|
||||||
navigationBarOffset = -20.0
|
navigationBarOffset = 0.0
|
||||||
} else {
|
} else {
|
||||||
navigationBarOffset = 0.0
|
navigationBarOffset = 0.0
|
||||||
}
|
}
|
||||||
var navigationBarFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationBarOffset), size: CGSize(width: layout.size.width, height: navigationBarHeight))
|
var navigationBarFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationBarOffset), size: CGSize(width: layout.size.width, height: navigationBarHeight))
|
||||||
if layout.statusBarHeight == nil {
|
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 {
|
if !self.displayNavigationBar {
|
||||||
@ -332,7 +340,7 @@ open class ViewControllerPresentationArguments {
|
|||||||
if !self.isViewLoaded {
|
if !self.isViewLoaded {
|
||||||
self.loadView()
|
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 {
|
if let _ = layout.statusBarHeight {
|
||||||
self.statusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 40.0))
|
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]) {
|
open func navigationStackConfigurationUpdated(next: [ViewController]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -377,6 +389,10 @@ open class ViewControllerPresentationArguments {
|
|||||||
self.blocksBackgroundWhenInOverlay = true
|
self.blocksBackgroundWhenInOverlay = true
|
||||||
self.isOpaqueWhenInOverlay = true
|
self.isOpaqueWhenInOverlay = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.isModalWhenInOverlay {
|
||||||
|
self.displayNode.clipsToBounds = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func requestLayout(transition: ContainedViewLayoutTransition) {
|
public func requestLayout(transition: ContainedViewLayoutTransition) {
|
||||||
@ -580,7 +596,7 @@ private func traceViewVisibility(view: UIView, rect: CGRect) -> Bool {
|
|||||||
if view.window == nil {
|
if view.window == nil {
|
||||||
return false
|
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)
|
let viewFrame = view.convert(rect, to: superview)
|
||||||
for i in (index + 1) ..< siblings.count {
|
for i in (index + 1) ..< siblings.count {
|
||||||
if siblings[i].frame.contains(viewFrame) {
|
if siblings[i].frame.contains(viewFrame) {
|
||||||
|
|||||||
@ -344,10 +344,6 @@ public class Window1 {
|
|||||||
|
|
||||||
private var isInteractionBlocked = false
|
private var isInteractionBlocked = false
|
||||||
|
|
||||||
/*private var accessibilityElements: [Any]? {
|
|
||||||
return self.viewController?.view.accessibilityElements
|
|
||||||
}*/
|
|
||||||
|
|
||||||
public init(hostView: WindowHostView, statusBarHost: StatusBarHost?) {
|
public init(hostView: WindowHostView, statusBarHost: StatusBarHost?) {
|
||||||
self.hostView = hostView
|
self.hostView = hostView
|
||||||
|
|
||||||
@ -377,6 +373,8 @@ public class Window1 {
|
|||||||
self.presentationContext = PresentationContext()
|
self.presentationContext = PresentationContext()
|
||||||
self.overlayPresentationContext = GlobalOverlayPresentationContext(statusBarHost: statusBarHost, parentView: self.hostView.aboveStatusBarView)
|
self.overlayPresentationContext = GlobalOverlayPresentationContext(statusBarHost: statusBarHost, parentView: self.hostView.aboveStatusBarView)
|
||||||
|
|
||||||
|
self.presentationContextContainerView = UIView()
|
||||||
|
|
||||||
self.presentationContext.updateIsInteractionBlocked = { [weak self] value in
|
self.presentationContext.updateIsInteractionBlocked = { [weak self] value in
|
||||||
self?.isInteractionBlocked = value
|
self?.isInteractionBlocked = value
|
||||||
}
|
}
|
||||||
@ -385,6 +383,10 @@ public class Window1 {
|
|||||||
self?._rootController?.displayNode.accessibilityElementsHidden = value
|
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.hostView.present = { [weak self] controller, level, blockInteraction, completion in
|
||||||
self?.present(controller, on: level, blockInteraction: blockInteraction, completion: completion)
|
self?.present(controller, on: level, blockInteraction: blockInteraction, completion: completion)
|
||||||
}
|
}
|
||||||
@ -440,12 +442,7 @@ public class Window1 {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/*self.hostView.getAccessibilityElements = { [weak self] in
|
self.presentationContext.view = self.presentationContextContainerView
|
||||||
return self?.accessibilityElements
|
|
||||||
}*/
|
|
||||||
|
|
||||||
self.presentationContext.view = self.hostView.containerView
|
|
||||||
self.presentationContext.volumeControlStatusBarNodeView = self.volumeControlStatusBarNode.view
|
|
||||||
self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout, hasOnScreenNavigation: self.hostView.hasOnScreenNavigation), transition: .immediate)
|
self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout, hasOnScreenNavigation: self.hostView.hasOnScreenNavigation), transition: .immediate)
|
||||||
self.overlayPresentationContext.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.windowPanRecognizer = recognizer
|
||||||
self.hostView.containerView.addGestureRecognizer(recognizer)
|
self.hostView.containerView.addGestureRecognizer(recognizer)
|
||||||
|
|
||||||
|
self.hostView.containerView.addSubview(self.presentationContextContainerView)
|
||||||
|
|
||||||
self.hostView.containerView.addSubview(self.volumeControlStatusBar)
|
self.hostView.containerView.addSubview(self.volumeControlStatusBar)
|
||||||
self.hostView.containerView.addSubview(self.volumeControlStatusBarNode.view)
|
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 result
|
||||||
}
|
}
|
||||||
return self.viewController?.view.hitTest(point, with: event)
|
return self.viewController?.view.hitTest(point, with: event)
|
||||||
@ -697,7 +696,19 @@ public class Window1 {
|
|||||||
|
|
||||||
if let rootController = self._rootController {
|
if let rootController = self._rootController {
|
||||||
if !self.windowLayout.size.width.isZero && !self.windowLayout.size.height.isZero {
|
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)
|
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] = []
|
private var _topLevelOverlayControllers: [ContainableController] = []
|
||||||
public var topLevelOverlayControllers: [ContainableController] {
|
public var topLevelOverlayControllers: [ContainableController] {
|
||||||
get {
|
get {
|
||||||
@ -721,18 +752,17 @@ public class Window1 {
|
|||||||
let layout = containedLayoutForWindowLayout(self.windowLayout, hasOnScreenNavigation: self.hostView.hasOnScreenNavigation)
|
let layout = containedLayoutForWindowLayout(self.windowLayout, hasOnScreenNavigation: self.hostView.hasOnScreenNavigation)
|
||||||
for controller in self._topLevelOverlayControllers {
|
for controller in self._topLevelOverlayControllers {
|
||||||
controller.containerLayoutUpdated(layout, transition: .immediate)
|
controller.containerLayoutUpdated(layout, transition: .immediate)
|
||||||
|
self.insertContentViewAtTop(controller.view)
|
||||||
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.presentationContext.topLevelSubview = self._topLevelOverlayControllers.first?.view
|
self.presentationContext.topLevelSubview = self._topLevelOverlayControllers.first?.view
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var dimView: UIView? = nil
|
||||||
|
|
||||||
|
private let presentationContextContainerView: UIView
|
||||||
|
|
||||||
public var coveringView: WindowCoveringView? {
|
public var coveringView: WindowCoveringView? {
|
||||||
didSet {
|
didSet {
|
||||||
if self.coveringView !== oldValue {
|
if self.coveringView !== oldValue {
|
||||||
@ -746,7 +776,7 @@ public class Window1 {
|
|||||||
coveringView.layer.removeAnimation(forKey: "opacity")
|
coveringView.layer.removeAnimation(forKey: "opacity")
|
||||||
coveringView.layer.allowsGroupOpacity = false
|
coveringView.layer.allowsGroupOpacity = false
|
||||||
coveringView.alpha = 1.0
|
coveringView.alpha = 1.0
|
||||||
self.hostView.containerView.insertSubview(coveringView, belowSubview: self.volumeControlStatusBarNode.view)
|
self.insertCoveringView(coveringView)
|
||||||
if !self.windowLayout.size.width.isZero {
|
if !self.windowLayout.size.width.isZero {
|
||||||
coveringView.frame = CGRect(origin: CGPoint(), size: self.windowLayout.size)
|
coveringView.frame = CGRect(origin: CGPoint(), size: self.windowLayout.size)
|
||||||
coveringView.updateLayout(self.windowLayout.size)
|
coveringView.updateLayout(self.windowLayout.size)
|
||||||
@ -756,6 +786,8 @@ public class Window1 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var appliedModalLevel: CGFloat = 0.0
|
||||||
|
|
||||||
private func layoutSubviews() {
|
private func layoutSubviews() {
|
||||||
var hasPreview = false
|
var hasPreview = false
|
||||||
var updatedHasPreview = false
|
var updatedHasPreview = false
|
||||||
@ -771,11 +803,19 @@ public class Window1 {
|
|||||||
updatedHasPreview = true
|
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
|
self.tracingStatusBarsInvalidated = false
|
||||||
|
|
||||||
if self.statusBarHidden {
|
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 {
|
} else {
|
||||||
var statusBarSurfaces: [StatusBarSurface] = []
|
var statusBarSurfaces: [StatusBarSurface] = []
|
||||||
for layers in self.hostView.containerView.layer.traceableLayerSurfaces(withTag: WindowTracingTags.statusBar) {
|
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
|
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] = []
|
var keyboardSurfaces: [KeyboardSurface] = []
|
||||||
@ -969,13 +1009,31 @@ public class Window1 {
|
|||||||
let childLayoutUpdated = self.updatedContainerLayout != childLayout
|
let childLayoutUpdated = self.updatedContainerLayout != childLayout
|
||||||
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 {
|
if childLayoutUpdated {
|
||||||
var rootLayout = childLayout
|
var rootLayout = childLayout
|
||||||
let rootTransition = updatingLayout.transition
|
let rootTransition = updatingLayout.transition
|
||||||
if self.presentationContext.isCurrentlyOpaque {
|
if self.presentationContext.isCurrentlyOpaque {
|
||||||
rootLayout.inputHeight = nil
|
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.presentationContext.containerLayoutUpdated(childLayout, transition: updatingLayout.transition)
|
||||||
self.overlayPresentationContext.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 = {}) {
|
public func present(_ controller: ContainableController, on level: PresentationSurfaceLevel, blockInteraction: Bool = false, completion: @escaping () -> Void = {}) {
|
||||||
self.presentationContext.present(controller, on: level, blockInteraction: blockInteraction, completion: completion)
|
self.presentationContext.present(controller, on: level, blockInteraction: blockInteraction, completion: completion)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -230,6 +230,7 @@ open class ItemListController<Entry: ItemListNodeEntry>: ViewController, KeyShor
|
|||||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: theme), strings: NavigationBarStrings(presentationStrings: strings)))
|
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: theme), strings: NavigationBarStrings(presentationStrings: strings)))
|
||||||
|
|
||||||
self.isOpaqueWhenInOverlay = true
|
self.isOpaqueWhenInOverlay = true
|
||||||
|
self.isModalWhenInOverlay = true
|
||||||
self.blocksBackgroundWhenInOverlay = true
|
self.blocksBackgroundWhenInOverlay = true
|
||||||
|
|
||||||
self.statusBar.statusBarStyle = theme.rootController.statusBarStyle.style
|
self.statusBar.statusBarStyle = theme.rootController.statusBarStyle.style
|
||||||
@ -478,6 +479,7 @@ open class ItemListController<Entry: ItemListNodeEntry>: ViewController, KeyShor
|
|||||||
presentationArguments.completion?()
|
presentationArguments.completion?()
|
||||||
completion()
|
completion()
|
||||||
})
|
})
|
||||||
|
self.updateTransitionWhenPresentedAsModal?(1.0, .animated(duration: 0.5, curve: .spring))
|
||||||
} else {
|
} else {
|
||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
@ -506,6 +508,7 @@ open class ItemListController<Entry: ItemListNodeEntry>: ViewController, KeyShor
|
|||||||
if !self.isDismissed {
|
if !self.isDismissed {
|
||||||
self.isDismissed = true
|
self.isDismissed = true
|
||||||
(self.displayNode as! ItemListControllerNode<Entry>).animateOut(completion: completion)
|
(self.displayNode as! ItemListControllerNode<Entry>).animateOut(completion: completion)
|
||||||
|
self.updateTransitionWhenPresentedAsModal?(0.0, .animated(duration: 0.2, curve: .easeInOut))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
22
submodules/MessageReactionListUI/Info.plist
Normal file
22
submodules/MessageReactionListUI/Info.plist
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>$(PRODUCT_NAME)</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>FMWK</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@ -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 = "<group>"; };
|
||||||
|
D072F36723154C230009E66F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
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 = "<group>"; };
|
||||||
|
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 = "<group>"; };
|
||||||
|
/* 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 = "<group>";
|
||||||
|
};
|
||||||
|
D072F36423154C230009E66F /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
D072F36323154C230009E66F /* MessageReactionListUI.framework */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
D072F36523154C230009E66F /* Sources */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
D072F36623154C230009E66F /* MessageReactionListUI.h */,
|
||||||
|
D072F37F23154E390009E66F /* MessageReactionListController.swift */,
|
||||||
|
D072F38523156B450009E66F /* MessageReactionCategoryNode.swift */,
|
||||||
|
);
|
||||||
|
path = Sources;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
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 = "<group>";
|
||||||
|
};
|
||||||
|
/* 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 */;
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<Bool>()
|
||||||
|
override public var ready: Promise<Bool> {
|
||||||
|
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<Bool>()
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// MessageReactionListUI.h
|
||||||
|
// MessageReactionListUI
|
||||||
|
//
|
||||||
|
// Created by Peter on 8/27/19.
|
||||||
|
// Copyright © 2019 Telegram Messenger LLP. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
//! 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 <MessageReactionListUI/PublicHeader.h>
|
||||||
|
|
||||||
|
|
||||||
216
submodules/TelegramCore/TelegramCore/MessageReactionList.swift
Normal file
216
submodules/TelegramCore/TelegramCore/MessageReactionList.swift
Normal file
@ -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<MessageReactionListCategoryState>
|
||||||
|
|
||||||
|
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<Api.MessageReactionsList, LoadReactionsError> 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<MessageReactionListState>()
|
||||||
|
public var state: Signal<MessageReactionListState, NoError> {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -450,6 +450,8 @@
|
|||||||
D07047B81F3DF2CD00F6A8D4 /* ManagedConsumePersonalMessagesActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07047B61F3DF2CD00F6A8D4 /* ManagedConsumePersonalMessagesActions.swift */; };
|
D07047B81F3DF2CD00F6A8D4 /* ManagedConsumePersonalMessagesActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07047B61F3DF2CD00F6A8D4 /* ManagedConsumePersonalMessagesActions.swift */; };
|
||||||
D07047BA1F3DF75500F6A8D4 /* ConsumePersonalMessageAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07047B91F3DF75500F6A8D4 /* ConsumePersonalMessageAction.swift */; };
|
D07047BA1F3DF75500F6A8D4 /* ConsumePersonalMessageAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07047B91F3DF75500F6A8D4 /* ConsumePersonalMessageAction.swift */; };
|
||||||
D07047BB1F3DF75500F6A8D4 /* 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 */; };
|
D073CE5D1DCB97F6007511FD /* ForwardSourceInfoAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D073CE5C1DCB97F6007511FD /* ForwardSourceInfoAttribute.swift */; };
|
||||||
D073CE601DCB9D14007511FD /* OutgoingMessageInfoAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D073CE5F1DCB9D14007511FD /* OutgoingMessageInfoAttribute.swift */; };
|
D073CE601DCB9D14007511FD /* OutgoingMessageInfoAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D073CE5F1DCB9D14007511FD /* OutgoingMessageInfoAttribute.swift */; };
|
||||||
D073CE6A1DCBCF17007511FD /* ViewCountMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0CE71D6224AD00955575 /* ViewCountMessageAttribute.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 = "<group>"; };
|
D07047B31F3DF1FE00F6A8D4 /* ConsumablePersonalMentionMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsumablePersonalMentionMessageAttribute.swift; sourceTree = "<group>"; };
|
||||||
D07047B61F3DF2CD00F6A8D4 /* ManagedConsumePersonalMessagesActions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedConsumePersonalMessagesActions.swift; sourceTree = "<group>"; };
|
D07047B61F3DF2CD00F6A8D4 /* ManagedConsumePersonalMessagesActions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedConsumePersonalMessagesActions.swift; sourceTree = "<group>"; };
|
||||||
D07047B91F3DF75500F6A8D4 /* ConsumePersonalMessageAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsumePersonalMessageAction.swift; sourceTree = "<group>"; };
|
D07047B91F3DF75500F6A8D4 /* ConsumePersonalMessageAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsumePersonalMessageAction.swift; sourceTree = "<group>"; };
|
||||||
|
D072F356231542740009E66F /* MessageReactionList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageReactionList.swift; sourceTree = "<group>"; };
|
||||||
D073CE5C1DCB97F6007511FD /* ForwardSourceInfoAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForwardSourceInfoAttribute.swift; sourceTree = "<group>"; };
|
D073CE5C1DCB97F6007511FD /* ForwardSourceInfoAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForwardSourceInfoAttribute.swift; sourceTree = "<group>"; };
|
||||||
D073CE5F1DCB9D14007511FD /* OutgoingMessageInfoAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutgoingMessageInfoAttribute.swift; sourceTree = "<group>"; };
|
D073CE5F1DCB9D14007511FD /* OutgoingMessageInfoAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutgoingMessageInfoAttribute.swift; sourceTree = "<group>"; };
|
||||||
D0750C8F22B2FD8300BE5F6E /* PeerAccessHash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerAccessHash.swift; sourceTree = "<group>"; };
|
D0750C8F22B2FD8300BE5F6E /* PeerAccessHash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerAccessHash.swift; sourceTree = "<group>"; };
|
||||||
@ -1606,6 +1609,7 @@
|
|||||||
D01AC91C1DD5DA5E00E8160F /* RequestMessageActionCallback.swift */,
|
D01AC91C1DD5DA5E00E8160F /* RequestMessageActionCallback.swift */,
|
||||||
D0AB262A21C3CE80008F6685 /* Polls.swift */,
|
D0AB262A21C3CE80008F6685 /* Polls.swift */,
|
||||||
D0329EA122FC5A7C00F9F071 /* MessageReactions.swift */,
|
D0329EA122FC5A7C00F9F071 /* MessageReactions.swift */,
|
||||||
|
D072F356231542740009E66F /* MessageReactionList.swift */,
|
||||||
D01AC9201DD5E7E500E8160F /* RequestEditMessage.swift */,
|
D01AC9201DD5E7E500E8160F /* RequestEditMessage.swift */,
|
||||||
D0DC354D1DE368F7000195EB /* RequestChatContextResults.swift */,
|
D0DC354D1DE368F7000195EB /* RequestChatContextResults.swift */,
|
||||||
D0DC354F1DE36900000195EB /* ChatContextResult.swift */,
|
D0DC354F1DE36900000195EB /* ChatContextResult.swift */,
|
||||||
@ -2453,6 +2457,7 @@
|
|||||||
D054649120738653002ECC1E /* SecureIdIDCardValue.swift in Sources */,
|
D054649120738653002ECC1E /* SecureIdIDCardValue.swift in Sources */,
|
||||||
D018EE052045E95000CBB130 /* CheckPeerChatServiceActions.swift in Sources */,
|
D018EE052045E95000CBB130 /* CheckPeerChatServiceActions.swift in Sources */,
|
||||||
D0F3A8A51E82C94C00B4C64C /* SynchronizeableChatInputState.swift in Sources */,
|
D0F3A8A51E82C94C00B4C64C /* SynchronizeableChatInputState.swift in Sources */,
|
||||||
|
D072F357231542740009E66F /* MessageReactionList.swift in Sources */,
|
||||||
D03B0CD71D62245300955575 /* TelegramGroup.swift in Sources */,
|
D03B0CD71D62245300955575 /* TelegramGroup.swift in Sources */,
|
||||||
D02609BF20C6EC08006C34AC /* Crypto.m in Sources */,
|
D02609BF20C6EC08006C34AC /* Crypto.m in Sources */,
|
||||||
D0B8438C1DA7CF50005F29E1 /* BotInfo.swift in Sources */,
|
D0B8438C1DA7CF50005F29E1 /* BotInfo.swift in Sources */,
|
||||||
@ -2783,6 +2788,7 @@
|
|||||||
D05A32E81E6F0B5C002760B4 /* RecentAccountSession.swift in Sources */,
|
D05A32E81E6F0B5C002760B4 /* RecentAccountSession.swift in Sources */,
|
||||||
D0F7B1E31E045C7B007EB8A5 /* RichText.swift in Sources */,
|
D0F7B1E31E045C7B007EB8A5 /* RichText.swift in Sources */,
|
||||||
D0575C2E22B922DF00A71A0E /* DeleteAccount.swift in Sources */,
|
D0575C2E22B922DF00A71A0E /* DeleteAccount.swift in Sources */,
|
||||||
|
D072F358231542740009E66F /* MessageReactionList.swift in Sources */,
|
||||||
D0FA8BB11E1FEC7E001E855B /* SecretChatEncryptionConfig.swift in Sources */,
|
D0FA8BB11E1FEC7E001E855B /* SecretChatEncryptionConfig.swift in Sources */,
|
||||||
D0B418AA1D7E0597004562A4 /* Download.swift in Sources */,
|
D0B418AA1D7E0597004562A4 /* Download.swift in Sources */,
|
||||||
D001F3F41E128A1C007A8C60 /* UpdatesApiUtils.swift in Sources */,
|
D001F3F41E128A1C007A8C60 /* UpdatesApiUtils.swift in Sources */,
|
||||||
|
|||||||
@ -57,12 +57,13 @@ private class ApplicationStatusBarHost: StatusBarHost {
|
|||||||
return self.application.statusBarFrame
|
return self.application.statusBarFrame
|
||||||
}
|
}
|
||||||
var statusBarStyle: UIStatusBarStyle {
|
var statusBarStyle: UIStatusBarStyle {
|
||||||
get {
|
return self.application.statusBarStyle
|
||||||
return self.application.statusBarStyle
|
|
||||||
} set(value) {
|
|
||||||
self.application.setStatusBarStyle(value, animated: false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setStatusBarStyle(_ style: UIStatusBarStyle, animated: Bool) {
|
||||||
|
self.application.setStatusBarStyle(style, animated: animated)
|
||||||
|
}
|
||||||
|
|
||||||
var statusBarWindow: UIView? {
|
var statusBarWindow: UIView? {
|
||||||
return self.application.value(forKey: "statusBarWindow") as? UIView
|
return self.application.value(forKey: "statusBarWindow") as? UIView
|
||||||
}
|
}
|
||||||
@ -237,7 +238,7 @@ final class SharedApplicationContext {
|
|||||||
let (window, hostView, aboveStatusbarWindow) = nativeWindowHostView()
|
let (window, hostView, aboveStatusbarWindow) = nativeWindowHostView()
|
||||||
self.mainWindow = Window1(hostView: hostView, statusBarHost: statusBarHost)
|
self.mainWindow = Window1(hostView: hostView, statusBarHost: statusBarHost)
|
||||||
self.aboveStatusbarWindow = aboveStatusbarWindow
|
self.aboveStatusbarWindow = aboveStatusbarWindow
|
||||||
window.backgroundColor = UIColor.white
|
hostView.containerView.backgroundColor = UIColor.white
|
||||||
self.window = window
|
self.window = window
|
||||||
self.nativeWindow = window
|
self.nativeWindow = window
|
||||||
|
|
||||||
@ -656,7 +657,7 @@ final class SharedApplicationContext {
|
|||||||
}
|
}
|
||||||
|> deliverOnMainQueue
|
|> deliverOnMainQueue
|
||||||
|> mapToSignal { accountManager, initialPresentationDataAndSettings -> Signal<(SharedApplicationContext, LoggingSettings), NoError> in
|
|> 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 legacyBasePath = appGroupUrl.path
|
||||||
let legacyCache = LegacyCache(path: legacyBasePath + "/Caches")
|
let legacyCache = LegacyCache(path: legacyBasePath + "/Caches")
|
||||||
|
|||||||
@ -44,6 +44,7 @@ import PeerInfoUI
|
|||||||
import RaiseToListen
|
import RaiseToListen
|
||||||
import UrlHandling
|
import UrlHandling
|
||||||
import ReactionSelectionNode
|
import ReactionSelectionNode
|
||||||
|
import MessageReactionListUI
|
||||||
|
|
||||||
public enum ChatControllerPeekActions {
|
public enum ChatControllerPeekActions {
|
||||||
case standard
|
case standard
|
||||||
@ -286,6 +287,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
|
|
||||||
private var updateSlowmodeStatusDisposable = MetaDisposable()
|
private var updateSlowmodeStatusDisposable = MetaDisposable()
|
||||||
private var updateSlowmodeStatusTimerValue: Int32?
|
private var updateSlowmodeStatusTimerValue: Int32?
|
||||||
|
|
||||||
|
private var isDismissed = false
|
||||||
|
|
||||||
public override var customData: Any? {
|
public override var customData: Any? {
|
||||||
return self.chatLocation
|
return self.chatLocation
|
||||||
@ -343,6 +346,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
super.init(context: context, navigationBarPresentationData: navigationBarPresentationData, mediaAccessoryPanelVisibility: mediaAccessoryPanelVisibility, locationBroadcastPanelSource: locationBroadcastPanelSource)
|
super.init(context: context, navigationBarPresentationData: navigationBarPresentationData, mediaAccessoryPanelVisibility: mediaAccessoryPanelVisibility, locationBroadcastPanelSource: locationBroadcastPanelSource)
|
||||||
|
|
||||||
self.blocksBackgroundWhenInOverlay = true
|
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)
|
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 {
|
if let strongSelf = self {
|
||||||
strongSelf.chatDisplayNode.sendCurrentMessage(scheduleTime: scheduleTime)
|
strongSelf.chatDisplayNode.sendCurrentMessage(scheduleTime: scheduleTime)
|
||||||
if !strongSelf.presentationInterfaceState.isScheduledMessages {
|
if !strongSelf.presentationInterfaceState.isScheduledMessages {
|
||||||
let controller = ChatControllerImpl(context: strongSelf.context, chatLocation: strongSelf.chatLocation, subject: .scheduledMessages)
|
strongSelf.openScheduledMessages()
|
||||||
(strongSelf.navigationController as? NavigationController)?.pushViewController(controller)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -1581,6 +1586,28 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
let _ = updateMessageReactionsInteractively(postbox: strongSelf.context.account.postbox, messageId: messageId, reaction: reaction).start()
|
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
|
}, requestMessageUpdate: { [weak self] id in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id)
|
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id)
|
||||||
@ -3856,8 +3883,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
}, openScheduledMessages: { [weak self] in
|
}, openScheduledMessages: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
let controller = ChatControllerImpl(context: strongSelf.context, chatLocation: strongSelf.chatLocation, subject: .scheduledMessages)
|
strongSelf.openScheduledMessages()
|
||||||
(strongSelf.navigationController as? NavigationController)?.pushViewController(controller)
|
|
||||||
}
|
}
|
||||||
}, statuses: ChatPanelInterfaceInteractionStatuses(editingMessage: self.editingMessage.get(), startingBot: self.startingBot.get(), unblockingPeer: self.unblockingPeer.get(), searching: self.searching.get(), loadingMessage: self.loadingMessage.get()))
|
}, 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) {
|
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 {
|
if self.leftNavigationButton != button {
|
||||||
var animated = transition.isAnimated
|
var animated = transition.isAnimated
|
||||||
if let currentButton = self.leftNavigationButton?.action, currentButton == button.action {
|
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) {
|
private func navigationButtonAction(_ action: ChatNavigationButtonAction) {
|
||||||
switch action {
|
switch action {
|
||||||
case .cancelMessageSelection:
|
case .cancelMessageSelection:
|
||||||
self.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } })
|
self.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } })
|
||||||
case .clearHistory:
|
case .clearHistory:
|
||||||
if case let .peer(peerId) = self.chatLocation {
|
if case let .peer(peerId) = self.chatLocation {
|
||||||
guard let peer = self.presentationInterfaceState.renderedPeer, let chatPeer = peer.peers[peer.peerId], let mainPeer = peer.chatMainPeer else {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } })
|
||||||
|
strongSelf.chatDisplayNode.historyNode.historyAppearsCleared = true
|
||||||
|
|
||||||
let text: String
|
let statusText: String
|
||||||
if peerId == self.context.account.peerId {
|
if strongSelf.presentationInterfaceState.isScheduledMessages {
|
||||||
text = self.presentationData.strings.Conversation_ClearSelfHistory
|
statusText = strongSelf.presentationData.strings.Undo_ScheduledMessagesCleared
|
||||||
} else if peerId.namespace == Namespaces.Peer.SecretChat {
|
} else if case .forEveryone = type {
|
||||||
text = self.presentationData.strings.Conversation_ClearSecretHistory
|
statusText = strongSelf.presentationData.strings.Undo_ChatClearedForBothSides
|
||||||
} else if peerId.namespace == Namespaces.Peer.CloudGroup || peerId.namespace == Namespaces.Peer.CloudChannel {
|
|
||||||
text = self.presentationData.strings.Conversation_ClearGroupHistory
|
|
||||||
} else {
|
} else {
|
||||||
text = self.presentationData.strings.Conversation_ClearPrivateHistory
|
statusText = strongSelf.presentationData.strings.Undo_ChatCleared
|
||||||
}
|
}
|
||||||
|
|
||||||
var canRemoveGlobally = false
|
strongSelf.present(UndoOverlayController(context: strongSelf.context, content: .removedChat(text: statusText), elevatedLayout: true, action: { shouldCommit in
|
||||||
let limitsConfiguration = self.context.currentLimitsConfiguration.with { $0 }
|
if shouldCommit {
|
||||||
if peerId.namespace == Namespaces.Peer.CloudUser && peerId != self.context.account.peerId {
|
let _ = clearHistoryInteractively(postbox: account.postbox, peerId: peerId, type: type).start(completed: {
|
||||||
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 {
|
|
||||||
self?.chatDisplayNode.historyNode.historyAppearsCleared = false
|
self?.chatDisplayNode.historyNode.historyAppearsCleared = false
|
||||||
}
|
})
|
||||||
}), in: .window(.root))
|
} else {
|
||||||
}
|
self?.chatDisplayNode.historyNode.historyAppearsCleared = false
|
||||||
|
}
|
||||||
let actionSheet = ActionSheetController(presentationTheme: self.presentationData.theme)
|
}), in: .window(.root))
|
||||||
var items: [ActionSheetItem] = []
|
}
|
||||||
|
|
||||||
if self.presentationInterfaceState.isScheduledMessages {
|
let actionSheet = ActionSheetController(presentationTheme: self.presentationData.theme)
|
||||||
items.append(ActionSheetButtonItem(title: self.presentationData.strings.ScheduledMessages_ClearAllConfirmation, color: .destructive, action: { [weak actionSheet] in
|
var items: [ActionSheetItem] = []
|
||||||
actionSheet?.dismissAnimated()
|
|
||||||
beginClear(.scheduledMessages)
|
if self.presentationInterfaceState.isScheduledMessages {
|
||||||
}))
|
items.append(ActionSheetButtonItem(title: self.presentationData.strings.ScheduledMessages_ClearAllConfirmation, color: .destructive, action: { [weak actionSheet] in
|
||||||
} else if canRemoveGlobally {
|
actionSheet?.dismissAnimated()
|
||||||
items.append(DeleteChatPeerActionSheetItem(context: self.context, peer: mainPeer, chatPeer: chatPeer, action: .clearHistory, strings: self.presentationData.strings))
|
beginClear(.scheduledMessages)
|
||||||
items.append(ActionSheetButtonItem(title: self.presentationData.strings.ChatList_DeleteForEveryone(mainPeer.compactDisplayTitle).0, color: .destructive, action: { [weak actionSheet] in
|
}))
|
||||||
beginClear(.forEveryone)
|
} else if canRemoveGlobally {
|
||||||
actionSheet?.dismissAnimated()
|
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
|
||||||
items.append(ActionSheetButtonItem(title: self.presentationData.strings.ChatList_DeleteForCurrentUser, color: .destructive, action: { [weak actionSheet] in
|
beginClear(.forEveryone)
|
||||||
actionSheet?.dismissAnimated()
|
actionSheet?.dismissAnimated()
|
||||||
beginClear(.forLocalPeer)
|
}))
|
||||||
}))
|
items.append(ActionSheetButtonItem(title: self.presentationData.strings.ChatList_DeleteForCurrentUser, color: .destructive, action: { [weak actionSheet] in
|
||||||
} else {
|
actionSheet?.dismissAnimated()
|
||||||
items.append(ActionSheetTextItem(title: text))
|
beginClear(.forLocalPeer)
|
||||||
items.append(ActionSheetButtonItem(title: self.presentationData.strings.Conversation_ClearAll, color: .destructive, action: { [weak actionSheet] in
|
}))
|
||||||
actionSheet?.dismissAnimated()
|
} else {
|
||||||
beginClear(.forLocalPeer)
|
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: [
|
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
|
||||||
ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
|
ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
|
||||||
actionSheet?.dismissAnimated()
|
actionSheet?.dismissAnimated()
|
||||||
})
|
})
|
||||||
])])
|
])])
|
||||||
|
|
||||||
self.chatDisplayNode.dismissInput()
|
self.chatDisplayNode.dismissInput()
|
||||||
self.present(actionSheet, in: .window(.root))
|
self.present(actionSheet, in: .window(.root))
|
||||||
}
|
}
|
||||||
case .openChatInfo:
|
case .openChatInfo:
|
||||||
switch self.chatLocationInfoData {
|
switch self.chatLocationInfoData {
|
||||||
case let .peer(peerView):
|
case let .peer(peerView):
|
||||||
self.navigationActionDisposable.set((peerView.get()
|
self.navigationActionDisposable.set((peerView.get()
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] peerView in
|
|> 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 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) {
|
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic) {
|
||||||
(strongSelf.navigationController as? NavigationController)?.pushViewController(infoController)
|
(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 {
|
if let strongSelf = self {
|
||||||
done(time)
|
done(time)
|
||||||
if !strongSelf.presentationInterfaceState.isScheduledMessages {
|
if !strongSelf.presentationInterfaceState.isScheduledMessages {
|
||||||
let controller = ChatControllerImpl(context: strongSelf.context, chatLocation: strongSelf.chatLocation, subject: .scheduledMessages)
|
strongSelf.openScheduledMessages()
|
||||||
(strongSelf.navigationController as? NavigationController)?.pushViewController(controller)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -4986,8 +5018,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
done(time)
|
done(time)
|
||||||
if !strongSelf.presentationInterfaceState.isScheduledMessages {
|
if !strongSelf.presentationInterfaceState.isScheduledMessages {
|
||||||
let controller = ChatControllerImpl(context: strongSelf.context, chatLocation: strongSelf.chatLocation, subject: .scheduledMessages)
|
strongSelf.openScheduledMessages()
|
||||||
(strongSelf.navigationController as? NavigationController)?.pushViewController(controller)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -5171,8 +5202,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
done(time)
|
done(time)
|
||||||
if !strongSelf.presentationInterfaceState.isScheduledMessages {
|
if !strongSelf.presentationInterfaceState.isScheduledMessages {
|
||||||
let controller = ChatControllerImpl(context: strongSelf.context, chatLocation: strongSelf.chatLocation, subject: .scheduledMessages)
|
strongSelf.openScheduledMessages()
|
||||||
(strongSelf.navigationController as? NavigationController)?.pushViewController(controller)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -7535,4 +7565,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
self.updateSlowmodeStatusDisposable.set(nil)
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -95,6 +95,7 @@ public final class ChatControllerInteraction {
|
|||||||
let editScheduledMessagesTime: ([MessageId]) -> Void
|
let editScheduledMessagesTime: ([MessageId]) -> Void
|
||||||
let performTextSelectionAction: (UInt32, String, TextSelectionAction) -> Void
|
let performTextSelectionAction: (UInt32, String, TextSelectionAction) -> Void
|
||||||
let updateMessageReaction: (MessageId, String) -> Void
|
let updateMessageReaction: (MessageId, String) -> Void
|
||||||
|
let openMessageReactions: (MessageId) -> Void
|
||||||
|
|
||||||
let requestMessageUpdate: (MessageId) -> Void
|
let requestMessageUpdate: (MessageId) -> Void
|
||||||
let cancelInteractiveKeyboardGestures: () -> Void
|
let cancelInteractiveKeyboardGestures: () -> Void
|
||||||
@ -109,7 +110,7 @@ public final class ChatControllerInteraction {
|
|||||||
var searchTextHighightState: String?
|
var searchTextHighightState: String?
|
||||||
var seenOneTimeAnimatedMedia = Set<MessageId>()
|
var seenOneTimeAnimatedMedia = Set<MessageId>()
|
||||||
|
|
||||||
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.openMessage = openMessage
|
||||||
self.openPeer = openPeer
|
self.openPeer = openPeer
|
||||||
self.openPeerMention = openPeerMention
|
self.openPeerMention = openPeerMention
|
||||||
@ -158,6 +159,7 @@ public final class ChatControllerInteraction {
|
|||||||
self.editScheduledMessagesTime = editScheduledMessagesTime
|
self.editScheduledMessagesTime = editScheduledMessagesTime
|
||||||
self.performTextSelectionAction = performTextSelectionAction
|
self.performTextSelectionAction = performTextSelectionAction
|
||||||
self.updateMessageReaction = updateMessageReaction
|
self.updateMessageReaction = updateMessageReaction
|
||||||
|
self.openMessageReactions = openMessageReactions
|
||||||
|
|
||||||
self.requestMessageUpdate = requestMessageUpdate
|
self.requestMessageUpdate = requestMessageUpdate
|
||||||
self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures
|
self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures
|
||||||
@ -193,6 +195,7 @@ public final class ChatControllerInteraction {
|
|||||||
}, editScheduledMessagesTime: { _ in
|
}, editScheduledMessagesTime: { _ in
|
||||||
}, performTextSelectionAction: { _, _, _ in
|
}, performTextSelectionAction: { _, _, _ in
|
||||||
}, updateMessageReaction: { _, _ in
|
}, updateMessageReaction: { _, _ in
|
||||||
|
}, openMessageReactions: { _ in
|
||||||
}, requestMessageUpdate: { _ in
|
}, requestMessageUpdate: { _ in
|
||||||
}, cancelInteractiveKeyboardGestures: {
|
}, cancelInteractiveKeyboardGestures: {
|
||||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||||
|
|||||||
@ -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?()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,12 +3,14 @@ import UIKit
|
|||||||
import Postbox
|
import Postbox
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
|
import AccountContext
|
||||||
|
|
||||||
enum ChatNavigationButtonAction {
|
enum ChatNavigationButtonAction {
|
||||||
case openChatInfo
|
case openChatInfo
|
||||||
case clearHistory
|
case clearHistory
|
||||||
case cancelMessageSelection
|
case cancelMessageSelection
|
||||||
case search
|
case search
|
||||||
|
case dismiss
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ChatNavigationButton: Equatable {
|
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 _ = presentationInterfaceState.interfaceState.selectionState {
|
||||||
if let currentButton = currentButton, currentButton.action == .clearHistory {
|
if let currentButton = currentButton, currentButton.action == .clearHistory {
|
||||||
return currentButton
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -98,10 +98,13 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
private var impressionIcon: ASImageNode?
|
private var impressionIcon: ASImageNode?
|
||||||
private var reactionNodes: [StatusReactionNode] = []
|
private var reactionNodes: [StatusReactionNode] = []
|
||||||
private var reactionCountNode: TextNode?
|
private var reactionCountNode: TextNode?
|
||||||
|
private var reactionButtonNode: HighlightTrackingButtonNode?
|
||||||
|
|
||||||
private var type: ChatMessageDateAndStatusType?
|
private var type: ChatMessageDateAndStatusType?
|
||||||
private var theme: ChatPresentationThemeData?
|
private var theme: ChatPresentationThemeData?
|
||||||
|
|
||||||
|
var openReactions: (() -> Void)?
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
self.dateNode = TextNode()
|
self.dateNode = TextNode()
|
||||||
self.dateNode.isUserInteractionEnabled = false
|
self.dateNode.isUserInteractionEnabled = false
|
||||||
@ -109,8 +112,6 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.isUserInteractionEnabled = false
|
|
||||||
|
|
||||||
self.addSubnode(self.dateNode)
|
self.addSubnode(self.dateNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -561,7 +562,8 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
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 {
|
} else if let reactionCountNode = strongSelf.reactionCountNode {
|
||||||
strongSelf.reactionCountNode = nil
|
strongSelf.reactionCountNode = nil
|
||||||
if animated {
|
if animated {
|
||||||
@ -572,6 +574,27 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
reactionCountNode.removeFromSupernode()
|
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
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -65,6 +65,13 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
self.textAccessibilityOverlayNode.openUrl = { [weak self] url in
|
self.textAccessibilityOverlayNode.openUrl = { [weak self] url in
|
||||||
self?.item?.controllerInteraction.openUrl(url, false, false)
|
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) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
|||||||
@ -411,6 +411,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
|||||||
}, editScheduledMessagesTime: { _ in
|
}, editScheduledMessagesTime: { _ in
|
||||||
}, performTextSelectionAction: { _, _, _ in
|
}, performTextSelectionAction: { _, _, _ in
|
||||||
}, updateMessageReaction: { _, _ in
|
}, updateMessageReaction: { _, _ in
|
||||||
|
}, openMessageReactions: { _ in
|
||||||
}, requestMessageUpdate: { _ in
|
}, requestMessageUpdate: { _ in
|
||||||
}, cancelInteractiveKeyboardGestures: {
|
}, cancelInteractiveKeyboardGestures: {
|
||||||
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,
|
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,
|
||||||
|
|||||||
@ -113,6 +113,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
|||||||
}, editScheduledMessagesTime: { _ in
|
}, editScheduledMessagesTime: { _ in
|
||||||
}, performTextSelectionAction: { _, _, _ in
|
}, performTextSelectionAction: { _, _, _ in
|
||||||
}, updateMessageReaction: { _, _ in
|
}, updateMessageReaction: { _, _ in
|
||||||
|
}, openMessageReactions: { _ in
|
||||||
}, requestMessageUpdate: { _ in
|
}, requestMessageUpdate: { _ in
|
||||||
}, cancelInteractiveKeyboardGestures: {
|
}, cancelInteractiveKeyboardGestures: {
|
||||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false))
|
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false))
|
||||||
|
|||||||
@ -287,6 +287,7 @@ public class PeerMediaCollectionController: TelegramBaseController {
|
|||||||
}, editScheduledMessagesTime: { _ in
|
}, editScheduledMessagesTime: { _ in
|
||||||
}, performTextSelectionAction: { _, _, _ in
|
}, performTextSelectionAction: { _, _, _ in
|
||||||
}, updateMessageReaction: { _, _ in
|
}, updateMessageReaction: { _, _ in
|
||||||
|
}, openMessageReactions: { _ in
|
||||||
}, requestMessageUpdate: { _ in
|
}, requestMessageUpdate: { _ in
|
||||||
}, cancelInteractiveKeyboardGestures: {
|
}, cancelInteractiveKeyboardGestures: {
|
||||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||||
|
|||||||
@ -178,6 +178,7 @@
|
|||||||
D06BB8821F58994B0084FC30 /* LegacyInstantVideoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06BB8811F58994B0084FC30 /* LegacyInstantVideoController.swift */; };
|
D06BB8821F58994B0084FC30 /* LegacyInstantVideoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06BB8811F58994B0084FC30 /* LegacyInstantVideoController.swift */; };
|
||||||
D06E0F8E1F79ABFB003CF3DD /* ChatLoadingNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06E0F8D1F79ABFB003CF3DD /* ChatLoadingNode.swift */; };
|
D06E0F8E1F79ABFB003CF3DD /* ChatLoadingNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06E0F8D1F79ABFB003CF3DD /* ChatLoadingNode.swift */; };
|
||||||
D06F1EA41F6C0A5D00FE8B74 /* ChatHistorySearchContainerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06F1EA31F6C0A5D00FE8B74 /* ChatHistorySearchContainerNode.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 */; };
|
D0750C7822B2A13300BE5F6E /* UniversalMediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0750C7722B2A13300BE5F6E /* UniversalMediaPlayer.framework */; };
|
||||||
D0750C7A22B2A14300BE5F6E /* DeviceAccess.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0750C7922B2A14300BE5F6E /* DeviceAccess.framework */; };
|
D0750C7A22B2A14300BE5F6E /* DeviceAccess.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0750C7922B2A14300BE5F6E /* DeviceAccess.framework */; };
|
||||||
D0750C7C22B2A14300BE5F6E /* TelegramPresentationData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0750C7B22B2A14300BE5F6E /* TelegramPresentationData.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 = "<group>"; };
|
D06BB8811F58994B0084FC30 /* LegacyInstantVideoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyInstantVideoController.swift; sourceTree = "<group>"; };
|
||||||
D06E0F8D1F79ABFB003CF3DD /* ChatLoadingNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatLoadingNode.swift; sourceTree = "<group>"; };
|
D06E0F8D1F79ABFB003CF3DD /* ChatLoadingNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatLoadingNode.swift; sourceTree = "<group>"; };
|
||||||
D06F1EA31F6C0A5D00FE8B74 /* ChatHistorySearchContainerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatHistorySearchContainerNode.swift; sourceTree = "<group>"; };
|
D06F1EA31F6C0A5D00FE8B74 /* ChatHistorySearchContainerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatHistorySearchContainerNode.swift; sourceTree = "<group>"; };
|
||||||
|
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 = "<group>"; };
|
D073CE621DCBBE5D007511FD /* MessageSent.caf */ = {isa = PBXFileReference; lastKnownFileType = file; name = MessageSent.caf; path = TelegramUI/Sounds/MessageSent.caf; sourceTree = "<group>"; };
|
||||||
D073CE641DCBC26B007511FD /* ServiceSoundManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceSoundManager.swift; sourceTree = "<group>"; };
|
D073CE641DCBC26B007511FD /* ServiceSoundManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceSoundManager.swift; sourceTree = "<group>"; };
|
||||||
D073CE701DCBF23F007511FD /* DeclareEncodables.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeclareEncodables.swift; sourceTree = "<group>"; };
|
D073CE701DCBF23F007511FD /* DeclareEncodables.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeclareEncodables.swift; sourceTree = "<group>"; };
|
||||||
@ -1169,6 +1171,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
D072F38423155EAF0009E66F /* MessageReactionListUI.framework in Frameworks */,
|
||||||
D03E495D230868DF0049C28B /* PersistentStringHash.framework in Frameworks */,
|
D03E495D230868DF0049C28B /* PersistentStringHash.framework in Frameworks */,
|
||||||
D03E493C2308679D0049C28B /* InstantPageCache.framework in Frameworks */,
|
D03E493C2308679D0049C28B /* InstantPageCache.framework in Frameworks */,
|
||||||
D03E4910230866280049C28B /* GridMessageSelectionNode.framework in Frameworks */,
|
D03E4910230866280049C28B /* GridMessageSelectionNode.framework in Frameworks */,
|
||||||
@ -1773,6 +1776,7 @@
|
|||||||
D08D45281D5E340200A7428A /* Frameworks */ = {
|
D08D45281D5E340200A7428A /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D072F38323155EAF0009E66F /* MessageReactionListUI.framework */,
|
||||||
D03E495C230868DF0049C28B /* PersistentStringHash.framework */,
|
D03E495C230868DF0049C28B /* PersistentStringHash.framework */,
|
||||||
D03E493B2308679D0049C28B /* InstantPageCache.framework */,
|
D03E493B2308679D0049C28B /* InstantPageCache.framework */,
|
||||||
D03E490F230866280049C28B /* GridMessageSelectionNode.framework */,
|
D03E490F230866280049C28B /* GridMessageSelectionNode.framework */,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user