mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Modal UI improvements
This commit is contained in:
parent
f8b434f341
commit
6c544eb9c2
@ -15,6 +15,7 @@ static_library(
|
||||
"//submodules/HorizontalPeerItem:HorizontalPeerItem",
|
||||
"//submodules/MergeLists:MergeLists",
|
||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||
"//submodules/ContextUI:ContextUI",
|
||||
],
|
||||
frameworks = [
|
||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||
|
@ -9,6 +9,7 @@ import TelegramPresentationData
|
||||
import MergeLists
|
||||
import HorizontalPeerItem
|
||||
import ListSectionHeaderNode
|
||||
import ContextUI
|
||||
|
||||
private func calculateItemCustomWidth(width: CGFloat) -> CGFloat {
|
||||
let itemInsets = UIEdgeInsets(top: 0.0, left: 6.0, bottom: 0.0, right: 6.0)
|
||||
@ -76,9 +77,9 @@ private struct ChatListSearchRecentPeersEntry: Comparable, Identifiable {
|
||||
return lhs.index < rhs.index
|
||||
}
|
||||
|
||||
func item(account: Account, mode: HorizontalPeerItemMode, peerSelected: @escaping (Peer) -> Void, peerLongTapped: @escaping (Peer) -> Void, isPeerSelected: @escaping (PeerId) -> Bool) -> ListViewItem {
|
||||
return HorizontalPeerItem(theme: self.theme, strings: self.strings, mode: mode, account: account, peer: self.peer, presence: self.presence, unreadBadge: self.unreadBadge, action: peerSelected, longTapAction: { peer in
|
||||
peerLongTapped(peer)
|
||||
func item(account: Account, mode: HorizontalPeerItemMode, peerSelected: @escaping (Peer) -> Void, peerContextAction: @escaping (Peer, ASDisplayNode, ContextGesture?) -> Void, isPeerSelected: @escaping (PeerId) -> Bool) -> ListViewItem {
|
||||
return HorizontalPeerItem(theme: self.theme, strings: self.strings, mode: mode, account: account, peer: self.peer, presence: self.presence, unreadBadge: self.unreadBadge, action: peerSelected, contextAction: { peer, node, gesture in
|
||||
peerContextAction(peer, node, gesture)
|
||||
}, isPeerSelected: isPeerSelected, customWidth: self.itemCustomWidth)
|
||||
}
|
||||
}
|
||||
@ -91,16 +92,12 @@ private struct ChatListSearchRecentNodeTransition {
|
||||
let animated: Bool
|
||||
}
|
||||
|
||||
private func preparedRecentPeersTransition(account: Account, mode: HorizontalPeerItemMode, peerSelected: @escaping (Peer) -> Void, peerLongTapped: @escaping (Peer) -> Void, isPeerSelected: @escaping (PeerId) -> Bool, share: Bool = false, from fromEntries: [ChatListSearchRecentPeersEntry], to toEntries: [ChatListSearchRecentPeersEntry], firstTime: Bool, animated: Bool) -> ChatListSearchRecentNodeTransition {
|
||||
private func preparedRecentPeersTransition(account: Account, mode: HorizontalPeerItemMode, peerSelected: @escaping (Peer) -> Void, peerContextAction: @escaping (Peer, ASDisplayNode, ContextGesture?) -> Void, isPeerSelected: @escaping (PeerId) -> Bool, share: Bool = false, from fromEntries: [ChatListSearchRecentPeersEntry], to toEntries: [ChatListSearchRecentPeersEntry], firstTime: Bool, animated: Bool) -> ChatListSearchRecentNodeTransition {
|
||||
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(account: account, mode: mode, peerSelected: peerSelected, peerLongTapped: { peer in
|
||||
peerLongTapped(peer)
|
||||
}, isPeerSelected: isPeerSelected), directionHint: .Down) }
|
||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, mode: mode, peerSelected: peerSelected, peerLongTapped: { peer in
|
||||
peerLongTapped(peer)
|
||||
}, isPeerSelected: isPeerSelected), directionHint: nil) }
|
||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, mode: mode, peerSelected: peerSelected, peerContextAction: peerContextAction, isPeerSelected: isPeerSelected), directionHint: .Down) }
|
||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, mode: mode, peerSelected: peerSelected, peerContextAction: peerContextAction, isPeerSelected: isPeerSelected), directionHint: nil) }
|
||||
|
||||
return ChatListSearchRecentNodeTransition(deletions: deletions, insertions: insertions, updates: updates, firstTime: firstTime, animated: animated)
|
||||
}
|
||||
@ -115,7 +112,7 @@ public final class ChatListSearchRecentPeersNode: ASDisplayNode {
|
||||
private let share: Bool
|
||||
|
||||
private let peerSelected: (Peer) -> Void
|
||||
private let peerLongTapped: (Peer) -> Void
|
||||
private let peerContextAction: (Peer, ASDisplayNode, ContextGesture?) -> Void
|
||||
private let isPeerSelected: (PeerId) -> Bool
|
||||
|
||||
private let disposable = MetaDisposable()
|
||||
@ -124,14 +121,14 @@ public final class ChatListSearchRecentPeersNode: ASDisplayNode {
|
||||
private var items: [ListViewItem] = []
|
||||
private var queuedTransitions: [ChatListSearchRecentNodeTransition] = []
|
||||
|
||||
public init(account: Account, theme: PresentationTheme, mode: HorizontalPeerItemMode, strings: PresentationStrings, peerSelected: @escaping (Peer) -> Void, peerLongTapped: @escaping (Peer) -> Void, isPeerSelected: @escaping (PeerId) -> Bool, share: Bool = false) {
|
||||
public init(account: Account, theme: PresentationTheme, mode: HorizontalPeerItemMode, strings: PresentationStrings, peerSelected: @escaping (Peer) -> Void, peerContextAction: @escaping (Peer, ASDisplayNode, ContextGesture?) -> Void, isPeerSelected: @escaping (PeerId) -> Bool, share: Bool = false) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.themeAndStringsPromise = Promise((self.theme, self.strings))
|
||||
self.mode = mode
|
||||
self.share = share
|
||||
self.peerSelected = peerSelected
|
||||
self.peerLongTapped = peerLongTapped
|
||||
self.peerContextAction = peerContextAction
|
||||
self.isPeerSelected = isPeerSelected
|
||||
|
||||
self.sectionHeaderNode = ListSectionHeaderNode(theme: theme)
|
||||
@ -210,7 +207,7 @@ public final class ChatListSearchRecentPeersNode: ASDisplayNode {
|
||||
|
||||
let animated = !firstTime.swap(false)
|
||||
|
||||
let transition = preparedRecentPeersTransition(account: account, mode: mode, peerSelected: peerSelected, peerLongTapped: peerLongTapped, isPeerSelected: isPeerSelected, from: previous.swap(entries), to: entries, firstTime: !animated, animated: animated)
|
||||
let transition = preparedRecentPeersTransition(account: account, mode: mode, peerSelected: peerSelected, peerContextAction: peerContextAction, isPeerSelected: isPeerSelected, from: previous.swap(entries), to: entries, firstTime: !animated, animated: animated)
|
||||
|
||||
strongSelf.enqueueTransition(transition)
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, source: ChatC
|
||||
if case let .search(search) = source {
|
||||
switch search {
|
||||
case .recentPeers:
|
||||
items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_RemoveFromRecents, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { _, f in
|
||||
items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_RemoveFromRecents, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor) }, action: { _, f in
|
||||
let _ = (removeRecentPeer(account: context.account, peerId: peerId)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
f(.default)
|
||||
|
@ -7,6 +7,7 @@ import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import ChatListSearchRecentPeersNode
|
||||
import ContextUI
|
||||
|
||||
class ChatListRecentPeersListItem: ListViewItem {
|
||||
let theme: PresentationTheme
|
||||
@ -14,17 +15,17 @@ class ChatListRecentPeersListItem: ListViewItem {
|
||||
let account: Account
|
||||
let peers: [Peer]
|
||||
let peerSelected: (Peer) -> Void
|
||||
let peerLongTapped: (Peer) -> Void
|
||||
let peerContextAction: (Peer, ASDisplayNode, ContextGesture?) -> Void
|
||||
|
||||
let header: ListViewItemHeader?
|
||||
|
||||
init(theme: PresentationTheme, strings: PresentationStrings, account: Account, peers: [Peer], peerSelected: @escaping (Peer) -> Void, peerLongTapped: @escaping (Peer) -> Void) {
|
||||
init(theme: PresentationTheme, strings: PresentationStrings, account: Account, peers: [Peer], peerSelected: @escaping (Peer) -> Void, peerContextAction: @escaping (Peer, ASDisplayNode, ContextGesture?) -> Void) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.account = account
|
||||
self.peers = peers
|
||||
self.peerSelected = peerSelected
|
||||
self.peerLongTapped = peerLongTapped
|
||||
self.peerContextAction = peerContextAction
|
||||
self.header = nil
|
||||
}
|
||||
|
||||
@ -115,8 +116,8 @@ class ChatListRecentPeersListItemNode: ListViewItemNode {
|
||||
} else {
|
||||
peersNode = ChatListSearchRecentPeersNode(account: item.account, theme: item.theme, mode: .list, strings: item.strings, peerSelected: { peer in
|
||||
self?.item?.peerSelected(peer)
|
||||
}, peerLongTapped: { peer in
|
||||
self?.item?.peerLongTapped(peer)
|
||||
}, peerContextAction: { peer, node, gesture in
|
||||
self?.item?.peerContextAction(peer, node, gesture)
|
||||
}, isPeerSelected: { _ in
|
||||
return false
|
||||
})
|
||||
|
@ -78,13 +78,17 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
|
||||
}
|
||||
}
|
||||
|
||||
func item(context: AccountContext, filter: ChatListNodePeersFilter, peerSelected: @escaping (Peer) -> Void, peerLongTapped: @escaping (Peer) -> Void, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, clearRecentlySearchedPeers: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, deletePeer: @escaping (PeerId) -> Void) -> ListViewItem {
|
||||
func item(context: AccountContext, filter: ChatListNodePeersFilter, peerSelected: @escaping (Peer) -> Void, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, clearRecentlySearchedPeers: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, deletePeer: @escaping (PeerId) -> Void) -> ListViewItem {
|
||||
switch self {
|
||||
case let .topPeers(peers, theme, strings):
|
||||
return ChatListRecentPeersListItem(theme: theme, strings: strings, account: context.account, peers: peers, peerSelected: { peer in
|
||||
peerSelected(peer)
|
||||
}, peerLongTapped: { peer in
|
||||
peerLongTapped(peer)
|
||||
}, peerContextAction: { peer, node, gesture in
|
||||
if let peerContextAction = peerContextAction {
|
||||
peerContextAction(peer, .recentPeers, node, gesture)
|
||||
} else {
|
||||
gesture?.cancel()
|
||||
}
|
||||
})
|
||||
case let .peer(_, peer, theme, strings, timeFormat, nameSortOrder, nameDisplayOrder, hasRevealControls):
|
||||
let primaryPeer: Peer
|
||||
@ -466,12 +470,12 @@ public struct ChatListSearchContainerTransition {
|
||||
}
|
||||
}
|
||||
|
||||
private func chatListSearchContainerPreparedRecentTransition(from fromEntries: [ChatListRecentEntry], to toEntries: [ChatListRecentEntry], context: AccountContext, filter: ChatListNodePeersFilter, peerSelected: @escaping (Peer) -> Void, peerLongTapped: @escaping (Peer) -> Void, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, clearRecentlySearchedPeers: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, deletePeer: @escaping (PeerId) -> Void) -> ChatListSearchContainerRecentTransition {
|
||||
private func chatListSearchContainerPreparedRecentTransition(from fromEntries: [ChatListRecentEntry], to toEntries: [ChatListRecentEntry], context: AccountContext, filter: ChatListNodePeersFilter, peerSelected: @escaping (Peer) -> Void, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, clearRecentlySearchedPeers: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, deletePeer: @escaping (PeerId) -> Void) -> ChatListSearchContainerRecentTransition {
|
||||
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, filter: filter, peerSelected: peerSelected, peerLongTapped: peerLongTapped, peerContextAction: peerContextAction, clearRecentlySearchedPeers: clearRecentlySearchedPeers, setPeerIdWithRevealedOptions: setPeerIdWithRevealedOptions, deletePeer: deletePeer), directionHint: nil) }
|
||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, filter: filter, peerSelected: peerSelected, peerLongTapped: peerLongTapped, peerContextAction: peerContextAction, clearRecentlySearchedPeers: clearRecentlySearchedPeers, setPeerIdWithRevealedOptions: setPeerIdWithRevealedOptions, deletePeer: deletePeer), directionHint: nil) }
|
||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, filter: filter, peerSelected: peerSelected, peerContextAction: peerContextAction, clearRecentlySearchedPeers: clearRecentlySearchedPeers, setPeerIdWithRevealedOptions: setPeerIdWithRevealedOptions, deletePeer: deletePeer), directionHint: nil) }
|
||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, filter: filter, peerSelected: peerSelected, peerContextAction: peerContextAction, clearRecentlySearchedPeers: clearRecentlySearchedPeers, setPeerIdWithRevealedOptions: setPeerIdWithRevealedOptions, deletePeer: deletePeer), directionHint: nil) }
|
||||
|
||||
return ChatListSearchContainerRecentTransition(deletions: deletions, insertions: insertions, updates: updates)
|
||||
}
|
||||
@ -969,8 +973,6 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
openPeer(peer, true)
|
||||
let _ = addRecentlySearchedPeer(postbox: context.account.postbox, peerId: peer.id).start()
|
||||
self?.recentListNode.clearHighlightAnimated(true)
|
||||
}, peerLongTapped: { peer in
|
||||
openRecentPeerOptions(peer)
|
||||
}, peerContextAction: peerContextAction,
|
||||
clearRecentlySearchedPeers: {
|
||||
self?.clearRecentSearch()
|
||||
|
@ -63,7 +63,7 @@ public func childWindowHostView(parent: UIView) -> WindowHostView {
|
||||
let view = ChildWindowHostView()
|
||||
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
|
||||
let hostView = WindowHostView(containerView: view, eventView: view, aboveStatusBarView: view, isRotating: {
|
||||
let hostView = WindowHostView(containerView: view, eventView: view, isRotating: {
|
||||
return false
|
||||
}, updateSupportedInterfaceOrientations: { orientations in
|
||||
}, updateDeferScreenEdgeGestures: { edges in
|
||||
|
@ -90,6 +90,39 @@ public extension ContainedViewLayoutTransition {
|
||||
}
|
||||
}
|
||||
|
||||
func updateFrameAsPositionAndBounds(node: ASDisplayNode, frame: CGRect, force: Bool = false, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
if node.frame.equalTo(frame) && !force {
|
||||
completion?(true)
|
||||
} else {
|
||||
switch self {
|
||||
case .immediate:
|
||||
node.position = frame.center
|
||||
node.bounds = CGRect(origin: CGPoint(), size: frame.size)
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
}
|
||||
case let .animated(duration, curve):
|
||||
let previousPosition: CGPoint
|
||||
let previousBounds: CGRect
|
||||
if beginWithCurrentState, let presentation = node.layer.presentation() {
|
||||
previousPosition = presentation.position
|
||||
previousBounds = presentation.bounds
|
||||
} else {
|
||||
previousPosition = node.position
|
||||
previousBounds = node.bounds
|
||||
}
|
||||
node.position = frame.center
|
||||
node.bounds = CGRect(origin: CGPoint(), size: frame.size)
|
||||
node.layer.animateFrame(from:
|
||||
CGRect(origin: CGPoint(x: previousPosition.x - previousBounds.width / 2.0, y: previousPosition.y - previousBounds.height / 2.0), size: previousBounds.size), to: frame, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, force: force, completion: { result in
|
||||
if let completion = completion {
|
||||
completion(result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateFrameAdditive(node: ASDisplayNode, frame: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
if node.frame.equalTo(frame) && !force {
|
||||
completion?(true)
|
||||
|
@ -125,7 +125,7 @@ public enum GeneralScrollDirection {
|
||||
}
|
||||
|
||||
open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGestureRecognizerDelegate {
|
||||
private final let scroller: ListViewScroller
|
||||
final let scroller: ListViewScroller
|
||||
private final var visibleSize: CGSize = CGSize()
|
||||
public private(set) final var insets = UIEdgeInsets()
|
||||
public final var visualInsets: UIEdgeInsets?
|
||||
|
@ -339,7 +339,7 @@ private final class NativeWindow: UIWindow, WindowHost {
|
||||
}
|
||||
}
|
||||
|
||||
public func nativeWindowHostView() -> (UIWindow & WindowHost, WindowHostView, UIWindow) {
|
||||
public func nativeWindowHostView() -> (UIWindow & WindowHost, WindowHostView) {
|
||||
let window = NativeWindow(frame: UIScreen.main.bounds)
|
||||
|
||||
let rootViewController = WindowRootViewController()
|
||||
@ -348,16 +348,7 @@ public func nativeWindowHostView() -> (UIWindow & WindowHost, WindowHostView, UI
|
||||
rootViewController.view.frame = CGRect(origin: CGPoint(), size: window.bounds.size)
|
||||
rootViewController.viewDidAppear(false)
|
||||
|
||||
let aboveStatusbarWindow = AboveStatusBarWindow(frame: UIScreen.main.bounds)
|
||||
aboveStatusbarWindow.supportedOrientations = { [weak rootViewController] in
|
||||
if let rootViewController = rootViewController {
|
||||
return rootViewController.supportedInterfaceOrientations
|
||||
} else {
|
||||
return .portrait
|
||||
}
|
||||
}
|
||||
|
||||
let hostView = WindowHostView(containerView: rootViewController.view, eventView: window, aboveStatusBarView: rootViewController.view, isRotating: {
|
||||
let hostView = WindowHostView(containerView: rootViewController.view, eventView: window, isRotating: {
|
||||
return window.isRotating()
|
||||
}, updateSupportedInterfaceOrientations: { orientations in
|
||||
rootViewController.orientations = orientations
|
||||
@ -433,5 +424,5 @@ public func nativeWindowHostView() -> (UIWindow & WindowHost, WindowHostView, UI
|
||||
}
|
||||
}
|
||||
|
||||
return (window, hostView, aboveStatusbarWindow)
|
||||
return (window, hostView)
|
||||
}
|
||||
|
@ -217,37 +217,50 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
func update(layout: ContainerViewLayout, canBeClosed: Bool, controllers: [ViewController], transition: ContainedViewLayoutTransition) {
|
||||
let previousControllers = self.controllers
|
||||
self.controllers = controllers
|
||||
|
||||
for i in 0 ..< controllers.count {
|
||||
if i == 0 {
|
||||
if canBeClosed {
|
||||
controllers[i].navigationBar?.previousItem = .close
|
||||
} else {
|
||||
controllers[i].navigationBar?.previousItem = nil
|
||||
}
|
||||
} else {
|
||||
controllers[i].navigationBar?.previousItem = .item(controllers[i - 1].navigationItem)
|
||||
}
|
||||
}
|
||||
|
||||
self.state.layout = layout
|
||||
self.state.canBeClosed = canBeClosed
|
||||
|
||||
if controllers.last !== self.state.top?.value {
|
||||
if controllers.last !== self.state.pending?.value.value {
|
||||
self.state.pending = nil
|
||||
if let last = controllers.last {
|
||||
let transitionType: PendingChild.TransitionType
|
||||
if !previousControllers.contains(where: { $0 === last }) {
|
||||
transitionType = .push
|
||||
var controllersUpdated = false
|
||||
if self.controllers.count != controllers.count {
|
||||
controllersUpdated = true
|
||||
} else {
|
||||
for i in 0 ..< controllers.count {
|
||||
if self.controllers[i] !== controllers[i] {
|
||||
controllersUpdated = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if controllersUpdated {
|
||||
let previousControllers = self.controllers
|
||||
self.controllers = controllers
|
||||
|
||||
for i in 0 ..< controllers.count {
|
||||
if i == 0 {
|
||||
if canBeClosed {
|
||||
controllers[i].navigationBar?.previousItem = .close
|
||||
} else {
|
||||
transitionType = .pop
|
||||
controllers[i].navigationBar?.previousItem = nil
|
||||
}
|
||||
} else {
|
||||
controllers[i].navigationBar?.previousItem = .item(controllers[i - 1].navigationItem)
|
||||
}
|
||||
}
|
||||
|
||||
if controllers.last !== self.state.top?.value {
|
||||
if controllers.last !== self.state.pending?.value.value {
|
||||
self.state.pending = nil
|
||||
if let last = controllers.last {
|
||||
let transitionType: PendingChild.TransitionType
|
||||
if !previousControllers.contains(where: { $0 === last }) {
|
||||
transitionType = .push
|
||||
} else {
|
||||
transitionType = .pop
|
||||
}
|
||||
self.state.pending = PendingChild(value: self.makeChild(layout: layout, value: last), transitionType: transitionType, transition: transition, update: { [weak self] pendingChild in
|
||||
self?.pendingChildIsReady(pendingChild)
|
||||
})
|
||||
}
|
||||
self.state.pending = PendingChild(value: self.makeChild(layout: layout, value: last), transitionType: transitionType, transition: transition, update: { [weak self] pendingChild in
|
||||
self?.pendingChildIsReady(pendingChild)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -357,6 +370,10 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
private func applyLayout(layout: ContainerViewLayout, to child: Child, transition: ContainedViewLayoutTransition) {
|
||||
let childFrame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
if child.value.displayNode.frame != childFrame {
|
||||
transition.updateFrame(node: child.value.displayNode, frame: childFrame)
|
||||
}
|
||||
if child.layout != layout {
|
||||
child.layout = layout
|
||||
child.value.containerLayoutUpdated(layout, transition: transition)
|
||||
|
@ -232,7 +232,6 @@ 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))
|
||||
self.updateContainers(layout: layout, transition: transition)
|
||||
}
|
||||
|
||||
@ -254,10 +253,8 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
}
|
||||
|
||||
let modalContainer: NavigationModalContainer
|
||||
let containerTransition: ContainedViewLayoutTransition
|
||||
if let existingModalContainer = existingModalContainer {
|
||||
modalContainer = existingModalContainer
|
||||
containerTransition = transition
|
||||
} else {
|
||||
modalContainer = NavigationModalContainer(theme: self.theme, controllerRemoved: { [weak self] controller in
|
||||
self?.controllerRemoved(controller)
|
||||
@ -273,12 +270,12 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
}
|
||||
}
|
||||
}
|
||||
modalContainer.updateDismissProgress = { [weak self, weak modalContainer] _ in
|
||||
modalContainer.updateDismissProgress = { [weak self, weak modalContainer] _, transition in
|
||||
guard let strongSelf = self, let modalContainer = modalContainer else {
|
||||
return
|
||||
}
|
||||
if let layout = strongSelf.validLayout {
|
||||
strongSelf.updateContainers(layout: layout, transition: .immediate)
|
||||
strongSelf.updateContainers(layout: layout, transition: transition)
|
||||
}
|
||||
}
|
||||
modalContainer.interactivelyDismissed = { [weak self, weak modalContainer] in
|
||||
@ -290,11 +287,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
}
|
||||
strongSelf.setViewControllers(controllers, animated: false)
|
||||
}
|
||||
containerTransition = .immediate
|
||||
}
|
||||
|
||||
containerTransition.updateFrame(node: modalContainer, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
modalContainer.update(layout: layout, controllers: navigationLayout.modal[i].controllers, transition: containerTransition)
|
||||
modalContainers.append(modalContainer)
|
||||
}
|
||||
|
||||
@ -313,6 +306,26 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
|
||||
for i in (0 ..< navigationLayout.modal.count).reversed() {
|
||||
let modalContainer = self.modalContainers[i]
|
||||
|
||||
let containerTransition: ContainedViewLayoutTransition
|
||||
if modalContainer.supernode == nil {
|
||||
containerTransition = .immediate
|
||||
} else {
|
||||
containerTransition = transition
|
||||
}
|
||||
|
||||
let effectiveModalTransition: CGFloat
|
||||
if visibleModalCount == 0 {
|
||||
effectiveModalTransition = 0.0
|
||||
} else if visibleModalCount == 1 {
|
||||
effectiveModalTransition = 1.0 - topModalDismissProgress
|
||||
} else {
|
||||
effectiveModalTransition = 1.0
|
||||
}
|
||||
|
||||
containerTransition.updateFrame(node: modalContainer, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
modalContainer.update(layout: layout, controllers: navigationLayout.modal[i].controllers, coveredByModalTransition: effectiveModalTransition, transition: containerTransition)
|
||||
|
||||
if modalContainer.supernode == nil && modalContainer.isReady {
|
||||
if let previousModalContainer = previousModalContainer {
|
||||
self.displayNode.insertSubnode(modalContainer, belowSubnode: previousModalContainer)
|
||||
@ -321,6 +334,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
}
|
||||
modalContainer.animateIn(transition: transition)
|
||||
}
|
||||
|
||||
if modalContainer.supernode != nil {
|
||||
visibleModalCount += 1
|
||||
if previousModalContainer == nil {
|
||||
@ -398,6 +412,19 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
switch layout.metrics.widthClass {
|
||||
case .compact:
|
||||
if visibleModalCount != 0 {
|
||||
let effectiveRootModalDismissProgress: CGFloat
|
||||
let additionalModalFrameProgress: CGFloat
|
||||
if visibleModalCount == 1 {
|
||||
effectiveRootModalDismissProgress = topModalDismissProgress
|
||||
additionalModalFrameProgress = 0.0
|
||||
} else if visibleModalCount == 2 {
|
||||
effectiveRootModalDismissProgress = 0.0
|
||||
additionalModalFrameProgress = 1.0 - topModalDismissProgress
|
||||
} else {
|
||||
effectiveRootModalDismissProgress = 0.0
|
||||
additionalModalFrameProgress = 1.0
|
||||
}
|
||||
|
||||
let rootModalFrame: NavigationModalFrame
|
||||
var modalFrameTransition: ContainedViewLayoutTransition = transition
|
||||
var forceStatusBarAnimation = false
|
||||
@ -405,14 +432,11 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
rootModalFrame = current
|
||||
transition.updateFrame(node: rootModalFrame, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
rootModalFrame.update(layout: layout, transition: modalFrameTransition)
|
||||
rootModalFrame.updateDismissal(transition: transition, progress: topModalDismissProgress, completion: {})
|
||||
rootModalFrame.updateDismissal(transition: transition, progress: effectiveRootModalDismissProgress, additionalProgress: additionalModalFrameProgress, completion: {})
|
||||
forceStatusBarAnimation = true
|
||||
} else {
|
||||
rootModalFrame = NavigationModalFrame(theme: self.theme)
|
||||
self.rootModalFrame = rootModalFrame
|
||||
rootModalFrame.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
rootModalFrame.update(layout: layout, transition: .immediate)
|
||||
rootModalFrame.animateIn(transition: transition)
|
||||
if let rootContainer = self.rootContainer {
|
||||
var rootContainerNode: ASDisplayNode
|
||||
switch rootContainer {
|
||||
@ -423,9 +447,11 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
}
|
||||
self.displayNode.insertSubnode(rootModalFrame, aboveSubnode: rootContainerNode)
|
||||
}
|
||||
rootModalFrame.updateDismissal(transition: transition, progress: topModalDismissProgress, completion: {})
|
||||
rootModalFrame.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
rootModalFrame.update(layout: layout, transition: .immediate)
|
||||
rootModalFrame.updateDismissal(transition: transition, progress: effectiveRootModalDismissProgress, additionalProgress: additionalModalFrameProgress, completion: {})
|
||||
}
|
||||
if topModalDismissProgress < 0.5 {
|
||||
if effectiveRootModalDismissProgress < 0.5 {
|
||||
self.statusBarHost?.setStatusBarStyle(.lightContent, animated: transition.isAnimated || forceStatusBarAnimation)
|
||||
} else {
|
||||
let normalStatusBarStyle: UIStatusBarStyle
|
||||
@ -445,21 +471,28 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
case let .split(container):
|
||||
rootContainerNode = container
|
||||
}
|
||||
let maxScale = (layout.size.width - 16.0 * 2.0) / layout.size.width
|
||||
var topInset: CGFloat = 0.0
|
||||
if let statusBarHeight = layout.statusBarHeight {
|
||||
topInset += statusBarHeight
|
||||
}
|
||||
let maxOffset: CGFloat = (topInset - (layout.size.height - layout.size.height * maxScale) / 2.0)
|
||||
let maxScale: CGFloat
|
||||
let maxOffset: CGFloat
|
||||
if visibleModalCount == 1 {
|
||||
maxScale = (layout.size.width - 16.0 * 2.0) / layout.size.width
|
||||
maxOffset = (topInset - (layout.size.height - layout.size.height * maxScale) / 2.0)
|
||||
} else {
|
||||
maxScale = (layout.size.width - 16.0 * 2.0 * 2.0) / layout.size.width
|
||||
maxOffset = (topInset + 10.0 - (layout.size.height - layout.size.height * maxScale) / 2.0)
|
||||
}
|
||||
|
||||
let scale = 1.0 * topModalDismissProgress + (1.0 - topModalDismissProgress) * maxScale
|
||||
let offset = (1.0 - topModalDismissProgress) * maxOffset
|
||||
let scale = 1.0 * effectiveRootModalDismissProgress + (1.0 - effectiveRootModalDismissProgress) * maxScale
|
||||
let offset = (1.0 - effectiveRootModalDismissProgress) * maxOffset
|
||||
transition.updateSublayerTransformScaleAndOffset(node: rootContainerNode, scale: scale, offset: CGPoint(x: 0.0, y: offset))
|
||||
}
|
||||
} else {
|
||||
if let rootModalFrame = self.rootModalFrame {
|
||||
self.rootModalFrame = nil
|
||||
rootModalFrame.updateDismissal(transition: transition, progress: 1.0, completion: { [weak rootModalFrame] in
|
||||
rootModalFrame.updateDismissal(transition: transition, progress: 1.0, additionalProgress: 0.0, completion: { [weak rootModalFrame] in
|
||||
rootModalFrame?.removeFromSupernode()
|
||||
})
|
||||
let normalStatusBarStyle: UIStatusBarStyle
|
||||
@ -485,7 +518,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
case .regular:
|
||||
if let rootModalFrame = self.rootModalFrame {
|
||||
self.rootModalFrame = nil
|
||||
rootModalFrame.updateDismissal(transition: .immediate, progress: 1.0, completion: { [weak rootModalFrame] in
|
||||
rootModalFrame.updateDismissal(transition: .immediate, progress: 1.0, additionalProgress: 0.0, completion: { [weak rootModalFrame] in
|
||||
rootModalFrame?.removeFromSupernode()
|
||||
})
|
||||
let normalStatusBarStyle: UIStatusBarStyle
|
||||
|
@ -13,17 +13,16 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
|
||||
private(set) var isReady: Bool = false
|
||||
private(set) var dismissProgress: CGFloat = 0.0
|
||||
var isReadyUpdated: (() -> Void)?
|
||||
var updateDismissProgress: ((CGFloat) -> Void)?
|
||||
var updateDismissProgress: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
|
||||
var interactivelyDismissed: (() -> Void)?
|
||||
|
||||
private var ignoreScrolling = false
|
||||
private var animator: DisplayLinkAnimator?
|
||||
private var isDismissed = false
|
||||
|
||||
init(theme: NavigationControllerTheme, controllerRemoved: @escaping (ViewController) -> Void) {
|
||||
self.theme = theme
|
||||
|
||||
self.dim = ASDisplayNode()
|
||||
self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.4)
|
||||
self.dim.alpha = 0.0
|
||||
|
||||
self.scrollNode = ASScrollNode()
|
||||
@ -47,10 +46,8 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
|
||||
strongSelf.isReadyUpdated?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.animator?.invalidate()
|
||||
|
||||
applySmoothRoundedCorners(self.container.layer)
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
@ -69,64 +66,92 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
if self.ignoreScrolling {
|
||||
if self.ignoreScrolling || self.isDismissed {
|
||||
return
|
||||
}
|
||||
var progress = (self.bounds.height - scrollView.bounds.origin.y) / self.bounds.height
|
||||
progress = max(0.0, min(1.0, progress))
|
||||
self.dismissProgress = progress
|
||||
self.dim.alpha = 1.0 - progress
|
||||
self.updateDismissProgress?(progress)
|
||||
self.updateDismissProgress?(progress, .immediate)
|
||||
}
|
||||
|
||||
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
|
||||
private var endDraggingVelocity: CGPoint?
|
||||
|
||||
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||||
let velocity = self.endDraggingVelocity ?? CGPoint()
|
||||
self.endDraggingVelocity = nil
|
||||
|
||||
var progress = (self.bounds.height - scrollView.bounds.origin.y) / self.bounds.height
|
||||
progress = max(0.0, min(1.0, progress))
|
||||
|
||||
self.ignoreScrolling = true
|
||||
targetContentOffset.pointee = scrollView.contentOffset
|
||||
scrollView.setContentOffset(scrollView.contentOffset, animated: false)
|
||||
self.ignoreScrolling = false
|
||||
self.animator?.invalidate()
|
||||
let targetOffset: CGFloat
|
||||
if velocity.y < -0.5 || progress >= 0.5 {
|
||||
targetOffset = 0.0
|
||||
} else {
|
||||
targetOffset = self.bounds.height
|
||||
}
|
||||
let velocityFactor: CGFloat = 0.4 / max(1.0, abs(velocity.y))
|
||||
self.animator = DisplayLinkAnimator(duration: Double(min(0.3, velocityFactor)), from: scrollView.contentOffset.y, to: targetOffset, update: { [weak self] value in
|
||||
let duration = Double(min(0.3, velocityFactor))
|
||||
let transition: ContainedViewLayoutTransition
|
||||
let dismissProgress: CGFloat
|
||||
if velocity.y < -0.5 || progress >= 0.5 {
|
||||
dismissProgress = 1.0
|
||||
targetOffset = 0.0
|
||||
transition = .animated(duration: duration, curve: .easeInOut)
|
||||
self.isDismissed = true
|
||||
} else {
|
||||
dismissProgress = 0.0
|
||||
targetOffset = self.bounds.height
|
||||
transition = .animated(duration: 0.5, curve: .spring)
|
||||
}
|
||||
self.ignoreScrolling = true
|
||||
let deltaY = targetOffset - scrollView.contentOffset.y
|
||||
scrollView.setContentOffset(scrollView.contentOffset, animated: false)
|
||||
scrollView.setContentOffset(CGPoint(x: 0.0, y: targetOffset), animated: false)
|
||||
transition.animateOffsetAdditive(layer: self.scrollNode.layer, offset: -deltaY, completion: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.scrollNode.view.contentOffset = CGPoint(x: 0.0, y: value)
|
||||
}, completion: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.animator = nil
|
||||
if targetOffset == 0.0 {
|
||||
strongSelf.interactivelyDismissed?()
|
||||
}
|
||||
})
|
||||
self.ignoreScrolling = false
|
||||
self.dismissProgress = dismissProgress
|
||||
transition.updateAlpha(node: self.dim, alpha: 1.0 - dismissProgress)
|
||||
self.updateDismissProgress?(dismissProgress, transition)
|
||||
}
|
||||
|
||||
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
|
||||
self.endDraggingVelocity = velocity
|
||||
}
|
||||
|
||||
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
||||
}
|
||||
|
||||
func update(layout: ContainerViewLayout, controllers: [ViewController], transition: ContainedViewLayoutTransition) {
|
||||
transition.updateFrame(node: self.dim, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
self.scrollNode.view.contentSize = CGSize(width: layout.size.width, height: layout.size.height * 2.0)
|
||||
if !self.scrollNode.view.isDecelerating && !self.scrollNode.view.isDragging && self.animator == nil {
|
||||
transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height), size: layout.size))
|
||||
func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func update(layout: ContainerViewLayout, controllers: [ViewController], coveredByModalTransition: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
if self.isDismissed {
|
||||
return
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.dim, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
self.ignoreScrolling = true
|
||||
self.scrollNode.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
self.scrollNode.view.contentSize = CGSize(width: layout.size.width, height: layout.size.height * 2.0)
|
||||
if !self.scrollNode.view.isDecelerating && !self.scrollNode.view.isDragging {
|
||||
let defaultBounds = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height), size: layout.size)
|
||||
if self.scrollNode.bounds != defaultBounds {
|
||||
self.scrollNode.bounds = defaultBounds
|
||||
}
|
||||
}
|
||||
self.ignoreScrolling = false
|
||||
|
||||
let containerLayout: ContainerViewLayout
|
||||
let containerFrame: CGRect
|
||||
let containerScale: CGFloat
|
||||
switch layout.metrics.widthClass {
|
||||
case .compact:
|
||||
self.dim.isHidden = true
|
||||
self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.25)
|
||||
self.container.clipsToBounds = true
|
||||
self.container.cornerRadius = 10.0
|
||||
if #available(iOS 11.0, *) {
|
||||
@ -139,9 +164,14 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
|
||||
containerLayout = ContainerViewLayout(size: CGSize(width: layout.size.width, height: layout.size.height - topInset), metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: layout.intrinsicInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.intrinsicInsets.right), safeInsets: UIEdgeInsets(top: 0.0, left: layout.safeInsets.left, bottom: layout.safeInsets.bottom, right: layout.safeInsets.right), statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver)
|
||||
containerFrame = CGRect(origin: CGPoint(x: 0.0, y: topInset), size: containerLayout.size)
|
||||
let unscaledFrame = CGRect(origin: CGPoint(x: 0.0, y: topInset - coveredByModalTransition * 10.0), size: containerLayout.size)
|
||||
let maxScale: CGFloat = (containerLayout.size.width - 16.0 * 2.0) / containerLayout.size.width
|
||||
containerScale = 1.0 * (1.0 - coveredByModalTransition) + maxScale * coveredByModalTransition
|
||||
let maxScaledTopInset: CGFloat = topInset - 10.0
|
||||
let scaledTopInset: CGFloat = topInset * (1.0 - coveredByModalTransition) + maxScaledTopInset * coveredByModalTransition
|
||||
containerFrame = unscaledFrame.offsetBy(dx: 0.0, dy: scaledTopInset - (unscaledFrame.midY - containerScale * unscaledFrame.height / 2.0))
|
||||
case .regular:
|
||||
self.dim.isHidden = false
|
||||
self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.4)
|
||||
self.container.clipsToBounds = true
|
||||
self.container.cornerRadius = 10.0
|
||||
if #available(iOS 11.0, *) {
|
||||
@ -153,6 +183,7 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
|
||||
let maxSide = max(layout.size.width, layout.size.height)
|
||||
let containerSize = CGSize(width: min(layout.size.width - 20.0, floor(maxSide / 2.0)), height: layout.size.height - verticalInset * 2.0)
|
||||
containerFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - containerSize.width) / 2.0), y: floor((layout.size.height - containerSize.height) / 2.0)), size: containerSize)
|
||||
containerScale = 1.0
|
||||
|
||||
var inputHeight: CGFloat?
|
||||
if let inputHeightValue = layout.inputHeight {
|
||||
@ -160,7 +191,8 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
containerLayout = ContainerViewLayout(size: containerSize, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver)
|
||||
}
|
||||
transition.updateFrame(node: self.container, frame: containerFrame.offsetBy(dx: 0.0, dy: layout.size.height))
|
||||
transition.updateFrameAsPositionAndBounds(node: self.container, frame: containerFrame.offsetBy(dx: 0.0, dy: layout.size.height))
|
||||
transition.updateTransformScale(node: self.container, scale: containerScale)
|
||||
self.container.update(layout: containerLayout, canBeClosed: true, controllers: controllers, transition: transition)
|
||||
}
|
||||
|
||||
@ -193,4 +225,30 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
|
||||
return transition
|
||||
}
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
guard let result = super.hitTest(point, with: event) else {
|
||||
return nil
|
||||
}
|
||||
var currentParent: UIView? = result
|
||||
while true {
|
||||
if currentParent == nil {
|
||||
break
|
||||
}
|
||||
if let scrollView = currentParent as? UIScrollView {
|
||||
if scrollView === self.scrollNode.view {
|
||||
break
|
||||
}
|
||||
if scrollView.isDecelerating && scrollView.contentOffset.y < scrollView.contentInset.top {
|
||||
return self.scrollNode.view
|
||||
}
|
||||
} else if let listView = currentParent as? ListViewBackingView, let listNode = listView.target {
|
||||
if listNode.scroller.isDecelerating && listNode.scroller.contentOffset.y < listNode.scroller.contentInset.top {
|
||||
return self.scrollNode.view
|
||||
}
|
||||
}
|
||||
currentParent = currentParent?.superview
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
@ -9,12 +9,14 @@ private func generateCornerImage(radius: CGFloat, mirror: Bool) -> UIImage? {
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
context.setBlendMode(.copy)
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: mirror ? (-radius) : 0.0, y: 0.0), size: CGSize(width: radius * 2.0, height: radius * 2.0)))
|
||||
|
||||
UIGraphicsPushContext(context)
|
||||
UIBezierPath(roundedRect: CGRect(origin: CGPoint(x: mirror ? (-radius) : 0.0, y: 0.0), size: CGSize(width: radius * 2.0, height: radius * 2.0)), cornerRadius: radius).fill()
|
||||
UIGraphicsPopContext()
|
||||
})
|
||||
}
|
||||
|
||||
final class NavigationModalFrame: ASDisplayNode {
|
||||
private let dim: ASDisplayNode
|
||||
private let topShade: ASDisplayNode
|
||||
private let leftShade: ASDisplayNode
|
||||
private let rightShade: ASDisplayNode
|
||||
@ -23,14 +25,11 @@ final class NavigationModalFrame: ASDisplayNode {
|
||||
|
||||
private var currentMaxCornerRadius: CGFloat?
|
||||
|
||||
private var progress: CGFloat = 0.0
|
||||
private var progress: CGFloat = 1.0
|
||||
private var additionalProgress: CGFloat = 0.0
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
init(theme: NavigationControllerTheme) {
|
||||
self.dim = ASDisplayNode()
|
||||
self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.4)
|
||||
self.dim.alpha = 0.0
|
||||
|
||||
self.topShade = ASDisplayNode()
|
||||
self.topShade.backgroundColor = .black
|
||||
self.leftShade = ASDisplayNode()
|
||||
@ -47,7 +46,6 @@ final class NavigationModalFrame: ASDisplayNode {
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.dim)
|
||||
self.addSubnode(self.topShade)
|
||||
self.addSubnode(self.leftShade)
|
||||
self.addSubnode(self.rightShade)
|
||||
@ -58,39 +56,29 @@ final class NavigationModalFrame: ASDisplayNode {
|
||||
func update(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
self.validLayout = layout
|
||||
|
||||
transition.updateFrame(node: self.dim, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
|
||||
self.updateShades(layout: layout, progress: 1.0 - self.progress, transition: transition)
|
||||
self.updateShades(layout: layout, progress: 1.0 - self.progress, additionalProgress: self.additionalProgress, transition: transition, completion: {})
|
||||
}
|
||||
|
||||
func animateIn(transition: ContainedViewLayoutTransition) {
|
||||
transition.updateAlpha(node: self.dim, alpha: 1.0)
|
||||
|
||||
if let layout = self.validLayout {
|
||||
self.updateShades(layout: layout, progress: 0.0, transition: .immediate)
|
||||
self.updateShades(layout: layout, progress: 1.0, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
func updateDismissal(transition: ContainedViewLayoutTransition, progress: CGFloat, completion: @escaping () -> Void) {
|
||||
func updateDismissal(transition: ContainedViewLayoutTransition, progress: CGFloat, additionalProgress: CGFloat, completion: @escaping () -> Void) {
|
||||
self.progress = progress
|
||||
self.additionalProgress = additionalProgress
|
||||
|
||||
transition.updateAlpha(node: self.dim, alpha: 1.0 - progress, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
if let layout = self.validLayout {
|
||||
self.updateShades(layout: layout, progress: 1.0 - progress, transition: transition)
|
||||
self.updateShades(layout: layout, progress: 1.0 - progress, additionalProgress: additionalProgress, transition: transition, completion: completion)
|
||||
} else {
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
private func updateShades(layout: ContainerViewLayout, progress: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
private func updateShades(layout: ContainerViewLayout, progress: CGFloat, additionalProgress: CGFloat, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
|
||||
let sideInset: CGFloat = 16.0
|
||||
var topInset: CGFloat = 0.0
|
||||
if let statusBarHeight = layout.statusBarHeight {
|
||||
topInset += statusBarHeight
|
||||
}
|
||||
let additionalTopInset: CGFloat = 10.0
|
||||
|
||||
let cornerRadius: CGFloat = 8.0
|
||||
let cornerRadius: CGFloat = 9.0
|
||||
let initialCornerRadius: CGFloat
|
||||
if !layout.safeInsets.top.isZero {
|
||||
initialCornerRadius = 40.0
|
||||
@ -103,11 +91,20 @@ final class NavigationModalFrame: ASDisplayNode {
|
||||
}
|
||||
|
||||
let cornerSize = progress * cornerRadius + (1.0 - progress) * initialCornerRadius
|
||||
transition.updateFrame(node: self.topLeftCorner, frame: CGRect(origin: CGPoint(x: progress * sideInset, y: progress * topInset), size: CGSize(width: cornerSize, height: cornerSize)))
|
||||
transition.updateFrame(node: self.topRightCorner, frame: CGRect(origin: CGPoint(x: layout.size.width - progress * sideInset - cornerSize, y: progress * topInset), size: CGSize(width: cornerSize, height: cornerSize)))
|
||||
let cornerSideOffset: CGFloat = progress * sideInset + additionalProgress * sideInset
|
||||
let cornerTopOffset: CGFloat = progress * topInset + additionalProgress * additionalTopInset
|
||||
transition.updateFrame(node: self.topLeftCorner, frame: CGRect(origin: CGPoint(x: cornerSideOffset, y: cornerTopOffset), size: CGSize(width: cornerSize, height: cornerSize)))
|
||||
transition.updateFrame(node: self.topRightCorner, frame: CGRect(origin: CGPoint(x: layout.size.width - cornerSideOffset - cornerSize, y: cornerTopOffset), size: CGSize(width: cornerSize, height: cornerSize)))
|
||||
|
||||
transition.updateFrame(node: self.topShade, frame: CGRect(origin: CGPoint(x: 0.0, y: (1.0 - progress) * (-topInset)), size: CGSize(width: layout.size.width, height: topInset)))
|
||||
transition.updateFrame(node: self.leftShade, frame: CGRect(origin: CGPoint(x: (1.0 - progress) * (-sideInset), y: 0.0), size: CGSize(width: sideInset, height: layout.size.height)))
|
||||
transition.updateFrame(node: self.rightShade, frame: CGRect(origin: CGPoint(x: layout.size.width - sideInset * progress, y: 0.0), size: CGSize(width: sideInset, height: layout.size.height)))
|
||||
let topShadeOffset: CGFloat = progress * topInset + additionalProgress * additionalTopInset
|
||||
let leftShadeOffset: CGFloat = progress * sideInset + additionalProgress * sideInset
|
||||
let rightShadeWidth: CGFloat = progress * sideInset + additionalProgress * sideInset
|
||||
let rightShadeOffset: CGFloat = layout.size.width - rightShadeWidth
|
||||
|
||||
transition.updateFrame(node: self.topShade, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: topShadeOffset)))
|
||||
transition.updateFrame(node: self.leftShade, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: leftShadeOffset, height: layout.size.height)))
|
||||
transition.updateFrame(node: self.rightShade, frame: CGRect(origin: CGPoint(x: rightShadeOffset, y: 0.0), size: CGSize(width: rightShadeWidth, height: layout.size.height)), completion: { _ in
|
||||
completion()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -10,12 +10,12 @@ public enum StatusBarStyle {
|
||||
|
||||
public init(systemStyle: UIStatusBarStyle) {
|
||||
switch systemStyle {
|
||||
case .default:
|
||||
self = .Black
|
||||
case .lightContent:
|
||||
self = .White
|
||||
case .blackOpaque:
|
||||
self = .Black
|
||||
case .default:
|
||||
self = .Black
|
||||
case .lightContent:
|
||||
self = .White
|
||||
case .blackOpaque:
|
||||
self = .Black
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,3 +13,4 @@ CGFloat springAnimationValueAt(CABasicAnimation * _Nonnull animation, CGFloat t)
|
||||
|
||||
void testZoomBlurEffect(UIVisualEffect *effect);
|
||||
UIBlurEffect *makeCustomZoomBlurEffect();
|
||||
void applySmoothRoundedCorners(CALayer * _Nonnull layer);
|
||||
|
@ -154,6 +154,20 @@ static void setNilField(CustomBlurEffect *object, NSString *name) {
|
||||
[inv invoke];
|
||||
}
|
||||
|
||||
static void setBoolField(CustomBlurEffect *object, NSString *name, BOOL value) {
|
||||
SEL selector = NSSelectorFromString(name);
|
||||
NSMethodSignature *signature = [[object class] instanceMethodSignatureForSelector:selector];
|
||||
if (signature == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:signature];
|
||||
[inv setSelector:selector];
|
||||
[inv setArgument:&value atIndex:2];
|
||||
[inv setTarget:object];
|
||||
[inv invoke];
|
||||
}
|
||||
|
||||
UIBlurEffect *makeCustomZoomBlurEffect() {
|
||||
if (@available(iOS 11.0, *)) {
|
||||
NSString *string = [@[@"_", @"UI", @"Custom", @"BlurEffect"] componentsJoinedByString:@""];
|
||||
@ -178,3 +192,9 @@ UIBlurEffect *makeCustomZoomBlurEffect() {
|
||||
return [UIBlurEffect effectWithStyle:UIBlurEffectStyleRegular];
|
||||
}
|
||||
}
|
||||
|
||||
void applySmoothRoundedCorners(CALayer * _Nonnull layer) {
|
||||
if (@available(iOS 11.0, *)) {
|
||||
setBoolField(layer, encodeText(@"tfuDpoujovpvtDpsofst;", -1), true);
|
||||
}
|
||||
}
|
||||
|
@ -362,7 +362,6 @@ public enum ViewControllerNavigationPresentation {
|
||||
if !self.isViewLoaded {
|
||||
self.loadView()
|
||||
}
|
||||
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))
|
||||
}
|
||||
|
@ -199,7 +199,6 @@ public func getFirstResponderAndAccessoryHeight(_ view: UIView, _ accessoryHeigh
|
||||
public final class WindowHostView {
|
||||
public let containerView: UIView
|
||||
public let eventView: UIView
|
||||
public let aboveStatusBarView: UIView
|
||||
public let isRotating: () -> Bool
|
||||
|
||||
let updateSupportedInterfaceOrientations: (UIInterfaceOrientationMask) -> Void
|
||||
@ -221,10 +220,9 @@ public final class WindowHostView {
|
||||
var forEachController: (((ContainableController) -> Void) -> Void)?
|
||||
var getAccessibilityElements: (() -> [Any]?)?
|
||||
|
||||
init(containerView: UIView, eventView: UIView, aboveStatusBarView: UIView, isRotating: @escaping () -> Bool, updateSupportedInterfaceOrientations: @escaping (UIInterfaceOrientationMask) -> Void, updateDeferScreenEdgeGestures: @escaping (UIRectEdge) -> Void, updatePreferNavigationUIHidden: @escaping (Bool) -> Void) {
|
||||
init(containerView: UIView, eventView: UIView, isRotating: @escaping () -> Bool, updateSupportedInterfaceOrientations: @escaping (UIInterfaceOrientationMask) -> Void, updateDeferScreenEdgeGestures: @escaping (UIRectEdge) -> Void, updatePreferNavigationUIHidden: @escaping (Bool) -> Void) {
|
||||
self.containerView = containerView
|
||||
self.eventView = eventView
|
||||
self.aboveStatusBarView = aboveStatusBarView
|
||||
self.isRotating = isRotating
|
||||
self.updateSupportedInterfaceOrientations = updateSupportedInterfaceOrientations
|
||||
self.updateDeferScreenEdgeGestures = updateDeferScreenEdgeGestures
|
||||
@ -359,7 +357,7 @@ public class Window1 {
|
||||
self.windowLayout = WindowLayout(size: boundsSize, metrics: layoutMetricsForScreenSize(boundsSize), statusBarHeight: statusBarHeight, forceInCallStatusBarText: self.forceInCallStatusBarText, inputHeight: 0.0, safeInsets: safeInsets, onScreenNavigationHeight: onScreenNavigationHeight, upperKeyboardInputPositionBound: nil, inVoiceOver: UIAccessibility.isVoiceOverRunning)
|
||||
self.updatingLayout = UpdatingLayout(layout: self.windowLayout, transition: .immediate)
|
||||
self.presentationContext = PresentationContext()
|
||||
self.overlayPresentationContext = GlobalOverlayPresentationContext(statusBarHost: statusBarHost, parentView: self.hostView.aboveStatusBarView)
|
||||
self.overlayPresentationContext = GlobalOverlayPresentationContext(statusBarHost: statusBarHost, parentView: self.hostView.containerView)
|
||||
|
||||
self.presentationContext.updateIsInteractionBlocked = { [weak self] value in
|
||||
self?.isInteractionBlocked = value
|
||||
@ -697,6 +695,7 @@ public class Window1 {
|
||||
rootController.keyboardManager = self.keyboardManager
|
||||
}
|
||||
if !self.windowLayout.size.width.isZero && !self.windowLayout.size.height.isZero {
|
||||
rootController.displayNode.frame = CGRect(origin: CGPoint(), size: self.windowLayout.size)
|
||||
rootController.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout, deviceMetrics: self.deviceMetrics), transition: .immediate)
|
||||
}
|
||||
|
||||
@ -720,6 +719,7 @@ public class Window1 {
|
||||
|
||||
let layout = containedLayoutForWindowLayout(self.windowLayout, deviceMetrics: self.deviceMetrics)
|
||||
for controller in self._topLevelOverlayControllers {
|
||||
controller.displayNode.frame = CGRect(origin: CGPoint(), size: self.windowLayout.size)
|
||||
controller.containerLayoutUpdated(layout, transition: .immediate)
|
||||
|
||||
if let coveringView = self.coveringView {
|
||||
@ -977,11 +977,15 @@ public class Window1 {
|
||||
if self.presentationContext.isCurrentlyOpaque {
|
||||
rootLayout.inputHeight = nil
|
||||
}
|
||||
self._rootController?.containerLayoutUpdated(rootLayout, transition: rootTransition)
|
||||
if let rootController = self._rootController {
|
||||
rootTransition.updateFrame(node: rootController.displayNode, frame: CGRect(origin: CGPoint(), size: self.windowLayout.size))
|
||||
rootController.containerLayoutUpdated(rootLayout, transition: rootTransition)
|
||||
}
|
||||
self.presentationContext.containerLayoutUpdated(childLayout, transition: updatingLayout.transition)
|
||||
self.overlayPresentationContext.containerLayoutUpdated(childLayout, transition: updatingLayout.transition)
|
||||
|
||||
for controller in self.topLevelOverlayControllers {
|
||||
updatingLayout.transition.updateFrame(node: controller.displayNode, frame: CGRect(origin: CGPoint(), size: self.windowLayout.size))
|
||||
controller.containerLayoutUpdated(childLayout, transition: updatingLayout.transition)
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ static_library(
|
||||
"//submodules/SelectablePeerNode:SelectablePeerNode",
|
||||
"//submodules/PeerOnlineMarkerNode:PeerOnlineMarkerNode",
|
||||
"//submodules/TelegramStringFormatting:TelegramStringFormatting",
|
||||
"//submodules/ContextUI:ContextUI",
|
||||
],
|
||||
frameworks = [
|
||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||
|
@ -9,6 +9,7 @@ import TelegramPresentationData
|
||||
import TelegramStringFormatting
|
||||
import PeerOnlineMarkerNode
|
||||
import SelectablePeerNode
|
||||
import ContextUI
|
||||
|
||||
public enum HorizontalPeerItemMode {
|
||||
case list
|
||||
@ -24,20 +25,20 @@ public final class HorizontalPeerItem: ListViewItem {
|
||||
let account: Account
|
||||
public let peer: Peer
|
||||
let action: (Peer) -> Void
|
||||
let longTapAction: (Peer) -> Void
|
||||
let contextAction: (Peer, ASDisplayNode, ContextGesture?) -> Void
|
||||
let isPeerSelected: (PeerId) -> Bool
|
||||
let customWidth: CGFloat?
|
||||
let presence: PeerPresence?
|
||||
let unreadBadge: (Int32, Bool)?
|
||||
|
||||
public init(theme: PresentationTheme, strings: PresentationStrings, mode: HorizontalPeerItemMode, account: Account, peer: Peer, presence: PeerPresence?, unreadBadge: (Int32, Bool)?, action: @escaping (Peer) -> Void, longTapAction: @escaping (Peer) -> Void, isPeerSelected: @escaping (PeerId) -> Bool, customWidth: CGFloat?) {
|
||||
public init(theme: PresentationTheme, strings: PresentationStrings, mode: HorizontalPeerItemMode, account: Account, peer: Peer, presence: PeerPresence?, unreadBadge: (Int32, Bool)?, action: @escaping (Peer) -> Void, contextAction: @escaping (Peer, ASDisplayNode, ContextGesture?) -> Void, isPeerSelected: @escaping (PeerId) -> Bool, customWidth: CGFloat?) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.mode = mode
|
||||
self.account = account
|
||||
self.peer = peer
|
||||
self.action = action
|
||||
self.longTapAction = longTapAction
|
||||
self.contextAction = contextAction
|
||||
self.isPeerSelected = isPeerSelected
|
||||
self.customWidth = customWidth
|
||||
self.presence = presence
|
||||
@ -110,9 +111,9 @@ public final class HorizontalPeerItemNode: ListViewItemNode {
|
||||
item.action(item.peer)
|
||||
}
|
||||
}
|
||||
self.peerNode.longTapAction = { [weak self] in
|
||||
self.peerNode.contextAction = { [weak self] node, gesture in
|
||||
if let item = self?.item {
|
||||
item.longTapAction(item.peer)
|
||||
item.contextAction(item.peer, node, gesture)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,8 @@ public final class InstantPageController: ViewController {
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
|
||||
self.navigationPresentation = .modalInLargeLayout
|
||||
|
||||
self.statusBar.statusBarStyle = .White
|
||||
|
||||
self.webpageDisposable = (actualizedWebpage(postbox: self.context.account.postbox, network: self.context.account.network, webpage: webPage) |> deliverOnMainQueue).start(next: { [weak self] result in
|
||||
|
@ -1219,7 +1219,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
|
||||
if let map = media.media as? TelegramMediaMap {
|
||||
let controller = legacyLocationController(message: nil, mapMedia: map, context: self.context, isModal: false, openPeer: { _ in }, sendLiveLocation: { _, _ in }, stopLiveLocation: { }, openUrl: { _ in })
|
||||
let controller = legacyLocationController(message: nil, mapMedia: map, context: self.context, openPeer: { _ in }, sendLiveLocation: { _, _ in }, stopLiveLocation: { }, openUrl: { _ in })
|
||||
self.pushController(controller)
|
||||
return
|
||||
}
|
||||
|
@ -127,7 +127,7 @@ func legacyLocationPalette(from theme: PresentationTheme) -> TGLocationPallete {
|
||||
return TGLocationPallete(backgroundColor: listTheme.plainBackgroundColor, selectionColor: listTheme.itemHighlightedBackgroundColor, separatorColor: listTheme.itemPlainSeparatorColor, textColor: listTheme.itemPrimaryTextColor, secondaryTextColor: listTheme.itemSecondaryTextColor, accentColor: listTheme.itemAccentColor, destructiveColor: listTheme.itemDestructiveColor, locationColor: UIColor(rgb: 0x008df2), liveLocationColor: UIColor(rgb: 0xff6464), iconColor: searchTheme.backgroundColor, sectionHeaderBackgroundColor: theme.chatList.sectionHeaderFillColor, sectionHeaderTextColor: theme.chatList.sectionHeaderTextColor, searchBarPallete: TGSearchBarPallete(dark: theme.overallDarkAppearance, backgroundColor: searchTheme.backgroundColor, highContrastBackgroundColor: searchTheme.backgroundColor, textColor: searchTheme.inputTextColor, placeholderColor: searchTheme.inputPlaceholderTextColor, clearIcon: generateClearIcon(color: theme.rootController.navigationSearchBar.inputClearButtonColor), barBackgroundColor: searchTheme.backgroundColor, barSeparatorColor: searchTheme.separatorColor, plainBackgroundColor: searchTheme.backgroundColor, accentColor: searchTheme.accentColor, accentContrastColor: searchTheme.backgroundColor, menuBackgroundColor: searchTheme.backgroundColor, segmentedControlBackgroundImage: nil, segmentedControlSelectedImage: nil, segmentedControlHighlightedImage: nil, segmentedControlDividerImage: nil), avatarPlaceholder: nil)
|
||||
}
|
||||
|
||||
public func legacyLocationController(message: Message?, mapMedia: TelegramMediaMap, context: AccountContext, isModal: Bool, openPeer: @escaping (Peer) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D, Int32) -> Void, stopLiveLocation: @escaping () -> Void, openUrl: @escaping (String) -> Void) -> ViewController {
|
||||
public func legacyLocationController(message: Message?, mapMedia: TelegramMediaMap, context: AccountContext, openPeer: @escaping (Peer) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D, Int32) -> Void, stopLiveLocation: @escaping () -> Void, openUrl: @escaping (String) -> Void) -> ViewController {
|
||||
let legacyLocation = TGLocationMediaAttachment()
|
||||
legacyLocation.latitude = mapMedia.latitude
|
||||
legacyLocation.longitude = mapMedia.longitude
|
||||
@ -137,7 +137,8 @@ public func legacyLocationController(message: Message?, mapMedia: TelegramMediaM
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let legacyController = LegacyController(presentation: isModal ? .modal(animateIn: true) : .navigation, theme: presentationData.theme, strings: presentationData.strings)
|
||||
let legacyController = LegacyController(presentation: .navigation, theme: presentationData.theme, strings: presentationData.strings)
|
||||
legacyController.navigationPresentation = .modal
|
||||
let controller: TGLocationViewController
|
||||
|
||||
if let message = message {
|
||||
@ -234,7 +235,7 @@ public func legacyLocationController(message: Message?, mapMedia: TelegramMediaM
|
||||
let theme = (context.sharedContext.currentPresentationData.with { $0 }).theme
|
||||
controller.pallete = legacyLocationPalette(from: theme)
|
||||
|
||||
controller.modalMode = isModal
|
||||
controller.modalMode = true
|
||||
controller.presentActionsMenu = { [weak legacyController] legacyLocation, directions in
|
||||
if let strongLegacyController = legacyController, let location = legacyLocation {
|
||||
let map = telegramMap(for: location)
|
||||
@ -256,23 +257,15 @@ public func legacyLocationController(message: Message?, mapMedia: TelegramMediaM
|
||||
}
|
||||
}
|
||||
|
||||
if isModal {
|
||||
let navigationController = TGNavigationController(controllers: [controller])!
|
||||
legacyController.bind(controller: navigationController)
|
||||
controller.navigation_setDismiss({ [weak legacyController] in
|
||||
legacyController?.dismiss()
|
||||
}, rootController: nil)
|
||||
} else {
|
||||
legacyController.navigationItem.title = controller.navigationItem.title
|
||||
controller.updateRightBarItem = { item, action, animated in
|
||||
if action {
|
||||
legacyController.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationShareIcon(theme), style: .plain, target: controller, action: #selector(controller.actionsButtonPressed))
|
||||
} else {
|
||||
legacyController.navigationItem.setRightBarButton(item, animated: animated)
|
||||
}
|
||||
legacyController.navigationItem.title = controller.navigationItem.title
|
||||
controller.updateRightBarItem = { item, action, animated in
|
||||
if action {
|
||||
legacyController.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationShareIcon(theme), style: .plain, target: controller, action: #selector(controller.actionsButtonPressed))
|
||||
} else {
|
||||
legacyController.navigationItem.setRightBarButton(item, animated: animated)
|
||||
}
|
||||
legacyController.bind(controller: controller)
|
||||
}
|
||||
legacyController.bind(controller: controller)
|
||||
|
||||
let presentationDisposable = context.sharedContext.presentationData.start(next: { [weak controller] presentationData in
|
||||
if let controller = controller {
|
||||
|
@ -2028,7 +2028,7 @@ public func groupInfoController(context: AccountContext, peerId originalPeerId:
|
||||
return
|
||||
}
|
||||
let mapMedia = TelegramMediaMap(latitude: location.latitude, longitude: location.longitude, geoPlace: nil, venue: MapVenue(title: peer.displayTitle, address: location.address, provider: nil, id: nil, type: nil), liveBroadcastingTimeout: nil)
|
||||
let controller = legacyLocationController(message: nil, mapMedia: mapMedia, context: context, isModal: false, openPeer: { _ in }, sendLiveLocation: { _, _ in }, stopLiveLocation: {}, openUrl: { url in
|
||||
let controller = legacyLocationController(message: nil, mapMedia: mapMedia, context: context, openPeer: { _ in }, sendLiveLocation: { _, _ in }, stopLiveLocation: {}, openUrl: { url in
|
||||
context.sharedContext.applicationBindings.openUrl(url)
|
||||
})
|
||||
pushControllerImpl?(controller)
|
||||
|
@ -15,6 +15,7 @@ static_library(
|
||||
"//submodules/PeerOnlineMarkerNode:PeerOnlineMarkerNode",
|
||||
"//submodules/AvatarNode:AvatarNode",
|
||||
"//submodules/LegacyComponents:LegacyComponents",
|
||||
"//submodules/ContextUI:ContextUI",
|
||||
],
|
||||
frameworks = [
|
||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||
|
@ -9,6 +9,7 @@ import TelegramPresentationData
|
||||
import AvatarNode
|
||||
import PeerOnlineMarkerNode
|
||||
import LegacyComponents
|
||||
import ContextUI
|
||||
|
||||
private let avatarFont = UIFont(name: ".SFCompactRounded-Semibold", size: 24.0)!
|
||||
private let textFont = Font.regular(11.0)
|
||||
@ -62,6 +63,7 @@ public final class SelectablePeerNodeTheme {
|
||||
}
|
||||
|
||||
public final class SelectablePeerNode: ASDisplayNode {
|
||||
private let contextContainer: ContextControllerSourceNode
|
||||
private let avatarSelectionNode: ASImageNode
|
||||
private let avatarNodeContainer: ASDisplayNode
|
||||
private let avatarNode: AvatarNode
|
||||
@ -70,7 +72,11 @@ public final class SelectablePeerNode: ASDisplayNode {
|
||||
private let textNode: ASTextNode
|
||||
|
||||
public var toggleSelection: (() -> Void)?
|
||||
public var longTapAction: (() -> Void)?
|
||||
public var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? {
|
||||
didSet {
|
||||
self.contextContainer.isGestureEnabled = self.contextAction != nil
|
||||
}
|
||||
}
|
||||
|
||||
private var currentSelected = false
|
||||
|
||||
@ -87,6 +93,9 @@ public final class SelectablePeerNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
override public init() {
|
||||
self.contextContainer = ContextControllerSourceNode()
|
||||
self.contextContainer.isGestureEnabled = false
|
||||
|
||||
self.avatarNodeContainer = ASDisplayNode()
|
||||
|
||||
self.avatarSelectionNode = ASImageNode()
|
||||
@ -108,11 +117,20 @@ public final class SelectablePeerNode: ASDisplayNode {
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.contextContainer)
|
||||
self.avatarNodeContainer.addSubnode(self.avatarSelectionNode)
|
||||
self.avatarNodeContainer.addSubnode(self.avatarNode)
|
||||
self.addSubnode(self.avatarNodeContainer)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.onlineNode)
|
||||
self.contextContainer.addSubnode(self.avatarNodeContainer)
|
||||
self.contextContainer.addSubnode(self.textNode)
|
||||
self.contextContainer.addSubnode(self.onlineNode)
|
||||
|
||||
self.contextContainer.activated = { [weak self] gesture in
|
||||
guard let strongSelf = self, let contextAction = strongSelf.contextAction else {
|
||||
gesture.cancel()
|
||||
return
|
||||
}
|
||||
contextAction(strongSelf.contextContainer, gesture)
|
||||
}
|
||||
}
|
||||
|
||||
public func setup(account: Account, theme: PresentationTheme, strings: PresentationStrings, peer: RenderedPeer, online: Bool = false, numberOfLines: Int = 2, synchronousLoad: Bool) {
|
||||
@ -210,16 +228,6 @@ public final class SelectablePeerNode: ASDisplayNode {
|
||||
super.didLoad()
|
||||
|
||||
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
|
||||
let longTapRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.longTapGesture(_:)))
|
||||
longTapRecognizer.minimumPressDuration = 0.3
|
||||
self.view.addGestureRecognizer(longTapRecognizer)
|
||||
}
|
||||
|
||||
@objc private func longTapGesture(_ recognizer: UILongPressGestureRecognizer) {
|
||||
if case .began = recognizer.state {
|
||||
self.longTapAction?()
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
@ -233,6 +241,8 @@ public final class SelectablePeerNode: ASDisplayNode {
|
||||
|
||||
let bounds = self.bounds
|
||||
|
||||
self.contextContainer.frame = bounds
|
||||
|
||||
self.avatarNodeContainer.frame = CGRect(origin: CGPoint(x: floor((bounds.size.width - 60.0) / 2.0), y: 4.0), size: CGSize(width: 60.0, height: 60.0))
|
||||
self.textNode.frame = CGRect(origin: CGPoint(x: 2.0, y: 4.0 + 60.0 + 4.0), size: CGSize(width: bounds.size.width - 4.0, height: 34.0))
|
||||
|
||||
|
@ -61,7 +61,7 @@ final class ShareControllerRecentPeersGridItemNode: GridItemNode {
|
||||
} else {
|
||||
peersNode = ChatListSearchRecentPeersNode(account: account, theme: theme, mode: .actionSheet, strings: strings, peerSelected: { [weak self] peer in
|
||||
self?.controllerInteraction?.togglePeer(RenderedPeer(peer: peer), true)
|
||||
}, peerLongTapped: {_ in }, isPeerSelected: { [weak self] peerId in
|
||||
}, peerContextAction: { _, _, gesture in gesture?.cancel() }, isPeerSelected: { [weak self] peerId in
|
||||
return self?.controllerInteraction?.selectedPeerIds.contains(peerId) ?? false
|
||||
}, share: true)
|
||||
self.peersNode = peersNode
|
||||
|
@ -69,7 +69,7 @@ private class ApplicationStatusBarHost: StatusBarHost {
|
||||
}
|
||||
|
||||
var statusBarWindow: UIView? {
|
||||
return self.application.value(forKey: "statusBarWindow") as? UIView
|
||||
return nil//self.application.value(forKey: "statusBarWindow") as? UIView
|
||||
}
|
||||
|
||||
var statusBarView: UIView? {
|
||||
@ -165,7 +165,6 @@ final class SharedApplicationContext {
|
||||
@objc var window: UIWindow?
|
||||
var nativeWindow: (UIWindow & WindowHost)?
|
||||
var mainWindow: Window1!
|
||||
var aboveStatusbarWindow: UIWindow?
|
||||
private var dataImportSplash: LegacyDataImportSplash?
|
||||
|
||||
let episodeId = arc4random()
|
||||
@ -240,9 +239,8 @@ final class SharedApplicationContext {
|
||||
|
||||
|
||||
let statusBarHost = ApplicationStatusBarHost()
|
||||
let (window, hostView, aboveStatusbarWindow) = nativeWindowHostView()
|
||||
let (window, hostView) = nativeWindowHostView()
|
||||
self.mainWindow = Window1(hostView: hostView, statusBarHost: statusBarHost)
|
||||
self.aboveStatusbarWindow = aboveStatusbarWindow
|
||||
hostView.containerView.backgroundColor = UIColor.white
|
||||
self.window = window
|
||||
self.nativeWindow = window
|
||||
@ -448,7 +446,6 @@ final class SharedApplicationContext {
|
||||
#endif
|
||||
#endif
|
||||
|
||||
self.aboveStatusbarWindow?.isHidden = false
|
||||
self.window?.makeKeyAndVisible()
|
||||
|
||||
self.hasActiveAudioSession.set(MediaManagerImpl.globalAudioSession.isActive())
|
||||
@ -1093,7 +1090,9 @@ final class SharedApplicationContext {
|
||||
})
|
||||
|
||||
let pushRegistry = PKPushRegistry(queue: .main)
|
||||
pushRegistry.desiredPushTypes = Set([.voIP])
|
||||
if #available(iOS 9.0, *) {
|
||||
pushRegistry.desiredPushTypes = Set([.voIP])
|
||||
}
|
||||
self.pushRegistry = pushRegistry
|
||||
pushRegistry.delegate = self
|
||||
|
||||
@ -1341,24 +1340,28 @@ final class SharedApplicationContext {
|
||||
}
|
||||
|
||||
public func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {
|
||||
if case PKPushType.voIP = type {
|
||||
Logger.shared.log("App \(self.episodeId)", "pushRegistry credentials: \(credentials.token as NSData)")
|
||||
|
||||
self.voipTokenPromise.set(.single(credentials.token))
|
||||
if #available(iOS 9.0, *) {
|
||||
if case PKPushType.voIP = type {
|
||||
Logger.shared.log("App \(self.episodeId)", "pushRegistry credentials: \(credentials.token as NSData)")
|
||||
|
||||
self.voipTokenPromise.set(.single(credentials.token))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) {
|
||||
let _ = (self.sharedContextPromise.get()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { sharedApplicationContext in
|
||||
sharedApplicationContext.wakeupManager.allowBackgroundTimeExtension(timeout: 4.0)
|
||||
|
||||
if case PKPushType.voIP = type {
|
||||
Logger.shared.log("App \(self.episodeId)", "pushRegistry payload: \(payload.dictionaryPayload)")
|
||||
sharedApplicationContext.notificationManager.addNotification(payload.dictionaryPayload)
|
||||
}
|
||||
})
|
||||
if #available(iOS 9.0, *) {
|
||||
let _ = (self.sharedContextPromise.get()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { sharedApplicationContext in
|
||||
sharedApplicationContext.wakeupManager.allowBackgroundTimeExtension(timeout: 4.0)
|
||||
|
||||
if case PKPushType.voIP = type {
|
||||
Logger.shared.log("App \(self.episodeId)", "pushRegistry payload: \(payload.dictionaryPayload)")
|
||||
sharedApplicationContext.notificationManager.addNotification(payload.dictionaryPayload)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
|
||||
|
@ -253,7 +253,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
|
||||
case let .map(mapMedia):
|
||||
params.dismissInput()
|
||||
|
||||
let controller = legacyLocationController(message: params.message, mapMedia: mapMedia, context: params.context, isModal: params.modal, openPeer: { peer in
|
||||
let controller = legacyLocationController(message: params.message, mapMedia: mapMedia, context: params.context, openPeer: { peer in
|
||||
params.openPeer(peer, .info)
|
||||
}, sendLiveLocation: { coordinate, period in
|
||||
let outMessage: EnqueueMessage = .message(text: "", attributes: [], mediaReference: .standalone(media: TelegramMediaMap(latitude: coordinate.latitude, longitude: coordinate.longitude, geoPlace: nil, venue: nil, liveBroadcastingTimeout: period)), replyToMessageId: nil, localGroupingKey: nil)
|
||||
@ -426,7 +426,6 @@ func openChatInstantPage(context: AccountContext, message: Message, sourcePeerTy
|
||||
}
|
||||
|
||||
let pageController = InstantPageController(context: context, webPage: webpage, sourcePeerType: sourcePeerType ?? .channel, anchor: anchor)
|
||||
pageController.navigationPresentation = .modal
|
||||
navigationController.pushViewController(pageController)
|
||||
}
|
||||
break
|
||||
|
Loading…
x
Reference in New Issue
Block a user