mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2026-02-05 18:58:51 +00:00
no message
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
<key>DisplayMac.xcscheme</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>24</integer>
|
||||
<integer>25</integer>
|
||||
</dict>
|
||||
<key>DisplayTests.xcscheme</key>
|
||||
<dict>
|
||||
|
||||
@@ -38,7 +38,7 @@ public class ActionSheetTextNode: ActionSheetItemNode {
|
||||
|
||||
self.label = ASTextNode()
|
||||
self.label.isLayerBacked = true
|
||||
self.label.maximumNumberOfLines = 1
|
||||
self.label.maximumNumberOfLines = 0
|
||||
self.label.displaysAsynchronously = false
|
||||
self.label.truncationMode = .byTruncatingTail
|
||||
|
||||
@@ -51,7 +51,7 @@ public class ActionSheetTextNode: ActionSheetItemNode {
|
||||
func setItem(_ item: ActionSheetTextItem) {
|
||||
self.item = item
|
||||
|
||||
self.label.attributedText = NSAttributedString(string: item.title, font: ActionSheetTextNode.defaultFont, textColor: self.theme.secondaryTextColor)
|
||||
self.label.attributedText = NSAttributedString(string: item.title, font: ActionSheetTextNode.defaultFont, textColor: self.theme.secondaryTextColor, paragraphAlignment: .center)
|
||||
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
|
||||
@@ -28,10 +28,12 @@ open class AlertController: ViewController {
|
||||
|
||||
private let theme: AlertControllerTheme
|
||||
private let contentNode: AlertContentNode
|
||||
private let allowInputInset: Bool
|
||||
|
||||
public init(theme: AlertControllerTheme, contentNode: AlertContentNode) {
|
||||
public init(theme: AlertControllerTheme, contentNode: AlertContentNode, allowInputInset: Bool = true) {
|
||||
self.theme = theme
|
||||
self.contentNode = contentNode
|
||||
self.allowInputInset = allowInputInset
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
|
||||
@@ -43,7 +45,7 @@ open class AlertController: ViewController {
|
||||
}
|
||||
|
||||
override open func loadDisplayNode() {
|
||||
self.displayNode = AlertControllerNode(contentNode: self.contentNode, theme: self.theme)
|
||||
self.displayNode = AlertControllerNode(contentNode: self.contentNode, theme: self.theme, allowInputInset: self.allowInputInset)
|
||||
self.displayNodeDidLoad()
|
||||
|
||||
self.controllerNode.dismiss = { [weak self] in
|
||||
|
||||
@@ -6,10 +6,12 @@ final class AlertControllerNode: ASDisplayNode {
|
||||
private let containerNode: ASDisplayNode
|
||||
private let effectNode: ASDisplayNode
|
||||
private let contentNode: AlertContentNode
|
||||
private let allowInputInset: Bool
|
||||
|
||||
var dismiss: (() -> Void)?
|
||||
|
||||
init(contentNode: AlertContentNode, theme: AlertControllerTheme) {
|
||||
init(contentNode: AlertContentNode, theme: AlertControllerTheme, allowInputInset: Bool) {
|
||||
self.allowInputInset = allowInputInset
|
||||
self.dimmingNode = ASDisplayNode()
|
||||
self.dimmingNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
||||
|
||||
@@ -58,7 +60,11 @@ final class AlertControllerNode: ASDisplayNode {
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
transition.updateFrame(node: self.dimmingNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
|
||||
var insets = layout.insets(options: [.statusBar, .input])
|
||||
var insetOptions: ContainerViewLayoutInsetOptions = [.statusBar]
|
||||
if self.allowInputInset {
|
||||
insetOptions.insert(.input)
|
||||
}
|
||||
var insets = layout.insets(options: insetOptions)
|
||||
let maxWidth = min(240.0, layout.size.width - 70.0)
|
||||
insets.left = floor((layout.size.width - maxWidth) / 2.0)
|
||||
insets.right = floor((layout.size.width - maxWidth) / 2.0)
|
||||
|
||||
@@ -40,7 +40,7 @@ public struct ContainerViewLayout: Equatable {
|
||||
public let intrinsicInsets: UIEdgeInsets
|
||||
public let safeInsets: UIEdgeInsets
|
||||
public let statusBarHeight: CGFloat?
|
||||
public let inputHeight: CGFloat?
|
||||
public var inputHeight: CGFloat?
|
||||
public let standardInputHeight: CGFloat
|
||||
public let inputHeightIsInteractivellyChanging: Bool
|
||||
|
||||
|
||||
@@ -27,6 +27,10 @@ private final class HapticFeedbackImpl {
|
||||
self.notificationGenerator.notificationOccurred(.success)
|
||||
}
|
||||
|
||||
func prepareError() {
|
||||
self.notificationGenerator.prepare()
|
||||
}
|
||||
|
||||
func error() {
|
||||
self.notificationGenerator.notificationOccurred(.error)
|
||||
}
|
||||
@@ -100,6 +104,14 @@ public final class HapticFeedback {
|
||||
}
|
||||
}
|
||||
|
||||
public func prepareError() {
|
||||
if #available(iOSApplicationExtension 10.0, *) {
|
||||
self.withImpl { impl in
|
||||
impl.prepareError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func error() {
|
||||
if #available(iOSApplicationExtension 10.0, *) {
|
||||
self.withImpl { impl in
|
||||
|
||||
@@ -22,6 +22,7 @@ public class ImmediateTextNode: TextNode {
|
||||
}
|
||||
|
||||
public var tapAttributeAction: (([NSAttributedStringKey: Any]) -> Void)?
|
||||
public var longTapAttributeAction: (([NSAttributedStringKey: Any]) -> Void)?
|
||||
|
||||
public func updateLayout(_ constrainedSize: CGSize) -> CGSize {
|
||||
let makeLayout = TextNode.asyncLayout(self)
|
||||
@@ -61,7 +62,7 @@ public class ImmediateTextNode: TextNode {
|
||||
strongSelf.addSubnode(linkHighlightingNode)
|
||||
}
|
||||
linkHighlightingNode.frame = strongSelf.bounds
|
||||
linkHighlightingNode.updateRects(rects.map { $0.offsetBy(dx: 0.0, dy: -3.0) })
|
||||
linkHighlightingNode.updateRects(rects.map { $0.offsetBy(dx: 0.0, dy: 0.0) })
|
||||
} else if let linkHighlightingNode = strongSelf.linkHighlightingNode {
|
||||
strongSelf.linkHighlightingNode = nil
|
||||
linkHighlightingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak linkHighlightingNode] _ in
|
||||
@@ -87,6 +88,10 @@ public class ImmediateTextNode: TextNode {
|
||||
if let (_, attributes) = self.attributesAtPoint(CGPoint(x: location.x, y: location.y)) {
|
||||
self.tapAttributeAction?(attributes)
|
||||
}
|
||||
case .longTap:
|
||||
if let (_, attributes) = self.attributesAtPoint(CGPoint(x: location.x, y: location.y)) {
|
||||
self.longTapAttributeAction?(attributes)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
|
||||
private enum CornerType {
|
||||
case topLeft
|
||||
|
||||
@@ -189,9 +189,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
public final var displayedItemRangeChanged: (ListViewDisplayedItemRange, Any?) -> Void = { _, _ in }
|
||||
public private(set) final var displayedItemRange: ListViewDisplayedItemRange = ListViewDisplayedItemRange(loadedRange: nil, visibleRange: nil)
|
||||
|
||||
private final var opaqueTransactionState: Any?
|
||||
public private(set) final var opaqueTransactionState: Any?
|
||||
|
||||
public final var visibleContentOffsetChanged: (ListViewVisibleContentOffset) -> Void = { _ in }
|
||||
public final var visibleBottomContentOffsetChanged: (ListViewVisibleContentOffset) -> Void = { _ in }
|
||||
public final var beganInteractiveDragging: () -> Void = { }
|
||||
|
||||
public final var reorderItem: (Int, Int, Any?) -> Void = { _, _, _ in }
|
||||
@@ -400,7 +401,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
var closestIndex: (Int, CGFloat)?
|
||||
for i in 0 ..< self.itemNodes.count {
|
||||
if let itemNodeIndex = self.itemNodes[i].index, itemNodeIndex != reorderItemIndex {
|
||||
let itemOffset = self.itemNodes[i].frame.midY
|
||||
let itemFrame = self.itemNodes[i].apparentContentFrame
|
||||
let itemOffset = itemFrame.midY
|
||||
let deltaOffset = itemOffset - verticalOffset
|
||||
if let (_, closestOffset) = closestIndex {
|
||||
if abs(deltaOffset) < abs(closestOffset) {
|
||||
@@ -793,8 +795,26 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
return offset
|
||||
}
|
||||
|
||||
public func visibleBottomContentOffset() -> ListViewVisibleContentOffset {
|
||||
var offset: ListViewVisibleContentOffset = .unknown
|
||||
var bottomItemIndexAndFrame: (Int, CGRect) = (-1, CGRect())
|
||||
for itemNode in self.itemNodes.reversed() {
|
||||
if let index = itemNode.index {
|
||||
bottomItemIndexAndFrame = (index, itemNode.apparentFrame)
|
||||
break
|
||||
}
|
||||
}
|
||||
if bottomItemIndexAndFrame.0 == self.items.count - 1 {
|
||||
offset = .known(bottomItemIndexAndFrame.1.maxY - (self.visibleSize.height - self.insets.bottom))
|
||||
} else if bottomItemIndexAndFrame.0 == -1 {
|
||||
offset = .none
|
||||
}
|
||||
return offset
|
||||
}
|
||||
|
||||
private func updateVisibleContentOffset() {
|
||||
self.visibleContentOffsetChanged(self.visibleContentOffset())
|
||||
self.visibleBottomContentOffsetChanged(self.visibleBottomContentOffset())
|
||||
}
|
||||
|
||||
private func stopScrolling() {
|
||||
@@ -1188,11 +1208,6 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
widthUpdated = false
|
||||
}
|
||||
|
||||
if let scrollToItem = scrollToItem {
|
||||
state.scrollPosition = (scrollToItem.index, scrollToItem.position)
|
||||
}
|
||||
state.fixScrollPostition(self.items.count)
|
||||
|
||||
let sortedDeleteIndices = deleteIndices.sorted(by: {$0.index < $1.index})
|
||||
for deleteItem in sortedDeleteIndices.reversed() {
|
||||
self.items.remove(at: deleteItem.index)
|
||||
@@ -1227,6 +1242,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
}
|
||||
}
|
||||
|
||||
if let scrollToItem = scrollToItem {
|
||||
state.scrollPosition = (scrollToItem.index, scrollToItem.position)
|
||||
}
|
||||
let itemsCount = self.items.count
|
||||
state.fixScrollPostition(itemsCount)
|
||||
|
||||
let actions = {
|
||||
var previousFrames: [Int: CGRect] = [:]
|
||||
for i in 0 ..< state.nodes.count {
|
||||
@@ -1371,6 +1392,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
state.setupStationaryOffset(index, boundary: boundary, frames: previousFrames)
|
||||
}
|
||||
|
||||
if let _ = scrollToItem {
|
||||
state.fixScrollPostition(itemsCount)
|
||||
}
|
||||
|
||||
if self.debugInfo {
|
||||
print("deleteAndInsertItemsTransaction prepare \((CACurrentMediaTime() - startTime) * 1000.0) ms")
|
||||
}
|
||||
@@ -3177,6 +3202,15 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
return nil
|
||||
}
|
||||
|
||||
public func itemNodeAtIndex(_ index: Int) -> ListViewItemNode? {
|
||||
for itemNode in self.itemNodes {
|
||||
if itemNode.index == index {
|
||||
return itemNode
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public func forEachItemNode(_ f: (ASDisplayNode) -> Void) {
|
||||
for itemNode in self.itemNodes {
|
||||
if itemNode.index != nil {
|
||||
|
||||
@@ -506,8 +506,12 @@ struct ListViewState {
|
||||
|
||||
if currentUpperNode.index != 0 && currentUpperNode.frame.minY > -self.invisibleInset - CGFloat.ulpOfOne {
|
||||
var directionHint: ListViewInsertionOffsetDirection?
|
||||
if let hint = insertDirectionHints[currentUpperNode.index - 1] , currentUpperNode.frame.minY > self.insets.top - CGFloat.ulpOfOne {
|
||||
directionHint = ListViewInsertionOffsetDirection(hint)
|
||||
if let hint = insertDirectionHints[currentUpperNode.index - 1] {
|
||||
if currentUpperNode.frame.minY >= self.insets.top - CGFloat.ulpOfOne {
|
||||
directionHint = ListViewInsertionOffsetDirection(hint)
|
||||
}
|
||||
} else if currentUpperNode.frame.minY >= self.insets.top - CGFloat.ulpOfOne {
|
||||
directionHint = .Down
|
||||
}
|
||||
|
||||
return ListViewInsertionPoint(index: currentUpperNode.index - 1, point: CGPoint(x: 0.0, y: currentUpperNode.frame.minY), direction: directionHint ?? .Up)
|
||||
|
||||
@@ -19,6 +19,7 @@ final class ListViewReorderingItemNode: ASDisplayNode {
|
||||
if let copyView = self.copyView {
|
||||
self.view.addSubview(copyView)
|
||||
copyView.frame = CGRect(origin: CGPoint(x: initialLocation.x, y: initialLocation.y), size: copyView.bounds.size)
|
||||
copyView.bounds = itemNode.bounds
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -315,6 +315,9 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
if isMaster, let firstControllerFrameAndLayout = firstControllerFrameAndLayout {
|
||||
masterController = record.controller
|
||||
frame = firstControllerFrameAndLayout.0
|
||||
if let controller = masterController as? ViewController {
|
||||
self.controllerView.sharedStatusBar.statusBarStyle = controller.statusBar.statusBarStyle
|
||||
}
|
||||
} else {
|
||||
frame = lastControllerFrameAndLayout.0
|
||||
}
|
||||
@@ -355,6 +358,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
animatedAppearingDetailController = true
|
||||
|
||||
previousController.viewWillDisappear(true)
|
||||
record.controller.viewWillAppear(true)
|
||||
record.controller.setIgnoreAppearanceMethodInvocations(true)
|
||||
self.controllerView.containerView.addSubview(record.controller.view)
|
||||
record.controller.setIgnoreAppearanceMethodInvocations(false)
|
||||
@@ -732,7 +736,8 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
public func replaceAllButRootController(_ controller: ViewController, animated: Bool, ready: ValuePromise<Bool>? = nil) {
|
||||
self.view.endEditing(true)
|
||||
if let validLayout = self.validLayout {
|
||||
let (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: validLayout), layout: validLayout, index: self.viewControllers.count)
|
||||
var (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: validLayout), layout: validLayout, index: self.viewControllers.count)
|
||||
controllerLayout.inputHeight = nil
|
||||
controller.containerLayoutUpdated(controllerLayout, transition: .immediate)
|
||||
}
|
||||
self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { [weak self] _ in
|
||||
|
||||
@@ -6,14 +6,21 @@ public enum PeekControllerMenuItemColor {
|
||||
case destructive
|
||||
}
|
||||
|
||||
public enum PeekControllerMenuItemFont {
|
||||
case `default`
|
||||
case bold
|
||||
}
|
||||
|
||||
public struct PeekControllerMenuItem {
|
||||
public let title: String
|
||||
public let color: PeekControllerMenuItemColor
|
||||
public let font: PeekControllerMenuItemFont
|
||||
public let action: () -> Void
|
||||
|
||||
public init(title: String, color: PeekControllerMenuItemColor, action: @escaping () -> Void) {
|
||||
public init(title: String, color: PeekControllerMenuItemColor, font: PeekControllerMenuItemFont = .default, action: @escaping () -> Void) {
|
||||
self.title = title
|
||||
self.color = color
|
||||
self.font = font
|
||||
self.action = action
|
||||
}
|
||||
}
|
||||
@@ -44,13 +51,20 @@ final class PeekControllerMenuItemNode: HighlightTrackingButtonNode {
|
||||
self.textNode.displaysAsynchronously = false
|
||||
|
||||
let textColor: UIColor
|
||||
let textFont: UIFont
|
||||
switch item.color {
|
||||
case .accent:
|
||||
textColor = theme.accentColor
|
||||
case .destructive:
|
||||
textColor = theme.destructiveColor
|
||||
}
|
||||
self.textNode.attributedText = NSAttributedString(string: item.title, font: Font.regular(20.0), textColor: textColor)
|
||||
switch item.font {
|
||||
case .default:
|
||||
textFont = Font.regular(20.0)
|
||||
case .bold:
|
||||
textFont = Font.semibold(20.0)
|
||||
}
|
||||
self.textNode.attributedText = NSAttributedString(string: item.title, font: textFont, textColor: textColor)
|
||||
|
||||
super.init()
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import UIKit
|
||||
import SwiftSignalKit
|
||||
|
||||
public protocol StatusBarHost {
|
||||
var statusBarFrame: CGRect { get }
|
||||
@@ -8,4 +9,6 @@ public protocol StatusBarHost {
|
||||
|
||||
var keyboardWindow: UIWindow? { get }
|
||||
var keyboardView: UIView? { get }
|
||||
|
||||
var handleVolumeControl: Signal<Bool, NoError> { get }
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ open class TabBarController: ViewController {
|
||||
self.updateSelectedIndex()
|
||||
} else {
|
||||
if let controller = self.currentController {
|
||||
controller.scrollToTop?()
|
||||
controller.scrollToTopWithTabBar?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,7 +351,7 @@ class TabBarNode: ASDisplayNode {
|
||||
if horizontal {
|
||||
backgroundFrame = CGRect(origin: CGPoint(x: originX, y: 2.0), size: backgroundSize)
|
||||
} else {
|
||||
backgroundFrame = CGRect(origin: CGPoint(x: floor(originX + node.frame.width / 2.0) - 3.0 + node.frame.width - backgroundSize.width - 1.0, y: 2.0), size: backgroundSize)
|
||||
backgroundFrame = CGRect(origin: CGPoint(x: floor(originX + node.frame.width / 2.0) - 1.0 + node.frame.width - backgroundSize.width - 1.0, y: 2.0), size: backgroundSize)
|
||||
}
|
||||
transition.updateFrame(node: container.badgeContainerNode, frame: backgroundFrame)
|
||||
container.badgeBackgroundNode.frame = CGRect(origin: CGPoint(), size: backgroundFrame.size)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import Foundation
|
||||
import UIKit.UIGestureRecognizerSubclass
|
||||
import Display
|
||||
|
||||
private class TapLongTapOrDoubleTapGestureRecognizerTimerTarget: NSObject {
|
||||
weak var target: TapLongTapOrDoubleTapGestureRecognizer?
|
||||
|
||||
@@ -78,15 +78,48 @@ private final class TextAlertContentActionNode: HighlightableButtonNode {
|
||||
}
|
||||
}
|
||||
|
||||
final class TextAlertContentNode: AlertContentNode {
|
||||
public enum TextAlertContentActionLayout {
|
||||
case horizontal
|
||||
case vertical
|
||||
}
|
||||
|
||||
public final class TextAlertContentNode: AlertContentNode {
|
||||
private let theme: AlertControllerTheme
|
||||
private let actionLayout: TextAlertContentActionLayout
|
||||
|
||||
private let titleNode: ASTextNode?
|
||||
private let textNode: ASTextNode
|
||||
private let textNode: ImmediateTextNode
|
||||
|
||||
private let actionNodesSeparator: ASDisplayNode
|
||||
private let actionNodes: [TextAlertContentActionNode]
|
||||
private let actionVerticalSeparators: [ASDisplayNode]
|
||||
|
||||
init(theme: AlertControllerTheme, title: NSAttributedString?, text: NSAttributedString, actions: [TextAlertAction]) {
|
||||
public var textAttributeAction: (NSAttributedStringKey, (Any) -> Void)? {
|
||||
didSet {
|
||||
if let (attribute, textAttributeAction) = self.textAttributeAction {
|
||||
self.textNode.highlightAttributeAction = { attributes in
|
||||
if let _ = attributes[attribute] {
|
||||
return attribute
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
self.textNode.tapAttributeAction = { attributes in
|
||||
if let value = attributes[attribute] {
|
||||
textAttributeAction(value)
|
||||
}
|
||||
}
|
||||
self.textNode.linkHighlightColor = self.theme.accentColor.withAlphaComponent(0.5)
|
||||
} else {
|
||||
self.textNode.highlightAttributeAction = nil
|
||||
self.textNode.tapAttributeAction = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public init(theme: AlertControllerTheme, title: NSAttributedString?, text: NSAttributedString, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout) {
|
||||
self.theme = theme
|
||||
self.actionLayout = actionLayout
|
||||
if let title = title {
|
||||
let titleNode = ASTextNode()
|
||||
titleNode.attributedText = title
|
||||
@@ -99,10 +132,16 @@ final class TextAlertContentNode: AlertContentNode {
|
||||
self.titleNode = nil
|
||||
}
|
||||
|
||||
self.textNode = ASTextNode()
|
||||
self.textNode = ImmediateTextNode()
|
||||
self.textNode.maximumNumberOfLines = 0
|
||||
self.textNode.attributedText = text
|
||||
self.textNode.displaysAsynchronously = false
|
||||
self.textNode.isLayerBacked = true
|
||||
self.textNode.isLayerBacked = false
|
||||
if text.length != 0 {
|
||||
if let paragraphStyle = text.attribute(.paragraphStyle, at: 0, effectiveRange: nil) as? NSParagraphStyle {
|
||||
self.textNode.textAlignment = paragraphStyle.alignment
|
||||
}
|
||||
}
|
||||
|
||||
self.actionNodesSeparator = ASDisplayNode()
|
||||
self.actionNodesSeparator.isLayerBacked = true
|
||||
@@ -141,27 +180,40 @@ final class TextAlertContentNode: AlertContentNode {
|
||||
}
|
||||
}
|
||||
|
||||
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
override public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0)
|
||||
|
||||
var titleSize: CGSize?
|
||||
if let titleNode = self.titleNode {
|
||||
titleSize = titleNode.measure(CGSize(width: size.width - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude))
|
||||
}
|
||||
let textSize = self.textNode.measure(CGSize(width: size.width - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude))
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: size.width - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude))
|
||||
|
||||
let actionsHeight: CGFloat = 44.0
|
||||
let actionButtonHeight: CGFloat = 44.0
|
||||
|
||||
var minActionsWidth: CGFloat = 0.0
|
||||
let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
|
||||
let actionTitleInsets: CGFloat = 8.0
|
||||
for actionNode in self.actionNodes {
|
||||
let actionTitleSize = actionNode.titleNode.measure(CGSize(width: maxActionWidth, height: actionsHeight))
|
||||
minActionsWidth += actionTitleSize.width + actionTitleInsets
|
||||
let actionTitleSize = actionNode.titleNode.measure(CGSize(width: maxActionWidth, height: actionButtonHeight))
|
||||
switch self.actionLayout {
|
||||
case .horizontal:
|
||||
minActionsWidth += actionTitleSize.width + actionTitleInsets
|
||||
case .vertical:
|
||||
minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets)
|
||||
}
|
||||
}
|
||||
|
||||
let resultSize: CGSize
|
||||
|
||||
var actionsHeight: CGFloat = 0.0
|
||||
switch self.actionLayout {
|
||||
case .horizontal:
|
||||
actionsHeight = actionButtonHeight
|
||||
case .vertical:
|
||||
actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
|
||||
}
|
||||
|
||||
if let titleNode = titleNode, let titleSize = titleSize {
|
||||
var contentWidth = max(max(titleSize.width, textSize.width), minActionsWidth)
|
||||
contentWidth = max(contentWidth, 150.0)
|
||||
@@ -193,20 +245,37 @@ final class TextAlertContentNode: AlertContentNode {
|
||||
for actionNode in self.actionNodes {
|
||||
if separatorIndex >= 0 {
|
||||
let separatorNode = self.actionVerticalSeparators[separatorIndex]
|
||||
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel)))
|
||||
switch self.actionLayout {
|
||||
case .horizontal:
|
||||
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel)))
|
||||
case .vertical:
|
||||
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||
}
|
||||
}
|
||||
separatorIndex += 1
|
||||
|
||||
let currentActionWidth: CGFloat
|
||||
if nodeIndex == self.actionNodes.count - 1 {
|
||||
currentActionWidth = resultSize.width - actionOffset
|
||||
} else {
|
||||
currentActionWidth = actionWidth
|
||||
switch self.actionLayout {
|
||||
case .horizontal:
|
||||
if nodeIndex == self.actionNodes.count - 1 {
|
||||
currentActionWidth = resultSize.width - actionOffset
|
||||
} else {
|
||||
currentActionWidth = actionWidth
|
||||
}
|
||||
case .vertical:
|
||||
currentActionWidth = resultSize.width
|
||||
}
|
||||
|
||||
let actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionsHeight))
|
||||
let actionNodeFrame: CGRect
|
||||
switch self.actionLayout {
|
||||
case .horizontal:
|
||||
actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
|
||||
actionOffset += currentActionWidth
|
||||
case .vertical:
|
||||
actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
|
||||
actionOffset += actionButtonHeight
|
||||
}
|
||||
|
||||
actionOffset += currentActionWidth
|
||||
transition.updateFrame(node: actionNode, frame: actionNodeFrame)
|
||||
|
||||
nodeIndex += 1
|
||||
@@ -216,18 +285,18 @@ final class TextAlertContentNode: AlertContentNode {
|
||||
}
|
||||
}
|
||||
|
||||
public func textAlertController(theme: AlertControllerTheme, title: NSAttributedString?, text: NSAttributedString, actions: [TextAlertAction]) -> AlertController {
|
||||
return AlertController(theme: theme, contentNode: TextAlertContentNode(theme: theme, title: title, text: text, actions: actions))
|
||||
public func textAlertController(theme: AlertControllerTheme, title: NSAttributedString?, text: NSAttributedString, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal) -> AlertController {
|
||||
return AlertController(theme: theme, contentNode: TextAlertContentNode(theme: theme, title: title, text: text, actions: actions, actionLayout: actionLayout))
|
||||
}
|
||||
|
||||
public func standardTextAlertController(theme: AlertControllerTheme, title: String?, text: String, actions: [TextAlertAction]) -> AlertController {
|
||||
public func standardTextAlertController(theme: AlertControllerTheme, title: String?, text: String, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal) -> AlertController {
|
||||
var dismissImpl: (() -> Void)?
|
||||
let controller = AlertController(theme: theme, contentNode: TextAlertContentNode(theme: theme, title: title != nil ? NSAttributedString(string: title!, font: Font.medium(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) : nil, text: NSAttributedString(string: text, font: title == nil ? Font.semibold(17.0) : Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center), actions: actions.map { action in
|
||||
return TextAlertAction(type: action.type, title: action.title, action: {
|
||||
dismissImpl?()
|
||||
action.action()
|
||||
})
|
||||
}))
|
||||
}, actionLayout: actionLayout))
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismissAnimated()
|
||||
}
|
||||
|
||||
@@ -7,11 +7,15 @@ public final class TextFieldNodeView: UITextField {
|
||||
var fixOffset: Bool = true
|
||||
|
||||
override public func editingRect(forBounds bounds: CGRect) -> CGRect {
|
||||
return bounds.offsetBy(dx: 0.0, dy: -UIScreenPixel)
|
||||
return bounds.offsetBy(dx: 0.0, dy: 0.0).integral
|
||||
}
|
||||
|
||||
override public func textRect(forBounds bounds: CGRect) -> CGRect {
|
||||
return bounds.offsetBy(dx: 0.0, dy: 0.0).integral
|
||||
}
|
||||
|
||||
override public func placeholderRect(forBounds bounds: CGRect) -> CGRect {
|
||||
return self.editingRect(forBounds: bounds.offsetBy(dx: 0.0, dy: self.fixOffset ? 0.0 : -UIScreenPixel))
|
||||
return self.editingRect(forBounds: bounds.offsetBy(dx: 0.0, dy: self.fixOffset ? 0.0 : 0.0))
|
||||
}
|
||||
|
||||
override public func deleteBackward() {
|
||||
|
||||
@@ -273,7 +273,7 @@ public class TextNode: ASDisplayNode {
|
||||
cutoutEnabled = true
|
||||
}
|
||||
|
||||
let firstLineOffset = floorToScreenPixels(fontLineSpacing * 2.0)
|
||||
let firstLineOffset = floorToScreenPixels(fontDescent)
|
||||
|
||||
var first = true
|
||||
while true {
|
||||
|
||||
@@ -2,11 +2,34 @@ import Foundation
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
|
||||
private enum SourceAndRect {
|
||||
case node(() -> (ASDisplayNode, CGRect)?)
|
||||
case view(() -> (UIView, CGRect)?)
|
||||
|
||||
func globalRect() -> CGRect? {
|
||||
switch self {
|
||||
case let .node(node):
|
||||
if let (sourceNode, sourceRect) = node() {
|
||||
return sourceNode.view.convert(sourceRect, to: nil)
|
||||
}
|
||||
case let .view(view):
|
||||
if let (sourceView, sourceRect) = view() {
|
||||
return sourceView.convert(sourceRect, to: nil)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public final class TooltipControllerPresentationArguments {
|
||||
fileprivate let sourceNodeAndRect: () -> (ASDisplayNode, CGRect)?
|
||||
fileprivate let sourceAndRect: SourceAndRect
|
||||
|
||||
public init(sourceNodeAndRect: @escaping () -> (ASDisplayNode, CGRect)?) {
|
||||
self.sourceNodeAndRect = sourceNodeAndRect
|
||||
self.sourceAndRect = .node(sourceNodeAndRect)
|
||||
}
|
||||
|
||||
public init(sourceViewAndRect: @escaping () -> (UIView, CGRect)?) {
|
||||
self.sourceAndRect = .view(sourceViewAndRect)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,15 +54,17 @@ public final class TooltipController: ViewController {
|
||||
}
|
||||
|
||||
private let timeout: Double
|
||||
private let dismissByTapOutside: Bool
|
||||
private var timeoutTimer: SwiftSignalKit.Timer?
|
||||
|
||||
private var layout: ContainerViewLayout?
|
||||
|
||||
public var dismissed: (() -> Void)?
|
||||
|
||||
public init(text: String, timeout: Double = 1.0) {
|
||||
public init(text: String, timeout: Double = 1.0, dismissByTapOutside: Bool = false) {
|
||||
self.text = text
|
||||
self.timeout = timeout
|
||||
self.dismissByTapOutside = dismissByTapOutside
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
}
|
||||
@@ -52,10 +77,10 @@ public final class TooltipController: ViewController {
|
||||
self.timeoutTimer?.invalidate()
|
||||
}
|
||||
|
||||
open override func loadDisplayNode() {
|
||||
public override func loadDisplayNode() {
|
||||
self.displayNode = TooltipControllerNode(text: self.text, dismiss: { [weak self] in
|
||||
self?.dismiss()
|
||||
})
|
||||
}, dismissByTapOutside: self.dismissByTapOutside)
|
||||
self.displayNodeDidLoad()
|
||||
}
|
||||
|
||||
@@ -66,7 +91,7 @@ public final class TooltipController: ViewController {
|
||||
self.beginTimeout()
|
||||
}
|
||||
|
||||
override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
if self.layout != nil && self.layout! != layout {
|
||||
@@ -77,8 +102,8 @@ public final class TooltipController: ViewController {
|
||||
} else {
|
||||
self.layout = layout
|
||||
|
||||
if let presentationArguments = self.presentationArguments as? TooltipControllerPresentationArguments, let (sourceNode, sourceRect) = presentationArguments.sourceNodeAndRect() {
|
||||
self.controllerNode.sourceRect = sourceNode.view.convert(sourceRect, to: nil)
|
||||
if let presentationArguments = self.presentationArguments as? TooltipControllerPresentationArguments, let sourceRect = presentationArguments.sourceAndRect.globalRect() {
|
||||
self.controllerNode.sourceRect = sourceRect
|
||||
} else {
|
||||
self.controllerNode.sourceRect = nil
|
||||
}
|
||||
@@ -87,7 +112,7 @@ public final class TooltipController: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
open override func viewWillAppear(_ animated: Bool) {
|
||||
public override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
self.controllerNode.animateIn()
|
||||
|
||||
@@ -10,12 +10,16 @@ final class TooltipControllerNode: ASDisplayNode {
|
||||
private let containerNode: ContextMenuContainerNode
|
||||
private let textNode: ImmediateTextNode
|
||||
|
||||
private let dismissByTapOutside: Bool
|
||||
|
||||
var sourceRect: CGRect?
|
||||
var arrowOnBottom: Bool = true
|
||||
|
||||
private var dismissedByTouchOutside = false
|
||||
|
||||
init(text: String, dismiss: @escaping () -> Void) {
|
||||
init(text: String, dismiss: @escaping () -> Void, dismissByTapOutside: Bool) {
|
||||
self.dismissByTapOutside = dismissByTapOutside
|
||||
|
||||
self.containerNode = ContextMenuContainerNode()
|
||||
self.containerNode.backgroundColor = UIColor(white: 0.0, alpha: 0.8)
|
||||
|
||||
@@ -23,6 +27,7 @@ final class TooltipControllerNode: ASDisplayNode {
|
||||
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white, paragraphAlignment: .center)
|
||||
self.textNode.isLayerBacked = true
|
||||
self.textNode.displaysAsynchronously = false
|
||||
self.textNode.maximumNumberOfLines = 0
|
||||
|
||||
self.dismiss = dismiss
|
||||
|
||||
@@ -103,7 +108,7 @@ final class TooltipControllerNode: ASDisplayNode {
|
||||
eventIsPresses = event.type == .presses
|
||||
}
|
||||
if event.type == .touches || eventIsPresses {
|
||||
if self.containerNode.frame.contains(point) {
|
||||
if self.containerNode.frame.contains(point) || self.dismissByTapOutside {
|
||||
if !self.dismissedByTouchOutside {
|
||||
self.dismissedByTouchOutside = true
|
||||
self.dismiss()
|
||||
|
||||
@@ -130,6 +130,7 @@ open class ViewControllerPresentationArguments {
|
||||
}
|
||||
}
|
||||
}
|
||||
public var scrollToTopWithTabBar: (() -> Void)?
|
||||
|
||||
private func updateScrollToTopView() {
|
||||
if self.scrollToTop != nil {
|
||||
@@ -165,6 +166,10 @@ open class ViewControllerPresentationArguments {
|
||||
}
|
||||
self.navigationBar?.item = self.navigationItem
|
||||
self.automaticallyAdjustsScrollViewInsets = false
|
||||
|
||||
self.scrollToTopWithTabBar = { [weak self] in
|
||||
self?.scrollToTop?()
|
||||
}
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
|
||||
@@ -2,6 +2,7 @@ import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import MediaPlayer
|
||||
import SwiftSignalKit
|
||||
|
||||
private let volumeNotificationKey = "AVSystemController_SystemVolumeDidChangeNotification"
|
||||
private let volumeParameterKey = "AVSystemController_AudioVolumeNotificationParameter"
|
||||
@@ -13,7 +14,9 @@ final class VolumeControlStatusBar: UIView {
|
||||
|
||||
var valueChanged: ((Float, Float) -> Void)?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
private var disposable: Disposable?
|
||||
|
||||
init(frame: CGRect, shouldBeVisible: Signal<Bool, NoError>) {
|
||||
self.control = MPVolumeView(frame: CGRect(origin: CGPoint(), size: CGSize(width: 100.0, height: 20.0)))
|
||||
self.currentValue = AVAudioSession.sharedInstance().outputVolume
|
||||
|
||||
@@ -25,10 +28,26 @@ final class VolumeControlStatusBar: UIView {
|
||||
if let volume = notification.userInfo?[volumeParameterKey] as? Float {
|
||||
let previous = strongSelf.currentValue
|
||||
strongSelf.currentValue = volume
|
||||
strongSelf.valueChanged?(previous, volume)
|
||||
if strongSelf.control.superview != nil {
|
||||
strongSelf.valueChanged?(previous, volume)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
self.disposable = (shouldBeVisible
|
||||
|> deliverOnMainQueue).start(next: { [weak self] value in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if value {
|
||||
if strongSelf.control.superview == nil {
|
||||
strongSelf.addSubview(strongSelf.control)
|
||||
}
|
||||
} else {
|
||||
strongSelf.control.removeFromSuperview()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
@@ -39,6 +58,7 @@ final class VolumeControlStatusBar: UIView {
|
||||
if let observer = self.observer {
|
||||
NotificationCenter.default.removeObserver(observer)
|
||||
}
|
||||
self.disposable?.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ private func containedLayoutForWindowLayout(_ layout: WindowLayout) -> Container
|
||||
|
||||
if layout.size.width.isEqual(to: 320.0) {
|
||||
standardInputHeight = 216.0
|
||||
predictiveHeight = 42.0
|
||||
predictiveHeight = 37.0
|
||||
} else if layout.size.width.isEqual(to: 375.0) {
|
||||
standardInputHeight = 291.0
|
||||
predictiveHeight = 42.0
|
||||
@@ -342,7 +342,7 @@ public class Window1 {
|
||||
public init(hostView: WindowHostView, statusBarHost: StatusBarHost?) {
|
||||
self.hostView = hostView
|
||||
|
||||
self.volumeControlStatusBar = VolumeControlStatusBar(frame: CGRect(origin: CGPoint(x: 0.0, y: -20.0), size: CGSize(width: 100.0, height: 20.0)))
|
||||
self.volumeControlStatusBar = VolumeControlStatusBar(frame: CGRect(origin: CGPoint(x: 0.0, y: -20.0), size: CGSize(width: 100.0, height: 20.0)), shouldBeVisible: statusBarHost?.handleVolumeControl ?? .single(false))
|
||||
self.volumeControlStatusBarNode = VolumeControlStatusBarNode()
|
||||
|
||||
self.statusBarHost = statusBarHost
|
||||
|
||||
Reference in New Issue
Block a user