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/HorizontalPeerItem:HorizontalPeerItem",
"//submodules/MergeLists:MergeLists", "//submodules/MergeLists:MergeLists",
"//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/ContextUI:ContextUI",
], ],
frameworks = [ frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework", "$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@ -9,6 +9,7 @@ import TelegramPresentationData
import MergeLists import MergeLists
import HorizontalPeerItem import HorizontalPeerItem
import ListSectionHeaderNode import ListSectionHeaderNode
import ContextUI
private func calculateItemCustomWidth(width: CGFloat) -> CGFloat { private func calculateItemCustomWidth(width: CGFloat) -> CGFloat {
let itemInsets = UIEdgeInsets(top: 0.0, left: 6.0, bottom: 0.0, right: 6.0) 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 return lhs.index < rhs.index
} }
func item(account: Account, mode: HorizontalPeerItemMode, peerSelected: @escaping (Peer) -> Void, peerLongTapped: @escaping (Peer) -> Void, isPeerSelected: @escaping (PeerId) -> Bool) -> ListViewItem { 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, longTapAction: { peer in 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
peerLongTapped(peer) peerContextAction(peer, node, gesture)
}, isPeerSelected: isPeerSelected, customWidth: self.itemCustomWidth) }, isPeerSelected: isPeerSelected, customWidth: self.itemCustomWidth)
} }
} }
@ -91,16 +92,12 @@ private struct ChatListSearchRecentNodeTransition {
let animated: Bool 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 (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } 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 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) }
peerLongTapped(peer) 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) }
}, 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) }
return ChatListSearchRecentNodeTransition(deletions: deletions, insertions: insertions, updates: updates, firstTime: firstTime, animated: animated) 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 share: Bool
private let peerSelected: (Peer) -> Void private let peerSelected: (Peer) -> Void
private let peerLongTapped: (Peer) -> Void private let peerContextAction: (Peer, ASDisplayNode, ContextGesture?) -> Void
private let isPeerSelected: (PeerId) -> Bool private let isPeerSelected: (PeerId) -> Bool
private let disposable = MetaDisposable() private let disposable = MetaDisposable()
@ -124,14 +121,14 @@ public final class ChatListSearchRecentPeersNode: ASDisplayNode {
private var items: [ListViewItem] = [] private var items: [ListViewItem] = []
private var queuedTransitions: [ChatListSearchRecentNodeTransition] = [] 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.theme = theme
self.strings = strings self.strings = strings
self.themeAndStringsPromise = Promise((self.theme, self.strings)) self.themeAndStringsPromise = Promise((self.theme, self.strings))
self.mode = mode self.mode = mode
self.share = share self.share = share
self.peerSelected = peerSelected self.peerSelected = peerSelected
self.peerLongTapped = peerLongTapped self.peerContextAction = peerContextAction
self.isPeerSelected = isPeerSelected self.isPeerSelected = isPeerSelected
self.sectionHeaderNode = ListSectionHeaderNode(theme: theme) self.sectionHeaderNode = ListSectionHeaderNode(theme: theme)
@ -210,7 +207,7 @@ public final class ChatListSearchRecentPeersNode: ASDisplayNode {
let animated = !firstTime.swap(false) 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) strongSelf.enqueueTransition(transition)
} }

View File

@ -50,7 +50,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, source: ChatC
if case let .search(search) = source { if case let .search(search) = source {
switch search { switch search {
case .recentPeers: 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) let _ = (removeRecentPeer(account: context.account, peerId: peerId)
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
f(.default) f(.default)

View File

@ -7,6 +7,7 @@ import SwiftSignalKit
import TelegramCore import TelegramCore
import TelegramPresentationData import TelegramPresentationData
import ChatListSearchRecentPeersNode import ChatListSearchRecentPeersNode
import ContextUI
class ChatListRecentPeersListItem: ListViewItem { class ChatListRecentPeersListItem: ListViewItem {
let theme: PresentationTheme let theme: PresentationTheme
@ -14,17 +15,17 @@ class ChatListRecentPeersListItem: ListViewItem {
let account: Account let account: Account
let peers: [Peer] let peers: [Peer]
let peerSelected: (Peer) -> Void let peerSelected: (Peer) -> Void
let peerLongTapped: (Peer) -> Void let peerContextAction: (Peer, ASDisplayNode, ContextGesture?) -> Void
let header: ListViewItemHeader? 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.theme = theme
self.strings = strings self.strings = strings
self.account = account self.account = account
self.peers = peers self.peers = peers
self.peerSelected = peerSelected self.peerSelected = peerSelected
self.peerLongTapped = peerLongTapped self.peerContextAction = peerContextAction
self.header = nil self.header = nil
} }
@ -115,8 +116,8 @@ class ChatListRecentPeersListItemNode: ListViewItemNode {
} else { } else {
peersNode = ChatListSearchRecentPeersNode(account: item.account, theme: item.theme, mode: .list, strings: item.strings, peerSelected: { peer in peersNode = ChatListSearchRecentPeersNode(account: item.account, theme: item.theme, mode: .list, strings: item.strings, peerSelected: { peer in
self?.item?.peerSelected(peer) self?.item?.peerSelected(peer)
}, peerLongTapped: { peer in }, peerContextAction: { peer, node, gesture in
self?.item?.peerLongTapped(peer) self?.item?.peerContextAction(peer, node, gesture)
}, isPeerSelected: { _ in }, isPeerSelected: { _ in
return false 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 { switch self {
case let .topPeers(peers, theme, strings): case let .topPeers(peers, theme, strings):
return ChatListRecentPeersListItem(theme: theme, strings: strings, account: context.account, peers: peers, peerSelected: { peer in return ChatListRecentPeersListItem(theme: theme, strings: strings, account: context.account, peers: peers, peerSelected: { peer in
peerSelected(peer) peerSelected(peer)
}, peerLongTapped: { peer in }, peerContextAction: { peer, node, gesture in
peerLongTapped(peer) if let peerContextAction = peerContextAction {
peerContextAction(peer, .recentPeers, node, gesture)
} else {
gesture?.cancel()
}
}) })
case let .peer(_, peer, theme, strings, timeFormat, nameSortOrder, nameDisplayOrder, hasRevealControls): case let .peer(_, peer, theme, strings, timeFormat, nameSortOrder, nameDisplayOrder, hasRevealControls):
let primaryPeer: Peer 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 (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } 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 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, 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, peerContextAction: peerContextAction, clearRecentlySearchedPeers: clearRecentlySearchedPeers, setPeerIdWithRevealedOptions: setPeerIdWithRevealedOptions, deletePeer: deletePeer), directionHint: nil) }
return ChatListSearchContainerRecentTransition(deletions: deletions, insertions: insertions, updates: updates) return ChatListSearchContainerRecentTransition(deletions: deletions, insertions: insertions, updates: updates)
} }
@ -969,8 +973,6 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
openPeer(peer, true) openPeer(peer, true)
let _ = addRecentlySearchedPeer(postbox: context.account.postbox, peerId: peer.id).start() let _ = addRecentlySearchedPeer(postbox: context.account.postbox, peerId: peer.id).start()
self?.recentListNode.clearHighlightAnimated(true) self?.recentListNode.clearHighlightAnimated(true)
}, peerLongTapped: { peer in
openRecentPeerOptions(peer)
}, peerContextAction: peerContextAction, }, peerContextAction: peerContextAction,
clearRecentlySearchedPeers: { clearRecentlySearchedPeers: {
self?.clearRecentSearch() self?.clearRecentSearch()

View File

@ -63,7 +63,7 @@ public func childWindowHostView(parent: UIView) -> WindowHostView {
let view = ChildWindowHostView() let view = ChildWindowHostView()
view.autoresizingMask = [.flexibleWidth, .flexibleHeight] view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
let hostView = WindowHostView(containerView: view, eventView: view, aboveStatusBarView: view, isRotating: { let hostView = WindowHostView(containerView: view, eventView: view, isRotating: {
return false return false
}, updateSupportedInterfaceOrientations: { orientations in }, updateSupportedInterfaceOrientations: { orientations in
}, updateDeferScreenEdgeGestures: { edges 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) { func updateFrameAdditive(node: ASDisplayNode, frame: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) {
if node.frame.equalTo(frame) && !force { if node.frame.equalTo(frame) && !force {
completion?(true) completion?(true)

View File

@ -125,7 +125,7 @@ public enum GeneralScrollDirection {
} }
open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGestureRecognizerDelegate { open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGestureRecognizerDelegate {
private final let scroller: ListViewScroller final let scroller: ListViewScroller
private final var visibleSize: CGSize = CGSize() private final var visibleSize: CGSize = CGSize()
public private(set) final var insets = UIEdgeInsets() public private(set) final var insets = UIEdgeInsets()
public final var visualInsets: 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 window = NativeWindow(frame: UIScreen.main.bounds)
let rootViewController = WindowRootViewController() 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.view.frame = CGRect(origin: CGPoint(), size: window.bounds.size)
rootViewController.viewDidAppear(false) rootViewController.viewDidAppear(false)
let aboveStatusbarWindow = AboveStatusBarWindow(frame: UIScreen.main.bounds) let hostView = WindowHostView(containerView: rootViewController.view, eventView: window, isRotating: {
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: {
return window.isRotating() return window.isRotating()
}, updateSupportedInterfaceOrientations: { orientations in }, updateSupportedInterfaceOrientations: { orientations in
rootViewController.orientations = orientations rootViewController.orientations = orientations
@ -433,5 +424,5 @@ public func nativeWindowHostView() -> (UIWindow & WindowHost, WindowHostView, UI
} }
} }
return (window, hostView, aboveStatusbarWindow) return (window, hostView)
} }

View File

@ -217,6 +217,21 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
} }
func update(layout: ContainerViewLayout, canBeClosed: Bool, controllers: [ViewController], transition: ContainedViewLayoutTransition) { func update(layout: ContainerViewLayout, canBeClosed: Bool, controllers: [ViewController], transition: ContainedViewLayoutTransition) {
self.state.layout = layout
self.state.canBeClosed = canBeClosed
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 let previousControllers = self.controllers
self.controllers = controllers self.controllers = controllers
@ -232,9 +247,6 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
} }
} }
self.state.layout = layout
self.state.canBeClosed = canBeClosed
if controllers.last !== self.state.top?.value { if controllers.last !== self.state.top?.value {
if controllers.last !== self.state.pending?.value.value { if controllers.last !== self.state.pending?.value.value {
self.state.pending = nil self.state.pending = nil
@ -251,6 +263,7 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
} }
} }
} }
}
if let pending = self.state.pending { if let pending = self.state.pending {
if pending.isReady { if pending.isReady {
@ -357,6 +370,10 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
} }
private func applyLayout(layout: ContainerViewLayout, to child: Child, transition: ContainedViewLayoutTransition) { 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 { if child.layout != layout {
child.layout = layout child.layout = layout
child.value.containerLayoutUpdated(layout, transition: transition) child.value.containerLayoutUpdated(layout, transition: transition)

View File

@ -232,7 +232,6 @@ open class NavigationController: UINavigationController, ContainableController,
self.loadView() self.loadView()
} }
self.validLayout = layout self.validLayout = layout
transition.updateFrame(view: self.view, frame: CGRect(origin: self.view.frame.origin, size: layout.size))
self.updateContainers(layout: layout, transition: transition) self.updateContainers(layout: layout, transition: transition)
} }
@ -254,10 +253,8 @@ open class NavigationController: UINavigationController, ContainableController,
} }
let modalContainer: NavigationModalContainer let modalContainer: NavigationModalContainer
let containerTransition: ContainedViewLayoutTransition
if let existingModalContainer = existingModalContainer { if let existingModalContainer = existingModalContainer {
modalContainer = existingModalContainer modalContainer = existingModalContainer
containerTransition = transition
} else { } else {
modalContainer = NavigationModalContainer(theme: self.theme, controllerRemoved: { [weak self] controller in modalContainer = NavigationModalContainer(theme: self.theme, controllerRemoved: { [weak self] controller in
self?.controllerRemoved(controller) 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 { guard let strongSelf = self, let modalContainer = modalContainer else {
return return
} }
if let layout = strongSelf.validLayout { if let layout = strongSelf.validLayout {
strongSelf.updateContainers(layout: layout, transition: .immediate) strongSelf.updateContainers(layout: layout, transition: transition)
} }
} }
modalContainer.interactivelyDismissed = { [weak self, weak modalContainer] in modalContainer.interactivelyDismissed = { [weak self, weak modalContainer] in
@ -290,11 +287,7 @@ open class NavigationController: UINavigationController, ContainableController,
} }
strongSelf.setViewControllers(controllers, animated: false) 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) modalContainers.append(modalContainer)
} }
@ -313,6 +306,26 @@ open class NavigationController: UINavigationController, ContainableController,
for i in (0 ..< navigationLayout.modal.count).reversed() { for i in (0 ..< navigationLayout.modal.count).reversed() {
let modalContainer = self.modalContainers[i] 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 modalContainer.supernode == nil && modalContainer.isReady {
if let previousModalContainer = previousModalContainer { if let previousModalContainer = previousModalContainer {
self.displayNode.insertSubnode(modalContainer, belowSubnode: previousModalContainer) self.displayNode.insertSubnode(modalContainer, belowSubnode: previousModalContainer)
@ -321,6 +334,7 @@ open class NavigationController: UINavigationController, ContainableController,
} }
modalContainer.animateIn(transition: transition) modalContainer.animateIn(transition: transition)
} }
if modalContainer.supernode != nil { if modalContainer.supernode != nil {
visibleModalCount += 1 visibleModalCount += 1
if previousModalContainer == nil { if previousModalContainer == nil {
@ -398,6 +412,19 @@ open class NavigationController: UINavigationController, ContainableController,
switch layout.metrics.widthClass { switch layout.metrics.widthClass {
case .compact: case .compact:
if visibleModalCount != 0 { 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 let rootModalFrame: NavigationModalFrame
var modalFrameTransition: ContainedViewLayoutTransition = transition var modalFrameTransition: ContainedViewLayoutTransition = transition
var forceStatusBarAnimation = false var forceStatusBarAnimation = false
@ -405,14 +432,11 @@ open class NavigationController: UINavigationController, ContainableController,
rootModalFrame = current rootModalFrame = current
transition.updateFrame(node: rootModalFrame, frame: CGRect(origin: CGPoint(), size: layout.size)) transition.updateFrame(node: rootModalFrame, frame: CGRect(origin: CGPoint(), size: layout.size))
rootModalFrame.update(layout: layout, transition: modalFrameTransition) rootModalFrame.update(layout: layout, transition: modalFrameTransition)
rootModalFrame.updateDismissal(transition: transition, progress: topModalDismissProgress, completion: {}) rootModalFrame.updateDismissal(transition: transition, progress: effectiveRootModalDismissProgress, additionalProgress: additionalModalFrameProgress, completion: {})
forceStatusBarAnimation = true forceStatusBarAnimation = true
} else { } else {
rootModalFrame = NavigationModalFrame(theme: self.theme) rootModalFrame = NavigationModalFrame(theme: self.theme)
self.rootModalFrame = rootModalFrame 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 { if let rootContainer = self.rootContainer {
var rootContainerNode: ASDisplayNode var rootContainerNode: ASDisplayNode
switch rootContainer { switch rootContainer {
@ -423,9 +447,11 @@ open class NavigationController: UINavigationController, ContainableController,
} }
self.displayNode.insertSubnode(rootModalFrame, aboveSubnode: rootContainerNode) 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) self.statusBarHost?.setStatusBarStyle(.lightContent, animated: transition.isAnimated || forceStatusBarAnimation)
} else { } else {
let normalStatusBarStyle: UIStatusBarStyle let normalStatusBarStyle: UIStatusBarStyle
@ -445,21 +471,28 @@ open class NavigationController: UINavigationController, ContainableController,
case let .split(container): case let .split(container):
rootContainerNode = container rootContainerNode = container
} }
let maxScale = (layout.size.width - 16.0 * 2.0) / layout.size.width
var topInset: CGFloat = 0.0 var topInset: CGFloat = 0.0
if let statusBarHeight = layout.statusBarHeight { if let statusBarHeight = layout.statusBarHeight {
topInset += 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 scale = 1.0 * effectiveRootModalDismissProgress + (1.0 - effectiveRootModalDismissProgress) * maxScale
let offset = (1.0 - topModalDismissProgress) * maxOffset let offset = (1.0 - effectiveRootModalDismissProgress) * maxOffset
transition.updateSublayerTransformScaleAndOffset(node: rootContainerNode, scale: scale, offset: CGPoint(x: 0.0, y: offset)) transition.updateSublayerTransformScaleAndOffset(node: rootContainerNode, scale: scale, offset: CGPoint(x: 0.0, y: offset))
} }
} else { } else {
if let rootModalFrame = self.rootModalFrame { if let rootModalFrame = self.rootModalFrame {
self.rootModalFrame = nil 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() rootModalFrame?.removeFromSupernode()
}) })
let normalStatusBarStyle: UIStatusBarStyle let normalStatusBarStyle: UIStatusBarStyle
@ -485,7 +518,7 @@ open class NavigationController: UINavigationController, ContainableController,
case .regular: case .regular:
if let rootModalFrame = self.rootModalFrame { if let rootModalFrame = self.rootModalFrame {
self.rootModalFrame = nil 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() rootModalFrame?.removeFromSupernode()
}) })
let normalStatusBarStyle: UIStatusBarStyle let normalStatusBarStyle: UIStatusBarStyle

View File

@ -13,17 +13,16 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
private(set) var isReady: Bool = false private(set) var isReady: Bool = false
private(set) var dismissProgress: CGFloat = 0.0 private(set) var dismissProgress: CGFloat = 0.0
var isReadyUpdated: (() -> Void)? var isReadyUpdated: (() -> Void)?
var updateDismissProgress: ((CGFloat) -> Void)? var updateDismissProgress: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
var interactivelyDismissed: (() -> Void)? var interactivelyDismissed: (() -> Void)?
private var ignoreScrolling = false private var ignoreScrolling = false
private var animator: DisplayLinkAnimator? private var isDismissed = false
init(theme: NavigationControllerTheme, controllerRemoved: @escaping (ViewController) -> Void) { init(theme: NavigationControllerTheme, controllerRemoved: @escaping (ViewController) -> Void) {
self.theme = theme self.theme = theme
self.dim = ASDisplayNode() self.dim = ASDisplayNode()
self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.4)
self.dim.alpha = 0.0 self.dim.alpha = 0.0
self.scrollNode = ASScrollNode() self.scrollNode = ASScrollNode()
@ -47,10 +46,8 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
strongSelf.isReadyUpdated?() strongSelf.isReadyUpdated?()
} }
} }
}
deinit { applySmoothRoundedCorners(self.container.layer)
self.animator?.invalidate()
} }
override func didLoad() { override func didLoad() {
@ -69,64 +66,92 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
} }
func scrollViewDidScroll(_ scrollView: UIScrollView) { func scrollViewDidScroll(_ scrollView: UIScrollView) {
if self.ignoreScrolling { if self.ignoreScrolling || self.isDismissed {
return return
} }
var progress = (self.bounds.height - scrollView.bounds.origin.y) / self.bounds.height var progress = (self.bounds.height - scrollView.bounds.origin.y) / self.bounds.height
progress = max(0.0, min(1.0, progress)) progress = max(0.0, min(1.0, progress))
self.dismissProgress = progress self.dismissProgress = progress
self.dim.alpha = 1.0 - 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 var progress = (self.bounds.height - scrollView.bounds.origin.y) / self.bounds.height
progress = max(0.0, min(1.0, progress)) 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 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)) 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 { guard let strongSelf = self else {
return 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 { if targetOffset == 0.0 {
strongSelf.interactivelyDismissed?() 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 scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
} }
func update(layout: ContainerViewLayout, controllers: [ViewController], transition: ContainedViewLayoutTransition) { func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool {
transition.updateFrame(node: self.dim, frame: CGRect(origin: CGPoint(), size: layout.size)) return false
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 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 containerLayout: ContainerViewLayout
let containerFrame: CGRect let containerFrame: CGRect
let containerScale: CGFloat
switch layout.metrics.widthClass { switch layout.metrics.widthClass {
case .compact: case .compact:
self.dim.isHidden = true self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.25)
self.container.clipsToBounds = true self.container.clipsToBounds = true
self.container.cornerRadius = 10.0 self.container.cornerRadius = 10.0
if #available(iOS 11.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) 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: case .regular:
self.dim.isHidden = false self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.4)
self.container.clipsToBounds = true self.container.clipsToBounds = true
self.container.cornerRadius = 10.0 self.container.cornerRadius = 10.0
if #available(iOS 11.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 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) 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) 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? var inputHeight: CGFloat?
if let inputHeightValue = layout.inputHeight { 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) 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) self.container.update(layout: containerLayout, canBeClosed: true, controllers: controllers, transition: transition)
} }
@ -193,4 +225,30 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
return transition 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.fill(CGRect(origin: CGPoint(), size: size))
context.setBlendMode(.copy) context.setBlendMode(.copy)
context.setFillColor(UIColor.clear.cgColor) 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 { final class NavigationModalFrame: ASDisplayNode {
private let dim: ASDisplayNode
private let topShade: ASDisplayNode private let topShade: ASDisplayNode
private let leftShade: ASDisplayNode private let leftShade: ASDisplayNode
private let rightShade: ASDisplayNode private let rightShade: ASDisplayNode
@ -23,14 +25,11 @@ final class NavigationModalFrame: ASDisplayNode {
private var currentMaxCornerRadius: CGFloat? 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? private var validLayout: ContainerViewLayout?
init(theme: NavigationControllerTheme) { 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 = ASDisplayNode()
self.topShade.backgroundColor = .black self.topShade.backgroundColor = .black
self.leftShade = ASDisplayNode() self.leftShade = ASDisplayNode()
@ -47,7 +46,6 @@ final class NavigationModalFrame: ASDisplayNode {
super.init() super.init()
self.addSubnode(self.dim)
self.addSubnode(self.topShade) self.addSubnode(self.topShade)
self.addSubnode(self.leftShade) self.addSubnode(self.leftShade)
self.addSubnode(self.rightShade) self.addSubnode(self.rightShade)
@ -58,39 +56,29 @@ final class NavigationModalFrame: ASDisplayNode {
func update(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { func update(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
self.validLayout = layout self.validLayout = layout
transition.updateFrame(node: self.dim, frame: CGRect(origin: CGPoint(), size: layout.size)) self.updateShades(layout: layout, progress: 1.0 - self.progress, additionalProgress: self.additionalProgress, transition: transition, completion: {})
self.updateShades(layout: layout, progress: 1.0 - self.progress, transition: transition)
} }
func animateIn(transition: ContainedViewLayoutTransition) { func updateDismissal(transition: ContainedViewLayoutTransition, progress: CGFloat, additionalProgress: CGFloat, completion: @escaping () -> Void) {
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) {
self.progress = progress self.progress = progress
self.additionalProgress = additionalProgress
transition.updateAlpha(node: self.dim, alpha: 1.0 - progress, completion: { _ in
completion()
})
if let layout = self.validLayout { 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 let sideInset: CGFloat = 16.0
var topInset: CGFloat = 0.0 var topInset: CGFloat = 0.0
if let statusBarHeight = layout.statusBarHeight { if let statusBarHeight = layout.statusBarHeight {
topInset += statusBarHeight topInset += statusBarHeight
} }
let additionalTopInset: CGFloat = 10.0
let cornerRadius: CGFloat = 8.0 let cornerRadius: CGFloat = 9.0
let initialCornerRadius: CGFloat let initialCornerRadius: CGFloat
if !layout.safeInsets.top.isZero { if !layout.safeInsets.top.isZero {
initialCornerRadius = 40.0 initialCornerRadius = 40.0
@ -103,11 +91,20 @@ final class NavigationModalFrame: ASDisplayNode {
} }
let cornerSize = progress * cornerRadius + (1.0 - progress) * initialCornerRadius 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))) let cornerSideOffset: CGFloat = progress * sideInset + additionalProgress * sideInset
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 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))) let topShadeOffset: CGFloat = progress * topInset + additionalProgress * additionalTopInset
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))) let leftShadeOffset: CGFloat = progress * sideInset + additionalProgress * sideInset
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 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

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

View File

@ -154,6 +154,20 @@ static void setNilField(CustomBlurEffect *object, NSString *name) {
[inv invoke]; [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() { UIBlurEffect *makeCustomZoomBlurEffect() {
if (@available(iOS 11.0, *)) { if (@available(iOS 11.0, *)) {
NSString *string = [@[@"_", @"UI", @"Custom", @"BlurEffect"] componentsJoinedByString:@""]; NSString *string = [@[@"_", @"UI", @"Custom", @"BlurEffect"] componentsJoinedByString:@""];
@ -178,3 +192,9 @@ UIBlurEffect *makeCustomZoomBlurEffect() {
return [UIBlurEffect effectWithStyle:UIBlurEffectStyleRegular]; 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 { if !self.isViewLoaded {
self.loadView() self.loadView()
} }
transition.updateFrame(node: self.displayNode, frame: CGRect(origin: self.view.frame.origin, size: layout.size))
if let _ = layout.statusBarHeight { if let _ = layout.statusBarHeight {
self.statusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 40.0)) self.statusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 40.0))
} }

View File

@ -199,7 +199,6 @@ public func getFirstResponderAndAccessoryHeight(_ view: UIView, _ accessoryHeigh
public final class WindowHostView { public final class WindowHostView {
public let containerView: UIView public let containerView: UIView
public let eventView: UIView public let eventView: UIView
public let aboveStatusBarView: UIView
public let isRotating: () -> Bool public let isRotating: () -> Bool
let updateSupportedInterfaceOrientations: (UIInterfaceOrientationMask) -> Void let updateSupportedInterfaceOrientations: (UIInterfaceOrientationMask) -> Void
@ -221,10 +220,9 @@ public final class WindowHostView {
var forEachController: (((ContainableController) -> Void) -> Void)? var forEachController: (((ContainableController) -> Void) -> Void)?
var getAccessibilityElements: (() -> [Any]?)? 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.containerView = containerView
self.eventView = eventView self.eventView = eventView
self.aboveStatusBarView = aboveStatusBarView
self.isRotating = isRotating self.isRotating = isRotating
self.updateSupportedInterfaceOrientations = updateSupportedInterfaceOrientations self.updateSupportedInterfaceOrientations = updateSupportedInterfaceOrientations
self.updateDeferScreenEdgeGestures = updateDeferScreenEdgeGestures 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.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.updatingLayout = UpdatingLayout(layout: self.windowLayout, transition: .immediate)
self.presentationContext = PresentationContext() 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.presentationContext.updateIsInteractionBlocked = { [weak self] value in
self?.isInteractionBlocked = value self?.isInteractionBlocked = value
@ -697,6 +695,7 @@ public class Window1 {
rootController.keyboardManager = self.keyboardManager rootController.keyboardManager = self.keyboardManager
} }
if !self.windowLayout.size.width.isZero && !self.windowLayout.size.height.isZero { 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) 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) let layout = containedLayoutForWindowLayout(self.windowLayout, deviceMetrics: self.deviceMetrics)
for controller in self._topLevelOverlayControllers { for controller in self._topLevelOverlayControllers {
controller.displayNode.frame = CGRect(origin: CGPoint(), size: self.windowLayout.size)
controller.containerLayoutUpdated(layout, transition: .immediate) controller.containerLayoutUpdated(layout, transition: .immediate)
if let coveringView = self.coveringView { if let coveringView = self.coveringView {
@ -977,11 +977,15 @@ public class Window1 {
if self.presentationContext.isCurrentlyOpaque { if self.presentationContext.isCurrentlyOpaque {
rootLayout.inputHeight = nil 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.presentationContext.containerLayoutUpdated(childLayout, transition: updatingLayout.transition)
self.overlayPresentationContext.containerLayoutUpdated(childLayout, transition: updatingLayout.transition) self.overlayPresentationContext.containerLayoutUpdated(childLayout, transition: updatingLayout.transition)
for controller in self.topLevelOverlayControllers { 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) controller.containerLayoutUpdated(childLayout, transition: updatingLayout.transition)
} }
} }

View File

@ -15,6 +15,7 @@ static_library(
"//submodules/SelectablePeerNode:SelectablePeerNode", "//submodules/SelectablePeerNode:SelectablePeerNode",
"//submodules/PeerOnlineMarkerNode:PeerOnlineMarkerNode", "//submodules/PeerOnlineMarkerNode:PeerOnlineMarkerNode",
"//submodules/TelegramStringFormatting:TelegramStringFormatting", "//submodules/TelegramStringFormatting:TelegramStringFormatting",
"//submodules/ContextUI:ContextUI",
], ],
frameworks = [ frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework", "$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@ -9,6 +9,7 @@ import TelegramPresentationData
import TelegramStringFormatting import TelegramStringFormatting
import PeerOnlineMarkerNode import PeerOnlineMarkerNode
import SelectablePeerNode import SelectablePeerNode
import ContextUI
public enum HorizontalPeerItemMode { public enum HorizontalPeerItemMode {
case list case list
@ -24,20 +25,20 @@ public final class HorizontalPeerItem: ListViewItem {
let account: Account let account: Account
public let peer: Peer public let peer: Peer
let action: (Peer) -> Void let action: (Peer) -> Void
let longTapAction: (Peer) -> Void let contextAction: (Peer, ASDisplayNode, ContextGesture?) -> Void
let isPeerSelected: (PeerId) -> Bool let isPeerSelected: (PeerId) -> Bool
let customWidth: CGFloat? let customWidth: CGFloat?
let presence: PeerPresence? let presence: PeerPresence?
let unreadBadge: (Int32, Bool)? 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.theme = theme
self.strings = strings self.strings = strings
self.mode = mode self.mode = mode
self.account = account self.account = account
self.peer = peer self.peer = peer
self.action = action self.action = action
self.longTapAction = longTapAction self.contextAction = contextAction
self.isPeerSelected = isPeerSelected self.isPeerSelected = isPeerSelected
self.customWidth = customWidth self.customWidth = customWidth
self.presence = presence self.presence = presence
@ -110,9 +111,9 @@ public final class HorizontalPeerItemNode: ListViewItemNode {
item.action(item.peer) item.action(item.peer)
} }
} }
self.peerNode.longTapAction = { [weak self] in self.peerNode.contextAction = { [weak self] node, gesture in
if let item = self?.item { 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) super.init(navigationBarPresentationData: nil)
self.navigationPresentation = .modalInLargeLayout
self.statusBar.statusBarStyle = .White 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 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 { 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) self.pushController(controller)
return 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) 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() let legacyLocation = TGLocationMediaAttachment()
legacyLocation.latitude = mapMedia.latitude legacyLocation.latitude = mapMedia.latitude
legacyLocation.longitude = mapMedia.longitude legacyLocation.longitude = mapMedia.longitude
@ -137,7 +137,8 @@ public func legacyLocationController(message: Message?, mapMedia: TelegramMediaM
let presentationData = context.sharedContext.currentPresentationData.with { $0 } 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 let controller: TGLocationViewController
if let message = message { if let message = message {
@ -234,7 +235,7 @@ public func legacyLocationController(message: Message?, mapMedia: TelegramMediaM
let theme = (context.sharedContext.currentPresentationData.with { $0 }).theme let theme = (context.sharedContext.currentPresentationData.with { $0 }).theme
controller.pallete = legacyLocationPalette(from: theme) controller.pallete = legacyLocationPalette(from: theme)
controller.modalMode = isModal controller.modalMode = true
controller.presentActionsMenu = { [weak legacyController] legacyLocation, directions in controller.presentActionsMenu = { [weak legacyController] legacyLocation, directions in
if let strongLegacyController = legacyController, let location = legacyLocation { if let strongLegacyController = legacyController, let location = legacyLocation {
let map = telegramMap(for: location) let map = telegramMap(for: location)
@ -256,13 +257,6 @@ 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 legacyController.navigationItem.title = controller.navigationItem.title
controller.updateRightBarItem = { item, action, animated in controller.updateRightBarItem = { item, action, animated in
if action { if action {
@ -272,7 +266,6 @@ public func legacyLocationController(message: Message?, mapMedia: TelegramMediaM
} }
} }
legacyController.bind(controller: controller) legacyController.bind(controller: controller)
}
let presentationDisposable = context.sharedContext.presentationData.start(next: { [weak controller] presentationData in let presentationDisposable = context.sharedContext.presentationData.start(next: { [weak controller] presentationData in
if let controller = controller { if let controller = controller {

View File

@ -2028,7 +2028,7 @@ public func groupInfoController(context: AccountContext, peerId originalPeerId:
return 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 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) context.sharedContext.applicationBindings.openUrl(url)
}) })
pushControllerImpl?(controller) pushControllerImpl?(controller)

View File

@ -15,6 +15,7 @@ static_library(
"//submodules/PeerOnlineMarkerNode:PeerOnlineMarkerNode", "//submodules/PeerOnlineMarkerNode:PeerOnlineMarkerNode",
"//submodules/AvatarNode:AvatarNode", "//submodules/AvatarNode:AvatarNode",
"//submodules/LegacyComponents:LegacyComponents", "//submodules/LegacyComponents:LegacyComponents",
"//submodules/ContextUI:ContextUI",
], ],
frameworks = [ frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework", "$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@ -9,6 +9,7 @@ import TelegramPresentationData
import AvatarNode import AvatarNode
import PeerOnlineMarkerNode import PeerOnlineMarkerNode
import LegacyComponents import LegacyComponents
import ContextUI
private let avatarFont = UIFont(name: ".SFCompactRounded-Semibold", size: 24.0)! private let avatarFont = UIFont(name: ".SFCompactRounded-Semibold", size: 24.0)!
private let textFont = Font.regular(11.0) private let textFont = Font.regular(11.0)
@ -62,6 +63,7 @@ public final class SelectablePeerNodeTheme {
} }
public final class SelectablePeerNode: ASDisplayNode { public final class SelectablePeerNode: ASDisplayNode {
private let contextContainer: ContextControllerSourceNode
private let avatarSelectionNode: ASImageNode private let avatarSelectionNode: ASImageNode
private let avatarNodeContainer: ASDisplayNode private let avatarNodeContainer: ASDisplayNode
private let avatarNode: AvatarNode private let avatarNode: AvatarNode
@ -70,7 +72,11 @@ public final class SelectablePeerNode: ASDisplayNode {
private let textNode: ASTextNode private let textNode: ASTextNode
public var toggleSelection: (() -> Void)? 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 private var currentSelected = false
@ -87,6 +93,9 @@ public final class SelectablePeerNode: ASDisplayNode {
} }
override public init() { override public init() {
self.contextContainer = ContextControllerSourceNode()
self.contextContainer.isGestureEnabled = false
self.avatarNodeContainer = ASDisplayNode() self.avatarNodeContainer = ASDisplayNode()
self.avatarSelectionNode = ASImageNode() self.avatarSelectionNode = ASImageNode()
@ -108,11 +117,20 @@ public final class SelectablePeerNode: ASDisplayNode {
super.init() super.init()
self.addSubnode(self.contextContainer)
self.avatarNodeContainer.addSubnode(self.avatarSelectionNode) self.avatarNodeContainer.addSubnode(self.avatarSelectionNode)
self.avatarNodeContainer.addSubnode(self.avatarNode) self.avatarNodeContainer.addSubnode(self.avatarNode)
self.addSubnode(self.avatarNodeContainer) self.contextContainer.addSubnode(self.avatarNodeContainer)
self.addSubnode(self.textNode) self.contextContainer.addSubnode(self.textNode)
self.addSubnode(self.onlineNode) 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) { 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() super.didLoad()
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) 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) { @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
@ -233,6 +241,8 @@ public final class SelectablePeerNode: ASDisplayNode {
let bounds = self.bounds 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.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)) 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 { } else {
peersNode = ChatListSearchRecentPeersNode(account: account, theme: theme, mode: .actionSheet, strings: strings, peerSelected: { [weak self] peer in peersNode = ChatListSearchRecentPeersNode(account: account, theme: theme, mode: .actionSheet, strings: strings, peerSelected: { [weak self] peer in
self?.controllerInteraction?.togglePeer(RenderedPeer(peer: peer), true) 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 return self?.controllerInteraction?.selectedPeerIds.contains(peerId) ?? false
}, share: true) }, share: true)
self.peersNode = peersNode self.peersNode = peersNode

View File

@ -69,7 +69,7 @@ private class ApplicationStatusBarHost: StatusBarHost {
} }
var statusBarWindow: UIView? { var statusBarWindow: UIView? {
return self.application.value(forKey: "statusBarWindow") as? UIView return nil//self.application.value(forKey: "statusBarWindow") as? UIView
} }
var statusBarView: UIView? { var statusBarView: UIView? {
@ -165,7 +165,6 @@ final class SharedApplicationContext {
@objc var window: UIWindow? @objc var window: UIWindow?
var nativeWindow: (UIWindow & WindowHost)? var nativeWindow: (UIWindow & WindowHost)?
var mainWindow: Window1! var mainWindow: Window1!
var aboveStatusbarWindow: UIWindow?
private var dataImportSplash: LegacyDataImportSplash? private var dataImportSplash: LegacyDataImportSplash?
let episodeId = arc4random() let episodeId = arc4random()
@ -240,9 +239,8 @@ final class SharedApplicationContext {
let statusBarHost = ApplicationStatusBarHost() let statusBarHost = ApplicationStatusBarHost()
let (window, hostView, aboveStatusbarWindow) = nativeWindowHostView() let (window, hostView) = nativeWindowHostView()
self.mainWindow = Window1(hostView: hostView, statusBarHost: statusBarHost) self.mainWindow = Window1(hostView: hostView, statusBarHost: statusBarHost)
self.aboveStatusbarWindow = aboveStatusbarWindow
hostView.containerView.backgroundColor = UIColor.white hostView.containerView.backgroundColor = UIColor.white
self.window = window self.window = window
self.nativeWindow = window self.nativeWindow = window
@ -448,7 +446,6 @@ final class SharedApplicationContext {
#endif #endif
#endif #endif
self.aboveStatusbarWindow?.isHidden = false
self.window?.makeKeyAndVisible() self.window?.makeKeyAndVisible()
self.hasActiveAudioSession.set(MediaManagerImpl.globalAudioSession.isActive()) self.hasActiveAudioSession.set(MediaManagerImpl.globalAudioSession.isActive())
@ -1093,7 +1090,9 @@ final class SharedApplicationContext {
}) })
let pushRegistry = PKPushRegistry(queue: .main) let pushRegistry = PKPushRegistry(queue: .main)
if #available(iOS 9.0, *) {
pushRegistry.desiredPushTypes = Set([.voIP]) pushRegistry.desiredPushTypes = Set([.voIP])
}
self.pushRegistry = pushRegistry self.pushRegistry = pushRegistry
pushRegistry.delegate = self pushRegistry.delegate = self
@ -1341,14 +1340,17 @@ final class SharedApplicationContext {
} }
public func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) { public func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {
if #available(iOS 9.0, *) {
if case PKPushType.voIP = type { if case PKPushType.voIP = type {
Logger.shared.log("App \(self.episodeId)", "pushRegistry credentials: \(credentials.token as NSData)") Logger.shared.log("App \(self.episodeId)", "pushRegistry credentials: \(credentials.token as NSData)")
self.voipTokenPromise.set(.single(credentials.token)) self.voipTokenPromise.set(.single(credentials.token))
} }
} }
}
public func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) { public func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) {
if #available(iOS 9.0, *) {
let _ = (self.sharedContextPromise.get() let _ = (self.sharedContextPromise.get()
|> take(1) |> take(1)
|> deliverOnMainQueue).start(next: { sharedApplicationContext in |> deliverOnMainQueue).start(next: { sharedApplicationContext in
@ -1360,6 +1362,7 @@ final class SharedApplicationContext {
} }
}) })
} }
}
public func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) { public func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
Logger.shared.log("App \(self.episodeId)", "invalidated token for \(type)") Logger.shared.log("App \(self.episodeId)", "invalidated token for \(type)")

View File

@ -253,7 +253,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
case let .map(mapMedia): case let .map(mapMedia):
params.dismissInput() 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) params.openPeer(peer, .info)
}, sendLiveLocation: { coordinate, period in }, 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) 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) let pageController = InstantPageController(context: context, webPage: webpage, sourcePeerType: sourcePeerType ?? .channel, anchor: anchor)
pageController.navigationPresentation = .modal
navigationController.pushViewController(pageController) navigationController.pushViewController(pageController)
} }
break break