iOS 13 style segmented control

iPad UI improvements
This commit is contained in:
Ilya Laktyushin 2019-09-07 09:13:07 +03:00
parent e99a9e8d99
commit c8837e04f8
52 changed files with 1483 additions and 265 deletions

View File

@ -274,6 +274,9 @@
<FileRef <FileRef
location = "group:submodules/RadialStatusNode/RadialStatusNode_Xcode.xcodeproj"> location = "group:submodules/RadialStatusNode/RadialStatusNode_Xcode.xcodeproj">
</FileRef> </FileRef>
<FileRef
location = "group:submodules/SegmentedControlNode/SegmentedControlNode_Xcode.xcodeproj">
</FileRef>
<FileRef <FileRef
location = "group:submodules/LiveLocationTimerNode/LiveLocationTimerNode_Xcode.xcodeproj"> location = "group:submodules/LiveLocationTimerNode/LiveLocationTimerNode_Xcode.xcodeproj">
</FileRef> </FileRef>

View File

@ -45,7 +45,7 @@ public final class CallListController: ViewController {
self.mode = mode self.mode = mode
self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.segmentedTitleView = ItemListControllerSegmentedTitleView(segments: [self.presentationData.strings.Calls_All, self.presentationData.strings.Calls_Missed], index: 0, color: self.presentationData.theme.rootController.navigationBar.accentTextColor) self.segmentedTitleView = ItemListControllerSegmentedTitleView(theme: self.presentationData.theme, segments: [self.presentationData.strings.Calls_All, self.presentationData.strings.Calls_Missed], selectedIndex: 0)
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
@ -101,7 +101,7 @@ public final class CallListController: ViewController {
private func updateThemeAndStrings() { private func updateThemeAndStrings() {
let index = self.segmentedTitleView.index let index = self.segmentedTitleView.index
self.segmentedTitleView.segments = [self.presentationData.strings.Calls_All, self.presentationData.strings.Calls_Missed] self.segmentedTitleView.segments = [self.presentationData.strings.Calls_All, self.presentationData.strings.Calls_Missed]
self.segmentedTitleView.color = self.presentationData.theme.rootController.navigationBar.accentTextColor self.segmentedTitleView.theme = self.presentationData.theme
self.segmentedTitleView.index = index self.segmentedTitleView.index = index
self.tabBarItem.title = self.presentationData.strings.Calls_TabTitle self.tabBarItem.title = self.presentationData.strings.Calls_TabTitle

View File

@ -151,9 +151,8 @@ public func generateImage(_ size: CGSize, opaque: Bool = false, scale: CGFloat?
guard let provider = CGDataProvider(dataInfo: bytes, data: bytes, size: length, releaseData: { bytes, _, _ in guard let provider = CGDataProvider(dataInfo: bytes, data: bytes, size: length, releaseData: { bytes, _, _ in
free(bytes) free(bytes)
}) }) else {
else { return nil
return nil
} }
let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | (opaque ? CGImageAlphaInfo.noneSkipFirst.rawValue : CGImageAlphaInfo.premultipliedFirst.rawValue)) let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | (opaque ? CGImageAlphaInfo.noneSkipFirst.rawValue : CGImageAlphaInfo.premultipliedFirst.rawValue))

View File

@ -6,11 +6,12 @@ import TelegramCore
import TelegramPresentationData import TelegramPresentationData
import AccountContext import AccountContext
import ChatListUI import ChatListUI
import SegmentedControlNode
final class HashtagSearchControllerNode: ASDisplayNode { final class HashtagSearchControllerNode: ASDisplayNode {
private let toolbarBackgroundNode: ASDisplayNode private let toolbarBackgroundNode: ASDisplayNode
private let toolbarSeparatorNode: ASDisplayNode private let toolbarSeparatorNode: ASDisplayNode
private let segmentedControl: UISegmentedControl private let segmentedControlNode: SegmentedControlNode
let listNode: ListView let listNode: ListView
var chatController: ChatController? var chatController: ChatController?
@ -35,9 +36,11 @@ final class HashtagSearchControllerNode: ASDisplayNode {
self.toolbarSeparatorNode = ASDisplayNode() self.toolbarSeparatorNode = ASDisplayNode()
self.toolbarSeparatorNode.backgroundColor = theme.rootController.navigationBar.separatorColor self.toolbarSeparatorNode.backgroundColor = theme.rootController.navigationBar.separatorColor
self.segmentedControl = UISegmentedControl(items: [peer?.displayTitle ?? "", strings.HashtagSearch_AllChats]) let items = [
self.segmentedControl.tintColor = theme.rootController.navigationBar.accentTextColor peer?.displayTitle ?? "",
self.segmentedControl.selectedSegmentIndex = 0 strings.HashtagSearch_AllChats
]
self.segmentedControlNode = SegmentedControlNode(theme: SegmentedControlTheme(theme: theme), items: items.map { SegmentedControlItem(title: $0) }, selectedIndex: 0)
if let peer = peer { if let peer = peer {
self.chatController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(peer.id), subject: nil, botStart: nil, mode: .inline) self.chatController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(peer.id), subject: nil, botStart: nil, mode: .inline)
@ -56,7 +59,17 @@ final class HashtagSearchControllerNode: ASDisplayNode {
self.addSubnode(self.listNode) self.addSubnode(self.listNode)
self.listNode.isHidden = true self.listNode.isHidden = true
self.segmentedControl.addTarget(self, action: #selector(self.indexChanged), for: .valueChanged) self.segmentedControlNode.selectedIndexChanged = { [weak self] index in
if let strongSelf = self {
if index == 0 {
strongSelf.chatController?.displayNode.isHidden = false
strongSelf.listNode.isHidden = true
} else {
strongSelf.chatController?.displayNode.isHidden = true
strongSelf.listNode.isHidden = false
}
}
}
} }
func enqueueTransition(_ transition: ChatListSearchContainerTransition, firstTime: Bool) { func enqueueTransition(_ transition: ChatListSearchContainerTransition, firstTime: Bool) {
@ -84,8 +97,7 @@ final class HashtagSearchControllerNode: ASDisplayNode {
if self.chatController != nil && self.toolbarBackgroundNode.supernode == nil { if self.chatController != nil && self.toolbarBackgroundNode.supernode == nil {
self.addSubnode(self.toolbarBackgroundNode) self.addSubnode(self.toolbarBackgroundNode)
self.addSubnode(self.toolbarSeparatorNode) self.addSubnode(self.toolbarSeparatorNode)
self.addSubnode(self.segmentedControlNode)
self.view.addSubview(self.segmentedControl)
} }
var insets = layout.insets(options: [.input]) var insets = layout.insets(options: [.input])
@ -97,10 +109,8 @@ final class HashtagSearchControllerNode: ASDisplayNode {
transition.updateFrame(node: self.toolbarBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelY), size: CGSize(width: layout.size.width, height: toolbarHeight))) transition.updateFrame(node: self.toolbarBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelY), size: CGSize(width: layout.size.width, height: toolbarHeight)))
transition.updateFrame(node: self.toolbarSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelY + toolbarHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel))) transition.updateFrame(node: self.toolbarSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelY + toolbarHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel)))
var controlSize = self.segmentedControl.sizeThatFits(layout.size) let controlSize = self.segmentedControlNode.updateLayout(.stretchToFill(width: layout.size.width - 14.0 * 2.0), transition: transition)
controlSize.width = layout.size.width - 14.0 * 2.0 transition.updateFrame(node: self.segmentedControlNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - controlSize.width) / 2.0), y: panelY + floor((toolbarHeight - controlSize.height) / 2.0)), size: controlSize))
transition.updateFrame(view: self.segmentedControl, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - controlSize.width) / 2.0), y: panelY + floor((toolbarHeight - controlSize.height) / 2.0)), size: controlSize))
if let chatController = self.chatController { if let chatController = self.chatController {
insets.top += toolbarHeight - 4.0 insets.top += toolbarHeight - 4.0
@ -146,21 +156,11 @@ final class HashtagSearchControllerNode: ASDisplayNode {
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
if !hasValidLayout { if !self.hasValidLayout {
hasValidLayout = true self.hasValidLayout = true
while !self.enqueuedTransitions.isEmpty { while !self.enqueuedTransitions.isEmpty {
self.dequeueTransition() self.dequeueTransition()
} }
} }
} }
@objc private func indexChanged() {
if self.segmentedControl.selectedSegmentIndex == 0 {
self.chatController?.displayNode.isHidden = false
self.listNode.isHidden = true
} else {
self.chatController?.displayNode.isHidden = true
self.listNode.isHidden = false
}
}
} }

View File

@ -7,6 +7,7 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
09CE5B922322985500743FF4 /* ItemListMaskAccessory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09CE5B912322985500743FF4 /* ItemListMaskAccessory.swift */; };
D060182122F35C2300796784 /* ProgressNavigationButtonNode.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D060182022F35C2300796784 /* ProgressNavigationButtonNode.framework */; }; D060182122F35C2300796784 /* ProgressNavigationButtonNode.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D060182022F35C2300796784 /* ProgressNavigationButtonNode.framework */; };
D060185322F35E1F00796784 /* ItemListMultilineInputItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D060184922F35E1E00796784 /* ItemListMultilineInputItem.swift */; }; D060185322F35E1F00796784 /* ItemListMultilineInputItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D060184922F35E1E00796784 /* ItemListMultilineInputItem.swift */; };
D060185422F35E1F00796784 /* ItemListTextWithLabelItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D060184A22F35E1E00796784 /* ItemListTextWithLabelItem.swift */; }; D060185422F35E1F00796784 /* ItemListTextWithLabelItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D060184A22F35E1E00796784 /* ItemListTextWithLabelItem.swift */; };
@ -50,6 +51,7 @@
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
09CE5B912322985500743FF4 /* ItemListMaskAccessory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListMaskAccessory.swift; sourceTree = "<group>"; };
D060182022F35C2300796784 /* ProgressNavigationButtonNode.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ProgressNavigationButtonNode.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D060182022F35C2300796784 /* ProgressNavigationButtonNode.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ProgressNavigationButtonNode.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D060184922F35E1E00796784 /* ItemListMultilineInputItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemListMultilineInputItem.swift; sourceTree = "<group>"; }; D060184922F35E1E00796784 /* ItemListMultilineInputItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemListMultilineInputItem.swift; sourceTree = "<group>"; };
D060184A22F35E1E00796784 /* ItemListTextWithLabelItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemListTextWithLabelItem.swift; sourceTree = "<group>"; }; D060184A22F35E1E00796784 /* ItemListTextWithLabelItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemListTextWithLabelItem.swift; sourceTree = "<group>"; };
@ -168,6 +170,7 @@
D0D3286322F3366500D07EE2 /* ItemListControllerSearch.swift */, D0D3286322F3366500D07EE2 /* ItemListControllerSearch.swift */,
D0D3286122F3366500D07EE2 /* ItemListControllerSegmentedTitleView.swift */, D0D3286122F3366500D07EE2 /* ItemListControllerSegmentedTitleView.swift */,
D0D3289922F345C500D07EE2 /* ItemListItem.swift */, D0D3289922F345C500D07EE2 /* ItemListItem.swift */,
09CE5B912322985500743FF4 /* ItemListMaskAccessory.swift */,
D060186022F35F6B00796784 /* ItemListRevealOptionsNode.swift */, D060186022F35F6B00796784 /* ItemListRevealOptionsNode.swift */,
D06018BA22F3663900796784 /* ItemListEditableDeleteControlNode.swift */, D06018BA22F3663900796784 /* ItemListEditableDeleteControlNode.swift */,
D06018B822F3663800796784 /* ItemListEditableReorderControlNode.swift */, D06018B822F3663800796784 /* ItemListEditableReorderControlNode.swift */,
@ -294,6 +297,7 @@
D060185422F35E1F00796784 /* ItemListTextWithLabelItem.swift in Sources */, D060185422F35E1F00796784 /* ItemListTextWithLabelItem.swift in Sources */,
D0C9BFB222FE327700FAB518 /* ItemListPlaceholderItem.swift in Sources */, D0C9BFB222FE327700FAB518 /* ItemListPlaceholderItem.swift in Sources */,
D0D3286722F3366600D07EE2 /* ItemListController.swift in Sources */, D0D3286722F3366600D07EE2 /* ItemListController.swift in Sources */,
09CE5B922322985500743FF4 /* ItemListMaskAccessory.swift in Sources */,
D0D3286822F3366600D07EE2 /* ItemListControllerSearch.swift in Sources */, D0D3286822F3366600D07EE2 /* ItemListControllerSearch.swift in Sources */,
D060185722F35E1F00796784 /* ItemListSwitchItem.swift in Sources */, D060185722F35E1F00796784 /* ItemListSwitchItem.swift in Sources */,
D0D3286622F3366600D07EE2 /* ItemListControllerSegmentedTitleView.swift in Sources */, D0D3286622F3366600D07EE2 /* ItemListControllerSegmentedTitleView.swift in Sources */,

View File

@ -283,7 +283,7 @@ open class ItemListController<Entry: ItemListNodeEntry>: ViewController, KeyShor
if let segmentedTitleView = strongSelf.segmentedTitleView, segmentedTitleView.segments == sections { if let segmentedTitleView = strongSelf.segmentedTitleView, segmentedTitleView.segments == sections {
segmentedTitleView.index = index segmentedTitleView.index = index
} else { } else {
let segmentedTitleView = ItemListControllerSegmentedTitleView(segments: sections, index: index, color: controllerState.theme.rootController.navigationBar.accentTextColor) let segmentedTitleView = ItemListControllerSegmentedTitleView(theme: controllerState.theme, segments: sections, selectedIndex: index)
strongSelf.segmentedTitleView = segmentedTitleView strongSelf.segmentedTitleView = segmentedTitleView
strongSelf.navigationItem.titleView = strongSelf.segmentedTitleView strongSelf.navigationItem.titleView = strongSelf.segmentedTitleView
segmentedTitleView.indexUpdated = { index in segmentedTitleView.indexUpdated = { index in
@ -406,7 +406,7 @@ open class ItemListController<Entry: ItemListNodeEntry>: ViewController, KeyShor
strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: strongSelf.theme), strings: NavigationBarStrings(presentationStrings: strongSelf.strings))) strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: strongSelf.theme), strings: NavigationBarStrings(presentationStrings: strongSelf.strings)))
strongSelf.statusBar.statusBarStyle = strongSelf.theme.rootController.statusBarStyle.style strongSelf.statusBar.statusBarStyle = strongSelf.theme.rootController.statusBarStyle.style
strongSelf.segmentedTitleView?.color = controllerState.theme.rootController.navigationBar.accentTextColor strongSelf.segmentedTitleView?.theme = controllerState.theme
var items = strongSelf.navigationItem.rightBarButtonItems ?? [] var items = strongSelf.navigationItem.rightBarButtonItems ?? []
for i in 0 ..< strongSelf.rightNavigationButtonTitleAndStyle.count { for i in 0 ..< strongSelf.rightNavigationButtonTitleAndStyle.count {

View File

@ -162,6 +162,8 @@ open class ItemListControllerNode<Entry: ItemListNodeEntry>: ASDisplayNode, UISc
private let navigationBar: NavigationBar private let navigationBar: NavigationBar
public let listNode: ListView public let listNode: ListView
private let leftOverlayNode: ASDisplayNode
private let rightOverlayNode: ASDisplayNode
private var emptyStateItem: ItemListControllerEmptyStateItem? private var emptyStateItem: ItemListControllerEmptyStateItem?
private var emptyStateNode: ItemListControllerEmptyStateItemNode? private var emptyStateNode: ItemListControllerEmptyStateItemNode?
@ -204,6 +206,8 @@ open class ItemListControllerNode<Entry: ItemListNodeEntry>: ASDisplayNode, UISc
self.updateNavigationOffset = updateNavigationOffset self.updateNavigationOffset = updateNavigationOffset
self.listNode = ListView() self.listNode = ListView()
self.leftOverlayNode = ASDisplayNode()
self.rightOverlayNode = ASDisplayNode()
super.init() super.init()
@ -351,15 +355,45 @@ open class ItemListControllerNode<Entry: ItemListNodeEntry>: ASDisplayNode, UISc
var insets = layout.insets(options: [.input]) var insets = layout.insets(options: [.input])
insets.top += navigationBarHeight insets.top += navigationBarHeight
insets.left += layout.safeInsets.left
insets.right += layout.safeInsets.right var addedInsets: UIEdgeInsets?
if case .tablet = layout.deviceMetrics.type, layout.size.width > 460.0 {
let inset = max(20.0, floor((layout.size.width - 674.0) / 2.0))
insets.left += inset
insets.right += inset
addedInsets = UIEdgeInsets(top: 0.0, left: inset, bottom: 0.0, right: inset)
if self.leftOverlayNode.supernode == nil {
self.addSubnode(self.leftOverlayNode)
}
if self.rightOverlayNode.supernode == nil {
self.addSubnode(self.rightOverlayNode)
}
} else {
insets.left += layout.safeInsets.left
insets.right += layout.safeInsets.right
if self.leftOverlayNode.supernode != nil {
self.leftOverlayNode.removeFromSupernode()
}
if self.rightOverlayNode.supernode != nil {
self.rightOverlayNode.removeFromSupernode()
}
}
self.listNode.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height) self.listNode.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)
self.listNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0) self.listNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0)
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, duration: duration, curve: listViewCurve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, duration: duration, curve: listViewCurve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
self.leftOverlayNode.frame = CGRect(x: 0.0, y: insets.top + 1.0, width: insets.left, height: layout.size.height - insets.top)
self.rightOverlayNode.frame = CGRect(x: layout.size.width - insets.right, y: insets.top + 1.0, width: insets.right, height: layout.size.height - insets.top)
if let emptyStateNode = self.emptyStateNode { if let emptyStateNode = self.emptyStateNode {
var layout = layout
if let addedInsets = addedInsets {
layout = layout.addedInsets(insets: addedInsets)
}
emptyStateNode.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, transition: transition) emptyStateNode.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, transition: transition)
} }
@ -401,9 +435,13 @@ open class ItemListControllerNode<Entry: ItemListNodeEntry>: ASDisplayNode, UISc
case .plain: case .plain:
self.backgroundColor = transition.theme.list.plainBackgroundColor self.backgroundColor = transition.theme.list.plainBackgroundColor
self.listNode.backgroundColor = transition.theme.list.plainBackgroundColor self.listNode.backgroundColor = transition.theme.list.plainBackgroundColor
self.leftOverlayNode.backgroundColor = transition.theme.list.plainBackgroundColor
self.rightOverlayNode.backgroundColor = transition.theme.list.plainBackgroundColor
case .blocks: case .blocks:
self.backgroundColor = transition.theme.list.blocksBackgroundColor self.backgroundColor = transition.theme.list.blocksBackgroundColor
self.listNode.backgroundColor = transition.theme.list.blocksBackgroundColor self.listNode.backgroundColor = transition.theme.list.blocksBackgroundColor
self.leftOverlayNode.backgroundColor = transition.theme.list.blocksBackgroundColor
self.rightOverlayNode.backgroundColor = transition.theme.list.blocksBackgroundColor
} }
} }
} }
@ -416,9 +454,13 @@ open class ItemListControllerNode<Entry: ItemListNodeEntry>: ASDisplayNode, UISc
case .plain: case .plain:
self.backgroundColor = transition.theme.list.plainBackgroundColor self.backgroundColor = transition.theme.list.plainBackgroundColor
self.listNode.backgroundColor = transition.theme.list.plainBackgroundColor self.listNode.backgroundColor = transition.theme.list.plainBackgroundColor
self.leftOverlayNode.backgroundColor = transition.theme.list.plainBackgroundColor
self.rightOverlayNode.backgroundColor = transition.theme.list.plainBackgroundColor
case .blocks: case .blocks:
self.backgroundColor = transition.theme.list.blocksBackgroundColor self.backgroundColor = transition.theme.list.blocksBackgroundColor
self.listNode.backgroundColor = transition.theme.list.blocksBackgroundColor self.listNode.backgroundColor = transition.theme.list.blocksBackgroundColor
self.leftOverlayNode.backgroundColor = transition.theme.list.blocksBackgroundColor
self.rightOverlayNode.backgroundColor = transition.theme.list.blocksBackgroundColor
} }
} }
} }
@ -553,10 +595,6 @@ open class ItemListControllerNode<Entry: ItemListNodeEntry>: ASDisplayNode, UISc
if itemTag.isEqual(to: ensureVisibleItemTag) { if itemTag.isEqual(to: ensureVisibleItemTag) {
if let itemNode = itemNode as? ListViewItemNode { if let itemNode = itemNode as? ListViewItemNode {
strongSelf.listNode.ensureItemNodeVisible(itemNode) strongSelf.listNode.ensureItemNodeVisible(itemNode)
/*itemNode.setHighlighted(true, at: CGPoint(), animated: false)
Queue.mainQueue().after(1.0, {
itemNode.setHighlighted(false, at: CGPoint(), animated: true)
})*/
applied = true applied = true
} }
} }
@ -613,21 +651,13 @@ open class ItemListControllerNode<Entry: ItemListNodeEntry>: ASDisplayNode, UISc
open func scrollViewDidScroll(_ scrollView: UIScrollView) { open func scrollViewDidScroll(_ scrollView: UIScrollView) {
let distanceFromEquilibrium = scrollView.contentOffset.y - scrollView.contentSize.height / 3.0 let distanceFromEquilibrium = scrollView.contentOffset.y - scrollView.contentSize.height / 3.0
//let transition = 1.0 - min(1.0, max(0.0, abs(distanceFromEquilibrium) / 50.0))
self.updateNavigationOffset(-distanceFromEquilibrium) self.updateNavigationOffset(-distanceFromEquilibrium)
/*if let toolbarNode = toolbarNode {
toolbarNode.layer.position = CGPoint(x: toolbarNode.layer.position.x, y: self.bounds.size.height - toolbarNode.bounds.size.height / 2.0 + (1.0 - transition) * toolbarNode.bounds.size.height)
}*/
} }
open func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) { open func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
targetContentOffset.pointee = scrollView.contentOffset targetContentOffset.pointee = scrollView.contentOffset
let scrollVelocity = scrollView.panGestureRecognizer.velocity(in: scrollView) let scrollVelocity = scrollView.panGestureRecognizer.velocity(in: scrollView)
if abs(scrollVelocity.y) > 200.0 { if abs(scrollVelocity.y) > 200.0 {
self.animateOut() self.animateOut()
} }

View File

@ -1,50 +1,49 @@
import Foundation import Foundation
import UIKit import UIKit
import SegmentedControlNode
import TelegramPresentationData
public final class ItemListControllerSegmentedTitleView: UIView { public final class ItemListControllerSegmentedTitleView: UIView {
private let segmentedControlNode: SegmentedControlNode
public var theme: PresentationTheme {
didSet {
self.segmentedControlNode.updateTheme(SegmentedControlTheme(theme: self.theme))
}
}
public var segments: [String] { public var segments: [String] {
didSet { didSet {
if self.segments != oldValue { if self.segments != oldValue {
self.control.removeAllSegments() self.segmentedControlNode.items = self.segments.map { SegmentedControlItem(title: $0) }
var index = 0
for segment in self.segments {
self.control.insertSegment(withTitle: segment, at: index, animated: false)
index += 1
}
self.setNeedsLayout() self.setNeedsLayout()
} }
} }
} }
public var index: Int { public var index: Int {
didSet { get {
self.control.selectedSegmentIndex = self.index return self.segmentedControlNode.selectedIndex
}
set {
self.segmentedControlNode.selectedIndex = newValue
} }
} }
private let control: UISegmentedControl
public var indexUpdated: ((Int) -> Void)? public var indexUpdated: ((Int) -> Void)?
public var color: UIColor { public init(theme: PresentationTheme, segments: [String], selectedIndex: Int) {
didSet { self.theme = theme
self.control.tintColor = self.color
}
}
public init(segments: [String], index: Int, color: UIColor) {
self.segments = segments self.segments = segments
self.index = index
self.color = color
self.control = UISegmentedControl(items: segments) self.segmentedControlNode = SegmentedControlNode(theme: SegmentedControlTheme(theme: theme), items: segments.map { SegmentedControlItem(title: $0) }, selectedIndex: selectedIndex)
self.control.selectedSegmentIndex = index
self.control.tintColor = color
super.init(frame: CGRect()) super.init(frame: CGRect())
self.addSubview(self.control) self.segmentedControlNode.selectedIndexChanged = { [weak self] index in
self.control.addTarget(self, action: #selector(indexChanged), for: .valueChanged) self?.indexUpdated?(index)
}
self.addSubnode(self.segmentedControlNode)
} }
required public init?(coder aDecoder: NSCoder) { required public init?(coder aDecoder: NSCoder) {
@ -55,13 +54,7 @@ public final class ItemListControllerSegmentedTitleView: UIView {
super.layoutSubviews() super.layoutSubviews()
let size = self.bounds.size let size = self.bounds.size
let controlSize = self.segmentedControlNode.updateLayout(.sizeToFit(maximumWidth: size.width, minimumWidth: 160.0), transition: .immediate)
var controlSize = self.control.sizeThatFits(size) self.segmentedControlNode.frame = CGRect(origin: CGPoint(x: floor((size.width - controlSize.width) / 2.0), y: floor((size.height - controlSize.height) / 2.0)), size: controlSize)
controlSize.width = min(size.width, max(160.0, controlSize.width))
self.control.frame = CGRect(origin: CGPoint(x: floor((size.width - controlSize.width) / 2.0), y: floor((size.height - controlSize.height) / 2.0)), size: controlSize)
}
@objc private func indexChanged() {
self.indexUpdated?(self.control.selectedSegmentIndex)
} }
} }

View File

@ -14,6 +14,8 @@ public protocol ItemListItem {
} }
public extension ItemListItem { public extension ItemListItem {
//let accessoryItem: ListViewAccessoryItem?
var isAlwaysPlain: Bool { var isAlwaysPlain: Bool {
return false return false
} }

View File

@ -0,0 +1,38 @@
import Foundation
import UIKit
import Display
final class ItemListMaskAccessoryItem: ListViewAccessoryItem {
private let sectionId: Int32
init(sectionId: Int32) {
self.sectionId = sectionId
}
func isEqualToItem(_ other: ListViewAccessoryItem) -> Bool {
if case let other as ItemListMaskAccessoryItem = other {
return self.sectionId == other.sectionId
}
return false
}
func node(synchronous: Bool) -> ListViewAccessoryItemNode {
let node = ItemListMaskAccessoryItemItemNode()
node.frame = CGRect(origin: CGPoint(), size: CGSize(width: 38.0, height: 38.0))
return node
}
}
final class ItemListMaskAccessoryItemItemNode: ListViewAccessoryItemNode {
let node: ASDisplayNode
override init() {
self.node = ASDisplayNode()
self.node.backgroundColor = .red
super.init()
self.addSubnode(self.node)
}
}

View File

@ -92,6 +92,7 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
private let topStripeNode: ASDisplayNode private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode private let bottomStripeNode: ASDisplayNode
private let highlightedBackgroundNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode
private let maskNode: ASImageNode
private let titleNode: TextNode private let titleNode: TextNode
@ -107,7 +108,7 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
self.backgroundNode = ASDisplayNode() self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true self.backgroundNode.isLayerBacked = true
self.backgroundNode.backgroundColor = .white self.backgroundNode.backgroundColor = .white
self.maskNode = ASImageNode()
self.topStripeNode = ASDisplayNode() self.topStripeNode = ASDisplayNode()
self.topStripeNode.isLayerBacked = true self.topStripeNode.isLayerBacked = true
@ -216,7 +217,9 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
if strongSelf.bottomStripeNode.supernode == nil { if strongSelf.bottomStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 0) strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 0)
} }
if strongSelf.maskNode.supernode != nil {
strongSelf.maskNode.removeFromSupernode()
}
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight)) strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight))
case .blocks: case .blocks:
if strongSelf.backgroundNode.supernode == nil { if strongSelf.backgroundNode.supernode == nil {
@ -228,11 +231,19 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
if strongSelf.bottomStripeNode.supernode == nil { if strongSelf.bottomStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2) strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
} }
if strongSelf.maskNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
}
let hasCorners = params.width > 480
var hasTopCorners = false
var hasBottomCorners = false
switch neighbors.top { switch neighbors.top {
case .sameSection(false): case .sameSection(false):
strongSelf.topStripeNode.isHidden = true strongSelf.topStripeNode.isHidden = true
default: default:
strongSelf.topStripeNode.isHidden = false hasTopCorners = true
strongSelf.topStripeNode.isHidden = hasCorners
} }
let bottomStripeInset: CGFloat let bottomStripeInset: CGFloat
let bottomStripeOffset: CGFloat let bottomStripeOffset: CGFloat
@ -243,8 +254,14 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
default: default:
bottomStripeInset = 0.0 bottomStripeInset = 0.0
bottomStripeOffset = 0.0 bottomStripeOffset = 0.0
hasBottomCorners = true
strongSelf.bottomStripeNode.isHidden = hasCorners
} }
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight))
} }

View File

@ -108,6 +108,7 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
private let topStripeNode: ASDisplayNode private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode private let bottomStripeNode: ASDisplayNode
private let highlightedBackgroundNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode
private let maskNode: ASImageNode
let iconNode: ASImageNode let iconNode: ASImageNode
let titleNode: TextNode let titleNode: TextNode
@ -137,6 +138,8 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
self.backgroundNode.isLayerBacked = true self.backgroundNode.isLayerBacked = true
self.backgroundNode.backgroundColor = .white self.backgroundNode.backgroundColor = .white
self.maskNode = ASImageNode()
self.topStripeNode = ASDisplayNode() self.topStripeNode = ASDisplayNode()
self.topStripeNode.isLayerBacked = true self.topStripeNode.isLayerBacked = true
@ -357,7 +360,9 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
if strongSelf.bottomStripeNode.supernode == nil { if strongSelf.bottomStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 0) strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 0)
} }
if strongSelf.maskNode.supernode != nil {
strongSelf.maskNode.removeFromSupernode()
}
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight)) strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight))
case .blocks: case .blocks:
if strongSelf.backgroundNode.supernode == nil { if strongSelf.backgroundNode.supernode == nil {
@ -369,11 +374,19 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
if strongSelf.bottomStripeNode.supernode == nil { if strongSelf.bottomStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2) strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
} }
if strongSelf.maskNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
}
let hasCorners = params.width > 480
var hasTopCorners = false
var hasBottomCorners = false
switch neighbors.top { switch neighbors.top {
case .sameSection(false): case .sameSection(false):
strongSelf.topStripeNode.isHidden = true strongSelf.topStripeNode.isHidden = true
default: default:
strongSelf.topStripeNode.isHidden = false hasTopCorners = true
strongSelf.topStripeNode.isHidden = hasCorners
} }
let bottomStripeInset: CGFloat let bottomStripeInset: CGFloat
switch neighbors.bottom { switch neighbors.bottom {
@ -381,10 +394,15 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
bottomStripeInset = leftInset bottomStripeInset = leftInset
default: default:
bottomStripeInset = 0.0 bottomStripeInset = 0.0
hasBottomCorners = true
strongSelf.bottomStripeNode.isHidden = hasCorners
} }
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight))
} }

View File

@ -114,6 +114,7 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
private let topStripeNode: ASDisplayNode private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode private let bottomStripeNode: ASDisplayNode
private let highlightedBackgroundNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode
private let maskNode: ASImageNode
private let titleNode: TextNode private let titleNode: TextNode
private var switchNode: ASDisplayNode & ItemListSwitchNodeImpl private var switchNode: ASDisplayNode & ItemListSwitchNodeImpl
@ -133,6 +134,8 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
self.backgroundNode.isLayerBacked = true self.backgroundNode.isLayerBacked = true
self.backgroundNode.backgroundColor = .white self.backgroundNode.backgroundColor = .white
self.maskNode = ASImageNode()
self.topStripeNode = ASDisplayNode() self.topStripeNode = ASDisplayNode()
self.topStripeNode.isLayerBacked = true self.topStripeNode.isLayerBacked = true
@ -299,7 +302,9 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
if strongSelf.bottomStripeNode.supernode == nil { if strongSelf.bottomStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 0) strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 0)
} }
if strongSelf.maskNode.supernode != nil {
strongSelf.maskNode.removeFromSupernode()
}
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight)) strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight))
case .blocks: case .blocks:
if strongSelf.backgroundNode.supernode == nil { if strongSelf.backgroundNode.supernode == nil {
@ -311,11 +316,19 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
if strongSelf.bottomStripeNode.supernode == nil { if strongSelf.bottomStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2) strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
} }
if strongSelf.maskNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
}
let hasCorners = params.width > 480
var hasTopCorners = false
var hasBottomCorners = false
switch neighbors.top { switch neighbors.top {
case .sameSection(false): case .sameSection(false):
strongSelf.topStripeNode.isHidden = true strongSelf.topStripeNode.isHidden = true
default: default:
strongSelf.topStripeNode.isHidden = false hasTopCorners = true
strongSelf.topStripeNode.isHidden = hasCorners
} }
let bottomStripeInset: CGFloat let bottomStripeInset: CGFloat
switch neighbors.bottom { switch neighbors.bottom {
@ -323,9 +336,14 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
bottomStripeInset = 16.0 + params.leftInset bottomStripeInset = 16.0 + params.leftInset
default: default:
bottomStripeInset = 0.0 bottomStripeInset = 0.0
hasBottomCorners = true
strongSelf.bottomStripeNode.isHidden = hasCorners
} }
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)) strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
} }

View File

@ -60,7 +60,7 @@ public final class ItemListTextEmptyStateItemNode: ItemListControllerEmptyStateI
self.validLayout = (layout, navigationBarHeight) self.validLayout = (layout, navigationBarHeight)
var insets = layout.insets(options: [.statusBar]) var insets = layout.insets(options: [.statusBar])
insets.top += navigationBarHeight insets.top += navigationBarHeight
let textSize = self.textNode.measure(CGSize(width: layout.size.width - 40.0 - layout.safeInsets.left - layout.safeInsets.right, height: max(1.0, layout.size.height - insets.top - insets.bottom))) let textSize = self.textNode.measure(CGSize(width: layout.size.width - 40.0 - layout.safeInsets.left - layout.safeInsets.right - layout.intrinsicInsets.left - layout.intrinsicInsets.right, height: max(1.0, layout.size.height - insets.top - insets.bottom)))
self.textNode.frame = CGRect(origin: CGPoint(x: layout.safeInsets.left + floor((layout.size.width - layout.safeInsets.left - layout.safeInsets.right - textSize.width) / 2.0), y: insets.top + floor((layout.size.height - insets.top - insets.bottom - textSize.height) / 2.0)), size: textSize) self.textNode.frame = CGRect(origin: CGPoint(x: layout.safeInsets.left + layout.intrinsicInsets.left + floor((layout.size.width - layout.safeInsets.left - layout.safeInsets.right - layout.intrinsicInsets.left - layout.intrinsicInsets.right - textSize.width) / 2.0), y: insets.top + floor((layout.size.height - insets.top - insets.bottom - textSize.height) / 2.0)), size: textSize)
} }
} }

View File

@ -10,7 +10,7 @@
@implementation OggOpusReader @implementation OggOpusReader
- (instancetype _Nullable)init:(NSString *)path { - (instancetype _Nullable)initWithPath:(NSString *)path {
self = [super init]; self = [super init];
if (self != nil) { if (self != nil) {
int error = OPUS_OK; int error = OPUS_OK;

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
</plist>

View File

@ -0,0 +1,563 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
09CE5B9023225AC200743FF4 /* TelegramPresentationData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09CE5B8F23225AC200743FF4 /* TelegramPresentationData.framework */; };
D060188A22F3604000796784 /* SegmentedControlNode.h in Headers */ = {isa = PBXBuildFile; fileRef = D060188822F3604000796784 /* SegmentedControlNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
D060189522F3615800796784 /* SegmentedControlNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D060189422F3615800796784 /* SegmentedControlNode.swift */; };
D060189822F3616300796784 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D060189722F3616300796784 /* Foundation.framework */; };
D060189A22F3616600796784 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D060189922F3616600796784 /* UIKit.framework */; };
D060189C22F3616A00796784 /* AsyncDisplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D060189B22F3616A00796784 /* AsyncDisplayKit.framework */; };
D060189E22F3616E00796784 /* LegacyComponents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D060189D22F3616E00796784 /* LegacyComponents.framework */; };
D0A0B53922F370DF00628AF3 /* Display.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0A0B53822F370DF00628AF3 /* Display.framework */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
09CE5B8F23225AC200743FF4 /* TelegramPresentationData.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TelegramPresentationData.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D060188522F3604000796784 /* SegmentedControlNode.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SegmentedControlNode.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D060188822F3604000796784 /* SegmentedControlNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SegmentedControlNode.h; sourceTree = "<group>"; };
D060188922F3604000796784 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D060189422F3615800796784 /* SegmentedControlNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegmentedControlNode.swift; sourceTree = "<group>"; };
D060189722F3616300796784 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
D060189922F3616600796784 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
D060189B22F3616A00796784 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D060189D22F3616E00796784 /* LegacyComponents.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LegacyComponents.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D0A0B53822F370DF00628AF3 /* Display.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Display.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
D060188222F3604000796784 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
09CE5B9023225AC200743FF4 /* TelegramPresentationData.framework in Frameworks */,
D0A0B53922F370DF00628AF3 /* Display.framework in Frameworks */,
D060189E22F3616E00796784 /* LegacyComponents.framework in Frameworks */,
D060189C22F3616A00796784 /* AsyncDisplayKit.framework in Frameworks */,
D060189A22F3616600796784 /* UIKit.framework in Frameworks */,
D060189822F3616300796784 /* Foundation.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
D060187B22F3604000796784 = {
isa = PBXGroup;
children = (
D060188922F3604000796784 /* Info.plist */,
D060188722F3604000796784 /* Sources */,
D060188622F3604000796784 /* Products */,
D060189622F3616300796784 /* Frameworks */,
);
sourceTree = "<group>";
};
D060188622F3604000796784 /* Products */ = {
isa = PBXGroup;
children = (
D060188522F3604000796784 /* SegmentedControlNode.framework */,
);
name = Products;
sourceTree = "<group>";
};
D060188722F3604000796784 /* Sources */ = {
isa = PBXGroup;
children = (
D060189422F3615800796784 /* SegmentedControlNode.swift */,
D060188822F3604000796784 /* SegmentedControlNode.h */,
);
path = Sources;
sourceTree = "<group>";
};
D060189622F3616300796784 /* Frameworks */ = {
isa = PBXGroup;
children = (
09CE5B8F23225AC200743FF4 /* TelegramPresentationData.framework */,
D0A0B53822F370DF00628AF3 /* Display.framework */,
D060189D22F3616E00796784 /* LegacyComponents.framework */,
D060189B22F3616A00796784 /* AsyncDisplayKit.framework */,
D060189922F3616600796784 /* UIKit.framework */,
D060189722F3616300796784 /* Foundation.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
D060188022F3604000796784 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
D060188A22F3604000796784 /* SegmentedControlNode.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
D060188422F3604000796784 /* SegmentedControlNode */ = {
isa = PBXNativeTarget;
buildConfigurationList = D060188D22F3604000796784 /* Build configuration list for PBXNativeTarget "SegmentedControlNode" */;
buildPhases = (
D060188022F3604000796784 /* Headers */,
D060188122F3604000796784 /* Sources */,
D060188222F3604000796784 /* Frameworks */,
D060188322F3604000796784 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = SegmentedControlNode;
productName = SegmentedControlNode;
productReference = D060188522F3604000796784 /* SegmentedControlNode.framework */;
productType = "com.apple.product-type.framework";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
D060187C22F3604000796784 /* Project object */ = {
isa = PBXProject;
attributes = {
DefaultBuildSystemTypeForWorkspace = Latest;
LastUpgradeCheck = 1010;
ORGANIZATIONNAME = "Telegram Messenger LLP";
TargetAttributes = {
D060188422F3604000796784 = {
CreatedOnToolsVersion = 10.1;
LastSwiftMigration = 1010;
};
};
};
buildConfigurationList = D060187F22F3604000796784 /* Build configuration list for PBXProject "SegmentedControlNode_Xcode" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = D060187B22F3604000796784;
productRefGroup = D060188622F3604000796784 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
D060188422F3604000796784 /* SegmentedControlNode */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
D060188322F3604000796784 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
D060188122F3604000796784 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D060189522F3615800796784 /* SegmentedControlNode.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
D060188B22F3604000796784 /* DebugAppStoreLLC */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = DebugAppStoreLLC;
};
D060188C22F3604000796784 /* ReleaseAppStoreLLC */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = ReleaseAppStoreLLC;
};
D060188E22F3604000796784 /* DebugAppStoreLLC */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Manual;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACH_O_TYPE = staticlib;
PRODUCT_BUNDLE_IDENTIFIER = org.telegram.SegmentedControlNode;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = DebugAppStoreLLC;
};
D060188F22F3604000796784 /* ReleaseAppStoreLLC */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Manual;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACH_O_TYPE = staticlib;
PRODUCT_BUNDLE_IDENTIFIER = org.telegram.SegmentedControlNode;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = ReleaseAppStoreLLC;
};
D060189022F360BF00796784 /* DebugHockeyapp */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = DebugHockeyapp;
};
D060189122F360BF00796784 /* DebugHockeyapp */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Manual;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACH_O_TYPE = staticlib;
PRODUCT_BUNDLE_IDENTIFIER = org.telegram.SegmentedControlNode;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = DebugHockeyapp;
};
D060189222F360CA00796784 /* ReleaseHockeyappInternal */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = ReleaseHockeyappInternal;
};
D060189322F360CA00796784 /* ReleaseHockeyappInternal */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Manual;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACH_O_TYPE = staticlib;
PRODUCT_BUNDLE_IDENTIFIER = org.telegram.SegmentedControlNode;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = ReleaseHockeyappInternal;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
D060187F22F3604000796784 /* Build configuration list for PBXProject "SegmentedControlNode_Xcode" */ = {
isa = XCConfigurationList;
buildConfigurations = (
D060188B22F3604000796784 /* DebugAppStoreLLC */,
D060189022F360BF00796784 /* DebugHockeyapp */,
D060188C22F3604000796784 /* ReleaseAppStoreLLC */,
D060189222F360CA00796784 /* ReleaseHockeyappInternal */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = ReleaseAppStoreLLC;
};
D060188D22F3604000796784 /* Build configuration list for PBXNativeTarget "SegmentedControlNode" */ = {
isa = XCConfigurationList;
buildConfigurations = (
D060188E22F3604000796784 /* DebugAppStoreLLC */,
D060189122F360BF00796784 /* DebugHockeyapp */,
D060188F22F3604000796784 /* ReleaseAppStoreLLC */,
D060189322F360CA00796784 /* ReleaseHockeyappInternal */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = ReleaseAppStoreLLC;
};
/* End XCConfigurationList section */
};
rootObject = D060187C22F3604000796784 /* Project object */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,11 @@
#import <UIKit/UIKit.h>
//! Project version number for SegmentedControlNode.
FOUNDATION_EXPORT double SegmentedControlNodeVersionNumber;
//! Project version string for SegmentedControlNode.
FOUNDATION_EXPORT const unsigned char SegmentedControlNodeVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <SegmentedControlNode/PublicHeader.h>

View File

@ -0,0 +1,381 @@
import Foundation
import Display
import UIKit
import AsyncDisplayKit
import TelegramPresentationData
private let textFont = Font.regular(13.0)
private let selectedTextFont = Font.bold(13.0)
public enum SegmentedControlLayout {
case stretchToFill(width: CGFloat)
case sizeToFit(maximumWidth: CGFloat, minimumWidth: CGFloat)
}
public final class SegmentedControlTheme: Equatable {
public let backgroundColor: UIColor
public let foregroundColor: UIColor
public let shadowColor: UIColor
public let textColor: UIColor
public let dividerColor: UIColor
public init(backgroundColor: UIColor, foregroundColor: UIColor, shadowColor: UIColor, textColor: UIColor, dividerColor: UIColor) {
self.backgroundColor = backgroundColor
self.foregroundColor = foregroundColor
self.shadowColor = shadowColor
self.textColor = textColor
self.dividerColor = dividerColor
}
public static func ==(lhs: SegmentedControlTheme, rhs: SegmentedControlTheme) -> Bool {
if lhs.backgroundColor != rhs.backgroundColor {
return false
}
if lhs.foregroundColor != rhs.foregroundColor {
return false
}
if lhs.shadowColor != rhs.shadowColor {
return false
}
if lhs.textColor != rhs.textColor {
return false
}
if lhs.dividerColor != rhs.dividerColor {
return false
}
return true
}
}
public extension SegmentedControlTheme {
convenience init(theme: PresentationTheme) {
self.init(backgroundColor: theme.rootController.navigationSearchBar.inputFillColor, foregroundColor: theme.rootController.navigationBar.backgroundColor, shadowColor: .black, textColor: theme.rootController.navigationBar.primaryTextColor, dividerColor: theme.list.freeInputField.strokeColor)
}
}
private func generateSelectionImage(theme: SegmentedControlTheme) -> UIImage? {
return generateImage(CGSize(width: 20.0, height: 20.0), rotatedContext: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
if theme.shadowColor != .clear {
context.setShadow(offset: CGSize(width: 0.0, height: -3.0), blur: 6.0, color: theme.shadowColor.withAlphaComponent(0.12).cgColor)
}
context.setFillColor(theme.foregroundColor.cgColor)
context.fillEllipse(in: CGRect(x: 2.0, y: 2.0, width: 16.0, height: 16.0))
})?.stretchableImage(withLeftCapWidth: 10, topCapHeight: 10)
}
public struct SegmentedControlItem: Equatable {
public let title: String
public init(title: String) {
self.title = title
}
}
public final class SegmentedControlNode: ASDisplayNode, UIGestureRecognizerDelegate {
private var theme: SegmentedControlTheme
private var _items: [SegmentedControlItem]
private var _selectedIndex: Int = 0
private var validLayout: SegmentedControlLayout?
private let selectionNode: ASImageNode
private var itemNodes: [HighlightTrackingButtonNode]
private var dividerNodes: [ASDisplayNode]
private var gestureRecognizer: UIPanGestureRecognizer?
private var gestureSelectedIndex: Int?
public var items: [SegmentedControlItem] {
get {
return self._items
}
set {
let previousItems = self._items
self._items = newValue
guard previousItems != newValue else {
return
}
self.itemNodes.forEach { $0.removeFromSupernode() }
self.itemNodes = self._items.map { item in
let itemNode = HighlightTrackingButtonNode()
itemNode.setTitle(item.title, with: textFont, with: self.theme.textColor, for: .normal)
itemNode.setTitle(item.title, with: selectedTextFont, with: self.theme.textColor, for: .selected)
itemNode.setTitle(item.title, with: selectedTextFont, with: self.theme.textColor, for: [.selected, .highlighted])
return itemNode
}
self.setupButtons()
self.itemNodes.forEach(self.addSubnode(_:))
let dividersCount = self._items.count > 2 ? self._items.count - 1 : 0
if self.dividerNodes.count != dividersCount {
self.dividerNodes.forEach { $0.removeFromSupernode() }
self.dividerNodes = (0 ..< dividersCount).map { _ in ASDisplayNode() }
}
if let layout = self.validLayout {
let _ = self.updateLayout(layout, transition: .immediate)
}
}
}
public var selectedIndex: Int {
get {
return self._selectedIndex
}
set {
guard newValue != self._selectedIndex else {
return
}
self._selectedIndex = newValue
if let layout = self.validLayout {
let _ = self.updateLayout(layout, transition: .immediate)
}
}
}
public var selectedIndexChanged: (Int) -> Void = { _ in }
public init(theme: SegmentedControlTheme, items: [SegmentedControlItem], selectedIndex: Int) {
self.theme = theme
self._items = items
self._selectedIndex = selectedIndex
self.selectionNode = ASImageNode()
self.selectionNode.displaysAsynchronously = false
self.selectionNode.displayWithoutProcessing = true
self.itemNodes = items.map { item in
let itemNode = HighlightTrackingButtonNode()
itemNode.setTitle(item.title, with: textFont, with: theme.textColor, for: .normal)
itemNode.setTitle(item.title, with: selectedTextFont, with: theme.textColor, for: .selected)
itemNode.setTitle(item.title, with: selectedTextFont, with: theme.textColor, for: [.selected, .highlighted])
return itemNode
}
let dividersCount = items.count > 2 ? items.count - 1 : 0
self.dividerNodes = (0 ..< dividersCount).map { _ in
let node = ASDisplayNode()
node.backgroundColor = theme.dividerColor
return node
}
super.init()
self.clipsToBounds = true
self.cornerRadius = 9.0
self.addSubnode(self.selectionNode)
self.itemNodes.forEach(self.addSubnode(_:))
self.setupButtons()
self.dividerNodes.forEach(self.addSubnode(_:))
self.backgroundColor = self.theme.backgroundColor
self.selectionNode.image = generateSelectionImage(theme: self.theme)
}
override public func didLoad() {
super.didLoad()
self.view.disablesInteractiveTransitionGestureRecognizer = true
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
gestureRecognizer.delegate = self
self.view.addGestureRecognizer(gestureRecognizer)
self.gestureRecognizer = gestureRecognizer
}
private func setupButtons() {
for i in 0 ..< self.itemNodes.count {
let itemNode = self.itemNodes[i]
itemNode.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside)
itemNode.highligthedChanged = { [weak self, weak itemNode] highlighted in
if let strongSelf = self, let itemNode = itemNode {
let transition = ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut)
if strongSelf.selectedIndex == i {
if let gestureRecognizer = strongSelf.gestureRecognizer, case .began = gestureRecognizer.state {
} else {
strongSelf.updateButtonsHighlights(highlightedIndex: highlighted ? i : nil, gestureSelectedIndex: strongSelf.gestureSelectedIndex)
}
} else if highlighted {
transition.updateAlpha(node: itemNode, alpha: 0.4)
}
if !highlighted {
transition.updateAlpha(node: itemNode, alpha: 1.0)
}
}
}
}
}
private func updateButtonsHighlights(highlightedIndex: Int?, gestureSelectedIndex: Int?) {
let transition = ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut)
if highlightedIndex == nil && gestureSelectedIndex == nil {
transition.updateTransformScale(node: self.selectionNode, scale: 1.0)
} else {
transition.updateTransformScale(node: self.selectionNode, scale: 0.92)
}
for i in 0 ..< self.itemNodes.count {
let itemNode = self.itemNodes[i]
if i == highlightedIndex || i == gestureSelectedIndex {
transition.updateTransformScale(node: itemNode, scale: 0.92)
} else {
transition.updateTransformScale(node: itemNode, scale: 1.0)
}
}
}
private func updateButtonsHighlights() {
let transition = ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut)
if let gestureSelectedIndex = self.gestureSelectedIndex {
for i in 0 ..< self.itemNodes.count {
let itemNode = self.itemNodes[i]
transition.updateTransformScale(node: itemNode, scale: i == gestureSelectedIndex ? 0.92 : 1.0)
}
} else {
for itemNode in self.itemNodes {
transition.updateTransformScale(node: itemNode, scale: 1.0)
}
}
}
public func updateTheme(_ theme: SegmentedControlTheme) {
guard theme != self.theme else {
return
}
self.theme = theme
self.backgroundColor = self.theme.backgroundColor
self.selectionNode.image = generateSelectionImage(theme: self.theme)
for itemNode in self.itemNodes {
if let title = itemNode.attributedTitle(for: .normal)?.string {
itemNode.setTitle(title, with: textFont, with: self.theme.textColor, for: .normal)
itemNode.setTitle(title, with: selectedTextFont, with: self.theme.textColor, for: .selected)
itemNode.setTitle(title, with: selectedTextFont, with: self.theme.textColor, for: [.selected, .highlighted])
}
}
for dividerNode in self.dividerNodes {
dividerNode.backgroundColor = theme.dividerColor
}
}
public func updateLayout(_ layout: SegmentedControlLayout, transition: ContainedViewLayoutTransition) -> CGSize {
self.validLayout = layout
let calculatedWidth: CGFloat = 0.0
let width: CGFloat
switch layout {
case let .stretchToFill(targetWidth):
width = targetWidth
case let .sizeToFit(maximumWidth, minimumWidth):
width = max(minimumWidth, min(maximumWidth, calculatedWidth))
}
let size = CGSize(width: width, height: 32.0)
if !self.itemNodes.isEmpty {
let itemSize = CGSize(width: floorToScreenPixels(size.width / CGFloat(self.itemNodes.count)), height: size.height)
let selectedIndex: Int
if let gestureSelectedIndex = self.gestureSelectedIndex {
selectedIndex = gestureSelectedIndex
} else {
selectedIndex = self.selectedIndex
}
transition.updateBounds(node: self.selectionNode, bounds: CGRect(origin: CGPoint(), size: itemSize))
transition.updatePosition(node: self.selectionNode, position: CGPoint(x: itemSize.width / 2.0 + itemSize.width * CGFloat(selectedIndex), y: size.height / 2.0))
for i in 0 ..< self.itemNodes.count {
let itemNode = self.itemNodes[i]
transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(x: itemSize.width * CGFloat(i), y: (size.height - itemSize.height) / 2.0), size: itemSize))
let isSelected = selectedIndex == i
if itemNode.isSelected != isSelected {
if case .animated = transition {
UIView.transition(with: itemNode.view, duration: 0.2, options: .transitionCrossDissolve, animations: {
itemNode.isSelected = isSelected
}, completion: nil)
} else {
itemNode.isSelected = isSelected
}
}
}
}
if !self.dividerNodes.isEmpty {
let dividerSize = CGSize(width: 1.0, height: 16.0)
let delta: CGFloat = size.width / CGFloat(self.dividerNodes.count + 1)
for i in 0 ..< self.dividerNodes.count {
let dividerNode = self.dividerNodes[i]
transition.updateFrame(node: dividerNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels(delta * CGFloat(i + 1) - dividerSize.width / 2.0), y: (size.height - dividerSize.height) / 2.0), size: dividerSize))
let dividerAlpha: CGFloat
if (self.selectedIndex - 1 ... self.selectedIndex).contains(i) {
dividerAlpha = 0.0
} else {
dividerAlpha = 1.0
}
transition.updateAlpha(node: dividerNode, alpha: dividerAlpha)
}
}
return size
}
@objc private func buttonPressed(_ button: HighlightTrackingButtonNode) {
guard let index = self.itemNodes.firstIndex(of: button) else {
return
}
self._selectedIndex = index
self.selectedIndexChanged(index)
if let layout = self.validLayout {
let _ = self.updateLayout(layout, transition: .animated(duration: 0.2, curve: .slide))
}
}
public override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
let location = gestureRecognizer.location(in: self.view)
return self.selectionNode.frame.contains(location)
}
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
let location = recognizer.location(in: self.view)
switch recognizer.state {
case .changed:
if !self.selectionNode.frame.contains(location) {
let point = CGPoint(x: max(0.0, min(self.bounds.width, location.x)), y: 1.0)
for i in 0 ..< self.itemNodes.count {
let itemNode = self.itemNodes[i]
if itemNode.frame.contains(point) {
if i != self.gestureSelectedIndex {
self.gestureSelectedIndex = i
self.updateButtonsHighlights(highlightedIndex: nil, gestureSelectedIndex: i)
if let layout = self.validLayout {
let _ = self.updateLayout(layout, transition: .animated(duration: 0.35, curve: .slide))
}
}
break
}
}
}
case .ended:
if let gestureSelectedIndex = self.gestureSelectedIndex {
if gestureSelectedIndex != self.selectedIndex {
self._selectedIndex = gestureSelectedIndex
self.selectedIndexChanged(self._selectedIndex)
}
self.gestureSelectedIndex = nil
self.updateButtonsHighlights(highlightedIndex: nil, gestureSelectedIndex: nil)
}
default:
break
}
}
}

View File

@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
091EABA5231DAC7500A0EC14 /* ThemeNameGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091EABA4231DAC7500A0EC14 /* ThemeNameGenerator.swift */; }; 091EABA5231DAC7500A0EC14 /* ThemeNameGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091EABA4231DAC7500A0EC14 /* ThemeNameGenerator.swift */; };
09B4A9B823102B7A005C2E08 /* EditThemeController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4A9B723102B7A005C2E08 /* EditThemeController.swift */; }; 09B4A9B823102B7A005C2E08 /* EditThemeController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4A9B723102B7A005C2E08 /* EditThemeController.swift */; };
09CE5B8E2322154400743FF4 /* SegmentedControlNode.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09CE5B8D2322154400743FF4 /* SegmentedControlNode.framework */; };
D03E465223075D930049C28B /* SettingsUI.h in Headers */ = {isa = PBXBuildFile; fileRef = D03E465023075D930049C28B /* SettingsUI.h */; settings = {ATTRIBUTES = (Public, ); }; }; D03E465223075D930049C28B /* SettingsUI.h in Headers */ = {isa = PBXBuildFile; fileRef = D03E465023075D930049C28B /* SettingsUI.h */; settings = {ATTRIBUTES = (Public, ); }; };
D03E466823075E660049C28B /* TabBarAccountSwitchControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E465C23075E630049C28B /* TabBarAccountSwitchControllerNode.swift */; }; D03E466823075E660049C28B /* TabBarAccountSwitchControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E465C23075E630049C28B /* TabBarAccountSwitchControllerNode.swift */; };
D03E466923075E660049C28B /* LogoutOptionsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E465D23075E630049C28B /* LogoutOptionsController.swift */; }; D03E466923075E660049C28B /* LogoutOptionsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E465D23075E630049C28B /* LogoutOptionsController.swift */; };
@ -194,6 +195,7 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
091EABA4231DAC7500A0EC14 /* ThemeNameGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeNameGenerator.swift; sourceTree = "<group>"; }; 091EABA4231DAC7500A0EC14 /* ThemeNameGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeNameGenerator.swift; sourceTree = "<group>"; };
09B4A9B723102B7A005C2E08 /* EditThemeController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditThemeController.swift; sourceTree = "<group>"; }; 09B4A9B723102B7A005C2E08 /* EditThemeController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditThemeController.swift; sourceTree = "<group>"; };
09CE5B8D2322154400743FF4 /* SegmentedControlNode.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SegmentedControlNode.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D03E464D23075D930049C28B /* SettingsUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SettingsUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D03E464D23075D930049C28B /* SettingsUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SettingsUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D03E465023075D930049C28B /* SettingsUI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SettingsUI.h; sourceTree = "<group>"; }; D03E465023075D930049C28B /* SettingsUI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SettingsUI.h; sourceTree = "<group>"; };
D03E465123075D930049C28B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; D03E465123075D930049C28B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@ -383,6 +385,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
09CE5B8E2322154400743FF4 /* SegmentedControlNode.framework in Frameworks */,
D03E493A2308678D0049C28B /* InstantPageCache.framework in Frameworks */, D03E493A2308678D0049C28B /* InstantPageCache.framework in Frameworks */,
D03E490E2308661A0049C28B /* GridMessageSelectionNode.framework in Frameworks */, D03E490E2308661A0049C28B /* GridMessageSelectionNode.framework in Frameworks */,
D03E48E22308649C0049C28B /* CounterContollerTitleView.framework in Frameworks */, D03E48E22308649C0049C28B /* CounterContollerTitleView.framework in Frameworks */,
@ -687,6 +690,7 @@
D03E4732230761E10049C28B /* Frameworks */ = { D03E4732230761E10049C28B /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
09CE5B8D2322154400743FF4 /* SegmentedControlNode.framework */,
D03E49392308678D0049C28B /* InstantPageCache.framework */, D03E49392308678D0049C28B /* InstantPageCache.framework */,
D03E490D2308661A0049C28B /* GridMessageSelectionNode.framework */, D03E490D2308661A0049C28B /* GridMessageSelectionNode.framework */,
D03E48E12308649C0049C28B /* CounterContollerTitleView.framework */, D03E48E12308649C0049C28B /* CounterContollerTitleView.framework */,

View File

@ -84,15 +84,15 @@ final class RecentSessionsEmptyStateItemNode: ItemListControllerEmptyStateItemNo
let imageSize = self.imageNode.image?.size ?? CGSize() let imageSize = self.imageNode.image?.size ?? CGSize()
let imageHeight = layout.size.width < layout.size.height ? imageSize.height + imageSpacing : 0.0 let imageHeight = layout.size.width < layout.size.height ? imageSize.height + imageSpacing : 0.0
let titleSize = self.titleNode.measure(CGSize(width: layout.size.width - 50.0, height: max(1.0, layout.size.height - insets.top - insets.bottom))) let titleSize = self.titleNode.measure(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - layout.intrinsicInsets.left - layout.intrinsicInsets.right - 50.0, height: max(1.0, layout.size.height - insets.top - insets.bottom)))
let textSize = self.textNode.measure(CGSize(width: layout.size.width - 50.0, height: max(1.0, layout.size.height - insets.top - insets.bottom))) let textSize = self.textNode.measure(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - layout.intrinsicInsets.left - layout.intrinsicInsets.right - 50.0, height: max(1.0, layout.size.height - insets.top - insets.bottom)))
let totalHeight = imageHeight + titleSize.height + textSpacing + textSize.height let totalHeight = imageHeight + titleSize.height + textSpacing + textSize.height
let topOffset = insets.top + floor((layout.size.height - insets.top - insets.bottom - totalHeight) / 2.0) let topOffset = insets.top + floor((layout.size.height - insets.top - insets.bottom - totalHeight) / 2.0)
transition.updateAlpha(node: self.imageNode, alpha: imageHeight > 0.0 ? 1.0 : 0.0) transition.updateAlpha(node: self.imageNode, alpha: imageHeight > 0.0 ? 1.0 : 0.0)
transition.updateFrame(node: self.imageNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - imageSize.width) / 2.0), y: topOffset), size: imageSize)) transition.updateFrame(node: self.imageNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - imageSize.width) / 2.0), y: topOffset), size: imageSize))
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: topOffset + imageHeight), size: titleSize)) transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width - layout.safeInsets.left - layout.safeInsets.right - layout.intrinsicInsets.left - layout.intrinsicInsets.right) / 2.0), y: topOffset + imageHeight), size: titleSize))
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width) / 2.0), y: self.titleNode.frame.maxY + textSpacing), size: textSize)) transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width - layout.safeInsets.left - layout.safeInsets.right - layout.intrinsicInsets.left - layout.intrinsicInsets.right) / 2.0), y: self.titleNode.frame.maxY + textSpacing), size: textSize))
} }
} }

View File

@ -335,9 +335,10 @@ func generateThemeName(accentColor: UIColor) -> String {
if let color = nearest?.color, let colorName = colors[color]?.capitalized { if let color = nearest?.color, let colorName = colors[color]?.capitalized {
if arc4random() % 2 == 0 { if arc4random() % 2 == 0 {
return "\(adjectives[Int(arc4random()) % adjectives.count].capitalized) \(colorName)"
return "\((adjectives.randomElement() ?? "").capitalized) \(colorName)"
} else { } else {
return "\(colorName) \(subjectives[Int(arc4random()) % subjectives.count].capitalized)" return "\(colorName) \((subjectives.randomElement() ?? "").capitalized)"
} }
} else { } else {
return "" return ""

View File

@ -83,6 +83,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.chatListBackgroundNode = ASDisplayNode() self.chatListBackgroundNode = ASDisplayNode()
self.chatContainerNode = ASDisplayNode() self.chatContainerNode = ASDisplayNode()
self.chatContainerNode.clipsToBounds = true
self.instantChatBackgroundNode = WallpaperBackgroundNode() self.instantChatBackgroundNode = WallpaperBackgroundNode()
self.instantChatBackgroundNode.displaysAsynchronously = false self.instantChatBackgroundNode.displaysAsynchronously = false
self.instantChatBackgroundNode.image = chatControllerBackgroundImage(theme: previewTheme, wallpaper: previewTheme.chat.defaultWallpaper, mediaBox: context.sharedContext.accountManager.mediaBox, knockoutMode: context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper) self.instantChatBackgroundNode.image = chatControllerBackgroundImage(theme: previewTheme, wallpaper: previewTheme.chat.defaultWallpaper, mediaBox: context.sharedContext.accountManager.mediaBox, knockoutMode: context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper)
@ -94,7 +95,6 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.remoteChatBackgroundNode.view.contentMode = .scaleAspectFill self.remoteChatBackgroundNode.view.contentMode = .scaleAspectFill
self.blurredNode = BlurredImageNode() self.blurredNode = BlurredImageNode()
self.blurredNode.clipsToBounds = true
self.blurredNode.blurView.contentMode = .scaleAspectFill self.blurredNode.blurView.contentMode = .scaleAspectFill
self.toolbarNode = WallpaperGalleryToolbarNode(theme: self.previewTheme, strings: self.presentationData.strings) self.toolbarNode = WallpaperGalleryToolbarNode(theme: self.previewTheme, strings: self.presentationData.strings)

View File

@ -362,6 +362,10 @@ public final class ManagedAudioSession {
} |> runOn(queue) } |> runOn(queue)
} }
public func isOtherAudioPlaying() -> Bool {
return AVAudioSession.sharedInstance().secondaryAudioShouldBeSilencedHint
}
public func push(audioSessionType: ManagedAudioSessionType, outputMode: AudioSessionOutputMode = .system, once: Bool = false, activate: @escaping (AudioSessionActivationState) -> Void, deactivate: @escaping () -> Signal<Void, NoError>) -> Disposable { public func push(audioSessionType: ManagedAudioSessionType, outputMode: AudioSessionOutputMode = .system, once: Bool = false, activate: @escaping (AudioSessionActivationState) -> Void, deactivate: @escaping () -> Signal<Void, NoError>) -> Disposable {
return self.push(audioSessionType: audioSessionType, once: once, manualActivate: { control in return self.push(audioSessionType: audioSessionType, once: once, manualActivate: { control in
control.setupAndActivate(synchronous: false, { state in control.setupAndActivate(synchronous: false, { state in

View File

@ -47,13 +47,13 @@ public enum PresentationResourceKey: Int32 {
case itemListAddExceptionIcon case itemListAddExceptionIcon
case itemListAddPhoneIcon case itemListAddPhoneIcon
case itemListClearInputIcon case itemListClearInputIcon
case itemListStickerItemUnreadDot case itemListStickerItemUnreadDot
case itemListVerifiedPeerIcon case itemListVerifiedPeerIcon
case itemListCloudFetchIcon case itemListCloudFetchIcon
case itemListCloseIconImage case itemListCloseIconImage
case itemListCornersTop
case itemListCornersBottom
case itemListCornersBoth
case chatListLockTopUnlockedImage case chatListLockTopUnlockedImage
case chatListLockBottomUnlockedImage case chatListLockBottomUnlockedImage

View File

@ -144,4 +144,40 @@ public struct PresentationResourcesItemList {
}) })
}) })
} }
public static func cornersImage(_ theme: PresentationTheme, top: Bool, bottom: Bool) -> UIImage? {
if !top && !bottom {
return nil
}
let key: PresentationResourceKey
if top && bottom {
key = PresentationResourceKey.itemListCornersBoth
} else if top {
key = PresentationResourceKey.itemListCornersTop
} else {
key = PresentationResourceKey.itemListCornersBottom
}
return theme.image(key.rawValue, { theme in
return generateImage(CGSize(width: 50.0, height: 50.0), rotatedContext: { (size, context) in
let bounds = CGRect(origin: CGPoint(), size: size)
context.setFillColor(theme.list.blocksBackgroundColor.cgColor)
context.fill(bounds)
context.setBlendMode(.clear)
var corners: UIRectCorner = []
if top {
corners.insert(.topLeft)
corners.insert(.topRight)
}
if bottom {
corners.insert(.bottomLeft)
corners.insert(.bottomRight)
}
let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: 11.0, height: 11.0))
context.addPath(path.cgPath)
context.fillPath()
})?.stretchableImage(withLeftCapWidth: 25, topCapHeight: 25)
})
}
} }

View File

@ -69,7 +69,7 @@ final class ChatButtonKeyboardInputNode: ChatInputNode {
} }
} }
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, isVisible: Bool) -> (CGFloat, CGFloat) { override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, deviceMetrics: DeviceMetrics, isVisible: Bool) -> (CGFloat, CGFloat) {
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: UIScreenPixel))) transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: UIScreenPixel)))
if self.theme !== interfaceState.theme { if self.theme !== interfaceState.theme {

View File

@ -4152,7 +4152,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
subscriber.putCompletion() subscriber.putCompletion()
return EmptyDisposable return EmptyDisposable
} }
subscriber.putNext(strongSelf.traceVisibility() && isTopmostChatController(strongSelf))
subscriber.putNext(strongSelf.traceVisibility() && isTopmostChatController(strongSelf) && !strongSelf.context.sharedContext.mediaManager.audioSession.isOtherAudioPlaying())
subscriber.putCompletion() subscriber.putCompletion()
return EmptyDisposable return EmptyDisposable
} |> then(.complete() |> delay(1.0, queue: Queue.mainQueue())) |> restart } |> then(.complete() |> delay(1.0, queue: Queue.mainQueue())) |> restart

View File

@ -591,7 +591,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.insertSubnode(inputNode, aboveSubnode: self.inputPanelBackgroundNode) self.insertSubnode(inputNode, aboveSubnode: self.inputPanelBackgroundNode)
} }
} }
inputNodeHeightAndOverflow = inputNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: cleanInsets.bottom, standardInputHeight: layout.standardInputHeight, inputHeight: layout.inputHeight ?? 0.0, maximumHeight: maximumInputNodeHeight, inputPanelHeight: inputPanelNodeBaseHeight, transition: immediatelyLayoutInputNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState, isVisible: true) inputNodeHeightAndOverflow = inputNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: cleanInsets.bottom, standardInputHeight: layout.standardInputHeight, inputHeight: layout.inputHeight ?? 0.0, maximumHeight: maximumInputNodeHeight, inputPanelHeight: inputPanelNodeBaseHeight, transition: immediatelyLayoutInputNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState, deviceMetrics: layout.deviceMetrics, isVisible: true)
} else if let inputNode = self.inputNode { } else if let inputNode = self.inputNode {
dismissedInputNode = inputNode dismissedInputNode = inputNode
self.inputNode = nil self.inputNode = nil
@ -672,7 +672,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
} }
if let inputMediaNode = self.inputMediaNode, inputMediaNode != self.inputNode { if let inputMediaNode = self.inputMediaNode, inputMediaNode != self.inputNode {
let _ = inputMediaNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: cleanInsets.bottom, standardInputHeight: layout.standardInputHeight, inputHeight: layout.inputHeight ?? 0.0, maximumHeight: maximumInputNodeHeight, inputPanelHeight: inputPanelSize?.height ?? 0.0, transition: .immediate, interfaceState: self.chatPresentationInterfaceState, isVisible: false) let _ = inputMediaNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: cleanInsets.bottom, standardInputHeight: layout.standardInputHeight, inputHeight: layout.inputHeight ?? 0.0, maximumHeight: maximumInputNodeHeight, inputPanelHeight: inputPanelSize?.height ?? 0.0, transition: .immediate, interfaceState: self.chatPresentationInterfaceState, deviceMetrics: layout.deviceMetrics, isVisible: false)
} }
transition.updateFrame(node: self.titleAccessoryPanelContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: 56.0))) transition.updateFrame(node: self.titleAccessoryPanelContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: 56.0)))
@ -1573,7 +1573,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
inputNode.interfaceInteraction = interfaceInteraction inputNode.interfaceInteraction = interfaceInteraction
self.inputMediaNode = inputNode self.inputMediaNode = inputNode
if let (validLayout, _) = self.validLayout { if let (validLayout, _) = self.validLayout {
let _ = inputNode.updateLayout(width: validLayout.size.width, leftInset: validLayout.safeInsets.left, rightInset: validLayout.safeInsets.right, bottomInset: validLayout.intrinsicInsets.bottom, standardInputHeight: validLayout.standardInputHeight, inputHeight: validLayout.inputHeight ?? 0.0, maximumHeight: validLayout.standardInputHeight, inputPanelHeight: 44.0, transition: .immediate, interfaceState: self.chatPresentationInterfaceState, isVisible: false) let _ = inputNode.updateLayout(width: validLayout.size.width, leftInset: validLayout.safeInsets.left, rightInset: validLayout.safeInsets.right, bottomInset: validLayout.intrinsicInsets.bottom, standardInputHeight: validLayout.standardInputHeight, inputHeight: validLayout.inputHeight ?? 0.0, maximumHeight: validLayout.standardInputHeight, inputPanelHeight: 44.0, transition: .immediate, interfaceState: self.chatPresentationInterfaceState, deviceMetrics: validLayout.deviceMetrics, isVisible: false)
} }
self.textInputPanelNode?.loadTextInputNodeIfNeeded() self.textInputPanelNode?.loadTextInputNodeIfNeeded()

View File

@ -10,7 +10,7 @@ class ChatInputNode: ASDisplayNode {
return .single(Void()) return .single(Void())
} }
func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, isVisible: Bool) -> (CGFloat, CGFloat) { func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, deviceMetrics: DeviceMetrics, isVisible: Bool) -> (CGFloat, CGFloat) {
return (0.0, 0.0) return (0.0, 0.0)
} }
} }

View File

@ -34,7 +34,7 @@ private func canEditMessage(accountPeerId: PeerId, limitsConfiguration: LimitsCo
if let peer = message.peers[message.id.peerId], let channel = peer as? TelegramChannel { if let peer = message.peers[message.id.peerId], let channel = peer as? TelegramChannel {
switch channel.info { switch channel.info {
case .broadcast: case .broadcast:
if channel.hasPermission(.editAllMessages) { if message.author?.id == message.id.peerId || channel.hasPermission(.editAllMessages) {
hasEditRights = true hasEditRights = true
} }
default: default:

View File

@ -36,7 +36,7 @@ final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate {
private let disposable = MetaDisposable() private let disposable = MetaDisposable()
let trendingPromise = Promise<[FileMediaReference]?>(nil) let trendingPromise = Promise<[FileMediaReference]?>(nil)
private var validLayout: (CGSize, CGFloat, CGFloat, Bool, Bool)? private var validLayout: (CGSize, CGFloat, CGFloat, Bool, Bool, DeviceMetrics)?
private var didScrollPreviousOffset: CGFloat? private var didScrollPreviousOffset: CGFloat?
private var didScrollPreviousState: ChatMediaInputPaneScrollState? private var didScrollPreviousState: ChatMediaInputPaneScrollState?
@ -76,18 +76,18 @@ final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate {
self.searchPlaceholderNode.setup(theme: theme, strings: strings, type: .gifs) self.searchPlaceholderNode.setup(theme: theme, strings: strings, type: .gifs)
if let layout = self.validLayout { if let layout = self.validLayout {
self.updateLayout(size: layout.0, topInset: layout.1, bottomInset: layout.2, isExpanded: layout.3, isVisible: layout.4, transition: .immediate) self.updateLayout(size: layout.0, topInset: layout.1, bottomInset: layout.2, isExpanded: layout.3, isVisible: layout.4, deviceMetrics: layout.5, transition: .immediate)
} }
} }
override func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, isExpanded: Bool, isVisible: Bool, transition: ContainedViewLayoutTransition) { override func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, isExpanded: Bool, isVisible: Bool, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition) {
var changedIsExpanded = false var changedIsExpanded = false
if let (_, _, _, previousIsExpanded, _) = self.validLayout { if let (_, _, _, previousIsExpanded, _, _) = self.validLayout {
if previousIsExpanded != isExpanded { if previousIsExpanded != isExpanded {
changedIsExpanded = true changedIsExpanded = true
} }
} }
self.validLayout = (size, topInset, bottomInset, isExpanded, isVisible) self.validLayout = (size, topInset, bottomInset, isExpanded, isVisible, deviceMetrics)
let emptySize = self.emptyNode.updateLayout(size) let emptySize = self.emptyNode.updateLayout(size)
transition.updateFrame(node: self.emptyNode, frame: CGRect(origin: CGPoint(x: floor(size.width - emptySize.width) / 2.0, y: topInset + floor(size.height - topInset - emptySize.height) / 2.0), size: emptySize)) transition.updateFrame(node: self.emptyNode, frame: CGRect(origin: CGPoint(x: floor(size.width - emptySize.width) / 2.0, y: topInset + floor(size.height - topInset - emptySize.height) / 2.0), size: emptySize))
@ -96,6 +96,13 @@ final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate {
let previousBounds = multiplexedNode.layer.bounds let previousBounds = multiplexedNode.layer.bounds
multiplexedNode.topInset = topInset + 60.0 multiplexedNode.topInset = topInset + 60.0
multiplexedNode.bottomInset = bottomInset multiplexedNode.bottomInset = bottomInset
if case .tablet = deviceMetrics.type, size.width > 480.0 {
multiplexedNode.idealHeight = 120.0
} else {
multiplexedNode.idealHeight = 93.0
}
let nodeFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)) let nodeFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))
var targetBounds = CGRect(origin: previousBounds.origin, size: nodeFrame.size) var targetBounds = CGRect(origin: previousBounds.origin, size: nodeFrame.size)

View File

@ -425,7 +425,7 @@ final class ChatMediaInputNode: ChatInputNode {
private var currentView: ItemCollectionsView? private var currentView: ItemCollectionsView?
private let dismissedPeerSpecificStickerPack = Promise<Bool>() private let dismissedPeerSpecificStickerPack = Promise<Bool>()
private var validLayout: (CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, ChatPresentationInterfaceState, Bool)? private var validLayout: (CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, ChatPresentationInterfaceState, DeviceMetrics, Bool)?
private var paneArrangement: ChatMediaInputPaneArrangement private var paneArrangement: ChatMediaInputPaneArrangement
private var initializedArrangement = false private var initializedArrangement = false
@ -1045,8 +1045,8 @@ final class ChatMediaInputNode: ChatInputNode {
if let index = self.paneArrangement.panes.firstIndex(of: pane), index != self.paneArrangement.currentIndex { if let index = self.paneArrangement.panes.firstIndex(of: pane), index != self.paneArrangement.currentIndex {
let previousGifPanelWasActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .gifs let previousGifPanelWasActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .gifs
self.paneArrangement = self.paneArrangement.withIndexTransition(0.0).withCurrentIndex(index) self.paneArrangement = self.paneArrangement.withIndexTransition(0.0).withCurrentIndex(index)
if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, isVisible) = self.validLayout { if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.validLayout {
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .animated(duration: 0.25, curve: .spring), interfaceState: interfaceState, isVisible: isVisible) let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .animated(duration: 0.25, curve: .spring), interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible)
self.updateAppearanceTransition(transition: transition) self.updateAppearanceTransition(transition: transition)
} }
let updatedGifPanelWasActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .gifs let updatedGifPanelWasActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .gifs
@ -1066,8 +1066,8 @@ final class ChatMediaInputNode: ChatInputNode {
self.setHighlightedItemCollectionId(ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue, id: 0)) self.setHighlightedItemCollectionId(ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue, id: 0))
} }
} else { } else {
if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, isVisible) = self.validLayout { if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.validLayout {
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .animated(duration: 0.25, curve: .spring), interfaceState: interfaceState, isVisible: isVisible) let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .animated(duration: 0.25, curve: .spring), interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible)
} }
} }
} }
@ -1195,13 +1195,13 @@ final class ChatMediaInputNode: ChatInputNode {
} }
} }
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, isVisible: Bool) -> (CGFloat, CGFloat) { override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, deviceMetrics: DeviceMetrics, isVisible: Bool) -> (CGFloat, CGFloat) {
var searchMode: ChatMediaInputSearchMode? var searchMode: ChatMediaInputSearchMode?
if let (_, _, _, _, _, _, _, _, interfaceState, _) = self.validLayout, case let .media(_, maybeExpanded) = interfaceState.inputMode, let expanded = maybeExpanded, case let .search(mode) = expanded { if let (_, _, _, _, _, _, _, _, interfaceState, _, _) = self.validLayout, case let .media(_, maybeExpanded) = interfaceState.inputMode, let expanded = maybeExpanded, case let .search(mode) = expanded {
searchMode = mode searchMode = mode
} }
self.validLayout = (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, isVisible) self.validLayout = (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible)
if self.theme !== interfaceState.theme || self.strings !== interfaceState.strings { if self.theme !== interfaceState.theme || self.strings !== interfaceState.strings {
self.updateThemeAndStrings(theme: interfaceState.theme, strings: interfaceState.strings) self.updateThemeAndStrings(theme: interfaceState.theme, strings: interfaceState.strings)
@ -1235,12 +1235,12 @@ final class ChatMediaInputNode: ChatInputNode {
let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: -inputPanelHeight), size: CGSize(width: width, height: panelHeight + inputPanelHeight)) let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: -inputPanelHeight), size: CGSize(width: width, height: panelHeight + inputPanelHeight))
if searchContainerNode.supernode != nil { if searchContainerNode.supernode != nil {
transition.updateFrame(node: searchContainerNode, frame: containerFrame) transition.updateFrame(node: searchContainerNode, frame: containerFrame)
searchContainerNode.updateLayout(size: containerFrame.size, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, inputHeight: inputHeight, transition: transition) searchContainerNode.updateLayout(size: containerFrame.size, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, inputHeight: inputHeight, deviceMetrics: deviceMetrics, transition: transition)
} else { } else {
self.searchContainerNode = searchContainerNode self.searchContainerNode = searchContainerNode
self.insertSubnode(searchContainerNode, belowSubnode: self.collectionListContainer) self.insertSubnode(searchContainerNode, belowSubnode: self.collectionListContainer)
searchContainerNode.frame = containerFrame searchContainerNode.frame = containerFrame
searchContainerNode.updateLayout(size: containerFrame.size, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, inputHeight: inputHeight, transition: .immediate) searchContainerNode.updateLayout(size: containerFrame.size, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, inputHeight: inputHeight, deviceMetrics: deviceMetrics, transition: .immediate)
var placeholderNode: PaneSearchBarPlaceholderNode? var placeholderNode: PaneSearchBarPlaceholderNode?
if let searchMode = searchMode { if let searchMode = searchMode {
switch searchMode { switch searchMode {
@ -1349,10 +1349,9 @@ final class ChatMediaInputNode: ChatInputNode {
} }
} }
self.gifPane.updateLayout(size: CGSize(width: width - leftInset - rightInset, height: panelHeight), topInset: 41.0, bottomInset: bottomInset, isExpanded: isExpanded, isVisible: isVisible, transition: transition) self.gifPane.updateLayout(size: CGSize(width: width - leftInset - rightInset, height: panelHeight), topInset: 41.0, bottomInset: bottomInset, isExpanded: isExpanded, isVisible: isVisible, deviceMetrics: deviceMetrics, transition: transition)
self.stickerPane.updateLayout(size: CGSize(width: width - leftInset - rightInset, height: panelHeight), topInset: 41.0, bottomInset: bottomInset, isExpanded: isExpanded, isVisible: isVisible && visiblePanes.contains(where: { $0.0 == .stickers }), deviceMetrics: deviceMetrics, transition: transition)
self.stickerPane.updateLayout(size: CGSize(width: width - leftInset - rightInset, height: panelHeight), topInset: 41.0, bottomInset: bottomInset, isExpanded: isExpanded, isVisible: isVisible && visiblePanes.contains(where: { $0.0 == .stickers }), transition: transition) self.trendingPane.updateLayout(size: CGSize(width: width - leftInset - rightInset, height: panelHeight), topInset: 41.0, bottomInset: bottomInset, isExpanded: isExpanded, isVisible: isVisible, deviceMetrics: deviceMetrics, transition: transition)
self.trendingPane.updateLayout(size: CGSize(width: width - leftInset - rightInset, height: panelHeight), topInset: 41.0, bottomInset: bottomInset, isExpanded: isExpanded, isVisible: isVisible, transition: transition)
if self.gifPane.supernode != nil { if self.gifPane.supernode != nil {
if !visiblePanes.contains(where: { $0.0 == .gifs }) { if !visiblePanes.contains(where: { $0.0 == .gifs }) {
@ -1525,7 +1524,7 @@ final class ChatMediaInputNode: ChatInputNode {
self.trendingPane.removeFromSupernode() self.trendingPane.removeFromSupernode()
} }
case .changed: case .changed:
if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, isVisible) = self.validLayout { if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.validLayout {
let translationX = -recognizer.translation(in: self.view).x let translationX = -recognizer.translation(in: self.view).x
var indexTransition = translationX / width var indexTransition = translationX / width
if self.paneArrangement.currentIndex == 0 { if self.paneArrangement.currentIndex == 0 {
@ -1534,10 +1533,10 @@ final class ChatMediaInputNode: ChatInputNode {
indexTransition = min(0.0, indexTransition) indexTransition = min(0.0, indexTransition)
} }
self.paneArrangement = self.paneArrangement.withIndexTransition(indexTransition) self.paneArrangement = self.paneArrangement.withIndexTransition(indexTransition)
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .immediate, interfaceState: interfaceState, isVisible: isVisible) let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .immediate, interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible)
} }
case .ended: case .ended:
if let (width, _, _, _, _, _, _, _, _, _) = self.validLayout { if let (width, _, _, _, _, _, _, _, _, _, _) = self.validLayout {
var updatedIndex = self.paneArrangement.currentIndex var updatedIndex = self.paneArrangement.currentIndex
if abs(self.paneArrangement.indexTransition * width) > 30.0 { if abs(self.paneArrangement.indexTransition * width) > 30.0 {
if self.paneArrangement.indexTransition < 0.0 { if self.paneArrangement.indexTransition < 0.0 {
@ -1550,9 +1549,9 @@ final class ChatMediaInputNode: ChatInputNode {
self.setCurrentPane(self.paneArrangement.panes[updatedIndex], transition: .animated(duration: 0.25, curve: .spring)) self.setCurrentPane(self.paneArrangement.panes[updatedIndex], transition: .animated(duration: 0.25, curve: .spring))
} }
case .cancelled: case .cancelled:
if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, isVisible) = self.validLayout { if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.validLayout {
self.paneArrangement = self.paneArrangement.withIndexTransition(0.0) self.paneArrangement = self.paneArrangement.withIndexTransition(0.0)
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .animated(duration: 0.25, curve: .spring), interfaceState: interfaceState, isVisible: isVisible) let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .animated(duration: 0.25, curve: .spring), interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible)
} }
default: default:
break break

View File

@ -16,7 +16,7 @@ class ChatMediaInputPane: ASDisplayNode {
return false return false
} }
func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, isExpanded: Bool, isVisible: Bool, transition: ContainedViewLayoutTransition) { func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, isExpanded: Bool, isVisible: Bool, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition) {
} }
func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {

View File

@ -251,7 +251,7 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
if self.currentSize != size { if self.currentSize != size {
self.currentSize = size self.currentSize = size
let sideSize: CGFloat = min(75.0 - 10.0, size.width) let sideSize: CGFloat = size.width - 10.0 //min(75.0 - 10.0, size.width)
let boundingSize = CGSize(width: sideSize, height: sideSize) let boundingSize = CGSize(width: sideSize, height: sideSize)
if let (_, _, mediaDimensions) = self.currentState { if let (_, _, mediaDimensions) = self.currentState {
@ -271,7 +271,7 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
return return
} }
if let interfaceInteraction = self.interfaceInteraction, let (_, item, _) = self.currentState, case .ended = recognizer.state { if let interfaceInteraction = self.interfaceInteraction, let (_, item, _) = self.currentState, case .ended = recognizer.state {
interfaceInteraction.sendSticker(.standalone(media: item.file), false, self, self.bounds) let _ = interfaceInteraction.sendSticker(.standalone(media: item.file), false, self, self.bounds)
self.imageNode.layer.animateAlpha(from: 0.5, to: 1.0, duration: 1.0) self.imageNode.layer.animateAlpha(from: 0.5, to: 1.0, duration: 1.0)
} }
} }

View File

@ -114,7 +114,7 @@ final class ChatMediaInputStickerPane: ChatMediaInputPane {
self.gridNode.scrollView.alwaysBounceVertical = true self.gridNode.scrollView.alwaysBounceVertical = true
} }
override func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, isExpanded: Bool, isVisible: Bool, transition: ContainedViewLayoutTransition) { override func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, isExpanded: Bool, isVisible: Bool, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition) {
var changedIsExpanded = false var changedIsExpanded = false
if let previousIsExpanded = self.isExpanded { if let previousIsExpanded = self.isExpanded {
if previousIsExpanded != isExpanded { if previousIsExpanded != isExpanded {
@ -123,10 +123,17 @@ final class ChatMediaInputStickerPane: ChatMediaInputPane {
} }
self.isExpanded = isExpanded self.isExpanded = isExpanded
let maxItemSize: CGSize
if case .tablet = deviceMetrics.type, size.width > 480.0 {
maxItemSize = CGSize(width: 90.0, height: 96.0)
} else {
maxItemSize = CGSize(width: 75.0, height: 80.0)
}
let sideInset: CGFloat = 2.0 let sideInset: CGFloat = 2.0
var itemSide: CGFloat = floor((size.width - sideInset * 2.0) / 5.0) var itemSide: CGFloat = floor((size.width - sideInset * 2.0) / 5.0)
itemSide = min(itemSide, 75.0) itemSide = min(itemSide, maxItemSize.width)
let itemSize = CGSize(width: itemSide, height: max(itemSide, 80.0)) let itemSize = CGSize(width: itemSide, height: max(itemSide, maxItemSize.height))
var scrollToItem: GridNodeScrollToItem? var scrollToItem: GridNodeScrollToItem?
if changedIsExpanded { if changedIsExpanded {

View File

@ -75,24 +75,31 @@ private final class TrendingPaneEntry: Identifiable, Comparable {
return lhs.index < rhs.index return lhs.index < rhs.index
} }
func item(account: Account, interaction: TrendingPaneInteraction) -> ListViewItem { func item(account: Account, interaction: TrendingPaneInteraction) -> GridItem {
return MediaInputPaneTrendingItem(account: account, theme: self.theme, strings: self.strings, interaction: interaction, info: self.info, topItems: self.topItems, installed: self.installed, unread: self.unread) let info = self.info
return StickerPaneSearchGlobalItem(account: account, theme: self.theme, strings: self.strings, info: self.info, topItems: self.topItems, grid: true, installed: self.installed, unread: self.unread, open: {
interaction.openPack(info)
}, install: {
interaction.installPack(info)
}, getItemIsPreviewed: { item in
return interaction.getItemIsPreviewed(item)
})
} }
} }
private struct TrendingPaneTransition { private struct TrendingPaneTransition {
let deletions: [ListViewDeleteItem] let deletions: [Int]
let insertions: [ListViewInsertItem] let insertions: [GridNodeInsertItem]
let updates: [ListViewUpdateItem] let updates: [GridNodeUpdateItem]
let initial: Bool let initial: Bool
} }
private func preparedTransition(from fromEntries: [TrendingPaneEntry], to toEntries: [TrendingPaneEntry], account: Account, interaction: TrendingPaneInteraction, initial: Bool) -> TrendingPaneTransition { private func preparedTransition(from fromEntries: [TrendingPaneEntry], to toEntries: [TrendingPaneEntry], account: Account, interaction: TrendingPaneInteraction, initial: Bool) -> TrendingPaneTransition {
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
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, interaction: interaction), directionHint: nil) } let insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(account: account, interaction: interaction), previousIndex: $0.2) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, interaction: interaction), directionHint: nil) } let updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, interaction: interaction)) }
return TrendingPaneTransition(deletions: deletions, insertions: insertions, updates: updates, initial: initial) return TrendingPaneTransition(deletions: deletions, insertions: insertions, updates: updates, initial: initial)
} }
@ -114,7 +121,7 @@ final class ChatMediaInputTrendingPane: ChatMediaInputPane {
private let controllerInteraction: ChatControllerInteraction private let controllerInteraction: ChatControllerInteraction
private let getItemIsPreviewed: (StickerPackItem) -> Bool private let getItemIsPreviewed: (StickerPackItem) -> Bool
private let listNode: ListView let gridNode: GridNode
private var enqueuedTransitions: [TrendingPaneTransition] = [] private var enqueuedTransitions: [TrendingPaneTransition] = []
private var validLayout: (CGSize, CGFloat)? private var validLayout: (CGSize, CGFloat)?
@ -135,13 +142,13 @@ final class ChatMediaInputTrendingPane: ChatMediaInputPane {
self.controllerInteraction = controllerInteraction self.controllerInteraction = controllerInteraction
self.getItemIsPreviewed = getItemIsPreviewed self.getItemIsPreviewed = getItemIsPreviewed
self.listNode = ListView() self.gridNode = GridNode()
super.init() super.init()
self.addSubnode(self.listNode) self.addSubnode(self.gridNode)
self.listNode.beganInteractiveDragging = { [weak self] in self.gridNode.scrollingInitiated = { [weak self] in
self?.scrollingInitiated?() self?.scrollingInitiated?()
} }
} }
@ -224,28 +231,39 @@ final class ChatMediaInputTrendingPane: ChatMediaInputPane {
}) })
} }
override func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, isExpanded: Bool, isVisible: Bool, transition: ContainedViewLayoutTransition) { override func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, isExpanded: Bool, isVisible: Bool, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition) {
let hadValidLayout = self.validLayout != nil let hadValidLayout = self.validLayout != nil
self.validLayout = (size, bottomInset) self.validLayout = (size, bottomInset)
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(), size: size)) let itemSize: CGSize
if case .tablet = deviceMetrics.type, size.width > 480.0 {
var duration: Double = 0.0 itemSize = CGSize(width: floor(size.width / 2.0), height: 128.0)
var listViewCurve: ListViewAnimationCurve = .Default(duration: nil) } else {
switch transition { itemSize = CGSize(width: size.width, height: 128.0)
case .immediate:
break
case let .animated(animationDuration, animationCurve):
duration = animationDuration
switch animationCurve {
case .easeInOut, .custom:
listViewCurve = .Default(duration: duration)
case .spring:
listViewCurve = .Spring(duration: duration)
}
} }
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: size, insets: UIEdgeInsets(top: topInset, left: 0.0, bottom: bottomInset, right: 0.0), duration: duration, curve: listViewCurve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: size, insets: UIEdgeInsets(top: topInset, left: 0.0, bottom: bottomInset, right: 0.0), preloadSize: isVisible ? 300.0 : 0.0, type: .fixed(itemSize: itemSize, fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in })
transition.updateFrame(node: self.gridNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height)))
// transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(), size: size))
//
// var duration: Double = 0.0
// var listViewCurve: ListViewAnimationCurve = .Default(duration: nil)
// switch transition {
// case .immediate:
// break
// case let .animated(animationDuration, animationCurve):
// duration = animationDuration
// switch animationCurve {
// case .easeInOut, .custom:
// listViewCurve = .Default(duration: duration)
// case .spring:
// listViewCurve = .Spring(duration: duration)
// }
// }
//
// self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: size, insets: UIEdgeInsets(top: topInset, left: 0.0, bottom: bottomInset, right: 0.0), duration: duration, curve: listViewCurve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
if !hadValidLayout { if !hadValidLayout {
while !self.enqueuedTransitions.isEmpty { while !self.enqueuedTransitions.isEmpty {
@ -274,38 +292,28 @@ final class ChatMediaInputTrendingPane: ChatMediaInputPane {
if let transition = self.enqueuedTransitions.first { if let transition = self.enqueuedTransitions.first {
self.enqueuedTransitions.remove(at: 0) self.enqueuedTransitions.remove(at: 0)
var options = ListViewDeleteAndInsertOptions() let itemTransition: ContainedViewLayoutTransition = .immediate
if transition.initial { self.gridNode.transaction(GridNodeTransaction(deleteItems: transition.deletions, insertItems: transition.insertions, updateItems: transition.updates, scrollToItem: nil, updateLayout: nil, itemTransition: itemTransition, stationaryItems: .none, updateFirstIndexInSectionOffset: nil, synchronousLoads: transition.initial), completion: { _ in })
options.insert(.Synchronous)
options.insert(.LowLatency)
options.insert(.PreferSynchronousResourceLoading)
} else {
options.insert(.AnimateInsertion)
}
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { _ in
})
} }
} }
func itemAt(point: CGPoint) -> (ASDisplayNode, StickerPackItem)? { func itemAt(point: CGPoint) -> (ASDisplayNode, StickerPackItem)? {
let localPoint = self.view.convert(point, to: self.listNode.view) let localPoint = self.view.convert(point, to: self.gridNode.view)
var resultNode: MediaInputPaneTrendingItemNode? var resultNode: StickerPaneSearchGlobalItemNode?
self.listNode.forEachItemNode { itemNode in self.gridNode.forEachItemNode { itemNode in
if itemNode.frame.contains(localPoint), let itemNode = itemNode as? MediaInputPaneTrendingItemNode { if itemNode.frame.contains(localPoint), let itemNode = itemNode as? StickerPaneSearchGlobalItemNode {
resultNode = itemNode resultNode = itemNode
} }
} }
if let resultNode = resultNode { if let resultNode = resultNode {
return resultNode.itemAt(point: self.listNode.view.convert(localPoint, to: resultNode.view)) return resultNode.itemAt(point: self.gridNode.view.convert(localPoint, to: resultNode.view))
} }
return nil return nil
} }
func updatePreviewing(animated: Bool) { func updatePreviewing(animated: Bool) {
self.listNode.forEachItemNode { itemNode in self.gridNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? MediaInputPaneTrendingItemNode { if let itemNode = itemNode as? StickerPaneSearchGlobalItemNode {
itemNode.updatePreviewing(animated: animated) itemNode.updatePreviewing(animated: animated)
} }
} }

View File

@ -31,6 +31,7 @@ struct ChatMessageItemBubbleLayoutConstants {
let minimumSize: CGSize let minimumSize: CGSize
let contentInsets: UIEdgeInsets let contentInsets: UIEdgeInsets
let borderInset: CGFloat let borderInset: CGFloat
let strokeInsets: UIEdgeInsets
} }
struct ChatMessageItemTextLayoutConstants { struct ChatMessageItemTextLayoutConstants {
@ -81,7 +82,7 @@ struct ChatMessageItemLayoutConstants {
self.avatarDiameter = 37.0 self.avatarDiameter = 37.0
self.timestampHeaderHeight = 34.0 self.timestampHeaderHeight = 34.0
self.bubble = ChatMessageItemBubbleLayoutConstants(edgeInset: 4.0, defaultSpacing: 2.0 + UIScreenPixel, mergedSpacing: 1.0, maximumWidthFill: ChatMessageItemWidthFill(compactInset: 36.0, compactWidthBoundary: 500.0, freeMaximumFillFactor: 0.85), minimumSize: CGSize(width: 40.0, height: 35.0), contentInsets: UIEdgeInsets(top: 0.0, left: 6.0, bottom: 0.0, right: 0.0), borderInset: UIScreenPixel) self.bubble = ChatMessageItemBubbleLayoutConstants(edgeInset: 4.0, defaultSpacing: 2.0 + UIScreenPixel, mergedSpacing: 1.0, maximumWidthFill: ChatMessageItemWidthFill(compactInset: 36.0, compactWidthBoundary: 500.0, freeMaximumFillFactor: 0.85), minimumSize: CGSize(width: 40.0, height: 35.0), contentInsets: UIEdgeInsets(top: 0.0, left: 6.0, bottom: 0.0, right: 0.0), borderInset: UIScreenPixel, strokeInsets: UIEdgeInsets(top: 1.0 - UIScreenPixel, left: 1.0 - UIScreenPixel, bottom: 1.0 - UIScreenPixel, right: 1.0 - UIScreenPixel))
self.text = ChatMessageItemTextLayoutConstants(bubbleInsets: UIEdgeInsets(top: 6.0 + UIScreenPixel, left: 12.0, bottom: 6.0 - UIScreenPixel, right: 12.0)) self.text = ChatMessageItemTextLayoutConstants(bubbleInsets: UIEdgeInsets(top: 6.0 + UIScreenPixel, left: 12.0, bottom: 6.0 - UIScreenPixel, right: 12.0))
self.image = ChatMessageItemImageLayoutConstants(bubbleInsets: UIEdgeInsets(top: 1.0 + UIScreenPixel, left: 1.0 + UIScreenPixel, bottom: 1.0 + UIScreenPixel, right: 1.0 + UIScreenPixel), statusInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 6.0, right: 6.0), defaultCornerRadius: 17.0, mergedCornerRadius: 5.0, contentMergedCornerRadius: 5.0, maxDimensions: CGSize(width: 300.0, height: 300.0), minDimensions: CGSize(width: 170.0, height: 74.0)) self.image = ChatMessageItemImageLayoutConstants(bubbleInsets: UIEdgeInsets(top: 1.0 + UIScreenPixel, left: 1.0 + UIScreenPixel, bottom: 1.0 + UIScreenPixel, right: 1.0 + UIScreenPixel), statusInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 6.0, right: 6.0), defaultCornerRadius: 17.0, mergedCornerRadius: 5.0, contentMergedCornerRadius: 5.0, maxDimensions: CGSize(width: 300.0, height: 300.0), minDimensions: CGSize(width: 170.0, height: 74.0))
self.video = ChatMessageItemVideoLayoutConstants(maxHorizontalHeight: 250.0, maxVerticalHeight: 360.0) self.video = ChatMessageItemVideoLayoutConstants(maxHorizontalHeight: 250.0, maxVerticalHeight: 360.0)

View File

@ -6,6 +6,7 @@ import SwiftSignalKit
import Postbox import Postbox
import TelegramCore import TelegramCore
import TelegramUIPreferences import TelegramUIPreferences
import TelegramPresentationData
import AccountContext import AccountContext
import GridMessageSelectionNode import GridMessageSelectionNode
@ -115,7 +116,17 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
switch preparePosition { switch preparePosition {
case .linear: case .linear:
if case .color = item.presentationData.theme.wallpaper { if case .color = item.presentationData.theme.wallpaper {
bubbleInsets = UIEdgeInsets() let colors: PresentationThemeBubbleColorComponents
if item.message.effectivelyIncoming(item.context.account.peerId) {
colors = item.presentationData.theme.theme.chat.message.incoming.bubble.withoutWallpaper
} else {
colors = item.presentationData.theme.theme.chat.message.outgoing.bubble.withoutWallpaper
}
if colors.fill == colors.stroke {
bubbleInsets = UIEdgeInsets()
} else {
bubbleInsets = layoutConstants.bubble.strokeInsets
}
} else { } else {
bubbleInsets = layoutConstants.image.bubbleInsets bubbleInsets = layoutConstants.image.bubbleInsets
} }

View File

@ -666,7 +666,6 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
func animateLayoutTransition() { func animateLayoutTransition() {
UIView.transition(with: self, duration: 0.25, options: [.transitionCrossDissolve], animations: { UIView.transition(with: self, duration: 0.25, options: [.transitionCrossDissolve], animations: {
}, completion: nil) }, completion: nil)
} }
} }

View File

@ -196,7 +196,7 @@ final class GifPaneSearchContentNode: ASDisplayNode & PaneSearchContentNode {
} }
} }
func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, inputHeight: CGFloat, transition: ContainedViewLayoutTransition) { func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, inputHeight: CGFloat, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition) {
let firstLayout = self.validLayout == nil let firstLayout = self.validLayout == nil
self.validLayout = size self.validLayout = size

View File

@ -51,6 +51,12 @@ final class MultiplexedVideoNode: ASScrollNode, UIScrollViewDelegate {
} }
} }
var idealHeight: CGFloat = 93.0 {
didSet {
self.setNeedsLayout()
}
}
var files: [FileMediaReference] = [] { var files: [FileMediaReference] = [] {
didSet { didSet {
self.updateVisibleItems() self.updateVisibleItems()
@ -341,7 +347,7 @@ final class MultiplexedVideoNode: ASScrollNode, UIScrollViewDelegate {
if !drawableSize.width.isZero { if !drawableSize.width.isZero {
var displayItems: [VisibleVideoItem] = [] var displayItems: [VisibleVideoItem] = []
let idealHeight: CGFloat = 93.0 let idealHeight = self.idealHeight
var weights: [Int] = [] var weights: [Int] = []
var totalItemSize: CGFloat = 0.0 var totalItemSize: CGFloat = 0.0

View File

@ -17,7 +17,7 @@ protocol PaneSearchContentNode {
func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings)
func updateText(_ text: String, languageCode: String?) func updateText(_ text: String, languageCode: String?)
func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, inputHeight: CGFloat, transition: ContainedViewLayoutTransition) func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, inputHeight: CGFloat, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition)
func animateIn(additivePosition: CGFloat, transition: ContainedViewLayoutTransition) func animateIn(additivePosition: CGFloat, transition: ContainedViewLayoutTransition)
func animateOut(transition: ContainedViewLayoutTransition) func animateOut(transition: ContainedViewLayoutTransition)
@ -103,7 +103,7 @@ final class PaneSearchContainerNode: ASDisplayNode {
return self.contentNode.itemAt(point: CGPoint(x: point.x, y: point.y - searchBarHeight)) return self.contentNode.itemAt(point: CGPoint(x: point.x, y: point.y - searchBarHeight))
} }
func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, inputHeight: CGFloat, transition: ContainedViewLayoutTransition) { func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, inputHeight: CGFloat, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition) {
self.validLayout = size self.validLayout = size
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size)) transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size))
@ -113,7 +113,7 @@ final class PaneSearchContainerNode: ASDisplayNode {
let contentFrame = CGRect(origin: CGPoint(x: leftInset, y: searchBarHeight), size: CGSize(width: size.width - leftInset - rightInset, height: size.height - searchBarHeight)) let contentFrame = CGRect(origin: CGPoint(x: leftInset, y: searchBarHeight), size: CGSize(width: size.width - leftInset - rightInset, height: size.height - searchBarHeight))
transition.updateFrame(node: self.contentNode, frame: contentFrame) transition.updateFrame(node: self.contentNode, frame: contentFrame)
self.contentNode.updateLayout(size: contentFrame.size, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, inputHeight: inputHeight, transition: transition) self.contentNode.updateLayout(size: contentFrame.size, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, inputHeight: inputHeight, deviceMetrics: deviceMetrics, transition: transition)
} }
func deactivate() { func deactivate() {

View File

@ -4,12 +4,13 @@ import AsyncDisplayKit
import Display import Display
import TelegramCore import TelegramCore
import TelegramPresentationData import TelegramPresentationData
import SegmentedControlNode
final class PeerMediaCollectionSectionsNode: ASDisplayNode { final class PeerMediaCollectionSectionsNode: ASDisplayNode {
private var theme: PresentationTheme private var theme: PresentationTheme
private var strings: PresentationStrings private var strings: PresentationStrings
private let segmentedControl: UISegmentedControl private let segmentedControlNode: SegmentedControlNode
private let separatorNode: ASDisplayNode private let separatorNode: ASDisplayNode
var indexUpdated: ((Int) -> Void)? var indexUpdated: ((Int) -> Void)?
@ -18,14 +19,13 @@ final class PeerMediaCollectionSectionsNode: ASDisplayNode {
self.theme = theme self.theme = theme
self.strings = strings self.strings = strings
self.segmentedControl = UISegmentedControl(items: [ let items = [
strings.SharedMedia_CategoryMedia, strings.SharedMedia_CategoryMedia,
strings.SharedMedia_CategoryDocs, strings.SharedMedia_CategoryDocs,
strings.SharedMedia_CategoryLinks, strings.SharedMedia_CategoryLinks,
strings.SharedMedia_CategoryOther strings.SharedMedia_CategoryOther
]) ]
self.segmentedControl.selectedSegmentIndex = 0 self.segmentedControlNode = SegmentedControlNode(theme: SegmentedControlTheme(theme: theme), items: items.map { SegmentedControlItem(title: $0) }, selectedIndex: 0)
self.segmentedControl.tintColor = theme.rootController.navigationBar.accentTextColor
self.separatorNode = ASDisplayNode() self.separatorNode = ASDisplayNode()
self.separatorNode.isLayerBacked = true self.separatorNode.isLayerBacked = true
@ -34,22 +34,22 @@ final class PeerMediaCollectionSectionsNode: ASDisplayNode {
super.init() super.init()
self.addSubnode(self.separatorNode)
self.view.addSubview(self.segmentedControl)
self.backgroundColor = self.theme.rootController.navigationBar.backgroundColor self.backgroundColor = self.theme.rootController.navigationBar.backgroundColor
self.segmentedControl.addTarget(self, action: #selector(indexChanged), for: .valueChanged) self.segmentedControlNode.selectedIndexChanged = { [weak self] index in
self?.indexUpdated?(index)
}
self.addSubnode(self.separatorNode)
self.addSubnode(self.segmentedControlNode)
} }
func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, additionalInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: PeerMediaCollectionInterfaceState) -> CGFloat { func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, additionalInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: PeerMediaCollectionInterfaceState) -> CGFloat {
let panelHeight: CGFloat = 39.0 + additionalInset let panelHeight: CGFloat = 39.0 + additionalInset
let controlHeight: CGFloat = 29.0
let sideInset: CGFloat = 8.0 let sideInset: CGFloat = 8.0
transition.animateView {
self.segmentedControl.frame = CGRect(origin: CGPoint(x: sideInset + leftInset, y: panelHeight - 11.0 - controlHeight), size: CGSize(width: width - sideInset * 2.0 - leftInset - rightInset, height: controlHeight)) let controlSize = self.segmentedControlNode.updateLayout(.stretchToFill(width: width - sideInset * 2.0 - leftInset - rightInset), transition: transition)
} transition.updateFrame(node: self.segmentedControlNode, frame: CGRect(origin: CGPoint(x: sideInset + leftInset, y: panelHeight - 8.0 - controlSize.height), size: controlSize))
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelHeight - UIScreenPixel), size: CGSize(width: width, height: UIScreenPixel))) transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelHeight - UIScreenPixel), size: CGSize(width: width, height: UIScreenPixel)))
@ -57,13 +57,9 @@ final class PeerMediaCollectionSectionsNode: ASDisplayNode {
self.theme = interfaceState.theme self.theme = interfaceState.theme
self.separatorNode.backgroundColor = self.theme.rootController.navigationBar.separatorColor self.separatorNode.backgroundColor = self.theme.rootController.navigationBar.separatorColor
self.backgroundColor = self.theme.rootController.navigationBar.backgroundColor self.backgroundColor = self.theme.rootController.navigationBar.backgroundColor
self.segmentedControl.tintColor = theme.rootController.navigationBar.accentTextColor self.segmentedControlNode.updateTheme(SegmentedControlTheme(theme: self.theme))
} }
return panelHeight return panelHeight
} }
@objc func indexChanged() {
self.indexUpdated?(self.segmentedControl.selectedSegmentIndex)
}
} }

View File

@ -11,6 +11,7 @@ import SearchBarNode
import SearchUI import SearchUI
import ContactListUI import ContactListUI
import ChatListUI import ChatListUI
import SegmentedControlNode
final class PeerSelectionControllerNode: ASDisplayNode { final class PeerSelectionControllerNode: ASDisplayNode {
private let context: AccountContext private let context: AccountContext
@ -28,7 +29,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
private let toolbarBackgroundNode: ASDisplayNode? private let toolbarBackgroundNode: ASDisplayNode?
private let toolbarSeparatorNode: ASDisplayNode? private let toolbarSeparatorNode: ASDisplayNode?
private let segmentedControl: UISegmentedControl? private let segmentedControlNode: SegmentedControlNode?
var contactListNode: ContactListNode? var contactListNode: ContactListNode?
let chatListNode: ChatListNode let chatListNode: ChatListNode
@ -71,13 +72,15 @@ final class PeerSelectionControllerNode: ASDisplayNode {
self.toolbarSeparatorNode = ASDisplayNode() self.toolbarSeparatorNode = ASDisplayNode()
self.toolbarSeparatorNode?.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor self.toolbarSeparatorNode?.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor
self.segmentedControl = UISegmentedControl(items: [self.presentationData.strings.DialogList_TabTitle, self.presentationData.strings.Contacts_TabTitle]) let items = [
self.segmentedControl?.tintColor = self.presentationData.theme.rootController.navigationBar.accentTextColor self.presentationData.strings.DialogList_TabTitle,
self.segmentedControl?.selectedSegmentIndex = 0 self.presentationData.strings.Contacts_TabTitle
]
self.segmentedControlNode = SegmentedControlNode(theme: SegmentedControlTheme(theme: self.presentationData.theme), items: items.map { SegmentedControlItem(title: $0) }, selectedIndex: 0)
} else { } else {
self.toolbarBackgroundNode = nil self.toolbarBackgroundNode = nil
self.toolbarSeparatorNode = nil self.toolbarSeparatorNode = nil
self.segmentedControl = nil self.segmentedControlNode = nil
} }
@ -121,10 +124,13 @@ final class PeerSelectionControllerNode: ASDisplayNode {
}) })
if hasContactSelector { if hasContactSelector {
self.segmentedControlNode!.selectedIndexChanged = { [weak self] index in
self?.indexChanged(index)
}
self.addSubnode(self.toolbarBackgroundNode!) self.addSubnode(self.toolbarBackgroundNode!)
self.addSubnode(self.toolbarSeparatorNode!) self.addSubnode(self.toolbarSeparatorNode!)
self.view.addSubview(self.segmentedControl!) self.addSubnode(self.segmentedControlNode!)
self.segmentedControl!.addTarget(self, action: #selector(indexChanged), for: .valueChanged)
} }
@ -142,7 +148,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
self.toolbarBackgroundNode?.backgroundColor = self.presentationData.theme.rootController.navigationBar.backgroundColor self.toolbarBackgroundNode?.backgroundColor = self.presentationData.theme.rootController.navigationBar.backgroundColor
self.toolbarSeparatorNode?.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor self.toolbarSeparatorNode?.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor
self.segmentedControl?.tintColor = self.presentationData.theme.rootController.navigationBar.accentTextColor self.segmentedControlNode?.updateTheme(SegmentedControlTheme(theme: self.presentationData.theme))
} }
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, actualNavigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, actualNavigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
@ -152,14 +158,13 @@ final class PeerSelectionControllerNode: ASDisplayNode {
var toolbarHeight: CGFloat = cleanInsets.bottom var toolbarHeight: CGFloat = cleanInsets.bottom
if let segmentedControl = segmentedControl, let toolbarBackgroundNode = toolbarBackgroundNode, let toolbarSeparatorNode = toolbarSeparatorNode { if let segmentedControlNode = self.segmentedControlNode, let toolbarBackgroundNode = self.toolbarBackgroundNode, let toolbarSeparatorNode = self.toolbarSeparatorNode {
toolbarHeight += 44 toolbarHeight += 44
transition.updateFrame(node: toolbarBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - toolbarHeight), size: CGSize(width: layout.size.width, height: toolbarHeight))) transition.updateFrame(node: toolbarBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - toolbarHeight), size: CGSize(width: layout.size.width, height: toolbarHeight)))
transition.updateFrame(node: toolbarSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - toolbarHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel))) transition.updateFrame(node: toolbarSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - toolbarHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel)))
var controlSize = segmentedControl.sizeThatFits(layout.size) let controlSize = segmentedControlNode.updateLayout(.sizeToFit(maximumWidth: layout.size.width, minimumWidth: 200.0), transition: transition)
controlSize.width = min(layout.size.width, max(200.0, controlSize.width)) transition.updateFrame(node: segmentedControlNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - controlSize.width) / 2.0), y: layout.size.height - toolbarHeight + floor((44.0 - controlSize.height) / 2.0)), size: controlSize))
transition.updateFrame(view: segmentedControl, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - controlSize.width) / 2.0), y: layout.size.height - toolbarHeight + floor((44.0 - controlSize.height) / 2.0)), size: controlSize))
} }
var insets = layout.insets(options: [.input]) var insets = layout.insets(options: [.input])
@ -318,12 +323,12 @@ final class PeerSelectionControllerNode: ASDisplayNode {
}) })
} }
@objc func indexChanged() { private func indexChanged(_ index: Int) {
guard let (layout, navigationHeight, actualNavigationHeight) = self.containerLayout, let segmentedControl = self.segmentedControl else { guard let (layout, navigationHeight, actualNavigationHeight) = self.containerLayout else {
return return
} }
let contactListActive = segmentedControl.selectedSegmentIndex == 1 let contactListActive = index == 1
if contactListActive != self.contactListActive { if contactListActive != self.contactListActive {
self.contactListActive = contactListActive self.contactListActive = contactListActive
if contactListActive { if contactListActive {

View File

@ -99,7 +99,7 @@ private enum StickerSearchEntry: Identifiable, Comparable {
interaction.sendSticker(.standalone(media: stickerItem.file), node, rect) interaction.sendSticker(.standalone(media: stickerItem.file), node, rect)
}) })
case let .global(_, info, topItems, installed): case let .global(_, info, topItems, installed):
return StickerPaneSearchGlobalItem(account: account, theme: theme, strings: strings, info: info, topItems: topItems, installed: installed, unread: false, open: { return StickerPaneSearchGlobalItem(account: account, theme: theme, strings: strings, info: info, topItems: topItems, grid: false, installed: installed, unread: false, open: {
interaction.open(info) interaction.open(info)
}, install: { }, install: {
interaction.install(info) interaction.install(info)
@ -247,7 +247,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
} }
}, sendSticker: { [weak self] file, sourceNode, sourceRect in }, sendSticker: { [weak self] file, sourceNode, sourceRect in
if let strongSelf = self { if let strongSelf = self {
strongSelf.controllerInteraction.sendSticker(file, false, sourceNode, sourceRect) let _ = strongSelf.controllerInteraction.sendSticker(file, false, sourceNode, sourceRect)
} }
}, getItemIsPreviewed: { item in }, getItemIsPreviewed: { item in
return inputNodeInteraction.previewedStickerPackItem == .pack(item) return inputNodeInteraction.previewedStickerPackItem == .pack(item)
@ -460,7 +460,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
return nil return nil
} }
func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, inputHeight: CGFloat, transition: ContainedViewLayoutTransition) { func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, inputHeight: CGFloat, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition) {
let firstLayout = self.validLayout == nil let firstLayout = self.validLayout == nil
self.validLayout = size self.validLayout = size
@ -478,7 +478,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: contentFrame.size, insets: UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0 + bottomInset, right: 0.0), preloadSize: 300.0, type: .fixed(itemSize: CGSize(width: 75.0, height: 75.0), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: contentFrame.size, insets: UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0 + bottomInset, right: 0.0), preloadSize: 300.0, type: .fixed(itemSize: CGSize(width: 75.0, height: 75.0), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in })
transition.updateFrame(node: self.trendingPane, frame: contentFrame) transition.updateFrame(node: self.trendingPane, frame: contentFrame)
self.trendingPane.updateLayout(size: contentFrame.size, topInset: 0.0, bottomInset: bottomInset, isExpanded: false, isVisible: true, transition: transition) self.trendingPane.updateLayout(size: contentFrame.size, topInset: 0.0, bottomInset: bottomInset, isExpanded: false, isVisible: true, deviceMetrics: deviceMetrics, transition: transition)
transition.updateFrame(node: self.gridNode, frame: contentFrame) transition.updateFrame(node: self.gridNode, frame: contentFrame)
if firstLayout { if firstLayout {

View File

@ -37,6 +37,7 @@ final class StickerPaneSearchGlobalItem: GridItem {
let strings: PresentationStrings let strings: PresentationStrings
let info: StickerPackCollectionInfo let info: StickerPackCollectionInfo
let topItems: [StickerPackItem] let topItems: [StickerPackItem]
let grid: Bool
let installed: Bool let installed: Bool
let unread: Bool let unread: Bool
let open: () -> Void let open: () -> Void
@ -44,14 +45,17 @@ final class StickerPaneSearchGlobalItem: GridItem {
let getItemIsPreviewed: (StickerPackItem) -> Bool let getItemIsPreviewed: (StickerPackItem) -> Bool
let section: GridSection? = StickerPaneSearchGlobalSection() let section: GridSection? = StickerPaneSearchGlobalSection()
let fillsRowWithHeight: CGFloat? = 128.0 var fillsRowWithHeight: CGFloat? {
return self.grid ? nil : 128.0
}
init(account: Account, theme: PresentationTheme, strings: PresentationStrings, info: StickerPackCollectionInfo, topItems: [StickerPackItem], installed: Bool, unread: Bool, open: @escaping () -> Void, install: @escaping () -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool) { init(account: Account, theme: PresentationTheme, strings: PresentationStrings, info: StickerPackCollectionInfo, topItems: [StickerPackItem], grid: Bool, installed: Bool, unread: Bool, open: @escaping () -> Void, install: @escaping () -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool) {
self.account = account self.account = account
self.theme = theme self.theme = theme
self.strings = strings self.strings = strings
self.info = info self.info = info
self.topItems = topItems self.topItems = topItems
self.grid = grid
self.installed = installed self.installed = installed
self.unread = unread self.unread = unread
self.open = open self.open = open

View File

@ -126,7 +126,6 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex
private var enqueuedTransitions: [(VerticalListContextResultsChatInputContextPanelTransition, Bool)] = [] private var enqueuedTransitions: [(VerticalListContextResultsChatInputContextPanelTransition, Bool)] = []
private var validLayout: (CGSize, CGFloat, CGFloat)? private var validLayout: (CGSize, CGFloat, CGFloat)?
override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings) { override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings) {
self.listView = ListView() self.listView = ListView()
self.listView.isOpaque = false self.listView.isOpaque = false

View File

@ -8,6 +8,7 @@ import Postbox
import TelegramPresentationData import TelegramPresentationData
import RadialStatusNode import RadialStatusNode
import PhotoResources import PhotoResources
import StickerResources
final class VerticalListContextResultsChatInputPanelItem: ListViewItem { final class VerticalListContextResultsChatInputPanelItem: ListViewItem {
fileprivate let account: Account fileprivate let account: Account
@ -165,7 +166,6 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode {
var textString: NSAttributedString? var textString: NSAttributedString?
var iconText: NSAttributedString? var iconText: NSAttributedString?
var iconImageRepresentation: TelegramMediaImageRepresentation?
var updateIconImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? var updateIconImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
var updatedStatusSignal: Signal<MediaResourceStatus, NoError>? var updatedStatusSignal: Signal<MediaResourceStatus, NoError>?
@ -178,8 +178,9 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode {
} }
var imageResource: TelegramMediaResource? var imageResource: TelegramMediaResource?
var stickerFile: TelegramMediaFile?
switch item.result { switch item.result {
case let .externalReference(_, _, _, title, _, url, content, thumbnail, _): case let .externalReference(_, _, _, _, _, url, content, thumbnail, _):
if let thumbnail = thumbnail { if let thumbnail = thumbnail {
imageResource = thumbnail.resource imageResource = thumbnail.resource
} }
@ -198,11 +199,16 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode {
iconText = NSAttributedString(string: host.substring(to: host.index(after: host.startIndex)).uppercased(), font: iconFont, textColor: UIColor.white) iconText = NSAttributedString(string: host.substring(to: host.index(after: host.startIndex)).uppercased(), font: iconFont, textColor: UIColor.white)
} }
} }
case let .internalReference(_, _, _, title, _, image, file, _): case let .internalReference(_, _, _, _, _, image, file, _):
if let image = image { if let image = image {
imageResource = imageRepresentationLargerThan(image.representations, size: CGSize(width: 200.0, height: 200.0))?.resource imageResource = imageRepresentationLargerThan(image.representations, size: CGSize(width: 200.0, height: 200.0))?.resource
} else if let file = file { } else if let file = file {
imageResource = smallestImageRepresentation(file.previewRepresentations)?.resource if file.isSticker {
stickerFile = file
imageResource = file.resource
} else {
imageResource = smallestImageRepresentation(file.previewRepresentations)?.resource
}
} }
} }
@ -215,9 +221,15 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode {
var iconImageApply: (() -> Void)? var iconImageApply: (() -> Void)?
if let imageResource = imageResource { if let imageResource = imageResource {
let iconSize = CGSize(width: 55.0, height: 55.0) let boundingSize = CGSize(width: 55.0, height: 55.0)
let iconSize: CGSize
if let stickerFile = stickerFile, let dimensions = stickerFile.dimensions {
iconSize = dimensions.fitted(boundingSize)
} else {
iconSize = boundingSize
}
let imageCorners = ImageCorners(topLeft: .Corner(2.0), topRight: .Corner(2.0), bottomLeft: .Corner(2.0), bottomRight: .Corner(2.0)) let imageCorners = ImageCorners(topLeft: .Corner(2.0), topRight: .Corner(2.0), bottomLeft: .Corner(2.0), bottomRight: .Corner(2.0))
let arguments = TransformImageArguments(corners: imageCorners, imageSize: iconSize, boundingSize: iconSize, intrinsicInsets: UIEdgeInsets()) let arguments = TransformImageArguments(corners: imageCorners, imageSize: iconSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets())
iconImageApply = iconImageLayout(arguments) iconImageApply = iconImageLayout(arguments)
updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(imageResource) updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(imageResource)
@ -235,9 +247,13 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode {
if updatedIconImageResource { if updatedIconImageResource {
if let imageResource = imageResource { if let imageResource = imageResource {
let tmpRepresentation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 55.0, height: 55.0), resource: imageResource) if let stickerFile = stickerFile {
let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [tmpRepresentation], immediateThumbnailData: nil, reference: nil, partialReference: nil) updateIconImageSignal = chatMessageSticker(account: item.account, file: stickerFile, small: false, fetched: true)
updateIconImageSignal = chatWebpageSnippetPhoto(account: item.account, photoReference: .standalone(media: tmpImage)) } else {
let tmpRepresentation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 55.0, height: 55.0), resource: imageResource)
let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [tmpRepresentation], immediateThumbnailData: nil, reference: nil, partialReference: nil)
updateIconImageSignal = chatWebpageSnippetPhoto(account: item.account, photoReference: .standalone(media: tmpImage))
}
} else { } else {
updateIconImageSignal = .complete() updateIconImageSignal = .complete()
} }

View File

@ -12,6 +12,7 @@ import MergeLists
import AccountContext import AccountContext
import GalleryUI import GalleryUI
import ChatListSearchItemHeader import ChatListSearchItemHeader
import SegmentedControlNode
private struct WebSearchContextResultStableId: Hashable { private struct WebSearchContextResultStableId: Hashable {
let result: ChatContextResult let result: ChatContextResult
@ -135,7 +136,7 @@ class WebSearchControllerNode: ASDisplayNode {
private let segmentedBackgroundNode: ASDisplayNode private let segmentedBackgroundNode: ASDisplayNode
private let segmentedSeparatorNode: ASDisplayNode private let segmentedSeparatorNode: ASDisplayNode
private let segmentedControl: UISegmentedControl private let segmentedControlNode: SegmentedControlNode
private let toolbarBackgroundNode: ASDisplayNode private let toolbarBackgroundNode: ASDisplayNode
private let toolbarSeparatorNode: ASDisplayNode private let toolbarSeparatorNode: ASDisplayNode
@ -189,8 +190,11 @@ class WebSearchControllerNode: ASDisplayNode {
self.segmentedBackgroundNode = ASDisplayNode() self.segmentedBackgroundNode = ASDisplayNode()
self.segmentedSeparatorNode = ASDisplayNode() self.segmentedSeparatorNode = ASDisplayNode()
self.segmentedControl = UISegmentedControl(items: [strings.WebSearch_Images, strings.WebSearch_GIFs]) let items = [
self.segmentedControl.selectedSegmentIndex = 0 strings.WebSearch_Images,
strings.WebSearch_GIFs
]
self.segmentedControlNode = SegmentedControlNode(theme: SegmentedControlTheme(theme: theme), items: items.map { SegmentedControlItem(title: $0) }, selectedIndex: 0)
self.toolbarBackgroundNode = ASDisplayNode() self.toolbarBackgroundNode = ASDisplayNode()
self.toolbarSeparatorNode = ASDisplayNode() self.toolbarSeparatorNode = ASDisplayNode()
@ -221,7 +225,7 @@ class WebSearchControllerNode: ASDisplayNode {
self.addSubnode(self.segmentedBackgroundNode) self.addSubnode(self.segmentedBackgroundNode)
self.addSubnode(self.segmentedSeparatorNode) self.addSubnode(self.segmentedSeparatorNode)
if case .media = mode { if case .media = mode {
self.view.addSubview(self.segmentedControl) self.addSubnode(self.segmentedControlNode)
} }
self.addSubnode(self.toolbarBackgroundNode) self.addSubnode(self.toolbarBackgroundNode)
self.addSubnode(self.toolbarSeparatorNode) self.addSubnode(self.toolbarSeparatorNode)
@ -230,7 +234,16 @@ class WebSearchControllerNode: ASDisplayNode {
self.addSubnode(self.attributionNode) self.addSubnode(self.attributionNode)
self.addSubnode(self.badgeNode) self.addSubnode(self.badgeNode)
self.segmentedControl.addTarget(self, action: #selector(self.indexChanged), for: .valueChanged) self.segmentedControlNode.selectedIndexChanged = { [weak self] index in
if let strongSelf = self, let scope = WebSearchScope(rawValue: Int32(index)) {
let _ = updateWebSearchSettingsInteractively(accountManager: strongSelf.context.sharedContext.accountManager) { _ -> WebSearchSettings in
return WebSearchSettings(scope: scope)
}.start()
strongSelf.requestUpdateInterfaceState(true) { current in
return current.withUpdatedScope(scope)
}
}
}
self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside) self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside)
self.sendButton.addTarget(self, action: #selector(self.sendPressed), forControlEvents: .touchUpInside) self.sendButton.addTarget(self, action: #selector(self.sendPressed), forControlEvents: .touchUpInside)
@ -334,7 +347,7 @@ class WebSearchControllerNode: ASDisplayNode {
self.segmentedBackgroundNode.backgroundColor = self.theme.rootController.navigationBar.backgroundColor self.segmentedBackgroundNode.backgroundColor = self.theme.rootController.navigationBar.backgroundColor
self.segmentedSeparatorNode.backgroundColor = self.theme.rootController.navigationBar.separatorColor self.segmentedSeparatorNode.backgroundColor = self.theme.rootController.navigationBar.separatorColor
self.segmentedControl.tintColor = self.theme.rootController.navigationBar.accentTextColor self.segmentedControlNode.updateTheme(SegmentedControlTheme(theme: self.theme))
self.toolbarBackgroundNode.backgroundColor = self.theme.rootController.navigationBar.backgroundColor self.toolbarBackgroundNode.backgroundColor = self.theme.rootController.navigationBar.backgroundColor
self.toolbarSeparatorNode.backgroundColor = self.theme.rootController.navigationBar.separatorColor self.toolbarSeparatorNode.backgroundColor = self.theme.rootController.navigationBar.separatorColor
@ -358,16 +371,14 @@ class WebSearchControllerNode: ASDisplayNode {
var insets = layout.insets(options: [.input]) var insets = layout.insets(options: [.input])
insets.top += navigationBarHeight insets.top += navigationBarHeight
let segmentedHeight: CGFloat = self.segmentedControl.superview != nil ? 44.0 : 5.0 let segmentedHeight: CGFloat = self.segmentedControlNode.supernode != nil ? 44.0 : 5.0
let panelY: CGFloat = insets.top - UIScreenPixel - 4.0 let panelY: CGFloat = insets.top - UIScreenPixel - 4.0
transition.updateFrame(node: self.segmentedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelY), size: CGSize(width: layout.size.width, height: segmentedHeight))) transition.updateFrame(node: self.segmentedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelY), size: CGSize(width: layout.size.width, height: segmentedHeight)))
transition.updateFrame(node: self.segmentedSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelY + segmentedHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel))) transition.updateFrame(node: self.segmentedSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelY + segmentedHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel)))
var controlSize = self.segmentedControl.sizeThatFits(layout.size) let controlSize = self.segmentedControlNode.updateLayout(.stretchToFill(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - 10.0 * 2.0), transition: transition)
controlSize.width = layout.size.width - layout.safeInsets.left - layout.safeInsets.right - 10.0 * 2.0 transition.updateFrame(node: self.segmentedControlNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + floor((layout.size.width - layout.safeInsets.left - layout.safeInsets.right - controlSize.width) / 2.0), y: panelY + 5.0), size: controlSize))
transition.updateFrame(view: self.segmentedControl, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + floor((layout.size.width - layout.safeInsets.left - layout.safeInsets.right - controlSize.width) / 2.0), y: panelY + 5.0), size: controlSize))
insets.top -= 4.0 insets.top -= 4.0
@ -471,7 +482,7 @@ class WebSearchControllerNode: ASDisplayNode {
self.webSearchInterfaceStatePromise.set(self.webSearchInterfaceState) self.webSearchInterfaceStatePromise.set(self.webSearchInterfaceState)
if let state = interfaceState.state { if let state = interfaceState.state {
self.segmentedControl.selectedSegmentIndex = Int(state.scope.rawValue) self.segmentedControlNode.selectedIndex = Int(state.scope.rawValue)
} }
if let validLayout = self.containerLayout { if let validLayout = self.containerLayout {
@ -656,17 +667,6 @@ class WebSearchControllerNode: ASDisplayNode {
} }
} }
@objc private func indexChanged() {
if let scope = WebSearchScope(rawValue: Int32(self.segmentedControl.selectedSegmentIndex)) {
let _ = updateWebSearchSettingsInteractively(accountManager: self.context.sharedContext.accountManager) { _ -> WebSearchSettings in
return WebSearchSettings(scope: scope)
}.start()
self.requestUpdateInterfaceState(true) { current in
return current.withUpdatedScope(scope)
}
}
}
@objc private func cancelPressed() { @objc private func cancelPressed() {
self.cancel?() self.cancel?()
} }