mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 01:10:09 +00:00
Modal UI improvements
This commit is contained in:
parent
f8b434f341
commit
6c544eb9c2
@ -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",
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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
|
||||||
})
|
})
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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?
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -217,37 +217,50 @@ 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) {
|
||||||
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.layout = layout
|
||||||
self.state.canBeClosed = canBeClosed
|
self.state.canBeClosed = canBeClosed
|
||||||
|
|
||||||
if controllers.last !== self.state.top?.value {
|
var controllersUpdated = false
|
||||||
if controllers.last !== self.state.pending?.value.value {
|
if self.controllers.count != controllers.count {
|
||||||
self.state.pending = nil
|
controllersUpdated = true
|
||||||
if let last = controllers.last {
|
} else {
|
||||||
let transitionType: PendingChild.TransitionType
|
for i in 0 ..< controllers.count {
|
||||||
if !previousControllers.contains(where: { $0 === last }) {
|
if self.controllers[i] !== controllers[i] {
|
||||||
transitionType = .push
|
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 {
|
} 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) {
|
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)
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
applySmoothRoundedCorners(self.container.layer)
|
||||||
deinit {
|
|
||||||
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 {
|
func update(layout: ContainerViewLayout, controllers: [ViewController], coveredByModalTransition: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height), size: layout.size))
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,12 +10,12 @@ public enum StatusBarStyle {
|
|||||||
|
|
||||||
public init(systemStyle: UIStatusBarStyle) {
|
public init(systemStyle: UIStatusBarStyle) {
|
||||||
switch systemStyle {
|
switch systemStyle {
|
||||||
case .default:
|
case .default:
|
||||||
self = .Black
|
self = .Black
|
||||||
case .lightContent:
|
case .lightContent:
|
||||||
self = .White
|
self = .White
|
||||||
case .blackOpaque:
|
case .blackOpaque:
|
||||||
self = .Black
|
self = .Black
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -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))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,23 +257,15 @@ public func legacyLocationController(message: Message?, mapMedia: TelegramMediaM
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if isModal {
|
legacyController.navigationItem.title = controller.navigationItem.title
|
||||||
let navigationController = TGNavigationController(controllers: [controller])!
|
controller.updateRightBarItem = { item, action, animated in
|
||||||
legacyController.bind(controller: navigationController)
|
if action {
|
||||||
controller.navigation_setDismiss({ [weak legacyController] in
|
legacyController.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationShareIcon(theme), style: .plain, target: controller, action: #selector(controller.actionsButtonPressed))
|
||||||
legacyController?.dismiss()
|
} else {
|
||||||
}, rootController: nil)
|
legacyController.navigationItem.setRightBarButton(item, animated: animated)
|
||||||
} 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.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 {
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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))
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
pushRegistry.desiredPushTypes = Set([.voIP])
|
if #available(iOS 9.0, *) {
|
||||||
|
pushRegistry.desiredPushTypes = Set([.voIP])
|
||||||
|
}
|
||||||
self.pushRegistry = pushRegistry
|
self.pushRegistry = pushRegistry
|
||||||
pushRegistry.delegate = self
|
pushRegistry.delegate = self
|
||||||
|
|
||||||
@ -1341,24 +1340,28 @@ 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 case PKPushType.voIP = type {
|
if #available(iOS 9.0, *) {
|
||||||
Logger.shared.log("App \(self.episodeId)", "pushRegistry credentials: \(credentials.token as NSData)")
|
if case PKPushType.voIP = type {
|
||||||
|
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) {
|
||||||
let _ = (self.sharedContextPromise.get()
|
if #available(iOS 9.0, *) {
|
||||||
|> take(1)
|
let _ = (self.sharedContextPromise.get()
|
||||||
|> deliverOnMainQueue).start(next: { sharedApplicationContext in
|
|> take(1)
|
||||||
sharedApplicationContext.wakeupManager.allowBackgroundTimeExtension(timeout: 4.0)
|
|> 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)")
|
if case PKPushType.voIP = type {
|
||||||
sharedApplicationContext.notificationManager.addNotification(payload.dictionaryPayload)
|
Logger.shared.log("App \(self.episodeId)", "pushRegistry payload: \(payload.dictionaryPayload)")
|
||||||
}
|
sharedApplicationContext.notificationManager.addNotification(payload.dictionaryPayload)
|
||||||
})
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
|
public func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user