mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit '3c1afe4014bf770c30c745dc58864aae8682ee5b'
This commit is contained in:
commit
bcd23c4250
BIN
Telegram/Telegram-iOS/Resources/ClearCache.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/ClearCache.tgs
Normal file
Binary file not shown.
@ -7228,3 +7228,6 @@ Sorry for the inconvenience.";
|
|||||||
"Contacts.Sort" = "Sort";
|
"Contacts.Sort" = "Sort";
|
||||||
"Contacts.Sort.ByName" = "by Name";
|
"Contacts.Sort.ByName" = "by Name";
|
||||||
"Contacts.Sort.ByLastSeen" = "by Last Seen";
|
"Contacts.Sort.ByLastSeen" = "by Last Seen";
|
||||||
|
|
||||||
|
"ClearCache.Progress" = "Clearing the Cache • %d%";
|
||||||
|
"ClearCache.KeepOpenedDescription" = "Please keep this window open until the clearing is completed.";
|
||||||
|
@ -869,6 +869,9 @@ public final class AnimatedStickerNode: ASDisplayNode {
|
|||||||
|
|
||||||
public var isPlayingChanged: (Bool) -> Void = { _ in }
|
public var isPlayingChanged: (Bool) -> Void = { _ in }
|
||||||
|
|
||||||
|
private var overlayColor: (UIColor?, Bool)? = nil
|
||||||
|
private var size: CGSize?
|
||||||
|
|
||||||
override public init() {
|
override public init() {
|
||||||
self.queue = sharedQueue
|
self.queue = sharedQueue
|
||||||
self.eventsNode = AnimatedStickerNodeDisplayEvents()
|
self.eventsNode = AnimatedStickerNodeDisplayEvents()
|
||||||
@ -900,10 +903,13 @@ public final class AnimatedStickerNode: ASDisplayNode {
|
|||||||
self.renderer = SoftwareAnimationRenderer()
|
self.renderer = SoftwareAnimationRenderer()
|
||||||
//self.renderer = MetalAnimationRenderer()
|
//self.renderer = MetalAnimationRenderer()
|
||||||
#endif
|
#endif
|
||||||
self.renderer?.frame = CGRect(origin: CGPoint(), size: self.bounds.size)
|
self.renderer?.frame = CGRect(origin: CGPoint(), size: self.size ?? self.bounds.size)
|
||||||
if let contents = self.nodeToCopyFrameFrom?.renderer?.contents {
|
if let contents = self.nodeToCopyFrameFrom?.renderer?.contents {
|
||||||
self.renderer?.contents = contents
|
self.renderer?.contents = contents
|
||||||
}
|
}
|
||||||
|
if let (overlayColor, replace) = self.overlayColor {
|
||||||
|
self.renderer?.setOverlayColor(overlayColor, replace: replace, animated: false)
|
||||||
|
}
|
||||||
self.nodeToCopyFrameFrom = nil
|
self.nodeToCopyFrameFrom = nil
|
||||||
self.addSubnode(self.renderer!)
|
self.addSubnode(self.renderer!)
|
||||||
}
|
}
|
||||||
@ -1347,10 +1353,12 @@ public final class AnimatedStickerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func updateLayout(size: CGSize) {
|
public func updateLayout(size: CGSize) {
|
||||||
|
self.size = size
|
||||||
self.renderer?.frame = CGRect(origin: CGPoint(), size: size)
|
self.renderer?.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setOverlayColor(_ color: UIColor?, animated: Bool) {
|
public func setOverlayColor(_ color: UIColor?, replace: Bool, animated: Bool) {
|
||||||
self.renderer?.setOverlayColor(color, animated: animated)
|
self.overlayColor = (color, replace)
|
||||||
|
self.renderer?.setOverlayColor(color, replace: replace, animated: animated)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,5 +10,5 @@ public enum AnimationRendererFrameType {
|
|||||||
protocol AnimationRenderer {
|
protocol AnimationRenderer {
|
||||||
func render(queue: Queue, width: Int, height: Int, bytesPerRow: Int, data: Data, type: AnimationRendererFrameType, completion: @escaping () -> Void)
|
func render(queue: Queue, width: Int, height: Int, bytesPerRow: Int, data: Data, type: AnimationRendererFrameType, completion: @escaping () -> Void)
|
||||||
|
|
||||||
func setOverlayColor(_ color: UIColor?, animated: Bool)
|
func setOverlayColor(_ color: UIColor?, replace: Bool, animated: Bool)
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,8 @@ import YuvConversion
|
|||||||
final class SoftwareAnimationRenderer: ASDisplayNode, AnimationRenderer {
|
final class SoftwareAnimationRenderer: ASDisplayNode, AnimationRenderer {
|
||||||
private var highlightedContentNode: ASDisplayNode?
|
private var highlightedContentNode: ASDisplayNode?
|
||||||
private var highlightedColor: UIColor?
|
private var highlightedColor: UIColor?
|
||||||
|
private var highlightReplacesContent = false
|
||||||
|
|
||||||
func render(queue: Queue, width: Int, height: Int, bytesPerRow: Int, data: Data, type: AnimationRendererFrameType, completion: @escaping () -> Void) {
|
func render(queue: Queue, width: Int, height: Int, bytesPerRow: Int, data: Data, type: AnimationRendererFrameType, completion: @escaping () -> Void) {
|
||||||
assert(bytesPerRow > 0)
|
assert(bytesPerRow > 0)
|
||||||
queue.async { [weak self] in
|
queue.async { [weak self] in
|
||||||
@ -53,20 +54,29 @@ final class SoftwareAnimationRenderer: ASDisplayNode, AnimationRenderer {
|
|||||||
}
|
}
|
||||||
strongSelf.contents = image?.cgImage
|
strongSelf.contents = image?.cgImage
|
||||||
strongSelf.updateHighlightedContentNode()
|
strongSelf.updateHighlightedContentNode()
|
||||||
|
if strongSelf.highlightedContentNode?.frame != strongSelf.bounds {
|
||||||
|
strongSelf.highlightedContentNode?.frame = strongSelf.bounds
|
||||||
|
}
|
||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateHighlightedContentNode() {
|
private func updateHighlightedContentNode() {
|
||||||
guard let highlightedContentNode = self.highlightedContentNode, let highlightedColor = self.highlightedColor, let contents = self.contents, CFGetTypeID(contents as CFTypeRef) == CGImage.typeID else {
|
guard let highlightedContentNode = self.highlightedContentNode, let highlightedColor = self.highlightedColor else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
(highlightedContentNode.view as! UIImageView).image = UIImage(cgImage: contents as! CGImage).withRenderingMode(.alwaysTemplate)
|
if let contents = self.contents, CFGetTypeID(contents as CFTypeRef) == CGImage.typeID {
|
||||||
|
(highlightedContentNode.view as! UIImageView).image = UIImage(cgImage: contents as! CGImage).withRenderingMode(.alwaysTemplate)
|
||||||
|
}
|
||||||
highlightedContentNode.tintColor = highlightedColor
|
highlightedContentNode.tintColor = highlightedColor
|
||||||
|
if self.highlightReplacesContent {
|
||||||
|
self.contents = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setOverlayColor(_ color: UIColor?, animated: Bool) {
|
func setOverlayColor(_ color: UIColor?, replace: Bool, animated: Bool) {
|
||||||
|
self.highlightReplacesContent = replace
|
||||||
var updated = false
|
var updated = false
|
||||||
if let current = self.highlightedColor, let color = color {
|
if let current = self.highlightedColor, let color = color {
|
||||||
updated = !current.isEqual(color)
|
updated = !current.isEqual(color)
|
||||||
|
@ -1330,7 +1330,7 @@ public final class CalendarMessageScreen: ViewController {
|
|||||||
selectionToolbarNode.updateLayout(size: tabBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: bottomInset, toolbar: Toolbar(leftAction: nil, rightAction: nil, middleAction: ToolbarAction(title: toolbarText, isEnabled: true, color: .custom(self.selectionState?.dayRange != nil ? self.presentationData.theme.list.itemDestructiveColor : self.presentationData.theme.list.itemDisabledTextColor))), transition: transition)
|
selectionToolbarNode.updateLayout(size: tabBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: bottomInset, toolbar: Toolbar(leftAction: nil, rightAction: nil, middleAction: ToolbarAction(title: toolbarText, isEnabled: true, color: .custom(self.selectionState?.dayRange != nil ? self.presentationData.theme.list.itemDestructiveColor : self.presentationData.theme.list.itemDisabledTextColor))), transition: transition)
|
||||||
} else {
|
} else {
|
||||||
selectionToolbarNode = ToolbarNode(
|
selectionToolbarNode = ToolbarNode(
|
||||||
theme: TabBarControllerTheme(
|
theme: ToolbarTheme(
|
||||||
rootControllerTheme: self.presentationData.theme),
|
rootControllerTheme: self.presentationData.theme),
|
||||||
displaySeparator: true,
|
displaySeparator: true,
|
||||||
left: {
|
left: {
|
||||||
|
@ -115,6 +115,7 @@ public final class CallListController: TelegramBaseController {
|
|||||||
self.tabBarItem.title = self.presentationData.strings.Calls_TabTitle
|
self.tabBarItem.title = self.presentationData.strings.Calls_TabTitle
|
||||||
self.tabBarItem.image = icon
|
self.tabBarItem.image = icon
|
||||||
self.tabBarItem.selectedImage = icon
|
self.tabBarItem.selectedImage = icon
|
||||||
|
self.tabBarItem.animationName = "TabCalls"
|
||||||
}
|
}
|
||||||
|
|
||||||
self.segmentedTitleView.indexUpdated = { [weak self] index in
|
self.segmentedTitleView.indexUpdated = { [weak self] index in
|
||||||
|
@ -206,6 +206,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
|
|
||||||
self.tabBarItem.image = icon
|
self.tabBarItem.image = icon
|
||||||
self.tabBarItem.selectedImage = icon
|
self.tabBarItem.selectedImage = icon
|
||||||
|
self.tabBarItem.animationName = "TabChats"
|
||||||
|
|
||||||
let leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed))
|
let leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed))
|
||||||
leftBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Edit
|
leftBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Edit
|
||||||
|
@ -1116,7 +1116,7 @@ final class ChatListControllerNode: ASDisplayNode {
|
|||||||
self.searchDisplayController?.updatePresentationData(presentationData)
|
self.searchDisplayController?.updatePresentationData(presentationData)
|
||||||
|
|
||||||
if let toolbarNode = self.toolbarNode {
|
if let toolbarNode = self.toolbarNode {
|
||||||
toolbarNode.updateTheme(TabBarControllerTheme(rootControllerTheme: self.presentationData.theme))
|
toolbarNode.updateTheme(ToolbarTheme(rootControllerTheme: self.presentationData.theme))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1149,7 +1149,7 @@ final class ChatListControllerNode: ASDisplayNode {
|
|||||||
transition.updateFrame(node: toolbarNode, frame: tabBarFrame)
|
transition.updateFrame(node: toolbarNode, frame: tabBarFrame)
|
||||||
toolbarNode.updateLayout(size: tabBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: bottomInset, toolbar: toolbar, transition: transition)
|
toolbarNode.updateLayout(size: tabBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: bottomInset, toolbar: toolbar, transition: transition)
|
||||||
} else {
|
} else {
|
||||||
let toolbarNode = ToolbarNode(theme: TabBarControllerTheme(rootControllerTheme: self.presentationData.theme), displaySeparator: true, left: { [weak self] in
|
let toolbarNode = ToolbarNode(theme: ToolbarTheme(rootControllerTheme: self.presentationData.theme), displaySeparator: true, left: { [weak self] in
|
||||||
self?.toolbarActionSelected?(.left)
|
self?.toolbarActionSelected?(.left)
|
||||||
}, right: { [weak self] in
|
}, right: { [weak self] in
|
||||||
self?.toolbarActionSelected?(.right)
|
self?.toolbarActionSelected?(.right)
|
||||||
|
@ -192,6 +192,7 @@ public class ContactsController: ViewController {
|
|||||||
|
|
||||||
self.tabBarItem.image = icon
|
self.tabBarItem.image = icon
|
||||||
self.tabBarItem.selectedImage = icon
|
self.tabBarItem.selectedImage = icon
|
||||||
|
self.tabBarItem.animationName = "TabContacts"
|
||||||
|
|
||||||
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
|
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
|
||||||
|
|
||||||
|
@ -940,7 +940,6 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
|
|||||||
entries.append(.enableDebugDataDisplay(experimentalSettings.enableDebugDataDisplay))
|
entries.append(.enableDebugDataDisplay(experimentalSettings.enableDebugDataDisplay))
|
||||||
entries.append(.acceleratedStickers(experimentalSettings.acceleratedStickers))
|
entries.append(.acceleratedStickers(experimentalSettings.acceleratedStickers))
|
||||||
entries.append(.experimentalBackground(experimentalSettings.experimentalBackground))
|
entries.append(.experimentalBackground(experimentalSettings.experimentalBackground))
|
||||||
entries.append(.snow(experimentalSettings.snow))
|
|
||||||
entries.append(.playerEmbedding(experimentalSettings.playerEmbedding))
|
entries.append(.playerEmbedding(experimentalSettings.playerEmbedding))
|
||||||
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
|
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import AsyncDisplayKit
|
||||||
|
|
||||||
|
public protocol ActionSheetGroupOverlayNode: ASDisplayNode {
|
||||||
|
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition)
|
||||||
|
}
|
||||||
|
|
||||||
open class ActionSheetController: ViewController, PresentableController, StandalonePresentableController {
|
open class ActionSheetController: ViewController, PresentableController, StandalonePresentableController {
|
||||||
private var actionSheetNode: ActionSheetControllerNode {
|
private var actionSheetNode: ActionSheetControllerNode {
|
||||||
@ -83,4 +88,10 @@ open class ActionSheetController: ViewController, PresentableController, Standal
|
|||||||
self.actionSheetNode.updateItem(groupIndex: groupIndex, itemIndex: itemIndex, f)
|
self.actionSheetNode.updateItem(groupIndex: groupIndex, itemIndex: itemIndex, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func setItemGroupOverlayNode(groupIndex: Int, node: ActionSheetGroupOverlayNode) {
|
||||||
|
if self.isViewLoaded {
|
||||||
|
self.actionSheetNode.setItemGroupOverlayNode(groupIndex: groupIndex, node: node)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -219,7 +219,11 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
self.itemGroupsContainerNode.setGroups(groups)
|
self.itemGroupsContainerNode.setGroups(groups)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func updateItem(groupIndex: Int, itemIndex: Int, _ f: (ActionSheetItem) -> ActionSheetItem) {
|
func updateItem(groupIndex: Int, itemIndex: Int, _ f: (ActionSheetItem) -> ActionSheetItem) {
|
||||||
self.itemGroupsContainerNode.updateItem(groupIndex: groupIndex, itemIndex: itemIndex, f)
|
self.itemGroupsContainerNode.updateItem(groupIndex: groupIndex, itemIndex: itemIndex, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setItemGroupOverlayNode(groupIndex: Int, node: ActionSheetGroupOverlayNode) {
|
||||||
|
self.itemGroupsContainerNode.setItemGroupOverlayNode(groupIndex: groupIndex, node: node)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,8 @@ final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
private var validLayout: CGSize?
|
private var validLayout: CGSize?
|
||||||
|
|
||||||
|
private var overlayNode: ActionSheetGroupOverlayNode?
|
||||||
|
|
||||||
init(theme: ActionSheetControllerTheme) {
|
init(theme: ActionSheetControllerTheme) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
|
||||||
@ -66,6 +68,31 @@ final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
self.addSubnode(self.clippingNode)
|
self.addSubnode(self.clippingNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setOverlayNode(_ overlayNode: ActionSheetGroupOverlayNode?) {
|
||||||
|
guard self.overlayNode == nil else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear)
|
||||||
|
overlayNode?.alpha = 0.0
|
||||||
|
|
||||||
|
self.overlayNode = overlayNode
|
||||||
|
if let overlayNode = overlayNode {
|
||||||
|
transition.updateAlpha(node: overlayNode, alpha: 1.0)
|
||||||
|
self.clippingNode.addSubnode(overlayNode)
|
||||||
|
} else if let overlayNode = self.overlayNode {
|
||||||
|
overlayNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
|
||||||
|
if let size = self.validLayout, let overlayNode = overlayNode {
|
||||||
|
overlayNode.updateLayout(size: size, transition: .immediate)
|
||||||
|
}
|
||||||
|
|
||||||
|
for node in self.itemNodes {
|
||||||
|
transition.updateAlpha(node: node, alpha: 0.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func updateItemNodes(_ nodes: [ActionSheetItemNode], leadingVisibleNodeCount: CGFloat = 1000.0) {
|
func updateItemNodes(_ nodes: [ActionSheetItemNode], leadingVisibleNodeCount: CGFloat = 1000.0) {
|
||||||
for node in self.itemNodes {
|
for node in self.itemNodes {
|
||||||
if !nodes.contains(where: { $0 === node }) {
|
if !nodes.contains(where: { $0 === node }) {
|
||||||
@ -140,6 +167,10 @@ final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
self.scrollNode.view.contentOffset = CGPoint(x: 0.0, y: -scrollViewContentInsets.top)
|
self.scrollNode.view.contentOffset = CGPoint(x: 0.0, y: -scrollViewContentInsets.top)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let overlayNode = self.overlayNode {
|
||||||
|
overlayNode.updateLayout(size: size, transition: transition)
|
||||||
|
}
|
||||||
|
|
||||||
self.updateOverscroll(size: size, transition: transition)
|
self.updateOverscroll(size: size, transition: transition)
|
||||||
|
|
||||||
return size
|
return size
|
||||||
|
@ -112,4 +112,8 @@ final class ActionSheetItemGroupsContainerNode: ASDisplayNode {
|
|||||||
|
|
||||||
self.groups[groupIndex] = ActionSheetItemGroup(items: groupItems)
|
self.groups[groupIndex] = ActionSheetItemGroup(items: groupItems)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setItemGroupOverlayNode(groupIndex: Int, node: ActionSheetGroupOverlayNode) {
|
||||||
|
self.groupNodes[groupIndex].setOverlayNode(node)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,442 +3,25 @@ import UIKit
|
|||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
|
|
||||||
public final class TabBarControllerTheme {
|
public enum TabBarItemSwipeDirection {
|
||||||
public let backgroundColor: UIColor
|
case left
|
||||||
public let tabBarBackgroundColor: UIColor
|
case right
|
||||||
public let tabBarSeparatorColor: UIColor
|
|
||||||
public let tabBarIconColor: UIColor
|
|
||||||
public let tabBarSelectedIconColor: UIColor
|
|
||||||
public let tabBarTextColor: UIColor
|
|
||||||
public let tabBarSelectedTextColor: UIColor
|
|
||||||
public let tabBarBadgeBackgroundColor: UIColor
|
|
||||||
public let tabBarBadgeStrokeColor: UIColor
|
|
||||||
public let tabBarBadgeTextColor: UIColor
|
|
||||||
public let tabBarExtractedIconColor: UIColor
|
|
||||||
public let tabBarExtractedTextColor: UIColor
|
|
||||||
|
|
||||||
public init(backgroundColor: UIColor, tabBarBackgroundColor: UIColor, tabBarSeparatorColor: UIColor, tabBarIconColor: UIColor, tabBarSelectedIconColor: UIColor, tabBarTextColor: UIColor, tabBarSelectedTextColor: UIColor, tabBarBadgeBackgroundColor: UIColor, tabBarBadgeStrokeColor: UIColor, tabBarBadgeTextColor: UIColor, tabBarExtractedIconColor: UIColor, tabBarExtractedTextColor: UIColor) {
|
|
||||||
self.backgroundColor = backgroundColor
|
|
||||||
self.tabBarBackgroundColor = tabBarBackgroundColor
|
|
||||||
self.tabBarSeparatorColor = tabBarSeparatorColor
|
|
||||||
self.tabBarIconColor = tabBarIconColor
|
|
||||||
self.tabBarSelectedIconColor = tabBarSelectedIconColor
|
|
||||||
self.tabBarTextColor = tabBarTextColor
|
|
||||||
self.tabBarSelectedTextColor = tabBarSelectedTextColor
|
|
||||||
self.tabBarBadgeBackgroundColor = tabBarBadgeBackgroundColor
|
|
||||||
self.tabBarBadgeStrokeColor = tabBarBadgeStrokeColor
|
|
||||||
self.tabBarBadgeTextColor = tabBarBadgeTextColor
|
|
||||||
self.tabBarExtractedIconColor = tabBarExtractedIconColor
|
|
||||||
self.tabBarExtractedTextColor = tabBarExtractedTextColor
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class TabBarItemInfo: NSObject {
|
public protocol TabBarController: ViewController {
|
||||||
public let previewing: Bool
|
var currentController: ViewController? { get }
|
||||||
|
var controllers: [ViewController] { get }
|
||||||
|
var selectedIndex: Int { get set }
|
||||||
|
|
||||||
public init(previewing: Bool) {
|
func setControllers(_ controllers: [ViewController], selectedIndex: Int?)
|
||||||
self.previewing = previewing
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
override public func isEqual(_ object: Any?) -> Bool {
|
func updateBackgroundAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition)
|
||||||
if let object = object as? TabBarItemInfo {
|
|
||||||
if self.previewing != object.previewing {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func ==(lhs: TabBarItemInfo, rhs: TabBarItemInfo) -> Bool {
|
func frameForControllerTab(controller: ViewController) -> CGRect?
|
||||||
if lhs.previewing != rhs.previewing {
|
func isPointInsideContentArea(point: CGPoint) -> Bool
|
||||||
return false
|
func sourceNodesForController(at index: Int) -> [ASDisplayNode]?
|
||||||
}
|
|
||||||
return true
|
func updateIsTabBarEnabled(_ value: Bool, transition: ContainedViewLayoutTransition)
|
||||||
}
|
func updateIsTabBarHidden(_ value: Bool, transition: ContainedViewLayoutTransition)
|
||||||
}
|
func updateLayout(transition: ContainedViewLayoutTransition)
|
||||||
|
|
||||||
public enum TabBarContainedControllerPresentationUpdate {
|
|
||||||
case dismiss
|
|
||||||
case present
|
|
||||||
case progress(CGFloat)
|
|
||||||
}
|
|
||||||
|
|
||||||
public protocol TabBarContainedController {
|
|
||||||
func presentTabBarPreviewingController(sourceNodes: [ASDisplayNode])
|
|
||||||
func updateTabBarPreviewingControllerPresentation(_ update: TabBarContainedControllerPresentationUpdate)
|
|
||||||
}
|
|
||||||
|
|
||||||
open class TabBarController: ViewController {
|
|
||||||
private var validLayout: ContainerViewLayout?
|
|
||||||
|
|
||||||
private var tabBarControllerNode: TabBarControllerNode {
|
|
||||||
get {
|
|
||||||
return super.displayNode as! TabBarControllerNode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
open override func updateNavigationCustomData(_ data: Any?, progress: CGFloat, transition: ContainedViewLayoutTransition) {
|
|
||||||
for controller in self.controllers {
|
|
||||||
controller.updateNavigationCustomData(data, progress: progress, transition: transition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public private(set) var controllers: [ViewController] = []
|
|
||||||
|
|
||||||
private let _ready = Promise<Bool>()
|
|
||||||
override open var ready: Promise<Bool> {
|
|
||||||
return self._ready
|
|
||||||
}
|
|
||||||
|
|
||||||
private var _selectedIndex: Int?
|
|
||||||
public var selectedIndex: Int {
|
|
||||||
get {
|
|
||||||
if let _selectedIndex = self._selectedIndex {
|
|
||||||
return _selectedIndex
|
|
||||||
} else {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
} set(value) {
|
|
||||||
let index = max(0, min(self.controllers.count - 1, value))
|
|
||||||
if _selectedIndex != index {
|
|
||||||
_selectedIndex = index
|
|
||||||
|
|
||||||
self.updateSelectedIndex()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var currentController: ViewController?
|
|
||||||
|
|
||||||
private let pendingControllerDisposable = MetaDisposable()
|
|
||||||
|
|
||||||
private var theme: TabBarControllerTheme
|
|
||||||
|
|
||||||
public init(navigationBarPresentationData: NavigationBarPresentationData, theme: TabBarControllerTheme) {
|
|
||||||
self.theme = theme
|
|
||||||
|
|
||||||
super.init(navigationBarPresentationData: nil)
|
|
||||||
|
|
||||||
self.scrollToTop = { [weak self] in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if let controller = strongSelf.currentController {
|
|
||||||
controller.scrollToTop?()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
required public init(coder aDecoder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
self.pendingControllerDisposable.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
public func updateTheme(navigationBarPresentationData: NavigationBarPresentationData, theme: TabBarControllerTheme) {
|
|
||||||
if self.theme !== theme {
|
|
||||||
self.theme = theme
|
|
||||||
if self.isNodeLoaded {
|
|
||||||
self.tabBarControllerNode.updateTheme(theme)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var debugTapCounter: (Double, Int) = (0.0, 0)
|
|
||||||
|
|
||||||
public func sourceNodesForController(at index: Int) -> [ASDisplayNode]? {
|
|
||||||
return self.tabBarControllerNode.tabBarNode.sourceNodesForController(at: index)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func frameForControllerTab(controller: ViewController) -> CGRect? {
|
|
||||||
if let index = self.controllers.firstIndex(of: controller) {
|
|
||||||
return self.tabBarControllerNode.tabBarNode.frameForControllerTab(at: index).flatMap { self.tabBarControllerNode.tabBarNode.view.convert($0, to: self.view) }
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func isPointInsideContentArea(point: CGPoint) -> Bool {
|
|
||||||
if point.y < self.tabBarControllerNode.tabBarNode.frame.minY {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
public func updateIsTabBarEnabled(_ value: Bool, transition: ContainedViewLayoutTransition) {
|
|
||||||
self.tabBarControllerNode.updateIsTabBarEnabled(value, transition: transition)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func updateIsTabBarHidden(_ value: Bool, transition: ContainedViewLayoutTransition) {
|
|
||||||
self.tabBarControllerNode.tabBarHidden = value
|
|
||||||
if let layout = self.validLayout {
|
|
||||||
self.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .slide))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override open func loadDisplayNode() {
|
|
||||||
self.displayNode = TabBarControllerNode(theme: self.theme, itemSelected: { [weak self] index, longTap, itemNodes in
|
|
||||||
if let strongSelf = self {
|
|
||||||
if longTap, let controller = strongSelf.controllers[index] as? TabBarContainedController {
|
|
||||||
controller.presentTabBarPreviewingController(sourceNodes: itemNodes)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if strongSelf.selectedIndex == index {
|
|
||||||
let timestamp = CACurrentMediaTime()
|
|
||||||
if strongSelf.debugTapCounter.0 < timestamp - 0.4 {
|
|
||||||
strongSelf.debugTapCounter.0 = timestamp
|
|
||||||
strongSelf.debugTapCounter.1 = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if strongSelf.debugTapCounter.0 >= timestamp - 0.4 {
|
|
||||||
strongSelf.debugTapCounter.0 = timestamp
|
|
||||||
strongSelf.debugTapCounter.1 += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if strongSelf.debugTapCounter.1 >= 10 {
|
|
||||||
strongSelf.debugTapCounter.1 = 0
|
|
||||||
|
|
||||||
strongSelf.controllers[index].tabBarItemDebugTapAction?()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let validLayout = strongSelf.validLayout {
|
|
||||||
var updatedLayout = validLayout
|
|
||||||
|
|
||||||
var tabBarHeight: CGFloat
|
|
||||||
var options: ContainerViewLayoutInsetOptions = []
|
|
||||||
if validLayout.metrics.widthClass == .regular {
|
|
||||||
options.insert(.input)
|
|
||||||
}
|
|
||||||
let bottomInset: CGFloat = validLayout.insets(options: options).bottom
|
|
||||||
if !validLayout.safeInsets.left.isZero {
|
|
||||||
tabBarHeight = 34.0 + bottomInset
|
|
||||||
} else {
|
|
||||||
tabBarHeight = 49.0 + bottomInset
|
|
||||||
}
|
|
||||||
updatedLayout.intrinsicInsets.bottom = tabBarHeight
|
|
||||||
|
|
||||||
strongSelf.controllers[index].containerLayoutUpdated(updatedLayout, transition: .immediate)
|
|
||||||
}
|
|
||||||
let startTime = CFAbsoluteTimeGetCurrent()
|
|
||||||
strongSelf.pendingControllerDisposable.set((strongSelf.controllers[index].ready.get()
|
|
||||||
|> deliverOnMainQueue).start(next: { _ in
|
|
||||||
if let strongSelf = self {
|
|
||||||
let readyTime = CFAbsoluteTimeGetCurrent() - startTime
|
|
||||||
if readyTime > 0.5 {
|
|
||||||
print("TabBarController: controller took \(readyTime) to become ready")
|
|
||||||
}
|
|
||||||
|
|
||||||
if strongSelf.selectedIndex == index {
|
|
||||||
if let controller = strongSelf.currentController {
|
|
||||||
if longTap {
|
|
||||||
controller.longTapWithTabBar?()
|
|
||||||
} else {
|
|
||||||
controller.scrollToTopWithTabBar?()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
strongSelf.selectedIndex = index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}, contextAction: { [weak self] index, node, gesture in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if index >= 0 && index < strongSelf.controllers.count {
|
|
||||||
strongSelf.controllers[index].tabBarItemContextAction(sourceNode: node, gesture: gesture)
|
|
||||||
}
|
|
||||||
}, swipeAction: { [weak self] index, direction in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if index >= 0 && index < strongSelf.controllers.count {
|
|
||||||
strongSelf.controllers[index].tabBarItemSwipeAction(direction: direction)
|
|
||||||
}
|
|
||||||
}, toolbarActionSelected: { [weak self] action in
|
|
||||||
self?.currentController?.toolbarActionSelected(action: action)
|
|
||||||
}, disabledPressed: { [weak self] in
|
|
||||||
self?.currentController?.tabBarDisabledAction()
|
|
||||||
})
|
|
||||||
|
|
||||||
self.updateSelectedIndex()
|
|
||||||
self.displayNodeDidLoad()
|
|
||||||
}
|
|
||||||
|
|
||||||
public func updateBackgroundAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition) {
|
|
||||||
let alpha = max(0.0, min(1.0, alpha))
|
|
||||||
transition.updateAlpha(node: self.tabBarControllerNode.tabBarNode.backgroundNode, alpha: alpha, delay: 0.15)
|
|
||||||
transition.updateAlpha(node: self.tabBarControllerNode.tabBarNode.separatorNode, alpha: alpha, delay: 0.15)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func updateSelectedIndex() {
|
|
||||||
if !self.isNodeLoaded {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.tabBarControllerNode.tabBarNode.selectedIndex = self.selectedIndex
|
|
||||||
|
|
||||||
if let currentController = self.currentController {
|
|
||||||
currentController.willMove(toParent: nil)
|
|
||||||
self.tabBarControllerNode.currentControllerNode = nil
|
|
||||||
currentController.removeFromParent()
|
|
||||||
currentController.didMove(toParent: nil)
|
|
||||||
|
|
||||||
self.currentController = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if let _selectedIndex = self._selectedIndex, _selectedIndex < self.controllers.count {
|
|
||||||
self.currentController = self.controllers[_selectedIndex]
|
|
||||||
}
|
|
||||||
|
|
||||||
if let currentController = self.currentController {
|
|
||||||
currentController.willMove(toParent: self)
|
|
||||||
self.tabBarControllerNode.currentControllerNode = currentController.displayNode
|
|
||||||
self.addChild(currentController)
|
|
||||||
currentController.didMove(toParent: self)
|
|
||||||
|
|
||||||
currentController.displayNode.recursivelyEnsureDisplaySynchronously(true)
|
|
||||||
self.statusBar.statusBarStyle = currentController.statusBar.statusBarStyle
|
|
||||||
} else {
|
|
||||||
}
|
|
||||||
|
|
||||||
if let layout = self.validLayout {
|
|
||||||
self.containerLayoutUpdated(layout, transition: .immediate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func updateLayout(transition: ContainedViewLayoutTransition = .immediate) {
|
|
||||||
if let layout = self.validLayout {
|
|
||||||
self.containerLayoutUpdated(layout, transition: transition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
|
||||||
super.containerLayoutUpdated(layout, transition: transition)
|
|
||||||
|
|
||||||
self.validLayout = layout
|
|
||||||
|
|
||||||
self.tabBarControllerNode.containerLayoutUpdated(layout, toolbar: self.currentController?.toolbar, transition: transition)
|
|
||||||
|
|
||||||
if let currentController = self.currentController {
|
|
||||||
currentController.view.frame = CGRect(origin: CGPoint(), size: layout.size)
|
|
||||||
|
|
||||||
var updatedLayout = layout
|
|
||||||
|
|
||||||
var tabBarHeight: CGFloat
|
|
||||||
var options: ContainerViewLayoutInsetOptions = []
|
|
||||||
if updatedLayout.metrics.widthClass == .regular {
|
|
||||||
options.insert(.input)
|
|
||||||
}
|
|
||||||
let bottomInset: CGFloat = updatedLayout.insets(options: options).bottom
|
|
||||||
if !updatedLayout.safeInsets.left.isZero {
|
|
||||||
tabBarHeight = 34.0 + bottomInset
|
|
||||||
} else {
|
|
||||||
tabBarHeight = 49.0 + bottomInset
|
|
||||||
}
|
|
||||||
updatedLayout.intrinsicInsets.bottom = tabBarHeight
|
|
||||||
|
|
||||||
currentController.containerLayoutUpdated(updatedLayout, transition: transition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override open func navigationStackConfigurationUpdated(next: [ViewController]) {
|
|
||||||
super.navigationStackConfigurationUpdated(next: next)
|
|
||||||
for controller in self.controllers {
|
|
||||||
controller.navigationStackConfigurationUpdated(next: next)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override open func viewWillDisappear(_ animated: Bool) {
|
|
||||||
if let currentController = self.currentController {
|
|
||||||
currentController.viewWillDisappear(animated)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override open func viewWillAppear(_ animated: Bool) {
|
|
||||||
if let currentController = self.currentController {
|
|
||||||
currentController.viewWillAppear(animated)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override open func viewDidAppear(_ animated: Bool) {
|
|
||||||
if let currentController = self.currentController {
|
|
||||||
currentController.viewDidAppear(animated)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override open func viewDidDisappear(_ animated: Bool) {
|
|
||||||
if let currentController = self.currentController {
|
|
||||||
currentController.viewDidDisappear(animated)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func setControllers(_ controllers: [ViewController], selectedIndex: Int?) {
|
|
||||||
var updatedSelectedIndex: Int? = selectedIndex
|
|
||||||
if updatedSelectedIndex == nil, let selectedIndex = self._selectedIndex, selectedIndex < self.controllers.count {
|
|
||||||
if let index = controllers.firstIndex(where: { $0 === self.controllers[selectedIndex] }) {
|
|
||||||
updatedSelectedIndex = index
|
|
||||||
} else {
|
|
||||||
updatedSelectedIndex = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.controllers = controllers
|
|
||||||
self.tabBarControllerNode.tabBarNode.tabBarItems = self.controllers.map({ TabBarNodeItem(item: $0.tabBarItem, contextActionType: $0.tabBarItemContextActionType) })
|
|
||||||
|
|
||||||
let signals = combineLatest(self.controllers.map({ $0.tabBarItem }).map { tabBarItem -> Signal<Bool, NoError> in
|
|
||||||
if let tabBarItem = tabBarItem, tabBarItem.image == nil {
|
|
||||||
return Signal { [weak tabBarItem] subscriber in
|
|
||||||
let index = tabBarItem?.addSetImageListener({ image in
|
|
||||||
if image != nil {
|
|
||||||
subscriber.putNext(true)
|
|
||||||
subscriber.putCompletion()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return ActionDisposable {
|
|
||||||
Queue.mainQueue().async {
|
|
||||||
if let index = index {
|
|
||||||
tabBarItem?.removeSetImageListener(index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|> runOn(.mainQueue())
|
|
||||||
} else {
|
|
||||||
return .single(true)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|> map { items -> Bool in
|
|
||||||
for item in items {
|
|
||||||
if !item {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|> filter { $0 }
|
|
||||||
|> take(1)
|
|
||||||
|
|
||||||
let allReady = signals
|
|
||||||
|> deliverOnMainQueue
|
|
||||||
|> mapToSignal { _ -> Signal<Bool, NoError> in
|
|
||||||
// wait for tab bar items to be applied
|
|
||||||
return .single(true)
|
|
||||||
|> delay(0.0, queue: Queue.mainQueue())
|
|
||||||
}
|
|
||||||
|
|
||||||
self._ready.set(allReady)
|
|
||||||
|
|
||||||
if let updatedSelectedIndex = updatedSelectedIndex {
|
|
||||||
self.selectedIndex = updatedSelectedIndex
|
|
||||||
self.updateSelectedIndex()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,83 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import UIKit
|
|
||||||
import SwiftSignalKit
|
|
||||||
|
|
||||||
final class TabBarTapRecognizer: UIGestureRecognizer {
|
|
||||||
private let tap: (CGPoint) -> Void
|
|
||||||
private let longTap: (CGPoint) -> Void
|
|
||||||
|
|
||||||
private var initialLocation: CGPoint?
|
|
||||||
private var longTapTimer: SwiftSignalKit.Timer?
|
|
||||||
|
|
||||||
init(tap: @escaping (CGPoint) -> Void, longTap: @escaping (CGPoint) -> Void) {
|
|
||||||
self.tap = tap
|
|
||||||
self.longTap = longTap
|
|
||||||
|
|
||||||
super.init(target: nil, action: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func reset() {
|
|
||||||
super.reset()
|
|
||||||
|
|
||||||
self.initialLocation = nil
|
|
||||||
self.longTapTimer?.invalidate()
|
|
||||||
self.longTapTimer = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
|
||||||
super.touchesBegan(touches, with: event)
|
|
||||||
|
|
||||||
if self.initialLocation == nil {
|
|
||||||
self.initialLocation = touches.first?.location(in: self.view)
|
|
||||||
let longTapTimer = SwiftSignalKit.Timer(timeout: 0.4, repeat: false, completion: { [weak self] in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if let initialLocation = strongSelf.initialLocation {
|
|
||||||
strongSelf.initialLocation = nil
|
|
||||||
strongSelf.longTap(initialLocation)
|
|
||||||
strongSelf.state = .ended
|
|
||||||
}
|
|
||||||
}, queue: Queue.mainQueue())
|
|
||||||
self.longTapTimer?.invalidate()
|
|
||||||
self.longTapTimer = longTapTimer
|
|
||||||
longTapTimer.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
|
|
||||||
super.touchesEnded(touches, with: event)
|
|
||||||
|
|
||||||
if let initialLocation = self.initialLocation {
|
|
||||||
self.initialLocation = nil
|
|
||||||
self.longTapTimer?.invalidate()
|
|
||||||
self.longTapTimer = nil
|
|
||||||
self.tap(initialLocation)
|
|
||||||
self.state = .ended
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
|
|
||||||
super.touchesMoved(touches, with: event)
|
|
||||||
|
|
||||||
if let initialLocation = self.initialLocation, let location = touches.first?.location(in: self.view) {
|
|
||||||
let deltaX = initialLocation.x - location.x
|
|
||||||
let deltaY = initialLocation.y - location.y
|
|
||||||
if deltaX * deltaX + deltaY * deltaY > 4.0 {
|
|
||||||
self.longTapTimer?.invalidate()
|
|
||||||
self.longTapTimer = nil
|
|
||||||
self.initialLocation = nil
|
|
||||||
self.state = .failed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
|
|
||||||
super.touchesCancelled(touches, with: event)
|
|
||||||
|
|
||||||
self.initialLocation = nil
|
|
||||||
self.longTapTimer?.invalidate()
|
|
||||||
self.longTapTimer = nil
|
|
||||||
self.state = .failed
|
|
||||||
}
|
|
||||||
}
|
|
@ -1063,9 +1063,17 @@ public class TextNode: ASDisplayNode {
|
|||||||
let tokenString = "\u{2026}"
|
let tokenString = "\u{2026}"
|
||||||
let truncatedTokenString = NSAttributedString(string: tokenString, attributes: truncationTokenAttributes)
|
let truncatedTokenString = NSAttributedString(string: tokenString, attributes: truncationTokenAttributes)
|
||||||
let truncationToken = CTLineCreateWithAttributedString(truncatedTokenString)
|
let truncationToken = CTLineCreateWithAttributedString(truncatedTokenString)
|
||||||
|
|
||||||
coreTextLine = CTLineCreateTruncatedLine(originalLine, Double(lineConstrainedSize.width), truncationType, truncationToken) ?? truncationToken
|
coreTextLine = CTLineCreateTruncatedLine(originalLine, Double(lineConstrainedSize.width), truncationType, truncationToken) ?? truncationToken
|
||||||
brokenLineRange.length = CTLineGetGlyphCount(coreTextLine) - 1
|
let runs = (CTLineGetGlyphRuns(coreTextLine) as [AnyObject]) as! [CTRun]
|
||||||
|
for run in runs {
|
||||||
|
let runAttributes: NSDictionary = CTRunGetAttributes(run)
|
||||||
|
if let _ = runAttributes["CTForegroundColorFromContext"] {
|
||||||
|
brokenLineRange.length = CTRunGetStringRange(run).location
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// brokenLineRange.length = CTLineGetGlyphCount(coreTextLine) - 1
|
||||||
if brokenLineRange.location + brokenLineRange.length > attributedString.length {
|
if brokenLineRange.location + brokenLineRange.length > attributedString.length {
|
||||||
brokenLineRange.length = attributedString.length - brokenLineRange.location
|
brokenLineRange.length = attributedString.length - brokenLineRange.location
|
||||||
}
|
}
|
||||||
@ -1330,7 +1338,9 @@ public class TextNode: ASDisplayNode {
|
|||||||
context.saveGState()
|
context.saveGState()
|
||||||
var clipRects: [CGRect] = []
|
var clipRects: [CGRect] = []
|
||||||
for spoiler in line.spoilerWords {
|
for spoiler in line.spoilerWords {
|
||||||
clipRects.append(spoiler.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY))
|
var spoilerClipRect = spoiler.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY)
|
||||||
|
spoilerClipRect.size.height += 1.0 + UIScreenPixel
|
||||||
|
clipRects.append(spoilerClipRect)
|
||||||
}
|
}
|
||||||
context.clip(to: clipRects)
|
context.clip(to: clipRects)
|
||||||
}
|
}
|
||||||
@ -1365,7 +1375,9 @@ public class TextNode: ASDisplayNode {
|
|||||||
context.restoreGState()
|
context.restoreGState()
|
||||||
} else {
|
} else {
|
||||||
for spoiler in line.spoilerWords {
|
for spoiler in line.spoilerWords {
|
||||||
clearRects.append(spoiler.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY))
|
var spoilerClearRect = spoiler.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY)
|
||||||
|
spoilerClearRect.size.height += 1.0 + UIScreenPixel
|
||||||
|
clearRects.append(spoilerClearRect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,28 @@ import Foundation
|
|||||||
import UIKit
|
import UIKit
|
||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
|
|
||||||
|
public enum ToolbarActionOption {
|
||||||
|
case left
|
||||||
|
case right
|
||||||
|
case middle
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class ToolbarTheme {
|
||||||
|
public let barBackgroundColor: UIColor
|
||||||
|
public let barSeparatorColor: UIColor
|
||||||
|
public let barTextColor: UIColor
|
||||||
|
public let barSelectedTextColor: UIColor
|
||||||
|
|
||||||
|
public init(barBackgroundColor: UIColor, barSeparatorColor: UIColor, barTextColor: UIColor, barSelectedTextColor: UIColor) {
|
||||||
|
self.barBackgroundColor = barBackgroundColor
|
||||||
|
self.barSeparatorColor = barSeparatorColor
|
||||||
|
self.barTextColor = barTextColor
|
||||||
|
self.barSelectedTextColor = barSelectedTextColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public final class ToolbarNode: ASDisplayNode {
|
public final class ToolbarNode: ASDisplayNode {
|
||||||
private var theme: TabBarControllerTheme
|
private var theme: ToolbarTheme
|
||||||
private let displaySeparator: Bool
|
private let displaySeparator: Bool
|
||||||
public var left: () -> Void
|
public var left: () -> Void
|
||||||
public var right: () -> Void
|
public var right: () -> Void
|
||||||
@ -18,14 +38,14 @@ public final class ToolbarNode: ASDisplayNode {
|
|||||||
private let middleTitle: ImmediateTextNode
|
private let middleTitle: ImmediateTextNode
|
||||||
private let middleButton: HighlightTrackingButtonNode
|
private let middleButton: HighlightTrackingButtonNode
|
||||||
|
|
||||||
public init(theme: TabBarControllerTheme, displaySeparator: Bool = false, left: @escaping () -> Void = {}, right: @escaping () -> Void = {}, middle: @escaping () -> Void = {}) {
|
public init(theme: ToolbarTheme, displaySeparator: Bool = false, left: @escaping () -> Void = {}, right: @escaping () -> Void = {}, middle: @escaping () -> Void = {}) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.displaySeparator = displaySeparator
|
self.displaySeparator = displaySeparator
|
||||||
self.left = left
|
self.left = left
|
||||||
self.right = right
|
self.right = right
|
||||||
self.middle = middle
|
self.middle = middle
|
||||||
|
|
||||||
self.backgroundNode = NavigationBackgroundNode(color: theme.tabBarBackgroundColor)
|
self.backgroundNode = NavigationBackgroundNode(color: theme.barBackgroundColor)
|
||||||
|
|
||||||
self.separatorNode = ASDisplayNode()
|
self.separatorNode = ASDisplayNode()
|
||||||
self.separatorNode.isLayerBacked = true
|
self.separatorNode.isLayerBacked = true
|
||||||
@ -100,9 +120,9 @@ public final class ToolbarNode: ASDisplayNode {
|
|||||||
self.middleButton.accessibilityTraits = .button
|
self.middleButton.accessibilityTraits = .button
|
||||||
}
|
}
|
||||||
|
|
||||||
public func updateTheme(_ theme: TabBarControllerTheme) {
|
public func updateTheme(_ theme: ToolbarTheme) {
|
||||||
self.separatorNode.backgroundColor = theme.tabBarSeparatorColor
|
self.separatorNode.backgroundColor = theme.barSeparatorColor
|
||||||
self.backgroundNode.updateColor(color: theme.tabBarBackgroundColor, transition: .immediate)
|
self.backgroundNode.updateColor(color: theme.barBackgroundColor, transition: .immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, additionalSideInsets: UIEdgeInsets, bottomInset: CGFloat, toolbar: Toolbar, transition: ContainedViewLayoutTransition) {
|
public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, additionalSideInsets: UIEdgeInsets, bottomInset: CGFloat, toolbar: Toolbar, transition: ContainedViewLayoutTransition) {
|
||||||
@ -112,10 +132,10 @@ public final class ToolbarNode: ASDisplayNode {
|
|||||||
|
|
||||||
var sideInset: CGFloat = 16.0
|
var sideInset: CGFloat = 16.0
|
||||||
|
|
||||||
self.leftTitle.attributedText = NSAttributedString(string: toolbar.leftAction?.title ?? "", font: Font.regular(17.0), textColor: (toolbar.leftAction?.isEnabled ?? false) ? self.theme.tabBarSelectedTextColor : self.theme.tabBarTextColor)
|
self.leftTitle.attributedText = NSAttributedString(string: toolbar.leftAction?.title ?? "", font: Font.regular(17.0), textColor: (toolbar.leftAction?.isEnabled ?? false) ? self.theme.barSelectedTextColor : self.theme.barTextColor)
|
||||||
self.leftButton.accessibilityLabel = toolbar.leftAction?.title
|
self.leftButton.accessibilityLabel = toolbar.leftAction?.title
|
||||||
|
|
||||||
self.rightTitle.attributedText = NSAttributedString(string: toolbar.rightAction?.title ?? "", font: Font.regular(17.0), textColor: (toolbar.rightAction?.isEnabled ?? false) ? self.theme.tabBarSelectedTextColor : self.theme.tabBarTextColor)
|
self.rightTitle.attributedText = NSAttributedString(string: toolbar.rightAction?.title ?? "", font: Font.regular(17.0), textColor: (toolbar.rightAction?.isEnabled ?? false) ? self.theme.barSelectedTextColor : self.theme.barTextColor)
|
||||||
self.rightButton.accessibilityLabel = toolbar.rightAction?.title
|
self.rightButton.accessibilityLabel = toolbar.rightAction?.title
|
||||||
|
|
||||||
let middleColor: UIColor
|
let middleColor: UIColor
|
||||||
@ -123,15 +143,15 @@ public final class ToolbarNode: ASDisplayNode {
|
|||||||
if middleAction.isEnabled {
|
if middleAction.isEnabled {
|
||||||
switch middleAction.color {
|
switch middleAction.color {
|
||||||
case .accent:
|
case .accent:
|
||||||
middleColor = self.theme.tabBarSelectedTextColor
|
middleColor = self.theme.barSelectedTextColor
|
||||||
case let .custom(color):
|
case let .custom(color):
|
||||||
middleColor = color
|
middleColor = color
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
middleColor = self.theme.tabBarTextColor
|
middleColor = self.theme.barTextColor
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
middleColor = self.theme.tabBarTextColor
|
middleColor = self.theme.barTextColor
|
||||||
}
|
}
|
||||||
self.middleTitle.attributedText = NSAttributedString(string: toolbar.middleAction?.title ?? "", font: Font.regular(17.0), textColor: middleColor)
|
self.middleTitle.attributedText = NSAttributedString(string: toolbar.middleAction?.title ?? "", font: Font.regular(17.0), textColor: middleColor)
|
||||||
self.middleButton.accessibilityLabel = toolbar.middleAction?.title
|
self.middleButton.accessibilityLabel = toolbar.middleAction?.title
|
||||||
|
@ -191,7 +191,7 @@ public enum TabBarItemContextActionType {
|
|||||||
|
|
||||||
public let statusBar: StatusBar
|
public let statusBar: StatusBar
|
||||||
public let navigationBar: NavigationBar?
|
public let navigationBar: NavigationBar?
|
||||||
private(set) var toolbar: Toolbar?
|
public private(set) var toolbar: Toolbar?
|
||||||
|
|
||||||
public var displayNavigationBar = true
|
public var displayNavigationBar = true
|
||||||
open var navigationBarRequiresEntireLayoutUpdate: Bool {
|
open var navigationBarRequiresEntireLayoutUpdate: Bool {
|
||||||
|
@ -501,7 +501,7 @@ open class ItemListControllerNode: ASDisplayNode {
|
|||||||
transition.updateFrame(node: toolbarNode, frame: toolbarFrame)
|
transition.updateFrame(node: toolbarNode, frame: toolbarFrame)
|
||||||
toolbarNode.updateLayout(size: toolbarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: layout.intrinsicInsets.bottom, toolbar: toolbarItem.toolbar, transition: transition)
|
toolbarNode.updateLayout(size: toolbarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: layout.intrinsicInsets.bottom, toolbar: toolbarItem.toolbar, transition: transition)
|
||||||
} else if let theme = self.theme {
|
} else if let theme = self.theme {
|
||||||
let toolbarNode = ToolbarNode(theme: TabBarControllerTheme(rootControllerTheme: theme), displaySeparator: true)
|
let toolbarNode = ToolbarNode(theme: ToolbarTheme(rootControllerTheme: theme), displaySeparator: true)
|
||||||
toolbarNode.frame = toolbarFrame
|
toolbarNode.frame = toolbarFrame
|
||||||
toolbarNode.updateLayout(size: toolbarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: layout.intrinsicInsets.bottom, toolbar: toolbarItem.toolbar, transition: .immediate)
|
toolbarNode.updateLayout(size: toolbarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: layout.intrinsicInsets.bottom, toolbar: toolbarItem.toolbar, transition: .immediate)
|
||||||
self.addSubnode(toolbarNode)
|
self.addSubnode(toolbarNode)
|
||||||
|
@ -1244,7 +1244,7 @@ public final class MediaBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func removeOtherCachedResources(paths: [String]) -> Signal<Void, NoError> {
|
public func removeOtherCachedResources(paths: [String]) -> Signal<Float, NoError> {
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
self.dataQueue.async {
|
self.dataQueue.async {
|
||||||
var keepPrefixes: [String] = []
|
var keepPrefixes: [String] = []
|
||||||
@ -1254,14 +1254,31 @@ public final class MediaBox {
|
|||||||
keepPrefixes.append(resourcePaths.complete)
|
keepPrefixes.append(resourcePaths.complete)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var count: Int = 0
|
||||||
|
let totalCount = paths.count
|
||||||
|
if totalCount == 0 {
|
||||||
|
subscriber.putNext(1.0)
|
||||||
|
subscriber.putCompletion()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let reportProgress: (Int) -> Void = { count in
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
subscriber.putNext(min(1.0, Float(count) / Float(totalCount)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
outer: for path in paths {
|
outer: for path in paths {
|
||||||
for prefix in keepPrefixes {
|
for prefix in keepPrefixes {
|
||||||
if path.starts(with: prefix) {
|
if path.starts(with: prefix) {
|
||||||
|
count += 1
|
||||||
continue outer
|
continue outer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
count += 1
|
||||||
unlink(self.basePath + "/" + path)
|
unlink(self.basePath + "/" + path)
|
||||||
|
reportProgress(count)
|
||||||
}
|
}
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
}
|
}
|
||||||
@ -1269,27 +1286,10 @@ public final class MediaBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func removeCachedResources(_ ids: Set<MediaResourceId>, force: Bool = false) -> Signal<Void, NoError> {
|
public func removeCachedResources(_ ids: Set<MediaResourceId>, force: Bool = false) -> Signal<Float, NoError> {
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
self.dataQueue.async {
|
self.dataQueue.async {
|
||||||
for id in ids {
|
|
||||||
if !force {
|
|
||||||
if self.fileContexts[id] != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if self.keepResourceContexts[id] != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let paths = self.storePathsForId(id)
|
|
||||||
unlink(paths.complete)
|
|
||||||
unlink(paths.partial)
|
|
||||||
unlink(paths.partial + ".meta")
|
|
||||||
self.fileContexts.removeValue(forKey: id)
|
|
||||||
}
|
|
||||||
|
|
||||||
let uniqueIds = Set(ids.map { $0.stringRepresentation })
|
let uniqueIds = Set(ids.map { $0.stringRepresentation })
|
||||||
|
|
||||||
var pathsToDelete: [String] = []
|
var pathsToDelete: [String] = []
|
||||||
|
|
||||||
for cacheType in ["cache", "short-cache"] {
|
for cacheType in ["cache", "short-cache"] {
|
||||||
@ -1309,8 +1309,46 @@ public final class MediaBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var count: Int = 0
|
||||||
|
let totalCount = ids.count * 3 + pathsToDelete.count
|
||||||
|
if totalCount == 0 {
|
||||||
|
subscriber.putNext(1.0)
|
||||||
|
subscriber.putCompletion()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let reportProgress: (Int) -> Void = { count in
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
subscriber.putNext(min(1.0, Float(count) / Float(totalCount)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for id in ids {
|
||||||
|
if !force {
|
||||||
|
if self.fileContexts[id] != nil {
|
||||||
|
count += 3
|
||||||
|
reportProgress(count)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if self.keepResourceContexts[id] != nil {
|
||||||
|
count += 3
|
||||||
|
reportProgress(count)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let paths = self.storePathsForId(id)
|
||||||
|
unlink(paths.complete)
|
||||||
|
unlink(paths.partial)
|
||||||
|
unlink(paths.partial + ".meta")
|
||||||
|
self.fileContexts.removeValue(forKey: id)
|
||||||
|
count += 3
|
||||||
|
reportProgress(count)
|
||||||
|
}
|
||||||
|
|
||||||
for path in pathsToDelete {
|
for path in pathsToDelete {
|
||||||
unlink(path)
|
unlink(path)
|
||||||
|
count += 1
|
||||||
|
reportProgress(count)
|
||||||
}
|
}
|
||||||
|
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
import Display
|
import Display
|
||||||
|
import AsyncDisplayKit
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import Postbox
|
import Postbox
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
@ -14,6 +15,8 @@ import AccountContext
|
|||||||
import ItemListPeerItem
|
import ItemListPeerItem
|
||||||
import DeleteChatPeerActionSheetItem
|
import DeleteChatPeerActionSheetItem
|
||||||
import UndoUI
|
import UndoUI
|
||||||
|
import AnimatedStickerNode
|
||||||
|
import TelegramAnimatedStickerNode
|
||||||
|
|
||||||
private func totalDiskSpace() -> Int64 {
|
private func totalDiskSpace() -> Int64 {
|
||||||
do {
|
do {
|
||||||
@ -275,15 +278,15 @@ private enum StorageUsageEntry: ItemListNodeEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct StoragUsageState: Equatable {
|
private struct StorageUsageState: Equatable {
|
||||||
let peerIdWithRevealedOptions: PeerId?
|
let peerIdWithRevealedOptions: PeerId?
|
||||||
|
|
||||||
func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> StoragUsageState {
|
func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> StorageUsageState {
|
||||||
return StoragUsageState(peerIdWithRevealedOptions: peerIdWithRevealedOptions)
|
return StorageUsageState(peerIdWithRevealedOptions: peerIdWithRevealedOptions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func storageUsageControllerEntries(presentationData: PresentationData, cacheSettings: CacheStorageSettings, cacheStats: CacheUsageStatsResult?, state: StoragUsageState) -> [StorageUsageEntry] {
|
private func storageUsageControllerEntries(presentationData: PresentationData, cacheSettings: CacheStorageSettings, cacheStats: CacheUsageStatsResult?, state: StorageUsageState) -> [StorageUsageEntry] {
|
||||||
var entries: [StorageUsageEntry] = []
|
var entries: [StorageUsageEntry] = []
|
||||||
|
|
||||||
entries.append(.keepMediaHeader(presentationData.theme, presentationData.strings.Cache_KeepMedia.uppercased()))
|
entries.append(.keepMediaHeader(presentationData.theme, presentationData.strings.Cache_KeepMedia.uppercased()))
|
||||||
@ -398,9 +401,9 @@ func cacheUsageStats(context: AccountContext) -> Signal<CacheUsageStatsResult?,
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func storageUsageController(context: AccountContext, cacheUsagePromise: Promise<CacheUsageStatsResult?>? = nil, isModal: Bool = false) -> ViewController {
|
public func storageUsageController(context: AccountContext, cacheUsagePromise: Promise<CacheUsageStatsResult?>? = nil, isModal: Bool = false) -> ViewController {
|
||||||
let statePromise = ValuePromise(StoragUsageState(peerIdWithRevealedOptions: nil))
|
let statePromise = ValuePromise(StorageUsageState(peerIdWithRevealedOptions: nil))
|
||||||
let stateValue = Atomic(value: StoragUsageState(peerIdWithRevealedOptions: nil))
|
let stateValue = Atomic(value: StorageUsageState(peerIdWithRevealedOptions: nil))
|
||||||
let updateState: ((StoragUsageState) -> StoragUsageState) -> Void = { f in
|
let updateState: ((StorageUsageState) -> StorageUsageState) -> Void = { f in
|
||||||
statePromise.set(stateValue.modify { f($0) })
|
statePromise.set(stateValue.modify { f($0) })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -546,7 +549,9 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P
|
|||||||
selectedSize = totalSize
|
selectedSize = totalSize
|
||||||
|
|
||||||
if !items.isEmpty {
|
if !items.isEmpty {
|
||||||
items.append(ActionSheetButtonItem(title: presentationData.strings.Cache_Clear("\(dataSizeString(totalSize, formatting: DataSizeStringFormatting(presentationData: presentationData)))").string, action: {
|
var cancelImpl: (() -> Void)?
|
||||||
|
|
||||||
|
items.append(ActionSheetButtonItem(title: presentationData.strings.Cache_Clear("\(dataSizeString(totalSize, formatting: DataSizeStringFormatting(presentationData: presentationData)))").string, action: { [weak controller] in
|
||||||
if let statsPromise = statsPromise {
|
if let statsPromise = statsPromise {
|
||||||
let clearCategories = sizeIndex.keys.filter({ sizeIndex[$0]!.0 })
|
let clearCategories = sizeIndex.keys.filter({ sizeIndex[$0]!.0 })
|
||||||
|
|
||||||
@ -582,20 +587,37 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P
|
|||||||
var updatedTempPaths = stats.tempPaths
|
var updatedTempPaths = stats.tempPaths
|
||||||
var updatedTempSize = stats.tempSize
|
var updatedTempSize = stats.tempSize
|
||||||
|
|
||||||
var signal: Signal<Void, NoError> = context.engine.resources.clearCachedMediaResources(mediaResourceIds: clearResourceIds)
|
var signal: Signal<Float, NoError> = context.engine.resources.clearCachedMediaResources(mediaResourceIds: clearResourceIds)
|
||||||
if otherSize.0 {
|
if otherSize.0 {
|
||||||
let removeTempFiles: Signal<Void, NoError> = Signal { subscriber in
|
let removeTempFiles: Signal<Float, NoError> = Signal { subscriber in
|
||||||
let fileManager = FileManager.default
|
let fileManager = FileManager.default
|
||||||
|
var count: Int = 0
|
||||||
|
let totalCount = stats.tempPaths.count
|
||||||
|
|
||||||
|
let reportProgress: (Int) -> Void = { count in
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
subscriber.putNext(min(1.0, Float(count) / Float(totalCount)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if totalCount == 0 {
|
||||||
|
subscriber.putNext(1.0)
|
||||||
|
subscriber.putCompletion()
|
||||||
|
return EmptyDisposable
|
||||||
|
}
|
||||||
|
|
||||||
for path in stats.tempPaths {
|
for path in stats.tempPaths {
|
||||||
let _ = try? fileManager.removeItem(atPath: path)
|
let _ = try? fileManager.removeItem(atPath: path)
|
||||||
|
count += 1
|
||||||
|
reportProgress(count)
|
||||||
}
|
}
|
||||||
|
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
return EmptyDisposable
|
return EmptyDisposable
|
||||||
} |> runOn(Queue.concurrentDefaultQueue())
|
} |> runOn(Queue.concurrentDefaultQueue())
|
||||||
signal = signal
|
signal = (signal |> map { $0 * 0.7 })
|
||||||
|> then(context.account.postbox.mediaBox.removeOtherCachedResources(paths: stats.otherPaths))
|
|> then(context.account.postbox.mediaBox.removeOtherCachedResources(paths: stats.otherPaths) |> map { 0.7 + 0.2 * $0 })
|
||||||
|> then(removeTempFiles)
|
|> then(removeTempFiles |> map { 0.9 + 0.1 * $0 })
|
||||||
}
|
}
|
||||||
|
|
||||||
if otherSize.0 {
|
if otherSize.0 {
|
||||||
@ -606,49 +628,39 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P
|
|||||||
updatedTempSize = 0
|
updatedTempSize = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let progressPromise = ValuePromise<Float>(0.0)
|
||||||
|
let overlayNode = StorageUsageClearProgressOverlayNode(presentationData: presentationData)
|
||||||
|
overlayNode.setProgressSignal(progressPromise.get())
|
||||||
|
controller?.setItemGroupOverlayNode(groupIndex: 0, node: overlayNode)
|
||||||
|
|
||||||
let resultStats = CacheUsageStats(media: media, mediaResourceIds: stats.mediaResourceIds, peers: stats.peers, otherSize: updatedOtherSize, otherPaths: updatedOtherPaths, cacheSize: updatedCacheSize, tempPaths: updatedTempPaths, tempSize: updatedTempSize, immutableSize: stats.immutableSize)
|
let resultStats = CacheUsageStats(media: media, mediaResourceIds: stats.mediaResourceIds, peers: stats.peers, otherSize: updatedOtherSize, otherPaths: updatedOtherPaths, cacheSize: updatedCacheSize, tempPaths: updatedTempPaths, tempSize: updatedTempSize, immutableSize: stats.immutableSize)
|
||||||
|
|
||||||
var cancelImpl: (() -> Void)?
|
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
||||||
let progressSignal = Signal<Never, NoError> { subscriber in
|
|
||||||
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
|
|
||||||
cancelImpl?()
|
|
||||||
}))
|
|
||||||
presentControllerImpl?(controller, .window(.root), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
|
||||||
return ActionDisposable { [weak controller] in
|
|
||||||
Queue.mainQueue().async() {
|
|
||||||
controller?.dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|> runOn(Queue.mainQueue())
|
|
||||||
|> delay(0.15, queue: Queue.mainQueue())
|
|
||||||
let progressDisposable = progressSignal.start()
|
|
||||||
|
|
||||||
signal = signal
|
|
||||||
|> afterDisposed {
|
|
||||||
Queue.mainQueue().async {
|
|
||||||
progressDisposable.dispose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cancelImpl = {
|
cancelImpl = {
|
||||||
clearDisposable.set(nil)
|
clearDisposable.set(nil)
|
||||||
resetStats()
|
resetStats()
|
||||||
}
|
}
|
||||||
|
statsPromise.set(.single(.result(resultStats)))
|
||||||
clearDisposable.set((signal
|
clearDisposable.set((signal
|
||||||
|> deliverOnMainQueue).start(completed: {
|
|> deliverOnMainQueue).start(next: { progress in
|
||||||
|
progressPromise.set(progress)
|
||||||
|
}, completed: {
|
||||||
statsPromise.set(.single(.result(resultStats)))
|
statsPromise.set(.single(.result(resultStats)))
|
||||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(selectedSize, formatting: DataSizeStringFormatting(presentationData: presentationData)))", stringForDeviceType()).string), elevatedLayout: false, action: { _ in return false }), .current, nil)
|
progressPromise.set(1.0)
|
||||||
|
Queue.mainQueue().after(1.0) {
|
||||||
|
dismissAction()
|
||||||
|
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(selectedSize, formatting: DataSizeStringFormatting(presentationData: presentationData)))", stringForDeviceType()).string), elevatedLayout: false, action: { _ in return false }), .current, nil)
|
||||||
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
dismissAction()
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
controller.setItemGroups([
|
controller.setItemGroups([
|
||||||
ActionSheetItemGroup(items: items),
|
ActionSheetItemGroup(items: items),
|
||||||
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: {
|
||||||
])
|
cancelImpl?()
|
||||||
|
dismissAction()
|
||||||
|
})])
|
||||||
|
])
|
||||||
presentControllerImpl?(controller, .window(.root), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
presentControllerImpl?(controller, .window(.root), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -743,7 +755,9 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P
|
|||||||
selectedSize = totalSize
|
selectedSize = totalSize
|
||||||
|
|
||||||
if !items.isEmpty {
|
if !items.isEmpty {
|
||||||
items.append(ActionSheetButtonItem(title: presentationData.strings.Cache_Clear("\(dataSizeString(totalSize, formatting: DataSizeStringFormatting(presentationData: presentationData)))").string, action: {
|
var cancelImpl: (() -> Void)?
|
||||||
|
|
||||||
|
items.append(ActionSheetButtonItem(title: presentationData.strings.Cache_Clear("\(dataSizeString(totalSize, formatting: DataSizeStringFormatting(presentationData: presentationData)))").string, action: { [weak controller] in
|
||||||
if let statsPromise = statsPromise {
|
if let statsPromise = statsPromise {
|
||||||
let clearCategories = sizeIndex.keys.filter({ sizeIndex[$0]!.0 })
|
let clearCategories = sizeIndex.keys.filter({ sizeIndex[$0]!.0 })
|
||||||
var clearMediaIds = Set<MediaId>()
|
var clearMediaIds = Set<MediaId>()
|
||||||
@ -785,50 +799,39 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var signal = context.engine.resources.clearCachedMediaResources(mediaResourceIds: clearResourceIds)
|
let signal = context.engine.resources.clearCachedMediaResources(mediaResourceIds: clearResourceIds)
|
||||||
|
|
||||||
|
let progressPromise = ValuePromise<Float>(0.0)
|
||||||
|
let overlayNode = StorageUsageClearProgressOverlayNode(presentationData: presentationData)
|
||||||
|
overlayNode.setProgressSignal(progressPromise.get())
|
||||||
|
controller?.setItemGroupOverlayNode(groupIndex: 0, node: overlayNode)
|
||||||
|
|
||||||
let resultStats = CacheUsageStats(media: media, mediaResourceIds: stats.mediaResourceIds, peers: stats.peers, otherSize: stats.otherSize, otherPaths: stats.otherPaths, cacheSize: stats.cacheSize, tempPaths: stats.tempPaths, tempSize: stats.tempSize, immutableSize: stats.immutableSize)
|
let resultStats = CacheUsageStats(media: media, mediaResourceIds: stats.mediaResourceIds, peers: stats.peers, otherSize: stats.otherSize, otherPaths: stats.otherPaths, cacheSize: stats.cacheSize, tempPaths: stats.tempPaths, tempSize: stats.tempSize, immutableSize: stats.immutableSize)
|
||||||
|
|
||||||
var cancelImpl: (() -> Void)?
|
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
||||||
let progressSignal = Signal<Never, NoError> { subscriber in
|
|
||||||
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
|
|
||||||
cancelImpl?()
|
|
||||||
}))
|
|
||||||
presentControllerImpl?(controller, .window(.root), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
|
||||||
return ActionDisposable { [weak controller] in
|
|
||||||
Queue.mainQueue().async() {
|
|
||||||
controller?.dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|> runOn(Queue.mainQueue())
|
|
||||||
|> delay(0.15, queue: Queue.mainQueue())
|
|
||||||
let progressDisposable = progressSignal.start()
|
|
||||||
|
|
||||||
signal = signal
|
|
||||||
|> afterDisposed {
|
|
||||||
Queue.mainQueue().async {
|
|
||||||
progressDisposable.dispose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cancelImpl = {
|
cancelImpl = {
|
||||||
clearDisposable.set(nil)
|
clearDisposable.set(nil)
|
||||||
resetStats()
|
resetStats()
|
||||||
}
|
}
|
||||||
clearDisposable.set((signal
|
clearDisposable.set((signal
|
||||||
|> deliverOnMainQueue).start(completed: {
|
|> deliverOnMainQueue).start(next: { progress in
|
||||||
|
progressPromise.set(progress)
|
||||||
|
}, completed: {
|
||||||
statsPromise.set(.single(.result(resultStats)))
|
statsPromise.set(.single(.result(resultStats)))
|
||||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(selectedSize, formatting: DataSizeStringFormatting(presentationData: presentationData)))", stringForDeviceType()).string), elevatedLayout: false, action: { _ in return false }), .current, nil)
|
progressPromise.set(1.0)
|
||||||
|
Queue.mainQueue().after(1.0) {
|
||||||
|
dismissAction()
|
||||||
|
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(selectedSize, formatting: DataSizeStringFormatting(presentationData: presentationData)))", stringForDeviceType()).string), elevatedLayout: false, action: { _ in return false }), .current, nil)
|
||||||
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
dismissAction()
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
controller.setItemGroups([
|
controller.setItemGroups([
|
||||||
ActionSheetItemGroup(items: items),
|
ActionSheetItemGroup(items: items),
|
||||||
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: {
|
||||||
|
cancelImpl?()
|
||||||
|
dismissAction()
|
||||||
|
})])
|
||||||
])
|
])
|
||||||
presentControllerImpl?(controller, .window(.root), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
presentControllerImpl?(controller, .window(.root), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
}
|
}
|
||||||
@ -995,3 +998,105 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P
|
|||||||
}
|
}
|
||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class StorageUsageClearProgressOverlayNode: ASDisplayNode, ActionSheetGroupOverlayNode {
|
||||||
|
private let presentationData: PresentationData
|
||||||
|
|
||||||
|
private let animationNode: AnimatedStickerNode
|
||||||
|
private let progressTextNode: ImmediateTextNode
|
||||||
|
private let descriptionTextNode: ImmediateTextNode
|
||||||
|
private let progressBackgroundNode: ASDisplayNode
|
||||||
|
private let progressForegroundNode: ASDisplayNode
|
||||||
|
|
||||||
|
private let progressDisposable = MetaDisposable()
|
||||||
|
|
||||||
|
private var validLayout: CGSize?
|
||||||
|
|
||||||
|
init(presentationData: PresentationData) {
|
||||||
|
self.presentationData = presentationData
|
||||||
|
|
||||||
|
self.animationNode = AnimatedStickerNode()
|
||||||
|
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "ClearCache"), width: 256, height: 256, playbackMode: .loop, mode: .direct(cachePathPrefix: nil))
|
||||||
|
self.animationNode.visibility = true
|
||||||
|
|
||||||
|
self.progressTextNode = ImmediateTextNode()
|
||||||
|
self.progressTextNode.textAlignment = .center
|
||||||
|
|
||||||
|
self.descriptionTextNode = ImmediateTextNode()
|
||||||
|
self.descriptionTextNode.textAlignment = .center
|
||||||
|
self.descriptionTextNode.maximumNumberOfLines = 0
|
||||||
|
|
||||||
|
self.progressBackgroundNode = ASDisplayNode()
|
||||||
|
self.progressBackgroundNode.backgroundColor = self.presentationData.theme.actionSheet.controlAccentColor.withMultipliedAlpha(0.2)
|
||||||
|
self.progressBackgroundNode.cornerRadius = 3.0
|
||||||
|
|
||||||
|
self.progressForegroundNode = ASDisplayNode()
|
||||||
|
self.progressForegroundNode.backgroundColor = self.presentationData.theme.actionSheet.controlAccentColor
|
||||||
|
self.progressForegroundNode.cornerRadius = 3.0
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.addSubnode(self.animationNode)
|
||||||
|
self.addSubnode(self.progressTextNode)
|
||||||
|
self.addSubnode(self.descriptionTextNode)
|
||||||
|
self.addSubnode(self.progressBackgroundNode)
|
||||||
|
self.addSubnode(self.progressForegroundNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.progressDisposable.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
func setProgressSignal(_ signal: Signal<Float, NoError>) {
|
||||||
|
self.progressDisposable.set((signal
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] progress in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.setProgress(progress)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
private var progress: Float = 0.0
|
||||||
|
private func setProgress(_ progress: Float) {
|
||||||
|
self.progress = progress
|
||||||
|
|
||||||
|
if let size = self.validLayout {
|
||||||
|
self.updateLayout(size: size, transition: .animated(duration: 0.2, curve: .easeInOut))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||||
|
self.validLayout = size
|
||||||
|
|
||||||
|
let inset: CGFloat = 24.0
|
||||||
|
let progressHeight: CGFloat = 6.0
|
||||||
|
let spacing: CGFloat = 16.0
|
||||||
|
|
||||||
|
let progressFrame = CGRect(x: inset, y: size.height - inset - progressHeight, width: size.width - inset * 2.0, height: progressHeight)
|
||||||
|
self.progressBackgroundNode.frame = progressFrame
|
||||||
|
let progressForegroundFrame = CGRect(x: inset, y: size.height - inset - progressHeight, width: floorToScreenPixels(progressFrame.width * CGFloat(self.progress)), height: progressHeight)
|
||||||
|
if !self.progressForegroundNode.frame.width.isZero {
|
||||||
|
transition.updateFrame(node: self.progressForegroundNode, frame: progressForegroundFrame)
|
||||||
|
} else {
|
||||||
|
self.progressForegroundNode.frame = progressForegroundFrame
|
||||||
|
}
|
||||||
|
|
||||||
|
self.descriptionTextNode.attributedText = NSAttributedString(string: self.presentationData.strings.ClearCache_KeepOpenedDescription, font: Font.regular(15.0), textColor: self.presentationData.theme.actionSheet.secondaryTextColor)
|
||||||
|
let descriptionTextSize = self.descriptionTextNode.updateLayout(CGSize(width: size.width - inset * 3.0, height: size.height))
|
||||||
|
let descriptionTextFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - descriptionTextSize.width) / 2.0), y: progressFrame.minY - spacing - 9.0 - descriptionTextSize.height), size: descriptionTextSize)
|
||||||
|
self.descriptionTextNode.frame = descriptionTextFrame
|
||||||
|
|
||||||
|
self.progressTextNode.attributedText = NSAttributedString(string: self.presentationData.strings.ClearCache_Progress(Int(progress * 100.0)).string, font: Font.with(size: 17.0, design: .regular, weight: .bold, traits: [.monospacedNumbers]), textColor: self.presentationData.theme.actionSheet.primaryTextColor)
|
||||||
|
let progressTextSize = self.progressTextNode.updateLayout(size)
|
||||||
|
let progressTextFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - progressTextSize.width) / 2.0), y: descriptionTextFrame.minY - spacing - progressTextSize.height), size: progressTextSize)
|
||||||
|
self.progressTextNode.frame = progressTextFrame
|
||||||
|
|
||||||
|
let availableHeight = progressTextFrame.minY
|
||||||
|
let imageSide = min(160.0, availableHeight - 30.0)
|
||||||
|
let imageSize = CGSize(width: imageSide, height: imageSide)
|
||||||
|
|
||||||
|
let animationFrame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floorToScreenPixels((progressTextFrame.minY - imageSize.height) / 2.0)), size: imageSize)
|
||||||
|
self.animationNode.frame = animationFrame
|
||||||
|
self.animationNode.updateLayout(size: imageSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -246,7 +246,7 @@ public final class SlotMachineAnimationNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setOverlayColor(_ color: UIColor?, animated: Bool) {
|
public func setOverlayColor(_ color: UIColor?, replace: Bool, animated: Bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,6 +352,6 @@ class DiceAnimatedStickerNode: ASDisplayNode {
|
|||||||
self.animationNode.frame = self.bounds
|
self.animationNode.frame = self.bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setOverlayColor(_ color: UIColor?, animated: Bool) {
|
public func setOverlayColor(_ color: UIColor?, replace: Bool, animated: Bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
23
submodules/TabBarUI/BUILD
Normal file
23
submodules/TabBarUI/BUILD
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "TabBarUI",
|
||||||
|
module_name = "TabBarUI",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
"-warnings-as-errors",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||||
|
"//submodules/Display:Display",
|
||||||
|
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||||
|
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
||||||
|
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
||||||
|
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
@ -1,11 +1,12 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
|
import Display
|
||||||
|
|
||||||
public enum ToolbarActionOption {
|
private extension ToolbarTheme {
|
||||||
case left
|
convenience init(tabBarTheme theme: TabBarControllerTheme) {
|
||||||
case right
|
self.init(barBackgroundColor: theme.tabBarBackgroundColor, barSeparatorColor: theme.tabBarSeparatorColor, barTextColor: theme.tabBarTextColor, barSelectedTextColor: theme.tabBarSelectedTextColor)
|
||||||
case middle
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class TabBarControllerNode: ASDisplayNode {
|
final class TabBarControllerNode: ASDisplayNode {
|
||||||
@ -65,7 +66,7 @@ final class TabBarControllerNode: ASDisplayNode {
|
|||||||
|
|
||||||
self.tabBarNode.updateTheme(theme)
|
self.tabBarNode.updateTheme(theme)
|
||||||
self.disabledOverlayNode.backgroundColor = theme.backgroundColor.withAlphaComponent(0.5)
|
self.disabledOverlayNode.backgroundColor = theme.backgroundColor.withAlphaComponent(0.5)
|
||||||
self.toolbarNode?.updateTheme(theme)
|
self.toolbarNode?.updateTheme(ToolbarTheme(tabBarTheme: theme))
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateIsTabBarEnabled(_ value: Bool, transition: ContainedViewLayoutTransition) {
|
func updateIsTabBarEnabled(_ value: Bool, transition: ContainedViewLayoutTransition) {
|
||||||
@ -99,7 +100,7 @@ final class TabBarControllerNode: ASDisplayNode {
|
|||||||
transition.updateFrame(node: toolbarNode, frame: tabBarFrame)
|
transition.updateFrame(node: toolbarNode, frame: tabBarFrame)
|
||||||
toolbarNode.updateLayout(size: tabBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: bottomInset, toolbar: toolbar, transition: transition)
|
toolbarNode.updateLayout(size: tabBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: bottomInset, toolbar: toolbar, transition: transition)
|
||||||
} else {
|
} else {
|
||||||
let toolbarNode = ToolbarNode(theme: self.theme, left: { [weak self] in
|
let toolbarNode = ToolbarNode(theme: ToolbarTheme(tabBarTheme: self.theme), left: { [weak self] in
|
||||||
self?.toolbarActionSelected(.left)
|
self?.toolbarActionSelected(.left)
|
||||||
}, right: { [weak self] in
|
}, right: { [weak self] in
|
||||||
self?.toolbarActionSelected(.right)
|
self?.toolbarActionSelected(.right)
|
451
submodules/TabBarUI/Sources/TabBarController.swift
Normal file
451
submodules/TabBarUI/Sources/TabBarController.swift
Normal file
@ -0,0 +1,451 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import SwiftSignalKit
|
||||||
|
import Display
|
||||||
|
import TelegramPresentationData
|
||||||
|
|
||||||
|
public final class TabBarControllerTheme {
|
||||||
|
public let backgroundColor: UIColor
|
||||||
|
public let tabBarBackgroundColor: UIColor
|
||||||
|
public let tabBarSeparatorColor: UIColor
|
||||||
|
public let tabBarIconColor: UIColor
|
||||||
|
public let tabBarSelectedIconColor: UIColor
|
||||||
|
public let tabBarTextColor: UIColor
|
||||||
|
public let tabBarSelectedTextColor: UIColor
|
||||||
|
public let tabBarBadgeBackgroundColor: UIColor
|
||||||
|
public let tabBarBadgeStrokeColor: UIColor
|
||||||
|
public let tabBarBadgeTextColor: UIColor
|
||||||
|
public let tabBarExtractedIconColor: UIColor
|
||||||
|
public let tabBarExtractedTextColor: UIColor
|
||||||
|
|
||||||
|
public init(backgroundColor: UIColor, tabBarBackgroundColor: UIColor, tabBarSeparatorColor: UIColor, tabBarIconColor: UIColor, tabBarSelectedIconColor: UIColor, tabBarTextColor: UIColor, tabBarSelectedTextColor: UIColor, tabBarBadgeBackgroundColor: UIColor, tabBarBadgeStrokeColor: UIColor, tabBarBadgeTextColor: UIColor, tabBarExtractedIconColor: UIColor, tabBarExtractedTextColor: UIColor) {
|
||||||
|
self.backgroundColor = backgroundColor
|
||||||
|
self.tabBarBackgroundColor = tabBarBackgroundColor
|
||||||
|
self.tabBarSeparatorColor = tabBarSeparatorColor
|
||||||
|
self.tabBarIconColor = tabBarIconColor
|
||||||
|
self.tabBarSelectedIconColor = tabBarSelectedIconColor
|
||||||
|
self.tabBarTextColor = tabBarTextColor
|
||||||
|
self.tabBarSelectedTextColor = tabBarSelectedTextColor
|
||||||
|
self.tabBarBadgeBackgroundColor = tabBarBadgeBackgroundColor
|
||||||
|
self.tabBarBadgeStrokeColor = tabBarBadgeStrokeColor
|
||||||
|
self.tabBarBadgeTextColor = tabBarBadgeTextColor
|
||||||
|
self.tabBarExtractedIconColor = tabBarExtractedIconColor
|
||||||
|
self.tabBarExtractedTextColor = tabBarExtractedTextColor
|
||||||
|
}
|
||||||
|
|
||||||
|
public convenience init(rootControllerTheme: PresentationTheme) {
|
||||||
|
let theme = rootControllerTheme.rootController.tabBar
|
||||||
|
self.init(backgroundColor: rootControllerTheme.list.plainBackgroundColor, tabBarBackgroundColor: theme.backgroundColor, tabBarSeparatorColor: theme.separatorColor, tabBarIconColor: theme.iconColor, tabBarSelectedIconColor: theme.selectedIconColor, tabBarTextColor: theme.textColor, tabBarSelectedTextColor: theme.selectedTextColor, tabBarBadgeBackgroundColor: theme.badgeBackgroundColor, tabBarBadgeStrokeColor: theme.badgeStrokeColor, tabBarBadgeTextColor: theme.badgeTextColor, tabBarExtractedIconColor: rootControllerTheme.contextMenu.extractedContentTintColor, tabBarExtractedTextColor: rootControllerTheme.contextMenu.extractedContentTintColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class TabBarItemInfo: NSObject {
|
||||||
|
public let previewing: Bool
|
||||||
|
|
||||||
|
public init(previewing: Bool) {
|
||||||
|
self.previewing = previewing
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func isEqual(_ object: Any?) -> Bool {
|
||||||
|
if let object = object as? TabBarItemInfo {
|
||||||
|
if self.previewing != object.previewing {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: TabBarItemInfo, rhs: TabBarItemInfo) -> Bool {
|
||||||
|
if lhs.previewing != rhs.previewing {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TabBarContainedControllerPresentationUpdate {
|
||||||
|
case dismiss
|
||||||
|
case present
|
||||||
|
case progress(CGFloat)
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol TabBarContainedController {
|
||||||
|
func presentTabBarPreviewingController(sourceNodes: [ASDisplayNode])
|
||||||
|
func updateTabBarPreviewingControllerPresentation(_ update: TabBarContainedControllerPresentationUpdate)
|
||||||
|
}
|
||||||
|
|
||||||
|
open class TabBarControllerImpl: ViewController, TabBarController {
|
||||||
|
private var validLayout: ContainerViewLayout?
|
||||||
|
|
||||||
|
private var tabBarControllerNode: TabBarControllerNode {
|
||||||
|
get {
|
||||||
|
return super.displayNode as! TabBarControllerNode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open override func updateNavigationCustomData(_ data: Any?, progress: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
|
for controller in self.controllers {
|
||||||
|
controller.updateNavigationCustomData(data, progress: progress, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public private(set) var controllers: [ViewController] = []
|
||||||
|
|
||||||
|
private let _ready = Promise<Bool>()
|
||||||
|
override open var ready: Promise<Bool> {
|
||||||
|
return self._ready
|
||||||
|
}
|
||||||
|
|
||||||
|
private var _selectedIndex: Int?
|
||||||
|
public var selectedIndex: Int {
|
||||||
|
get {
|
||||||
|
if let _selectedIndex = self._selectedIndex {
|
||||||
|
return _selectedIndex
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
} set(value) {
|
||||||
|
let index = max(0, min(self.controllers.count - 1, value))
|
||||||
|
if _selectedIndex != index {
|
||||||
|
_selectedIndex = index
|
||||||
|
|
||||||
|
self.updateSelectedIndex()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var currentController: ViewController?
|
||||||
|
|
||||||
|
private let pendingControllerDisposable = MetaDisposable()
|
||||||
|
|
||||||
|
private var theme: TabBarControllerTheme
|
||||||
|
|
||||||
|
public init(navigationBarPresentationData: NavigationBarPresentationData, theme: TabBarControllerTheme) {
|
||||||
|
self.theme = theme
|
||||||
|
|
||||||
|
super.init(navigationBarPresentationData: nil)
|
||||||
|
|
||||||
|
self.scrollToTop = { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let controller = strongSelf.currentController {
|
||||||
|
controller.scrollToTop?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
required public init(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.pendingControllerDisposable.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func updateTheme(navigationBarPresentationData: NavigationBarPresentationData, theme: TabBarControllerTheme) {
|
||||||
|
if self.theme !== theme {
|
||||||
|
self.theme = theme
|
||||||
|
if self.isNodeLoaded {
|
||||||
|
self.tabBarControllerNode.updateTheme(theme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var debugTapCounter: (Double, Int) = (0.0, 0)
|
||||||
|
|
||||||
|
public func sourceNodesForController(at index: Int) -> [ASDisplayNode]? {
|
||||||
|
return self.tabBarControllerNode.tabBarNode.sourceNodesForController(at: index)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func frameForControllerTab(controller: ViewController) -> CGRect? {
|
||||||
|
if let index = self.controllers.firstIndex(of: controller) {
|
||||||
|
return self.tabBarControllerNode.tabBarNode.frameForControllerTab(at: index).flatMap { self.tabBarControllerNode.tabBarNode.view.convert($0, to: self.view) }
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func isPointInsideContentArea(point: CGPoint) -> Bool {
|
||||||
|
if point.y < self.tabBarControllerNode.tabBarNode.frame.minY {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
public func updateIsTabBarEnabled(_ value: Bool, transition: ContainedViewLayoutTransition) {
|
||||||
|
self.tabBarControllerNode.updateIsTabBarEnabled(value, transition: transition)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func updateIsTabBarHidden(_ value: Bool, transition: ContainedViewLayoutTransition) {
|
||||||
|
self.tabBarControllerNode.tabBarHidden = value
|
||||||
|
if let layout = self.validLayout {
|
||||||
|
self.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .slide))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override open func loadDisplayNode() {
|
||||||
|
self.displayNode = TabBarControllerNode(theme: self.theme, itemSelected: { [weak self] index, longTap, itemNodes in
|
||||||
|
if let strongSelf = self {
|
||||||
|
if longTap, let controller = strongSelf.controllers[index] as? TabBarContainedController {
|
||||||
|
controller.presentTabBarPreviewingController(sourceNodes: itemNodes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if strongSelf.selectedIndex == index {
|
||||||
|
let timestamp = CACurrentMediaTime()
|
||||||
|
if strongSelf.debugTapCounter.0 < timestamp - 0.4 {
|
||||||
|
strongSelf.debugTapCounter.0 = timestamp
|
||||||
|
strongSelf.debugTapCounter.1 = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if strongSelf.debugTapCounter.0 >= timestamp - 0.4 {
|
||||||
|
strongSelf.debugTapCounter.0 = timestamp
|
||||||
|
strongSelf.debugTapCounter.1 += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if strongSelf.debugTapCounter.1 >= 10 {
|
||||||
|
strongSelf.debugTapCounter.1 = 0
|
||||||
|
|
||||||
|
strongSelf.controllers[index].tabBarItemDebugTapAction?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let validLayout = strongSelf.validLayout {
|
||||||
|
var updatedLayout = validLayout
|
||||||
|
|
||||||
|
var tabBarHeight: CGFloat
|
||||||
|
var options: ContainerViewLayoutInsetOptions = []
|
||||||
|
if validLayout.metrics.widthClass == .regular {
|
||||||
|
options.insert(.input)
|
||||||
|
}
|
||||||
|
let bottomInset: CGFloat = validLayout.insets(options: options).bottom
|
||||||
|
if !validLayout.safeInsets.left.isZero {
|
||||||
|
tabBarHeight = 34.0 + bottomInset
|
||||||
|
} else {
|
||||||
|
tabBarHeight = 49.0 + bottomInset
|
||||||
|
}
|
||||||
|
updatedLayout.intrinsicInsets.bottom = tabBarHeight
|
||||||
|
|
||||||
|
strongSelf.controllers[index].containerLayoutUpdated(updatedLayout, transition: .immediate)
|
||||||
|
}
|
||||||
|
let startTime = CFAbsoluteTimeGetCurrent()
|
||||||
|
strongSelf.pendingControllerDisposable.set((strongSelf.controllers[index].ready.get()
|
||||||
|
|> deliverOnMainQueue).start(next: { _ in
|
||||||
|
if let strongSelf = self {
|
||||||
|
let readyTime = CFAbsoluteTimeGetCurrent() - startTime
|
||||||
|
if readyTime > 0.5 {
|
||||||
|
print("TabBarController: controller took \(readyTime) to become ready")
|
||||||
|
}
|
||||||
|
|
||||||
|
if strongSelf.selectedIndex == index {
|
||||||
|
if let controller = strongSelf.currentController {
|
||||||
|
if longTap {
|
||||||
|
controller.longTapWithTabBar?()
|
||||||
|
} else {
|
||||||
|
controller.scrollToTopWithTabBar?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
strongSelf.selectedIndex = index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}, contextAction: { [weak self] index, node, gesture in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if index >= 0 && index < strongSelf.controllers.count {
|
||||||
|
strongSelf.controllers[index].tabBarItemContextAction(sourceNode: node, gesture: gesture)
|
||||||
|
}
|
||||||
|
}, swipeAction: { [weak self] index, direction in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if index >= 0 && index < strongSelf.controllers.count {
|
||||||
|
strongSelf.controllers[index].tabBarItemSwipeAction(direction: direction)
|
||||||
|
}
|
||||||
|
}, toolbarActionSelected: { [weak self] action in
|
||||||
|
self?.currentController?.toolbarActionSelected(action: action)
|
||||||
|
}, disabledPressed: { [weak self] in
|
||||||
|
self?.currentController?.tabBarDisabledAction()
|
||||||
|
})
|
||||||
|
|
||||||
|
self.updateSelectedIndex()
|
||||||
|
self.displayNodeDidLoad()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func updateBackgroundAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
|
let alpha = max(0.0, min(1.0, alpha))
|
||||||
|
transition.updateAlpha(node: self.tabBarControllerNode.tabBarNode.backgroundNode, alpha: alpha, delay: 0.15)
|
||||||
|
transition.updateAlpha(node: self.tabBarControllerNode.tabBarNode.separatorNode, alpha: alpha, delay: 0.15)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateSelectedIndex() {
|
||||||
|
if !self.isNodeLoaded {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.tabBarControllerNode.tabBarNode.selectedIndex = self.selectedIndex
|
||||||
|
|
||||||
|
if let currentController = self.currentController {
|
||||||
|
currentController.willMove(toParent: nil)
|
||||||
|
self.tabBarControllerNode.currentControllerNode = nil
|
||||||
|
currentController.removeFromParent()
|
||||||
|
currentController.didMove(toParent: nil)
|
||||||
|
|
||||||
|
self.currentController = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if let _selectedIndex = self._selectedIndex, _selectedIndex < self.controllers.count {
|
||||||
|
self.currentController = self.controllers[_selectedIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
if let currentController = self.currentController {
|
||||||
|
currentController.willMove(toParent: self)
|
||||||
|
self.tabBarControllerNode.currentControllerNode = currentController.displayNode
|
||||||
|
self.addChild(currentController)
|
||||||
|
currentController.didMove(toParent: self)
|
||||||
|
|
||||||
|
currentController.displayNode.recursivelyEnsureDisplaySynchronously(true)
|
||||||
|
self.statusBar.statusBarStyle = currentController.statusBar.statusBarStyle
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
|
||||||
|
if let layout = self.validLayout {
|
||||||
|
self.containerLayoutUpdated(layout, transition: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func updateLayout(transition: ContainedViewLayoutTransition = .immediate) {
|
||||||
|
if let layout = self.validLayout {
|
||||||
|
self.containerLayoutUpdated(layout, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
|
super.containerLayoutUpdated(layout, transition: transition)
|
||||||
|
|
||||||
|
self.validLayout = layout
|
||||||
|
|
||||||
|
self.tabBarControllerNode.containerLayoutUpdated(layout, toolbar: self.currentController?.toolbar, transition: transition)
|
||||||
|
|
||||||
|
if let currentController = self.currentController {
|
||||||
|
currentController.view.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||||
|
|
||||||
|
var updatedLayout = layout
|
||||||
|
|
||||||
|
var tabBarHeight: CGFloat
|
||||||
|
var options: ContainerViewLayoutInsetOptions = []
|
||||||
|
if updatedLayout.metrics.widthClass == .regular {
|
||||||
|
options.insert(.input)
|
||||||
|
}
|
||||||
|
let bottomInset: CGFloat = updatedLayout.insets(options: options).bottom
|
||||||
|
if !updatedLayout.safeInsets.left.isZero {
|
||||||
|
tabBarHeight = 34.0 + bottomInset
|
||||||
|
} else {
|
||||||
|
tabBarHeight = 49.0 + bottomInset
|
||||||
|
}
|
||||||
|
updatedLayout.intrinsicInsets.bottom = tabBarHeight
|
||||||
|
|
||||||
|
currentController.containerLayoutUpdated(updatedLayout, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override open func navigationStackConfigurationUpdated(next: [ViewController]) {
|
||||||
|
super.navigationStackConfigurationUpdated(next: next)
|
||||||
|
for controller in self.controllers {
|
||||||
|
controller.navigationStackConfigurationUpdated(next: next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override open func viewWillDisappear(_ animated: Bool) {
|
||||||
|
if let currentController = self.currentController {
|
||||||
|
currentController.viewWillDisappear(animated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override open func viewWillAppear(_ animated: Bool) {
|
||||||
|
if let currentController = self.currentController {
|
||||||
|
currentController.viewWillAppear(animated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override open func viewDidAppear(_ animated: Bool) {
|
||||||
|
if let currentController = self.currentController {
|
||||||
|
currentController.viewDidAppear(animated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override open func viewDidDisappear(_ animated: Bool) {
|
||||||
|
if let currentController = self.currentController {
|
||||||
|
currentController.viewDidDisappear(animated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func setControllers(_ controllers: [ViewController], selectedIndex: Int?) {
|
||||||
|
var updatedSelectedIndex: Int? = selectedIndex
|
||||||
|
if updatedSelectedIndex == nil, let selectedIndex = self._selectedIndex, selectedIndex < self.controllers.count {
|
||||||
|
if let index = controllers.firstIndex(where: { $0 === self.controllers[selectedIndex] }) {
|
||||||
|
updatedSelectedIndex = index
|
||||||
|
} else {
|
||||||
|
updatedSelectedIndex = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.controllers = controllers
|
||||||
|
self.tabBarControllerNode.tabBarNode.tabBarItems = self.controllers.map({ TabBarNodeItem(item: $0.tabBarItem, contextActionType: $0.tabBarItemContextActionType) })
|
||||||
|
|
||||||
|
let signals = combineLatest(self.controllers.map({ $0.tabBarItem }).map { tabBarItem -> Signal<Bool, NoError> in
|
||||||
|
if let tabBarItem = tabBarItem, tabBarItem.image == nil {
|
||||||
|
return Signal { [weak tabBarItem] subscriber in
|
||||||
|
let index = tabBarItem?.addSetImageListener({ image in
|
||||||
|
if image != nil {
|
||||||
|
subscriber.putNext(true)
|
||||||
|
subscriber.putCompletion()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return ActionDisposable {
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
if let index = index {
|
||||||
|
tabBarItem?.removeSetImageListener(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> runOn(.mainQueue())
|
||||||
|
} else {
|
||||||
|
return .single(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|> map { items -> Bool in
|
||||||
|
for item in items {
|
||||||
|
if !item {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|> filter { $0 }
|
||||||
|
|> take(1)
|
||||||
|
|
||||||
|
let allReady = signals
|
||||||
|
|> deliverOnMainQueue
|
||||||
|
|> mapToSignal { _ -> Signal<Bool, NoError> in
|
||||||
|
// wait for tab bar items to be applied
|
||||||
|
return .single(true)
|
||||||
|
|> delay(0.0, queue: Queue.mainQueue())
|
||||||
|
}
|
||||||
|
|
||||||
|
self._ready.set(allReady)
|
||||||
|
|
||||||
|
if let updatedSelectedIndex = updatedSelectedIndex {
|
||||||
|
self.selectedIndex = updatedSelectedIndex
|
||||||
|
self.updateSelectedIndex()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,10 @@ import Foundation
|
|||||||
import UIKit
|
import UIKit
|
||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
|
import Display
|
||||||
import UIKitRuntimeUtils
|
import UIKitRuntimeUtils
|
||||||
|
import AnimatedStickerNode
|
||||||
|
import TelegramAnimatedStickerNode
|
||||||
|
|
||||||
private let separatorHeight: CGFloat = 1.0 / UIScreen.main.scale
|
private let separatorHeight: CGFloat = 1.0 / UIScreen.main.scale
|
||||||
private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: UIColor, tintColor: UIColor, horizontal: Bool, imageMode: Bool, centered: Bool = false) -> (UIImage, CGFloat) {
|
private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: UIColor, tintColor: UIColor, horizontal: Bool, imageMode: Bool, centered: Bool = false) -> (UIImage, CGFloat) {
|
||||||
@ -41,35 +44,24 @@ private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor:
|
|||||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
if let image = image, imageMode {
|
if let image = image, imageMode {
|
||||||
|
let imageRect: CGRect
|
||||||
if horizontal {
|
if horizontal {
|
||||||
let imageRect = CGRect(origin: CGPoint(x: 0.0, y: floor((size.height - imageSize.height) / 2.0)), size: imageSize)
|
imageRect = CGRect(origin: CGPoint(x: 0.0, y: floor((size.height - imageSize.height) / 2.0)), size: imageSize)
|
||||||
context.saveGState()
|
|
||||||
context.translateBy(x: imageRect.midX, y: imageRect.midY)
|
|
||||||
context.scaleBy(x: 1.0, y: -1.0)
|
|
||||||
context.translateBy(x: -imageRect.midX, y: -imageRect.midY)
|
|
||||||
if image.renderingMode == .alwaysOriginal {
|
|
||||||
context.draw(image.cgImage!, in: imageRect)
|
|
||||||
} else {
|
|
||||||
context.clip(to: imageRect, mask: image.cgImage!)
|
|
||||||
context.setFillColor(tintColor.cgColor)
|
|
||||||
context.fill(imageRect)
|
|
||||||
}
|
|
||||||
context.restoreGState()
|
|
||||||
} else {
|
} else {
|
||||||
let imageRect = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - imageSize.width) / 2.0), y: centered ? floor((size.height - imageSize.height) / 2.0) : 0.0), size: imageSize)
|
imageRect = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - imageSize.width) / 2.0), y: centered ? floor((size.height - imageSize.height) / 2.0) : 0.0), size: imageSize)
|
||||||
context.saveGState()
|
|
||||||
context.translateBy(x: imageRect.midX, y: imageRect.midY)
|
|
||||||
context.scaleBy(x: 1.0, y: -1.0)
|
|
||||||
context.translateBy(x: -imageRect.midX, y: -imageRect.midY)
|
|
||||||
if image.renderingMode == .alwaysOriginal {
|
|
||||||
context.draw(image.cgImage!, in: imageRect)
|
|
||||||
} else {
|
|
||||||
context.clip(to: imageRect, mask: image.cgImage!)
|
|
||||||
context.setFillColor(tintColor.cgColor)
|
|
||||||
context.fill(imageRect)
|
|
||||||
}
|
|
||||||
context.restoreGState()
|
|
||||||
}
|
}
|
||||||
|
context.saveGState()
|
||||||
|
context.translateBy(x: imageRect.midX, y: imageRect.midY)
|
||||||
|
context.scaleBy(x: 1.0, y: -1.0)
|
||||||
|
context.translateBy(x: -imageRect.midX, y: -imageRect.midY)
|
||||||
|
if image.renderingMode == .alwaysOriginal {
|
||||||
|
context.draw(image.cgImage!, in: imageRect)
|
||||||
|
} else {
|
||||||
|
context.clip(to: imageRect, mask: image.cgImage!)
|
||||||
|
context.setFillColor(tintColor.cgColor)
|
||||||
|
context.fill(imageRect)
|
||||||
|
}
|
||||||
|
context.restoreGState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,15 +81,12 @@ private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor:
|
|||||||
|
|
||||||
private let badgeFont = Font.regular(13.0)
|
private let badgeFont = Font.regular(13.0)
|
||||||
|
|
||||||
public enum TabBarItemSwipeDirection {
|
|
||||||
case left
|
|
||||||
case right
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class TabBarItemNode: ASDisplayNode {
|
private final class TabBarItemNode: ASDisplayNode {
|
||||||
let extractedContainerNode: ContextExtractedContentContainingNode
|
let extractedContainerNode: ContextExtractedContentContainingNode
|
||||||
let containerNode: ContextControllerSourceNode
|
let containerNode: ContextControllerSourceNode
|
||||||
let imageNode: ASImageNode
|
let imageNode: ASImageNode
|
||||||
|
let animationContainerNode: ASDisplayNode
|
||||||
|
let animationNode: AnimatedStickerNode
|
||||||
let textImageNode: ASImageNode
|
let textImageNode: ASImageNode
|
||||||
let contextImageNode: ASImageNode
|
let contextImageNode: ASImageNode
|
||||||
let contextTextImageNode: ASImageNode
|
let contextTextImageNode: ASImageNode
|
||||||
@ -117,6 +106,12 @@ private final class TabBarItemNode: ASDisplayNode {
|
|||||||
self.imageNode.displayWithoutProcessing = true
|
self.imageNode.displayWithoutProcessing = true
|
||||||
self.imageNode.displaysAsynchronously = false
|
self.imageNode.displaysAsynchronously = false
|
||||||
self.imageNode.isAccessibilityElement = false
|
self.imageNode.isAccessibilityElement = false
|
||||||
|
|
||||||
|
self.animationContainerNode = ASDisplayNode()
|
||||||
|
|
||||||
|
self.animationNode = AnimatedStickerNode()
|
||||||
|
self.animationNode.automaticallyLoadFirstFrame = true
|
||||||
|
|
||||||
self.textImageNode = ASImageNode()
|
self.textImageNode = ASImageNode()
|
||||||
self.textImageNode.isUserInteractionEnabled = false
|
self.textImageNode.isUserInteractionEnabled = false
|
||||||
self.textImageNode.displayWithoutProcessing = true
|
self.textImageNode.displayWithoutProcessing = true
|
||||||
@ -142,6 +137,8 @@ private final class TabBarItemNode: ASDisplayNode {
|
|||||||
|
|
||||||
self.extractedContainerNode.contentNode.addSubnode(self.textImageNode)
|
self.extractedContainerNode.contentNode.addSubnode(self.textImageNode)
|
||||||
self.extractedContainerNode.contentNode.addSubnode(self.imageNode)
|
self.extractedContainerNode.contentNode.addSubnode(self.imageNode)
|
||||||
|
self.extractedContainerNode.contentNode.addSubnode(self.animationContainerNode)
|
||||||
|
self.animationContainerNode.addSubnode(self.animationNode)
|
||||||
self.extractedContainerNode.contentNode.addSubnode(self.contextTextImageNode)
|
self.extractedContainerNode.contentNode.addSubnode(self.contextTextImageNode)
|
||||||
self.extractedContainerNode.contentNode.addSubnode(self.contextImageNode)
|
self.extractedContainerNode.contentNode.addSubnode(self.contextImageNode)
|
||||||
self.containerNode.addSubnode(self.extractedContainerNode)
|
self.containerNode.addSubnode(self.extractedContainerNode)
|
||||||
@ -451,7 +448,26 @@ class TabBarNode: ASDisplayNode {
|
|||||||
})
|
})
|
||||||
if let selectedIndex = self.selectedIndex, selectedIndex == i {
|
if let selectedIndex = self.selectedIndex, selectedIndex == i {
|
||||||
let (textImage, contentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
|
let (textImage, contentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
|
||||||
let (image, imageContentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
|
let (image, imageContentWidth): (UIImage, CGFloat)
|
||||||
|
|
||||||
|
if let _ = item.item.animationName {
|
||||||
|
(image, imageContentWidth) = (UIImage(), 0.0)
|
||||||
|
|
||||||
|
node.animationNode.isHidden = false
|
||||||
|
let animationSize: Int = Int(51.0 * UIScreen.main.scale)
|
||||||
|
node.animationNode.visibility = true
|
||||||
|
if !node.isSelected {
|
||||||
|
node.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: item.item.animationName ?? ""), width: animationSize, height: animationSize, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
|
||||||
|
}
|
||||||
|
node.animationNode.setOverlayColor(self.theme.tabBarSelectedIconColor, replace: true, animated: false)
|
||||||
|
node.animationNode.updateLayout(size: CGSize(width: 51.0, height: 51.0))
|
||||||
|
} else {
|
||||||
|
(image, imageContentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
|
||||||
|
|
||||||
|
node.animationNode.isHidden = true
|
||||||
|
node.animationNode.visibility = false
|
||||||
|
}
|
||||||
|
|
||||||
let (contextTextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
|
let (contextTextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
|
||||||
let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
|
let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
|
||||||
node.textImageNode.image = textImage
|
node.textImageNode.image = textImage
|
||||||
@ -466,6 +482,10 @@ class TabBarNode: ASDisplayNode {
|
|||||||
let (image, imageContentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
|
let (image, imageContentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
|
||||||
let (contextTextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
|
let (contextTextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
|
||||||
let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
|
let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
|
||||||
|
|
||||||
|
node.animationNode.isHidden = true
|
||||||
|
node.animationNode.visibility = false
|
||||||
|
|
||||||
node.textImageNode.image = textImage
|
node.textImageNode.image = textImage
|
||||||
node.accessibilityLabel = item.item.title
|
node.accessibilityLabel = item.item.title
|
||||||
node.imageNode.image = image
|
node.imageNode.image = image
|
||||||
@ -496,7 +516,25 @@ class TabBarNode: ASDisplayNode {
|
|||||||
let previousTextImageSize = node.textImageNode.image?.size ?? CGSize()
|
let previousTextImageSize = node.textImageNode.image?.size ?? CGSize()
|
||||||
if let selectedIndex = self.selectedIndex, selectedIndex == index {
|
if let selectedIndex = self.selectedIndex, selectedIndex == index {
|
||||||
let (textImage, contentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
|
let (textImage, contentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
|
||||||
let (image, imageContentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
|
let (image, imageContentWidth): (UIImage, CGFloat)
|
||||||
|
if let _ = item.item.animationName {
|
||||||
|
(image, imageContentWidth) = (UIImage(), 0.0)
|
||||||
|
|
||||||
|
node.animationNode.isHidden = false
|
||||||
|
let animationSize: Int = Int(51.0 * UIScreen.main.scale)
|
||||||
|
node.animationNode.visibility = true
|
||||||
|
if !node.isSelected {
|
||||||
|
node.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: item.item.animationName ?? ""), width: animationSize, height: animationSize, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
|
||||||
|
}
|
||||||
|
node.animationNode.setOverlayColor(self.theme.tabBarSelectedIconColor, replace: true, animated: false)
|
||||||
|
node.animationNode.updateLayout(size: CGSize(width: 51.0, height: 51.0))
|
||||||
|
} else {
|
||||||
|
(image, imageContentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
|
||||||
|
|
||||||
|
node.animationNode.isHidden = true
|
||||||
|
node.animationNode.visibility = false
|
||||||
|
}
|
||||||
|
|
||||||
let (contextTextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
|
let (contextTextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
|
||||||
let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
|
let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
|
||||||
node.textImageNode.image = textImage
|
node.textImageNode.image = textImage
|
||||||
@ -511,6 +549,11 @@ class TabBarNode: ASDisplayNode {
|
|||||||
let (image, imageContentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
|
let (image, imageContentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
|
||||||
let (contextTextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
|
let (contextTextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
|
||||||
let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
|
let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
|
||||||
|
|
||||||
|
node.animationNode.stop()
|
||||||
|
node.animationNode.isHidden = true
|
||||||
|
node.animationNode.visibility = false
|
||||||
|
|
||||||
node.textImageNode.image = textImage
|
node.textImageNode.image = textImage
|
||||||
node.accessibilityLabel = item.item.title
|
node.accessibilityLabel = item.item.title
|
||||||
node.imageNode.image = image
|
node.imageNode.image = image
|
||||||
@ -614,6 +657,14 @@ class TabBarNode: ASDisplayNode {
|
|||||||
node.contextImageNode.frame = CGRect(origin: CGPoint(), size: nodeFrame.size)
|
node.contextImageNode.frame = CGRect(origin: CGPoint(), size: nodeFrame.size)
|
||||||
node.contextTextImageNode.frame = CGRect(origin: CGPoint(), size: nodeFrame.size)
|
node.contextTextImageNode.frame = CGRect(origin: CGPoint(), size: nodeFrame.size)
|
||||||
|
|
||||||
|
let scaleFactor: CGFloat = horizontal ? 0.8 : 1.0
|
||||||
|
node.animationContainerNode.subnodeTransform = CATransform3DMakeScale(scaleFactor, scaleFactor, 1.0)
|
||||||
|
if horizontal {
|
||||||
|
node.animationNode.frame = CGRect(origin: CGPoint(x: -10.0 - UIScreenPixel, y: -4.0 - UIScreenPixel), size: CGSize(width: 51.0, height: 51.0))
|
||||||
|
} else {
|
||||||
|
node.animationNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((nodeSize.width - 51.0) / 2.0), y: -10.0 - UIScreenPixel), size: CGSize(width: 51.0, height: 51.0))
|
||||||
|
}
|
||||||
|
|
||||||
if container.badgeValue != container.appliedBadgeValue {
|
if container.badgeValue != container.appliedBadgeValue {
|
||||||
container.appliedBadgeValue = container.badgeValue
|
container.appliedBadgeValue = container.badgeValue
|
||||||
if let badgeValue = container.badgeValue, !badgeValue.isEmpty {
|
if let badgeValue = container.badgeValue, !badgeValue.isEmpty {
|
||||||
@ -633,14 +684,14 @@ class TabBarNode: ASDisplayNode {
|
|||||||
let backgroundSize = CGSize(width: hasSingleLetterValue ? 18.0 : max(18.0, badgeSize.width + 10.0 + 1.0), height: 18.0)
|
let backgroundSize = CGSize(width: hasSingleLetterValue ? 18.0 : max(18.0, badgeSize.width + 10.0 + 1.0), height: 18.0)
|
||||||
let backgroundFrame: CGRect
|
let backgroundFrame: CGRect
|
||||||
if horizontal {
|
if horizontal {
|
||||||
backgroundFrame = CGRect(origin: CGPoint(x: 15.0, y: 0.0), size: backgroundSize)
|
backgroundFrame = CGRect(origin: CGPoint(x: 13.0, y: 0.0), size: backgroundSize)
|
||||||
} else {
|
} else {
|
||||||
let contentWidth: CGFloat = 25.0
|
let contentWidth: CGFloat = 25.0
|
||||||
backgroundFrame = CGRect(origin: CGPoint(x: floor(node.frame.width / 2.0) + contentWidth - backgroundSize.width - 5.0, y: self.centered ? 6.0 : -1.0), size: backgroundSize)
|
backgroundFrame = CGRect(origin: CGPoint(x: floor(node.frame.width / 2.0) + contentWidth - backgroundSize.width - 5.0, y: self.centered ? 6.0 : -1.0), size: backgroundSize)
|
||||||
}
|
}
|
||||||
transition.updateFrame(node: container.badgeContainerNode, frame: backgroundFrame)
|
transition.updateFrame(node: container.badgeContainerNode, frame: backgroundFrame)
|
||||||
container.badgeBackgroundNode.frame = CGRect(origin: CGPoint(), size: backgroundFrame.size)
|
container.badgeBackgroundNode.frame = CGRect(origin: CGPoint(), size: backgroundFrame.size)
|
||||||
let scaleFactor: CGFloat = horizontal ? 0.8 : 1.0
|
|
||||||
container.badgeContainerNode.subnodeTransform = CATransform3DMakeScale(scaleFactor, scaleFactor, 1.0)
|
container.badgeContainerNode.subnodeTransform = CATransform3DMakeScale(scaleFactor, scaleFactor, 1.0)
|
||||||
|
|
||||||
container.badgeTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundFrame.size.width - badgeSize.width) / 2.0), y: 1.0), size: badgeSize)
|
container.badgeTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundFrame.size.width - badgeSize.width) / 2.0), y: 1.0), size: badgeSize)
|
||||||
@ -673,7 +724,11 @@ class TabBarNode: ASDisplayNode {
|
|||||||
|
|
||||||
if let closestNode = closestNode {
|
if let closestNode = closestNode {
|
||||||
let container = self.tabBarNodeContainers[closestNode.0]
|
let container = self.tabBarNodeContainers[closestNode.0]
|
||||||
|
let previousSelectedIndex = self.selectedIndex
|
||||||
self.itemSelected(closestNode.0, longTap, [container.imageNode.imageNode, container.imageNode.textImageNode, container.badgeContainerNode])
|
self.itemSelected(closestNode.0, longTap, [container.imageNode.imageNode, container.imageNode.textImageNode, container.badgeContainerNode])
|
||||||
|
if previousSelectedIndex != closestNode.0 {
|
||||||
|
container.imageNode.animationNode.play()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -290,6 +290,6 @@ func _internal_collectCacheUsageStats(account: Account, peerId: PeerId? = nil, a
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func _internal_clearCachedMediaResources(account: Account, mediaResourceIds: Set<MediaResourceId>) -> Signal<Void, NoError> {
|
func _internal_clearCachedMediaResources(account: Account, mediaResourceIds: Set<MediaResourceId>) -> Signal<Float, NoError> {
|
||||||
return account.postbox.mediaBox.removeCachedResources(mediaResourceIds)
|
return account.postbox.mediaBox.removeCachedResources(mediaResourceIds)
|
||||||
}
|
}
|
||||||
|
@ -142,7 +142,7 @@ public extension TelegramEngine {
|
|||||||
return _internal_collectCacheUsageStats(account: self.account, peerId: peerId, additionalCachePaths: additionalCachePaths, logFilesPath: logFilesPath)
|
return _internal_collectCacheUsageStats(account: self.account, peerId: peerId, additionalCachePaths: additionalCachePaths, logFilesPath: logFilesPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func clearCachedMediaResources(mediaResourceIds: Set<MediaResourceId>) -> Signal<Void, NoError> {
|
public func clearCachedMediaResources(mediaResourceIds: Set<MediaResourceId>) -> Signal<Float, NoError> {
|
||||||
return _internal_clearCachedMediaResources(account: self.account, mediaResourceIds: mediaResourceIds)
|
return _internal_clearCachedMediaResources(account: self.account, mediaResourceIds: mediaResourceIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ func mediaBubbleCornerImage(incoming: Bool, radius: CGFloat, inset: CGFloat) ->
|
|||||||
return formContext.generateImage()!
|
return formContext.generateImage()!
|
||||||
}
|
}
|
||||||
|
|
||||||
public func messageBubbleImage(maxCornerRadius: CGFloat, minCornerRadius: CGFloat, incoming: Bool, fillColor: UIColor, strokeColor: UIColor, neighbors: MessageBubbleImageNeighbors, theme: PresentationThemeChat, wallpaper: TelegramWallpaper, knockout knockoutValue: Bool, mask: Bool = false, extendedEdges: Bool = false, onlyOutline: Bool = false, onlyShadow: Bool = false) -> UIImage {
|
public func messageBubbleImage(maxCornerRadius: CGFloat, minCornerRadius: CGFloat, incoming: Bool, fillColor: UIColor, strokeColor: UIColor, neighbors: MessageBubbleImageNeighbors, theme: PresentationThemeChat, wallpaper: TelegramWallpaper, knockout knockoutValue: Bool, mask: Bool = false, extendedEdges: Bool = false, onlyOutline: Bool = false, onlyShadow: Bool = false, alwaysFillColor: Bool = false) -> UIImage {
|
||||||
let topLeftRadius: CGFloat
|
let topLeftRadius: CGFloat
|
||||||
let topRightRadius: CGFloat
|
let topRightRadius: CGFloat
|
||||||
let bottomLeftRadius: CGFloat
|
let bottomLeftRadius: CGFloat
|
||||||
@ -346,6 +346,12 @@ public func messageBubbleImage(maxCornerRadius: CGFloat, minCornerRadius: CGFloa
|
|||||||
if !onlyOutline {
|
if !onlyOutline {
|
||||||
context.clip(to: CGRect(origin: CGPoint(), size: rawSize), mask: formImage.cgImage!)
|
context.clip(to: CGRect(origin: CGPoint(), size: rawSize), mask: formImage.cgImage!)
|
||||||
context.fill(CGRect(origin: CGPoint(), size: rawSize))
|
context.fill(CGRect(origin: CGPoint(), size: rawSize))
|
||||||
|
|
||||||
|
if alwaysFillColor && drawWithClearColor {
|
||||||
|
context.setBlendMode(.normal)
|
||||||
|
context.setFillColor(fillColor.cgColor)
|
||||||
|
context.fill(CGRect(origin: CGPoint(), size: rawSize))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
context.setFillColor(strokeColor.cgColor)
|
context.setFillColor(strokeColor.cgColor)
|
||||||
context.clip(to: CGRect(origin: CGPoint(), size: rawSize), mask: outlineImage.cgImage!)
|
context.clip(to: CGRect(origin: CGPoint(), size: rawSize), mask: outlineImage.cgImage!)
|
||||||
|
@ -38,10 +38,10 @@ public extension PresentationFontSize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension TabBarControllerTheme {
|
public extension ToolbarTheme {
|
||||||
convenience init(rootControllerTheme: PresentationTheme) {
|
convenience init(rootControllerTheme: PresentationTheme) {
|
||||||
let theme = rootControllerTheme.rootController.tabBar
|
let theme = rootControllerTheme.rootController.tabBar
|
||||||
self.init(backgroundColor: rootControllerTheme.list.plainBackgroundColor, tabBarBackgroundColor: theme.backgroundColor, tabBarSeparatorColor: theme.separatorColor, tabBarIconColor: theme.iconColor, tabBarSelectedIconColor: theme.selectedIconColor, tabBarTextColor: theme.textColor, tabBarSelectedTextColor: theme.selectedTextColor, tabBarBadgeBackgroundColor: theme.badgeBackgroundColor, tabBarBadgeStrokeColor: theme.badgeStrokeColor, tabBarBadgeTextColor: theme.badgeTextColor, tabBarExtractedIconColor: rootControllerTheme.contextMenu.extractedContentTintColor, tabBarExtractedTextColor: rootControllerTheme.contextMenu.extractedContentTintColor)
|
self.init(barBackgroundColor: theme.backgroundColor, barSeparatorColor: theme.separatorColor, barTextColor: theme.textColor, barSelectedTextColor: theme.selectedTextColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,7 +163,7 @@ public func customizeDefaultDayTheme(theme: PresentationTheme, editing: Bool, ti
|
|||||||
outgoingBubbleStrokeColor = .clear
|
outgoingBubbleStrokeColor = .clear
|
||||||
}
|
}
|
||||||
|
|
||||||
outgoingBubbleHighlightedFill = outgoingBubbleFillColors?.first?.withMultiplied(hue: 1.054, saturation: 1.589, brightness: 0.96)
|
outgoingBubbleHighlightedFill = outgoingBubbleFillColors?.first?.withMultiplied(hue: 1.00, saturation: 1.589, brightness: 0.96)
|
||||||
|
|
||||||
let lightnessColor = UIColor.average(of: bubbleColors.map(UIColor.init(rgb:)))
|
let lightnessColor = UIColor.average(of: bubbleColors.map(UIColor.init(rgb:)))
|
||||||
if lightnessColor.lightness > 0.705 {
|
if lightnessColor.lightness > 0.705 {
|
||||||
|
@ -250,6 +250,13 @@ public final class PrincipalThemeEssentialGraphics {
|
|||||||
let incomingKnockout = self.incomingBubbleGradientImage != nil
|
let incomingKnockout = self.incomingBubbleGradientImage != nil
|
||||||
let outgoingKnockout = self.outgoingBubbleGradientImage != nil
|
let outgoingKnockout = self.outgoingBubbleGradientImage != nil
|
||||||
|
|
||||||
|
let highlightKnockout: Bool
|
||||||
|
if case .color = wallpaper {
|
||||||
|
highlightKnockout = true
|
||||||
|
} else {
|
||||||
|
highlightKnockout = false
|
||||||
|
}
|
||||||
|
|
||||||
let serviceColor = serviceMessageColorComponents(chatTheme: theme, wallpaper: wallpaper)
|
let serviceColor = serviceMessageColorComponents(chatTheme: theme, wallpaper: wallpaper)
|
||||||
|
|
||||||
let maxCornerRadius = bubbleCorners.mainRadius
|
let maxCornerRadius = bubbleCorners.mainRadius
|
||||||
@ -376,27 +383,27 @@ public final class PrincipalThemeEssentialGraphics {
|
|||||||
self.chatMessageBackgroundIncomingOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true)
|
self.chatMessageBackgroundIncomingOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true)
|
||||||
self.chatMessageBackgroundIncomingExtractedOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true)
|
self.chatMessageBackgroundIncomingExtractedOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true)
|
||||||
self.chatMessageBackgroundIncomingShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true)
|
self.chatMessageBackgroundIncomingShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true)
|
||||||
self.chatMessageBackgroundIncomingHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: false, extendedEdges: true)
|
self.chatMessageBackgroundIncomingHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true)
|
||||||
self.chatMessageBackgroundIncomingMergedTopMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .top(side: false), theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
|
self.chatMessageBackgroundIncomingMergedTopMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .top(side: false), theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
|
||||||
self.chatMessageBackgroundIncomingMergedTopImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true)
|
self.chatMessageBackgroundIncomingMergedTopImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true)
|
||||||
self.chatMessageBackgroundIncomingMergedTopOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true)
|
self.chatMessageBackgroundIncomingMergedTopOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true)
|
||||||
self.chatMessageBackgroundIncomingMergedTopShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true)
|
self.chatMessageBackgroundIncomingMergedTopShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true)
|
||||||
self.chatMessageBackgroundIncomingMergedTopHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: false, extendedEdges: true)
|
self.chatMessageBackgroundIncomingMergedTopHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true)
|
||||||
self.chatMessageBackgroundIncomingMergedTopSideMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .top(side: true), theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
|
self.chatMessageBackgroundIncomingMergedTopSideMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .top(side: true), theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
|
||||||
self.chatMessageBackgroundIncomingMergedTopSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true)
|
self.chatMessageBackgroundIncomingMergedTopSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true)
|
||||||
self.chatMessageBackgroundIncomingMergedTopSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true)
|
self.chatMessageBackgroundIncomingMergedTopSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true)
|
||||||
self.chatMessageBackgroundIncomingMergedTopSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true)
|
self.chatMessageBackgroundIncomingMergedTopSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true)
|
||||||
self.chatMessageBackgroundIncomingMergedTopSideHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: false, extendedEdges: true)
|
self.chatMessageBackgroundIncomingMergedTopSideHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true)
|
||||||
self.chatMessageBackgroundIncomingMergedBottomMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .bottom, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
|
self.chatMessageBackgroundIncomingMergedBottomMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .bottom, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
|
||||||
self.chatMessageBackgroundIncomingMergedBottomImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true)
|
self.chatMessageBackgroundIncomingMergedBottomImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true)
|
||||||
self.chatMessageBackgroundIncomingMergedBottomOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true)
|
self.chatMessageBackgroundIncomingMergedBottomOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true)
|
||||||
self.chatMessageBackgroundIncomingMergedBottomShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true)
|
self.chatMessageBackgroundIncomingMergedBottomShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true)
|
||||||
self.chatMessageBackgroundIncomingMergedBottomHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: false, extendedEdges: true)
|
self.chatMessageBackgroundIncomingMergedBottomHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true)
|
||||||
self.chatMessageBackgroundIncomingMergedBothMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .both, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
|
self.chatMessageBackgroundIncomingMergedBothMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .both, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
|
||||||
self.chatMessageBackgroundIncomingMergedBothImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true)
|
self.chatMessageBackgroundIncomingMergedBothImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true)
|
||||||
self.chatMessageBackgroundIncomingMergedBothOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true)
|
self.chatMessageBackgroundIncomingMergedBothOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true)
|
||||||
self.chatMessageBackgroundIncomingMergedBothShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true)
|
self.chatMessageBackgroundIncomingMergedBothShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true)
|
||||||
self.chatMessageBackgroundIncomingMergedBothHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: false, extendedEdges: true)
|
self.chatMessageBackgroundIncomingMergedBothHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true)
|
||||||
|
|
||||||
self.chatMessageBackgroundOutgoingMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .none, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
|
self.chatMessageBackgroundOutgoingMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .none, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
|
||||||
self.chatMessageBackgroundOutgoingExtractedMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .extracted, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
|
self.chatMessageBackgroundOutgoingExtractedMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .extracted, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
|
||||||
@ -405,27 +412,27 @@ public final class PrincipalThemeEssentialGraphics {
|
|||||||
self.chatMessageBackgroundOutgoingOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
|
self.chatMessageBackgroundOutgoingOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
|
||||||
self.chatMessageBackgroundOutgoingExtractedOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
|
self.chatMessageBackgroundOutgoingExtractedOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
|
||||||
self.chatMessageBackgroundOutgoingShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true)
|
self.chatMessageBackgroundOutgoingShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true)
|
||||||
self.chatMessageBackgroundOutgoingHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: false, extendedEdges: true)
|
self.chatMessageBackgroundOutgoingHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true)
|
||||||
self.chatMessageBackgroundOutgoingMergedTopMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .top(side: false), theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
|
self.chatMessageBackgroundOutgoingMergedTopMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .top(side: false), theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
|
||||||
self.chatMessageBackgroundOutgoingMergedTopImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
|
self.chatMessageBackgroundOutgoingMergedTopImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
|
||||||
self.chatMessageBackgroundOutgoingMergedTopOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
|
self.chatMessageBackgroundOutgoingMergedTopOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
|
||||||
self.chatMessageBackgroundOutgoingMergedTopShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true)
|
self.chatMessageBackgroundOutgoingMergedTopShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true)
|
||||||
self.chatMessageBackgroundOutgoingMergedTopHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: false, extendedEdges: true)
|
self.chatMessageBackgroundOutgoingMergedTopHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true)
|
||||||
self.chatMessageBackgroundOutgoingMergedTopSideMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .top(side: true), theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
|
self.chatMessageBackgroundOutgoingMergedTopSideMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .top(side: true), theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
|
||||||
self.chatMessageBackgroundOutgoingMergedTopSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
|
self.chatMessageBackgroundOutgoingMergedTopSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
|
||||||
self.chatMessageBackgroundOutgoingMergedTopSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
|
self.chatMessageBackgroundOutgoingMergedTopSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
|
||||||
self.chatMessageBackgroundOutgoingMergedTopSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true)
|
self.chatMessageBackgroundOutgoingMergedTopSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true)
|
||||||
self.chatMessageBackgroundOutgoingMergedTopSideHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: false, extendedEdges: true)
|
self.chatMessageBackgroundOutgoingMergedTopSideHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true)
|
||||||
self.chatMessageBackgroundOutgoingMergedBottomMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .bottom, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
|
self.chatMessageBackgroundOutgoingMergedBottomMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .bottom, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
|
||||||
self.chatMessageBackgroundOutgoingMergedBottomImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
|
self.chatMessageBackgroundOutgoingMergedBottomImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
|
||||||
self.chatMessageBackgroundOutgoingMergedBottomOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
|
self.chatMessageBackgroundOutgoingMergedBottomOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
|
||||||
self.chatMessageBackgroundOutgoingMergedBottomShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true)
|
self.chatMessageBackgroundOutgoingMergedBottomShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true)
|
||||||
self.chatMessageBackgroundOutgoingMergedBottomHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: false, extendedEdges: true)
|
self.chatMessageBackgroundOutgoingMergedBottomHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true)
|
||||||
self.chatMessageBackgroundOutgoingMergedBothMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .both, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
|
self.chatMessageBackgroundOutgoingMergedBothMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .both, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
|
||||||
self.chatMessageBackgroundOutgoingMergedBothImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
|
self.chatMessageBackgroundOutgoingMergedBothImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
|
||||||
self.chatMessageBackgroundOutgoingMergedBothOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
|
self.chatMessageBackgroundOutgoingMergedBothOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
|
||||||
self.chatMessageBackgroundOutgoingMergedBothShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true)
|
self.chatMessageBackgroundOutgoingMergedBothShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true)
|
||||||
self.chatMessageBackgroundOutgoingMergedBothHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: false, extendedEdges: true)
|
self.chatMessageBackgroundOutgoingMergedBothHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true)
|
||||||
|
|
||||||
self.chatMessageBackgroundIncomingMergedSideMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .side, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
|
self.chatMessageBackgroundIncomingMergedSideMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .side, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
|
||||||
self.chatMessageBackgroundIncomingMergedSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
|
self.chatMessageBackgroundIncomingMergedSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
|
||||||
@ -435,8 +442,8 @@ public final class PrincipalThemeEssentialGraphics {
|
|||||||
self.chatMessageBackgroundOutgoingMergedSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
|
self.chatMessageBackgroundOutgoingMergedSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true)
|
||||||
self.chatMessageBackgroundOutgoingMergedSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
|
self.chatMessageBackgroundOutgoingMergedSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true)
|
||||||
self.chatMessageBackgroundOutgoingMergedSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true)
|
self.chatMessageBackgroundOutgoingMergedSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true)
|
||||||
self.chatMessageBackgroundIncomingMergedSideHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: false, extendedEdges: true)
|
self.chatMessageBackgroundIncomingMergedSideHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true)
|
||||||
self.chatMessageBackgroundOutgoingMergedSideHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: false, extendedEdges: true)
|
self.chatMessageBackgroundOutgoingMergedSideHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true)
|
||||||
|
|
||||||
self.checkBubbleFullImage = generateCheckImage(partial: false, color: theme.message.outgoingCheckColor, width: 11.0)!
|
self.checkBubbleFullImage = generateCheckImage(partial: false, color: theme.message.outgoingCheckColor, width: 11.0)!
|
||||||
self.checkBubblePartialImage = generateCheckImage(partial: true, color: theme.message.outgoingCheckColor, width: 11.0)!
|
self.checkBubblePartialImage = generateCheckImage(partial: true, color: theme.message.outgoingCheckColor, width: 11.0)!
|
||||||
|
@ -253,6 +253,7 @@ swift_library(
|
|||||||
"//submodules/Components/ReactionListContextMenuContent:ReactionListContextMenuContent",
|
"//submodules/Components/ReactionListContextMenuContent:ReactionListContextMenuContent",
|
||||||
"//submodules/Components/ReactionImageComponent:ReactionImageComponent",
|
"//submodules/Components/ReactionImageComponent:ReactionImageComponent",
|
||||||
"//submodules/Translate:Translate",
|
"//submodules/Translate:Translate",
|
||||||
|
"//submodules/TabBarUI:TabBarUI",
|
||||||
] + select({
|
] + select({
|
||||||
"@build_bazel_rules_apple//apple:ios_armv7": [],
|
"@build_bazel_rules_apple//apple:ios_armv7": [],
|
||||||
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
||||||
|
1
submodules/TelegramUI/Resources/Animations/TabCalls.json
Normal file
1
submodules/TelegramUI/Resources/Animations/TabCalls.json
Normal file
File diff suppressed because one or more lines are too long
1
submodules/TelegramUI/Resources/Animations/TabChats.json
Normal file
1
submodules/TelegramUI/Resources/Animations/TabChats.json
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -2041,7 +2041,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||||||
if result {
|
if result {
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
let reply = UNTextInputNotificationAction(identifier: "reply", title: replyString, options: [], textInputButtonTitle: replyString, textInputPlaceholder: messagePlaceholderString)
|
let reply = UNTextInputNotificationAction(identifier: "reply", title: replyString, options: [], textInputButtonTitle: replyString, textInputPlaceholder: messagePlaceholderString)
|
||||||
|
|
||||||
let unknownMessageCategory: UNNotificationCategory
|
let unknownMessageCategory: UNNotificationCategory
|
||||||
let replyMessageCategory: UNNotificationCategory
|
let replyMessageCategory: UNNotificationCategory
|
||||||
let replyLegacyMessageCategory: UNNotificationCategory
|
let replyLegacyMessageCategory: UNNotificationCategory
|
||||||
|
@ -32,7 +32,7 @@ private let inlineBotPrefixFont = Font.regular(14.0)
|
|||||||
private let inlineBotNameFont = nameFont
|
private let inlineBotNameFont = nameFont
|
||||||
|
|
||||||
protocol GenericAnimatedStickerNode: ASDisplayNode {
|
protocol GenericAnimatedStickerNode: ASDisplayNode {
|
||||||
func setOverlayColor(_ color: UIColor?, animated: Bool)
|
func setOverlayColor(_ color: UIColor?, replace: Bool, animated: Bool)
|
||||||
|
|
||||||
var currentFrameIndex: Int { get }
|
var currentFrameIndex: Int { get }
|
||||||
func setFrameIndex(_ frameIndex: Int)
|
func setFrameIndex(_ frameIndex: Int)
|
||||||
@ -58,7 +58,7 @@ private class VideoStickerNode: ASDisplayNode, GenericAnimatedStickerNode {
|
|||||||
private var layerHolder: SampleBufferLayer?
|
private var layerHolder: SampleBufferLayer?
|
||||||
var manager: SoftwareVideoLayerFrameManager?
|
var manager: SoftwareVideoLayerFrameManager?
|
||||||
|
|
||||||
func setOverlayColor(_ color: UIColor?, animated: Bool) {
|
func setOverlayColor(_ color: UIColor?, replace: Bool, animated: Bool) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2193,10 +2193,10 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
|
|
||||||
if highlighted {
|
if highlighted {
|
||||||
self.imageNode.setOverlayColor(item.presentationData.theme.theme.chat.message.mediaHighlightOverlayColor, animated: false)
|
self.imageNode.setOverlayColor(item.presentationData.theme.theme.chat.message.mediaHighlightOverlayColor, animated: false)
|
||||||
self.animationNode?.setOverlayColor(item.presentationData.theme.theme.chat.message.mediaHighlightOverlayColor, animated: false)
|
self.animationNode?.setOverlayColor(item.presentationData.theme.theme.chat.message.mediaHighlightOverlayColor, replace: false, animated: false)
|
||||||
} else {
|
} else {
|
||||||
self.imageNode.setOverlayColor(nil, animated: animated)
|
self.imageNode.setOverlayColor(nil, animated: animated)
|
||||||
self.animationNode?.setOverlayColor(nil, animated: false)
|
self.animationNode?.setOverlayColor(nil, replace: false, animated: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -203,7 +203,7 @@ final class ManagedDiceAnimationNode: ManagedAnimationNode, GenericAnimatedStick
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setOverlayColor(_ color: UIColor?, animated: Bool) {
|
func setOverlayColor(_ color: UIColor?, replace: Bool, animated: Bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func setFrameIndex(_ frameIndex: Int) {
|
func setFrameIndex(_ frameIndex: Int) {
|
||||||
|
@ -279,7 +279,7 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode {
|
|||||||
textColorValue = presentationData.theme.list.itemAccentColor
|
textColorValue = presentationData.theme.list.itemAccentColor
|
||||||
}
|
}
|
||||||
|
|
||||||
self.expandNode.attributedText = NSAttributedString(string: presentationData.strings.PeerInfo_BioExpand, font: Font.medium(15.0), textColor: presentationData.theme.list.itemAccentColor)
|
self.expandNode.attributedText = NSAttributedString(string: presentationData.strings.PeerInfo_BioExpand, font: Font.regular(17.0), textColor: presentationData.theme.list.itemAccentColor)
|
||||||
let expandSize = self.expandNode.updateLayout(CGSize(width: width, height: 100.0))
|
let expandSize = self.expandNode.updateLayout(CGSize(width: width, height: 100.0))
|
||||||
|
|
||||||
self.labelNode.attributedText = NSAttributedString(string: item.label, font: Font.regular(14.0), textColor: presentationData.theme.list.itemPrimaryTextColor)
|
self.labelNode.attributedText = NSAttributedString(string: item.label, font: Font.regular(14.0), textColor: presentationData.theme.list.itemPrimaryTextColor)
|
||||||
@ -341,7 +341,7 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode {
|
|||||||
let labelFrame = CGRect(origin: CGPoint(x: sideInset, y: 11.0), size: labelSize)
|
let labelFrame = CGRect(origin: CGPoint(x: sideInset, y: 11.0), size: labelSize)
|
||||||
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: labelFrame.maxY + 3.0), size: textSize)
|
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: labelFrame.maxY + 3.0), size: textSize)
|
||||||
|
|
||||||
let expandFrame = CGRect(origin: CGPoint(x: width - safeInsets.right - expandSize.width - 14.0 - UIScreenPixel, y: textFrame.maxY - expandSize.height - 1.0), size: expandSize)
|
let expandFrame = CGRect(origin: CGPoint(x: width - safeInsets.right - expandSize.width - 14.0 - UIScreenPixel, y: textFrame.maxY - expandSize.height), size: expandSize)
|
||||||
self.expandNode.frame = expandFrame
|
self.expandNode.frame = expandFrame
|
||||||
self.expandButonNode.frame = expandFrame.insetBy(dx: -8.0, dy: -8.0)
|
self.expandButonNode.frame = expandFrame.insetBy(dx: -8.0, dy: -8.0)
|
||||||
|
|
||||||
|
@ -7170,8 +7170,8 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen {
|
|||||||
icon = UIImage(bundleImageName: "Chat List/Tabs/IconSettings")
|
icon = UIImage(bundleImageName: "Chat List/Tabs/IconSettings")
|
||||||
}
|
}
|
||||||
|
|
||||||
let tabBarItem: Signal<(String, UIImage?, UIImage?, String?), NoError> = combineLatest(queue: .mainQueue(), self.context.sharedContext.presentationData, notificationsAuthorizationStatus.get(), notificationsWarningSuppressed.get(), getServerProvidedSuggestions(account: self.context.account), accountTabBarAvatar, accountTabBarAvatarBadge)
|
let tabBarItem: Signal<(String, UIImage?, UIImage?, String?, Bool), NoError> = combineLatest(queue: .mainQueue(), self.context.sharedContext.presentationData, notificationsAuthorizationStatus.get(), notificationsWarningSuppressed.get(), getServerProvidedSuggestions(account: self.context.account), accountTabBarAvatar, accountTabBarAvatarBadge)
|
||||||
|> map { presentationData, notificationsAuthorizationStatus, notificationsWarningSuppressed, suggestions, accountTabBarAvatar, accountTabBarAvatarBadge -> (String, UIImage?, UIImage?, String?) in
|
|> map { presentationData, notificationsAuthorizationStatus, notificationsWarningSuppressed, suggestions, accountTabBarAvatar, accountTabBarAvatarBadge -> (String, UIImage?, UIImage?, String?, Bool) in
|
||||||
let notificationsWarning = shouldDisplayNotificationsPermissionWarning(status: notificationsAuthorizationStatus, suppressed: notificationsWarningSuppressed)
|
let notificationsWarning = shouldDisplayNotificationsPermissionWarning(status: notificationsAuthorizationStatus, suppressed: notificationsWarningSuppressed)
|
||||||
let phoneNumberWarning = suggestions.contains(.validatePhoneNumber)
|
let phoneNumberWarning = suggestions.contains(.validatePhoneNumber)
|
||||||
let passwordWarning = suggestions.contains(.validatePassword)
|
let passwordWarning = suggestions.contains(.validatePassword)
|
||||||
@ -7179,14 +7179,15 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen {
|
|||||||
if accountTabBarAvatarBadge > 0 {
|
if accountTabBarAvatarBadge > 0 {
|
||||||
otherAccountsBadge = compactNumericCountString(Int(accountTabBarAvatarBadge), decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)
|
otherAccountsBadge = compactNumericCountString(Int(accountTabBarAvatarBadge), decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)
|
||||||
}
|
}
|
||||||
return (presentationData.strings.Settings_Title, accountTabBarAvatar?.0 ?? icon, accountTabBarAvatar?.1 ?? icon, notificationsWarning || phoneNumberWarning || passwordWarning ? "!" : otherAccountsBadge)
|
return (presentationData.strings.Settings_Title, accountTabBarAvatar?.0 ?? icon, accountTabBarAvatar?.1 ?? icon, notificationsWarning || phoneNumberWarning || passwordWarning ? "!" : otherAccountsBadge, accountTabBarAvatar != nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.tabBarItemDisposable = (tabBarItem |> deliverOnMainQueue).start(next: { [weak self] title, image, selectedImage, badgeValue in
|
self.tabBarItemDisposable = (tabBarItem |> deliverOnMainQueue).start(next: { [weak self] title, image, selectedImage, badgeValue, isAvatar in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.tabBarItem.title = title
|
strongSelf.tabBarItem.title = title
|
||||||
strongSelf.tabBarItem.image = image
|
strongSelf.tabBarItem.image = image
|
||||||
strongSelf.tabBarItem.selectedImage = selectedImage
|
strongSelf.tabBarItem.selectedImage = selectedImage
|
||||||
|
strongSelf.tabBarItem.animationName = isAvatar ? nil : "TabSettings"
|
||||||
strongSelf.tabBarItem.badgeValue = badgeValue
|
strongSelf.tabBarItem.badgeValue = badgeValue
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -14,6 +14,7 @@ import SettingsUI
|
|||||||
import AppBundle
|
import AppBundle
|
||||||
import DatePickerNode
|
import DatePickerNode
|
||||||
import DebugSettingsUI
|
import DebugSettingsUI
|
||||||
|
import TabBarUI
|
||||||
|
|
||||||
public final class TelegramRootController: NavigationController {
|
public final class TelegramRootController: NavigationController {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
@ -64,7 +65,7 @@ public final class TelegramRootController: NavigationController {
|
|||||||
let previousTheme = strongSelf.presentationData.theme
|
let previousTheme = strongSelf.presentationData.theme
|
||||||
strongSelf.presentationData = presentationData
|
strongSelf.presentationData = presentationData
|
||||||
if previousTheme !== presentationData.theme {
|
if previousTheme !== presentationData.theme {
|
||||||
strongSelf.rootTabController?.updateTheme(navigationBarPresentationData: NavigationBarPresentationData(presentationData: presentationData), theme: TabBarControllerTheme(rootControllerTheme: presentationData.theme))
|
(strongSelf.rootTabController as? TabBarControllerImpl)?.updateTheme(navigationBarPresentationData: NavigationBarPresentationData(presentationData: presentationData), theme: TabBarControllerTheme(rootControllerTheme: presentationData.theme))
|
||||||
strongSelf.rootTabController?.statusBar.statusBarStyle = presentationData.theme.rootController.statusBarStyle.style
|
strongSelf.rootTabController?.statusBar.statusBarStyle = presentationData.theme.rootController.statusBarStyle.style
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -81,7 +82,7 @@ public final class TelegramRootController: NavigationController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func addRootControllers(showCallsTab: Bool) {
|
public func addRootControllers(showCallsTab: Bool) {
|
||||||
let tabBarController = TabBarController(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), theme: TabBarControllerTheme(rootControllerTheme: self.presentationData.theme))
|
let tabBarController = TabBarControllerImpl(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), theme: TabBarControllerTheme(rootControllerTheme: self.presentationData.theme))
|
||||||
tabBarController.navigationPresentation = .master
|
tabBarController.navigationPresentation = .master
|
||||||
let chatListController = self.context.sharedContext.makeChatListController(context: self.context, groupId: .root, controlsHistoryPreload: true, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: !GlobalExperimentalSettings.isAppStoreBuild)
|
let chatListController = self.context.sharedContext.makeChatListController(context: self.context, groupId: .root, controlsHistoryPreload: true, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: !GlobalExperimentalSettings.isAppStoreBuild)
|
||||||
if let sharedContext = self.context.sharedContext as? SharedAccountContextImpl {
|
if let sharedContext = self.context.sharedContext as? SharedAccountContextImpl {
|
||||||
@ -131,7 +132,7 @@ public final class TelegramRootController: NavigationController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func updateRootControllers(showCallsTab: Bool) {
|
public func updateRootControllers(showCallsTab: Bool) {
|
||||||
guard let rootTabController = self.rootTabController else {
|
guard let rootTabController = self.rootTabController as? TabBarControllerImpl else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var controllers: [ViewController] = []
|
var controllers: [ViewController] = []
|
||||||
|
@ -728,44 +728,68 @@ public func convertMarkdownToAttributes(_ text: NSAttributedString) -> NSAttribu
|
|||||||
|
|
||||||
var pre = match.range(at: 3)
|
var pre = match.range(at: 3)
|
||||||
if pre.location != NSNotFound {
|
if pre.location != NSNotFound {
|
||||||
let text = string.substring(with: pre)
|
var intersectsWithEntities = false
|
||||||
|
text.enumerateAttributes(in: pre, options: [], using: { attributes, _, _ in
|
||||||
stringOffset -= match.range(at: 2).length + match.range(at: 4).length
|
for (key, _) in attributes {
|
||||||
|
if key.rawValue.hasPrefix("Attribute__") {
|
||||||
let substring = string.substring(with: match.range(at: 1)) + text + string.substring(with: match.range(at: 5))
|
intersectsWithEntities = true
|
||||||
result.append(NSAttributedString(string: substring, attributes: [ChatTextInputAttributes.monospace: true as NSNumber]))
|
}
|
||||||
offsetRanges.append((NSMakeRange(matchIndex + match.range(at: 1).length, text.count), 6))
|
}
|
||||||
|
})
|
||||||
|
if intersectsWithEntities {
|
||||||
|
result.append(text.attributedSubstring(from: match.range(at: 0)))
|
||||||
|
} else {
|
||||||
|
let text = string.substring(with: pre)
|
||||||
|
|
||||||
|
stringOffset -= match.range(at: 2).length + match.range(at: 4).length
|
||||||
|
|
||||||
|
let substring = string.substring(with: match.range(at: 1)) + text + string.substring(with: match.range(at: 5))
|
||||||
|
result.append(NSAttributedString(string: substring, attributes: [ChatTextInputAttributes.monospace: true as NSNumber]))
|
||||||
|
offsetRanges.append((NSMakeRange(matchIndex + match.range(at: 1).length, text.count), 6))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pre = match.range(at: 8)
|
pre = match.range(at: 8)
|
||||||
if pre.location != NSNotFound {
|
if pre.location != NSNotFound {
|
||||||
let text = string.substring(with: pre)
|
var intersectsWithEntities = false
|
||||||
|
text.enumerateAttributes(in: pre, options: [], using: { attributes, _, _ in
|
||||||
let entity = string.substring(with: match.range(at: 7))
|
for (key, _) in attributes {
|
||||||
let substring = string.substring(with: match.range(at: 6)) + text + string.substring(with: match.range(at: 9))
|
if key.rawValue.hasPrefix("Attribute__") {
|
||||||
|
intersectsWithEntities = true
|
||||||
let textInputAttribute: NSAttributedString.Key?
|
}
|
||||||
switch entity {
|
}
|
||||||
case "`":
|
})
|
||||||
textInputAttribute = ChatTextInputAttributes.monospace
|
if intersectsWithEntities {
|
||||||
case "**":
|
result.append(text.attributedSubstring(from: match.range(at: 0)))
|
||||||
textInputAttribute = ChatTextInputAttributes.bold
|
} else {
|
||||||
case "__":
|
let text = string.substring(with: pre)
|
||||||
textInputAttribute = ChatTextInputAttributes.italic
|
|
||||||
case "~~":
|
let entity = string.substring(with: match.range(at: 7))
|
||||||
textInputAttribute = ChatTextInputAttributes.strikethrough
|
let substring = string.substring(with: match.range(at: 6)) + text + string.substring(with: match.range(at: 9))
|
||||||
case "||":
|
|
||||||
textInputAttribute = ChatTextInputAttributes.spoiler
|
let textInputAttribute: NSAttributedString.Key?
|
||||||
default:
|
switch entity {
|
||||||
textInputAttribute = nil
|
case "`":
|
||||||
|
textInputAttribute = ChatTextInputAttributes.monospace
|
||||||
|
case "**":
|
||||||
|
textInputAttribute = ChatTextInputAttributes.bold
|
||||||
|
case "__":
|
||||||
|
textInputAttribute = ChatTextInputAttributes.italic
|
||||||
|
case "~~":
|
||||||
|
textInputAttribute = ChatTextInputAttributes.strikethrough
|
||||||
|
case "||":
|
||||||
|
textInputAttribute = ChatTextInputAttributes.spoiler
|
||||||
|
default:
|
||||||
|
textInputAttribute = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if let textInputAttribute = textInputAttribute {
|
||||||
|
result.append(NSAttributedString(string: substring, attributes: [textInputAttribute: true as NSNumber]))
|
||||||
|
offsetRanges.append((NSMakeRange(matchIndex + match.range(at: 6).length, text.count), match.range(at: 6).length * 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
stringOffset -= match.range(at: 7).length * 2
|
||||||
}
|
}
|
||||||
|
|
||||||
if let textInputAttribute = textInputAttribute {
|
|
||||||
result.append(NSAttributedString(string: substring, attributes: [textInputAttribute: true as NSNumber]))
|
|
||||||
offsetRanges.append((NSMakeRange(matchIndex + match.range(at: 6).length, text.count), match.range(at: 6).length * 2))
|
|
||||||
}
|
|
||||||
|
|
||||||
stringOffset -= match.range(at: 7).length * 2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string = string.substring(from: match.range.location + match.range(at: 0).length) as NSString
|
string = string.substring(from: match.range.location + match.range(at: 0).length) as NSString
|
||||||
|
@ -59,7 +59,7 @@ public func translateText(context: AccountContext, text: String) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if #available(iOS 15.0, *) {
|
if #available(iOS 15.0, *) {
|
||||||
let text = text.unicodeScalars.filter { !$0.properties.isEmojiPresentation}.reduce("") { $0 + String($1) }
|
let text = text.unicodeScalars.filter { !$0.properties.isEmojiPresentation }.reduce("") { $0 + String($1) }
|
||||||
|
|
||||||
let textView = UITextView()
|
let textView = UITextView()
|
||||||
textView.text = text
|
textView.text = text
|
||||||
|
@ -48,7 +48,6 @@ NSInteger UITabBarItem_addSetBadgeListener(UITabBarItem * _Nonnull item, UITabBa
|
|||||||
- (NSInteger)addSetSelectedImageListener:(UINavigationItemSetImageListener _Nonnull)listener;
|
- (NSInteger)addSetSelectedImageListener:(UINavigationItemSetImageListener _Nonnull)listener;
|
||||||
- (void)removeSetSelectedImageListener:(NSInteger)key;
|
- (void)removeSetSelectedImageListener:(NSInteger)key;
|
||||||
|
|
||||||
- (NSObject * _Nullable)userInfo;
|
@property (nonatomic, strong) NSString * _Nullable animationName;
|
||||||
- (void)setUserInfo:(NSObject * _Nullable)userInfo;
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -16,7 +16,7 @@ static const void *setMultipleRightBarButtonItemsListenerKey = &setMultipleRight
|
|||||||
static const void *setBackBarButtonItemListenerBagKey = &setBackBarButtonItemListenerBagKey;
|
static const void *setBackBarButtonItemListenerBagKey = &setBackBarButtonItemListenerBagKey;
|
||||||
static const void *setBadgeListenerBagKey = &setBadgeListenerBagKey;
|
static const void *setBadgeListenerBagKey = &setBadgeListenerBagKey;
|
||||||
static const void *badgeKey = &badgeKey;
|
static const void *badgeKey = &badgeKey;
|
||||||
static const void *userInfoKey = &userInfoKey;
|
static const void *animationNameKey = &animationNameKey;
|
||||||
|
|
||||||
@implementation UINavigationItem (Proxy)
|
@implementation UINavigationItem (Proxy)
|
||||||
|
|
||||||
@ -402,12 +402,16 @@ NSInteger UITabBarItem_addSetBadgeListener(UITabBarItem *item, UITabBarItemSetBa
|
|||||||
[(NSBag *)[self associatedObjectForKey:setSelectedImageListenerBagKey] removeItem:key];
|
[(NSBag *)[self associatedObjectForKey:setSelectedImageListenerBagKey] removeItem:key];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSObject * _Nullable)userInfo {
|
- (void)setAnimationName:(NSString *)animationName {
|
||||||
return [self associatedObjectForKey:userInfoKey];
|
[self setAssociatedObject:animationName forKey:animationNameKey];
|
||||||
|
|
||||||
|
// [(NSBag *)[self associatedObjectForKey:setBadgeListenerBagKey] enumerateItems:^(UITabBarItemSetBadgeListener listener) {
|
||||||
|
// listener(badge);
|
||||||
|
// }];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setUserInfo:(NSObject * _Nullable)userInfo {
|
- (NSString *)animationName {
|
||||||
[self setAssociatedObject:userInfo forKey:userInfoKey];
|
return [self associatedObjectForKey:animationNameKey];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -432,9 +432,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
|
|||||||
var isReady: Signal<Bool, NoError> {
|
var isReady: Signal<Bool, NoError> {
|
||||||
return self._isReady.get()
|
return self._isReady.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var newYearNode: WallpaperNewYearNode?
|
|
||||||
|
|
||||||
init(context: AccountContext, useSharedAnimationPhase: Bool) {
|
init(context: AccountContext, useSharedAnimationPhase: Bool) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.useSharedAnimationPhase = useSharedAnimationPhase
|
self.useSharedAnimationPhase = useSharedAnimationPhase
|
||||||
@ -800,16 +798,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
|
|||||||
|
|
||||||
if isFirstLayout && !self.frame.isEmpty {
|
if isFirstLayout && !self.frame.isEmpty {
|
||||||
self.updateScale()
|
self.updateScale()
|
||||||
|
|
||||||
if self.context.sharedContext.immediateExperimentalUISettings.snow, self.newYearNode == nil {
|
|
||||||
let newYearNode = WallpaperNewYearNode()
|
|
||||||
self.addSubnode(newYearNode)
|
|
||||||
self.newYearNode = newYearNode
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.newYearNode?.frame = CGRect(origin: CGPoint(), size: size)
|
|
||||||
self.newYearNode?.updateLayout(size: size)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool) {
|
func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool) {
|
||||||
@ -1720,47 +1709,3 @@ public func createWallpaperBackgroundNode(context: AccountContext, forChatDispla
|
|||||||
|
|
||||||
return WallpaperBackgroundNodeImpl(context: context, useSharedAnimationPhase: useSharedAnimationPhase)
|
return WallpaperBackgroundNodeImpl(context: context, useSharedAnimationPhase: useSharedAnimationPhase)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class WallpaperNewYearNode: ASDisplayNode {
|
|
||||||
private var emitterLayer: CAEmitterLayer?
|
|
||||||
|
|
||||||
func updateLayout(size: CGSize) {
|
|
||||||
if self.emitterLayer == nil {
|
|
||||||
let particlesLayer = CAEmitterLayer()
|
|
||||||
self.emitterLayer = particlesLayer
|
|
||||||
|
|
||||||
self.layer.addSublayer(particlesLayer)
|
|
||||||
self.layer.masksToBounds = true
|
|
||||||
|
|
||||||
particlesLayer.backgroundColor = UIColor.clear.cgColor
|
|
||||||
particlesLayer.emitterShape = .circle
|
|
||||||
particlesLayer.emitterMode = .surface
|
|
||||||
particlesLayer.renderMode = .oldestLast
|
|
||||||
|
|
||||||
let cell1 = CAEmitterCell()
|
|
||||||
cell1.contents = UIImage(bundleImageName: "Components/Snowflake")?.cgImage
|
|
||||||
cell1.name = "snow"
|
|
||||||
cell1.birthRate = 252.0
|
|
||||||
cell1.lifetime = 20.0
|
|
||||||
cell1.velocity = 19.0
|
|
||||||
cell1.velocityRange = -5.0
|
|
||||||
cell1.xAcceleration = 2.5
|
|
||||||
cell1.yAcceleration = 10.0
|
|
||||||
cell1.emissionRange = .pi
|
|
||||||
cell1.spin = -28.6 * (.pi / 180.0)
|
|
||||||
cell1.spinRange = 57.2 * (.pi / 180.0)
|
|
||||||
cell1.scale = 0.04
|
|
||||||
cell1.scaleRange = 0.15
|
|
||||||
cell1.color = UIColor.white.withAlphaComponent(0.58).cgColor
|
|
||||||
// cell1.alphaRange = -0.2
|
|
||||||
|
|
||||||
particlesLayer.emitterCells = [cell1]
|
|
||||||
}
|
|
||||||
|
|
||||||
if let emitterLayer = self.emitterLayer {
|
|
||||||
emitterLayer.emitterPosition = CGPoint(x: size.width / 2.0, y: -size.height / 2.0)
|
|
||||||
emitterLayer.emitterSize = CGSize(width: size.width * 2.5, height: size.height)
|
|
||||||
emitterLayer.frame = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user