no message

This commit is contained in:
Peter
2018-06-16 20:00:34 +03:00
parent eb412cdc3d
commit 3dfd2af198
25 changed files with 276 additions and 64 deletions

View File

@@ -12,7 +12,7 @@
<key>DisplayMac.xcscheme</key>
<dict>
<key>orderHint</key>
<integer>24</integer>
<integer>25</integer>
</dict>
<key>DisplayTests.xcscheme</key>
<dict>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
import Foundation
import AsyncDisplayKit
import Display
private enum CornerType {
case topLeft

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -52,7 +52,7 @@ open class TabBarController: ViewController {
self.updateSelectedIndex()
} else {
if let controller = self.currentController {
controller.scrollToTop?()
controller.scrollToTopWithTabBar?()
}
}
}

View File

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

View File

@@ -1,6 +1,5 @@
import Foundation
import UIKit.UIGestureRecognizerSubclass
import Display
private class TapLongTapOrDoubleTapGestureRecognizerTimerTarget: NSObject {
weak var target: TapLongTapOrDoubleTapGestureRecognizer?

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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