mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-15 18:59:54 +00:00
Reaction list
This commit is contained in:
parent
2442653541
commit
6784f1bc02
@ -551,6 +551,9 @@
|
||||
<FileRef
|
||||
location = "group:submodules/SettingsUI/SettingsUI_Xcode.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:/Users/peter/build/telegram-temp/telegram-ios/submodules/MessageReactionListUI/MessageReactionListUI_Xcode.xcodeproj">
|
||||
</FileRef>
|
||||
</Group>
|
||||
<Group
|
||||
location = "container:"
|
||||
|
||||
@ -513,6 +513,7 @@ public func createPollController(context: AccountContext, peerId: PeerId, comple
|
||||
}
|
||||
}
|
||||
controller.isOpaqueWhenInOverlay = true
|
||||
controller.isModalWhenInOverlay = true
|
||||
controller.blocksBackgroundWhenInOverlay = true
|
||||
controller.experimentalSnapScrollToItem = true
|
||||
|
||||
|
||||
@ -11,14 +11,17 @@ public protocol ContainableController: class {
|
||||
var displayNode: ASDisplayNode { get }
|
||||
var isViewLoaded: Bool { get }
|
||||
var isOpaqueWhenInOverlay: Bool { get }
|
||||
var isModalWhenInOverlay: Bool { get }
|
||||
var blocksBackgroundWhenInOverlay: Bool { get }
|
||||
var ready: Promise<Bool> { get }
|
||||
var updateTransitionWhenPresentedAsModal: ((CGFloat, ContainedViewLayoutTransition) -> Void)? { get set }
|
||||
|
||||
func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations
|
||||
var deferScreenEdgeGestures: UIRectEdge { get }
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition)
|
||||
func updateToInterfaceOrientation(_ orientation: UIInterfaceOrientation)
|
||||
func updateModalTransition(_ value: CGFloat, transition: ContainedViewLayoutTransition)
|
||||
|
||||
func viewWillAppear(_ animated: Bool)
|
||||
func viewWillDisappear(_ animated: Bool)
|
||||
|
||||
@ -124,6 +124,8 @@ public enum MasterDetailLayoutBlackout : Equatable {
|
||||
open class NavigationController: UINavigationController, ContainableController, UIGestureRecognizerDelegate {
|
||||
public var isOpaqueWhenInOverlay: Bool = true
|
||||
public var blocksBackgroundWhenInOverlay: Bool = true
|
||||
public var isModalWhenInOverlay: Bool = false
|
||||
public var updateTransitionWhenPresentedAsModal: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
|
||||
|
||||
private let _ready = Promise<Bool>(true)
|
||||
open var ready: Promise<Bool> {
|
||||
@ -551,7 +553,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
self.controllerView.containerView.addSubview(record.controller.view)
|
||||
record.controller.setIgnoreAppearanceMethodInvocations(false)
|
||||
|
||||
if let _ = previousControllers.index(where: { $0.controller === record.controller }) {
|
||||
if let _ = previousControllers.firstIndex(where: { $0.controller === record.controller }) {
|
||||
//previousControllers[index].transition = .appearance
|
||||
let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.controllerView.containerView, topView: previousController.view, topNavigationBar: (previousController as? ViewController)?.navigationBar, bottomView: record.controller.view, bottomNavigationBar: (record.controller as? ViewController)?.navigationBar)
|
||||
self.navigationTransitionCoordinator = navigationTransitionCoordinator
|
||||
@ -571,7 +573,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if let index = self._viewControllers.index(where: { $0.controller === previousController }) {
|
||||
if let index = self._viewControllers.firstIndex(where: { $0.controller === previousController }) {
|
||||
self._viewControllers[index].transition = .appearance
|
||||
}
|
||||
let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Push, container: self.controllerView.containerView, topView: record.controller.view, topNavigationBar: (record.controller as? ViewController)?.navigationBar, bottomView: previousController.view, bottomNavigationBar: (previousController as? ViewController)?.navigationBar)
|
||||
@ -580,7 +582,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
self.controllerView.inTransition = true
|
||||
navigationTransitionCoordinator.animateCompletion(0.0, completion: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
if let index = strongSelf._viewControllers.index(where: { $0.controller === previousController }) {
|
||||
if let index = strongSelf._viewControllers.firstIndex(where: { $0.controller === previousController }) {
|
||||
strongSelf._viewControllers[index].transition = .none
|
||||
}
|
||||
strongSelf.navigationTransitionCoordinator = nil
|
||||
@ -713,7 +715,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
self.loadView()
|
||||
}
|
||||
self.validLayout = layout
|
||||
transition.updateFrame(view: self.view, frame: CGRect(origin: self.view.frame.origin, size: layout.size))
|
||||
//transition.updateFrame(view: self.view, frame: CGRect(origin: self.view.frame.origin, size: layout.size))
|
||||
|
||||
self.updateControllerLayouts(previousControllers: self._viewControllers, layout: layout, transition: transition)
|
||||
|
||||
@ -732,6 +734,33 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
}
|
||||
}
|
||||
|
||||
private var modalTransition: CGFloat = 0.0
|
||||
|
||||
public func updateModalTransition(_ value: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
if self.modalTransition == value {
|
||||
return
|
||||
}
|
||||
let scale = (self.view.bounds.width - 20.0 * 2.0) / self.view.bounds.width
|
||||
let cornerRadius = value * 10.0 / scale
|
||||
switch transition {
|
||||
case let .animated(duration, curve):
|
||||
let previous = self.displayNode.layer.cornerRadius
|
||||
self.displayNode.layer.cornerRadius = cornerRadius
|
||||
if !cornerRadius.isZero {
|
||||
self.displayNode.clipsToBounds = true
|
||||
}
|
||||
self.displayNode.layer.animate(from: previous as NSNumber, to: cornerRadius as NSNumber, keyPath: "cornerRadius", timingFunction: curve.timingFunction, duration: duration, completion: { [weak self] _ in
|
||||
if cornerRadius.isZero {
|
||||
self?.displayNode.clipsToBounds = false
|
||||
}
|
||||
})
|
||||
case .immediate:
|
||||
self.displayNode.layer.cornerRadius = cornerRadius
|
||||
self.displayNode.clipsToBounds = !cornerRadius.isZero
|
||||
}
|
||||
self.modalTransition = value
|
||||
}
|
||||
|
||||
public func updateToInterfaceOrientation(_ orientation: UIInterfaceOrientation) {
|
||||
for record in self._viewControllers {
|
||||
if let controller = record.controller as? ContainableController {
|
||||
|
||||
@ -34,9 +34,8 @@ public final class PresentationContext {
|
||||
}
|
||||
}
|
||||
|
||||
weak var volumeControlStatusBarNodeView: UIView?
|
||||
|
||||
var updateIsInteractionBlocked: ((Bool) -> Void)?
|
||||
var updateHasBlocked: ((Bool) -> Void)?
|
||||
|
||||
var updateHasOpaqueOverlay: ((Bool) -> Void)?
|
||||
private(set) var hasOpaqueOverlay: Bool = false {
|
||||
@ -47,6 +46,9 @@ public final class PresentationContext {
|
||||
}
|
||||
}
|
||||
|
||||
private var modalPresentationValue: CGFloat = 0.0
|
||||
var updateModalTransition: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
|
||||
|
||||
private var layout: ContainerViewLayout?
|
||||
|
||||
private var ready: Bool {
|
||||
@ -120,6 +122,18 @@ public final class PresentationContext {
|
||||
}
|
||||
}
|
||||
|
||||
private func layoutForController(containerLayout: ContainerViewLayout, controller: ContainableController) -> (ContainerViewLayout, CGRect) {
|
||||
if controller.isModalWhenInOverlay {
|
||||
let topInset = (containerLayout.statusBarHeight ?? 0.0) + 20.0
|
||||
var updatedLayout = containerLayout
|
||||
updatedLayout.statusBarHeight = nil
|
||||
updatedLayout.size.height -= topInset
|
||||
return (updatedLayout, CGRect(origin: CGPoint(x: 0.0, y: topInset), size: updatedLayout.size))
|
||||
} else {
|
||||
return (containerLayout, CGRect(origin: CGPoint(), size: containerLayout.size))
|
||||
}
|
||||
}
|
||||
|
||||
public func present(_ controller: ContainableController, on level: PresentationSurfaceLevel, blockInteraction: Bool = false, completion: @escaping () -> Void) {
|
||||
let controllerReady = controller.ready.get()
|
||||
|> filter({ $0 })
|
||||
@ -140,8 +154,9 @@ public final class PresentationContext {
|
||||
controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: orientations, compactSize: orientations)
|
||||
}
|
||||
}
|
||||
controller.view.frame = CGRect(origin: CGPoint(), size: initialLayout.size)
|
||||
controller.containerLayoutUpdated(initialLayout, transition: .immediate)
|
||||
let (controllerLayout, controllerFrame) = self.layoutForController(containerLayout: initialLayout, controller: controller)
|
||||
controller.view.frame = controllerFrame
|
||||
controller.containerLayoutUpdated(controllerLayout, transition: .immediate)
|
||||
var blockInteractionToken: Int?
|
||||
if blockInteraction {
|
||||
blockInteractionToken = self.addBlockInteraction()
|
||||
@ -170,37 +185,32 @@ public final class PresentationContext {
|
||||
}
|
||||
strongSelf.controllers.insert((controller, level), at: insertIndex ?? strongSelf.controllers.count)
|
||||
if let view = strongSelf.view, let layout = strongSelf.layout {
|
||||
let (updatedControllerLayout, updatedControllerFrame) = strongSelf.layoutForController(containerLayout: layout, controller: controller)
|
||||
|
||||
(controller as? UIViewController)?.navigation_setDismiss({ [weak controller] in
|
||||
if let strongSelf = self, let controller = controller {
|
||||
strongSelf.dismiss(controller)
|
||||
}
|
||||
}, rootController: nil)
|
||||
(controller as? UIViewController)?.setIgnoreAppearanceMethodInvocations(true)
|
||||
if layout != initialLayout {
|
||||
controller.view.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
if updatedControllerLayout != controllerLayout {
|
||||
controller.view.frame = updatedControllerFrame
|
||||
if let topLevelSubview = strongSelf.topLevelSubview(for: level) {
|
||||
view.insertSubview(controller.view, belowSubview: topLevelSubview)
|
||||
} else {
|
||||
if let volumeControlStatusBarNodeView = strongSelf.volumeControlStatusBarNodeView {
|
||||
view.insertSubview(controller.view, belowSubview: volumeControlStatusBarNodeView)
|
||||
} else {
|
||||
view.addSubview(controller.view)
|
||||
}
|
||||
view.addSubview(controller.view)
|
||||
}
|
||||
controller.containerLayoutUpdated(layout, transition: .immediate)
|
||||
controller.containerLayoutUpdated(updatedControllerLayout, transition: .immediate)
|
||||
} else {
|
||||
if let topLevelSubview = strongSelf.topLevelSubview(for: level) {
|
||||
view.insertSubview(controller.view, belowSubview: topLevelSubview)
|
||||
} else {
|
||||
if let volumeControlStatusBarNodeView = strongSelf.volumeControlStatusBarNodeView {
|
||||
view.insertSubview(controller.view, belowSubview: volumeControlStatusBarNodeView)
|
||||
} else {
|
||||
view.addSubview(controller.view)
|
||||
}
|
||||
view.addSubview(controller.view)
|
||||
}
|
||||
}
|
||||
(controller as? UIViewController)?.setIgnoreAppearanceMethodInvocations(false)
|
||||
view.layer.invalidateUpTheTree()
|
||||
strongSelf.updateViews()
|
||||
controller.viewWillAppear(false)
|
||||
if let controller = controller as? PresentableController {
|
||||
controller.viewDidAppear(completion: { [weak self] in
|
||||
@ -211,7 +221,6 @@ public final class PresentationContext {
|
||||
strongSelf.notifyAccessibilityScreenChanged()
|
||||
}
|
||||
}
|
||||
strongSelf.updateViews()
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
@ -225,7 +234,7 @@ public final class PresentationContext {
|
||||
}
|
||||
|
||||
private func dismiss(_ controller: ContainableController) {
|
||||
if let index = self.controllers.index(where: { $0.0 === controller }) {
|
||||
if let index = self.controllers.firstIndex(where: { $0.0 === controller }) {
|
||||
self.controllers.remove(at: index)
|
||||
controller.viewWillDisappear(false)
|
||||
controller.view.removeFromSuperview()
|
||||
@ -242,7 +251,9 @@ public final class PresentationContext {
|
||||
self.readyChanged(wasReady: wasReady)
|
||||
} else if self.ready {
|
||||
for (controller, _) in self.controllers {
|
||||
controller.containerLayoutUpdated(layout, transition: transition)
|
||||
let (controllerLayout, controllerFrame) = self.layoutForController(containerLayout: layout, controller: controller)
|
||||
controller.view.frame = controllerFrame
|
||||
controller.containerLayoutUpdated(controllerLayout, transition: transition)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -262,14 +273,11 @@ public final class PresentationContext {
|
||||
if let topLevelSubview = self.topLevelSubview {
|
||||
view.insertSubview(controller.view, belowSubview: topLevelSubview)
|
||||
} else {
|
||||
if let volumeControlStatusBarNodeView = self.volumeControlStatusBarNodeView {
|
||||
view.insertSubview(controller.view, belowSubview: volumeControlStatusBarNodeView)
|
||||
} else {
|
||||
view.addSubview(controller.view)
|
||||
}
|
||||
view.addSubview(controller.view)
|
||||
}
|
||||
controller.view.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
controller.containerLayoutUpdated(layout, transition: .immediate)
|
||||
let (controllerLayout, controllerFrame) = self.layoutForController(containerLayout: layout, controller: controller)
|
||||
controller.view.frame = controllerFrame
|
||||
controller.containerLayoutUpdated(controllerLayout, transition: .immediate)
|
||||
if let controller = controller as? PresentableController {
|
||||
controller.viewDidAppear(completion: { [weak self] in
|
||||
self?.notifyAccessibilityScreenChanged()
|
||||
@ -291,10 +299,18 @@ public final class PresentationContext {
|
||||
}
|
||||
}
|
||||
|
||||
private weak var currentModalController: ContainableController?
|
||||
|
||||
private func updateViews() {
|
||||
self.hasOpaqueOverlay = self.currentlyBlocksBackgroundWhenInOverlay
|
||||
var modalController: ContainableController?
|
||||
var topHasOpaque = false
|
||||
for (controller, _) in self.controllers.reversed() {
|
||||
if controller.isModalWhenInOverlay {
|
||||
if modalController == nil {
|
||||
modalController = controller
|
||||
}
|
||||
}
|
||||
if topHasOpaque {
|
||||
controller.displayNode.accessibilityElementsHidden = true
|
||||
} else {
|
||||
@ -304,16 +320,47 @@ public final class PresentationContext {
|
||||
controller.displayNode.accessibilityElementsHidden = false
|
||||
}
|
||||
}
|
||||
|
||||
if self.currentModalController !== modalController {
|
||||
if let currentModalController = self.currentModalController {
|
||||
currentModalController.updateTransitionWhenPresentedAsModal = nil
|
||||
if #available(iOSApplicationExtension 11.0, *) {
|
||||
currentModalController.displayNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner]
|
||||
}
|
||||
currentModalController.displayNode.layer.cornerRadius = 0.0
|
||||
}
|
||||
self.currentModalController = modalController
|
||||
if let modalController = modalController {
|
||||
if #available(iOSApplicationExtension 11.0, *) {
|
||||
modalController.displayNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
}
|
||||
modalController.displayNode.layer.cornerRadius = 10.0
|
||||
modalController.updateTransitionWhenPresentedAsModal = { [weak self, weak modalController] value, transition in
|
||||
guard let strongSelf = self, let modalController = modalController, modalController === strongSelf.currentModalController else {
|
||||
return
|
||||
}
|
||||
if strongSelf.modalPresentationValue != value {
|
||||
strongSelf.modalPresentationValue = value
|
||||
strongSelf.updateModalTransition?(value, transition)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if self.modalPresentationValue != 0.0 {
|
||||
self.modalPresentationValue = 0.0
|
||||
self.updateModalTransition?(0.0, .animated(duration: 0.3, curve: .spring))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func notifyAccessibilityScreenChanged() {
|
||||
UIAccessibility.post(notification: UIAccessibility.Notification.screenChanged, argument: nil)
|
||||
}
|
||||
|
||||
func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
func hitTest(view: UIView, point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
for (controller, _) in self.controllers.reversed() {
|
||||
if controller.isViewLoaded {
|
||||
if let result = controller.view.hitTest(point, with: event) {
|
||||
if let result = controller.view.hitTest(view.convert(point, to: controller.view), with: event) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ import SwiftSignalKit
|
||||
|
||||
public protocol StatusBarHost {
|
||||
var statusBarFrame: CGRect { get }
|
||||
var statusBarStyle: UIStatusBarStyle { get set }
|
||||
var statusBarStyle: UIStatusBarStyle { get }
|
||||
var statusBarWindow: UIView? { get }
|
||||
var statusBarView: UIView? { get }
|
||||
|
||||
@ -11,4 +11,6 @@ public protocol StatusBarHost {
|
||||
var keyboardView: UIView? { get }
|
||||
|
||||
var handleVolumeControl: Signal<Bool, NoError> { get }
|
||||
|
||||
func setStatusBarStyle(_ style: UIStatusBarStyle, animated: Bool)
|
||||
}
|
||||
|
||||
@ -76,7 +76,7 @@ class StatusBarManager {
|
||||
private let volumeControlStatusBarNode: VolumeControlStatusBarNode
|
||||
|
||||
private var surfaces: [StatusBarSurface] = []
|
||||
private var validParams: (withSafeInsets: Bool, forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool)?
|
||||
private var validParams: (withSafeInsets: Bool, forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool, UIStatusBarStyle?)?
|
||||
|
||||
var inCallNavigate: (() -> Void)?
|
||||
|
||||
@ -111,8 +111,8 @@ class StatusBarManager {
|
||||
self?.volumeControlStatusBarNode.allowsGroupOpacity = false
|
||||
})
|
||||
}
|
||||
if let (withSafeInsets, forceInCallStatusBarText, forceHiddenBySystemWindows) = self.validParams {
|
||||
self.updateSurfaces(self.surfaces, withSafeInsets: withSafeInsets, forceInCallStatusBarText: forceInCallStatusBarText, forceHiddenBySystemWindows: forceHiddenBySystemWindows, animated: false, alphaTransition: .animated(duration: 0.2, curve: .easeInOut))
|
||||
if let (withSafeInsets, forceInCallStatusBarText, forceHiddenBySystemWindows, forceAppearance) = self.validParams {
|
||||
self.updateSurfaces(self.surfaces, withSafeInsets: withSafeInsets, forceInCallStatusBarText: forceInCallStatusBarText, forceHiddenBySystemWindows: forceHiddenBySystemWindows, animated: false, forceAppearance: forceAppearance, alphaTransition: .animated(duration: 0.2, curve: .easeInOut))
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,24 +126,24 @@ class StatusBarManager {
|
||||
}
|
||||
})
|
||||
self.volumeTimer = nil
|
||||
if let (withSafeInsets, forceInCallStatusBarText, forceHiddenBySystemWindows) = self.validParams {
|
||||
self.updateSurfaces(self.surfaces, withSafeInsets: withSafeInsets, forceInCallStatusBarText: forceInCallStatusBarText, forceHiddenBySystemWindows: forceHiddenBySystemWindows, animated: false, alphaTransition: .animated(duration: 0.2, curve: .easeInOut))
|
||||
if let (withSafeInsets, forceInCallStatusBarText, forceHiddenBySystemWindows, forceAppearance) = self.validParams {
|
||||
self.updateSurfaces(self.surfaces, withSafeInsets: withSafeInsets, forceInCallStatusBarText: forceInCallStatusBarText, forceHiddenBySystemWindows: forceHiddenBySystemWindows, animated: false, forceAppearance: forceAppearance, alphaTransition: .animated(duration: 0.2, curve: .easeInOut))
|
||||
}
|
||||
}
|
||||
|
||||
func updateState(surfaces: [StatusBarSurface], withSafeInsets: Bool, forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool, animated: Bool) {
|
||||
func updateState(surfaces: [StatusBarSurface], withSafeInsets: Bool, forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool, forceAppearance: UIStatusBarStyle?, animated: Bool) {
|
||||
let previousSurfaces = self.surfaces
|
||||
self.surfaces = surfaces
|
||||
self.updateSurfaces(previousSurfaces, withSafeInsets: withSafeInsets, forceInCallStatusBarText: forceInCallStatusBarText, forceHiddenBySystemWindows: forceHiddenBySystemWindows, animated: animated, alphaTransition: .immediate)
|
||||
self.updateSurfaces(previousSurfaces, withSafeInsets: withSafeInsets, forceInCallStatusBarText: forceInCallStatusBarText, forceHiddenBySystemWindows: forceHiddenBySystemWindows, animated: animated, forceAppearance: forceAppearance, alphaTransition: .immediate)
|
||||
}
|
||||
|
||||
private func updateSurfaces(_ previousSurfaces: [StatusBarSurface], withSafeInsets: Bool, forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool, animated: Bool, alphaTransition: ContainedViewLayoutTransition) {
|
||||
private func updateSurfaces(_ previousSurfaces: [StatusBarSurface], withSafeInsets: Bool, forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool, animated: Bool, forceAppearance: UIStatusBarStyle?, alphaTransition: ContainedViewLayoutTransition) {
|
||||
let statusBarFrame = self.host.statusBarFrame
|
||||
guard let statusBarView = self.host.statusBarView else {
|
||||
return
|
||||
}
|
||||
|
||||
self.validParams = (withSafeInsets, forceInCallStatusBarText, forceHiddenBySystemWindows)
|
||||
self.validParams = (withSafeInsets, forceInCallStatusBarText, forceHiddenBySystemWindows, forceAppearance)
|
||||
|
||||
if self.host.statusBarWindow?.isUserInteractionEnabled != (forceInCallStatusBarText == nil) {
|
||||
self.host.statusBarWindow?.isUserInteractionEnabled = (forceInCallStatusBarText == nil)
|
||||
@ -278,6 +278,17 @@ class StatusBarManager {
|
||||
}
|
||||
self.volumeControlStatusBarNode.isDark = isDark
|
||||
|
||||
if let forceAppearance = forceAppearance {
|
||||
let style: StatusBarStyle
|
||||
switch forceAppearance {
|
||||
case .lightContent:
|
||||
style = .White
|
||||
default:
|
||||
style = .Black
|
||||
}
|
||||
globalStatusBar = (style, 1.0, 0.0)
|
||||
}
|
||||
|
||||
if let globalStatusBar = globalStatusBar, !forceHiddenBySystemWindows {
|
||||
let statusBarStyle: UIStatusBarStyle
|
||||
if forceInCallStatusBarText != nil {
|
||||
@ -286,7 +297,7 @@ class StatusBarManager {
|
||||
statusBarStyle = globalStatusBar.0 == .Black ? .default : .lightContent
|
||||
}
|
||||
if self.host.statusBarStyle != statusBarStyle {
|
||||
self.host.statusBarStyle = statusBarStyle
|
||||
self.host.setStatusBarStyle(statusBarStyle, animated: animated)
|
||||
}
|
||||
if let statusBarWindow = self.host.statusBarWindow {
|
||||
alphaTransition.updateAlpha(layer: statusBarView.layer, alpha: globalStatusBar.1)
|
||||
|
||||
@ -88,6 +88,14 @@ open class ViewControllerPresentationArguments {
|
||||
public final var isOpaqueWhenInOverlay: Bool = false
|
||||
public final var blocksBackgroundWhenInOverlay: Bool = false
|
||||
public final var automaticallyControlPresentationContextLayout: Bool = true
|
||||
public final var isModalWhenInOverlay: Bool = false {
|
||||
didSet {
|
||||
if self.isNodeLoaded {
|
||||
self.displayNode.clipsToBounds = true
|
||||
}
|
||||
}
|
||||
}
|
||||
public var updateTransitionWhenPresentedAsModal: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
|
||||
|
||||
public func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations {
|
||||
return self.supportedOrientations
|
||||
@ -190,7 +198,7 @@ open class ViewControllerPresentationArguments {
|
||||
|
||||
open var visualNavigationInsetHeight: CGFloat {
|
||||
if let navigationBar = self.navigationBar {
|
||||
var height = navigationBar.frame.maxY
|
||||
let height = navigationBar.frame.maxY
|
||||
if let contentNode = navigationBar.contentNode, case .expansion = contentNode.mode {
|
||||
//height += contentNode.height
|
||||
}
|
||||
@ -233,7 +241,7 @@ open class ViewControllerPresentationArguments {
|
||||
private func updateScrollToTopView() {
|
||||
if self.scrollToTop != nil {
|
||||
if let displayNode = self._displayNode , self.scrollToTopView == nil {
|
||||
let scrollToTopView = ScrollToTopView(frame: CGRect(x: 0.0, y: -1.0, width: displayNode.frame.size.width, height: 1.0))
|
||||
let scrollToTopView = ScrollToTopView(frame: CGRect(x: 0.0, y: -1.0, width: displayNode.bounds.size.width, height: 1.0))
|
||||
scrollToTopView.action = { [weak self] in
|
||||
if let scrollToTop = self?.scrollToTop {
|
||||
scrollToTop()
|
||||
@ -293,16 +301,16 @@ open class ViewControllerPresentationArguments {
|
||||
|
||||
private func updateNavigationBarLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0
|
||||
let navigationBarHeight: CGFloat = max(20.0, statusBarHeight) + (self.navigationBar?.contentHeight ?? 44.0)
|
||||
let navigationBarHeight: CGFloat = statusBarHeight + (self.navigationBar?.contentHeight ?? 44.0)
|
||||
let navigationBarOffset: CGFloat
|
||||
if statusBarHeight.isZero {
|
||||
navigationBarOffset = -20.0
|
||||
navigationBarOffset = 0.0
|
||||
} else {
|
||||
navigationBarOffset = 0.0
|
||||
}
|
||||
var navigationBarFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationBarOffset), size: CGSize(width: layout.size.width, height: navigationBarHeight))
|
||||
if layout.statusBarHeight == nil {
|
||||
navigationBarFrame.size.height = (self.navigationBar?.contentHeight ?? 44.0) + 20.0
|
||||
//navigationBarFrame.size.height = (self.navigationBar?.contentHeight ?? 44.0) + 20.0
|
||||
}
|
||||
|
||||
if !self.displayNavigationBar {
|
||||
@ -332,7 +340,7 @@ open class ViewControllerPresentationArguments {
|
||||
if !self.isViewLoaded {
|
||||
self.loadView()
|
||||
}
|
||||
transition.updateFrame(node: self.displayNode, frame: CGRect(origin: self.view.frame.origin, size: layout.size))
|
||||
//transition.updateFrame(node: self.displayNode, frame: CGRect(origin: self.view.frame.origin, size: layout.size))
|
||||
if let _ = layout.statusBarHeight {
|
||||
self.statusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 40.0))
|
||||
}
|
||||
@ -348,6 +356,10 @@ open class ViewControllerPresentationArguments {
|
||||
}
|
||||
}
|
||||
|
||||
open func updateModalTransition(_ value: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
|
||||
}
|
||||
|
||||
open func navigationStackConfigurationUpdated(next: [ViewController]) {
|
||||
}
|
||||
|
||||
@ -377,6 +389,10 @@ open class ViewControllerPresentationArguments {
|
||||
self.blocksBackgroundWhenInOverlay = true
|
||||
self.isOpaqueWhenInOverlay = true
|
||||
}
|
||||
|
||||
if self.isModalWhenInOverlay {
|
||||
self.displayNode.clipsToBounds = true
|
||||
}
|
||||
}
|
||||
|
||||
public func requestLayout(transition: ContainedViewLayoutTransition) {
|
||||
@ -580,7 +596,7 @@ private func traceViewVisibility(view: UIView, rect: CGRect) -> Bool {
|
||||
if view.window == nil {
|
||||
return false
|
||||
}
|
||||
if let index = siblings.index(where: { $0 === view.layer }) {
|
||||
if let index = siblings.firstIndex(where: { $0 === view.layer }) {
|
||||
let viewFrame = view.convert(rect, to: superview)
|
||||
for i in (index + 1) ..< siblings.count {
|
||||
if siblings[i].frame.contains(viewFrame) {
|
||||
|
||||
@ -344,10 +344,6 @@ public class Window1 {
|
||||
|
||||
private var isInteractionBlocked = false
|
||||
|
||||
/*private var accessibilityElements: [Any]? {
|
||||
return self.viewController?.view.accessibilityElements
|
||||
}*/
|
||||
|
||||
public init(hostView: WindowHostView, statusBarHost: StatusBarHost?) {
|
||||
self.hostView = hostView
|
||||
|
||||
@ -377,6 +373,8 @@ public class Window1 {
|
||||
self.presentationContext = PresentationContext()
|
||||
self.overlayPresentationContext = GlobalOverlayPresentationContext(statusBarHost: statusBarHost, parentView: self.hostView.aboveStatusBarView)
|
||||
|
||||
self.presentationContextContainerView = UIView()
|
||||
|
||||
self.presentationContext.updateIsInteractionBlocked = { [weak self] value in
|
||||
self?.isInteractionBlocked = value
|
||||
}
|
||||
@ -385,6 +383,10 @@ public class Window1 {
|
||||
self?._rootController?.displayNode.accessibilityElementsHidden = value
|
||||
}
|
||||
|
||||
self.presentationContext.updateModalTransition = { [weak self] value, transition in
|
||||
self?.updateModalTransition(value, transition: transition)
|
||||
}
|
||||
|
||||
self.hostView.present = { [weak self] controller, level, blockInteraction, completion in
|
||||
self?.present(controller, on: level, blockInteraction: blockInteraction, completion: completion)
|
||||
}
|
||||
@ -440,12 +442,7 @@ public class Window1 {
|
||||
})
|
||||
}
|
||||
|
||||
/*self.hostView.getAccessibilityElements = { [weak self] in
|
||||
return self?.accessibilityElements
|
||||
}*/
|
||||
|
||||
self.presentationContext.view = self.hostView.containerView
|
||||
self.presentationContext.volumeControlStatusBarNodeView = self.volumeControlStatusBarNode.view
|
||||
self.presentationContext.view = self.presentationContextContainerView
|
||||
self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout, hasOnScreenNavigation: self.hostView.hasOnScreenNavigation), transition: .immediate)
|
||||
self.overlayPresentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout, hasOnScreenNavigation: self.hostView.hasOnScreenNavigation), transition: .immediate)
|
||||
|
||||
@ -567,6 +564,8 @@ public class Window1 {
|
||||
self.windowPanRecognizer = recognizer
|
||||
self.hostView.containerView.addGestureRecognizer(recognizer)
|
||||
|
||||
self.hostView.containerView.addSubview(self.presentationContextContainerView)
|
||||
|
||||
self.hostView.containerView.addSubview(self.volumeControlStatusBar)
|
||||
self.hostView.containerView.addSubview(self.volumeControlStatusBarNode.view)
|
||||
}
|
||||
@ -668,7 +667,7 @@ public class Window1 {
|
||||
}
|
||||
}
|
||||
|
||||
if let result = self.presentationContext.hitTest(point, with: event) {
|
||||
if let result = self.presentationContext.hitTest(view: self.hostView.containerView, point: point, with: event) {
|
||||
return result
|
||||
}
|
||||
return self.viewController?.view.hitTest(point, with: event)
|
||||
@ -697,7 +696,19 @@ public class Window1 {
|
||||
|
||||
if let rootController = self._rootController {
|
||||
if !self.windowLayout.size.width.isZero && !self.windowLayout.size.height.isZero {
|
||||
rootController.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout, hasOnScreenNavigation: self.hostView.hasOnScreenNavigation), transition: .immediate)
|
||||
let rootLayout = containedLayoutForWindowLayout(self.windowLayout, hasOnScreenNavigation: self.hostView.hasOnScreenNavigation)
|
||||
var positionOffset: CGFloat = 0.0
|
||||
if !self.appliedModalLevel.isZero {
|
||||
let scale = ((rootLayout.size.width - 20.0 * 2.0) / rootLayout.size.width)
|
||||
rootController.displayNode.transform = CATransform3DMakeScale(scale, scale, 1.0)
|
||||
let transformedUpperBound = (self.windowLayout.size.height - rootLayout.size.height * scale) / 2.0
|
||||
let targetBound = (self.windowLayout.statusBarHeight ?? 0.0) + 20.0 - 10.0
|
||||
positionOffset = targetBound - transformedUpperBound
|
||||
}
|
||||
rootController.displayNode.position = CGPoint(x: self.windowLayout.size.width / 2.0, y: self.windowLayout.size.height / 2.0 + positionOffset)
|
||||
rootController.displayNode.bounds = CGRect(origin: CGPoint(), size: rootLayout.size)
|
||||
rootController.containerLayoutUpdated(rootLayout, transition: .immediate)
|
||||
rootController.updateModalTransition(self.appliedModalLevel, transition: .immediate)
|
||||
}
|
||||
|
||||
self.hostView.containerView.insertSubview(rootController.view, at: 0)
|
||||
@ -707,6 +718,26 @@ public class Window1 {
|
||||
}
|
||||
}
|
||||
|
||||
private func insertContentViewAtTop(_ view: UIView) {
|
||||
if let dimView = self.dimView {
|
||||
self.hostView.containerView.insertSubview(view, belowSubview: dimView)
|
||||
} else {
|
||||
self.hostView.containerView.insertSubview(view, belowSubview: self.presentationContextContainerView)
|
||||
}
|
||||
}
|
||||
|
||||
private func insertCoveringView(_ view: UIView) {
|
||||
self.hostView.containerView.insertSubview(view, belowSubview: self.volumeControlStatusBarNode.view)
|
||||
}
|
||||
|
||||
private func insertDimView(_ view: UIView) {
|
||||
if let coveringView = self.coveringView {
|
||||
self.hostView.containerView.insertSubview(view, belowSubview: coveringView)
|
||||
} else {
|
||||
self.hostView.containerView.insertSubview(view, belowSubview: self.presentationContextContainerView)
|
||||
}
|
||||
}
|
||||
|
||||
private var _topLevelOverlayControllers: [ContainableController] = []
|
||||
public var topLevelOverlayControllers: [ContainableController] {
|
||||
get {
|
||||
@ -721,18 +752,17 @@ public class Window1 {
|
||||
let layout = containedLayoutForWindowLayout(self.windowLayout, hasOnScreenNavigation: self.hostView.hasOnScreenNavigation)
|
||||
for controller in self._topLevelOverlayControllers {
|
||||
controller.containerLayoutUpdated(layout, transition: .immediate)
|
||||
|
||||
if let coveringView = self.coveringView {
|
||||
self.hostView.containerView.insertSubview(controller.view, belowSubview: coveringView)
|
||||
} else {
|
||||
self.hostView.containerView.insertSubview(controller.view, belowSubview: self.volumeControlStatusBarNode.view)
|
||||
}
|
||||
self.insertContentViewAtTop(controller.view)
|
||||
}
|
||||
|
||||
self.presentationContext.topLevelSubview = self._topLevelOverlayControllers.first?.view
|
||||
}
|
||||
}
|
||||
|
||||
private var dimView: UIView? = nil
|
||||
|
||||
private let presentationContextContainerView: UIView
|
||||
|
||||
public var coveringView: WindowCoveringView? {
|
||||
didSet {
|
||||
if self.coveringView !== oldValue {
|
||||
@ -746,7 +776,7 @@ public class Window1 {
|
||||
coveringView.layer.removeAnimation(forKey: "opacity")
|
||||
coveringView.layer.allowsGroupOpacity = false
|
||||
coveringView.alpha = 1.0
|
||||
self.hostView.containerView.insertSubview(coveringView, belowSubview: self.volumeControlStatusBarNode.view)
|
||||
self.insertCoveringView(coveringView)
|
||||
if !self.windowLayout.size.width.isZero {
|
||||
coveringView.frame = CGRect(origin: CGPoint(), size: self.windowLayout.size)
|
||||
coveringView.updateLayout(self.windowLayout.size)
|
||||
@ -756,6 +786,8 @@ public class Window1 {
|
||||
}
|
||||
}
|
||||
|
||||
private var appliedModalLevel: CGFloat = 0.0
|
||||
|
||||
private func layoutSubviews() {
|
||||
var hasPreview = false
|
||||
var updatedHasPreview = false
|
||||
@ -771,11 +803,19 @@ public class Window1 {
|
||||
updatedHasPreview = true
|
||||
}
|
||||
|
||||
if self.tracingStatusBarsInvalidated || updatedHasPreview, let statusBarManager = statusBarManager, let keyboardManager = keyboardManager {
|
||||
var modalLevelUpdated = false
|
||||
if self.appliedModalLevel != self.modalTransition {
|
||||
modalLevelUpdated = self.appliedModalLevel.isZero != self.modalTransition.isZero
|
||||
self.appliedModalLevel = self.modalTransition
|
||||
}
|
||||
|
||||
if self.tracingStatusBarsInvalidated || updatedHasPreview || modalLevelUpdated, let statusBarManager = statusBarManager, let keyboardManager = keyboardManager {
|
||||
self.tracingStatusBarsInvalidated = false
|
||||
|
||||
if self.statusBarHidden {
|
||||
statusBarManager.updateState(surfaces: [], withSafeInsets: false, forceInCallStatusBarText: nil, forceHiddenBySystemWindows: false, animated: false)
|
||||
statusBarManager.updateState(surfaces: [], withSafeInsets: false, forceInCallStatusBarText: nil, forceHiddenBySystemWindows: false, forceAppearance: nil, animated: false)
|
||||
} else if !self.modalTransition.isZero || self.isInTransitionOutOfModal {
|
||||
statusBarManager.updateState(surfaces: [], withSafeInsets: false, forceInCallStatusBarText: nil, forceHiddenBySystemWindows: false, forceAppearance: self.isInTransitionOutOfModal ? .default : .lightContent, animated: modalLevelUpdated)
|
||||
} else {
|
||||
var statusBarSurfaces: [StatusBarSurface] = []
|
||||
for layers in self.hostView.containerView.layer.traceableLayerSurfaces(withTag: WindowTracingTags.statusBar) {
|
||||
@ -796,7 +836,7 @@ public class Window1 {
|
||||
}
|
||||
}
|
||||
self.cachedWindowSubviewCount = self.hostView.containerView.window?.subviews.count ?? 0
|
||||
statusBarManager.updateState(surfaces: statusBarSurfaces, withSafeInsets: !self.windowLayout.safeInsets.top.isZero, forceInCallStatusBarText: self.forceInCallStatusBarText, forceHiddenBySystemWindows: hasPreview, animated: animatedUpdate)
|
||||
statusBarManager.updateState(surfaces: statusBarSurfaces, withSafeInsets: !self.windowLayout.safeInsets.top.isZero, forceInCallStatusBarText: self.forceInCallStatusBarText, forceHiddenBySystemWindows: hasPreview, forceAppearance: nil, animated: animatedUpdate || modalLevelUpdated)
|
||||
}
|
||||
|
||||
var keyboardSurfaces: [KeyboardSurface] = []
|
||||
@ -969,13 +1009,31 @@ public class Window1 {
|
||||
let childLayoutUpdated = self.updatedContainerLayout != childLayout
|
||||
self.updatedContainerLayout = childLayout
|
||||
|
||||
updatingLayout.transition.updateFrame(view: self.presentationContextContainerView, frame: CGRect(origin: CGPoint(), size: self.windowLayout.size))
|
||||
if let dimView = self.dimView {
|
||||
updatingLayout.transition.updateFrame(view: dimView, frame: CGRect(origin: CGPoint(), size: self.windowLayout.size))
|
||||
}
|
||||
|
||||
if childLayoutUpdated {
|
||||
var rootLayout = childLayout
|
||||
let rootTransition = updatingLayout.transition
|
||||
if self.presentationContext.isCurrentlyOpaque {
|
||||
rootLayout.inputHeight = nil
|
||||
}
|
||||
self._rootController?.containerLayoutUpdated(rootLayout, transition: rootTransition)
|
||||
var positionOffset: CGFloat = 0.0
|
||||
if let rootController = self._rootController {
|
||||
if !self.modalTransition.isZero {
|
||||
let scale = (rootLayout.size.width - 20.0 * 2.0) / rootLayout.size.width
|
||||
rootTransition.updateTransformScale(node: rootController.displayNode, scale: scale)
|
||||
|
||||
let transformedUpperBound = (self.windowLayout.size.height - rootLayout.size.height * scale) / 2.0
|
||||
let targetBound = (self.windowLayout.statusBarHeight ?? 0.0) + 20.0 - 10.0
|
||||
positionOffset = targetBound - transformedUpperBound
|
||||
}
|
||||
rootTransition.updatePosition(node: rootController.displayNode, position: CGPoint(x: self.windowLayout.size.width / 2.0, y: self.windowLayout.size.height / 2.0 + positionOffset))
|
||||
rootTransition.updateBounds(node: rootController.displayNode, bounds: CGRect(origin: CGPoint(), size: rootLayout.size))
|
||||
rootController.containerLayoutUpdated(rootLayout, transition: rootTransition)
|
||||
}
|
||||
self.presentationContext.containerLayoutUpdated(childLayout, transition: updatingLayout.transition)
|
||||
self.overlayPresentationContext.containerLayoutUpdated(childLayout, transition: updatingLayout.transition)
|
||||
|
||||
@ -1008,6 +1066,62 @@ public class Window1 {
|
||||
}
|
||||
}
|
||||
|
||||
private var modalTransition: CGFloat = 0.0
|
||||
private var defaultBackgroundColor: UIColor?
|
||||
private var isInTransitionOutOfModal: Bool = false
|
||||
|
||||
private func updateModalTransition(_ value: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
self.modalTransition = value
|
||||
if !value.isZero {
|
||||
if self.dimView == nil {
|
||||
let dimView = UIView()
|
||||
dimView.frame = CGRect(origin: CGPoint(), size: self.windowLayout.size)
|
||||
dimView.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
||||
self.dimView = dimView
|
||||
self.insertDimView(dimView)
|
||||
dimView.alpha = 0.0
|
||||
transition.updateAlpha(layer: dimView.layer, alpha: 1.0)
|
||||
|
||||
self.defaultBackgroundColor = self.hostView.containerView.backgroundColor
|
||||
self.hostView.containerView.backgroundColor = .black
|
||||
|
||||
var positionOffset: CGFloat = 0.0
|
||||
if let rootController = self._rootController {
|
||||
let rootSize = rootController.displayNode.bounds.size
|
||||
let scale = (rootSize.width - 20.0 * 2.0) / rootSize.width
|
||||
transition.updateTransformScale(node: rootController.displayNode, scale: scale)
|
||||
|
||||
let transformedUpperBound = (self.windowLayout.size.height - rootSize.height * scale) / 2.0
|
||||
let targetBound = (self.windowLayout.statusBarHeight ?? 0.0) + 20.0 - 10.0
|
||||
positionOffset = targetBound - transformedUpperBound
|
||||
|
||||
transition.updatePosition(node: rootController.displayNode, position: CGPoint(x: self.windowLayout.size.width / 2.0, y: self.windowLayout.size.height / 2.0 + positionOffset))
|
||||
rootController.updateModalTransition(value, transition: transition)
|
||||
}
|
||||
self.layoutSubviews()
|
||||
}
|
||||
} else {
|
||||
if let dimView = self.dimView {
|
||||
self.dimView = nil
|
||||
self.isInTransitionOutOfModal = true
|
||||
transition.updateAlpha(layer: dimView.layer, alpha: 0.0, completion: { [weak self, weak dimView] _ in
|
||||
dimView?.removeFromSuperview()
|
||||
if let strongSelf = self {
|
||||
strongSelf.hostView.containerView.backgroundColor = strongSelf.defaultBackgroundColor
|
||||
strongSelf.isInTransitionOutOfModal = false
|
||||
strongSelf.layoutSubviews()
|
||||
}
|
||||
})
|
||||
if let rootController = self._rootController {
|
||||
transition.updateTransformScale(node: rootController.displayNode, scale: 1.0)
|
||||
transition.updatePosition(node: rootController.displayNode, position: CGPoint(x: self.windowLayout.size.width / 2.0, y: self.windowLayout.size.height / 2.0))
|
||||
rootController.updateModalTransition(0.0, transition: transition)
|
||||
}
|
||||
self.layoutSubviews()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func present(_ controller: ContainableController, on level: PresentationSurfaceLevel, blockInteraction: Bool = false, completion: @escaping () -> Void = {}) {
|
||||
self.presentationContext.present(controller, on: level, blockInteraction: blockInteraction, completion: completion)
|
||||
}
|
||||
|
||||
@ -230,6 +230,7 @@ open class ItemListController<Entry: ItemListNodeEntry>: ViewController, KeyShor
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: theme), strings: NavigationBarStrings(presentationStrings: strings)))
|
||||
|
||||
self.isOpaqueWhenInOverlay = true
|
||||
self.isModalWhenInOverlay = true
|
||||
self.blocksBackgroundWhenInOverlay = true
|
||||
|
||||
self.statusBar.statusBarStyle = theme.rootController.statusBarStyle.style
|
||||
@ -478,6 +479,7 @@ open class ItemListController<Entry: ItemListNodeEntry>: ViewController, KeyShor
|
||||
presentationArguments.completion?()
|
||||
completion()
|
||||
})
|
||||
self.updateTransitionWhenPresentedAsModal?(1.0, .animated(duration: 0.5, curve: .spring))
|
||||
} else {
|
||||
completion()
|
||||
}
|
||||
@ -506,6 +508,7 @@ open class ItemListController<Entry: ItemListNodeEntry>: ViewController, KeyShor
|
||||
if !self.isDismissed {
|
||||
self.isDismissed = true
|
||||
(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 */; };
|
||||
D07047BA1F3DF75500F6A8D4 /* ConsumePersonalMessageAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07047B91F3DF75500F6A8D4 /* ConsumePersonalMessageAction.swift */; };
|
||||
D07047BB1F3DF75500F6A8D4 /* ConsumePersonalMessageAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07047B91F3DF75500F6A8D4 /* ConsumePersonalMessageAction.swift */; };
|
||||
D072F357231542740009E66F /* MessageReactionList.swift in Sources */ = {isa = PBXBuildFile; fileRef = D072F356231542740009E66F /* MessageReactionList.swift */; };
|
||||
D072F358231542740009E66F /* MessageReactionList.swift in Sources */ = {isa = PBXBuildFile; fileRef = D072F356231542740009E66F /* MessageReactionList.swift */; };
|
||||
D073CE5D1DCB97F6007511FD /* ForwardSourceInfoAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D073CE5C1DCB97F6007511FD /* ForwardSourceInfoAttribute.swift */; };
|
||||
D073CE601DCB9D14007511FD /* OutgoingMessageInfoAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D073CE5F1DCB9D14007511FD /* OutgoingMessageInfoAttribute.swift */; };
|
||||
D073CE6A1DCBCF17007511FD /* ViewCountMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0CE71D6224AD00955575 /* ViewCountMessageAttribute.swift */; };
|
||||
@ -1042,6 +1044,7 @@
|
||||
D07047B31F3DF1FE00F6A8D4 /* ConsumablePersonalMentionMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsumablePersonalMentionMessageAttribute.swift; sourceTree = "<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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -1606,6 +1609,7 @@
|
||||
D01AC91C1DD5DA5E00E8160F /* RequestMessageActionCallback.swift */,
|
||||
D0AB262A21C3CE80008F6685 /* Polls.swift */,
|
||||
D0329EA122FC5A7C00F9F071 /* MessageReactions.swift */,
|
||||
D072F356231542740009E66F /* MessageReactionList.swift */,
|
||||
D01AC9201DD5E7E500E8160F /* RequestEditMessage.swift */,
|
||||
D0DC354D1DE368F7000195EB /* RequestChatContextResults.swift */,
|
||||
D0DC354F1DE36900000195EB /* ChatContextResult.swift */,
|
||||
@ -2453,6 +2457,7 @@
|
||||
D054649120738653002ECC1E /* SecureIdIDCardValue.swift in Sources */,
|
||||
D018EE052045E95000CBB130 /* CheckPeerChatServiceActions.swift in Sources */,
|
||||
D0F3A8A51E82C94C00B4C64C /* SynchronizeableChatInputState.swift in Sources */,
|
||||
D072F357231542740009E66F /* MessageReactionList.swift in Sources */,
|
||||
D03B0CD71D62245300955575 /* TelegramGroup.swift in Sources */,
|
||||
D02609BF20C6EC08006C34AC /* Crypto.m in Sources */,
|
||||
D0B8438C1DA7CF50005F29E1 /* BotInfo.swift in Sources */,
|
||||
@ -2783,6 +2788,7 @@
|
||||
D05A32E81E6F0B5C002760B4 /* RecentAccountSession.swift in Sources */,
|
||||
D0F7B1E31E045C7B007EB8A5 /* RichText.swift in Sources */,
|
||||
D0575C2E22B922DF00A71A0E /* DeleteAccount.swift in Sources */,
|
||||
D072F358231542740009E66F /* MessageReactionList.swift in Sources */,
|
||||
D0FA8BB11E1FEC7E001E855B /* SecretChatEncryptionConfig.swift in Sources */,
|
||||
D0B418AA1D7E0597004562A4 /* Download.swift in Sources */,
|
||||
D001F3F41E128A1C007A8C60 /* UpdatesApiUtils.swift in Sources */,
|
||||
|
||||
@ -57,12 +57,13 @@ private class ApplicationStatusBarHost: StatusBarHost {
|
||||
return self.application.statusBarFrame
|
||||
}
|
||||
var statusBarStyle: UIStatusBarStyle {
|
||||
get {
|
||||
return self.application.statusBarStyle
|
||||
} set(value) {
|
||||
self.application.setStatusBarStyle(value, animated: false)
|
||||
}
|
||||
return self.application.statusBarStyle
|
||||
}
|
||||
|
||||
func setStatusBarStyle(_ style: UIStatusBarStyle, animated: Bool) {
|
||||
self.application.setStatusBarStyle(style, animated: animated)
|
||||
}
|
||||
|
||||
var statusBarWindow: UIView? {
|
||||
return self.application.value(forKey: "statusBarWindow") as? UIView
|
||||
}
|
||||
@ -237,7 +238,7 @@ final class SharedApplicationContext {
|
||||
let (window, hostView, aboveStatusbarWindow) = nativeWindowHostView()
|
||||
self.mainWindow = Window1(hostView: hostView, statusBarHost: statusBarHost)
|
||||
self.aboveStatusbarWindow = aboveStatusbarWindow
|
||||
window.backgroundColor = UIColor.white
|
||||
hostView.containerView.backgroundColor = UIColor.white
|
||||
self.window = window
|
||||
self.nativeWindow = window
|
||||
|
||||
@ -656,7 +657,7 @@ final class SharedApplicationContext {
|
||||
}
|
||||
|> deliverOnMainQueue
|
||||
|> mapToSignal { accountManager, initialPresentationDataAndSettings -> Signal<(SharedApplicationContext, LoggingSettings), NoError> in
|
||||
self.window?.backgroundColor = initialPresentationDataAndSettings.presentationData.theme.chatList.backgroundColor
|
||||
self.mainWindow?.hostView.containerView.backgroundColor = initialPresentationDataAndSettings.presentationData.theme.chatList.backgroundColor
|
||||
|
||||
let legacyBasePath = appGroupUrl.path
|
||||
let legacyCache = LegacyCache(path: legacyBasePath + "/Caches")
|
||||
|
||||
@ -44,6 +44,7 @@ import PeerInfoUI
|
||||
import RaiseToListen
|
||||
import UrlHandling
|
||||
import ReactionSelectionNode
|
||||
import MessageReactionListUI
|
||||
|
||||
public enum ChatControllerPeekActions {
|
||||
case standard
|
||||
@ -286,6 +287,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
private var updateSlowmodeStatusDisposable = MetaDisposable()
|
||||
private var updateSlowmodeStatusTimerValue: Int32?
|
||||
|
||||
private var isDismissed = false
|
||||
|
||||
public override var customData: Any? {
|
||||
return self.chatLocation
|
||||
@ -343,6 +346,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
super.init(context: context, navigationBarPresentationData: navigationBarPresentationData, mediaAccessoryPanelVisibility: mediaAccessoryPanelVisibility, locationBroadcastPanelSource: locationBroadcastPanelSource)
|
||||
|
||||
self.blocksBackgroundWhenInOverlay = true
|
||||
if let subject = subject, case .scheduledMessages = subject {
|
||||
self.isModalWhenInOverlay = true
|
||||
}
|
||||
|
||||
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
|
||||
|
||||
@ -1499,8 +1505,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if let strongSelf = self {
|
||||
strongSelf.chatDisplayNode.sendCurrentMessage(scheduleTime: scheduleTime)
|
||||
if !strongSelf.presentationInterfaceState.isScheduledMessages {
|
||||
let controller = ChatControllerImpl(context: strongSelf.context, chatLocation: strongSelf.chatLocation, subject: .scheduledMessages)
|
||||
(strongSelf.navigationController as? NavigationController)?.pushViewController(controller)
|
||||
strongSelf.openScheduledMessages()
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -1581,6 +1586,28 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return
|
||||
}
|
||||
let _ = updateMessageReactionsInteractively(postbox: strongSelf.context.account.postbox, messageId: messageId, reaction: reaction).start()
|
||||
}, openMessageReactions: { [weak self] messageId in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let _ = (strongSelf.context.account.postbox.transaction { transaction -> Message? in
|
||||
return transaction.getMessage(messageId)
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { message in
|
||||
guard let strongSelf = self, let message = message else {
|
||||
return
|
||||
}
|
||||
var initialReactions: [MessageReaction] = []
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? ReactionsMessageAttribute {
|
||||
initialReactions = attribute.reactions
|
||||
}
|
||||
}
|
||||
|
||||
if !initialReactions.isEmpty {
|
||||
strongSelf.present(MessageReactionListController(context: strongSelf.context, messageId: message.id, initialReactions: initialReactions), in: .window(.root))
|
||||
}
|
||||
})
|
||||
}, requestMessageUpdate: { [weak self] id in
|
||||
if let strongSelf = self {
|
||||
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id)
|
||||
@ -3856,8 +3883,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}, openScheduledMessages: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
let controller = ChatControllerImpl(context: strongSelf.context, chatLocation: strongSelf.chatLocation, subject: .scheduledMessages)
|
||||
(strongSelf.navigationController as? NavigationController)?.pushViewController(controller)
|
||||
strongSelf.openScheduledMessages()
|
||||
}
|
||||
}, statuses: ChatPanelInterfaceInteractionStatuses(editingMessage: self.editingMessage.get(), startingBot: self.startingBot.get(), unblockingPeer: self.unblockingPeer.get(), searching: self.searching.get(), loadingMessage: self.loadingMessage.get()))
|
||||
|
||||
@ -4213,6 +4239,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let subject = self.subject, case .scheduledMessages = subject {
|
||||
self.chatDisplayNode.animateIn()
|
||||
self.updateTransitionWhenPresentedAsModal?(1.0, .animated(duration: 0.5, curve: .spring))
|
||||
}
|
||||
}
|
||||
|
||||
override public func viewWillDisappear(_ animated: Bool) {
|
||||
@ -4571,7 +4602,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
//
|
||||
// }
|
||||
|
||||
if let button = leftNavigationButtonForChatInterfaceState(updatedChatPresentationInterfaceState, strings: updatedChatPresentationInterfaceState.strings, currentButton: self.leftNavigationButton, target: self, selector: #selector(self.leftNavigationButtonAction)) {
|
||||
if let button = leftNavigationButtonForChatInterfaceState(updatedChatPresentationInterfaceState, subject: self.subject, strings: updatedChatPresentationInterfaceState.strings, currentButton: self.leftNavigationButton, target: self, selector: #selector(self.leftNavigationButtonAction)) {
|
||||
if self.leftNavigationButton != button {
|
||||
var animated = transition.isAnimated
|
||||
if let currentButton = self.leftNavigationButton?.action, currentButton == button.action {
|
||||
@ -4671,115 +4702,117 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
private func navigationButtonAction(_ action: ChatNavigationButtonAction) {
|
||||
switch action {
|
||||
case .cancelMessageSelection:
|
||||
self.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } })
|
||||
case .clearHistory:
|
||||
if case let .peer(peerId) = self.chatLocation {
|
||||
guard let peer = self.presentationInterfaceState.renderedPeer, let chatPeer = peer.peers[peer.peerId], let mainPeer = peer.chatMainPeer else {
|
||||
case .cancelMessageSelection:
|
||||
self.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } })
|
||||
case .clearHistory:
|
||||
if case let .peer(peerId) = self.chatLocation {
|
||||
guard let peer = self.presentationInterfaceState.renderedPeer, let chatPeer = peer.peers[peer.peerId], let mainPeer = peer.chatMainPeer else {
|
||||
return
|
||||
}
|
||||
|
||||
let text: String
|
||||
if peerId == self.context.account.peerId {
|
||||
text = self.presentationData.strings.Conversation_ClearSelfHistory
|
||||
} else if peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
text = self.presentationData.strings.Conversation_ClearSecretHistory
|
||||
} else if peerId.namespace == Namespaces.Peer.CloudGroup || peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
text = self.presentationData.strings.Conversation_ClearGroupHistory
|
||||
} else {
|
||||
text = self.presentationData.strings.Conversation_ClearPrivateHistory
|
||||
}
|
||||
|
||||
var canRemoveGlobally = false
|
||||
let limitsConfiguration = self.context.currentLimitsConfiguration.with { $0 }
|
||||
if peerId.namespace == Namespaces.Peer.CloudUser && peerId != self.context.account.peerId {
|
||||
if limitsConfiguration.maxMessageRevokeIntervalInPrivateChats == LimitsConfiguration.timeIntervalForever {
|
||||
canRemoveGlobally = true
|
||||
}
|
||||
}
|
||||
if let user = chatPeer as? TelegramUser, user.botInfo != nil {
|
||||
canRemoveGlobally = false
|
||||
}
|
||||
|
||||
let account = self.context.account
|
||||
|
||||
let beginClear: (InteractiveHistoryClearingType) -> Void = { [weak self] type in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } })
|
||||
strongSelf.chatDisplayNode.historyNode.historyAppearsCleared = true
|
||||
|
||||
let text: String
|
||||
if peerId == self.context.account.peerId {
|
||||
text = self.presentationData.strings.Conversation_ClearSelfHistory
|
||||
} else if peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
text = self.presentationData.strings.Conversation_ClearSecretHistory
|
||||
} else if peerId.namespace == Namespaces.Peer.CloudGroup || peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
text = self.presentationData.strings.Conversation_ClearGroupHistory
|
||||
let statusText: String
|
||||
if strongSelf.presentationInterfaceState.isScheduledMessages {
|
||||
statusText = strongSelf.presentationData.strings.Undo_ScheduledMessagesCleared
|
||||
} else if case .forEveryone = type {
|
||||
statusText = strongSelf.presentationData.strings.Undo_ChatClearedForBothSides
|
||||
} else {
|
||||
text = self.presentationData.strings.Conversation_ClearPrivateHistory
|
||||
statusText = strongSelf.presentationData.strings.Undo_ChatCleared
|
||||
}
|
||||
|
||||
var canRemoveGlobally = false
|
||||
let limitsConfiguration = self.context.currentLimitsConfiguration.with { $0 }
|
||||
if peerId.namespace == Namespaces.Peer.CloudUser && peerId != self.context.account.peerId {
|
||||
if limitsConfiguration.maxMessageRevokeIntervalInPrivateChats == LimitsConfiguration.timeIntervalForever {
|
||||
canRemoveGlobally = true
|
||||
}
|
||||
}
|
||||
if let user = chatPeer as? TelegramUser, user.botInfo != nil {
|
||||
canRemoveGlobally = false
|
||||
}
|
||||
|
||||
let account = self.context.account
|
||||
|
||||
let beginClear: (InteractiveHistoryClearingType) -> Void = { [weak self] type in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } })
|
||||
strongSelf.chatDisplayNode.historyNode.historyAppearsCleared = true
|
||||
|
||||
let statusText: String
|
||||
if strongSelf.presentationInterfaceState.isScheduledMessages {
|
||||
statusText = strongSelf.presentationData.strings.Undo_ScheduledMessagesCleared
|
||||
} else if case .forEveryone = type {
|
||||
statusText = strongSelf.presentationData.strings.Undo_ChatClearedForBothSides
|
||||
} else {
|
||||
statusText = strongSelf.presentationData.strings.Undo_ChatCleared
|
||||
}
|
||||
|
||||
strongSelf.present(UndoOverlayController(context: strongSelf.context, content: .removedChat(text: statusText), elevatedLayout: true, action: { shouldCommit in
|
||||
if shouldCommit {
|
||||
let _ = clearHistoryInteractively(postbox: account.postbox, peerId: peerId, type: type).start(completed: {
|
||||
self?.chatDisplayNode.historyNode.historyAppearsCleared = false
|
||||
})
|
||||
} else {
|
||||
strongSelf.present(UndoOverlayController(context: strongSelf.context, content: .removedChat(text: statusText), elevatedLayout: true, action: { shouldCommit in
|
||||
if shouldCommit {
|
||||
let _ = clearHistoryInteractively(postbox: account.postbox, peerId: peerId, type: type).start(completed: {
|
||||
self?.chatDisplayNode.historyNode.historyAppearsCleared = false
|
||||
}
|
||||
}), in: .window(.root))
|
||||
}
|
||||
|
||||
let actionSheet = ActionSheetController(presentationTheme: self.presentationData.theme)
|
||||
var items: [ActionSheetItem] = []
|
||||
|
||||
if self.presentationInterfaceState.isScheduledMessages {
|
||||
items.append(ActionSheetButtonItem(title: self.presentationData.strings.ScheduledMessages_ClearAllConfirmation, color: .destructive, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
beginClear(.scheduledMessages)
|
||||
}))
|
||||
} else if canRemoveGlobally {
|
||||
items.append(DeleteChatPeerActionSheetItem(context: self.context, peer: mainPeer, chatPeer: chatPeer, action: .clearHistory, strings: self.presentationData.strings))
|
||||
items.append(ActionSheetButtonItem(title: self.presentationData.strings.ChatList_DeleteForEveryone(mainPeer.compactDisplayTitle).0, color: .destructive, action: { [weak actionSheet] in
|
||||
beginClear(.forEveryone)
|
||||
actionSheet?.dismissAnimated()
|
||||
}))
|
||||
items.append(ActionSheetButtonItem(title: self.presentationData.strings.ChatList_DeleteForCurrentUser, color: .destructive, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
beginClear(.forLocalPeer)
|
||||
}))
|
||||
} else {
|
||||
items.append(ActionSheetTextItem(title: text))
|
||||
items.append(ActionSheetButtonItem(title: self.presentationData.strings.Conversation_ClearAll, color: .destructive, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
beginClear(.forLocalPeer)
|
||||
}))
|
||||
}
|
||||
})
|
||||
} else {
|
||||
self?.chatDisplayNode.historyNode.historyAppearsCleared = false
|
||||
}
|
||||
}), in: .window(.root))
|
||||
}
|
||||
|
||||
let actionSheet = ActionSheetController(presentationTheme: self.presentationData.theme)
|
||||
var items: [ActionSheetItem] = []
|
||||
|
||||
if self.presentationInterfaceState.isScheduledMessages {
|
||||
items.append(ActionSheetButtonItem(title: self.presentationData.strings.ScheduledMessages_ClearAllConfirmation, color: .destructive, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
beginClear(.scheduledMessages)
|
||||
}))
|
||||
} else if canRemoveGlobally {
|
||||
items.append(DeleteChatPeerActionSheetItem(context: self.context, peer: mainPeer, chatPeer: chatPeer, action: .clearHistory, strings: self.presentationData.strings))
|
||||
items.append(ActionSheetButtonItem(title: self.presentationData.strings.ChatList_DeleteForEveryone(mainPeer.compactDisplayTitle).0, color: .destructive, action: { [weak actionSheet] in
|
||||
beginClear(.forEveryone)
|
||||
actionSheet?.dismissAnimated()
|
||||
}))
|
||||
items.append(ActionSheetButtonItem(title: self.presentationData.strings.ChatList_DeleteForCurrentUser, color: .destructive, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
beginClear(.forLocalPeer)
|
||||
}))
|
||||
} else {
|
||||
items.append(ActionSheetTextItem(title: text))
|
||||
items.append(ActionSheetButtonItem(title: self.presentationData.strings.Conversation_ClearAll, color: .destructive, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
beginClear(.forLocalPeer)
|
||||
}))
|
||||
}
|
||||
|
||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
})
|
||||
])])
|
||||
|
||||
self.chatDisplayNode.dismissInput()
|
||||
self.present(actionSheet, in: .window(.root))
|
||||
}
|
||||
case .openChatInfo:
|
||||
switch self.chatLocationInfoData {
|
||||
case let .peer(peerView):
|
||||
self.navigationActionDisposable.set((peerView.get()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peerView in
|
||||
if let strongSelf = self, let peer = peerView.peers[peerView.peerId], peer.restrictionText(platform: "ios") == nil && !strongSelf.presentationInterfaceState.isNotAccessible {
|
||||
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic) {
|
||||
(strongSelf.navigationController as? NavigationController)?.pushViewController(infoController)
|
||||
}
|
||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
})
|
||||
])])
|
||||
|
||||
self.chatDisplayNode.dismissInput()
|
||||
self.present(actionSheet, in: .window(.root))
|
||||
}
|
||||
case .openChatInfo:
|
||||
switch self.chatLocationInfoData {
|
||||
case let .peer(peerView):
|
||||
self.navigationActionDisposable.set((peerView.get()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peerView in
|
||||
if let strongSelf = self, let peer = peerView.peers[peerView.peerId], peer.restrictionText(platform: "ios") == nil && !strongSelf.presentationInterfaceState.isNotAccessible {
|
||||
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic) {
|
||||
(strongSelf.navigationController as? NavigationController)?.pushViewController(infoController)
|
||||
}
|
||||
}))
|
||||
}
|
||||
case .search:
|
||||
self.interfaceInteraction?.beginMessageSearch(.everything, "")
|
||||
}
|
||||
}))
|
||||
}
|
||||
case .search:
|
||||
self.interfaceInteraction?.beginMessageSearch(.everything, "")
|
||||
case .dismiss:
|
||||
self.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
@ -4944,8 +4977,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if let strongSelf = self {
|
||||
done(time)
|
||||
if !strongSelf.presentationInterfaceState.isScheduledMessages {
|
||||
let controller = ChatControllerImpl(context: strongSelf.context, chatLocation: strongSelf.chatLocation, subject: .scheduledMessages)
|
||||
(strongSelf.navigationController as? NavigationController)?.pushViewController(controller)
|
||||
strongSelf.openScheduledMessages()
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -4986,8 +5018,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if let strongSelf = self {
|
||||
done(time)
|
||||
if !strongSelf.presentationInterfaceState.isScheduledMessages {
|
||||
let controller = ChatControllerImpl(context: strongSelf.context, chatLocation: strongSelf.chatLocation, subject: .scheduledMessages)
|
||||
(strongSelf.navigationController as? NavigationController)?.pushViewController(controller)
|
||||
strongSelf.openScheduledMessages()
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -5171,8 +5202,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if let strongSelf = self {
|
||||
done(time)
|
||||
if !strongSelf.presentationInterfaceState.isScheduledMessages {
|
||||
let controller = ChatControllerImpl(context: strongSelf.context, chatLocation: strongSelf.chatLocation, subject: .scheduledMessages)
|
||||
(strongSelf.navigationController as? NavigationController)?.pushViewController(controller)
|
||||
strongSelf.openScheduledMessages()
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -7535,4 +7565,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self.updateSlowmodeStatusDisposable.set(nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func openScheduledMessages() {
|
||||
let controller = ChatControllerImpl(context: self.context, chatLocation: self.chatLocation, subject: .scheduledMessages)
|
||||
self.present(controller, in: .window(.root))
|
||||
}
|
||||
|
||||
override public func dismiss(completion: (() -> Void)? = nil) {
|
||||
if !self.isDismissed {
|
||||
self.isDismissed = true
|
||||
self.chatDisplayNode.animateOut(completion: { [weak self] in
|
||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||
})
|
||||
self.updateTransitionWhenPresentedAsModal?(0.0, .animated(duration: 0.2, curve: .easeInOut))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,6 +95,7 @@ public final class ChatControllerInteraction {
|
||||
let editScheduledMessagesTime: ([MessageId]) -> Void
|
||||
let performTextSelectionAction: (UInt32, String, TextSelectionAction) -> Void
|
||||
let updateMessageReaction: (MessageId, String) -> Void
|
||||
let openMessageReactions: (MessageId) -> Void
|
||||
|
||||
let requestMessageUpdate: (MessageId) -> Void
|
||||
let cancelInteractiveKeyboardGestures: () -> Void
|
||||
@ -109,7 +110,7 @@ public final class ChatControllerInteraction {
|
||||
var searchTextHighightState: String?
|
||||
var seenOneTimeAnimatedMedia = Set<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.openPeer = openPeer
|
||||
self.openPeerMention = openPeerMention
|
||||
@ -158,6 +159,7 @@ public final class ChatControllerInteraction {
|
||||
self.editScheduledMessagesTime = editScheduledMessagesTime
|
||||
self.performTextSelectionAction = performTextSelectionAction
|
||||
self.updateMessageReaction = updateMessageReaction
|
||||
self.openMessageReactions = openMessageReactions
|
||||
|
||||
self.requestMessageUpdate = requestMessageUpdate
|
||||
self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures
|
||||
@ -193,6 +195,7 @@ public final class ChatControllerInteraction {
|
||||
}, editScheduledMessagesTime: { _ in
|
||||
}, performTextSelectionAction: { _, _, _ in
|
||||
}, updateMessageReaction: { _, _ in
|
||||
}, openMessageReactions: { _ in
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||
|
||||
@ -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 TelegramCore
|
||||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
|
||||
enum ChatNavigationButtonAction {
|
||||
case openChatInfo
|
||||
case clearHistory
|
||||
case cancelMessageSelection
|
||||
case search
|
||||
case dismiss
|
||||
}
|
||||
|
||||
struct ChatNavigationButton: Equatable {
|
||||
@ -20,7 +22,7 @@ struct ChatNavigationButton: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
func leftNavigationButtonForChatInterfaceState(_ presentationInterfaceState: ChatPresentationInterfaceState, strings: PresentationStrings, currentButton: ChatNavigationButton?, target: Any?, selector: Selector?) -> ChatNavigationButton? {
|
||||
func leftNavigationButtonForChatInterfaceState(_ presentationInterfaceState: ChatPresentationInterfaceState, subject: ChatControllerSubject?, strings: PresentationStrings, currentButton: ChatNavigationButton?, target: Any?, selector: Selector?) -> ChatNavigationButton? {
|
||||
if let _ = presentationInterfaceState.interfaceState.selectionState {
|
||||
if let currentButton = currentButton, currentButton.action == .clearHistory {
|
||||
return currentButton
|
||||
@ -45,6 +47,13 @@ func leftNavigationButtonForChatInterfaceState(_ presentationInterfaceState: Cha
|
||||
}
|
||||
}
|
||||
}
|
||||
if let subject = subject, case .scheduledMessages = subject {
|
||||
if let currentButton = currentButton, currentButton.action == .dismiss {
|
||||
return currentButton
|
||||
} else {
|
||||
return ChatNavigationButton(action: .dismiss, buttonItem: UIBarButtonItem(title: strings.Common_Done, style: .plain, target: target, action: selector))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@ -98,10 +98,13 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
private var impressionIcon: ASImageNode?
|
||||
private var reactionNodes: [StatusReactionNode] = []
|
||||
private var reactionCountNode: TextNode?
|
||||
private var reactionButtonNode: HighlightTrackingButtonNode?
|
||||
|
||||
private var type: ChatMessageDateAndStatusType?
|
||||
private var theme: ChatPresentationThemeData?
|
||||
|
||||
var openReactions: (() -> Void)?
|
||||
|
||||
override init() {
|
||||
self.dateNode = TextNode()
|
||||
self.dateNode.isUserInteractionEnabled = false
|
||||
@ -109,8 +112,6 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
|
||||
super.init()
|
||||
|
||||
self.isUserInteractionEnabled = false
|
||||
|
||||
self.addSubnode(self.dateNode)
|
||||
}
|
||||
|
||||
@ -561,7 +562,8 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
}
|
||||
}
|
||||
node.frame = CGRect(origin: CGPoint(x: reactionOffset + 1, y: backgroundInsets.top + 1.0 + offset), size: layout.size)
|
||||
node.frame = CGRect(origin: CGPoint(x: reactionOffset + 1.0, y: backgroundInsets.top + 1.0 + offset), size: layout.size)
|
||||
reactionOffset += 1.0 + layout.size.width
|
||||
} else if let reactionCountNode = strongSelf.reactionCountNode {
|
||||
strongSelf.reactionCountNode = nil
|
||||
if animated {
|
||||
@ -572,6 +574,27 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
reactionCountNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
|
||||
if !strongSelf.reactionNodes.isEmpty {
|
||||
if strongSelf.reactionButtonNode == nil {
|
||||
let reactionButtonNode = HighlightTrackingButtonNode()
|
||||
strongSelf.reactionButtonNode = reactionButtonNode
|
||||
strongSelf.addSubnode(reactionButtonNode)
|
||||
reactionButtonNode.addTarget(strongSelf, action: #selector(strongSelf.reactionButtonPressed), forControlEvents: .touchUpInside)
|
||||
reactionButtonNode.highligthedChanged = { [weak strongSelf] highlighted in
|
||||
guard let strongSelf = strongSelf else {
|
||||
return
|
||||
}
|
||||
if highlighted {
|
||||
strongSelf.reactionButtonPressed()
|
||||
}
|
||||
}
|
||||
}
|
||||
strongSelf.reactionButtonNode?.frame = CGRect(origin: CGPoint(x: leftInset - reactionInset + backgroundInsets.left - 5.0, y: backgroundInsets.top + 1.0 + offset - 5.0), size: CGSize(width: reactionOffset + 5.0 * 2.0, height: 20.0))
|
||||
} else if let reactionButtonNode = strongSelf.reactionButtonNode {
|
||||
strongSelf.reactionButtonNode = nil
|
||||
reactionButtonNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -605,4 +628,17 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@objc private func reactionButtonPressed() {
|
||||
self.openReactions?()
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if let reactionButtonNode = self.reactionButtonNode {
|
||||
if reactionButtonNode.frame.contains(point) {
|
||||
return reactionButtonNode.view
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,6 +65,13 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
self.textAccessibilityOverlayNode.openUrl = { [weak self] url in
|
||||
self?.item?.controllerInteraction.openUrl(url, false, false)
|
||||
}
|
||||
|
||||
self.statusNode.openReactions = { [weak self] in
|
||||
guard let strongSelf = self, let item = strongSelf.item else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.openMessageReactions(item.message.id)
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
|
||||
@ -411,6 +411,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
}, editScheduledMessagesTime: { _ in
|
||||
}, performTextSelectionAction: { _, _, _ in
|
||||
}, updateMessageReaction: { _, _ in
|
||||
}, openMessageReactions: { _ in
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,
|
||||
|
||||
@ -113,6 +113,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
||||
}, editScheduledMessagesTime: { _ in
|
||||
}, performTextSelectionAction: { _, _, _ in
|
||||
}, updateMessageReaction: { _, _ in
|
||||
}, openMessageReactions: { _ in
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false))
|
||||
|
||||
@ -287,6 +287,7 @@ public class PeerMediaCollectionController: TelegramBaseController {
|
||||
}, editScheduledMessagesTime: { _ in
|
||||
}, performTextSelectionAction: { _, _, _ in
|
||||
}, updateMessageReaction: { _, _ in
|
||||
}, openMessageReactions: { _ in
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||
|
||||
@ -178,6 +178,7 @@
|
||||
D06BB8821F58994B0084FC30 /* LegacyInstantVideoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06BB8811F58994B0084FC30 /* LegacyInstantVideoController.swift */; };
|
||||
D06E0F8E1F79ABFB003CF3DD /* ChatLoadingNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06E0F8D1F79ABFB003CF3DD /* ChatLoadingNode.swift */; };
|
||||
D06F1EA41F6C0A5D00FE8B74 /* ChatHistorySearchContainerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06F1EA31F6C0A5D00FE8B74 /* ChatHistorySearchContainerNode.swift */; };
|
||||
D072F38423155EAF0009E66F /* MessageReactionListUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D072F38323155EAF0009E66F /* MessageReactionListUI.framework */; };
|
||||
D0750C7822B2A13300BE5F6E /* UniversalMediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0750C7722B2A13300BE5F6E /* UniversalMediaPlayer.framework */; };
|
||||
D0750C7A22B2A14300BE5F6E /* DeviceAccess.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0750C7922B2A14300BE5F6E /* DeviceAccess.framework */; };
|
||||
D0750C7C22B2A14300BE5F6E /* TelegramPresentationData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0750C7B22B2A14300BE5F6E /* TelegramPresentationData.framework */; };
|
||||
@ -812,6 +813,7 @@
|
||||
D06BB8811F58994B0084FC30 /* LegacyInstantVideoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyInstantVideoController.swift; sourceTree = "<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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -1169,6 +1171,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D072F38423155EAF0009E66F /* MessageReactionListUI.framework in Frameworks */,
|
||||
D03E495D230868DF0049C28B /* PersistentStringHash.framework in Frameworks */,
|
||||
D03E493C2308679D0049C28B /* InstantPageCache.framework in Frameworks */,
|
||||
D03E4910230866280049C28B /* GridMessageSelectionNode.framework in Frameworks */,
|
||||
@ -1773,6 +1776,7 @@
|
||||
D08D45281D5E340200A7428A /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D072F38323155EAF0009E66F /* MessageReactionListUI.framework */,
|
||||
D03E495C230868DF0049C28B /* PersistentStringHash.framework */,
|
||||
D03E493B2308679D0049C28B /* InstantPageCache.framework */,
|
||||
D03E490F230866280049C28B /* GridMessageSelectionNode.framework */,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user