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.ByName" = "by Name";
|
||||
"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 }
|
||||
|
||||
private var overlayColor: (UIColor?, Bool)? = nil
|
||||
private var size: CGSize?
|
||||
|
||||
override public init() {
|
||||
self.queue = sharedQueue
|
||||
self.eventsNode = AnimatedStickerNodeDisplayEvents()
|
||||
@ -900,10 +903,13 @@ public final class AnimatedStickerNode: ASDisplayNode {
|
||||
self.renderer = SoftwareAnimationRenderer()
|
||||
//self.renderer = MetalAnimationRenderer()
|
||||
#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 {
|
||||
self.renderer?.contents = contents
|
||||
}
|
||||
if let (overlayColor, replace) = self.overlayColor {
|
||||
self.renderer?.setOverlayColor(overlayColor, replace: replace, animated: false)
|
||||
}
|
||||
self.nodeToCopyFrameFrom = nil
|
||||
self.addSubnode(self.renderer!)
|
||||
}
|
||||
@ -1347,10 +1353,12 @@ public final class AnimatedStickerNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
public func updateLayout(size: CGSize) {
|
||||
self.size = size
|
||||
self.renderer?.frame = CGRect(origin: CGPoint(), size: size)
|
||||
}
|
||||
|
||||
public func setOverlayColor(_ color: UIColor?, animated: Bool) {
|
||||
self.renderer?.setOverlayColor(color, animated: animated)
|
||||
public func setOverlayColor(_ color: UIColor?, replace: Bool, animated: Bool) {
|
||||
self.overlayColor = (color, replace)
|
||||
self.renderer?.setOverlayColor(color, replace: replace, animated: animated)
|
||||
}
|
||||
}
|
||||
|
@ -10,5 +10,5 @@ public enum AnimationRendererFrameType {
|
||||
protocol AnimationRenderer {
|
||||
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 {
|
||||
private var highlightedContentNode: ASDisplayNode?
|
||||
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) {
|
||||
assert(bytesPerRow > 0)
|
||||
queue.async { [weak self] in
|
||||
@ -53,20 +54,29 @@ final class SoftwareAnimationRenderer: ASDisplayNode, AnimationRenderer {
|
||||
}
|
||||
strongSelf.contents = image?.cgImage
|
||||
strongSelf.updateHighlightedContentNode()
|
||||
if strongSelf.highlightedContentNode?.frame != strongSelf.bounds {
|
||||
strongSelf.highlightedContentNode?.frame = strongSelf.bounds
|
||||
}
|
||||
completion()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
(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
|
||||
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
|
||||
if let current = self.highlightedColor, let color = 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)
|
||||
} else {
|
||||
selectionToolbarNode = ToolbarNode(
|
||||
theme: TabBarControllerTheme(
|
||||
theme: ToolbarTheme(
|
||||
rootControllerTheme: self.presentationData.theme),
|
||||
displaySeparator: true,
|
||||
left: {
|
||||
|
@ -115,6 +115,7 @@ public final class CallListController: TelegramBaseController {
|
||||
self.tabBarItem.title = self.presentationData.strings.Calls_TabTitle
|
||||
self.tabBarItem.image = icon
|
||||
self.tabBarItem.selectedImage = icon
|
||||
self.tabBarItem.animationName = "TabCalls"
|
||||
}
|
||||
|
||||
self.segmentedTitleView.indexUpdated = { [weak self] index in
|
||||
|
@ -206,6 +206,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
|
||||
self.tabBarItem.image = 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))
|
||||
leftBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Edit
|
||||
|
@ -1116,7 +1116,7 @@ final class ChatListControllerNode: ASDisplayNode {
|
||||
self.searchDisplayController?.updatePresentationData(presentationData)
|
||||
|
||||
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)
|
||||
toolbarNode.updateLayout(size: tabBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: bottomInset, toolbar: toolbar, transition: transition)
|
||||
} 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)
|
||||
}, right: { [weak self] in
|
||||
self?.toolbarActionSelected?(.right)
|
||||
|
@ -192,6 +192,7 @@ public class ContactsController: ViewController {
|
||||
|
||||
self.tabBarItem.image = 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)
|
||||
|
||||
|
@ -940,7 +940,6 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
|
||||
entries.append(.enableDebugDataDisplay(experimentalSettings.enableDebugDataDisplay))
|
||||
entries.append(.acceleratedStickers(experimentalSettings.acceleratedStickers))
|
||||
entries.append(.experimentalBackground(experimentalSettings.experimentalBackground))
|
||||
entries.append(.snow(experimentalSettings.snow))
|
||||
entries.append(.playerEmbedding(experimentalSettings.playerEmbedding))
|
||||
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
|
||||
}
|
||||
|
@ -1,5 +1,10 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
|
||||
public protocol ActionSheetGroupOverlayNode: ASDisplayNode {
|
||||
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition)
|
||||
}
|
||||
|
||||
open class ActionSheetController: ViewController, PresentableController, StandalonePresentableController {
|
||||
private var actionSheetNode: ActionSheetControllerNode {
|
||||
@ -83,4 +88,10 @@ open class ActionSheetController: ViewController, PresentableController, Standal
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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 overlayNode: ActionSheetGroupOverlayNode?
|
||||
|
||||
init(theme: ActionSheetControllerTheme) {
|
||||
self.theme = theme
|
||||
|
||||
@ -66,6 +68,31 @@ final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
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) {
|
||||
for node in self.itemNodes {
|
||||
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)
|
||||
}
|
||||
|
||||
if let overlayNode = self.overlayNode {
|
||||
overlayNode.updateLayout(size: size, transition: transition)
|
||||
}
|
||||
|
||||
self.updateOverscroll(size: size, transition: transition)
|
||||
|
||||
return size
|
||||
|
@ -112,4 +112,8 @@ final class ActionSheetItemGroupsContainerNode: ASDisplayNode {
|
||||
|
||||
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 SwiftSignalKit
|
||||
|
||||
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 enum TabBarItemSwipeDirection {
|
||||
case left
|
||||
case right
|
||||
}
|
||||
|
||||
public final class TabBarItemInfo: NSObject {
|
||||
public let previewing: Bool
|
||||
public protocol TabBarController: ViewController {
|
||||
var currentController: ViewController? { get }
|
||||
var controllers: [ViewController] { get }
|
||||
var selectedIndex: Int { get set }
|
||||
|
||||
public init(previewing: Bool) {
|
||||
self.previewing = previewing
|
||||
|
||||
super.init()
|
||||
}
|
||||
func setControllers(_ controllers: [ViewController], selectedIndex: Int?)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
func updateBackgroundAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition)
|
||||
|
||||
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 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()
|
||||
}
|
||||
}
|
||||
func frameForControllerTab(controller: ViewController) -> CGRect?
|
||||
func isPointInsideContentArea(point: CGPoint) -> Bool
|
||||
func sourceNodesForController(at index: Int) -> [ASDisplayNode]?
|
||||
|
||||
func updateIsTabBarEnabled(_ value: Bool, transition: ContainedViewLayoutTransition)
|
||||
func updateIsTabBarHidden(_ value: Bool, transition: ContainedViewLayoutTransition)
|
||||
func updateLayout(transition: ContainedViewLayoutTransition)
|
||||
}
|
||||
|
@ -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 truncatedTokenString = NSAttributedString(string: tokenString, attributes: truncationTokenAttributes)
|
||||
let truncationToken = CTLineCreateWithAttributedString(truncatedTokenString)
|
||||
|
||||
|
||||
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 {
|
||||
brokenLineRange.length = attributedString.length - brokenLineRange.location
|
||||
}
|
||||
@ -1330,7 +1338,9 @@ public class TextNode: ASDisplayNode {
|
||||
context.saveGState()
|
||||
var clipRects: [CGRect] = []
|
||||
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)
|
||||
}
|
||||
@ -1365,7 +1375,9 @@ public class TextNode: ASDisplayNode {
|
||||
context.restoreGState()
|
||||
} else {
|
||||
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 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 {
|
||||
private var theme: TabBarControllerTheme
|
||||
private var theme: ToolbarTheme
|
||||
private let displaySeparator: Bool
|
||||
public var left: () -> Void
|
||||
public var right: () -> Void
|
||||
@ -18,14 +38,14 @@ public final class ToolbarNode: ASDisplayNode {
|
||||
private let middleTitle: ImmediateTextNode
|
||||
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.displaySeparator = displaySeparator
|
||||
self.left = left
|
||||
self.right = right
|
||||
self.middle = middle
|
||||
|
||||
self.backgroundNode = NavigationBackgroundNode(color: theme.tabBarBackgroundColor)
|
||||
self.backgroundNode = NavigationBackgroundNode(color: theme.barBackgroundColor)
|
||||
|
||||
self.separatorNode = ASDisplayNode()
|
||||
self.separatorNode.isLayerBacked = true
|
||||
@ -100,9 +120,9 @@ public final class ToolbarNode: ASDisplayNode {
|
||||
self.middleButton.accessibilityTraits = .button
|
||||
}
|
||||
|
||||
public func updateTheme(_ theme: TabBarControllerTheme) {
|
||||
self.separatorNode.backgroundColor = theme.tabBarSeparatorColor
|
||||
self.backgroundNode.updateColor(color: theme.tabBarBackgroundColor, transition: .immediate)
|
||||
public func updateTheme(_ theme: ToolbarTheme) {
|
||||
self.separatorNode.backgroundColor = theme.barSeparatorColor
|
||||
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) {
|
||||
@ -112,10 +132,10 @@ public final class ToolbarNode: ASDisplayNode {
|
||||
|
||||
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.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
|
||||
|
||||
let middleColor: UIColor
|
||||
@ -123,15 +143,15 @@ public final class ToolbarNode: ASDisplayNode {
|
||||
if middleAction.isEnabled {
|
||||
switch middleAction.color {
|
||||
case .accent:
|
||||
middleColor = self.theme.tabBarSelectedTextColor
|
||||
middleColor = self.theme.barSelectedTextColor
|
||||
case let .custom(color):
|
||||
middleColor = color
|
||||
}
|
||||
} else {
|
||||
middleColor = self.theme.tabBarTextColor
|
||||
middleColor = self.theme.barTextColor
|
||||
}
|
||||
} 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.middleButton.accessibilityLabel = toolbar.middleAction?.title
|
||||
|
@ -191,7 +191,7 @@ public enum TabBarItemContextActionType {
|
||||
|
||||
public let statusBar: StatusBar
|
||||
public let navigationBar: NavigationBar?
|
||||
private(set) var toolbar: Toolbar?
|
||||
public private(set) var toolbar: Toolbar?
|
||||
|
||||
public var displayNavigationBar = true
|
||||
open var navigationBarRequiresEntireLayoutUpdate: Bool {
|
||||
|
@ -501,7 +501,7 @@ open class ItemListControllerNode: ASDisplayNode {
|
||||
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)
|
||||
} 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.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)
|
||||
|
@ -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
|
||||
self.dataQueue.async {
|
||||
var keepPrefixes: [String] = []
|
||||
@ -1254,14 +1254,31 @@ public final class MediaBox {
|
||||
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 {
|
||||
for prefix in keepPrefixes {
|
||||
if path.starts(with: prefix) {
|
||||
count += 1
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
|
||||
count += 1
|
||||
unlink(self.basePath + "/" + path)
|
||||
reportProgress(count)
|
||||
}
|
||||
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
|
||||
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 })
|
||||
|
||||
var pathsToDelete: [String] = []
|
||||
|
||||
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 {
|
||||
unlink(path)
|
||||
count += 1
|
||||
reportProgress(count)
|
||||
}
|
||||
|
||||
subscriber.putCompletion()
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
@ -14,6 +15,8 @@ import AccountContext
|
||||
import ItemListPeerItem
|
||||
import DeleteChatPeerActionSheetItem
|
||||
import UndoUI
|
||||
import AnimatedStickerNode
|
||||
import TelegramAnimatedStickerNode
|
||||
|
||||
private func totalDiskSpace() -> Int64 {
|
||||
do {
|
||||
@ -275,15 +278,15 @@ private enum StorageUsageEntry: ItemListNodeEntry {
|
||||
}
|
||||
}
|
||||
|
||||
private struct StoragUsageState: Equatable {
|
||||
private struct StorageUsageState: Equatable {
|
||||
let peerIdWithRevealedOptions: PeerId?
|
||||
|
||||
func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> StoragUsageState {
|
||||
return StoragUsageState(peerIdWithRevealedOptions: peerIdWithRevealedOptions)
|
||||
func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> StorageUsageState {
|
||||
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] = []
|
||||
|
||||
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 {
|
||||
let statePromise = ValuePromise(StoragUsageState(peerIdWithRevealedOptions: nil))
|
||||
let stateValue = Atomic(value: StoragUsageState(peerIdWithRevealedOptions: nil))
|
||||
let updateState: ((StoragUsageState) -> StoragUsageState) -> Void = { f in
|
||||
let statePromise = ValuePromise(StorageUsageState(peerIdWithRevealedOptions: nil))
|
||||
let stateValue = Atomic(value: StorageUsageState(peerIdWithRevealedOptions: nil))
|
||||
let updateState: ((StorageUsageState) -> StorageUsageState) -> Void = { f in
|
||||
statePromise.set(stateValue.modify { f($0) })
|
||||
}
|
||||
|
||||
@ -546,7 +549,9 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P
|
||||
selectedSize = totalSize
|
||||
|
||||
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 {
|
||||
let clearCategories = sizeIndex.keys.filter({ sizeIndex[$0]!.0 })
|
||||
|
||||
@ -582,20 +587,37 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P
|
||||
var updatedTempPaths = stats.tempPaths
|
||||
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 {
|
||||
let removeTempFiles: Signal<Void, NoError> = Signal { subscriber in
|
||||
let removeTempFiles: Signal<Float, NoError> = Signal { subscriber in
|
||||
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 {
|
||||
let _ = try? fileManager.removeItem(atPath: path)
|
||||
count += 1
|
||||
reportProgress(count)
|
||||
}
|
||||
|
||||
subscriber.putCompletion()
|
||||
return EmptyDisposable
|
||||
} |> runOn(Queue.concurrentDefaultQueue())
|
||||
signal = signal
|
||||
|> then(context.account.postbox.mediaBox.removeOtherCachedResources(paths: stats.otherPaths))
|
||||
|> then(removeTempFiles)
|
||||
signal = (signal |> map { $0 * 0.7 })
|
||||
|> then(context.account.postbox.mediaBox.removeOtherCachedResources(paths: stats.otherPaths) |> map { 0.7 + 0.2 * $0 })
|
||||
|> then(removeTempFiles |> map { 0.9 + 0.1 * $0 })
|
||||
}
|
||||
|
||||
if otherSize.0 {
|
||||
@ -606,49 +628,39 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P
|
||||
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)
|
||||
|
||||
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 = {
|
||||
clearDisposable.set(nil)
|
||||
resetStats()
|
||||
}
|
||||
statsPromise.set(.single(.result(resultStats)))
|
||||
clearDisposable.set((signal
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
|> deliverOnMainQueue).start(next: { progress in
|
||||
progressPromise.set(progress)
|
||||
}, completed: {
|
||||
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([
|
||||
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))
|
||||
}
|
||||
}
|
||||
@ -743,7 +755,9 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P
|
||||
selectedSize = totalSize
|
||||
|
||||
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 {
|
||||
let clearCategories = sizeIndex.keys.filter({ sizeIndex[$0]!.0 })
|
||||
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)
|
||||
|
||||
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 = {
|
||||
clearDisposable.set(nil)
|
||||
resetStats()
|
||||
}
|
||||
clearDisposable.set((signal
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
|> deliverOnMainQueue).start(next: { progress in
|
||||
progressPromise.set(progress)
|
||||
}, completed: {
|
||||
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([
|
||||
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))
|
||||
}
|
||||
@ -995,3 +998,105 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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 UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
|
||||
public enum ToolbarActionOption {
|
||||
case left
|
||||
case right
|
||||
case middle
|
||||
private extension ToolbarTheme {
|
||||
convenience init(tabBarTheme theme: TabBarControllerTheme) {
|
||||
self.init(barBackgroundColor: theme.tabBarBackgroundColor, barSeparatorColor: theme.tabBarSeparatorColor, barTextColor: theme.tabBarTextColor, barSelectedTextColor: theme.tabBarSelectedTextColor)
|
||||
}
|
||||
}
|
||||
|
||||
final class TabBarControllerNode: ASDisplayNode {
|
||||
@ -65,7 +66,7 @@ final class TabBarControllerNode: ASDisplayNode {
|
||||
|
||||
self.tabBarNode.updateTheme(theme)
|
||||
self.disabledOverlayNode.backgroundColor = theme.backgroundColor.withAlphaComponent(0.5)
|
||||
self.toolbarNode?.updateTheme(theme)
|
||||
self.toolbarNode?.updateTheme(ToolbarTheme(tabBarTheme: theme))
|
||||
}
|
||||
|
||||
func updateIsTabBarEnabled(_ value: Bool, transition: ContainedViewLayoutTransition) {
|
||||
@ -99,7 +100,7 @@ final class TabBarControllerNode: ASDisplayNode {
|
||||
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)
|
||||
} 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)
|
||||
}, right: { [weak self] in
|
||||
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 AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import Display
|
||||
import UIKitRuntimeUtils
|
||||
import AnimatedStickerNode
|
||||
import TelegramAnimatedStickerNode
|
||||
|
||||
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) {
|
||||
@ -41,35 +44,24 @@ private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor:
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
if let image = image, imageMode {
|
||||
let imageRect: CGRect
|
||||
if horizontal {
|
||||
let 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()
|
||||
imageRect = CGRect(origin: CGPoint(x: 0.0, y: floor((size.height - imageSize.height) / 2.0)), size: imageSize)
|
||||
} 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)
|
||||
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()
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,15 +81,12 @@ private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor:
|
||||
|
||||
private let badgeFont = Font.regular(13.0)
|
||||
|
||||
public enum TabBarItemSwipeDirection {
|
||||
case left
|
||||
case right
|
||||
}
|
||||
|
||||
private final class TabBarItemNode: ASDisplayNode {
|
||||
let extractedContainerNode: ContextExtractedContentContainingNode
|
||||
let containerNode: ContextControllerSourceNode
|
||||
let imageNode: ASImageNode
|
||||
let animationContainerNode: ASDisplayNode
|
||||
let animationNode: AnimatedStickerNode
|
||||
let textImageNode: ASImageNode
|
||||
let contextImageNode: ASImageNode
|
||||
let contextTextImageNode: ASImageNode
|
||||
@ -117,6 +106,12 @@ private final class TabBarItemNode: ASDisplayNode {
|
||||
self.imageNode.displayWithoutProcessing = true
|
||||
self.imageNode.displaysAsynchronously = false
|
||||
self.imageNode.isAccessibilityElement = false
|
||||
|
||||
self.animationContainerNode = ASDisplayNode()
|
||||
|
||||
self.animationNode = AnimatedStickerNode()
|
||||
self.animationNode.automaticallyLoadFirstFrame = true
|
||||
|
||||
self.textImageNode = ASImageNode()
|
||||
self.textImageNode.isUserInteractionEnabled = false
|
||||
self.textImageNode.displayWithoutProcessing = true
|
||||
@ -142,6 +137,8 @@ private final class TabBarItemNode: ASDisplayNode {
|
||||
|
||||
self.extractedContainerNode.contentNode.addSubnode(self.textImageNode)
|
||||
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.contextImageNode)
|
||||
self.containerNode.addSubnode(self.extractedContainerNode)
|
||||
@ -451,7 +448,26 @@ class TabBarNode: ASDisplayNode {
|
||||
})
|
||||
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 (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 (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
|
||||
@ -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 (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)
|
||||
|
||||
node.animationNode.isHidden = true
|
||||
node.animationNode.visibility = false
|
||||
|
||||
node.textImageNode.image = textImage
|
||||
node.accessibilityLabel = item.item.title
|
||||
node.imageNode.image = image
|
||||
@ -496,7 +516,25 @@ class TabBarNode: ASDisplayNode {
|
||||
let previousTextImageSize = node.textImageNode.image?.size ?? CGSize()
|
||||
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 (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 (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
|
||||
@ -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 (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)
|
||||
|
||||
node.animationNode.stop()
|
||||
node.animationNode.isHidden = true
|
||||
node.animationNode.visibility = false
|
||||
|
||||
node.textImageNode.image = textImage
|
||||
node.accessibilityLabel = item.item.title
|
||||
node.imageNode.image = image
|
||||
@ -614,6 +657,14 @@ class TabBarNode: ASDisplayNode {
|
||||
node.contextImageNode.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 {
|
||||
container.appliedBadgeValue = container.badgeValue
|
||||
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 backgroundFrame: CGRect
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
transition.updateFrame(node: container.badgeContainerNode, frame: backgroundFrame)
|
||||
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.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 {
|
||||
let container = self.tabBarNodeContainers[closestNode.0]
|
||||
let previousSelectedIndex = self.selectedIndex
|
||||
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)
|
||||
}
|
||||
|
@ -142,7 +142,7 @@ public extension TelegramEngine {
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ func mediaBubbleCornerImage(incoming: Bool, radius: CGFloat, inset: CGFloat) ->
|
||||
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 topRightRadius: CGFloat
|
||||
let bottomLeftRadius: CGFloat
|
||||
@ -346,6 +346,12 @@ public func messageBubbleImage(maxCornerRadius: CGFloat, minCornerRadius: CGFloa
|
||||
if !onlyOutline {
|
||||
context.clip(to: CGRect(origin: CGPoint(), size: rawSize), mask: formImage.cgImage!)
|
||||
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 {
|
||||
context.setFillColor(strokeColor.cgColor)
|
||||
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) {
|
||||
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
|
||||
}
|
||||
|
||||
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:)))
|
||||
if lightnessColor.lightness > 0.705 {
|
||||
|
@ -250,6 +250,13 @@ public final class PrincipalThemeEssentialGraphics {
|
||||
let incomingKnockout = self.incomingBubbleGradientImage != 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 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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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: highlightKnockout, extendedEdges: true, alwaysFillColor: true)
|
||||
|
||||
self.checkBubbleFullImage = generateCheckImage(partial: false, 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/ReactionImageComponent:ReactionImageComponent",
|
||||
"//submodules/Translate:Translate",
|
||||
"//submodules/TabBarUI:TabBarUI",
|
||||
] + select({
|
||||
"@build_bazel_rules_apple//apple:ios_armv7": [],
|
||||
"@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 {
|
||||
Queue.mainQueue().async {
|
||||
let reply = UNTextInputNotificationAction(identifier: "reply", title: replyString, options: [], textInputButtonTitle: replyString, textInputPlaceholder: messagePlaceholderString)
|
||||
|
||||
|
||||
let unknownMessageCategory: UNNotificationCategory
|
||||
let replyMessageCategory: UNNotificationCategory
|
||||
let replyLegacyMessageCategory: UNNotificationCategory
|
||||
|
@ -32,7 +32,7 @@ private let inlineBotPrefixFont = Font.regular(14.0)
|
||||
private let inlineBotNameFont = nameFont
|
||||
|
||||
protocol GenericAnimatedStickerNode: ASDisplayNode {
|
||||
func setOverlayColor(_ color: UIColor?, animated: Bool)
|
||||
func setOverlayColor(_ color: UIColor?, replace: Bool, animated: Bool)
|
||||
|
||||
var currentFrameIndex: Int { get }
|
||||
func setFrameIndex(_ frameIndex: Int)
|
||||
@ -58,7 +58,7 @@ private class VideoStickerNode: ASDisplayNode, GenericAnimatedStickerNode {
|
||||
private var layerHolder: SampleBufferLayer?
|
||||
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 {
|
||||
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 {
|
||||
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) {
|
||||
|
@ -279,7 +279,7 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode {
|
||||
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))
|
||||
|
||||
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 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.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")
|
||||
}
|
||||
|
||||
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)
|
||||
|> map { presentationData, notificationsAuthorizationStatus, notificationsWarningSuppressed, suggestions, accountTabBarAvatar, accountTabBarAvatarBadge -> (String, UIImage?, UIImage?, String?) in
|
||||
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?, Bool) in
|
||||
let notificationsWarning = shouldDisplayNotificationsPermissionWarning(status: notificationsAuthorizationStatus, suppressed: notificationsWarningSuppressed)
|
||||
let phoneNumberWarning = suggestions.contains(.validatePhoneNumber)
|
||||
let passwordWarning = suggestions.contains(.validatePassword)
|
||||
@ -7179,14 +7179,15 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen {
|
||||
if accountTabBarAvatarBadge > 0 {
|
||||
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 {
|
||||
strongSelf.tabBarItem.title = title
|
||||
strongSelf.tabBarItem.image = image
|
||||
strongSelf.tabBarItem.selectedImage = selectedImage
|
||||
strongSelf.tabBarItem.animationName = isAvatar ? nil : "TabSettings"
|
||||
strongSelf.tabBarItem.badgeValue = badgeValue
|
||||
}
|
||||
})
|
||||
|
@ -14,6 +14,7 @@ import SettingsUI
|
||||
import AppBundle
|
||||
import DatePickerNode
|
||||
import DebugSettingsUI
|
||||
import TabBarUI
|
||||
|
||||
public final class TelegramRootController: NavigationController {
|
||||
private let context: AccountContext
|
||||
@ -64,7 +65,7 @@ public final class TelegramRootController: NavigationController {
|
||||
let previousTheme = strongSelf.presentationData.theme
|
||||
strongSelf.presentationData = presentationData
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -81,7 +82,7 @@ public final class TelegramRootController: NavigationController {
|
||||
}
|
||||
|
||||
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
|
||||
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 {
|
||||
@ -131,7 +132,7 @@ public final class TelegramRootController: NavigationController {
|
||||
}
|
||||
|
||||
public func updateRootControllers(showCallsTab: Bool) {
|
||||
guard let rootTabController = self.rootTabController else {
|
||||
guard let rootTabController = self.rootTabController as? TabBarControllerImpl else {
|
||||
return
|
||||
}
|
||||
var controllers: [ViewController] = []
|
||||
|
@ -728,44 +728,68 @@ public func convertMarkdownToAttributes(_ text: NSAttributedString) -> NSAttribu
|
||||
|
||||
var pre = match.range(at: 3)
|
||||
if pre.location != NSNotFound {
|
||||
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))
|
||||
var intersectsWithEntities = false
|
||||
text.enumerateAttributes(in: pre, options: [], using: { attributes, _, _ in
|
||||
for (key, _) in attributes {
|
||||
if key.rawValue.hasPrefix("Attribute__") {
|
||||
intersectsWithEntities = true
|
||||
}
|
||||
}
|
||||
})
|
||||
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)
|
||||
if pre.location != NSNotFound {
|
||||
let text = string.substring(with: pre)
|
||||
|
||||
let entity = string.substring(with: match.range(at: 7))
|
||||
let substring = string.substring(with: match.range(at: 6)) + text + string.substring(with: match.range(at: 9))
|
||||
|
||||
let textInputAttribute: NSAttributedString.Key?
|
||||
switch entity {
|
||||
case "`":
|
||||
textInputAttribute = ChatTextInputAttributes.monospace
|
||||
case "**":
|
||||
textInputAttribute = ChatTextInputAttributes.bold
|
||||
case "__":
|
||||
textInputAttribute = ChatTextInputAttributes.italic
|
||||
case "~~":
|
||||
textInputAttribute = ChatTextInputAttributes.strikethrough
|
||||
case "||":
|
||||
textInputAttribute = ChatTextInputAttributes.spoiler
|
||||
default:
|
||||
textInputAttribute = nil
|
||||
var intersectsWithEntities = false
|
||||
text.enumerateAttributes(in: pre, options: [], using: { attributes, _, _ in
|
||||
for (key, _) in attributes {
|
||||
if key.rawValue.hasPrefix("Attribute__") {
|
||||
intersectsWithEntities = true
|
||||
}
|
||||
}
|
||||
})
|
||||
if intersectsWithEntities {
|
||||
result.append(text.attributedSubstring(from: match.range(at: 0)))
|
||||
} else {
|
||||
let text = string.substring(with: pre)
|
||||
|
||||
let entity = string.substring(with: match.range(at: 7))
|
||||
let substring = string.substring(with: match.range(at: 6)) + text + string.substring(with: match.range(at: 9))
|
||||
|
||||
let textInputAttribute: NSAttributedString.Key?
|
||||
switch entity {
|
||||
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
|
||||
|
@ -59,7 +59,7 @@ public func translateText(context: AccountContext, text: String) {
|
||||
return
|
||||
}
|
||||
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()
|
||||
textView.text = text
|
||||
|
@ -48,7 +48,6 @@ NSInteger UITabBarItem_addSetBadgeListener(UITabBarItem * _Nonnull item, UITabBa
|
||||
- (NSInteger)addSetSelectedImageListener:(UINavigationItemSetImageListener _Nonnull)listener;
|
||||
- (void)removeSetSelectedImageListener:(NSInteger)key;
|
||||
|
||||
- (NSObject * _Nullable)userInfo;
|
||||
- (void)setUserInfo:(NSObject * _Nullable)userInfo;
|
||||
@property (nonatomic, strong) NSString * _Nullable animationName;
|
||||
|
||||
@end
|
||||
|
@ -16,7 +16,7 @@ static const void *setMultipleRightBarButtonItemsListenerKey = &setMultipleRight
|
||||
static const void *setBackBarButtonItemListenerBagKey = &setBackBarButtonItemListenerBagKey;
|
||||
static const void *setBadgeListenerBagKey = &setBadgeListenerBagKey;
|
||||
static const void *badgeKey = &badgeKey;
|
||||
static const void *userInfoKey = &userInfoKey;
|
||||
static const void *animationNameKey = &animationNameKey;
|
||||
|
||||
@implementation UINavigationItem (Proxy)
|
||||
|
||||
@ -402,12 +402,16 @@ NSInteger UITabBarItem_addSetBadgeListener(UITabBarItem *item, UITabBarItemSetBa
|
||||
[(NSBag *)[self associatedObjectForKey:setSelectedImageListenerBagKey] removeItem:key];
|
||||
}
|
||||
|
||||
- (NSObject * _Nullable)userInfo {
|
||||
return [self associatedObjectForKey:userInfoKey];
|
||||
- (void)setAnimationName:(NSString *)animationName {
|
||||
[self setAssociatedObject:animationName forKey:animationNameKey];
|
||||
|
||||
// [(NSBag *)[self associatedObjectForKey:setBadgeListenerBagKey] enumerateItems:^(UITabBarItemSetBadgeListener listener) {
|
||||
// listener(badge);
|
||||
// }];
|
||||
}
|
||||
|
||||
- (void)setUserInfo:(NSObject * _Nullable)userInfo {
|
||||
[self setAssociatedObject:userInfo forKey:userInfoKey];
|
||||
- (NSString *)animationName {
|
||||
return [self associatedObjectForKey:animationNameKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -432,9 +432,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
|
||||
var isReady: Signal<Bool, NoError> {
|
||||
return self._isReady.get()
|
||||
}
|
||||
|
||||
private var newYearNode: WallpaperNewYearNode?
|
||||
|
||||
|
||||
init(context: AccountContext, useSharedAnimationPhase: Bool) {
|
||||
self.context = context
|
||||
self.useSharedAnimationPhase = useSharedAnimationPhase
|
||||
@ -800,16 +798,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
|
||||
|
||||
if isFirstLayout && !self.frame.isEmpty {
|
||||
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) {
|
||||
@ -1720,47 +1709,3 @@ public func createWallpaperBackgroundNode(context: AccountContext, forChatDispla
|
||||
|
||||
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