Modal UI improvements

This commit is contained in:
Peter 2019-09-16 13:21:18 +04:00
parent f8b434f341
commit 6c544eb9c2
29 changed files with 396 additions and 232 deletions

View File

@ -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",

View File

@ -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)
}

View File

@ -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)

View File

@ -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
})

View File

@ -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()

View File

@ -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

View File

@ -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)

View File

@ -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?

View File

@ -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)
}

View File

@ -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)

View File

@ -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

View File

@ -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
}
}

View File

@ -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()
})
}
}

View File

@ -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
}
}

View File

@ -13,3 +13,4 @@ CGFloat springAnimationValueAt(CABasicAnimation * _Nonnull animation, CGFloat t)
void testZoomBlurEffect(UIVisualEffect *effect);
UIBlurEffect *makeCustomZoomBlurEffect();
void applySmoothRoundedCorners(CALayer * _Nonnull layer);

View File

@ -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);
}
}

View File

@ -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))
}

View File

@ -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)
}
}

View File

@ -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",

View File

@ -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)
}
}
}

View File

@ -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

View File

@ -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
}

View File

@ -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 {

View File

@ -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)

View File

@ -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",

View File

@ -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))

View File

@ -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

View File

@ -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) {

View File

@ -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