Temp
This commit is contained in:
parent
6ee1edc8b0
commit
ebcd0557e5
76 changed files with 1352 additions and 1090 deletions
|
|
@ -16,7 +16,7 @@ private func generateIndefiniteActivityIndicatorImage(color: UIColor, diameter:
|
||||||
}
|
}
|
||||||
|
|
||||||
private func convertIndicatorColor(_ color: UIColor) -> UIColor {
|
private func convertIndicatorColor(_ color: UIColor) -> UIColor {
|
||||||
if color.isEqual(UIColor(rgb: 0x007aff)) {
|
if color.isEqual(UIColor(rgb: 0x007aff)) || color.isEqual(UIColor(rgb: 0x0088ff)) {
|
||||||
return .gray
|
return .gray
|
||||||
} else if color.isEqual(UIColor(rgb: 0x2ea6ff)) {
|
} else if color.isEqual(UIColor(rgb: 0x2ea6ff)) {
|
||||||
return .white
|
return .white
|
||||||
|
|
|
||||||
|
|
@ -461,7 +461,7 @@ final class BlobNode: ASDisplayNode {
|
||||||
|
|
||||||
if let backgroundView = self.backgroundView, let color = self.color {
|
if let backgroundView = self.backgroundView, let color = self.color {
|
||||||
let halfWidth = floor(self.bounds.width * self.minScale)
|
let halfWidth = floor(self.bounds.width * self.minScale)
|
||||||
backgroundView.update(size: CGSize(width: halfWidth, height: halfWidth), cornerRadius: halfWidth * 0.5, isDark: false, tintColor: color, transition: .immediate)
|
backgroundView.update(size: CGSize(width: halfWidth, height: halfWidth), cornerRadius: halfWidth * 0.5, isDark: false, tintColor: .init(kind: .custom, color: color), transition: .immediate)
|
||||||
backgroundView.frame = CGRect(origin: CGPoint(x: (self.bounds.width - halfWidth) * 0.5, y: (self.bounds.height - halfWidth) * 0.5), size: CGSize(width: halfWidth, height: halfWidth))
|
backgroundView.frame = CGRect(origin: CGPoint(x: (self.bounds.width - halfWidth) * 0.5, y: (self.bounds.height - halfWidth) * 0.5), size: CGSize(width: halfWidth, height: halfWidth))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,7 @@ swift_library(
|
||||||
"//submodules/TelegramUI/Components/Ads/AdsReportScreen",
|
"//submodules/TelegramUI/Components/Ads/AdsReportScreen",
|
||||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||||
"//submodules/TelegramUI/Components/AnimatedTextComponent",
|
"//submodules/TelegramUI/Components/AnimatedTextComponent",
|
||||||
|
"//submodules/TelegramUI/Components/EdgeEffect",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ import Postbox
|
||||||
import ChatListHeaderComponent
|
import ChatListHeaderComponent
|
||||||
import ActionPanelComponent
|
import ActionPanelComponent
|
||||||
import ChatFolderLinkPreviewScreen
|
import ChatFolderLinkPreviewScreen
|
||||||
|
import EdgeEffect
|
||||||
|
import ComponentDisplayAdapters
|
||||||
|
|
||||||
final class ChatListContainerItemNode: ASDisplayNode {
|
final class ChatListContainerItemNode: ASDisplayNode {
|
||||||
private final class TopPanelItem {
|
private final class TopPanelItem {
|
||||||
|
|
@ -37,6 +39,8 @@ final class ChatListContainerItemNode: ASDisplayNode {
|
||||||
|
|
||||||
private var floatingHeaderOffset: CGFloat?
|
private var floatingHeaderOffset: CGFloat?
|
||||||
|
|
||||||
|
private let edgeEffectView: EdgeEffectView
|
||||||
|
|
||||||
private(set) var emptyNode: ChatListEmptyNode?
|
private(set) var emptyNode: ChatListEmptyNode?
|
||||||
var emptyShimmerEffectNode: ChatListShimmerNode?
|
var emptyShimmerEffectNode: ChatListShimmerNode?
|
||||||
private var shimmerNodeOffset: CGFloat = 0.0
|
private var shimmerNodeOffset: CGFloat = 0.0
|
||||||
|
|
@ -74,9 +78,12 @@ final class ChatListContainerItemNode: ASDisplayNode {
|
||||||
self.listNode.scrollHeightTopInset = ChatListNavigationBar.searchScrollHeight + ChatListNavigationBar.storiesScrollHeight
|
self.listNode.scrollHeightTopInset = ChatListNavigationBar.searchScrollHeight + ChatListNavigationBar.storiesScrollHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.edgeEffectView = EdgeEffectView()
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.addSubnode(self.listNode)
|
self.addSubnode(self.listNode)
|
||||||
|
self.view.addSubview(self.edgeEffectView)
|
||||||
|
|
||||||
self.listNode.isEmptyUpdated = { [weak self] isEmptyState, _, transition in
|
self.listNode.isEmptyUpdated = { [weak self] isEmptyState, _, transition in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
|
|
@ -442,6 +449,11 @@ final class ChatListContainerItemNode: ASDisplayNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.layoutAdditionalPanels(transition: transition)
|
self.layoutAdditionalPanels(transition: transition)
|
||||||
|
|
||||||
|
let edgeEffectHeight: CGFloat = insets.bottom
|
||||||
|
let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - edgeEffectHeight), size: CGSize(width: size.width, height: edgeEffectHeight))
|
||||||
|
transition.updateFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
|
||||||
|
self.edgeEffectView.update(content: self.presentationData.theme.list.plainBackgroundColor, rect: edgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, containerSize: size, transition: ComponentTransition(transition))
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateScrollingOffset(navigationHeight: CGFloat, offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
func updateScrollingOffset(navigationHeight: CGFloat, offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import ChatFolderLinkPreviewScreen
|
||||||
import ChatListHeaderComponent
|
import ChatListHeaderComponent
|
||||||
import StoryPeerListComponent
|
import StoryPeerListComponent
|
||||||
import TelegramNotices
|
import TelegramNotices
|
||||||
|
import EdgeEffect
|
||||||
|
|
||||||
public enum ChatListContainerNodeFilter: Equatable {
|
public enum ChatListContainerNodeFilter: Equatable {
|
||||||
case all
|
case all
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ swift_library(
|
||||||
"//submodules/UndoUI",
|
"//submodules/UndoUI",
|
||||||
"//submodules/TelegramIntents",
|
"//submodules/TelegramIntents",
|
||||||
"//submodules/ContextUI",
|
"//submodules/ContextUI",
|
||||||
|
"//submodules/TelegramUI/Components/EdgeEffect",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import ChatListTitleView
|
||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ContactsUI
|
import ContactsUI
|
||||||
|
import EdgeEffect
|
||||||
|
|
||||||
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
|
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
|
||||||
let controller: ViewController
|
let controller: ViewController
|
||||||
|
|
@ -48,6 +49,7 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent
|
||||||
|
|
||||||
final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
||||||
let contactListNode: ContactListNode
|
let contactListNode: ContactListNode
|
||||||
|
private let edgeEffectView: EdgeEffectView
|
||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private(set) var searchDisplayController: SearchDisplayController?
|
private(set) var searchDisplayController: SearchDisplayController?
|
||||||
|
|
@ -116,6 +118,8 @@ final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
||||||
contextAction?(peer, node, gesture, location, isStories)
|
contextAction?(peer, node, gesture, location, isStories)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
self.edgeEffectView = EdgeEffectView()
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.setViewBlock({
|
self.setViewBlock({
|
||||||
|
|
@ -125,6 +129,7 @@ final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
||||||
self.backgroundColor = self.presentationData.theme.chatList.backgroundColor
|
self.backgroundColor = self.presentationData.theme.chatList.backgroundColor
|
||||||
|
|
||||||
self.addSubnode(self.contactListNode)
|
self.addSubnode(self.contactListNode)
|
||||||
|
self.view.addSubview(self.edgeEffectView)
|
||||||
|
|
||||||
self.presentationDataDisposable = (context.sharedContext.presentationData
|
self.presentationDataDisposable = (context.sharedContext.presentationData
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||||
|
|
@ -442,6 +447,11 @@ final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
||||||
|
|
||||||
self.contactListNode.frame = CGRect(origin: CGPoint(), size: layout.size)
|
self.contactListNode.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||||
|
|
||||||
|
let edgeEffectHeight: CGFloat = layout.intrinsicInsets.bottom
|
||||||
|
let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - edgeEffectHeight), size: CGSize(width: layout.size.width, height: edgeEffectHeight))
|
||||||
|
transition.updateFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
|
||||||
|
self.edgeEffectView.update(content: self.presentationData.theme.list.plainBackgroundColor, rect: edgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, containerSize: layout.size, transition: ComponentTransition(transition))
|
||||||
|
|
||||||
self.updateNavigationScrolling(transition: transition)
|
self.updateNavigationScrolling(transition: transition)
|
||||||
|
|
||||||
if let navigationBarComponentView = self.navigationBarView.view as? ChatListNavigationBar.View {
|
if let navigationBarComponentView = self.navigationBarView.view as? ChatListNavigationBar.View {
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ public class NavigationBackButtonNode: ASControlNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var color: UIColor = UIColor(rgb: 0x007aff) {
|
public var color: UIColor = UIColor(rgb: 0x0088ff) {
|
||||||
didSet {
|
didSet {
|
||||||
self.label.attributedText = NSAttributedString(string: self._text, attributes: self.attributesForCurrentState())
|
self.label.attributedText = NSAttributedString(string: self._text, attributes: self.attributesForCurrentState())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,7 @@ private final class NavigationButtonItemNode: ImmediateTextNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var color: UIColor = UIColor(rgb: 0x007aff) {
|
public var color: UIColor = UIColor(rgb: 0x0088ff) {
|
||||||
didSet {
|
didSet {
|
||||||
if let text = self._text {
|
if let text = self._text {
|
||||||
self.attributedText = NSAttributedString(string: text, attributes: self.attributesForCurrentState())
|
self.attributedText = NSAttributedString(string: text, attributes: self.attributesForCurrentState())
|
||||||
|
|
@ -347,7 +347,7 @@ public final class NavigationButtonNode: ContextControllerSourceNode {
|
||||||
public var pressed: (Int) -> () = { _ in }
|
public var pressed: (Int) -> () = { _ in }
|
||||||
public var highlightChanged: (Int, Bool) -> () = { _, _ in }
|
public var highlightChanged: (Int, Bool) -> () = { _, _ in }
|
||||||
|
|
||||||
public var color: UIColor = UIColor(rgb: 0x007aff) {
|
public var color: UIColor = UIColor(rgb: 0x0088ff) {
|
||||||
didSet {
|
didSet {
|
||||||
if !self.color.isEqual(oldValue) {
|
if !self.color.isEqual(oldValue) {
|
||||||
for node in self.nodes {
|
for node in self.nodes {
|
||||||
|
|
|
||||||
|
|
@ -13,16 +13,12 @@ public protocol TabBarController: ViewController {
|
||||||
var controllers: [ViewController] { get }
|
var controllers: [ViewController] { get }
|
||||||
var selectedIndex: Int { get set }
|
var selectedIndex: Int { get set }
|
||||||
|
|
||||||
var cameraItemAndAction: (item: UITabBarItem, action: () -> Void)? { get set }
|
|
||||||
|
|
||||||
func setControllers(_ controllers: [ViewController], selectedIndex: Int?)
|
func setControllers(_ controllers: [ViewController], selectedIndex: Int?)
|
||||||
|
|
||||||
func updateBackgroundAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition)
|
func updateBackgroundAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition)
|
||||||
|
|
||||||
func viewForCameraItem() -> UIView?
|
|
||||||
func frameForControllerTab(controller: ViewController) -> CGRect?
|
func frameForControllerTab(controller: ViewController) -> CGRect?
|
||||||
func isPointInsideContentArea(point: CGPoint) -> Bool
|
func isPointInsideContentArea(point: CGPoint) -> Bool
|
||||||
func sourceNodesForController(at index: Int) -> [ASDisplayNode]?
|
|
||||||
|
|
||||||
func updateIsTabBarEnabled(_ value: Bool, transition: ContainedViewLayoutTransition)
|
func updateIsTabBarEnabled(_ value: Bool, transition: ContainedViewLayoutTransition)
|
||||||
func updateIsTabBarHidden(_ value: Bool, transition: ContainedViewLayoutTransition)
|
func updateIsTabBarHidden(_ value: Bool, transition: ContainedViewLayoutTransition)
|
||||||
|
|
|
||||||
|
|
@ -193,11 +193,7 @@ public final class WindowHostView {
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate var onScreenNavigationHeight: CGFloat? {
|
fileprivate var onScreenNavigationHeight: CGFloat? {
|
||||||
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
return self.eventView.safeAreaInsets.bottom.isLessThanOrEqualTo(0.0) ? nil : self.eventView.safeAreaInsets.bottom
|
||||||
return self.eventView.safeAreaInsets.bottom.isLessThanOrEqualTo(0.0) ? nil : self.eventView.safeAreaInsets.bottom
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -544,7 +544,7 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
|
||||||
self.textView.frameColor = nil
|
self.textView.frameColor = nil
|
||||||
case .filled:
|
case .filled:
|
||||||
self.textView.textColor = color.lightness > 0.99 ? UIColor.black : UIColor.white
|
self.textView.textColor = color.lightness > 0.99 ? UIColor.black : UIColor.white
|
||||||
cursorColor = color.lightness > 0.99 ? UIColor(rgb: 0x007aff) : UIColor.white
|
cursorColor = color.lightness > 0.99 ? UIColor(rgb: 0x0088ff) : UIColor.white
|
||||||
self.textView.strokeColor = nil
|
self.textView.strokeColor = nil
|
||||||
self.textView.frameColor = color
|
self.textView.frameColor = color
|
||||||
case .semi:
|
case .semi:
|
||||||
|
|
|
||||||
|
|
@ -571,7 +571,7 @@ private func galleryEntriesForMessageHistoryEntries(_ entries: [MessageHistoryEn
|
||||||
|
|
||||||
public class GalleryController: ViewController, StandalonePresentableController, KeyShortcutResponder, GalleryControllerProtocol {
|
public class GalleryController: ViewController, StandalonePresentableController, KeyShortcutResponder, GalleryControllerProtocol {
|
||||||
public static let darkNavigationTheme = NavigationBarTheme(buttonColor: .white, disabledButtonColor: UIColor(rgb: 0x525252), primaryTextColor: .white, backgroundColor: UIColor(white: 0.0, alpha: 0.6), enableBackgroundBlur: false, separatorColor: UIColor(white: 0.0, alpha: 0.8), badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear)
|
public static let darkNavigationTheme = NavigationBarTheme(buttonColor: .white, disabledButtonColor: UIColor(rgb: 0x525252), primaryTextColor: .white, backgroundColor: UIColor(white: 0.0, alpha: 0.6), enableBackgroundBlur: false, separatorColor: UIColor(white: 0.0, alpha: 0.8), badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear)
|
||||||
public static let lightNavigationTheme = NavigationBarTheme(buttonColor: UIColor(rgb: 0x007aff), disabledButtonColor: UIColor(rgb: 0xd0d0d0), primaryTextColor: .black, backgroundColor: UIColor(red: 0.968626451, green: 0.968626451, blue: 0.968626451, alpha: 1.0), enableBackgroundBlur: false, separatorColor: UIColor(red: 0.6953125, green: 0.6953125, blue: 0.6953125, alpha: 1.0), badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear)
|
public static let lightNavigationTheme = NavigationBarTheme(buttonColor: UIColor(rgb: 0x0088ff), disabledButtonColor: UIColor(rgb: 0xd0d0d0), primaryTextColor: .black, backgroundColor: UIColor(red: 0.968626451, green: 0.968626451, blue: 0.968626451, alpha: 1.0), enableBackgroundBlur: false, separatorColor: UIColor(red: 0.6953125, green: 0.6953125, blue: 0.6953125, alpha: 1.0), badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear)
|
||||||
|
|
||||||
private var galleryNode: GalleryControllerNode {
|
private var galleryNode: GalleryControllerNode {
|
||||||
return self.displayNode as! GalleryControllerNode
|
return self.displayNode as! GalleryControllerNode
|
||||||
|
|
|
||||||
|
|
@ -776,7 +776,7 @@ final class SettingsHeaderButton: HighlightableButtonNode {
|
||||||
component: AnyComponent(BadgeComponent(
|
component: AnyComponent(BadgeComponent(
|
||||||
text: badgeText,
|
text: badgeText,
|
||||||
font: self.badgeFont,
|
font: self.badgeFont,
|
||||||
cornerRadius: 3.0,
|
cornerRadius: .custom(3.0),
|
||||||
insets: UIEdgeInsets(top: 1.33, left: 1.66, bottom: 1.33, right: 1.66),
|
insets: UIEdgeInsets(top: 1.33, left: 1.66, bottom: 1.33, right: 1.66),
|
||||||
outerInsets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0)
|
outerInsets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0)
|
||||||
)),
|
)),
|
||||||
|
|
@ -818,7 +818,7 @@ final class SettingsHeaderButton: HighlightableButtonNode {
|
||||||
component: AnyComponent(BadgeComponent(
|
component: AnyComponent(BadgeComponent(
|
||||||
text: badgeText,
|
text: badgeText,
|
||||||
font: self.badgeFont,
|
font: self.badgeFont,
|
||||||
cornerRadius: 3.0,
|
cornerRadius: .custom(3.0),
|
||||||
insets: UIEdgeInsets(top: 1.33, left: 1.66, bottom: 1.33, right: 1.66),
|
insets: UIEdgeInsets(top: 1.33, left: 1.66, bottom: 1.33, right: 1.66),
|
||||||
outerInsets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0)
|
outerInsets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0)
|
||||||
)),
|
)),
|
||||||
|
|
|
||||||
|
|
@ -71,10 +71,10 @@ class ChartStackSection: UIView, ChartThemeContainer {
|
||||||
backButton.addTarget(self, action: #selector(self.didTapBackButton), for: .touchUpInside)
|
backButton.addTarget(self, action: #selector(self.didTapBackButton), for: .touchUpInside)
|
||||||
backButton.setTitle("Zoom Out", for: .normal)
|
backButton.setTitle("Zoom Out", for: .normal)
|
||||||
backButton.titleLabel?.font = UIFont.systemFont(ofSize: 14, weight: .regular)
|
backButton.titleLabel?.font = UIFont.systemFont(ofSize: 14, weight: .regular)
|
||||||
backButton.setTitleColor(UIColor(rgb: 0x007aff), for: .normal)
|
backButton.setTitleColor(UIColor(rgb: 0x0088ff), for: .normal)
|
||||||
backButton.setImage(UIImage(bundleImageName: "Chart/arrow_left"), for: .normal)
|
backButton.setImage(UIImage(bundleImageName: "Chart/arrow_left"), for: .normal)
|
||||||
backButton.imageEdgeInsets = UIEdgeInsets(top: 0.0, left: 6.0, bottom: 0.0, right: 3.0)
|
backButton.imageEdgeInsets = UIEdgeInsets(top: 0.0, left: 6.0, bottom: 0.0, right: 3.0)
|
||||||
backButton.imageView?.tintColor = UIColor(rgb: 0x007aff)
|
backButton.imageView?.tintColor = UIColor(rgb: 0x0088ff)
|
||||||
backButton.adjustsImageWhenHighlighted = false
|
backButton.adjustsImageWhenHighlighted = false
|
||||||
|
|
||||||
backButton.setVisible(false, animated: false)
|
backButton.setVisible(false, animated: false)
|
||||||
|
|
|
||||||
|
|
@ -911,7 +911,7 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||||
if let current = self.linkHighlightingNode {
|
if let current = self.linkHighlightingNode {
|
||||||
linkHighlightingNode = current
|
linkHighlightingNode = current
|
||||||
} else {
|
} else {
|
||||||
let highlightColor = self.theme?.linkHighlightColor ?? UIColor(rgb: 0x007aff).withAlphaComponent(0.4)
|
let highlightColor = self.theme?.linkHighlightColor ?? UIColor(rgb: 0x0088ff).withAlphaComponent(0.4)
|
||||||
linkHighlightingNode = LinkHighlightingNode(color: highlightColor)
|
linkHighlightingNode = LinkHighlightingNode(color: highlightColor)
|
||||||
linkHighlightingNode.isUserInteractionEnabled = false
|
linkHighlightingNode.isUserInteractionEnabled = false
|
||||||
self.linkHighlightingNode = linkHighlightingNode
|
self.linkHighlightingNode = linkHighlightingNode
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ private let lightTheme = InstantPageSettingsItemTheme(
|
||||||
separatorColor: UIColor(rgb: 0xc8c7cc),
|
separatorColor: UIColor(rgb: 0xc8c7cc),
|
||||||
primaryColor: .black,
|
primaryColor: .black,
|
||||||
secondaryColor: UIColor(rgb: 0xa8a8a8),
|
secondaryColor: UIColor(rgb: 0xa8a8a8),
|
||||||
accentColor: UIColor(rgb: 0x007aff)
|
accentColor: UIColor(rgb: 0x0088ff)
|
||||||
)
|
)
|
||||||
|
|
||||||
private let sepiaTheme = InstantPageSettingsItemTheme(
|
private let sepiaTheme = InstantPageSettingsItemTheme(
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ final class InstantPageSettingsThemeItemNode: InstantPageSettingsItemNode {
|
||||||
selectedIndex = 3
|
selectedIndex = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
let selectionColor = UIColor(rgb: 0x007aff)
|
let selectionColor = UIColor(rgb: 0x0088ff)
|
||||||
self.themeNodes = [
|
self.themeNodes = [
|
||||||
InstantPageSettingsThemeSelectorNode(color: .white, edgeColor: (selectedIndex == 1 || selectedIndex == 2) ? UIColor.lightGray : UIColor.white, selectionColor: selectionColor),
|
InstantPageSettingsThemeSelectorNode(color: .white, edgeColor: (selectedIndex == 1 || selectedIndex == 2) ? UIColor.lightGray : UIColor.white, selectionColor: selectionColor),
|
||||||
InstantPageSettingsThemeSelectorNode(color: UIColor(rgb: 0xcbb98e), edgeColor: UIColor(rgb: 0xcbb98e), selectionColor: selectionColor),
|
InstantPageSettingsThemeSelectorNode(color: UIColor(rgb: 0xcbb98e), edgeColor: UIColor(rgb: 0xcbb98e), selectionColor: selectionColor),
|
||||||
|
|
|
||||||
|
|
@ -153,15 +153,15 @@ private let lightTheme = InstantPageTheme(
|
||||||
),
|
),
|
||||||
serif: false,
|
serif: false,
|
||||||
codeBlockBackgroundColor: UIColor(rgb: 0xf5f8fc),
|
codeBlockBackgroundColor: UIColor(rgb: 0xf5f8fc),
|
||||||
linkColor: UIColor(rgb: 0x007aff),
|
linkColor: UIColor(rgb: 0x0088ff),
|
||||||
textHighlightColor: UIColor(rgb: 0, alpha: 0.12),
|
textHighlightColor: UIColor(rgb: 0, alpha: 0.12),
|
||||||
linkHighlightColor: UIColor(rgb: 0x007aff, alpha: 0.07),
|
linkHighlightColor: UIColor(rgb: 0x0088ff, alpha: 0.07),
|
||||||
markerColor: UIColor(rgb: 0xfef3bc),
|
markerColor: UIColor(rgb: 0xfef3bc),
|
||||||
panelBackgroundColor: UIColor(rgb: 0xf3f4f5),
|
panelBackgroundColor: UIColor(rgb: 0xf3f4f5),
|
||||||
panelHighlightedBackgroundColor: UIColor(rgb: 0xe7e7e7),
|
panelHighlightedBackgroundColor: UIColor(rgb: 0xe7e7e7),
|
||||||
panelPrimaryColor: .black,
|
panelPrimaryColor: .black,
|
||||||
panelSecondaryColor: UIColor(rgb: 0x79828b),
|
panelSecondaryColor: UIColor(rgb: 0x79828b),
|
||||||
panelAccentColor: UIColor(rgb: 0x007aff),
|
panelAccentColor: UIColor(rgb: 0x0088ff),
|
||||||
tableBorderColor: UIColor(rgb: 0xe2e2e2),
|
tableBorderColor: UIColor(rgb: 0xe2e2e2),
|
||||||
tableHeaderColor: UIColor(rgb: 0xf4f4f4),
|
tableHeaderColor: UIColor(rgb: 0xf4f4f4),
|
||||||
controlColor: UIColor(rgb: 0xc7c7cd),
|
controlColor: UIColor(rgb: 0xc7c7cd),
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@
|
||||||
int32_t hex = 0x29c519;
|
int32_t hex = 0x29c519;
|
||||||
UIColor *greenColor = [[UIColor alloc] initWithRed:(((hex >> 16) & 0xff) / 255.0f) green:(((hex >> 8) & 0xff) / 255.0f) blue:(((hex) & 0xff) / 255.0f) alpha:1.0f];
|
UIColor *greenColor = [[UIColor alloc] initWithRed:(((hex >> 16) & 0xff) / 255.0f) green:(((hex >> 8) & 0xff) / 255.0f) blue:(((hex) & 0xff) / 255.0f) alpha:1.0f];
|
||||||
|
|
||||||
hex = 0x007aff;
|
hex = 0x0088ff;
|
||||||
UIColor *blueColor = [[UIColor alloc] initWithRed:(((hex >> 16) & 0xff) / 255.0f) green:(((hex >> 8) & 0xff) / 255.0f) blue:(((hex) & 0xff) / 255.0f) alpha:1.0f];
|
UIColor *blueColor = [[UIColor alloc] initWithRed:(((hex >> 16) & 0xff) / 255.0f) green:(((hex >> 8) & 0xff) / 255.0f) blue:(((hex) & 0xff) / 255.0f) alpha:1.0f];
|
||||||
|
|
||||||
hex = 0xcacacf;
|
hex = 0xcacacf;
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ UIColor *TGAccentColor()
|
||||||
static dispatch_once_t onceToken;
|
static dispatch_once_t onceToken;
|
||||||
dispatch_once(&onceToken, ^
|
dispatch_once(&onceToken, ^
|
||||||
{
|
{
|
||||||
color = TGColorWithHex(0x007aff);
|
color = TGColorWithHex(0x0088ff);
|
||||||
});
|
});
|
||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -466,7 +466,7 @@
|
||||||
_saveCoverButton.clipsToBounds = true;
|
_saveCoverButton.clipsToBounds = true;
|
||||||
_saveCoverButton.layer.cornerRadius = 10.0;
|
_saveCoverButton.layer.cornerRadius = 10.0;
|
||||||
_saveCoverButton.hidden = true;
|
_saveCoverButton.hidden = true;
|
||||||
[_saveCoverButton setBackgroundColor:UIColorRGB(0x007aff)];
|
[_saveCoverButton setBackgroundColor:UIColorRGB(0x0088ff)];
|
||||||
_saveCoverButton.titleLabel.font = TGBoldSystemFontOfSize(17.0);
|
_saveCoverButton.titleLabel.font = TGBoldSystemFontOfSize(17.0);
|
||||||
[_saveCoverButton setTitle:TGLocalized(@"Media.SaveCover") forState:UIControlStateNormal];
|
[_saveCoverButton setTitle:TGLocalized(@"Media.SaveCover") forState:UIControlStateNormal];
|
||||||
[_saveCoverButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
[_saveCoverButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
||||||
|
|
|
||||||
|
|
@ -719,7 +719,7 @@ private class SendStarsButtonView: HighlightTrackingButton, TGPhotoSendStarsButt
|
||||||
let backgroundSize = CGSize(width: width - 11.0, height: 33.0)
|
let backgroundSize = CGSize(width: width - 11.0, height: 33.0)
|
||||||
transition.updateFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((width - backgroundSize.width) / 2.0), y: floorToScreenPixels((buttonSize.height - backgroundSize.height) / 2.0)), size: backgroundSize))
|
transition.updateFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((width - backgroundSize.width) / 2.0), y: floorToScreenPixels((buttonSize.height - backgroundSize.height) / 2.0)), size: backgroundSize))
|
||||||
self.backgroundView.layer.cornerRadius = backgroundSize.height / 2.0
|
self.backgroundView.layer.cornerRadius = backgroundSize.height / 2.0
|
||||||
self.backgroundView.backgroundColor = UIColor(rgb: 0x007aff)
|
self.backgroundView.backgroundColor = UIColor(rgb: 0x0088ff)
|
||||||
|
|
||||||
return buttonSize;
|
return buttonSize;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ private func generateHeadingArrowImage() -> UIImage? {
|
||||||
context.clip()
|
context.clip()
|
||||||
|
|
||||||
var locations: [CGFloat] = [0.0, 0.4, 1.0]
|
var locations: [CGFloat] = [0.0, 0.4, 1.0]
|
||||||
let colors: [CGColor] = [UIColor(rgb: 0x007aff, alpha: 0.5).cgColor, UIColor(rgb: 0x007aff, alpha: 0.3).cgColor, UIColor(rgb: 0x007aff, alpha: 0.0).cgColor]
|
let colors: [CGColor] = [UIColor(rgb: 0x0088ff, alpha: 0.5).cgColor, UIColor(rgb: 0x0088ff, alpha: 0.3).cgColor, UIColor(rgb: 0x0088ff, alpha: 0.0).cgColor]
|
||||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||||
|
|
||||||
|
|
@ -184,7 +184,7 @@ public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
|
||||||
let pulseImage: UIImage?
|
let pulseImage: UIImage?
|
||||||
let arrowImage: UIImage?
|
let arrowImage: UIImage?
|
||||||
if hasPulse {
|
if hasPulse {
|
||||||
pulseImage = currentPulseImage ?? generateFilledCircleImage(diameter: 120.0, color: UIColor(rgb: 0x007aff, alpha: 0.27))
|
pulseImage = currentPulseImage ?? generateFilledCircleImage(diameter: 120.0, color: UIColor(rgb: 0x0088ff, alpha: 0.27))
|
||||||
} else {
|
} else {
|
||||||
pulseImage = nil
|
pulseImage = nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,7 @@ public class LocationPinAnnotationView: MKAnnotationView {
|
||||||
|
|
||||||
self.pulseNode = ASImageNode()
|
self.pulseNode = ASImageNode()
|
||||||
self.pulseNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 120.0, height: 120.0))
|
self.pulseNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 120.0, height: 120.0))
|
||||||
self.pulseNode.image = generateFilledCircleImage(diameter: 120.0, color: UIColor(rgb: 0x007aff, alpha: 0.27))
|
self.pulseNode.image = generateFilledCircleImage(diameter: 120.0, color: UIColor(rgb: 0x0088ff, alpha: 0.27))
|
||||||
self.pulseNode.isHidden = true
|
self.pulseNode.isHidden = true
|
||||||
|
|
||||||
self.arrowNode = ASImageNode()
|
self.arrowNode = ASImageNode()
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ func generateHeadingArrowImage() -> UIImage? {
|
||||||
context.clip()
|
context.clip()
|
||||||
|
|
||||||
var locations: [CGFloat] = [0.0, 0.4, 1.0]
|
var locations: [CGFloat] = [0.0, 0.4, 1.0]
|
||||||
let colors: [CGColor] = [UIColor(rgb: 0x007aff, alpha: 0.5).cgColor, UIColor(rgb: 0x007aff, alpha: 0.3).cgColor, UIColor(rgb: 0x007aff, alpha: 0.0).cgColor]
|
let colors: [CGColor] = [UIColor(rgb: 0x0088ff, alpha: 0.5).cgColor, UIColor(rgb: 0x0088ff, alpha: 0.3).cgColor, UIColor(rgb: 0x0088ff, alpha: 0.0).cgColor]
|
||||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -310,7 +310,7 @@ private final class BusinessListComponent: CombinedComponent {
|
||||||
UIColor(rgb: 0x9b4fed),
|
UIColor(rgb: 0x9b4fed),
|
||||||
UIColor(rgb: 0x8958ff),
|
UIColor(rgb: 0x8958ff),
|
||||||
UIColor(rgb: 0x676bff),
|
UIColor(rgb: 0x676bff),
|
||||||
UIColor(rgb: 0x007aff)
|
UIColor(rgb: 0x0088ff)
|
||||||
]
|
]
|
||||||
|
|
||||||
let titleColor = theme.list.itemPrimaryTextColor
|
let titleColor = theme.list.itemPrimaryTextColor
|
||||||
|
|
|
||||||
|
|
@ -2306,7 +2306,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
||||||
UIColor(rgb: 0x9b4fed),
|
UIColor(rgb: 0x9b4fed),
|
||||||
UIColor(rgb: 0x8958ff),
|
UIColor(rgb: 0x8958ff),
|
||||||
UIColor(rgb: 0x676bff),
|
UIColor(rgb: 0x676bff),
|
||||||
UIColor(rgb: 0x007aff)
|
UIColor(rgb: 0x0088ff)
|
||||||
]
|
]
|
||||||
|
|
||||||
var i = 0
|
var i = 0
|
||||||
|
|
|
||||||
|
|
@ -313,7 +313,7 @@ private final class StoriesListComponent: CombinedComponent {
|
||||||
let strings = context.component.context.sharedContext.currentPresentationData.with { $0 }.strings
|
let strings = context.component.context.sharedContext.currentPresentationData.with { $0 }.strings
|
||||||
|
|
||||||
let colors = [
|
let colors = [
|
||||||
UIColor(rgb: 0x007aff),
|
UIColor(rgb: 0x0088ff),
|
||||||
UIColor(rgb: 0x798aff),
|
UIColor(rgb: 0x798aff),
|
||||||
UIColor(rgb: 0xac64f3),
|
UIColor(rgb: 0xac64f3),
|
||||||
UIColor(rgb: 0xc456ae),
|
UIColor(rgb: 0xc456ae),
|
||||||
|
|
|
||||||
|
|
@ -1,105 +0,0 @@
|
||||||
import Foundation
|
|
||||||
import UIKit
|
|
||||||
import Display
|
|
||||||
import AsyncDisplayKit
|
|
||||||
import Postbox
|
|
||||||
import TelegramCore
|
|
||||||
import SwiftSignalKit
|
|
||||||
import TelegramPresentationData
|
|
||||||
import AccountContext
|
|
||||||
|
|
||||||
public final class TabBarAccountSwitchController: ViewController {
|
|
||||||
private var controllerNode: TabBarAccountSwitchControllerNode {
|
|
||||||
return self.displayNode as! TabBarAccountSwitchControllerNode
|
|
||||||
}
|
|
||||||
|
|
||||||
private let _ready = Promise<Bool>(true)
|
|
||||||
override public var ready: Promise<Bool> {
|
|
||||||
return self._ready
|
|
||||||
}
|
|
||||||
|
|
||||||
private let sharedContext: SharedAccountContext
|
|
||||||
private let accounts: (primary: (Account, Peer), other: [(Account, Peer, Int32)])
|
|
||||||
private let canAddAccounts: Bool
|
|
||||||
private let switchToAccount: (AccountRecordId) -> Void
|
|
||||||
private let addAccount: () -> Void
|
|
||||||
private let sourceNodes: [ASDisplayNode]
|
|
||||||
|
|
||||||
private var presentationData: PresentationData
|
|
||||||
private var didPlayPresentationAnimation = false
|
|
||||||
private var changedAccount = false
|
|
||||||
|
|
||||||
private let hapticFeedback = HapticFeedback()
|
|
||||||
|
|
||||||
public init(sharedContext: SharedAccountContext, accounts: (primary: (Account, Peer), other: [(Account, Peer, Int32)]), canAddAccounts: Bool, switchToAccount: @escaping (AccountRecordId) -> Void, addAccount: @escaping () -> Void, sourceNodes: [ASDisplayNode]) {
|
|
||||||
self.sharedContext = sharedContext
|
|
||||||
self.accounts = accounts
|
|
||||||
self.canAddAccounts = canAddAccounts
|
|
||||||
self.switchToAccount = switchToAccount
|
|
||||||
self.addAccount = addAccount
|
|
||||||
self.sourceNodes = sourceNodes
|
|
||||||
|
|
||||||
self.presentationData = sharedContext.currentPresentationData.with { $0 }
|
|
||||||
|
|
||||||
super.init(navigationBarPresentationData: nil)
|
|
||||||
|
|
||||||
self.statusBar.statusBarStyle = .Ignore
|
|
||||||
self.statusBar.ignoreInCall = true
|
|
||||||
|
|
||||||
self.lockOrientation = true
|
|
||||||
}
|
|
||||||
|
|
||||||
required public init(coder aDecoder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
}
|
|
||||||
|
|
||||||
override public func loadDisplayNode() {
|
|
||||||
self.displayNode = TabBarAccountSwitchControllerNode(sharedContext: self.sharedContext, accounts: self.accounts, presentationData: self.presentationData, canAddAccounts: self.canAddAccounts, switchToAccount: { [weak self] id in
|
|
||||||
guard let strongSelf = self, !strongSelf.changedAccount else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
strongSelf.changedAccount = true
|
|
||||||
strongSelf.switchToAccount(id)
|
|
||||||
}, addAccount: { [weak self] in
|
|
||||||
guard let strongSelf = self, !strongSelf.changedAccount else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
strongSelf.addAccount()
|
|
||||||
}, cancel: { [weak self] in
|
|
||||||
self?.dismiss()
|
|
||||||
}, sourceNodes: self.sourceNodes)
|
|
||||||
self.displayNodeDidLoad()
|
|
||||||
}
|
|
||||||
|
|
||||||
override public func viewDidAppear(_ animated: Bool) {
|
|
||||||
super.viewDidAppear(animated)
|
|
||||||
|
|
||||||
if !self.didPlayPresentationAnimation {
|
|
||||||
self.didPlayPresentationAnimation = true
|
|
||||||
|
|
||||||
self.hapticFeedback.impact()
|
|
||||||
self.controllerNode.animateIn()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
|
||||||
super.containerLayoutUpdated(layout, transition: transition)
|
|
||||||
|
|
||||||
self.controllerNode.containerLayoutUpdated(layout, transition: transition)
|
|
||||||
}
|
|
||||||
|
|
||||||
override public func dismiss(completion: (() -> Void)? = nil) {
|
|
||||||
self.changedAccount = false
|
|
||||||
self.dismiss(sourceNodes: [])
|
|
||||||
}
|
|
||||||
|
|
||||||
public func dismiss(sourceNodes: [ASDisplayNode]) {
|
|
||||||
self.controllerNode.animateOut(sourceNodes: sourceNodes, changedAccount: self.changedAccount, completion: { [weak self] in
|
|
||||||
self?.didPlayPresentationAnimation = false
|
|
||||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,568 +0,0 @@
|
||||||
import Foundation
|
|
||||||
import UIKit
|
|
||||||
import AsyncDisplayKit
|
|
||||||
import Display
|
|
||||||
import Postbox
|
|
||||||
import TelegramCore
|
|
||||||
import TelegramPresentationData
|
|
||||||
import AvatarNode
|
|
||||||
import AccountContext
|
|
||||||
import LocalizedPeerData
|
|
||||||
|
|
||||||
private let animationDurationFactor: Double = 1.0
|
|
||||||
private let avatarFont = avatarPlaceholderFont(size: 16.0)
|
|
||||||
|
|
||||||
private protocol AbstractSwitchAccountItemNode {
|
|
||||||
func updateLayout(maxWidth: CGFloat) -> (CGFloat, CGFloat, (CGFloat) -> Void)
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class AddAccountItemNode: ASDisplayNode, AbstractSwitchAccountItemNode {
|
|
||||||
private let action: () -> Void
|
|
||||||
|
|
||||||
private let separatorNode: ASDisplayNode
|
|
||||||
private let highlightedBackgroundNode: ASDisplayNode
|
|
||||||
private let buttonNode: HighlightTrackingButtonNode
|
|
||||||
private let plusNode: ASImageNode
|
|
||||||
private let titleNode: ImmediateTextNode
|
|
||||||
|
|
||||||
init(displaySeparator: Bool, presentationData: PresentationData, action: @escaping () -> Void) {
|
|
||||||
self.action = action
|
|
||||||
|
|
||||||
self.separatorNode = ASDisplayNode()
|
|
||||||
self.separatorNode.backgroundColor = presentationData.theme.actionSheet.opaqueItemSeparatorColor
|
|
||||||
self.separatorNode.isHidden = !displaySeparator
|
|
||||||
|
|
||||||
self.highlightedBackgroundNode = ASDisplayNode()
|
|
||||||
self.highlightedBackgroundNode.backgroundColor = presentationData.theme.actionSheet.opaqueItemHighlightedBackgroundColor
|
|
||||||
self.highlightedBackgroundNode.alpha = 0.0
|
|
||||||
|
|
||||||
self.buttonNode = HighlightTrackingButtonNode()
|
|
||||||
|
|
||||||
self.titleNode = ImmediateTextNode()
|
|
||||||
self.titleNode.maximumNumberOfLines = 1
|
|
||||||
self.titleNode.attributedText = NSAttributedString(string: presentationData.strings.Settings_AddAccount, font: Font.regular(17.0), textColor: presentationData.theme.actionSheet.primaryTextColor)
|
|
||||||
|
|
||||||
self.plusNode = ASImageNode()
|
|
||||||
self.plusNode.image = generateItemListPlusIcon(presentationData.theme.actionSheet.primaryTextColor)
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
self.addSubnode(self.separatorNode)
|
|
||||||
self.addSubnode(self.highlightedBackgroundNode)
|
|
||||||
self.addSubnode(self.titleNode)
|
|
||||||
self.addSubnode(self.plusNode)
|
|
||||||
self.addSubnode(self.buttonNode)
|
|
||||||
|
|
||||||
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
|
||||||
self.buttonNode.highligthedChanged = { [weak self] highlighted in
|
|
||||||
if let strongSelf = self {
|
|
||||||
if highlighted {
|
|
||||||
strongSelf.highlightedBackgroundNode.layer.removeAnimation(forKey: "opacity")
|
|
||||||
strongSelf.highlightedBackgroundNode.alpha = 1.0
|
|
||||||
} else {
|
|
||||||
strongSelf.highlightedBackgroundNode.alpha = 0.0
|
|
||||||
strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateLayout(maxWidth: CGFloat) -> (CGFloat, CGFloat, (CGFloat) -> Void) {
|
|
||||||
let leftInset: CGFloat = 56.0
|
|
||||||
let rightInset: CGFloat = 10.0
|
|
||||||
let titleSize = self.titleNode.updateLayout(CGSize(width: maxWidth - leftInset - rightInset, height: .greatestFiniteMagnitude))
|
|
||||||
let height: CGFloat = 61.0
|
|
||||||
|
|
||||||
return (titleSize.width + leftInset + rightInset, height, { width in
|
|
||||||
self.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - titleSize.height) / 2.0)), size: titleSize)
|
|
||||||
|
|
||||||
if let image = self.plusNode.image {
|
|
||||||
self.plusNode.frame = CGRect(origin: CGPoint(x: floor((leftInset - image.size.width) / 2.0), y: floor((height - image.size.height) / 2.0)), size: image.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: height - UIScreenPixel), size: CGSize(width: width, height: UIScreenPixel))
|
|
||||||
self.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: height))
|
|
||||||
self.buttonNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: height))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func buttonPressed() {
|
|
||||||
self.action()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class SwitchAccountItemNode: ASDisplayNode, AbstractSwitchAccountItemNode {
|
|
||||||
private let context: AccountContext
|
|
||||||
private let peer: Peer
|
|
||||||
private let isCurrent: Bool
|
|
||||||
private let unreadCount: Int32
|
|
||||||
private let presentationData: PresentationData
|
|
||||||
private let action: () -> Void
|
|
||||||
|
|
||||||
private let separatorNode: ASDisplayNode
|
|
||||||
private let highlightedBackgroundNode: ASDisplayNode
|
|
||||||
private let buttonNode: HighlightTrackingButtonNode
|
|
||||||
private let avatarNode: AvatarNode
|
|
||||||
private let titleNode: ImmediateTextNode
|
|
||||||
private let checkNode: ASImageNode
|
|
||||||
|
|
||||||
private let badgeBackgroundNode: ASImageNode
|
|
||||||
private let badgeTitleNode: ImmediateTextNode
|
|
||||||
|
|
||||||
init(context: AccountContext, peer: Peer, isCurrent: Bool, unreadCount: Int32, displaySeparator: Bool, presentationData: PresentationData, action: @escaping () -> Void) {
|
|
||||||
self.context = context
|
|
||||||
self.peer = peer
|
|
||||||
self.isCurrent = isCurrent
|
|
||||||
self.unreadCount = unreadCount
|
|
||||||
self.presentationData = presentationData
|
|
||||||
self.action = action
|
|
||||||
|
|
||||||
self.separatorNode = ASDisplayNode()
|
|
||||||
self.separatorNode.backgroundColor = presentationData.theme.actionSheet.opaqueItemSeparatorColor
|
|
||||||
self.separatorNode.isHidden = !displaySeparator
|
|
||||||
|
|
||||||
self.highlightedBackgroundNode = ASDisplayNode()
|
|
||||||
self.highlightedBackgroundNode.backgroundColor = presentationData.theme.actionSheet.opaqueItemHighlightedBackgroundColor
|
|
||||||
self.highlightedBackgroundNode.alpha = 0.0
|
|
||||||
|
|
||||||
self.buttonNode = HighlightTrackingButtonNode()
|
|
||||||
|
|
||||||
self.avatarNode = AvatarNode(font: avatarFont)
|
|
||||||
|
|
||||||
self.titleNode = ImmediateTextNode()
|
|
||||||
self.titleNode.maximumNumberOfLines = 1
|
|
||||||
self.titleNode.attributedText = NSAttributedString(string: EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), font: Font.regular(17.0), textColor: presentationData.theme.actionSheet.primaryTextColor)
|
|
||||||
|
|
||||||
self.checkNode = ASImageNode()
|
|
||||||
self.checkNode.image = generateItemListCheckIcon(color: presentationData.theme.actionSheet.primaryTextColor)
|
|
||||||
self.checkNode.isHidden = !isCurrent
|
|
||||||
|
|
||||||
self.badgeBackgroundNode = ASImageNode()
|
|
||||||
self.badgeBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 20.0, color: presentationData.theme.list.itemCheckColors.fillColor)
|
|
||||||
self.badgeTitleNode = ImmediateTextNode()
|
|
||||||
if unreadCount > 0 {
|
|
||||||
let countString = compactNumericCountString(Int(unreadCount), decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)
|
|
||||||
self.badgeTitleNode.attributedText = NSAttributedString(string: countString, font: Font.regular(14.0), textColor: presentationData.theme.list.itemCheckColors.foregroundColor)
|
|
||||||
} else {
|
|
||||||
self.badgeBackgroundNode.isHidden = true
|
|
||||||
self.badgeTitleNode.isHidden = true
|
|
||||||
}
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
self.addSubnode(self.separatorNode)
|
|
||||||
self.addSubnode(self.highlightedBackgroundNode)
|
|
||||||
self.addSubnode(self.avatarNode)
|
|
||||||
self.addSubnode(self.titleNode)
|
|
||||||
self.addSubnode(self.checkNode)
|
|
||||||
self.addSubnode(self.badgeBackgroundNode)
|
|
||||||
self.addSubnode(self.badgeTitleNode)
|
|
||||||
self.addSubnode(self.buttonNode)
|
|
||||||
|
|
||||||
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
|
||||||
self.buttonNode.highligthedChanged = { [weak self] highlighted in
|
|
||||||
if let strongSelf = self {
|
|
||||||
if highlighted {
|
|
||||||
strongSelf.highlightedBackgroundNode.layer.removeAnimation(forKey: "opacity")
|
|
||||||
strongSelf.highlightedBackgroundNode.alpha = 1.0
|
|
||||||
} else {
|
|
||||||
strongSelf.highlightedBackgroundNode.alpha = 0.0
|
|
||||||
strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateLayout(maxWidth: CGFloat) -> (CGFloat, CGFloat, (CGFloat) -> Void) {
|
|
||||||
let leftInset: CGFloat = 56.0
|
|
||||||
|
|
||||||
let badgeTitleSize = self.badgeTitleNode.updateLayout(CGSize(width: 100.0, height: .greatestFiniteMagnitude))
|
|
||||||
let badgeMinSize = self.badgeBackgroundNode.image?.size.width ?? 20.0
|
|
||||||
let badgeSize = CGSize(width: max(badgeMinSize, badgeTitleSize.width + 12.0), height: badgeMinSize)
|
|
||||||
|
|
||||||
let rightInset: CGFloat = max(60.0, badgeSize.width + 40.0)
|
|
||||||
|
|
||||||
let titleSize = self.titleNode.updateLayout(CGSize(width: maxWidth - leftInset - rightInset, height: .greatestFiniteMagnitude))
|
|
||||||
|
|
||||||
let height: CGFloat = 61.0
|
|
||||||
|
|
||||||
return (titleSize.width + leftInset + rightInset, height, { width in
|
|
||||||
let avatarSize = CGSize(width: 30.0, height: 30.0)
|
|
||||||
self.avatarNode.frame = CGRect(origin: CGPoint(x: floor((leftInset - avatarSize.width) / 2.0), y: floor((height - avatarSize.height) / 2.0)), size: avatarSize)
|
|
||||||
self.avatarNode.setPeer(context: self.context, theme: self.presentationData.theme, peer: EnginePeer(self.peer))
|
|
||||||
|
|
||||||
self.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - titleSize.height) / 2.0)), size: titleSize)
|
|
||||||
|
|
||||||
if let image = self.checkNode.image {
|
|
||||||
self.checkNode.frame = CGRect(origin: CGPoint(x: width - rightInset + floor((rightInset - image.size.width) / 2.0), y: floor((height - image.size.height) / 2.0)), size: image.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
let badgeBackgroundFrame = CGRect(origin: CGPoint(x: width - rightInset + floor((rightInset - badgeSize.width) / 2.0), y: floor((height - badgeSize.height) / 2.0)), size: badgeSize)
|
|
||||||
self.badgeBackgroundNode.frame = badgeBackgroundFrame
|
|
||||||
self.badgeTitleNode.frame = CGRect(origin: CGPoint(x: badgeBackgroundFrame.minX + floor((badgeBackgroundFrame.width - badgeTitleSize.width) / 2.0), y: badgeBackgroundFrame.minY + floor((badgeBackgroundFrame.height - badgeTitleSize.height) / 2.0)), size: badgeTitleSize)
|
|
||||||
|
|
||||||
self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: height - UIScreenPixel), size: CGSize(width: width, height: UIScreenPixel))
|
|
||||||
self.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: height))
|
|
||||||
self.buttonNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: height))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func buttonPressed() {
|
|
||||||
self.action()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final class TabBarAccountSwitchControllerNode: ViewControllerTracingNode {
|
|
||||||
private let presentationData: PresentationData
|
|
||||||
private let cancel: () -> Void
|
|
||||||
|
|
||||||
private let effectView: UIVisualEffectView
|
|
||||||
private var propertyAnimator: AnyObject?
|
|
||||||
private var displayLinkAnimator: DisplayLinkAnimator?
|
|
||||||
private let dimNode: ASDisplayNode
|
|
||||||
|
|
||||||
private let contentContainerNode: ASDisplayNode
|
|
||||||
private let contentNodes: [ASDisplayNode & AbstractSwitchAccountItemNode]
|
|
||||||
|
|
||||||
private var sourceNodes: [ASDisplayNode]
|
|
||||||
private var snapshotViews: [UIView] = []
|
|
||||||
|
|
||||||
private var validLayout: ContainerViewLayout?
|
|
||||||
|
|
||||||
init(sharedContext: SharedAccountContext, accounts: (primary: (Account, Peer), other: [(Account, Peer, Int32)]), presentationData: PresentationData, canAddAccounts: Bool, switchToAccount: @escaping (AccountRecordId) -> Void, addAccount: @escaping () -> Void, cancel: @escaping () -> Void, sourceNodes: [ASDisplayNode]) {
|
|
||||||
self.presentationData = presentationData
|
|
||||||
self.cancel = cancel
|
|
||||||
self.sourceNodes = sourceNodes
|
|
||||||
|
|
||||||
self.effectView = UIVisualEffectView()
|
|
||||||
if #available(iOS 9.0, *) {
|
|
||||||
} else {
|
|
||||||
if presentationData.theme.rootController.keyboardColor == .dark {
|
|
||||||
self.effectView.effect = UIBlurEffect(style: .dark)
|
|
||||||
} else {
|
|
||||||
self.effectView.effect = UIBlurEffect(style: .light)
|
|
||||||
}
|
|
||||||
self.effectView.alpha = 0.0
|
|
||||||
}
|
|
||||||
|
|
||||||
self.dimNode = ASDisplayNode()
|
|
||||||
self.dimNode.alpha = 1.0
|
|
||||||
if presentationData.theme.rootController.keyboardColor == .light {
|
|
||||||
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.04)
|
|
||||||
} else {
|
|
||||||
self.dimNode.backgroundColor = presentationData.theme.chatList.backgroundColor.withAlphaComponent(0.2)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.contentContainerNode = ASDisplayNode()
|
|
||||||
self.contentContainerNode.backgroundColor = self.presentationData.theme.actionSheet.opaqueItemBackgroundColor
|
|
||||||
self.contentContainerNode.cornerRadius = 20.0
|
|
||||||
self.contentContainerNode.clipsToBounds = true
|
|
||||||
|
|
||||||
var contentNodes: [ASDisplayNode & AbstractSwitchAccountItemNode] = []
|
|
||||||
if canAddAccounts {
|
|
||||||
contentNodes.append(AddAccountItemNode(displaySeparator: true, presentationData: presentationData, action: {
|
|
||||||
addAccount()
|
|
||||||
cancel()
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
contentNodes.append(SwitchAccountItemNode(context: sharedContext.makeTempAccountContext(account: accounts.primary.0), peer: accounts.primary.1, isCurrent: true, unreadCount: 0, displaySeparator: !accounts.other.isEmpty, presentationData: presentationData, action: {
|
|
||||||
cancel()
|
|
||||||
}))
|
|
||||||
for i in 0 ..< accounts.other.count {
|
|
||||||
let (account, peer, count) = accounts.other[i]
|
|
||||||
let id = account.id
|
|
||||||
contentNodes.append(SwitchAccountItemNode(context: sharedContext.makeTempAccountContext(account: account), peer: peer, isCurrent: false, unreadCount: count, displaySeparator: i != accounts.other.count - 1, presentationData: presentationData, action: {
|
|
||||||
switchToAccount(id)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
self.contentNodes = contentNodes
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
self.view.addSubview(self.effectView)
|
|
||||||
self.addSubnode(self.dimNode)
|
|
||||||
self.addSubnode(self.contentContainerNode)
|
|
||||||
self.contentNodes.forEach(self.contentContainerNode.addSubnode)
|
|
||||||
|
|
||||||
self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
if let propertyAnimator = self.propertyAnimator {
|
|
||||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
|
||||||
let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator
|
|
||||||
propertyAnimator?.stopAnimation(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func animateIn() {
|
|
||||||
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
|
||||||
|
|
||||||
if #available(iOS 10.0, *) {
|
|
||||||
if let propertyAnimator = self.propertyAnimator {
|
|
||||||
let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator
|
|
||||||
propertyAnimator?.stopAnimation(true)
|
|
||||||
}
|
|
||||||
self.propertyAnimator = UIViewPropertyAnimator(duration: 0.2 * animationDurationFactor, curve: .easeInOut, animations: { [weak self] in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
strongSelf.effectView.effect = makeCustomZoomBlurEffect(isLight: !strongSelf.presentationData.theme.overallDarkAppearance)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if let _ = self.propertyAnimator {
|
|
||||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
|
||||||
self.displayLinkAnimator = DisplayLinkAnimator(duration: 0.2 * animationDurationFactor, from: 0.0, to: 1.0, update: { [weak self] value in
|
|
||||||
(self?.propertyAnimator as? UIViewPropertyAnimator)?.fractionComplete = value
|
|
||||||
}, completion: {
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
UIView.animate(withDuration: 0.2 * animationDurationFactor, animations: {
|
|
||||||
self.effectView.effect = makeCustomZoomBlurEffect(isLight: !self.presentationData.theme.overallDarkAppearance)
|
|
||||||
}, completion: { _ in
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
self.contentContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
|
||||||
|
|
||||||
if let _ = self.validLayout, let sourceNode = self.sourceNodes.first {
|
|
||||||
let sourceFrame = sourceNode.view.convert(sourceNode.bounds, to: self.view)
|
|
||||||
self.contentContainerNode.layer.animateFrame(from: sourceFrame, to: self.contentContainerNode.frame, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
|
||||||
}
|
|
||||||
|
|
||||||
for sourceNode in self.sourceNodes {
|
|
||||||
if let imageNode = sourceNode as? ASImageNode {
|
|
||||||
let snapshot = UIImageView()
|
|
||||||
snapshot.image = imageNode.image
|
|
||||||
snapshot.frame = sourceNode.view.convert(sourceNode.bounds, to: self.view)
|
|
||||||
snapshot.isUserInteractionEnabled = false
|
|
||||||
self.view.addSubview(snapshot)
|
|
||||||
self.snapshotViews.append(snapshot)
|
|
||||||
} else if let snapshot = sourceNode.view.snapshotContentTree() {
|
|
||||||
snapshot.frame = sourceNode.view.convert(sourceNode.bounds, to: self.view)
|
|
||||||
snapshot.isUserInteractionEnabled = false
|
|
||||||
self.view.addSubview(snapshot)
|
|
||||||
self.snapshotViews.append(snapshot)
|
|
||||||
}
|
|
||||||
sourceNode.alpha = 0.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func animateOut(sourceNodes: [ASDisplayNode], changedAccount: Bool, completion: @escaping () -> Void) {
|
|
||||||
self.isUserInteractionEnabled = false
|
|
||||||
|
|
||||||
var completedEffect = false
|
|
||||||
var completedSourceNodes = false
|
|
||||||
|
|
||||||
let intermediateCompletion: () -> Void = {
|
|
||||||
if completedEffect && completedSourceNodes {
|
|
||||||
completion()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if #available(iOS 10.0, *) {
|
|
||||||
if let propertyAnimator = self.propertyAnimator {
|
|
||||||
let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator
|
|
||||||
propertyAnimator?.stopAnimation(true)
|
|
||||||
}
|
|
||||||
self.propertyAnimator = UIViewPropertyAnimator(duration: 0.2, curve: .easeInOut, animations: { [weak self] in
|
|
||||||
self?.effectView.effect = nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if let _ = self.propertyAnimator {
|
|
||||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
|
||||||
self.displayLinkAnimator = DisplayLinkAnimator(duration: 0.2 * animationDurationFactor, from: 0.0, to: 0.999, update: { [weak self] value in
|
|
||||||
(self?.propertyAnimator as? UIViewPropertyAnimator)?.fractionComplete = value
|
|
||||||
}, completion: { [weak self] in
|
|
||||||
if let strongSelf = self {
|
|
||||||
for sourceNode in strongSelf.sourceNodes {
|
|
||||||
sourceNode.alpha = 1.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
completedEffect = true
|
|
||||||
intermediateCompletion()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
self.effectView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.05 * animationDurationFactor, delay: 0.15, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false)
|
|
||||||
} else {
|
|
||||||
UIView.animate(withDuration: 0.21 * animationDurationFactor, animations: {
|
|
||||||
if #available(iOS 9.0, *) {
|
|
||||||
self.effectView.effect = nil
|
|
||||||
} else {
|
|
||||||
self.effectView.alpha = 0.0
|
|
||||||
}
|
|
||||||
}, completion: { [weak self] _ in
|
|
||||||
if let strongSelf = self {
|
|
||||||
for sourceNode in strongSelf.sourceNodes {
|
|
||||||
sourceNode.alpha = 1.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
completedEffect = true
|
|
||||||
intermediateCompletion()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
|
||||||
self.contentContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false, completion: { _ in
|
|
||||||
})
|
|
||||||
if let _ = self.validLayout, let sourceNode = self.sourceNodes.first {
|
|
||||||
let sourceFrame = sourceNode.view.convert(sourceNode.bounds, to: self.view)
|
|
||||||
self.contentContainerNode.layer.animateFrame(from: self.contentContainerNode.frame, to: sourceFrame, duration: 0.15, timingFunction: CAMediaTimingFunctionName.easeIn.rawValue, removeOnCompletion: false)
|
|
||||||
}
|
|
||||||
|
|
||||||
if changedAccount {
|
|
||||||
for sourceNode in self.sourceNodes {
|
|
||||||
sourceNode.alpha = 1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
var previousImage: UIImage?
|
|
||||||
for i in 0 ..< self.snapshotViews.count {
|
|
||||||
let view = self.snapshotViews[i]
|
|
||||||
if view.bounds.size.width.isEqual(to: 42.0) {
|
|
||||||
if i == 0, let imageView = view as? UIImageView {
|
|
||||||
previousImage = imageView.image
|
|
||||||
}
|
|
||||||
view.removeFromSuperview()
|
|
||||||
} else {
|
|
||||||
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak view] _ in
|
|
||||||
view?.removeFromSuperview()
|
|
||||||
})
|
|
||||||
view.layer.animateScale(from: 1.0, to: 0.2, duration: 0.25, removeOnCompletion: false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let previousSnapshotViews = self.snapshotViews
|
|
||||||
self.snapshotViews = []
|
|
||||||
|
|
||||||
self.sourceNodes = sourceNodes
|
|
||||||
|
|
||||||
var hadBounce = false
|
|
||||||
for i in 0 ..< self.sourceNodes.count {
|
|
||||||
let sourceNode = self.sourceNodes[i]
|
|
||||||
var snapshot: UIView?
|
|
||||||
if let imageNode = sourceNode as? ASImageNode {
|
|
||||||
let snapshotView = UIImageView()
|
|
||||||
snapshotView.image = imageNode.image
|
|
||||||
snapshotView.frame = sourceNode.view.convert(sourceNode.bounds, to: self.view)
|
|
||||||
snapshotView.isUserInteractionEnabled = false
|
|
||||||
self.view.addSubview(snapshotView)
|
|
||||||
self.snapshotViews.append(snapshotView)
|
|
||||||
snapshot = snapshotView
|
|
||||||
} else if let genericSnapshot = sourceNode.view.snapshotContentTree() {
|
|
||||||
genericSnapshot.frame = sourceNode.view.convert(sourceNode.bounds, to: self.view)
|
|
||||||
genericSnapshot.isUserInteractionEnabled = false
|
|
||||||
self.view.addSubview(genericSnapshot)
|
|
||||||
self.snapshotViews.append(genericSnapshot)
|
|
||||||
snapshot = genericSnapshot
|
|
||||||
}
|
|
||||||
|
|
||||||
if let snapshot = snapshot {
|
|
||||||
if snapshot.bounds.size.width.isEqual(to: 42.0) {
|
|
||||||
if i == 0, let imageView = snapshot as? UIImageView {
|
|
||||||
hadBounce = true
|
|
||||||
let updatedImage = imageView.image
|
|
||||||
imageView.image = previousImage
|
|
||||||
setAnchorPoint(anchorPoint: CGPoint(x: 0.5, y: 0.3), forView: imageView)
|
|
||||||
imageView.layer.animateScale(from: 1.0, to: 0.6, duration: 0.1, removeOnCompletion: false, completion: { [weak imageView] _ in
|
|
||||||
guard let imageView = imageView else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
imageView.image = updatedImage
|
|
||||||
if let previousContents = previousImage?.cgImage, let updatedContents = updatedImage?.cgImage {
|
|
||||||
imageView.layer.animate(from: previousContents as AnyObject, to: updatedContents as AnyObject, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.15)
|
|
||||||
}
|
|
||||||
imageView.layer.animateSpring(from: 0.6 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.6, completion: { _ in
|
|
||||||
completedSourceNodes = true
|
|
||||||
intermediateCompletion()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
snapshot.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
|
||||||
snapshot.layer.animateScale(from: 0.2, to: 1.0, duration: 0.2, removeOnCompletion: false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sourceNode.alpha = 0.0
|
|
||||||
}
|
|
||||||
|
|
||||||
previousSnapshotViews.forEach { view in
|
|
||||||
self.view.bringSubviewToFront(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !hadBounce {
|
|
||||||
completedSourceNodes = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
completedSourceNodes = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
|
||||||
self.validLayout = layout
|
|
||||||
|
|
||||||
transition.updateFrame(view: self.effectView, frame: CGRect(origin: CGPoint(), size: layout.size))
|
|
||||||
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
|
||||||
|
|
||||||
let sideInset: CGFloat = 18.0
|
|
||||||
|
|
||||||
var contentSize = CGSize()
|
|
||||||
contentSize.width = min(layout.size.width - 40.0, 250.0)
|
|
||||||
var applyNodes: [(ASDisplayNode, CGFloat, (CGFloat) -> Void)] = []
|
|
||||||
for itemNode in self.contentNodes {
|
|
||||||
let (width, height, apply) = itemNode.updateLayout(maxWidth: layout.size.width - sideInset * 2.0)
|
|
||||||
applyNodes.append((itemNode, height, apply))
|
|
||||||
contentSize.width = max(contentSize.width, width)
|
|
||||||
contentSize.height += height
|
|
||||||
}
|
|
||||||
|
|
||||||
let insets = layout.insets(options: .input)
|
|
||||||
|
|
||||||
let contentOrigin: CGPoint
|
|
||||||
if let sourceNode = self.sourceNodes.first, let screenFrame = sourceNode.supernode?.convert(sourceNode.frame, to: nil) {
|
|
||||||
contentOrigin = CGPoint(x: screenFrame.maxX - contentSize.width + 8.0, y: layout.size.height - 66.0 - insets.bottom - contentSize.height)
|
|
||||||
} else {
|
|
||||||
contentOrigin = CGPoint(x: layout.size.width - sideInset - contentSize.width, y: layout.size.height - 66.0 - layout.intrinsicInsets.bottom - contentSize.height)
|
|
||||||
}
|
|
||||||
|
|
||||||
transition.updateFrame(node: self.contentContainerNode, frame: CGRect(origin: contentOrigin, size: contentSize))
|
|
||||||
var nextY: CGFloat = 0.0
|
|
||||||
for (itemNode, height, apply) in applyNodes {
|
|
||||||
transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: nextY), size: CGSize(width: contentSize.width, height: height)))
|
|
||||||
apply(contentSize.width)
|
|
||||||
nextY += height
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func dimTapGesture(_ recognizer: UITapGestureRecognizer) {
|
|
||||||
if case .ended = recognizer.state {
|
|
||||||
self.cancel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setAnchorPoint(anchorPoint: CGPoint, forView view: UIView) {
|
|
||||||
var newPoint = CGPoint(x: view.bounds.size.width * anchorPoint.x,
|
|
||||||
y: view.bounds.size.height * anchorPoint.y)
|
|
||||||
|
|
||||||
|
|
||||||
var oldPoint = CGPoint(x: view.bounds.size.width * view.layer.anchorPoint.x,
|
|
||||||
y: view.bounds.size.height * view.layer.anchorPoint.y)
|
|
||||||
|
|
||||||
newPoint = newPoint.applying(view.transform)
|
|
||||||
oldPoint = oldPoint.applying(view.transform)
|
|
||||||
|
|
||||||
var position = view.layer.position
|
|
||||||
position.x -= oldPoint.x
|
|
||||||
position.x += newPoint.x
|
|
||||||
|
|
||||||
position.y -= oldPoint.y
|
|
||||||
position.y += newPoint.y
|
|
||||||
|
|
||||||
view.layer.position = position
|
|
||||||
view.layer.anchorPoint = anchorPoint
|
|
||||||
}
|
|
||||||
|
|
@ -31,14 +31,14 @@ var dayClassicColorPresets: [PresentationThemeAccentColor] = [
|
||||||
]
|
]
|
||||||
|
|
||||||
var dayColorPresets: [PresentationThemeAccentColor] = [
|
var dayColorPresets: [PresentationThemeAccentColor] = [
|
||||||
PresentationThemeAccentColor(index: 101, baseColor: .preset, accentColor: 0x007aff, bubbleColors: [0x007aff, 0xff53f4], wallpaper: nil),
|
PresentationThemeAccentColor(index: 101, baseColor: .preset, accentColor: 0x0088ff, bubbleColors: [0x0088ff, 0xff53f4], wallpaper: nil),
|
||||||
PresentationThemeAccentColor(index: 102, baseColor: .preset, accentColor: 0x00b09b, bubbleColors: [0xaee946, 0x00b09b], wallpaper: nil),
|
PresentationThemeAccentColor(index: 102, baseColor: .preset, accentColor: 0x00b09b, bubbleColors: [0xaee946, 0x00b09b], wallpaper: nil),
|
||||||
PresentationThemeAccentColor(index: 103, baseColor: .preset, accentColor: 0xd33213, bubbleColors: [0xf9db00, 0xd33213], wallpaper: nil),
|
PresentationThemeAccentColor(index: 103, baseColor: .preset, accentColor: 0xd33213, bubbleColors: [0xf9db00, 0xd33213], wallpaper: nil),
|
||||||
PresentationThemeAccentColor(index: 104, baseColor: .preset, accentColor: 0xea8ced, bubbleColors: [0xea8ced, 0x00c2ed], wallpaper: nil)
|
PresentationThemeAccentColor(index: 104, baseColor: .preset, accentColor: 0xea8ced, bubbleColors: [0xea8ced, 0x00c2ed], wallpaper: nil)
|
||||||
]
|
]
|
||||||
|
|
||||||
var nightColorPresets: [PresentationThemeAccentColor] = [
|
var nightColorPresets: [PresentationThemeAccentColor] = [
|
||||||
// PresentationThemeAccentColor(index: 101, baseColor: .preset, accentColor: 0x007aff, bubbleColors: [0x007aff, 0xff53f4], wallpaper: patternWallpaper(data: .variant4, colors: [0xe4b2ea, 0x8376c2, 0xeab9d9, 0xb493e6], intensity: -35, rotation: nil)),
|
// PresentationThemeAccentColor(index: 101, baseColor: .preset, accentColor: 0x0088ff, bubbleColors: [0x0088ff, 0xff53f4], wallpaper: patternWallpaper(data: .variant4, colors: [0xe4b2ea, 0x8376c2, 0xeab9d9, 0xb493e6], intensity: -35, rotation: nil)),
|
||||||
PresentationThemeAccentColor(index: 102, baseColor: .preset, accentColor: 0x00b09b, bubbleColors: [0xaee946, 0x00b09b], wallpaper: patternWallpaper(data: .variant9, colors: [0xe4b2ea, 0x8376c2, 0xeab9d9, 0xb493e6], intensity: -35, rotation: nil)),
|
PresentationThemeAccentColor(index: 102, baseColor: .preset, accentColor: 0x00b09b, bubbleColors: [0xaee946, 0x00b09b], wallpaper: patternWallpaper(data: .variant9, colors: [0xe4b2ea, 0x8376c2, 0xeab9d9, 0xb493e6], intensity: -35, rotation: nil)),
|
||||||
PresentationThemeAccentColor(index: 103, baseColor: .preset, accentColor: 0xd33213, bubbleColors: [0xf9db00, 0xd33213], wallpaper: patternWallpaper(data: .variant2, colors: [0xfec496, 0xdd6cb9, 0x962fbf, 0x4f5bd5], intensity: -40, rotation: nil)),
|
PresentationThemeAccentColor(index: 103, baseColor: .preset, accentColor: 0xd33213, bubbleColors: [0xf9db00, 0xd33213], wallpaper: patternWallpaper(data: .variant2, colors: [0xfec496, 0xdd6cb9, 0x962fbf, 0x4f5bd5], intensity: -40, rotation: nil)),
|
||||||
PresentationThemeAccentColor(index: 104, baseColor: .preset, accentColor: 0xea8ced, bubbleColors: [0xea8ced, 0x00c2ed], wallpaper: patternWallpaper(data: .variant6, colors: [0x8adbf2, 0x888dec, 0xe39fea, 0x679ced], intensity: -30, rotation: nil))
|
PresentationThemeAccentColor(index: 104, baseColor: .preset, accentColor: 0xea8ced, bubbleColors: [0xea8ced, 0x00c2ed], wallpaper: patternWallpaper(data: .variant6, colors: [0x8adbf2, 0x888dec, 0xe39fea, 0x679ced], intensity: -30, rotation: nil))
|
||||||
|
|
|
||||||
|
|
@ -385,7 +385,7 @@ private final class ThemeSettingsAccentColorIconItemNode : ListViewItemNode {
|
||||||
topColor = bubbleColor
|
topColor = bubbleColor
|
||||||
bottomColor = bubbleColor
|
bottomColor = bubbleColor
|
||||||
} else {
|
} else {
|
||||||
fillColor = UIColor(rgb: 0x007aff)
|
fillColor = UIColor(rgb: 0x0088ff)
|
||||||
strokeColor = fillColor
|
strokeColor = fillColor
|
||||||
topColor = UIColor(rgb: 0xe1ffc7)
|
topColor = UIColor(rgb: 0xe1ffc7)
|
||||||
bottomColor = topColor
|
bottomColor = topColor
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,15 @@ swift_library(
|
||||||
"-warnings-as-errors",
|
"-warnings-as-errors",
|
||||||
],
|
],
|
||||||
deps = [
|
deps = [
|
||||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
"//submodules/AsyncDisplayKit",
|
||||||
"//submodules/Display:Display",
|
"//submodules/Display",
|
||||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
"//submodules/SSignalKit/SwiftSignalKit",
|
||||||
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
"//submodules/AnimatedStickerNode",
|
||||||
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
"//submodules/TelegramAnimatedStickerNode",
|
||||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
"//submodules/TelegramPresentationData",
|
||||||
|
"//submodules/ComponentFlow",
|
||||||
|
"//submodules/Components/ComponentDisplayAdapters",
|
||||||
|
"//submodules/TelegramUI/Components/TabBarComponent",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|
|
||||||
|
|
@ -2,24 +2,72 @@ import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
import Display
|
import Display
|
||||||
|
import TelegramPresentationData
|
||||||
|
import ComponentFlow
|
||||||
|
import ComponentDisplayAdapters
|
||||||
|
import TabBarComponent
|
||||||
|
|
||||||
private extension ToolbarTheme {
|
private extension ToolbarTheme {
|
||||||
convenience init(tabBarTheme theme: TabBarControllerTheme) {
|
convenience init(theme: PresentationTheme) {
|
||||||
self.init(barBackgroundColor: theme.tabBarBackgroundColor, barSeparatorColor: theme.tabBarSeparatorColor, barTextColor: theme.tabBarTextColor, barSelectedTextColor: theme.tabBarSelectedTextColor)
|
self.init(barBackgroundColor: theme.rootController.tabBar.backgroundColor, barSeparatorColor: .clear, barTextColor: theme.rootController.tabBar.textColor, barSelectedTextColor: theme.rootController.tabBar.selectedTextColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class TabBarControllerNode: ASDisplayNode {
|
final class TabBarControllerNode: ASDisplayNode {
|
||||||
private var navigationBarPresentationData: NavigationBarPresentationData
|
private struct Params: Equatable {
|
||||||
private var theme: TabBarControllerTheme
|
let layout: ContainerViewLayout
|
||||||
let tabBarNode: TabBarNode
|
let toolbar: Toolbar?
|
||||||
|
|
||||||
|
init(
|
||||||
|
layout: ContainerViewLayout,
|
||||||
|
toolbar: Toolbar?
|
||||||
|
) {
|
||||||
|
self.layout = layout
|
||||||
|
self.toolbar = toolbar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct LayoutResult {
|
||||||
|
let params: Params
|
||||||
|
let bottomInset: CGFloat
|
||||||
|
|
||||||
|
init(params: Params, bottomInset: CGFloat) {
|
||||||
|
self.params = params
|
||||||
|
self.bottomInset = bottomInset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class View: UIView {
|
||||||
|
var onLayout: (() -> Void)?
|
||||||
|
|
||||||
|
override func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
|
||||||
|
self.onLayout?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var theme: PresentationTheme
|
||||||
|
private let itemSelected: (Int, Bool, [ASDisplayNode]) -> Void
|
||||||
|
private let contextAction: (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void
|
||||||
|
|
||||||
|
private let tabBarNode: TabBarNode
|
||||||
|
private let tabBarView = ComponentView<Empty>()
|
||||||
|
|
||||||
private let disabledOverlayNode: ASDisplayNode
|
private let disabledOverlayNode: ASDisplayNode
|
||||||
private var toolbarNode: ToolbarNode?
|
private var toolbarNode: ToolbarNode?
|
||||||
private let toolbarActionSelected: (ToolbarActionOption) -> Void
|
private let toolbarActionSelected: (ToolbarActionOption) -> Void
|
||||||
private let disabledPressed: () -> Void
|
private let disabledPressed: () -> Void
|
||||||
|
|
||||||
|
private(set) var tabBarItems: [TabBarNodeItem] = []
|
||||||
|
private(set) var selectedIndex: Int = 0
|
||||||
|
|
||||||
var currentControllerNode: ASDisplayNode?
|
var currentControllerNode: ASDisplayNode?
|
||||||
|
|
||||||
|
private var layoutResult: LayoutResult?
|
||||||
|
private var isUpdateRequested: Bool = false
|
||||||
|
private var isChangingSelectedIndex: Bool = false
|
||||||
|
|
||||||
func setCurrentControllerNode(_ node: ASDisplayNode?) -> () -> Void {
|
func setCurrentControllerNode(_ node: ASDisplayNode?) -> () -> Void {
|
||||||
guard node !== self.currentControllerNode else {
|
guard node !== self.currentControllerNode else {
|
||||||
return {}
|
return {}
|
||||||
|
|
@ -42,12 +90,13 @@ final class TabBarControllerNode: ASDisplayNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(theme: TabBarControllerTheme, navigationBarPresentationData: NavigationBarPresentationData, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, contextAction: @escaping (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void, swipeAction: @escaping (Int, TabBarItemSwipeDirection) -> Void, toolbarActionSelected: @escaping (ToolbarActionOption) -> Void, disabledPressed: @escaping () -> Void) {
|
init(theme: PresentationTheme, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, contextAction: @escaping (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void, swipeAction: @escaping (Int, TabBarItemSwipeDirection) -> Void, toolbarActionSelected: @escaping (ToolbarActionOption) -> Void, disabledPressed: @escaping () -> Void) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.navigationBarPresentationData = navigationBarPresentationData
|
self.itemSelected = itemSelected
|
||||||
|
self.contextAction = contextAction
|
||||||
self.tabBarNode = TabBarNode(theme: theme, itemSelected: itemSelected, contextAction: contextAction, swipeAction: swipeAction)
|
self.tabBarNode = TabBarNode(theme: theme, itemSelected: itemSelected, contextAction: contextAction, swipeAction: swipeAction)
|
||||||
self.disabledOverlayNode = ASDisplayNode()
|
self.disabledOverlayNode = ASDisplayNode()
|
||||||
self.disabledOverlayNode.backgroundColor = theme.backgroundColor.withAlphaComponent(0.5)
|
self.disabledOverlayNode.backgroundColor = theme.rootController.tabBar.backgroundColor.withAlphaComponent(0.5)
|
||||||
self.disabledOverlayNode.alpha = 0.0
|
self.disabledOverlayNode.alpha = 0.0
|
||||||
self.toolbarActionSelected = toolbarActionSelected
|
self.toolbarActionSelected = toolbarActionSelected
|
||||||
self.disabledPressed = disabledPressed
|
self.disabledPressed = disabledPressed
|
||||||
|
|
@ -55,13 +104,25 @@ final class TabBarControllerNode: ASDisplayNode {
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.setViewBlock({
|
self.setViewBlock({
|
||||||
return UITracingLayerView()
|
return View(frame: CGRect())
|
||||||
})
|
})
|
||||||
|
|
||||||
self.backgroundColor = theme.backgroundColor
|
(self.view as? View)?.onLayout = { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if self.isUpdateRequested {
|
||||||
|
self.isUpdateRequested = false
|
||||||
|
if let layoutResult = self.layoutResult {
|
||||||
|
let _ = self.updateImpl(params: layoutResult.params, transition: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.addSubnode(self.tabBarNode)
|
self.backgroundColor = theme.list.plainBackgroundColor
|
||||||
self.addSubnode(self.disabledOverlayNode)
|
|
||||||
|
//self.addSubnode(self.tabBarNode)
|
||||||
|
//self.addSubnode(self.disabledOverlayNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didLoad() {
|
override func didLoad() {
|
||||||
|
|
@ -76,14 +137,14 @@ final class TabBarControllerNode: ASDisplayNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateTheme(_ theme: TabBarControllerTheme, navigationBarPresentationData: NavigationBarPresentationData) {
|
func updateTheme(_ theme: PresentationTheme) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.navigationBarPresentationData = navigationBarPresentationData
|
self.backgroundColor = theme.list.plainBackgroundColor
|
||||||
self.backgroundColor = theme.backgroundColor
|
|
||||||
|
|
||||||
self.tabBarNode.updateTheme(theme)
|
self.tabBarNode.updateTheme(theme)
|
||||||
self.disabledOverlayNode.backgroundColor = theme.backgroundColor.withAlphaComponent(0.5)
|
self.disabledOverlayNode.backgroundColor = theme.rootController.tabBar.backgroundColor.withAlphaComponent(0.5)
|
||||||
self.toolbarNode?.updateTheme(ToolbarTheme(tabBarTheme: theme))
|
self.toolbarNode?.updateTheme(ToolbarTheme(theme: theme))
|
||||||
|
self.requestUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateIsTabBarEnabled(_ value: Bool, transition: ContainedViewLayoutTransition) {
|
func updateIsTabBarEnabled(_ value: Bool, transition: ContainedViewLayoutTransition) {
|
||||||
|
|
@ -92,32 +153,91 @@ final class TabBarControllerNode: ASDisplayNode {
|
||||||
|
|
||||||
var tabBarHidden = false
|
var tabBarHidden = false
|
||||||
|
|
||||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, toolbar: Toolbar?, transition: ContainedViewLayoutTransition) {
|
func containerLayoutUpdated(_ layout: ContainerViewLayout, toolbar: Toolbar?, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||||
var tabBarHeight: CGFloat
|
let params = Params(layout: layout, toolbar: toolbar)
|
||||||
|
if let layoutResult = self.layoutResult, layoutResult.params == params {
|
||||||
|
return layoutResult.bottomInset
|
||||||
|
} else {
|
||||||
|
let bottomInset = self.updateImpl(params: params, transition: transition)
|
||||||
|
self.layoutResult = LayoutResult(params: params, bottomInset: bottomInset)
|
||||||
|
return bottomInset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func requestUpdate() {
|
||||||
|
self.isUpdateRequested = true
|
||||||
|
self.view.setNeedsLayout()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateImpl(params: Params, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||||
var options: ContainerViewLayoutInsetOptions = []
|
var options: ContainerViewLayoutInsetOptions = []
|
||||||
if layout.metrics.widthClass == .regular {
|
if params.layout.metrics.widthClass == .regular {
|
||||||
options.insert(.input)
|
options.insert(.input)
|
||||||
}
|
}
|
||||||
let bottomInset: CGFloat = layout.insets(options: options).bottom
|
|
||||||
if !layout.safeInsets.left.isZero {
|
var bottomInset: CGFloat = params.layout.insets(options: options).bottom
|
||||||
tabBarHeight = 34.0 + bottomInset
|
if bottomInset == 0.0 {
|
||||||
|
bottomInset = 8.0
|
||||||
} else {
|
} else {
|
||||||
tabBarHeight = 49.0 + bottomInset
|
bottomInset = max(bottomInset - 13.0, 8.0)
|
||||||
|
}
|
||||||
|
let sideInset: CGFloat = 21.0
|
||||||
|
|
||||||
|
var selectedId: AnyHashable?
|
||||||
|
if self.selectedIndex < self.tabBarItems.count {
|
||||||
|
selectedId = ObjectIdentifier(self.tabBarItems[self.selectedIndex].item)
|
||||||
|
}
|
||||||
|
var tabBarTransition = ComponentTransition(transition)
|
||||||
|
if self.isChangingSelectedIndex {
|
||||||
|
self.isChangingSelectedIndex = false
|
||||||
|
tabBarTransition = .spring(duration: 0.4)
|
||||||
|
}
|
||||||
|
if self.tabBarView.view == nil {
|
||||||
|
tabBarTransition = .immediate
|
||||||
|
}
|
||||||
|
let tabBarSize = self.tabBarView.update(
|
||||||
|
transition: tabBarTransition,
|
||||||
|
component: AnyComponent(TabBarComponent(
|
||||||
|
theme: self.theme,
|
||||||
|
items: self.tabBarItems.map { item in
|
||||||
|
let itemId = AnyHashable(ObjectIdentifier(item.item))
|
||||||
|
return TabBarComponent.Item(
|
||||||
|
item: item.item,
|
||||||
|
action: { [weak self] isLongTap in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let index = self.tabBarItems.firstIndex(where: { AnyHashable(ObjectIdentifier($0.item)) == itemId }) {
|
||||||
|
self.itemSelected(index, isLongTap, [])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
selectedId: selectedId
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: params.layout.size.width - sideInset * 2.0, height: 100.0)
|
||||||
|
)
|
||||||
|
let tabBarFrame = CGRect(origin: CGPoint(x: floor((params.layout.size.width - tabBarSize.width) * 0.5), y: params.layout.size.height - (self.tabBarHidden ? 0.0 : (tabBarSize.height + bottomInset))), size: tabBarSize)
|
||||||
|
|
||||||
|
if let tabBarComponentView = self.tabBarView.view {
|
||||||
|
if tabBarComponentView.superview == nil {
|
||||||
|
self.view.addSubview(tabBarComponentView)
|
||||||
|
}
|
||||||
|
transition.updateFrame(view: tabBarComponentView, frame: tabBarFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
let tabBarFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - (self.tabBarHidden ? 0.0 : tabBarHeight)), size: CGSize(width: layout.size.width, height: tabBarHeight))
|
//transition.updateFrame(node: self.tabBarNode, frame: tabBarFrame)
|
||||||
|
//self.tabBarNode.updateLayout(size: params.layout.size, leftInset: params.layout.safeInsets.left, rightInset: params.layout.safeInsets.right, additionalSideInsets: params.layout.additionalInsets, bottomInset: bottomInset, transition: transition)
|
||||||
transition.updateFrame(node: self.tabBarNode, frame: tabBarFrame)
|
|
||||||
self.tabBarNode.updateLayout(size: layout.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: bottomInset, transition: transition)
|
|
||||||
|
|
||||||
transition.updateFrame(node: self.disabledOverlayNode, frame: tabBarFrame)
|
transition.updateFrame(node: self.disabledOverlayNode, frame: tabBarFrame)
|
||||||
|
|
||||||
if let toolbar = toolbar {
|
if let toolbar = params.toolbar {
|
||||||
if let toolbarNode = self.toolbarNode {
|
if let toolbarNode = self.toolbarNode {
|
||||||
transition.updateFrame(node: toolbarNode, frame: tabBarFrame)
|
transition.updateFrame(node: toolbarNode, frame: tabBarFrame)
|
||||||
toolbarNode.updateLayout(size: tabBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: bottomInset, toolbar: toolbar, transition: transition)
|
toolbarNode.updateLayout(size: tabBarFrame.size, leftInset: params.layout.safeInsets.left, rightInset: params.layout.safeInsets.right, additionalSideInsets: params.layout.additionalInsets, bottomInset: bottomInset, toolbar: toolbar, transition: transition)
|
||||||
} else {
|
} else {
|
||||||
let toolbarNode = ToolbarNode(theme: ToolbarTheme(tabBarTheme: self.theme), displaySeparator: true, left: { [weak self] in
|
let toolbarNode = ToolbarNode(theme: ToolbarTheme(theme: self.theme), displaySeparator: true, left: { [weak self] in
|
||||||
self?.toolbarActionSelected(.left)
|
self?.toolbarActionSelected(.left)
|
||||||
}, right: { [weak self] in
|
}, right: { [weak self] in
|
||||||
self?.toolbarActionSelected(.right)
|
self?.toolbarActionSelected(.right)
|
||||||
|
|
@ -125,7 +245,7 @@ final class TabBarControllerNode: ASDisplayNode {
|
||||||
self?.toolbarActionSelected(.middle)
|
self?.toolbarActionSelected(.middle)
|
||||||
})
|
})
|
||||||
toolbarNode.frame = tabBarFrame
|
toolbarNode.frame = tabBarFrame
|
||||||
toolbarNode.updateLayout(size: tabBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: bottomInset, toolbar: toolbar, transition: .immediate)
|
toolbarNode.updateLayout(size: tabBarFrame.size, leftInset: params.layout.safeInsets.left, rightInset: params.layout.safeInsets.right, additionalSideInsets: params.layout.additionalInsets, bottomInset: bottomInset, toolbar: toolbar, transition: .immediate)
|
||||||
self.addSubnode(toolbarNode)
|
self.addSubnode(toolbarNode)
|
||||||
self.toolbarNode = toolbarNode
|
self.toolbarNode = toolbarNode
|
||||||
if transition.isAnimated {
|
if transition.isAnimated {
|
||||||
|
|
@ -138,5 +258,31 @@ final class TabBarControllerNode: ASDisplayNode {
|
||||||
toolbarNode?.removeFromSupernode()
|
toolbarNode?.removeFromSupernode()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return params.layout.size.height - tabBarFrame.minY
|
||||||
|
}
|
||||||
|
|
||||||
|
func frameForControllerTab(at index: Int) -> CGRect? {
|
||||||
|
return self.tabBarNode.frameForControllerTab(at: index).flatMap { self.tabBarNode.view.convert($0, to: self.view) }
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPointInsideContentArea(point: CGPoint) -> Bool {
|
||||||
|
if point.y < self.tabBarNode.frame.minY {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateTabBarItems(items: [TabBarNodeItem]) {
|
||||||
|
self.tabBarItems = items
|
||||||
|
self.tabBarNode.tabBarItems = items
|
||||||
|
self.requestUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateSelectedIndex(index: Int) {
|
||||||
|
self.selectedIndex = index
|
||||||
|
self.tabBarNode.selectedIndex = index
|
||||||
|
self.isChangingSelectedIndex = true
|
||||||
|
self.requestUpdate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,41 +5,6 @@ import SwiftSignalKit
|
||||||
import Display
|
import Display
|
||||||
import TelegramPresentationData
|
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 final class TabBarItemInfo: NSObject {
|
||||||
public let previewing: Bool
|
public let previewing: Bool
|
||||||
|
|
||||||
|
|
@ -127,13 +92,9 @@ open class TabBarControllerImpl: ViewController, TabBarController {
|
||||||
|
|
||||||
private let pendingControllerDisposable = MetaDisposable()
|
private let pendingControllerDisposable = MetaDisposable()
|
||||||
|
|
||||||
private var navigationBarPresentationData: NavigationBarPresentationData
|
private var theme: PresentationTheme
|
||||||
private var theme: TabBarControllerTheme
|
|
||||||
|
|
||||||
public var cameraItemAndAction: (item: UITabBarItem, action: () -> Void)?
|
public init(theme: PresentationTheme) {
|
||||||
|
|
||||||
public init(navigationBarPresentationData: NavigationBarPresentationData, theme: TabBarControllerTheme) {
|
|
||||||
self.navigationBarPresentationData = navigationBarPresentationData
|
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
|
||||||
super.init(navigationBarPresentationData: nil)
|
super.init(navigationBarPresentationData: nil)
|
||||||
|
|
@ -156,54 +117,27 @@ open class TabBarControllerImpl: ViewController, TabBarController {
|
||||||
self.pendingControllerDisposable.dispose()
|
self.pendingControllerDisposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func updateTheme(navigationBarPresentationData: NavigationBarPresentationData, theme: TabBarControllerTheme) {
|
public func updateTheme(theme: PresentationTheme) {
|
||||||
if self.theme !== theme {
|
if self.theme !== theme {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.navigationBarPresentationData = navigationBarPresentationData
|
|
||||||
if self.isNodeLoaded {
|
if self.isNodeLoaded {
|
||||||
self.tabBarControllerNode.updateTheme(theme, navigationBarPresentationData: navigationBarPresentationData)
|
self.tabBarControllerNode.updateTheme(theme)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var debugTapCounter: (Double, Int) = (0.0, 0)
|
private var debugTapCounter: (Double, Int) = (0.0, 0)
|
||||||
|
|
||||||
public func sourceNodesForController(at index: Int) -> [ASDisplayNode]? {
|
|
||||||
return self.tabBarControllerNode.tabBarNode.sourceNodesForController(at: index)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func viewForCameraItem() -> UIView? {
|
|
||||||
if let (cameraItem, _) = self.cameraItemAndAction {
|
|
||||||
if let cameraItemIndex = self.tabBarControllerNode.tabBarNode.tabBarItems.firstIndex(where: { $0.item === cameraItem }) {
|
|
||||||
return self.tabBarControllerNode.tabBarNode.viewForControllerTab(at: cameraItemIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
public func frameForControllerTab(controller: ViewController) -> CGRect? {
|
public func frameForControllerTab(controller: ViewController) -> CGRect? {
|
||||||
if let index = self.controllers.firstIndex(of: controller) {
|
if let index = self.controllers.firstIndex(of: controller) {
|
||||||
var index = index
|
return self.tabBarControllerNode.frameForControllerTab(at: index)
|
||||||
if let (cameraItem, _) = self.cameraItemAndAction {
|
|
||||||
if let cameraItemIndex = self.tabBarControllerNode.tabBarNode.tabBarItems.firstIndex(where: { $0.item === cameraItem }) {
|
|
||||||
if index == cameraItemIndex {
|
|
||||||
|
|
||||||
} else if index > cameraItemIndex {
|
|
||||||
index -= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return self.tabBarControllerNode.tabBarNode.frameForControllerTab(at: index).flatMap { self.tabBarControllerNode.tabBarNode.view.convert($0, to: self.view) }
|
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func isPointInsideContentArea(point: CGPoint) -> Bool {
|
public func isPointInsideContentArea(point: CGPoint) -> Bool {
|
||||||
if point.y < self.tabBarControllerNode.tabBarNode.frame.minY {
|
return self.tabBarControllerNode.isPointInsideContentArea(point: point)
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func updateIsTabBarEnabled(_ value: Bool, transition: ContainedViewLayoutTransition) {
|
public func updateIsTabBarEnabled(_ value: Bool, transition: ContainedViewLayoutTransition) {
|
||||||
|
|
@ -218,19 +152,8 @@ open class TabBarControllerImpl: ViewController, TabBarController {
|
||||||
}
|
}
|
||||||
|
|
||||||
override open func loadDisplayNode() {
|
override open func loadDisplayNode() {
|
||||||
self.displayNode = TabBarControllerNode(theme: self.theme, navigationBarPresentationData: self.navigationBarPresentationData, itemSelected: { [weak self] index, longTap, itemNodes in
|
self.displayNode = TabBarControllerNode(theme: self.theme, itemSelected: { [weak self] index, longTap, itemNodes in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
var index = index
|
|
||||||
if let (cameraItem, cameraAction) = strongSelf.cameraItemAndAction {
|
|
||||||
if let cameraItemIndex = strongSelf.tabBarControllerNode.tabBarNode.tabBarItems.firstIndex(where: { $0.item === cameraItem }) {
|
|
||||||
if index == cameraItemIndex {
|
|
||||||
cameraAction()
|
|
||||||
return
|
|
||||||
} else if index > cameraItemIndex {
|
|
||||||
index -= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if longTap, let controller = strongSelf.controllers[index] as? TabBarContainedController {
|
if longTap, let controller = strongSelf.controllers[index] as? TabBarContainedController {
|
||||||
controller.presentTabBarPreviewingController(sourceNodes: itemNodes)
|
controller.presentTabBarPreviewingController(sourceNodes: itemNodes)
|
||||||
return
|
return
|
||||||
|
|
@ -298,34 +221,14 @@ open class TabBarControllerImpl: ViewController, TabBarController {
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if index >= 0 && index < strongSelf.tabBarControllerNode.tabBarNode.tabBarItems.count {
|
if index >= 0 && index < strongSelf.tabBarControllerNode.tabBarItems.count {
|
||||||
var index = index
|
|
||||||
if let (cameraItem, _) = strongSelf.cameraItemAndAction {
|
|
||||||
if let cameraItemIndex = strongSelf.tabBarControllerNode.tabBarNode.tabBarItems.firstIndex(where: { $0.item === cameraItem }) {
|
|
||||||
if index == cameraItemIndex {
|
|
||||||
return
|
|
||||||
} else if index > cameraItemIndex {
|
|
||||||
index -= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
strongSelf.controllers[index].tabBarItemContextAction(sourceNode: node, gesture: gesture)
|
strongSelf.controllers[index].tabBarItemContextAction(sourceNode: node, gesture: gesture)
|
||||||
}
|
}
|
||||||
}, swipeAction: { [weak self] index, direction in
|
}, swipeAction: { [weak self] index, direction in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if index >= 0 && index < strongSelf.tabBarControllerNode.tabBarNode.tabBarItems.count {
|
if index >= 0 && index < strongSelf.tabBarControllerNode.tabBarItems.count {
|
||||||
var index = index
|
|
||||||
if let (cameraItem, _) = strongSelf.cameraItemAndAction {
|
|
||||||
if let cameraItemIndex = strongSelf.tabBarControllerNode.tabBarNode.tabBarItems.firstIndex(where: { $0.item === cameraItem }) {
|
|
||||||
if index == cameraItemIndex {
|
|
||||||
return
|
|
||||||
} else if index > cameraItemIndex {
|
|
||||||
index -= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
strongSelf.controllers[index].tabBarItemSwipeAction(direction: direction)
|
strongSelf.controllers[index].tabBarItemSwipeAction(direction: direction)
|
||||||
}
|
}
|
||||||
}, toolbarActionSelected: { [weak self] action in
|
}, toolbarActionSelected: { [weak self] action in
|
||||||
|
|
@ -339,9 +242,6 @@ open class TabBarControllerImpl: ViewController, TabBarController {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func updateBackgroundAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition) {
|
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.1)
|
|
||||||
transition.updateAlpha(node: self.tabBarControllerNode.tabBarNode.separatorNode, alpha: alpha, delay: 0.1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateSelectedIndex(animated: Bool = false) {
|
private func updateSelectedIndex(animated: Bool = false) {
|
||||||
|
|
@ -354,15 +254,8 @@ open class TabBarControllerImpl: ViewController, TabBarController {
|
||||||
animated = false
|
animated = false
|
||||||
}
|
}
|
||||||
|
|
||||||
var tabBarSelectedIndex = self.selectedIndex
|
let tabBarSelectedIndex = self.selectedIndex
|
||||||
if let (cameraItem, _) = self.cameraItemAndAction {
|
self.tabBarControllerNode.updateSelectedIndex(index: tabBarSelectedIndex)
|
||||||
if let cameraItemIndex = self.tabBarControllerNode.tabBarNode.tabBarItems.firstIndex(where: { $0.item === cameraItem }) {
|
|
||||||
if tabBarSelectedIndex >= cameraItemIndex {
|
|
||||||
tabBarSelectedIndex += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.tabBarControllerNode.tabBarNode.selectedIndex = tabBarSelectedIndex
|
|
||||||
|
|
||||||
var transitionScale: CGFloat = 0.998
|
var transitionScale: CGFloat = 0.998
|
||||||
if let currentView = self.currentController?.view {
|
if let currentView = self.currentController?.view {
|
||||||
|
|
@ -428,26 +321,14 @@ open class TabBarControllerImpl: ViewController, TabBarController {
|
||||||
|
|
||||||
self.validLayout = layout
|
self.validLayout = layout
|
||||||
|
|
||||||
self.tabBarControllerNode.containerLayoutUpdated(layout, toolbar: self.currentController?.toolbar, transition: transition)
|
let bottomInset = self.tabBarControllerNode.containerLayoutUpdated(layout, toolbar: self.currentController?.toolbar, transition: transition)
|
||||||
|
|
||||||
if let currentController = self.currentController {
|
if let currentController = self.currentController {
|
||||||
currentController.view.frame = CGRect(origin: CGPoint(), size: layout.size)
|
currentController.view.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||||
|
|
||||||
var updatedLayout = layout
|
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
|
|
||||||
}
|
|
||||||
if !self.tabBarControllerNode.tabBarHidden {
|
if !self.tabBarControllerNode.tabBarHidden {
|
||||||
updatedLayout.intrinsicInsets.bottom = tabBarHeight
|
updatedLayout.intrinsicInsets.bottom = bottomInset
|
||||||
}
|
}
|
||||||
|
|
||||||
currentController.containerLayoutUpdated(updatedLayout, transition: transition)
|
currentController.containerLayoutUpdated(updatedLayout, transition: transition)
|
||||||
|
|
@ -525,12 +406,9 @@ open class TabBarControllerImpl: ViewController, TabBarController {
|
||||||
}
|
}
|
||||||
self.controllers = controllers
|
self.controllers = controllers
|
||||||
|
|
||||||
var tabBarItems = self.controllers.map({ TabBarNodeItem(item: $0.tabBarItem, contextActionType: $0.tabBarItemContextActionType) })
|
let tabBarItems = self.controllers.map({ TabBarNodeItem(item: $0.tabBarItem, contextActionType: $0.tabBarItemContextActionType) })
|
||||||
if let (cameraItem, _) = self.cameraItemAndAction {
|
|
||||||
tabBarItems.insert(TabBarNodeItem(item: cameraItem, contextActionType: .none), at: Int(floor(CGFloat(controllers.count) / 2)))
|
|
||||||
}
|
|
||||||
|
|
||||||
self.tabBarControllerNode.tabBarNode.tabBarItems = tabBarItems
|
self.tabBarControllerNode.updateTabBarItems(items: tabBarItems)
|
||||||
|
|
||||||
let signals = combineLatest(self.controllers.map({ $0.tabBarItem }).map { tabBarItem -> Signal<Bool, NoError> in
|
let signals = combineLatest(self.controllers.map({ $0.tabBarItem }).map { tabBarItem -> Signal<Bool, NoError> in
|
||||||
if let tabBarItem = tabBarItem, tabBarItem.image == nil {
|
if let tabBarItem = tabBarItem, tabBarItem.image == nil {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import Display
|
||||||
import UIKitRuntimeUtils
|
import UIKitRuntimeUtils
|
||||||
import AnimatedStickerNode
|
import AnimatedStickerNode
|
||||||
import TelegramAnimatedStickerNode
|
import TelegramAnimatedStickerNode
|
||||||
|
import TelegramPresentationData
|
||||||
|
|
||||||
private extension CGRect {
|
private extension CGRect {
|
||||||
var center: CGPoint {
|
var center: CGPoint {
|
||||||
|
|
@ -343,7 +344,7 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
||||||
private let contextAction: (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void
|
private let contextAction: (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void
|
||||||
private let swipeAction: (Int, TabBarItemSwipeDirection) -> Void
|
private let swipeAction: (Int, TabBarItemSwipeDirection) -> Void
|
||||||
|
|
||||||
private var theme: TabBarControllerTheme
|
private var theme: PresentationTheme
|
||||||
private var validLayout: (CGSize, CGFloat, CGFloat, UIEdgeInsets, CGFloat)?
|
private var validLayout: (CGSize, CGFloat, CGFloat, UIEdgeInsets, CGFloat)?
|
||||||
private var horizontal: Bool = false
|
private var horizontal: Bool = false
|
||||||
private var centered: Bool = false
|
private var centered: Bool = false
|
||||||
|
|
@ -351,25 +352,19 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
||||||
private var badgeImage: UIImage
|
private var badgeImage: UIImage
|
||||||
|
|
||||||
let backgroundNode: NavigationBackgroundNode
|
let backgroundNode: NavigationBackgroundNode
|
||||||
let separatorNode: ASDisplayNode
|
|
||||||
private var tabBarNodeContainers: [TabBarNodeContainer] = []
|
private var tabBarNodeContainers: [TabBarNodeContainer] = []
|
||||||
|
|
||||||
private var tapRecognizer: TapLongTapOrDoubleTapGestureRecognizer?
|
private var tapRecognizer: TapLongTapOrDoubleTapGestureRecognizer?
|
||||||
|
|
||||||
init(theme: TabBarControllerTheme, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, contextAction: @escaping (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void, swipeAction: @escaping (Int, TabBarItemSwipeDirection) -> Void) {
|
init(theme: PresentationTheme, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, contextAction: @escaping (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void, swipeAction: @escaping (Int, TabBarItemSwipeDirection) -> Void) {
|
||||||
self.itemSelected = itemSelected
|
self.itemSelected = itemSelected
|
||||||
self.contextAction = contextAction
|
self.contextAction = contextAction
|
||||||
self.swipeAction = swipeAction
|
self.swipeAction = swipeAction
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
|
||||||
self.backgroundNode = NavigationBackgroundNode(color: theme.tabBarBackgroundColor)
|
self.backgroundNode = NavigationBackgroundNode(color: theme.rootController.tabBar.backgroundColor)
|
||||||
|
|
||||||
self.separatorNode = ASDisplayNode()
|
self.badgeImage = generateStretchableFilledCircleImage(diameter: 18.0, color: theme.rootController.tabBar.badgeBackgroundColor, strokeColor: theme.rootController.tabBar.badgeStrokeColor, strokeWidth: 1.0, backgroundColor: nil)!
|
||||||
self.separatorNode.backgroundColor = theme.tabBarSeparatorColor
|
|
||||||
self.separatorNode.isOpaque = true
|
|
||||||
self.separatorNode.isLayerBacked = true
|
|
||||||
|
|
||||||
self.badgeImage = generateStretchableFilledCircleImage(diameter: 18.0, color: theme.tabBarBadgeBackgroundColor, strokeColor: theme.tabBarBadgeStrokeColor, strokeWidth: 1.0, backgroundColor: nil)!
|
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
|
@ -382,7 +377,6 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
||||||
self.isExclusiveTouch = true
|
self.isExclusiveTouch = true
|
||||||
|
|
||||||
self.addSubnode(self.backgroundNode)
|
self.addSubnode(self.backgroundNode)
|
||||||
self.addSubnode(self.separatorNode)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didLoad() {
|
override func didLoad() {
|
||||||
|
|
@ -417,17 +411,16 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateTheme(_ theme: TabBarControllerTheme) {
|
func updateTheme(_ theme: PresentationTheme) {
|
||||||
if self.theme !== theme {
|
if self.theme !== theme {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
|
||||||
self.separatorNode.backgroundColor = theme.tabBarSeparatorColor
|
self.backgroundNode.updateColor(color: theme.rootController.tabBar.backgroundColor, transition: .immediate)
|
||||||
self.backgroundNode.updateColor(color: theme.tabBarBackgroundColor, transition: .immediate)
|
|
||||||
|
|
||||||
self.badgeImage = generateStretchableFilledCircleImage(diameter: 18.0, color: theme.tabBarBadgeBackgroundColor, strokeColor: theme.tabBarBadgeStrokeColor, strokeWidth: 1.0, backgroundColor: nil)!
|
self.badgeImage = generateStretchableFilledCircleImage(diameter: 18.0, color: theme.rootController.tabBar.badgeBackgroundColor, strokeColor: theme.rootController.tabBar.badgeStrokeColor, strokeWidth: 1.0, backgroundColor: nil)!
|
||||||
for container in self.tabBarNodeContainers {
|
for container in self.tabBarNodeContainers {
|
||||||
if let attributedText = container.badgeTextNode.attributedText, !attributedText.string.isEmpty {
|
if let attributedText = container.badgeTextNode.attributedText, !attributedText.string.isEmpty {
|
||||||
container.badgeTextNode.attributedText = NSAttributedString(string: attributedText.string, font: badgeFont, textColor: self.theme.tabBarBadgeTextColor)
|
container.badgeTextNode.attributedText = NSAttributedString(string: attributedText.string, font: badgeFont, textColor: theme.rootController.tabBar.badgeTextColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -443,11 +436,6 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sourceNodesForController(at index: Int) -> [ASDisplayNode]? {
|
|
||||||
let container = self.tabBarNodeContainers[index]
|
|
||||||
return [container.imageNode.imageNode, container.imageNode.textImageNode, container.badgeContainerNode]
|
|
||||||
}
|
|
||||||
|
|
||||||
func frameForControllerTab(at index: Int) -> CGRect? {
|
func frameForControllerTab(at index: Int) -> CGRect? {
|
||||||
let container = self.tabBarNodeContainers[index]
|
let container = self.tabBarNodeContainers[index]
|
||||||
return container.imageNode.frame
|
return container.imageNode.frame
|
||||||
|
|
@ -463,7 +451,7 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
||||||
node.imageNode.removeFromSupernode()
|
node.imageNode.removeFromSupernode()
|
||||||
}
|
}
|
||||||
|
|
||||||
self.centered = self.theme.tabBarTextColor == .clear
|
self.centered = self.theme.rootController.tabBar.textColor == .clear
|
||||||
|
|
||||||
var tabBarNodeContainers: [TabBarNodeContainer] = []
|
var tabBarNodeContainers: [TabBarNodeContainer] = []
|
||||||
for i in 0 ..< self.tabBarItems.count {
|
for i in 0 ..< self.tabBarItems.count {
|
||||||
|
|
@ -484,13 +472,13 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
||||||
self?.swipeAction(i, direction)
|
self?.swipeAction(i, direction)
|
||||||
})
|
})
|
||||||
if item.item.ringSelection {
|
if item.item.ringSelection {
|
||||||
node.ringColor = self.theme.tabBarSelectedIconColor
|
node.ringColor = self.theme.rootController.tabBar.selectedIconColor
|
||||||
} else {
|
} else {
|
||||||
node.ringColor = nil
|
node.ringColor = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if let selectedIndex = self.selectedIndex, selectedIndex == i {
|
if let selectedIndex = self.selectedIndex, selectedIndex == i {
|
||||||
let (textImage, contentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
|
let (textImage, contentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.rootController.tabBar.selectedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
|
||||||
let (image, imageContentWidth): (UIImage, CGFloat)
|
let (image, imageContentWidth): (UIImage, CGFloat)
|
||||||
|
|
||||||
if let _ = item.item.animationName {
|
if let _ = item.item.animationName {
|
||||||
|
|
@ -502,17 +490,17 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
||||||
if !node.isSelected {
|
if !node.isSelected {
|
||||||
node.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: item.item.animationName ?? ""), width: animationSize, height: animationSize, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
|
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.setOverlayColor(self.theme.rootController.tabBar.selectedIconColor, replace: true, animated: false)
|
||||||
node.animationNode.updateLayout(size: CGSize(width: 51.0, height: 51.0))
|
node.animationNode.updateLayout(size: CGSize(width: 51.0, height: 51.0))
|
||||||
} else {
|
} 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)
|
(image, imageContentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.rootController.tabBar.selectedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
|
||||||
|
|
||||||
node.animationNode.isHidden = true
|
node.animationNode.isHidden = true
|
||||||
node.animationNode.visibility = false
|
node.animationNode.visibility = false
|
||||||
}
|
}
|
||||||
|
|
||||||
let (contextTextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
|
let (contextTextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.contextMenu.extractedContentTintColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
|
||||||
let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
|
let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.contextMenu.extractedContentTintColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
|
||||||
node.textImageNode.image = textImage
|
node.textImageNode.image = textImage
|
||||||
node.imageNode.image = image
|
node.imageNode.image = image
|
||||||
node.contextTextImageNode.image = contextTextImage
|
node.contextTextImageNode.image = contextTextImage
|
||||||
|
|
@ -522,10 +510,10 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
||||||
node.contentWidth = max(contentWidth, imageContentWidth)
|
node.contentWidth = max(contentWidth, imageContentWidth)
|
||||||
node.isSelected = true
|
node.isSelected = true
|
||||||
} else {
|
} else {
|
||||||
let (textImage, contentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
|
let (textImage, contentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.rootController.tabBar.textColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
|
||||||
let (image, imageContentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
|
let (image, imageContentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.rootController.tabBar.iconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
|
||||||
let (contextTextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
|
let (contextTextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.contextMenu.extractedContentTintColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
|
||||||
let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
|
let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.contextMenu.extractedContentTintColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
|
||||||
|
|
||||||
node.animationNode.isHidden = true
|
node.animationNode.isHidden = true
|
||||||
node.animationNode.visibility = false
|
node.animationNode.visibility = false
|
||||||
|
|
@ -555,10 +543,10 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
||||||
let node = self.tabBarNodeContainers[index].imageNode
|
let node = self.tabBarNodeContainers[index].imageNode
|
||||||
let item = self.tabBarItems[index]
|
let item = self.tabBarItems[index]
|
||||||
|
|
||||||
self.centered = self.theme.tabBarTextColor == .clear
|
self.centered = self.theme.rootController.tabBar.textColor == .clear
|
||||||
|
|
||||||
if item.item.ringSelection {
|
if item.item.ringSelection {
|
||||||
node.ringColor = self.theme.tabBarSelectedIconColor
|
node.ringColor = self.theme.rootController.tabBar.selectedIconColor
|
||||||
} else {
|
} else {
|
||||||
node.ringColor = nil
|
node.ringColor = nil
|
||||||
}
|
}
|
||||||
|
|
@ -566,7 +554,7 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
||||||
let previousImageSize = node.imageNode.image?.size ?? CGSize()
|
let previousImageSize = node.imageNode.image?.size ?? CGSize()
|
||||||
let previousTextImageSize = node.textImageNode.image?.size ?? CGSize()
|
let previousTextImageSize = node.textImageNode.image?.size ?? CGSize()
|
||||||
if let selectedIndex = self.selectedIndex, selectedIndex == index {
|
if let selectedIndex = self.selectedIndex, selectedIndex == index {
|
||||||
let (textImage, contentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
|
let (textImage, contentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.rootController.tabBar.selectedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
|
||||||
let (image, imageContentWidth): (UIImage, CGFloat)
|
let (image, imageContentWidth): (UIImage, CGFloat)
|
||||||
if let _ = item.item.animationName {
|
if let _ = item.item.animationName {
|
||||||
(image, imageContentWidth) = (UIImage(), 0.0)
|
(image, imageContentWidth) = (UIImage(), 0.0)
|
||||||
|
|
@ -577,21 +565,21 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
||||||
if !node.isSelected {
|
if !node.isSelected {
|
||||||
node.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: item.item.animationName ?? ""), width: animationSize, height: animationSize, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
|
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.setOverlayColor(self.theme.rootController.tabBar.selectedIconColor, replace: true, animated: false)
|
||||||
node.animationNode.updateLayout(size: CGSize(width: 51.0, height: 51.0))
|
node.animationNode.updateLayout(size: CGSize(width: 51.0, height: 51.0))
|
||||||
} else {
|
} else {
|
||||||
if item.item.ringSelection {
|
if item.item.ringSelection {
|
||||||
(image, imageContentWidth) = (item.item.selectedImage ?? UIImage(), item.item.selectedImage?.size.width ?? 0.0)
|
(image, imageContentWidth) = (item.item.selectedImage ?? UIImage(), item.item.selectedImage?.size.width ?? 0.0)
|
||||||
} else {
|
} 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)
|
(image, imageContentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.rootController.tabBar.selectedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
|
||||||
}
|
}
|
||||||
|
|
||||||
node.animationNode.isHidden = true
|
node.animationNode.isHidden = true
|
||||||
node.animationNode.visibility = false
|
node.animationNode.visibility = false
|
||||||
}
|
}
|
||||||
|
|
||||||
let (contextTextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
|
let (contextTextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.contextMenu.extractedContentTintColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
|
||||||
let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
|
let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.contextMenu.extractedContentTintColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
|
||||||
node.textImageNode.image = textImage
|
node.textImageNode.image = textImage
|
||||||
node.accessibilityLabel = item.item.title
|
node.accessibilityLabel = item.item.title
|
||||||
node.accessibilityTraits = [.button, .selected]
|
node.accessibilityTraits = [.button, .selected]
|
||||||
|
|
@ -610,16 +598,16 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let (textImage, contentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
|
let (textImage, contentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.rootController.tabBar.textColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
|
||||||
|
|
||||||
let (image, imageContentWidth): (UIImage, CGFloat)
|
let (image, imageContentWidth): (UIImage, CGFloat)
|
||||||
if item.item.ringSelection {
|
if item.item.ringSelection {
|
||||||
(image, imageContentWidth) = (item.item.image ?? UIImage(), item.item.image?.size.width ?? 0.0)
|
(image, imageContentWidth) = (item.item.image ?? UIImage(), item.item.image?.size.width ?? 0.0)
|
||||||
} else {
|
} else {
|
||||||
(image, imageContentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
|
(image, imageContentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.rootController.tabBar.iconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
|
||||||
}
|
}
|
||||||
let (contextTextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
|
let (contextTextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.contextMenu.extractedContentTintColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
|
||||||
let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
|
let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.contextMenu.extractedContentTintColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
|
||||||
|
|
||||||
node.animationNode.stop()
|
node.animationNode.stop()
|
||||||
node.animationNode.isHidden = true
|
node.animationNode.isHidden = true
|
||||||
|
|
@ -672,8 +660,6 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
||||||
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size))
|
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
self.backgroundNode.update(size: size, transition: transition)
|
self.backgroundNode.update(size: size, transition: transition)
|
||||||
|
|
||||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: separatorHeight)))
|
|
||||||
|
|
||||||
let horizontal = !leftInset.isZero
|
let horizontal = !leftInset.isZero
|
||||||
if self.horizontal != horizontal {
|
if self.horizontal != horizontal {
|
||||||
self.horizontal = horizontal
|
self.horizontal = horizontal
|
||||||
|
|
@ -759,7 +745,7 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
||||||
if container.badgeValue != container.appliedBadgeValue {
|
if container.badgeValue != container.appliedBadgeValue {
|
||||||
container.appliedBadgeValue = container.badgeValue
|
container.appliedBadgeValue = container.badgeValue
|
||||||
if let badgeValue = container.badgeValue, !badgeValue.isEmpty {
|
if let badgeValue = container.badgeValue, !badgeValue.isEmpty {
|
||||||
container.badgeTextNode.attributedText = NSAttributedString(string: badgeValue, font: badgeFont, textColor: self.theme.tabBarBadgeTextColor)
|
container.badgeTextNode.attributedText = NSAttributedString(string: badgeValue, font: badgeFont, textColor: self.theme.rootController.tabBar.badgeTextColor)
|
||||||
container.badgeContainerNode.isHidden = false
|
container.badgeContainerNode.isHidden = false
|
||||||
} else {
|
} else {
|
||||||
container.badgeContainerNode.isHidden = true
|
container.badgeContainerNode.isHidden = true
|
||||||
|
|
@ -797,7 +783,6 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var closestNode: (Int, CGFloat)?
|
var closestNode: (Int, CGFloat)?
|
||||||
|
|
||||||
for i in 0 ..< self.tabBarNodeContainers.count {
|
for i in 0 ..< self.tabBarNodeContainers.count {
|
||||||
let node = self.tabBarNodeContainers[i].imageNode
|
let node = self.tabBarNodeContainers[i].imageNode
|
||||||
if !node.isUserInteractionEnabled {
|
if !node.isUserInteractionEnabled {
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import PresentationDataUtils
|
||||||
import UIKitRuntimeUtils
|
import UIKitRuntimeUtils
|
||||||
import ReplayKit
|
import ReplayKit
|
||||||
|
|
||||||
private let accentColor: UIColor = UIColor(rgb: 0x007aff)
|
private let accentColor: UIColor = UIColor(rgb: 0x0088ff)
|
||||||
|
|
||||||
protocol PreviewVideoNode: ASDisplayNode {
|
protocol PreviewVideoNode: ASDisplayNode {
|
||||||
var ready: Signal<Bool, NoError> { get }
|
var ready: Signal<Bool, NoError> { get }
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ private let videoCornerRadius: CGFloat = 23.0
|
||||||
private let avatarSize: CGFloat = 50.0
|
private let avatarSize: CGFloat = 50.0
|
||||||
private let videoSize = CGSize(width: 180.0, height: 180.0)
|
private let videoSize = CGSize(width: 180.0, height: 180.0)
|
||||||
|
|
||||||
private let accentColor: UIColor = UIColor(rgb: 0x007aff)
|
private let accentColor: UIColor = UIColor(rgb: 0x0088ff)
|
||||||
private let constructiveColor: UIColor = UIColor(rgb: 0x34c759)
|
private let constructiveColor: UIColor = UIColor(rgb: 0x34c759)
|
||||||
private let destructiveColor: UIColor = UIColor(rgb: 0xff3b30)
|
private let destructiveColor: UIColor = UIColor(rgb: 0xff3b30)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ private let tileSize = CGSize(width: 84.0, height: 84.0)
|
||||||
private let backgroundCornerRadius: CGFloat = 14.0
|
private let backgroundCornerRadius: CGFloat = 14.0
|
||||||
private let avatarSize: CGFloat = 40.0
|
private let avatarSize: CGFloat = 40.0
|
||||||
|
|
||||||
private let accentColor: UIColor = UIColor(rgb: 0x007aff)
|
private let accentColor: UIColor = UIColor(rgb: 0x0088ff)
|
||||||
private let constructiveColor: UIColor = UIColor(rgb: 0x34c759)
|
private let constructiveColor: UIColor = UIColor(rgb: 0x34c759)
|
||||||
private let destructiveColor: UIColor = UIColor(rgb: 0xff3b30)
|
private let destructiveColor: UIColor = UIColor(rgb: 0xff3b30)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import SolidRoundedButtonNode
|
||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
import VoiceChatActionButton
|
import VoiceChatActionButton
|
||||||
|
|
||||||
private let accentColor: UIColor = UIColor(rgb: 0x007aff)
|
private let accentColor: UIColor = UIColor(rgb: 0x0088ff)
|
||||||
|
|
||||||
final class VoiceChatRecordingSetupController: ViewController {
|
final class VoiceChatRecordingSetupController: ViewController {
|
||||||
private var controllerNode: VoiceChatRecordingSetupControllerNode {
|
private var controllerNode: VoiceChatRecordingSetupControllerNode {
|
||||||
|
|
|
||||||
|
|
@ -561,7 +561,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
|
||||||
outgoing: PresentationThemePartedColors(
|
outgoing: PresentationThemePartedColors(
|
||||||
bubble: PresentationThemeBubbleColor(
|
bubble: PresentationThemeBubbleColor(
|
||||||
withWallpaper: PresentationThemeBubbleColorComponents(
|
withWallpaper: PresentationThemeBubbleColorComponents(
|
||||||
fill: [UIColor(rgb: 0x61BCF9), UIColor(rgb: 0x007AFF)],
|
fill: [UIColor(rgb: 0x61BCF9), UIColor(rgb: 0x0088ff)],
|
||||||
highlightedFill: UIColor(rgb: 0x61BCF9),
|
highlightedFill: UIColor(rgb: 0x61BCF9),
|
||||||
stroke: .clear,
|
stroke: .clear,
|
||||||
shadow: nil,
|
shadow: nil,
|
||||||
|
|
@ -577,7 +577,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
|
||||||
reactionActiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1)
|
reactionActiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1)
|
||||||
),
|
),
|
||||||
withoutWallpaper: PresentationThemeBubbleColorComponents(
|
withoutWallpaper: PresentationThemeBubbleColorComponents(
|
||||||
fill: [UIColor(rgb: 0x61BCF9), UIColor(rgb: 0x007AFF)],
|
fill: [UIColor(rgb: 0x61BCF9), UIColor(rgb: 0x0088ff)],
|
||||||
highlightedFill: UIColor(rgb: 0x61BCF9),
|
highlightedFill: UIColor(rgb: 0x61BCF9),
|
||||||
stroke: .clear,
|
stroke: .clear,
|
||||||
shadow: nil,
|
shadow: nil,
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ public func dateFillNeedsBlur(theme: PresentationTheme, wallpaper: TelegramWallp
|
||||||
|
|
||||||
public let defaultServiceBackgroundColor = UIColor(rgb: 0x000000, alpha: 0.2)
|
public let defaultServiceBackgroundColor = UIColor(rgb: 0x000000, alpha: 0.2)
|
||||||
public let defaultPresentationTheme = makeDefaultDayPresentationTheme(serviceBackgroundColor: defaultServiceBackgroundColor, day: false, preview: false)
|
public let defaultPresentationTheme = makeDefaultDayPresentationTheme(serviceBackgroundColor: defaultServiceBackgroundColor, day: false, preview: false)
|
||||||
public let defaultDayAccentColor = UIColor(rgb: 0x007aff)
|
public let defaultDayAccentColor = UIColor(rgb: 0x0088ff)
|
||||||
|
|
||||||
public func customizeDefaultDayTheme(theme: PresentationTheme, editing: Bool, title: String?, accentColor: UIColor?, outgoingAccentColor: UIColor?, backgroundColors: [UInt32], bubbleColors: [UInt32], animateBubbleColors: Bool?, wallpaper forcedWallpaper: TelegramWallpaper? = nil, serviceBackgroundColor: UIColor?) -> PresentationTheme {
|
public func customizeDefaultDayTheme(theme: PresentationTheme, editing: Bool, title: String?, accentColor: UIColor?, outgoingAccentColor: UIColor?, backgroundColors: [UInt32], bubbleColors: [UInt32], animateBubbleColors: Bool?, wallpaper forcedWallpaper: TelegramWallpaper? = nil, serviceBackgroundColor: UIColor?) -> PresentationTheme {
|
||||||
if (theme.referenceTheme != .day && theme.referenceTheme != .dayClassic) {
|
if (theme.referenceTheme != .day && theme.referenceTheme != .dayClassic) {
|
||||||
|
|
@ -436,7 +436,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio
|
||||||
separatorColor: UIColor(rgb: 0xb2b2b2),
|
separatorColor: UIColor(rgb: 0xb2b2b2),
|
||||||
iconColor: UIColor(rgb: 0x959595),
|
iconColor: UIColor(rgb: 0x959595),
|
||||||
selectedIconColor: defaultDayAccentColor,
|
selectedIconColor: defaultDayAccentColor,
|
||||||
textColor: UIColor(rgb: 0x959595),
|
textColor: UIColor(rgb: 0x000000, alpha: 0.8),
|
||||||
selectedTextColor: defaultDayAccentColor,
|
selectedTextColor: defaultDayAccentColor,
|
||||||
badgeBackgroundColor: UIColor(rgb: 0xff3b30),
|
badgeBackgroundColor: UIColor(rgb: 0xff3b30),
|
||||||
badgeStrokeColor: UIColor(rgb: 0xff3b30),
|
badgeStrokeColor: UIColor(rgb: 0xff3b30),
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,7 @@ public struct PresentationResourcesSettings {
|
||||||
context.addPath(path.cgPath)
|
context.addPath(path.cgPath)
|
||||||
context.clip()
|
context.clip()
|
||||||
|
|
||||||
context.setFillColor(UIColor(rgb: 0x007aff).cgColor)
|
context.setFillColor(UIColor(rgb: 0x0088ff).cgColor)
|
||||||
context.fill(bounds)
|
context.fill(bounds)
|
||||||
|
|
||||||
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Bot"), color: UIColor(rgb: 0xffffff)), let cgImage = image.cgImage {
|
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Bot"), color: UIColor(rgb: 0xffffff)), let cgImage = image.cgImage {
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,21 @@ import RasterizedCompositionComponent
|
||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
|
|
||||||
public final class BadgeComponent: Component {
|
public final class BadgeComponent: Component {
|
||||||
|
public enum CornerRadius: Equatable {
|
||||||
|
case automatic
|
||||||
|
case custom(CGFloat)
|
||||||
|
}
|
||||||
|
|
||||||
public let text: String
|
public let text: String
|
||||||
public let font: UIFont
|
public let font: UIFont
|
||||||
public let cornerRadius: CGFloat
|
public let cornerRadius: CornerRadius
|
||||||
public let insets: UIEdgeInsets
|
public let insets: UIEdgeInsets
|
||||||
public let outerInsets: UIEdgeInsets
|
public let outerInsets: UIEdgeInsets
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
text: String,
|
text: String,
|
||||||
font: UIFont,
|
font: UIFont,
|
||||||
cornerRadius: CGFloat,
|
cornerRadius: CornerRadius,
|
||||||
insets: UIEdgeInsets,
|
insets: UIEdgeInsets,
|
||||||
outerInsets: UIEdgeInsets
|
outerInsets: UIEdgeInsets
|
||||||
) {
|
) {
|
||||||
|
|
@ -144,12 +149,6 @@ public final class BadgeComponent: Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if component.cornerRadius != previousComponent?.cornerRadius {
|
|
||||||
self.backgroundLayer.image = generateStretchableFilledCircleImage(diameter: component.cornerRadius * 2.0, color: .white)
|
|
||||||
|
|
||||||
self.backgroundInsetLayer.image = generateStretchableFilledCircleImage(diameter: component.cornerRadius * 2.0, color: .black)
|
|
||||||
}
|
|
||||||
|
|
||||||
let textSize = self.textLayout?.size ?? CGSize(width: 1.0, height: 1.0)
|
let textSize = self.textLayout?.size ?? CGSize(width: 1.0, height: 1.0)
|
||||||
|
|
||||||
let size = CGSize(width: textSize.width + component.insets.left + component.insets.right, height: textSize.height + component.insets.top + component.insets.bottom)
|
let size = CGSize(width: textSize.width + component.insets.left + component.insets.right, height: textSize.height + component.insets.top + component.insets.bottom)
|
||||||
|
|
@ -170,6 +169,19 @@ public final class BadgeComponent: Component {
|
||||||
transition.setPosition(layer: self.textContentsLayer, position: textFrame.origin)
|
transition.setPosition(layer: self.textContentsLayer, position: textFrame.origin)
|
||||||
self.textContentsLayer.bounds = CGRect(origin: CGPoint(), size: textFrame.size)
|
self.textContentsLayer.bounds = CGRect(origin: CGPoint(), size: textFrame.size)
|
||||||
|
|
||||||
|
if component.cornerRadius != previousComponent?.cornerRadius {
|
||||||
|
let cornerRadius: CGFloat
|
||||||
|
switch component.cornerRadius {
|
||||||
|
case let .custom(value):
|
||||||
|
cornerRadius = value
|
||||||
|
case .automatic:
|
||||||
|
cornerRadius = floor(min(size.width, size.height) * 0.5)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.backgroundLayer.image = generateStretchableFilledCircleImage(diameter: cornerRadius * 2.0, color: .white)
|
||||||
|
self.backgroundInsetLayer.image = generateStretchableFilledCircleImage(diameter: cornerRadius * 2.0, color: .black)
|
||||||
|
}
|
||||||
|
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -113,9 +113,9 @@ final class PlaceholderComponent: Component {
|
||||||
component: AnyComponent(
|
component: AnyComponent(
|
||||||
ButtonComponent(
|
ButtonComponent(
|
||||||
background: ButtonComponent.Background(
|
background: ButtonComponent.Background(
|
||||||
color: UIColor(rgb: 0x007aff),
|
color: UIColor(rgb: 0x0088ff),
|
||||||
foreground: .white,
|
foreground: .white,
|
||||||
pressedColor: UIColor(rgb: 0x007aff, alpha: 0.55)
|
pressedColor: UIColor(rgb: 0x0088ff, alpha: 0.55)
|
||||||
),
|
),
|
||||||
content: AnyComponentWithIdentity(
|
content: AnyComponentWithIdentity(
|
||||||
id: buttonTitle,
|
id: buttonTitle,
|
||||||
|
|
|
||||||
|
|
@ -718,7 +718,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||||
if self.visibility != oldValue {
|
if self.visibility != oldValue {
|
||||||
self.visibilityStatus = self.visibility != .none
|
self.visibilityStatus = self.visibility != .none
|
||||||
|
|
||||||
self.updateVisibility()
|
self.updateVisibility(isScroll: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -743,6 +743,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||||
typealias Params = (item: ChatMessageItem, params: ListViewItemLayoutParams, mergedTop: ChatMessageMerge, mergedBottom: ChatMessageMerge, dateHeaderAtBottom: ChatMessageHeaderSpec)
|
typealias Params = (item: ChatMessageItem, params: ListViewItemLayoutParams, mergedTop: ChatMessageMerge, mergedBottom: ChatMessageMerge, dateHeaderAtBottom: ChatMessageHeaderSpec)
|
||||||
private var currentInputParams: Params?
|
private var currentInputParams: Params?
|
||||||
private var currentApplyParams: ListViewItemApply?
|
private var currentApplyParams: ListViewItemApply?
|
||||||
|
private var contentLayoutInsets = UIEdgeInsets()
|
||||||
|
|
||||||
required public init(rotated: Bool) {
|
required public init(rotated: Bool) {
|
||||||
self.mainContextSourceNode = ContextExtractedContentContainingNode()
|
self.mainContextSourceNode = ContextExtractedContentContainingNode()
|
||||||
|
|
@ -3338,7 +3339,9 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let layout = ListViewItemNodeLayout(contentSize: layoutSize, insets: layoutInsets)
|
layoutSize.height += layoutInsets.top + layoutInsets.bottom
|
||||||
|
|
||||||
|
let layout = ListViewItemNodeLayout(contentSize: layoutSize, insets: UIEdgeInsets())
|
||||||
|
|
||||||
let graphics = PresentationResourcesChat.principalGraphics(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper, bubbleCorners: item.presentationData.chatBubbleCorners)
|
let graphics = PresentationResourcesChat.principalGraphics(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper, bubbleCorners: item.presentationData.chatBubbleCorners)
|
||||||
|
|
||||||
|
|
@ -3364,6 +3367,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||||
params: params,
|
params: params,
|
||||||
applyInfo: applyInfo,
|
applyInfo: applyInfo,
|
||||||
layout: layout,
|
layout: layout,
|
||||||
|
layoutInsets: layoutInsets,
|
||||||
item: item,
|
item: item,
|
||||||
forwardSource: forwardSource,
|
forwardSource: forwardSource,
|
||||||
forwardAuthorSignature: forwardAuthorSignature,
|
forwardAuthorSignature: forwardAuthorSignature,
|
||||||
|
|
@ -3377,12 +3381,12 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||||
graphics: graphics,
|
graphics: graphics,
|
||||||
presentationContext: item.controllerInteraction.presentationContext,
|
presentationContext: item.controllerInteraction.presentationContext,
|
||||||
bubbleContentWidth: bubbleContentWidth,
|
bubbleContentWidth: bubbleContentWidth,
|
||||||
backgroundFrame: backgroundFrame,
|
backgroundFrame: backgroundFrame.offsetBy(dx: 0.0, dy: layoutInsets.top),
|
||||||
deliveryFailedInset: deliveryFailedInset,
|
deliveryFailedInset: deliveryFailedInset,
|
||||||
nameNodeSizeApply: nameNodeSizeApply,
|
nameNodeSizeApply: nameNodeSizeApply,
|
||||||
viaWidth: viaWidth,
|
viaWidth: viaWidth,
|
||||||
contentOrigin: contentOrigin,
|
contentOrigin: contentOrigin.offsetBy(dx: 0.0, dy: layoutInsets.top),
|
||||||
nameNodeOriginY: nameNodeOriginY + detachedContentNodesHeight + additionalTopHeight,
|
nameNodeOriginY: layoutInsets.top + nameNodeOriginY + detachedContentNodesHeight + additionalTopHeight,
|
||||||
hasTitleAvatar: hasTitleAvatar,
|
hasTitleAvatar: hasTitleAvatar,
|
||||||
hasTitleTopicNavigation: hasTitleTopicNavigation,
|
hasTitleTopicNavigation: hasTitleTopicNavigation,
|
||||||
authorNameColor: authorNameColor,
|
authorNameColor: authorNameColor,
|
||||||
|
|
@ -3392,25 +3396,27 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||||
boostNodeSizeApply: boostNodeSizeApply,
|
boostNodeSizeApply: boostNodeSizeApply,
|
||||||
contentUpperRightCorner: contentUpperRightCorner,
|
contentUpperRightCorner: contentUpperRightCorner,
|
||||||
threadInfoSizeApply: threadInfoSizeApply,
|
threadInfoSizeApply: threadInfoSizeApply,
|
||||||
threadInfoOriginY: threadInfoOriginY + detachedContentNodesHeight + additionalTopHeight,
|
threadInfoOriginY: layoutInsets.top + threadInfoOriginY + detachedContentNodesHeight + additionalTopHeight,
|
||||||
forwardInfoSizeApply: forwardInfoSizeApply,
|
forwardInfoSizeApply: forwardInfoSizeApply,
|
||||||
forwardInfoOriginY: forwardInfoOriginY + detachedContentNodesHeight + additionalTopHeight,
|
forwardInfoOriginY: layoutInsets.top + forwardInfoOriginY + detachedContentNodesHeight + additionalTopHeight,
|
||||||
replyInfoSizeApply: replyInfoSizeApply,
|
replyInfoSizeApply: replyInfoSizeApply,
|
||||||
replyInfoOriginY: replyInfoOriginY + detachedContentNodesHeight + additionalTopHeight,
|
replyInfoOriginY: layoutInsets.top + replyInfoOriginY + detachedContentNodesHeight + additionalTopHeight,
|
||||||
removedContentNodeIndices: removedContentNodeIndices,
|
removedContentNodeIndices: removedContentNodeIndices,
|
||||||
updatedContentNodeOrder: updatedContentNodeOrder,
|
updatedContentNodeOrder: updatedContentNodeOrder,
|
||||||
addedContentNodes: addedContentNodes,
|
addedContentNodes: addedContentNodes,
|
||||||
contentNodeMessagesAndClasses: contentNodeMessagesAndClasses,
|
contentNodeMessagesAndClasses: contentNodeMessagesAndClasses,
|
||||||
contentNodeFramesPropertiesAndApply: contentNodeFramesPropertiesAndApply,
|
contentNodeFramesPropertiesAndApply: contentNodeFramesPropertiesAndApply,
|
||||||
contentContainerNodeFrames: contentContainerNodeFrames,
|
contentContainerNodeFrames: contentContainerNodeFrames.map { containerGroupId, containerFrame, currentItemSelection, currentContainerGroupOverlap in
|
||||||
mosaicStatusOrigin: mosaicStatusOrigin,
|
return (containerGroupId, containerFrame.offsetBy(dx: 0.0, dy: layoutInsets.top), currentItemSelection, currentContainerGroupOverlap)
|
||||||
|
},
|
||||||
|
mosaicStatusOrigin: mosaicStatusOrigin?.offsetBy(dx: 0.0, dy: layoutInsets.top),
|
||||||
mosaicStatusSizeAndApply: mosaicStatusSizeAndApply,
|
mosaicStatusSizeAndApply: mosaicStatusSizeAndApply,
|
||||||
unlockButtonPosition: unlockButtonPosition,
|
unlockButtonPosition: unlockButtonPosition?.offsetBy(dx: 0.0, dy: layoutInsets.top),
|
||||||
unlockButtonSizeAndApply: unlockButtonSizeApply,
|
unlockButtonSizeAndApply: unlockButtonSizeApply,
|
||||||
mediaInfoOrigin: mediaInfoOrigin,
|
mediaInfoOrigin: mediaInfoOrigin?.offsetBy(dx: 0.0, dy: layoutInsets.top),
|
||||||
mediaInfoSizeAndApply: mediaInfoSizeApply,
|
mediaInfoSizeAndApply: mediaInfoSizeApply,
|
||||||
needsShareButton: needsShareButton,
|
needsShareButton: needsShareButton,
|
||||||
shareButtonOffset: shareButtonOffset,
|
shareButtonOffset: shareButtonOffset?.offsetBy(dx: 0.0, dy: layoutInsets.top),
|
||||||
avatarOffset: avatarOffset,
|
avatarOffset: avatarOffset,
|
||||||
hidesHeaders: hidesHeaders,
|
hidesHeaders: hidesHeaders,
|
||||||
disablesComments: disablesComments,
|
disablesComments: disablesComments,
|
||||||
|
|
@ -3428,6 +3434,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||||
params: ListViewItemLayoutParams,
|
params: ListViewItemLayoutParams,
|
||||||
applyInfo: ListViewItemApply,
|
applyInfo: ListViewItemApply,
|
||||||
layout: ListViewItemNodeLayout,
|
layout: ListViewItemNodeLayout,
|
||||||
|
layoutInsets: UIEdgeInsets,
|
||||||
item: ChatMessageItem,
|
item: ChatMessageItem,
|
||||||
forwardSource: Peer?,
|
forwardSource: Peer?,
|
||||||
forwardAuthorSignature: String?,
|
forwardAuthorSignature: String?,
|
||||||
|
|
@ -3488,6 +3495,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||||
|
|
||||||
strongSelf.currentInputParams = inputParams
|
strongSelf.currentInputParams = inputParams
|
||||||
strongSelf.currentApplyParams = applyInfo
|
strongSelf.currentApplyParams = applyInfo
|
||||||
|
strongSelf.contentLayoutInsets = layoutInsets
|
||||||
|
|
||||||
if item.message.id.namespace == Namespaces.Message.Local || item.message.id.namespace == Namespaces.Message.ScheduledLocal || item.message.id.namespace == Namespaces.Message.QuickReplyLocal {
|
if item.message.id.namespace == Namespaces.Message.Local || item.message.id.namespace == Namespaces.Message.ScheduledLocal || item.message.id.namespace == Namespaces.Message.QuickReplyLocal {
|
||||||
strongSelf.wasPending = true
|
strongSelf.wasPending = true
|
||||||
|
|
@ -3570,7 +3578,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||||
strongSelf.suggestedPostInfoNode = suggestedPostInfoNode
|
strongSelf.suggestedPostInfoNode = suggestedPostInfoNode
|
||||||
strongSelf.addSubnode(suggestedPostInfoNode)
|
strongSelf.addSubnode(suggestedPostInfoNode)
|
||||||
}
|
}
|
||||||
let suggestedPostInfoFrame = CGRect(origin: CGPoint(x: floor((params.width - suggestedPostInfoSize.width) * 0.5), y: 4.0), size: suggestedPostInfoSize)
|
let suggestedPostInfoFrame = CGRect(origin: CGPoint(x: floor((params.width - suggestedPostInfoSize.width) * 0.5), y: layoutInsets.top + 4.0), size: suggestedPostInfoSize)
|
||||||
suggestedPostInfoNode.frame = suggestedPostInfoFrame
|
suggestedPostInfoNode.frame = suggestedPostInfoFrame
|
||||||
} else if let suggestedPostInfoNode = strongSelf.suggestedPostInfoNode {
|
} else if let suggestedPostInfoNode = strongSelf.suggestedPostInfoNode {
|
||||||
strongSelf.suggestedPostInfoNode = nil
|
strongSelf.suggestedPostInfoNode = nil
|
||||||
|
|
@ -4999,7 +5007,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||||
|
|
||||||
strongSelf.updateSearchTextHighlightState()
|
strongSelf.updateSearchTextHighlightState()
|
||||||
|
|
||||||
strongSelf.updateVisibility()
|
strongSelf.updateVisibility(isScroll: false)
|
||||||
|
|
||||||
if let (_, f) = strongSelf.awaitingAppliedReaction {
|
if let (_, f) = strongSelf.awaitingAppliedReaction {
|
||||||
strongSelf.awaitingAppliedReaction = nil
|
strongSelf.awaitingAppliedReaction = nil
|
||||||
|
|
@ -6332,10 +6340,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||||
if let swipeToReplyNode = self.swipeToReplyNode {
|
if let swipeToReplyNode = self.swipeToReplyNode {
|
||||||
if translation.x < 0.0 {
|
if translation.x < 0.0 {
|
||||||
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
|
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
|
||||||
swipeToReplyNode.position = CGPoint(x: bounds.size.width + offset + 33.0 * 0.5, y: self.contentSize.height / 2.0)
|
swipeToReplyNode.position = CGPoint(x: bounds.size.width + offset + 33.0 * 0.5, y: self.contentLayoutInsets.top + (self.contentSize.height - self.contentLayoutInsets.top - self.contentLayoutInsets.bottom) / 2.0)
|
||||||
} else {
|
} else {
|
||||||
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
|
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
|
||||||
swipeToReplyNode.position = CGPoint(x: leftOffset - 33.0 * 0.5, y: self.contentSize.height / 2.0)
|
swipeToReplyNode.position = CGPoint(x: leftOffset - 33.0 * 0.5, y: self.contentLayoutInsets.top + (self.contentSize.height - self.contentLayoutInsets.top - self.contentLayoutInsets.bottom) / 2.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let (rect, containerSize) = self.absoluteRect {
|
if let (rect, containerSize) = self.absoluteRect {
|
||||||
|
|
@ -6561,7 +6569,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||||
contentNode.unreadMessageRangeUpdated()
|
contentNode.unreadMessageRangeUpdated()
|
||||||
}
|
}
|
||||||
|
|
||||||
self.updateVisibility()
|
self.updateVisibility(isScroll: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func animateQuizInvalidOptionSelected() {
|
public func animateQuizInvalidOptionSelected() {
|
||||||
|
|
@ -6761,10 +6769,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||||
|
|
||||||
override public func updateStickerSettings(forceStopAnimations: Bool) {
|
override public func updateStickerSettings(forceStopAnimations: Bool) {
|
||||||
self.forceStopAnimations = forceStopAnimations
|
self.forceStopAnimations = forceStopAnimations
|
||||||
self.updateVisibility()
|
self.updateVisibility(isScroll: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateVisibility() {
|
private func updateVisibility(isScroll: Bool) {
|
||||||
guard let item = self.item else {
|
guard let item = self.item else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -6841,6 +6849,15 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||||
self.playMessageEffect(force: false)
|
self.playMessageEffect(force: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if item.message.adAttribute != nil {
|
||||||
|
let transition: ContainedViewLayoutTransition = isScroll ? .animated(duration: 0.4, curve: .spring) : .immediate
|
||||||
|
if case let .visible(_, rect) = self.visibility, rect.height >= 1.0 {
|
||||||
|
transition.updateSublayerTransformOffset(layer: self.layer, offset: CGPoint(x: 0.0, y: 0.0))
|
||||||
|
} else {
|
||||||
|
transition.updateSublayerTransformOffset(layer: self.layer, offset: CGPoint(x: 0.0, y: 200.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func messageEffectTargetView() -> UIView? {
|
override public func messageEffectTargetView() -> UIView? {
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ final class PlayButtonNode: ASDisplayNode {
|
||||||
|
|
||||||
let backgroundFrame = buttonSize.centered(in: CGRect(origin: .zero, size: size))
|
let backgroundFrame = buttonSize.centered(in: CGRect(origin: .zero, size: size))
|
||||||
transition.updateFrame(view: self.backgroundView, frame: backgroundFrame)
|
transition.updateFrame(view: self.backgroundView, frame: backgroundFrame)
|
||||||
self.backgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.4), transition: ComponentTransition(transition))
|
self.backgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.4)), transition: ComponentTransition(transition))
|
||||||
|
|
||||||
self.playPauseIconNode.frame = CGRect(origin: CGPoint(x: 3.0, y: 1.0 - UIScreenPixel), size: CGSize(width: 21.0, height: 21.0))
|
self.playPauseIconNode.frame = CGRect(origin: CGPoint(x: 3.0, y: 1.0 - UIScreenPixel), size: CGSize(width: 21.0, height: 21.0))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ public final class ChatRecordingViewOnceButtonNode: HighlightTrackingButtonNode
|
||||||
|
|
||||||
let backgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(size.width / 2.0 - innerSize.width / 2.0), y: floorToScreenPixels(size.height / 2.0 - innerSize.height / 2.0)), size: innerSize)
|
let backgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(size.width / 2.0 - innerSize.width / 2.0), y: floorToScreenPixels(size.height / 2.0 - innerSize.height / 2.0)), size: innerSize)
|
||||||
self.backgroundView.frame = backgroundFrame
|
self.backgroundView.frame = backgroundFrame
|
||||||
self.backgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65), transition: .immediate)
|
self.backgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65)), transition: .immediate)
|
||||||
|
|
||||||
if let iconImage = self.iconNode.image {
|
if let iconImage = self.iconNode.image {
|
||||||
let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(size.width / 2.0 - iconImage.size.width / 2.0), y: floorToScreenPixels(size.height / 2.0 - iconImage.size.height / 2.0)), size: iconImage.size)
|
let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(size.width / 2.0 - iconImage.size.width / 2.0), y: floorToScreenPixels(size.height / 2.0 - iconImage.size.height / 2.0)), size: iconImage.size)
|
||||||
|
|
|
||||||
|
|
@ -334,13 +334,13 @@ public final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessag
|
||||||
}
|
}
|
||||||
|
|
||||||
transition.updateFrame(view: self.micButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: size))
|
transition.updateFrame(view: self.micButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
self.micButtonBackgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65), transition: ComponentTransition(transition))
|
self.micButtonBackgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65)), transition: ComponentTransition(transition))
|
||||||
|
|
||||||
transition.updateFrame(layer: self.micButton.layer, frame: CGRect(origin: CGPoint(), size: size))
|
transition.updateFrame(layer: self.micButton.layer, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
self.micButton.layoutItems()
|
self.micButton.layoutItems()
|
||||||
|
|
||||||
transition.updateFrame(view: self.sendButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: innerSize))
|
transition.updateFrame(view: self.sendButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: innerSize))
|
||||||
self.sendButtonBackgroundView.update(size: innerSize, cornerRadius: innerSize.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: interfaceState.theme.chat.inputPanel.actionControlFillColor, transition: ComponentTransition(transition))
|
self.sendButtonBackgroundView.update(size: innerSize, cornerRadius: innerSize.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .custom, color: interfaceState.theme.chat.inputPanel.actionControlFillColor), transition: ComponentTransition(transition))
|
||||||
transition.updateFrame(layer: self.sendButton.layer, frame: CGRect(origin: CGPoint(), size: innerSize))
|
transition.updateFrame(layer: self.sendButton.layer, frame: CGRect(origin: CGPoint(), size: innerSize))
|
||||||
transition.updateFrame(node: self.sendContainerNode, frame: CGRect(origin: CGPoint(), size: innerSize))
|
transition.updateFrame(node: self.sendContainerNode, frame: CGRect(origin: CGPoint(), size: innerSize))
|
||||||
|
|
||||||
|
|
@ -349,7 +349,7 @@ public final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessag
|
||||||
|
|
||||||
transition.updateFrame(view: self.expandMediaInputButton, frame: CGRect(origin: CGPoint(), size: size))
|
transition.updateFrame(view: self.expandMediaInputButton, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
transition.updateFrame(view: self.expandMediaInputButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: size))
|
transition.updateFrame(view: self.expandMediaInputButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
self.expandMediaInputButtonBackgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65), transition: ComponentTransition(transition))
|
self.expandMediaInputButtonBackgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65)), transition: ComponentTransition(transition))
|
||||||
if let image = self.expandMediaInputButtonIcon.image {
|
if let image = self.expandMediaInputButtonIcon.image {
|
||||||
let expandIconFrame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) * 0.5), y: floor((size.height - image.size.height) * 0.5)), size: image.size)
|
let expandIconFrame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) * 0.5), y: floor((size.height - image.size.height) * 0.5)), size: image.size)
|
||||||
transition.updatePosition(layer: self.expandMediaInputButtonIcon.layer, position: expandIconFrame.center)
|
transition.updatePosition(layer: self.expandMediaInputButtonIcon.layer, position: expandIconFrame.center)
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ final class AccessoryItemIconButton: HighlightTrackingButton, GlassBackgroundVie
|
||||||
if let text {
|
if let text {
|
||||||
if self.textView == nil {
|
if self.textView == nil {
|
||||||
let textView = ImmediateTextView()
|
let textView = ImmediateTextView()
|
||||||
|
textView.isUserInteractionEnabled = false
|
||||||
self.textView = textView
|
self.textView = textView
|
||||||
self.addSubview(textView)
|
self.addSubview(textView)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -222,6 +222,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||||
public let textInputNodeClippingContainer: ASDisplayNode
|
public let textInputNodeClippingContainer: ASDisplayNode
|
||||||
public let textInputSeparator: GlassBackgroundView.ContentColorView
|
public let textInputSeparator: GlassBackgroundView.ContentColorView
|
||||||
public var textInputNode: ChatInputTextNode?
|
public var textInputNode: ChatInputTextNode?
|
||||||
|
private var textInputNodeLayout: (frame: CGRect, insets: UIEdgeInsets)?
|
||||||
public var dustNode: InvisibleInkDustNode?
|
public var dustNode: InvisibleInkDustNode?
|
||||||
public var customEmojiContainerView: CustomEmojiContainerView?
|
public var customEmojiContainerView: CustomEmojiContainerView?
|
||||||
|
|
||||||
|
|
@ -925,11 +926,10 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||||
textInputNode.textContainerInset = calculateTextFieldRealInsets(presentationInterfaceState: presentationInterfaceState, accessoryButtonsWidth: accessoryButtonsWidth)
|
textInputNode.textContainerInset = calculateTextFieldRealInsets(presentationInterfaceState: presentationInterfaceState, accessoryButtonsWidth: accessoryButtonsWidth)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.textInputContainer.bounds.size.width.isZero {
|
if let textInputNodeLayout = self.textInputNodeLayout {
|
||||||
let textInputFrame = self.textInputContainer.frame
|
textInputNode.textContainerInset = textInputNodeLayout.insets
|
||||||
|
textInputNode.frame = textInputNodeLayout.frame
|
||||||
textInputNode.frame = CGRect(origin: CGPoint(x: self.textInputViewInternalInsets.left, y: self.textInputViewInternalInsets.top), size: CGSize(width: textInputFrame.size.width - (self.textInputViewInternalInsets.left + self.textInputViewInternalInsets.right), height: textInputFrame.size.height - self.textInputViewInternalInsets.top - self.textInputViewInternalInsets.bottom))
|
textInputNode.updateLayout(size: textInputNodeLayout.frame.size)
|
||||||
textInputNode.updateLayout(size: textInputNode.bounds.size)
|
|
||||||
textInputNode.view.layoutIfNeeded()
|
textInputNode.view.layoutIfNeeded()
|
||||||
self.updateSpoiler()
|
self.updateSpoiler()
|
||||||
}
|
}
|
||||||
|
|
@ -1865,7 +1865,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||||
let menuButtonFrame = CGRect(x: leftInset + 8.0, y: menuButtonOriginY, width: menuButtonExpanded ? menuButtonWidth : menuCollapsedButtonWidth, height: menuButtonHeight)
|
let menuButtonFrame = CGRect(x: leftInset + 8.0, y: menuButtonOriginY, width: menuButtonExpanded ? menuButtonWidth : menuCollapsedButtonWidth, height: menuButtonHeight)
|
||||||
transition.updateFrameAsPositionAndBounds(node: self.menuButton, frame: menuButtonFrame)
|
transition.updateFrameAsPositionAndBounds(node: self.menuButton, frame: menuButtonFrame)
|
||||||
transition.updateFrame(view: self.menuButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: menuButtonFrame.size))
|
transition.updateFrame(view: self.menuButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: menuButtonFrame.size))
|
||||||
self.menuButtonBackgroundView.update(size: menuButtonFrame.size, cornerRadius: menuButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: interfaceState.theme.chat.inputPanel.actionControlFillColor, transition: ComponentTransition(transition))
|
self.menuButtonBackgroundView.update(size: menuButtonFrame.size, cornerRadius: menuButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .custom, color: interfaceState.theme.chat.inputPanel.actionControlFillColor), transition: ComponentTransition(transition))
|
||||||
transition.updateFrame(node: self.menuButtonClippingNode, frame: CGRect(origin: CGPoint(x: 19.0, y: 0.0), size: CGSize(width: menuButtonWidth - 19.0, height: menuButtonFrame.height)))
|
transition.updateFrame(node: self.menuButtonClippingNode, frame: CGRect(origin: CGPoint(x: 19.0, y: 0.0), size: CGSize(width: menuButtonWidth - 19.0, height: menuButtonFrame.height)))
|
||||||
var menuButtonTitleTransition = transition
|
var menuButtonTitleTransition = transition
|
||||||
if buttonTitleUpdated {
|
if buttonTitleUpdated {
|
||||||
|
|
@ -2334,7 +2334,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||||
transition.updateFrame(node: self.textInputContainer, frame: textInputContainerBackgroundFrame)
|
transition.updateFrame(node: self.textInputContainer, frame: textInputContainerBackgroundFrame)
|
||||||
transition.updateFrame(view: self.textInputContainerBackgroundView, frame: CGRect(origin: CGPoint(), size: textInputContainerBackgroundFrame.size))
|
transition.updateFrame(view: self.textInputContainerBackgroundView, frame: CGRect(origin: CGPoint(), size: textInputContainerBackgroundFrame.size))
|
||||||
|
|
||||||
self.textInputContainerBackgroundView.update(size: textInputContainerBackgroundFrame.size, cornerRadius: floor(minimalInputHeight * 0.5), isDark: interfaceState.theme.overallDarkAppearance, tintColor: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65), transition: ComponentTransition(transition))
|
self.textInputContainerBackgroundView.update(size: textInputContainerBackgroundFrame.size, cornerRadius: floor(minimalInputHeight * 0.5), isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65)), transition: ComponentTransition(transition))
|
||||||
|
|
||||||
if let removedAccessoryPanelView {
|
if let removedAccessoryPanelView {
|
||||||
if let removedAccessoryPanelView = removedAccessoryPanelView as? ChatInputAccessoryPanelView {
|
if let removedAccessoryPanelView = removedAccessoryPanelView as? ChatInputAccessoryPanelView {
|
||||||
|
|
@ -2352,17 +2352,21 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let textFieldFrame = CGRect(origin: CGPoint(x: self.textInputViewInternalInsets.left, y: self.textInputViewInternalInsets.top + textFieldTopContentOffset), size: CGSize(width: textInputFrame.size.width - (self.textInputViewInternalInsets.left + self.textInputViewInternalInsets.right), height: textInputHeight - self.textInputViewInternalInsets.top - textInputViewInternalInsets.bottom))
|
||||||
|
let textInputNodeClippingContainerFrame = CGRect(origin: CGPoint(x: textFieldFrame.minX - self.textInputViewInternalInsets.left, y: textFieldFrame.minY - self.textInputViewInternalInsets.top), size: CGSize(width: textFieldFrame.width + self.textInputViewInternalInsets.left + self.textInputViewInternalInsets.right, height: textFieldFrame.height + self.textInputViewInternalInsets.top + self.textInputViewInternalInsets.bottom))
|
||||||
|
let shouldUpdateLayout = textInputNodeClippingContainerFrame.size != self.textInputNodeClippingContainer.frame.size
|
||||||
|
transition.updateFrame(node: self.textInputNodeClippingContainer, frame: textInputNodeClippingContainerFrame)
|
||||||
|
|
||||||
|
transition.updateFrame(view: self.textInputSeparator, frame: CGRect(origin: CGPoint(x: 15.0, y: textFieldTopContentOffset - UIScreenPixel), size: CGSize(width: textFieldFrame.width, height: UIScreenPixel)))
|
||||||
|
self.textInputSeparator.backgroundColor = interfaceState.theme.chat.inputPanel.inputPlaceholderColor
|
||||||
|
transition.updateAlpha(layer: self.textInputSeparator.layer, alpha: isTextFieldOverflow ? 1.0 : 0.0)
|
||||||
|
|
||||||
|
let actualTextFieldFrame = CGRect(origin: CGPoint(x: self.textInputViewInternalInsets.left, y: self.textInputViewInternalInsets.top), size: textFieldFrame.size)
|
||||||
|
self.textInputNodeLayout = (actualTextFieldFrame, textInputViewRealInsets)
|
||||||
|
|
||||||
if let textInputNode = self.textInputNode {
|
if let textInputNode = self.textInputNode {
|
||||||
textInputNode.textContainerInset = textInputViewRealInsets
|
textInputNode.textContainerInset = textInputViewRealInsets
|
||||||
let textFieldFrame = CGRect(origin: CGPoint(x: self.textInputViewInternalInsets.left, y: self.textInputViewInternalInsets.top + textFieldTopContentOffset), size: CGSize(width: textInputFrame.size.width - (self.textInputViewInternalInsets.left + self.textInputViewInternalInsets.right), height: textInputHeight - self.textInputViewInternalInsets.top - textInputViewInternalInsets.bottom))
|
textInputNode.frame = actualTextFieldFrame
|
||||||
let shouldUpdateLayout = textFieldFrame.size != textInputNode.frame.size
|
|
||||||
transition.updateFrame(node: self.textInputNodeClippingContainer, frame: CGRect(origin: CGPoint(x: textFieldFrame.minX - self.textInputViewInternalInsets.left, y: textFieldFrame.minY - self.textInputViewInternalInsets.top), size: CGSize(width: textFieldFrame.width + self.textInputViewInternalInsets.left + self.textInputViewInternalInsets.right, height: textFieldFrame.height + self.textInputViewInternalInsets.top + self.textInputViewInternalInsets.bottom)))
|
|
||||||
|
|
||||||
transition.updateFrame(view: self.textInputSeparator, frame: CGRect(origin: CGPoint(x: 15.0, y: textFieldTopContentOffset - UIScreenPixel), size: CGSize(width: textFieldFrame.width, height: UIScreenPixel)))
|
|
||||||
self.textInputSeparator.backgroundColor = interfaceState.theme.chat.inputPanel.inputPlaceholderColor
|
|
||||||
transition.updateAlpha(layer: self.textInputSeparator.layer, alpha: isTextFieldOverflow ? 1.0 : 0.0)
|
|
||||||
|
|
||||||
textInputNode.frame = CGRect(origin: CGPoint(x: self.textInputViewInternalInsets.left, y: self.textInputViewInternalInsets.top), size: textFieldFrame.size)
|
|
||||||
textInputNode.updateLayout(size: textFieldFrame.size)
|
textInputNode.updateLayout(size: textFieldFrame.size)
|
||||||
self.updateInputField(textInputFrame: textFieldFrame, transition: ComponentTransition(transition))
|
self.updateInputField(textInputFrame: textFieldFrame, transition: ComponentTransition(transition))
|
||||||
if shouldUpdateLayout {
|
if shouldUpdateLayout {
|
||||||
|
|
@ -2463,15 +2467,21 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||||
let textPlaceholderSize: CGSize
|
let textPlaceholderSize: CGSize
|
||||||
let textPlaceholderMaxWidth: CGFloat = max(1.0, nextButtonTopRight.x - 12.0)
|
let textPlaceholderMaxWidth: CGFloat = max(1.0, nextButtonTopRight.x - 12.0)
|
||||||
|
|
||||||
|
let placeholderColor: UIColor = interfaceState.theme.chat.inputPanel.inputPlaceholderColor
|
||||||
|
if #available(iOS 26.0, *) {
|
||||||
|
//placeholderColor = placeholderColor.withProminence(.tertiary)
|
||||||
|
}
|
||||||
|
//self.textPlaceholderNode.view.tintColor = .red
|
||||||
|
|
||||||
if (updatedPlaceholder != nil && self.currentPlaceholder != updatedPlaceholder) || themeUpdated {
|
if (updatedPlaceholder != nil && self.currentPlaceholder != updatedPlaceholder) || themeUpdated {
|
||||||
let currentPlaceholder = updatedPlaceholder ?? self.currentPlaceholder ?? ""
|
let currentPlaceholder = updatedPlaceholder ?? self.currentPlaceholder ?? ""
|
||||||
self.currentPlaceholder = currentPlaceholder
|
self.currentPlaceholder = currentPlaceholder
|
||||||
let baseFontSize = max(minInputFontSize, interfaceState.fontSize.baseDisplaySize)
|
let baseFontSize = max(minInputFontSize, interfaceState.fontSize.baseDisplaySize)
|
||||||
|
|
||||||
let attributedPlaceholder = NSMutableAttributedString(string: currentPlaceholder, font: Font.regular(baseFontSize), textColor: interfaceState.theme.chat.inputPanel.inputPlaceholderColor)
|
let attributedPlaceholder = NSMutableAttributedString(string: currentPlaceholder, font: Font.regular(baseFontSize), textColor: placeholderColor)
|
||||||
if placeholderHasStar, let range = attributedPlaceholder.string.range(of: "#") {
|
if placeholderHasStar, let range = attributedPlaceholder.string.range(of: "#") {
|
||||||
attributedPlaceholder.addAttribute(.attachment, value: PresentationResourcesChat.chatPlaceholderStarIcon(interfaceState.theme)!, range: NSRange(range, in: attributedPlaceholder.string))
|
attributedPlaceholder.addAttribute(.attachment, value: PresentationResourcesChat.chatPlaceholderStarIcon(interfaceState.theme)!, range: NSRange(range, in: attributedPlaceholder.string))
|
||||||
attributedPlaceholder.addAttribute(.foregroundColor, value: interfaceState.theme.chat.inputPanel.inputPlaceholderColor, range: NSRange(range, in: attributedPlaceholder.string))
|
attributedPlaceholder.addAttribute(.foregroundColor, value: placeholderColor, range: NSRange(range, in: attributedPlaceholder.string))
|
||||||
attributedPlaceholder.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: attributedPlaceholder.string))
|
attributedPlaceholder.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: attributedPlaceholder.string))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2615,7 +2625,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||||
|
|
||||||
let attachmentButtonFrame = CGRect(origin: CGPoint(x: attachmentButtonX, y: textInputFrame.maxY - 40.0), size: CGSize(width: 40.0, height: 40.0))
|
let attachmentButtonFrame = CGRect(origin: CGPoint(x: attachmentButtonX, y: textInputFrame.maxY - 40.0), size: CGSize(width: 40.0, height: 40.0))
|
||||||
self.attachmentButtonBackground.frame = CGRect(origin: CGPoint(), size: attachmentButtonFrame.size)
|
self.attachmentButtonBackground.frame = CGRect(origin: CGPoint(), size: attachmentButtonFrame.size)
|
||||||
self.attachmentButtonBackground.update(size: attachmentButtonFrame.size, cornerRadius: attachmentButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: isEditingMedia ? interfaceState.theme.chat.inputPanel.actionControlFillColor : interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65), transition: ComponentTransition(transition))
|
self.attachmentButtonBackground.update(size: attachmentButtonFrame.size, cornerRadius: attachmentButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: isEditingMedia ? .init(kind: .custom, color: interfaceState.theme.chat.inputPanel.actionControlFillColor) : .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65)), transition: ComponentTransition(transition))
|
||||||
|
|
||||||
transition.updateFrame(layer: self.attachmentButton.layer, frame: attachmentButtonFrame)
|
transition.updateFrame(layer: self.attachmentButton.layer, frame: attachmentButtonFrame)
|
||||||
transition.updateFrame(node: self.attachmentButtonDisabledNode, frame: self.attachmentButton.frame)
|
transition.updateFrame(node: self.attachmentButtonDisabledNode, frame: self.attachmentButton.frame)
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ import ObjCRuntimeUtils
|
||||||
private let innerCircleDiameter: CGFloat = 110.0
|
private let innerCircleDiameter: CGFloat = 110.0
|
||||||
private let outerCircleDiameter = innerCircleDiameter + 50.0
|
private let outerCircleDiameter = innerCircleDiameter + 50.0
|
||||||
private let outerCircleMinScale = innerCircleDiameter / outerCircleDiameter
|
private let outerCircleMinScale = innerCircleDiameter / outerCircleDiameter
|
||||||
private let innerCircleImage = generateFilledCircleImage(diameter: innerCircleDiameter, color: UIColor(rgb: 0x007aff))
|
private let innerCircleImage = generateFilledCircleImage(diameter: innerCircleDiameter, color: UIColor(rgb: 0x0088ff))
|
||||||
private let outerCircleImage = generateFilledCircleImage(diameter: outerCircleDiameter, color: UIColor(rgb: 0x007aff, alpha: 0.2))
|
private let outerCircleImage = generateFilledCircleImage(diameter: outerCircleDiameter, color: UIColor(rgb: 0x0088ff, alpha: 0.2))
|
||||||
private let micIcon = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/IconMicrophone"), color: .white)!
|
private let micIcon = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/IconMicrophone"), color: .white)!
|
||||||
|
|
||||||
private final class ChatTextInputAudioRecordingOverlayDisplayLinkTarget: NSObject {
|
private final class ChatTextInputAudioRecordingOverlayDisplayLinkTarget: NSObject {
|
||||||
|
|
|
||||||
|
|
@ -634,7 +634,7 @@ private class WrapperBlurrredBackgroundView: UIView, TGModernConversationInputMi
|
||||||
|
|
||||||
let view = GlassBackgroundView()
|
let view = GlassBackgroundView()
|
||||||
view.frame = CGRect(origin: CGPoint(), size: size)
|
view.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
view.update(size: size, cornerRadius: min(size.width, size.height) * 0.5, isDark: self.isDark, tintColor: self.glassTintColor, transition: .immediate)
|
view.update(size: size, cornerRadius: min(size.width, size.height) * 0.5, isDark: self.isDark, tintColor: .init(kind: .panel, color: self.glassTintColor), transition: .immediate)
|
||||||
self.view = view
|
self.view = view
|
||||||
|
|
||||||
super.init(frame: CGRect(origin: CGPoint(), size: size))
|
super.init(frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
@ -652,13 +652,13 @@ private class WrapperBlurrredBackgroundView: UIView, TGModernConversationInputMi
|
||||||
} set {
|
} set {
|
||||||
super.frame = newValue
|
super.frame = newValue
|
||||||
self.view.frame = CGRect(origin: CGPoint(), size: newValue.size)
|
self.view.frame = CGRect(origin: CGPoint(), size: newValue.size)
|
||||||
self.view.update(size: newValue.size, cornerRadius: min(newValue.width, newValue.height) * 0.5, isDark: self.isDark, tintColor: self.glassTintColor, transition: .immediate)
|
self.view.update(size: newValue.size, cornerRadius: min(newValue.width, newValue.height) * 0.5, isDark: self.isDark, tintColor: .init(kind: .panel, color: self.glassTintColor), transition: .immediate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(_ size: CGSize) {
|
func update(_ size: CGSize) {
|
||||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
|
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
|
||||||
transition.updateFrame(view: self.view, frame: CGRect(origin: CGPoint(), size: size))
|
transition.updateFrame(view: self.view, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
self.view.update(size: size, cornerRadius: min(size.width, size.height) * 0.5, isDark: self.isDark, tintColor: self.glassTintColor, transition: ComponentTransition(transition))
|
self.view.update(size: size, cornerRadius: min(size.width, size.height) * 0.5, isDark: self.isDark, tintColor: .init(kind: .panel, color: self.glassTintColor), transition: ComponentTransition(transition))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
19
submodules/TelegramUI/Components/EdgeEffect/BUILD
Normal file
19
submodules/TelegramUI/Components/EdgeEffect/BUILD
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "EdgeEffect",
|
||||||
|
module_name = "EdgeEffect",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
"-warnings-as-errors",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//submodules/Display",
|
||||||
|
"//submodules/ComponentFlow",
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import ComponentFlow
|
||||||
|
|
||||||
|
public final class EdgeEffectView: UIView {
|
||||||
|
public enum Edge {
|
||||||
|
case top
|
||||||
|
case bottom
|
||||||
|
}
|
||||||
|
|
||||||
|
private let contentView: UIView
|
||||||
|
private let contentMaskView: UIImageView
|
||||||
|
|
||||||
|
public override init(frame: CGRect) {
|
||||||
|
self.contentView = UIView()
|
||||||
|
self.contentMaskView = UIImageView()
|
||||||
|
self.contentView.mask = self.contentMaskView
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.addSubview(self.contentView)
|
||||||
|
}
|
||||||
|
|
||||||
|
required public init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(content: UIColor, rect: CGRect, edge: Edge, edgeSize: CGFloat, containerSize: CGSize, transition: ComponentTransition) {
|
||||||
|
self.contentView.backgroundColor = content
|
||||||
|
|
||||||
|
transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: rect.size))
|
||||||
|
transition.setFrame(view: self.contentMaskView, frame: CGRect(origin: CGPoint(), size: rect.size))
|
||||||
|
|
||||||
|
if self.contentMaskView.image?.size.height != edgeSize {
|
||||||
|
let baseGradientAlpha: CGFloat = 0.65
|
||||||
|
let numSteps = 8
|
||||||
|
let firstStep = 1
|
||||||
|
let firstLocation = 0.0
|
||||||
|
let colors: [UIColor] = (0 ..< numSteps).map { i in
|
||||||
|
if i < firstStep {
|
||||||
|
return UIColor(white: 1.0, alpha: 1.0)
|
||||||
|
} else {
|
||||||
|
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
|
||||||
|
let value: CGFloat = bezierPoint(0.42, 0.0, 0.58, 1.0, step)
|
||||||
|
return UIColor(white: 1.0, alpha: baseGradientAlpha * value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let locations: [CGFloat] = (0 ..< numSteps).map { i in
|
||||||
|
if i < firstStep {
|
||||||
|
return 0.0
|
||||||
|
} else {
|
||||||
|
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
|
||||||
|
return (firstLocation + (1.0 - firstLocation) * step)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if edgeSize > 0.0 {
|
||||||
|
self.contentMaskView.image = generateGradientImage(
|
||||||
|
size: CGSize(width: 8.0, height: edgeSize),
|
||||||
|
colors: colors,
|
||||||
|
locations: locations
|
||||||
|
)?.stretchableImage(withLeftCapWidth: 0, topCapHeight: Int(edgeSize))
|
||||||
|
} else {
|
||||||
|
self.contentMaskView.image = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1050,7 +1050,7 @@ private let tonImage: UIImage? = {
|
||||||
generateImage(CGSize(width: 32.0, height: 32.0), contextGenerator: { size, context in
|
generateImage(CGSize(width: 32.0, height: 32.0), contextGenerator: { size, context in
|
||||||
context.clear(CGRect(origin: .zero, size: size))
|
context.clear(CGRect(origin: .zero, size: size))
|
||||||
|
|
||||||
if let image = generateTintedImage(image: UIImage(bundleImageName: "Ads/TonBig"), color: UIColor(rgb: 0x007aff)), let cgImage = image.cgImage {
|
if let image = generateTintedImage(image: UIImage(bundleImageName: "Ads/TonBig"), color: UIColor(rgb: 0x0088ff)), let cgImage = image.cgImage {
|
||||||
context.draw(cgImage, in: CGRect(origin: .zero, size: size).insetBy(dx: 4.0, dy: 4.0), byTiling: false)
|
context.draw(cgImage, in: CGRect(origin: .zero, size: size).insetBy(dx: 4.0, dy: 4.0), byTiling: false)
|
||||||
}
|
}
|
||||||
})?.withRenderingMode(.alwaysTemplate)
|
})?.withRenderingMode(.alwaysTemplate)
|
||||||
|
|
|
||||||
|
|
@ -313,12 +313,27 @@ public final class GlassBackgroundView: UIView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct TintColor: Equatable {
|
||||||
|
public enum Kind {
|
||||||
|
case panel
|
||||||
|
case custom
|
||||||
|
}
|
||||||
|
|
||||||
|
public let kind: Kind
|
||||||
|
public let color: UIColor
|
||||||
|
|
||||||
|
public init(kind: Kind, color: UIColor) {
|
||||||
|
self.kind = kind
|
||||||
|
self.color = color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private struct Params: Equatable {
|
private struct Params: Equatable {
|
||||||
let cornerRadius: CGFloat
|
let cornerRadius: CGFloat
|
||||||
let isDark: Bool
|
let isDark: Bool
|
||||||
let tintColor: UIColor
|
let tintColor: TintColor
|
||||||
|
|
||||||
init(cornerRadius: CGFloat, isDark: Bool, tintColor: UIColor) {
|
init(cornerRadius: CGFloat, isDark: Bool, tintColor: TintColor) {
|
||||||
self.cornerRadius = cornerRadius
|
self.cornerRadius = cornerRadius
|
||||||
self.isDark = isDark
|
self.isDark = isDark
|
||||||
self.tintColor = tintColor
|
self.tintColor = tintColor
|
||||||
|
|
@ -329,12 +344,18 @@ public final class GlassBackgroundView: UIView {
|
||||||
private let nativeView: UIVisualEffectView?
|
private let nativeView: UIVisualEffectView?
|
||||||
|
|
||||||
private let foregroundView: UIImageView?
|
private let foregroundView: UIImageView?
|
||||||
|
private let shadowView: UIImageView?
|
||||||
|
private let shadowMaskView: UIImageView?
|
||||||
|
|
||||||
public let maskContentView: UIView
|
public let maskContentView: UIView
|
||||||
private let contentContainer: ContentContainer
|
private let contentContainer: ContentContainer
|
||||||
|
|
||||||
public var contentView: UIView {
|
public var contentView: UIView {
|
||||||
return self.contentContainer
|
if let nativeView = self.nativeView {
|
||||||
|
return nativeView.contentView
|
||||||
|
} else {
|
||||||
|
return self.contentContainer
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var params: Params?
|
private var params: Params?
|
||||||
|
|
@ -350,10 +371,14 @@ public final class GlassBackgroundView: UIView {
|
||||||
nativeView.traitOverrides.userInterfaceStyle = .light
|
nativeView.traitOverrides.userInterfaceStyle = .light
|
||||||
//self.foregroundView = UIImageView()
|
//self.foregroundView = UIImageView()
|
||||||
self.foregroundView = nil
|
self.foregroundView = nil
|
||||||
|
self.shadowView = nil
|
||||||
|
self.shadowMaskView = nil
|
||||||
} else {
|
} else {
|
||||||
self.backgroundNode = NavigationBackgroundNode(color: .black, enableBlur: true, customBlurRadius: 5.0)
|
self.backgroundNode = NavigationBackgroundNode(color: .black, enableBlur: true, customBlurRadius: 5.0)
|
||||||
self.nativeView = nil
|
self.nativeView = nil
|
||||||
self.foregroundView = UIImageView()
|
self.foregroundView = UIImageView()
|
||||||
|
self.shadowView = UIImageView()
|
||||||
|
self.shadowMaskView = UIImageView()
|
||||||
}
|
}
|
||||||
|
|
||||||
self.maskContentView = UIView()
|
self.maskContentView = UIView()
|
||||||
|
|
@ -366,6 +391,12 @@ public final class GlassBackgroundView: UIView {
|
||||||
|
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
if let shadowView = self.shadowView {
|
||||||
|
self.addSubview(shadowView)
|
||||||
|
if let shadowMaskView = self.shadowMaskView {
|
||||||
|
shadowView.mask = shadowMaskView
|
||||||
|
}
|
||||||
|
}
|
||||||
if let nativeView = self.nativeView {
|
if let nativeView = self.nativeView {
|
||||||
self.addSubview(nativeView)
|
self.addSubview(nativeView)
|
||||||
}
|
}
|
||||||
|
|
@ -383,7 +414,7 @@ public final class GlassBackgroundView: UIView {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update(size: CGSize, cornerRadius: CGFloat, isDark: Bool, tintColor: UIColor, transition: ComponentTransition) {
|
public func update(size: CGSize, cornerRadius: CGFloat, isDark: Bool, tintColor: TintColor, transition: ComponentTransition) {
|
||||||
if let nativeView = self.nativeView {
|
if let nativeView = self.nativeView {
|
||||||
let previousFrame = nativeView.frame
|
let previousFrame = nativeView.frame
|
||||||
|
|
||||||
|
|
@ -399,7 +430,7 @@ public final class GlassBackgroundView: UIView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let backgroundNode = self.backgroundNode {
|
if let backgroundNode = self.backgroundNode {
|
||||||
backgroundNode.updateColor(color: .clear, forceKeepBlur: tintColor.alpha != 1.0, transition: transition.containedViewLayoutTransition)
|
backgroundNode.updateColor(color: .clear, forceKeepBlur: tintColor.color.alpha != 1.0, transition: transition.containedViewLayoutTransition)
|
||||||
backgroundNode.update(size: size, cornerRadius: cornerRadius, transition: transition.containedViewLayoutTransition)
|
backgroundNode.update(size: size, cornerRadius: cornerRadius, transition: transition.containedViewLayoutTransition)
|
||||||
transition.setFrame(view: backgroundNode.view, frame: CGRect(origin: CGPoint(), size: size))
|
transition.setFrame(view: backgroundNode.view, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
}
|
}
|
||||||
|
|
@ -408,13 +439,38 @@ public final class GlassBackgroundView: UIView {
|
||||||
if self.params != params {
|
if self.params != params {
|
||||||
self.params = params
|
self.params = params
|
||||||
|
|
||||||
|
if let shadowView = self.shadowView {
|
||||||
|
shadowView.layer.shadowRadius = 10.0
|
||||||
|
shadowView.layer.shadowColor = UIColor(white: 0.0, alpha: 1.0).cgColor
|
||||||
|
shadowView.layer.shadowOpacity = 0.08
|
||||||
|
shadowView.layer.shadowOffset = CGSize(width: 0.0, height: 1.0)
|
||||||
|
|
||||||
|
if let shadowMaskView = self.shadowMaskView {
|
||||||
|
let shadowInset: CGFloat = 32.0
|
||||||
|
let shadowInnerInset: CGFloat = 0.5
|
||||||
|
shadowMaskView.image = generateImage(CGSize(width: shadowInset * 2.0 + cornerRadius * 2.0, height: shadowInset * 2.0 + cornerRadius * 2.0), rotatedContext: { size, context in
|
||||||
|
context.setFillColor(UIColor.white.cgColor)
|
||||||
|
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
|
context.setFillColor(UIColor.clear.cgColor)
|
||||||
|
context.setBlendMode(.copy)
|
||||||
|
context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowInset + shadowInnerInset, y: shadowInset + shadowInnerInset), size: CGSize(width: size.width - shadowInset * 2.0 - shadowInnerInset * 2.0, height: size.height - shadowInset * 2.0 - shadowInnerInset * 2.0)))
|
||||||
|
})?.stretchableImage(withLeftCapWidth: Int(shadowInset + cornerRadius), topCapHeight: Int(shadowInset + cornerRadius))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let foregroundView = self.foregroundView {
|
if let foregroundView = self.foregroundView {
|
||||||
foregroundView.image = generateForegroundImage(size: CGSize(width: cornerRadius * 2.0, height: cornerRadius * 2.0), isDark: isDark, fillColor: tintColor)
|
foregroundView.image = generateForegroundImage(size: CGSize(width: cornerRadius * 2.0, height: cornerRadius * 2.0), isDark: isDark, fillColor: tintColor.color)
|
||||||
} else {
|
} else {
|
||||||
if let nativeView {
|
if let nativeView {
|
||||||
if #available(iOS 26.0, *) {
|
if #available(iOS 26.0, *) {
|
||||||
let glassEffect = UIGlassEffect(style: .regular)
|
let glassEffect = UIGlassEffect(style: .regular)
|
||||||
//glassEffect.tintColor = tintColor.withMultipliedAlpha(0.1)
|
switch tintColor.kind {
|
||||||
|
case .panel:
|
||||||
|
glassEffect.tintColor = nil
|
||||||
|
case .custom:
|
||||||
|
glassEffect.tintColor = tintColor.color
|
||||||
|
}
|
||||||
glassEffect.isInteractive = false
|
glassEffect.isInteractive = false
|
||||||
|
|
||||||
nativeView.effect = glassEffect
|
nativeView.effect = glassEffect
|
||||||
|
|
@ -424,9 +480,19 @@ public final class GlassBackgroundView: UIView {
|
||||||
}
|
}
|
||||||
|
|
||||||
transition.setFrame(view: self.maskContentView, frame: CGRect(origin: CGPoint(), size: size))
|
transition.setFrame(view: self.maskContentView, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
if let foregroundView {
|
if let foregroundView = self.foregroundView {
|
||||||
transition.setFrame(view: foregroundView, frame: CGRect(origin: CGPoint(), size: size))
|
transition.setFrame(view: foregroundView, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
}
|
}
|
||||||
|
if let shadowView = self.shadowView {
|
||||||
|
if shadowView.bounds.size != size {
|
||||||
|
shadowView.layer.shadowPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: size.height * 0.5).cgPath
|
||||||
|
}
|
||||||
|
transition.setFrame(view: shadowView, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
|
if let shadowMaskView = self.shadowMaskView {
|
||||||
|
transition.setFrame(view: shadowMaskView, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: -32.0, dy: -32.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
transition.setFrame(view: self.contentContainer, frame: CGRect(origin: CGPoint(), size: size))
|
transition.setFrame(view: self.contentContainer, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -888,7 +888,7 @@ final class MediaEditorScreenComponent: Component {
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(PlainButtonComponent(
|
component: AnyComponent(PlainButtonComponent(
|
||||||
content: AnyComponent(DoneButtonContentComponent(
|
content: AnyComponent(DoneButtonContentComponent(
|
||||||
backgroundColor: UIColor(rgb: 0x007aff),
|
backgroundColor: UIColor(rgb: 0x0088ff),
|
||||||
icon: doneButtonIcon,
|
icon: doneButtonIcon,
|
||||||
title: doneButtonTitle)),
|
title: doneButtonTitle)),
|
||||||
effectAlignment: .center,
|
effectAlignment: .center,
|
||||||
|
|
|
||||||
|
|
@ -238,7 +238,7 @@ public final class MediaScrubberComponent: Component {
|
||||||
self.coverDotWrapper.isUserInteractionEnabled = false
|
self.coverDotWrapper.isUserInteractionEnabled = false
|
||||||
self.coverDotWrapper.isHidden = true
|
self.coverDotWrapper.isHidden = true
|
||||||
|
|
||||||
self.coverDotView = UIImageView(image: generateFilledCircleImage(diameter: 7.0, color: UIColor(rgb: 0x007aff)))
|
self.coverDotView = UIImageView(image: generateFilledCircleImage(diameter: 7.0, color: UIColor(rgb: 0x0088ff)))
|
||||||
|
|
||||||
self.coverImageView = UIImageView()
|
self.coverImageView = UIImageView()
|
||||||
self.coverImageView.clipsToBounds = true
|
self.coverImageView.clipsToBounds = true
|
||||||
|
|
|
||||||
|
|
@ -442,7 +442,7 @@ public final class HashtagListItemComponent: Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
if themeUpdated {
|
if themeUpdated {
|
||||||
let accentColor = UIColor(rgb: 0x007aff)
|
let accentColor = UIColor(rgb: 0x0088ff)
|
||||||
self.separatorLayer.backgroundColor = component.theme.list.itemPlainSeparatorColor.cgColor
|
self.separatorLayer.backgroundColor = component.theme.list.itemPlainSeparatorColor.cgColor
|
||||||
self.iconBackgroundLayer.backgroundColor = accentColor.cgColor
|
self.iconBackgroundLayer.backgroundColor = accentColor.cgColor
|
||||||
self.iconLayer.layerTintColor = UIColor.white.cgColor
|
self.iconLayer.layerTintColor = UIColor.white.cgColor
|
||||||
|
|
|
||||||
|
|
@ -165,6 +165,7 @@ swift_library(
|
||||||
"//submodules/TelegramUI/Components/BottomButtonPanelComponent",
|
"//submodules/TelegramUI/Components/BottomButtonPanelComponent",
|
||||||
"//submodules/TelegramUI/Components/MarqueeComponent",
|
"//submodules/TelegramUI/Components/MarqueeComponent",
|
||||||
"//submodules/TelegramUI/Components/MediaManager/PeerMessagesMediaPlaylist",
|
"//submodules/TelegramUI/Components/MediaManager/PeerMessagesMediaPlaylist",
|
||||||
|
"//submodules/TelegramUI/Components/EdgeEffect",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,7 @@ import UrlHandling
|
||||||
import VerifyAlertController
|
import VerifyAlertController
|
||||||
import GiftViewScreen
|
import GiftViewScreen
|
||||||
import PeerMessagesMediaPlaylist
|
import PeerMessagesMediaPlaylist
|
||||||
|
import EdgeEffect
|
||||||
|
|
||||||
public enum PeerInfoAvatarEditingMode {
|
public enum PeerInfoAvatarEditingMode {
|
||||||
case generic
|
case generic
|
||||||
|
|
@ -2927,6 +2928,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||||
fileprivate let cachedDataPromise = Promise<CachedPeerData?>()
|
fileprivate let cachedDataPromise = Promise<CachedPeerData?>()
|
||||||
|
|
||||||
let scrollNode: ASScrollNode
|
let scrollNode: ASScrollNode
|
||||||
|
private let edgeEffectView: EdgeEffectView
|
||||||
|
|
||||||
let headerNode: PeerInfoHeaderNode
|
let headerNode: PeerInfoHeaderNode
|
||||||
private var regularSections: [AnyHashable: PeerInfoScreenItemSectionContainerNode] = [:]
|
private var regularSections: [AnyHashable: PeerInfoScreenItemSectionContainerNode] = [:]
|
||||||
|
|
@ -3063,6 +3065,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||||
self.scrollNode.view.delaysContentTouches = false
|
self.scrollNode.view.delaysContentTouches = false
|
||||||
self.scrollNode.canCancelAllTouchesInViews = true
|
self.scrollNode.canCancelAllTouchesInViews = true
|
||||||
|
|
||||||
|
self.edgeEffectView = EdgeEffectView()
|
||||||
|
|
||||||
var forumTopicThreadId: Int64?
|
var forumTopicThreadId: Int64?
|
||||||
if case let .replyThread(message) = chatLocation {
|
if case let .replyThread(message) = chatLocation {
|
||||||
forumTopicThreadId = message.threadId
|
forumTopicThreadId = message.threadId
|
||||||
|
|
@ -3930,6 +3934,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||||
self.addSubnode(self.scrollNode)
|
self.addSubnode(self.scrollNode)
|
||||||
self.scrollNode.addSubnode(self.paneContainerNode)
|
self.scrollNode.addSubnode(self.paneContainerNode)
|
||||||
|
|
||||||
|
self.view.addSubview(self.edgeEffectView)
|
||||||
|
|
||||||
self.addSubnode(self.headerNode)
|
self.addSubnode(self.headerNode)
|
||||||
self.scrollNode.view.isScrollEnabled = !self.isMediaOnly
|
self.scrollNode.view.isScrollEnabled = !self.isMediaOnly
|
||||||
|
|
||||||
|
|
@ -12296,6 +12302,13 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||||
|
|
||||||
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||||
|
|
||||||
|
if self.isSettings {
|
||||||
|
let edgeEffectHeight: CGFloat = layout.intrinsicInsets.bottom
|
||||||
|
let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - edgeEffectHeight), size: CGSize(width: layout.size.width, height: edgeEffectHeight))
|
||||||
|
transition.updateFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
|
||||||
|
self.edgeEffectView.update(content: self.presentationData.theme.list.blocksBackgroundColor, rect: edgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, containerSize: layout.size, transition: ComponentTransition(transition))
|
||||||
|
}
|
||||||
|
|
||||||
let sectionSpacing: CGFloat = 24.0
|
let sectionSpacing: CGFloat = 24.0
|
||||||
|
|
||||||
var contentHeight: CGFloat = 0.0
|
var contentHeight: CGFloat = 0.0
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import MediaResources
|
||||||
import WallpaperGalleryScreen
|
import WallpaperGalleryScreen
|
||||||
import GenerateThemeName
|
import GenerateThemeName
|
||||||
|
|
||||||
private let randomBackgroundColors: [Int32] = [0x007aff, 0x00c2ed, 0x29b327, 0xeb6ca4, 0xf08200, 0x9472ee, 0xd33213, 0xedb400, 0x6d839e]
|
private let randomBackgroundColors: [Int32] = [0x0088ff, 0x00c2ed, 0x29b327, 0xeb6ca4, 0xf08200, 0x9472ee, 0xd33213, 0xedb400, 0x6d839e]
|
||||||
|
|
||||||
public extension TelegramThemeSettings {
|
public extension TelegramThemeSettings {
|
||||||
convenience init(baseTheme: TelegramBaseTheme, accentColor: UIColor, outgoingAccentColor: UIColor?, messageColors: [UInt32], animateMessageColors: Bool, wallpaper: TelegramWallpaper?) {
|
convenience init(baseTheme: TelegramBaseTheme, accentColor: UIColor, outgoingAccentColor: UIColor?, messageColors: [UInt32], animateMessageColors: Bool, wallpaper: TelegramWallpaper?) {
|
||||||
|
|
|
||||||
|
|
@ -1521,7 +1521,7 @@ final class ShareWithPeersScreenComponent: Component {
|
||||||
)),
|
)),
|
||||||
maximumNumberOfLines: 0,
|
maximumNumberOfLines: 0,
|
||||||
lineSpacing: 0.1,
|
lineSpacing: 0.1,
|
||||||
highlightColor: UIColor(rgb: 0x007aff, alpha: 0.2),
|
highlightColor: UIColor(rgb: 0x0088ff, alpha: 0.2),
|
||||||
highlightAction: { attributes in
|
highlightAction: { attributes in
|
||||||
if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] {
|
if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] {
|
||||||
return NSAttributedString.Key(rawValue: "URL")
|
return NSAttributedString.Key(rawValue: "URL")
|
||||||
|
|
|
||||||
|
|
@ -211,7 +211,7 @@ final class DataUsageScreenComponent: Component {
|
||||||
case .photos:
|
case .photos:
|
||||||
return UIColor(rgb: 0x5AC8FA)
|
return UIColor(rgb: 0x5AC8FA)
|
||||||
case .videos:
|
case .videos:
|
||||||
return UIColor(rgb: 0x007AFF)
|
return UIColor(rgb: 0x0088ff)
|
||||||
case .files:
|
case .files:
|
||||||
return UIColor(rgb: 0x34C759)
|
return UIColor(rgb: 0x34C759)
|
||||||
case .music:
|
case .music:
|
||||||
|
|
|
||||||
27
submodules/TelegramUI/Components/TabBarComponent/BUILD
Normal file
27
submodules/TelegramUI/Components/TabBarComponent/BUILD
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "TabBarComponent",
|
||||||
|
module_name = "TabBarComponent",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
"-warnings-as-errors",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//submodules/Display",
|
||||||
|
"//submodules/TelegramPresentationData",
|
||||||
|
"//submodules/ComponentFlow",
|
||||||
|
"//submodules/Components/ComponentDisplayAdapters",
|
||||||
|
"//submodules/Components/MultilineTextComponent",
|
||||||
|
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
|
||||||
|
"//submodules/TelegramUI/Components/LottieComponent",
|
||||||
|
"//submodules/Components/BundleIconComponent",
|
||||||
|
"//submodules/TelegramUI/Components/TextBadgeComponent",
|
||||||
|
"//submodules/UIKitRuntimeUtils",
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,623 @@
|
||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import TelegramPresentationData
|
||||||
|
import ComponentFlow
|
||||||
|
import ComponentDisplayAdapters
|
||||||
|
import GlassBackgroundComponent
|
||||||
|
import MultilineTextComponent
|
||||||
|
import LottieComponent
|
||||||
|
import UIKitRuntimeUtils
|
||||||
|
import BundleIconComponent
|
||||||
|
import TextBadgeComponent
|
||||||
|
|
||||||
|
public final class TabBarComponent: Component {
|
||||||
|
public final class Item: Equatable {
|
||||||
|
public let item: UITabBarItem
|
||||||
|
public let action: (Bool) -> Void
|
||||||
|
|
||||||
|
fileprivate var id: AnyHashable {
|
||||||
|
return AnyHashable(ObjectIdentifier(self.item))
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(item: UITabBarItem, action: @escaping (Bool) -> Void) {
|
||||||
|
self.item = item
|
||||||
|
self.action = action
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: Item, rhs: Item) -> Bool {
|
||||||
|
if lhs === rhs {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if lhs.item !== rhs.item {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public let theme: PresentationTheme
|
||||||
|
public let items: [Item]
|
||||||
|
public let selectedId: AnyHashable?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
theme: PresentationTheme,
|
||||||
|
items: [Item],
|
||||||
|
selectedId: AnyHashable?
|
||||||
|
) {
|
||||||
|
self.theme = theme
|
||||||
|
self.items = items
|
||||||
|
self.selectedId = selectedId
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: TabBarComponent, rhs: TabBarComponent) -> Bool {
|
||||||
|
if lhs.theme !== rhs.theme {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.items != rhs.items {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.selectedId != rhs.selectedId {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class View: UIView, UITabBarDelegate, UIGestureRecognizerDelegate {
|
||||||
|
private let backgroundView: GlassBackgroundView
|
||||||
|
private let selectionView: GlassBackgroundView.ContentImageView
|
||||||
|
private let nativeTabBar: UITabBar?
|
||||||
|
|
||||||
|
private var itemViews: [AnyHashable: ComponentView<Empty>] = [:]
|
||||||
|
private var selectedItemViews: [AnyHashable: ComponentView<Empty>] = [:]
|
||||||
|
|
||||||
|
private var component: TabBarComponent?
|
||||||
|
private weak var state: EmptyComponentState?
|
||||||
|
|
||||||
|
public override init(frame: CGRect) {
|
||||||
|
self.backgroundView = GlassBackgroundView(frame: CGRect())
|
||||||
|
self.selectionView = GlassBackgroundView.ContentImageView()
|
||||||
|
|
||||||
|
if #available(iOS 26.0, *) {
|
||||||
|
self.nativeTabBar = UITabBar()
|
||||||
|
} else {
|
||||||
|
self.nativeTabBar = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
if let nativeTabBar = self.nativeTabBar {
|
||||||
|
self.addSubview(nativeTabBar)
|
||||||
|
nativeTabBar.delegate = self
|
||||||
|
let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(self.onLongPressGesture(_:)))
|
||||||
|
longPressGesture.delegate = self
|
||||||
|
self.addGestureRecognizer(longPressGesture)
|
||||||
|
} else {
|
||||||
|
self.addSubview(self.backgroundView)
|
||||||
|
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onTapGesture(_:))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
required public init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
public func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
|
||||||
|
guard let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let index = tabBar.items?.firstIndex(where: { $0 === item }) {
|
||||||
|
if index < component.items.count {
|
||||||
|
component.items[index].action(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func onLongPressGesture(_ recognizer: UILongPressGestureRecognizer) {
|
||||||
|
if case .began = recognizer.state {
|
||||||
|
if let nativeTabBar = self.nativeTabBar {
|
||||||
|
func cancelGestures(view: UIView) {
|
||||||
|
for recognizer in view.gestureRecognizers ?? [] {
|
||||||
|
if NSStringFromClass(type(of: recognizer)).contains("sSelectionGestureRecognizer") {
|
||||||
|
recognizer.state = .cancelled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for subview in view.subviews {
|
||||||
|
cancelGestures(view: subview)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelGestures(view: nativeTabBar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func onTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||||
|
guard let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if case .ended = recognizer.state {
|
||||||
|
let point = recognizer.location(in: self)
|
||||||
|
var closestItemView: (AnyHashable, CGFloat)?
|
||||||
|
for (id, itemView) in self.itemViews {
|
||||||
|
guard let itemView = itemView.view else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let distance = abs(point.x - itemView.center.x)
|
||||||
|
if let previousClosestItemView = closestItemView {
|
||||||
|
if previousClosestItemView.1 > distance {
|
||||||
|
closestItemView = (id, distance)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
closestItemView = (id, distance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let (id, _) = closestItemView {
|
||||||
|
guard let item = component.items.first(where: { $0.id == id }) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
item.action(false)
|
||||||
|
/*if previousSelectedIndex != closestNode.0 {
|
||||||
|
if let selectedIndex = self.selectedIndex, let _ = self.tabBarItems[selectedIndex].item.animationName {
|
||||||
|
container.imageNode.animationNode.play(firstFrame: false, fromIndex: nil)
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
|
return super.hitTest(point, with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(component: TabBarComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||||
|
let innerInset: CGFloat = 3.0
|
||||||
|
|
||||||
|
let previousComponent = self.component
|
||||||
|
self.component = component
|
||||||
|
self.state = state
|
||||||
|
|
||||||
|
if let nativeTabBar = self.nativeTabBar {
|
||||||
|
if nativeTabBar.items?.count != component.items.count {
|
||||||
|
nativeTabBar.items = (0 ..< component.items.count).map { i in
|
||||||
|
return UITabBarItem(title: " ", image: nil, tag: i)
|
||||||
|
}
|
||||||
|
for (_, itemView) in self.itemViews {
|
||||||
|
itemView.view?.removeFromSuperview()
|
||||||
|
}
|
||||||
|
for (_, selectedItemView) in self.selectedItemViews {
|
||||||
|
selectedItemView.view?.removeFromSuperview()
|
||||||
|
}
|
||||||
|
if let index = component.items.firstIndex(where: { $0.id == component.selectedId }) {
|
||||||
|
nativeTabBar.selectedItem = nativeTabBar.items?[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let nativeSize = nativeTabBar.sizeThatFits(availableSize)
|
||||||
|
nativeTabBar.bounds = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: nativeSize.height))
|
||||||
|
nativeTabBar.layoutSubviews()
|
||||||
|
}
|
||||||
|
|
||||||
|
var nativeItemContainers: [Int: UIView] = [:]
|
||||||
|
var nativeSelectedItemContainers: [Int: UIView] = [:]
|
||||||
|
if let nativeTabBar = self.nativeTabBar {
|
||||||
|
for subview in nativeTabBar.subviews {
|
||||||
|
if NSStringFromClass(type(of: subview)).contains("PlatterView") {
|
||||||
|
for subview in subview.subviews {
|
||||||
|
if NSStringFromClass(type(of: subview)).hasSuffix("SelectedContentView") {
|
||||||
|
for subview in subview.subviews {
|
||||||
|
if NSStringFromClass(type(of: subview)).hasSuffix("TabButton") {
|
||||||
|
nativeSelectedItemContainers[nativeSelectedItemContainers.count] = subview
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if NSStringFromClass(type(of: subview)).hasSuffix("ContentView") {
|
||||||
|
for subview in subview.subviews {
|
||||||
|
if NSStringFromClass(type(of: subview)).hasSuffix("TabButton") {
|
||||||
|
nativeItemContainers[nativeItemContainers.count] = subview
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var itemSize = CGSize(width: floor((availableSize.width - innerInset * 2.0) / CGFloat(component.items.count)), height: 56.0)
|
||||||
|
itemSize.width = min(94.0, itemSize.width)
|
||||||
|
|
||||||
|
if let itemContainer = nativeItemContainers[0] {
|
||||||
|
itemSize = itemContainer.bounds.size
|
||||||
|
}
|
||||||
|
|
||||||
|
let contentHeight = itemSize.height + innerInset * 2.0
|
||||||
|
var contentWidth: CGFloat = innerInset
|
||||||
|
|
||||||
|
if self.selectionView.image?.size.height != itemSize.height {
|
||||||
|
self.selectionView.image = generateStretchableFilledCircleImage(radius: itemSize.height * 0.5, color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||||
|
}
|
||||||
|
self.selectionView.tintColor = component.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.05)
|
||||||
|
|
||||||
|
var validIds: [AnyHashable] = []
|
||||||
|
var selectionFrame: CGRect?
|
||||||
|
for index in 0 ..< component.items.count {
|
||||||
|
let item = component.items[index]
|
||||||
|
validIds.append(item.id)
|
||||||
|
|
||||||
|
let itemView: ComponentView<Empty>
|
||||||
|
var itemTransition = transition
|
||||||
|
|
||||||
|
if let current = self.itemViews[item.id] {
|
||||||
|
itemView = current
|
||||||
|
} else {
|
||||||
|
itemTransition = itemTransition.withAnimation(.none)
|
||||||
|
itemView = ComponentView()
|
||||||
|
self.itemViews[item.id] = itemView
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectedItemView: ComponentView<Empty>
|
||||||
|
if let current = self.selectedItemViews[item.id] {
|
||||||
|
selectedItemView = current
|
||||||
|
} else {
|
||||||
|
selectedItemView = ComponentView()
|
||||||
|
self.selectedItemViews[item.id] = selectedItemView
|
||||||
|
}
|
||||||
|
|
||||||
|
let isItemSelected = component.selectedId == item.id
|
||||||
|
|
||||||
|
let _ = itemView.update(
|
||||||
|
transition: itemTransition,
|
||||||
|
component: AnyComponent(ItemComponent(
|
||||||
|
item: item,
|
||||||
|
theme: component.theme,
|
||||||
|
isSelected: self.nativeTabBar == nil ? isItemSelected : false
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: itemSize
|
||||||
|
)
|
||||||
|
let _ = selectedItemView.update(
|
||||||
|
transition: itemTransition,
|
||||||
|
component: AnyComponent(ItemComponent(
|
||||||
|
item: item,
|
||||||
|
theme: component.theme,
|
||||||
|
isSelected: true
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: itemSize
|
||||||
|
)
|
||||||
|
|
||||||
|
let itemFrame = CGRect(origin: CGPoint(x: contentWidth, y: floor((contentHeight - itemSize.height) * 0.5)), size: itemSize)
|
||||||
|
if let itemComponentView = itemView.view as? ItemComponent.View, let selectedItemComponentView = selectedItemView.view as? ItemComponent.View {
|
||||||
|
if itemComponentView.superview == nil {
|
||||||
|
itemComponentView.isUserInteractionEnabled = false
|
||||||
|
selectedItemComponentView.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
if self.nativeTabBar != nil {
|
||||||
|
if let itemContainer = nativeItemContainers[index] {
|
||||||
|
itemContainer.addSubview(itemComponentView)
|
||||||
|
}
|
||||||
|
if let itemContainer = nativeSelectedItemContainers[index] {
|
||||||
|
itemContainer.addSubview(selectedItemComponentView)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.addSubview(itemComponentView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.nativeTabBar != nil {
|
||||||
|
if let parentView = itemComponentView.superview {
|
||||||
|
let itemFrame = CGRect(origin: CGPoint(x: floor((parentView.bounds.width - itemSize.width) * 0.5), y: floor((parentView.bounds.height - itemSize.height) * 0.5)), size: itemSize)
|
||||||
|
itemTransition.setFrame(view: itemComponentView, frame: itemFrame)
|
||||||
|
itemTransition.setFrame(view: selectedItemComponentView, frame: itemFrame)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
itemTransition.setFrame(view: itemComponentView, frame: itemFrame)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let previousComponent, previousComponent.selectedId != item.id, isItemSelected {
|
||||||
|
itemComponentView.playSelectionAnimation()
|
||||||
|
selectedItemComponentView.playSelectionAnimation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isItemSelected {
|
||||||
|
selectionFrame = itemFrame
|
||||||
|
}
|
||||||
|
|
||||||
|
contentWidth += itemFrame.width
|
||||||
|
}
|
||||||
|
contentWidth += innerInset
|
||||||
|
|
||||||
|
var removeIds: [AnyHashable] = []
|
||||||
|
for (id, itemView) in self.itemViews {
|
||||||
|
if !validIds.contains(id) {
|
||||||
|
removeIds.append(id)
|
||||||
|
itemView.view?.removeFromSuperview()
|
||||||
|
self.selectedItemViews[id]?.view?.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for id in removeIds {
|
||||||
|
self.itemViews.removeValue(forKey: id)
|
||||||
|
self.selectedItemViews.removeValue(forKey: id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let selectionFrame, self.nativeTabBar == nil {
|
||||||
|
var selectionViewTransition = transition
|
||||||
|
if self.selectionView.superview == nil {
|
||||||
|
selectionViewTransition = selectionViewTransition.withAnimation(.none)
|
||||||
|
self.backgroundView.contentView.addSubview(self.selectionView)
|
||||||
|
}
|
||||||
|
selectionViewTransition.setFrame(view: self.selectionView, frame: selectionFrame)
|
||||||
|
} else if self.selectionView.superview != nil {
|
||||||
|
self.selectionView.removeFromSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
let size = CGSize(width: min(availableSize.width, contentWidth), height: contentHeight)
|
||||||
|
|
||||||
|
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: component.theme.list.plainBackgroundColor.withMultipliedAlpha(0.75)), transition: transition)
|
||||||
|
|
||||||
|
if let nativeTabBar = self.nativeTabBar {
|
||||||
|
transition.setFrame(view: nativeTabBar, frame: CGRect(origin: CGPoint(x: floor((size.width - nativeTabBar.bounds.width) * 0.5), y: 0.0), size: nativeTabBar.bounds.size))
|
||||||
|
}
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func makeView() -> View {
|
||||||
|
return View(frame: CGRect())
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||||
|
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class ItemComponent: Component {
|
||||||
|
let item: TabBarComponent.Item
|
||||||
|
let theme: PresentationTheme
|
||||||
|
let isSelected: Bool
|
||||||
|
|
||||||
|
init(item: TabBarComponent.Item, theme: PresentationTheme, isSelected: Bool) {
|
||||||
|
self.item = item
|
||||||
|
self.theme = theme
|
||||||
|
self.isSelected = isSelected
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: ItemComponent, rhs: ItemComponent) -> Bool {
|
||||||
|
if lhs.item != rhs.item {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.theme !== rhs.theme {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.isSelected != rhs.isSelected {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
final class View: UIView {
|
||||||
|
private var imageIcon: ComponentView<Empty>?
|
||||||
|
private var animationIcon: ComponentView<Empty>?
|
||||||
|
private let title = ComponentView<Empty>()
|
||||||
|
private var badge: ComponentView<Empty>?
|
||||||
|
|
||||||
|
private var component: ItemComponent?
|
||||||
|
private weak var state: EmptyComponentState?
|
||||||
|
|
||||||
|
private var setImageListener: Int?
|
||||||
|
private var setSelectedImageListener: Int?
|
||||||
|
private var setBadgeListener: Int?
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
if let component = self.component {
|
||||||
|
if let setImageListener = self.setImageListener {
|
||||||
|
component.item.item.removeSetImageListener(setImageListener)
|
||||||
|
}
|
||||||
|
if let setSelectedImageListener = self.setSelectedImageListener {
|
||||||
|
component.item.item.removeSetSelectedImageListener(setSelectedImageListener)
|
||||||
|
}
|
||||||
|
if let setBadgeListener = self.setBadgeListener {
|
||||||
|
component.item.item.removeSetBadgeListener(setBadgeListener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func playSelectionAnimation() {
|
||||||
|
if let animationIconView = self.animationIcon?.view as? LottieComponent.View {
|
||||||
|
animationIconView.playOnce()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(component: ItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||||
|
let previousComponent = self.component
|
||||||
|
|
||||||
|
if previousComponent?.item.item !== component.item.item {
|
||||||
|
if let setImageListener = self.setImageListener {
|
||||||
|
self.component?.item.item.removeSetImageListener(setImageListener)
|
||||||
|
}
|
||||||
|
if let setSelectedImageListener = self.setSelectedImageListener {
|
||||||
|
self.component?.item.item.removeSetSelectedImageListener(setSelectedImageListener)
|
||||||
|
}
|
||||||
|
if let setBadgeListener = self.setBadgeListener {
|
||||||
|
self.component?.item.item.removeSetBadgeListener(setBadgeListener)
|
||||||
|
}
|
||||||
|
self.setImageListener = component.item.item.addSetImageListener { [weak self] _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.state?.updated(transition: .immediate, isLocal: true)
|
||||||
|
}
|
||||||
|
self.setSelectedImageListener = component.item.item.addSetSelectedImageListener { [weak self] _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.state?.updated(transition: .immediate, isLocal: true)
|
||||||
|
}
|
||||||
|
self.setBadgeListener = UITabBarItem_addSetBadgeListener(component.item.item) { [weak self] _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.state?.updated(transition: .immediate, isLocal: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.component = component
|
||||||
|
self.state = state
|
||||||
|
|
||||||
|
if let animationName = component.item.item.animationName {
|
||||||
|
if let imageIcon = self.imageIcon {
|
||||||
|
self.imageIcon = nil
|
||||||
|
imageIcon.view?.removeFromSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
let animationIcon: ComponentView<Empty>
|
||||||
|
var iconTransition = transition
|
||||||
|
if let current = self.animationIcon {
|
||||||
|
animationIcon = current
|
||||||
|
} else {
|
||||||
|
iconTransition = iconTransition.withAnimation(.none)
|
||||||
|
animationIcon = ComponentView()
|
||||||
|
self.animationIcon = animationIcon
|
||||||
|
}
|
||||||
|
|
||||||
|
let iconSize = animationIcon.update(
|
||||||
|
transition: iconTransition,
|
||||||
|
component: AnyComponent(LottieComponent(
|
||||||
|
content: LottieComponent.AppBundleContent(
|
||||||
|
name: animationName
|
||||||
|
),
|
||||||
|
color: component.isSelected ? component.theme.rootController.tabBar.selectedTextColor : component.theme.rootController.tabBar.textColor,
|
||||||
|
placeholderColor: nil,
|
||||||
|
startingPosition: .end,
|
||||||
|
size: CGSize(width: 48.0, height: 48.0),
|
||||||
|
loop: false
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: 48.0, height: 48.0)
|
||||||
|
)
|
||||||
|
let iconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) * 0.5), y: -4.0), size: iconSize).offsetBy(dx: component.item.item.animationOffset.x, dy: component.item.item.animationOffset.y)
|
||||||
|
if let animationIconView = animationIcon.view {
|
||||||
|
if animationIconView.superview == nil {
|
||||||
|
if let badgeView = self.badge?.view {
|
||||||
|
self.insertSubview(animationIconView, belowSubview: badgeView)
|
||||||
|
} else {
|
||||||
|
self.addSubview(animationIconView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
iconTransition.setFrame(view: animationIconView, frame: iconFrame)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let animationIcon = self.animationIcon {
|
||||||
|
self.animationIcon = nil
|
||||||
|
animationIcon.view?.removeFromSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
let imageIcon: ComponentView<Empty>
|
||||||
|
var iconTransition = transition
|
||||||
|
if let current = self.imageIcon {
|
||||||
|
imageIcon = current
|
||||||
|
} else {
|
||||||
|
iconTransition = iconTransition.withAnimation(.none)
|
||||||
|
imageIcon = ComponentView()
|
||||||
|
self.imageIcon = imageIcon
|
||||||
|
}
|
||||||
|
|
||||||
|
let iconSize = imageIcon.update(
|
||||||
|
transition: iconTransition,
|
||||||
|
component: AnyComponent(Image(
|
||||||
|
image: component.isSelected ? component.item.item.selectedImage : component.item.item.image,
|
||||||
|
tintColor: nil,
|
||||||
|
contentMode: .center
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||||
|
)
|
||||||
|
let iconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) * 0.5), y: 3.0), size: iconSize)
|
||||||
|
if let imageIconView = imageIcon.view {
|
||||||
|
if imageIconView.superview == nil {
|
||||||
|
if let badgeView = self.badge?.view {
|
||||||
|
self.insertSubview(imageIconView, belowSubview: badgeView)
|
||||||
|
} else {
|
||||||
|
self.addSubview(imageIconView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
iconTransition.setFrame(view: imageIconView, frame: iconFrame)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let titleSize = self.title.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(MultilineTextComponent(
|
||||||
|
text: .plain(NSAttributedString(string: component.item.item.title ?? " ", font: Font.semibold(10.0), textColor: component.isSelected ? component.theme.rootController.tabBar.selectedTextColor : component.theme.rootController.tabBar.textColor))
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: availableSize.width, height: 100.0)
|
||||||
|
)
|
||||||
|
let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: availableSize.height - 9.0 - titleSize.height), size: titleSize)
|
||||||
|
if let titleView = self.title.view {
|
||||||
|
if titleView.superview == nil {
|
||||||
|
self.addSubview(titleView)
|
||||||
|
}
|
||||||
|
titleView.frame = titleFrame
|
||||||
|
}
|
||||||
|
|
||||||
|
if let badgeText = component.item.item.badgeValue, !badgeText.isEmpty {
|
||||||
|
let badge: ComponentView<Empty>
|
||||||
|
var badgeTransition = transition
|
||||||
|
if let current = self.badge {
|
||||||
|
badge = current
|
||||||
|
} else {
|
||||||
|
badgeTransition = badgeTransition.withAnimation(.none)
|
||||||
|
badge = ComponentView()
|
||||||
|
self.badge = badge
|
||||||
|
}
|
||||||
|
let badgeSize = badge.update(
|
||||||
|
transition: badgeTransition,
|
||||||
|
component: AnyComponent(TextBadgeComponent(
|
||||||
|
text: badgeText,
|
||||||
|
font: Font.regular(13.0),
|
||||||
|
background: component.theme.rootController.tabBar.badgeBackgroundColor,
|
||||||
|
foreground: component.theme.rootController.tabBar.badgeTextColor,
|
||||||
|
insets: UIEdgeInsets(top: 0.0, left: 6.0, bottom: 1.0, right: 6.0)
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||||
|
)
|
||||||
|
let contentWidth: CGFloat = 25.0
|
||||||
|
let badgeFrame = CGRect(origin: CGPoint(x: floor(availableSize.width / 2.0) + contentWidth - badgeSize.width - 5.0, y: -1.0), size: badgeSize)
|
||||||
|
if let badgeView = badge.view {
|
||||||
|
if badgeView.superview == nil {
|
||||||
|
self.addSubview(badgeView)
|
||||||
|
}
|
||||||
|
badgeTransition.setFrame(view: badgeView, frame: badgeFrame)
|
||||||
|
}
|
||||||
|
} else if let badge = self.badge {
|
||||||
|
self.badge = nil
|
||||||
|
badge.view?.removeFromSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
return availableSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeView() -> View {
|
||||||
|
return View(frame: CGRect())
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||||
|
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1232,24 +1232,6 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
self.mainWindow.forEachViewController({ controller in
|
|
||||||
if let controller = controller as? TabBarAccountSwitchController {
|
|
||||||
if let rootController = self.mainWindow.viewController as? TelegramRootController {
|
|
||||||
if let tabsController = rootController.viewControllers.first as? TabBarController {
|
|
||||||
for i in 0 ..< tabsController.controllers.count {
|
|
||||||
if let _ = tabsController.controllers[i] as? (SettingsController & ViewController) {
|
|
||||||
let sourceNodes = tabsController.sourceNodesForController(at: i)
|
|
||||||
if let sourceNodes = sourceNodes {
|
|
||||||
controller.dismiss(sourceNodes: sourceNodes)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
self.mainWindow.topLevelOverlayControllers = [context.sharedApplicationContext.overlayMediaController, context.notificationController]
|
self.mainWindow.topLevelOverlayControllers = [context.sharedApplicationContext.overlayMediaController, context.notificationController]
|
||||||
(context.context.sharedContext as? SharedAccountContextImpl)?.notificationController = context.notificationController
|
(context.context.sharedContext as? SharedAccountContextImpl)?.notificationController = context.notificationController
|
||||||
var authorizeNotifications = true
|
var authorizeNotifications = true
|
||||||
|
|
|
||||||
|
|
@ -916,7 +916,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
||||||
}
|
}
|
||||||
|
|
||||||
self.dynamicBounceEnabled = !self.currentPresentationData.disableAnimations
|
self.dynamicBounceEnabled = !self.currentPresentationData.disableAnimations
|
||||||
self.experimentalSnapScrollToItem = true
|
self.experimentalSnapScrollToItem = false
|
||||||
|
|
||||||
//self.debugInfo = true
|
//self.debugInfo = true
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
|
||||||
|
|
||||||
self.buttonNode.view.addSubview(self.backgroundView)
|
self.buttonNode.view.addSubview(self.backgroundView)
|
||||||
self.backgroundView.frame = CGRect(origin: CGPoint(), size: size)
|
self.backgroundView.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65), transition: .immediate)
|
self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65)), transition: .immediate)
|
||||||
self.imageView.tintColor = theme.chat.inputPanel.inputControlColor
|
self.imageView.tintColor = theme.chat.inputPanel.inputControlColor
|
||||||
|
|
||||||
self.backgroundView.contentView.addSubview(self.imageView)
|
self.backgroundView.contentView.addSubview(self.imageView)
|
||||||
|
|
@ -110,7 +110,7 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
|
||||||
if self.theme !== theme {
|
if self.theme !== theme {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
|
||||||
self.backgroundView.update(size: self.backgroundView.bounds.size, cornerRadius: self.backgroundView.bounds.size.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65), transition: .immediate)
|
self.backgroundView.update(size: self.backgroundView.bounds.size, cornerRadius: self.backgroundView.bounds.size.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65)), transition: .immediate)
|
||||||
self.imageView.tintColor = theme.chat.inputPanel.inputControlColor
|
self.imageView.tintColor = theme.chat.inputPanel.inputControlColor
|
||||||
|
|
||||||
switch self.type {
|
switch self.type {
|
||||||
|
|
@ -124,7 +124,7 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
|
||||||
self.imageView.image = PresentationResourcesChat.chatHistoryReactionsButtonImage(theme)
|
self.imageView.image = PresentationResourcesChat.chatHistoryReactionsButtonImage(theme)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.badgeBackgroundView.update(size: self.badgeBackgroundView.bounds.size, cornerRadius: self.badgeBackgroundView.bounds.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: theme.chat.inputPanel.actionControlFillColor, transition: .immediate)
|
self.badgeBackgroundView.update(size: self.badgeBackgroundView.bounds.size, cornerRadius: self.badgeBackgroundView.bounds.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: .init(kind: .custom, color: theme.chat.inputPanel.actionControlFillColor), transition: .immediate)
|
||||||
|
|
||||||
var segments: [AnimatedCountLabelNode.Segment] = []
|
var segments: [AnimatedCountLabelNode.Segment] = []
|
||||||
if let value = Int(self.badge) {
|
if let value = Int(self.badge) {
|
||||||
|
|
@ -174,7 +174,7 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
|
||||||
self.badgeBackgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: self.theme.chat.inputPanel.actionControlFillColor, transition: ComponentTransition(transition))
|
self.badgeBackgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: .init(kind: .custom, color: self.theme.chat.inputPanel.actionControlFillColor), transition: ComponentTransition(transition))
|
||||||
|
|
||||||
self.badgeTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundFrame.width - badgeSize.width) / 2.0), y: 2.0), size: badgeSize)
|
self.badgeTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundFrame.width - badgeSize.width) / 2.0), y: 2.0), size: badgeSize)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -418,24 +418,6 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var isScheduledMessages = false
|
|
||||||
if case .scheduledMessages = chatPresentationInterfaceState.subject {
|
|
||||||
isScheduledMessages = true
|
|
||||||
}
|
|
||||||
var displayBotStartPanel = false
|
|
||||||
|
|
||||||
if !isScheduledMessages {
|
|
||||||
if let _ = chatPresentationInterfaceState.botStartPayload {
|
|
||||||
if let user = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser, user.botInfo != nil {
|
|
||||||
displayBotStartPanel = true
|
|
||||||
}
|
|
||||||
} else if let chatHistoryState = chatPresentationInterfaceState.chatHistoryState, case .loaded(true, _) = chatHistoryState {
|
|
||||||
if let user = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser, user.botInfo != nil {
|
|
||||||
displayBotStartPanel = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
displayInputTextPanel = true
|
displayInputTextPanel = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ private final class LegacyDataImportSplashImpl: WindowCoveringView, LegacyDataIm
|
||||||
self.updateLayout(size)
|
self.updateLayout(size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.progressNode.transitionToState(.progress(color: self.theme?.list.itemAccentColor ?? UIColor(rgb: 0x007aff), lineWidth: 2.0, value: CGFloat(max(0.025, self.progress.1)), cancelEnabled: false, animateRotation: true), animated: false, completion: {})
|
self.progressNode.transitionToState(.progress(color: self.theme?.list.itemAccentColor ?? UIColor(rgb: 0x0088ff), lineWidth: 2.0, value: CGFloat(max(0.025, self.progress.1)), cancelEnabled: false, animateRotation: true), animated: false, completion: {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
||||||
let previousTheme = strongSelf.presentationData.theme
|
let previousTheme = strongSelf.presentationData.theme
|
||||||
strongSelf.presentationData = presentationData
|
strongSelf.presentationData = presentationData
|
||||||
if previousTheme !== presentationData.theme {
|
if previousTheme !== presentationData.theme {
|
||||||
(strongSelf.rootTabController as? TabBarControllerImpl)?.updateTheme(navigationBarPresentationData: NavigationBarPresentationData(presentationData: presentationData), theme: TabBarControllerTheme(rootControllerTheme: presentationData.theme))
|
(strongSelf.rootTabController as? TabBarControllerImpl)?.updateTheme(theme: presentationData.theme)
|
||||||
strongSelf.rootTabController?.statusBar.statusBarStyle = presentationData.theme.rootController.statusBarStyle.style
|
strongSelf.rootTabController?.statusBar.statusBarStyle = presentationData.theme.rootController.statusBarStyle.style
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -188,7 +188,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
||||||
}
|
}
|
||||||
|
|
||||||
public func addRootControllers(showCallsTab: Bool) {
|
public func addRootControllers(showCallsTab: Bool) {
|
||||||
let tabBarController = TabBarControllerImpl(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), theme: TabBarControllerTheme(rootControllerTheme: self.presentationData.theme))
|
let tabBarController = TabBarControllerImpl(theme: self.presentationData.theme)
|
||||||
tabBarController.navigationPresentation = .master
|
tabBarController.navigationPresentation = .master
|
||||||
let chatListController = self.context.sharedContext.makeChatListController(context: self.context, location: .chatList(groupId: .root), controlsHistoryPreload: true, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: !GlobalExperimentalSettings.isAppStoreBuild)
|
let chatListController = self.context.sharedContext.makeChatListController(context: self.context, location: .chatList(groupId: .root), controlsHistoryPreload: true, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: !GlobalExperimentalSettings.isAppStoreBuild)
|
||||||
if let sharedContext = self.context.sharedContext as? SharedAccountContextImpl {
|
if let sharedContext = self.context.sharedContext as? SharedAccountContextImpl {
|
||||||
|
|
|
||||||
|
|
@ -407,7 +407,7 @@ public enum PresentationThemeBaseColor: Int32, CaseIterable {
|
||||||
let value: UInt32
|
let value: UInt32
|
||||||
switch self {
|
switch self {
|
||||||
case .blue:
|
case .blue:
|
||||||
value = 0x007aff
|
value = 0x0088ff
|
||||||
case .cyan:
|
case .cyan:
|
||||||
value = 0x00c2ed
|
value = 0x00c2ed
|
||||||
case .green:
|
case .green:
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,39 @@ public protocol WallpaperBackgroundNode: ASDisplayNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class EffectImageLayer: SimpleLayer, GradientBackgroundPatternOverlayLayer {
|
private final class EffectImageLayer: SimpleLayer, GradientBackgroundPatternOverlayLayer {
|
||||||
|
final class CloneLayer: SimpleLayer {
|
||||||
|
private weak var parentLayer: EffectImageLayer?
|
||||||
|
private var index: SparseBag<Weak<CloneLayer>>.Index?
|
||||||
|
|
||||||
|
init(parentLayer: EffectImageLayer) {
|
||||||
|
self.parentLayer = parentLayer
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.index = parentLayer.cloneLayers.add(Weak(self))
|
||||||
|
|
||||||
|
self.backgroundColor = parentLayer.backgroundColor
|
||||||
|
self.contents = parentLayer.contents
|
||||||
|
self.compositingFilter = parentLayer.compositingFilter
|
||||||
|
self.opacity = parentLayer.opacity
|
||||||
|
self.isOpaque = parentLayer.isOpaque
|
||||||
|
}
|
||||||
|
|
||||||
|
override init(layer: Any) {
|
||||||
|
super.init(layer: layer)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
if let parentLayer = self.parentLayer, let index = self.index {
|
||||||
|
parentLayer.cloneLayers.remove(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum SoftlightMode {
|
enum SoftlightMode {
|
||||||
case whileAnimating
|
case whileAnimating
|
||||||
case always
|
case always
|
||||||
|
|
@ -141,6 +174,12 @@ private final class EffectImageLayer: SimpleLayer, GradientBackgroundPatternOver
|
||||||
} else {
|
} else {
|
||||||
self.backgroundColor = nil
|
self.backgroundColor = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for cloneLayer in self.cloneLayers {
|
||||||
|
if let value = cloneLayer.value {
|
||||||
|
value.backgroundColor = self.backgroundColor
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -184,6 +223,8 @@ private final class EffectImageLayer: SimpleLayer, GradientBackgroundPatternOver
|
||||||
var suspendCompositionUpdates: Bool = false
|
var suspendCompositionUpdates: Bool = false
|
||||||
private var needsCompositionUpdate: Bool = false
|
private var needsCompositionUpdate: Bool = false
|
||||||
|
|
||||||
|
fileprivate let cloneLayers = SparseBag<Weak<CloneLayer>>()
|
||||||
|
|
||||||
private func updateFilters() {
|
private func updateFilters() {
|
||||||
let useSoftlight: Bool
|
let useSoftlight: Bool
|
||||||
let useFilter: Bool
|
let useFilter: Bool
|
||||||
|
|
@ -208,6 +249,12 @@ private final class EffectImageLayer: SimpleLayer, GradientBackgroundPatternOver
|
||||||
self.compositingFilter = nil
|
self.compositingFilter = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for cloneLayer in self.cloneLayers {
|
||||||
|
if let value = cloneLayer.value {
|
||||||
|
value.compositingFilter = self.compositingFilter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.updateContents()
|
self.updateContents()
|
||||||
self.updateOpacity()
|
self.updateOpacity()
|
||||||
}
|
}
|
||||||
|
|
@ -330,6 +377,13 @@ private final class EffectImageLayer: SimpleLayer, GradientBackgroundPatternOver
|
||||||
self.allowSettingContents = false
|
self.allowSettingContents = false
|
||||||
|
|
||||||
self.backgroundColor = nil
|
self.backgroundColor = nil
|
||||||
|
|
||||||
|
for cloneLayer in self.cloneLayers {
|
||||||
|
if let value = cloneLayer.value {
|
||||||
|
value.contents = self.contents
|
||||||
|
value.backgroundColor = self.backgroundColor
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -345,6 +399,13 @@ private final class EffectImageLayer: SimpleLayer, GradientBackgroundPatternOver
|
||||||
self.allowSettingOpacity = false
|
self.allowSettingOpacity = false
|
||||||
self.isOpaque = true
|
self.isOpaque = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for cloneLayer in self.cloneLayers {
|
||||||
|
if let value = cloneLayer.value {
|
||||||
|
value.opacity = self.opacity
|
||||||
|
value.isOpaque = self.isOpaque
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -726,6 +787,8 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
|
||||||
|
|
||||||
private let contentNode: ASDisplayNode
|
private let contentNode: ASDisplayNode
|
||||||
|
|
||||||
|
fileprivate let edgeEffectNodes = SparseBag<Weak<WallpaperEdgeEffectNodeImpl>>()
|
||||||
|
|
||||||
private var blurredBackgroundContents: UIImage?
|
private var blurredBackgroundContents: UIImage?
|
||||||
|
|
||||||
private var freeBackgroundPortalSourceView: PortalSourceView?
|
private var freeBackgroundPortalSourceView: PortalSourceView?
|
||||||
|
|
@ -773,9 +836,9 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var gradientBackgroundNode: GradientBackgroundNode?
|
fileprivate var gradientBackgroundNode: GradientBackgroundNode?
|
||||||
private var outgoingBubbleGradientBackgroundNode: GradientBackgroundNode?
|
private var outgoingBubbleGradientBackgroundNode: GradientBackgroundNode?
|
||||||
private let patternImageLayer: EffectImageLayer
|
fileprivate let patternImageLayer: EffectImageLayer
|
||||||
private let dimLayer: SimpleLayer
|
private let dimLayer: SimpleLayer
|
||||||
private var isGeneratingPatternImage: Bool = false
|
private var isGeneratingPatternImage: Bool = false
|
||||||
|
|
||||||
|
|
@ -786,8 +849,6 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
|
||||||
|
|
||||||
private var modelStickerNode: DefaultAnimatedStickerNodeImpl?
|
private var modelStickerNode: DefaultAnimatedStickerNodeImpl?
|
||||||
|
|
||||||
fileprivate let edgeEffectNodes = SparseBag<Weak<WallpaperEdgeEffectNodeImpl>>()
|
|
||||||
|
|
||||||
private var isSettingUpWallpaper: Bool = false
|
private var isSettingUpWallpaper: Bool = false
|
||||||
|
|
||||||
private struct CachedValidPatternImage {
|
private struct CachedValidPatternImage {
|
||||||
|
|
@ -1057,6 +1118,12 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
|
||||||
if self.isLooping {
|
if self.isLooping {
|
||||||
scheduleLoopingEvent = true
|
scheduleLoopingEvent = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for edgeEffectNode in self.edgeEffectNodes {
|
||||||
|
if let edgeEffectNode = edgeEffectNode.value {
|
||||||
|
edgeEffectNode.updateGradientNode()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.gradientBackgroundNode?.updateColors(colors: mappedColors)
|
self.gradientBackgroundNode?.updateColors(colors: mappedColors)
|
||||||
|
|
||||||
|
|
@ -1712,31 +1779,12 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
|
||||||
}
|
}
|
||||||
|
|
||||||
public func makeEdgeEffectNode() -> WallpaperEdgeEffectNode? {
|
public func makeEdgeEffectNode() -> WallpaperEdgeEffectNode? {
|
||||||
if let gradientBackgroundNode = self.gradientBackgroundNode {
|
let node = WallpaperEdgeEffectNodeImpl(parentNode: self)
|
||||||
let node = WallpaperEdgeEffectNodeImpl(parentNode: self)
|
return node
|
||||||
node.cloneNode = GradientBackgroundNode.CloneNode(parentNode: gradientBackgroundNode, isDimmed: false)
|
|
||||||
return node
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class WallpaperEdgeEffectNodeImpl: ASDisplayNode, WallpaperEdgeEffectNode {
|
private final class WallpaperEdgeEffectNodeImpl: ASDisplayNode, WallpaperEdgeEffectNode {
|
||||||
var cloneNode: GradientBackgroundNode.CloneNode? {
|
|
||||||
didSet {
|
|
||||||
if self.cloneNode !== oldValue {
|
|
||||||
if let cloneNode = self.cloneNode {
|
|
||||||
self.containerNode.insertSubnode(cloneNode, at: 0)
|
|
||||||
|
|
||||||
if let params = self.params {
|
|
||||||
self.updateImpl(rect: params.rect, edge: params.edge, containerSize: params.containerSize, transition: .immediate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct Params: Equatable {
|
private struct Params: Equatable {
|
||||||
let rect: CGRect
|
let rect: CGRect
|
||||||
let edge: WallpaperEdgeEffectEdge
|
let edge: WallpaperEdgeEffectEdge
|
||||||
|
|
@ -1749,6 +1797,9 @@ private final class WallpaperEdgeEffectNodeImpl: ASDisplayNode, WallpaperEdgeEff
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var gradientNode: GradientBackgroundNode.CloneNode?
|
||||||
|
private let patternImageLayer: EffectImageLayer.CloneLayer
|
||||||
|
|
||||||
private let containerNode: ASDisplayNode
|
private let containerNode: ASDisplayNode
|
||||||
private let containerMaskingNode: ASDisplayNode
|
private let containerMaskingNode: ASDisplayNode
|
||||||
private let overlayNode: ASDisplayNode
|
private let overlayNode: ASDisplayNode
|
||||||
|
|
@ -1763,6 +1814,14 @@ private final class WallpaperEdgeEffectNodeImpl: ASDisplayNode, WallpaperEdgeEff
|
||||||
init(parentNode: WallpaperBackgroundNodeImpl) {
|
init(parentNode: WallpaperBackgroundNodeImpl) {
|
||||||
self.parentNode = parentNode
|
self.parentNode = parentNode
|
||||||
|
|
||||||
|
if let gradientBackgroundNode = parentNode.gradientBackgroundNode {
|
||||||
|
self.gradientNode = GradientBackgroundNode.CloneNode(parentNode: gradientBackgroundNode, isDimmed: false)
|
||||||
|
} else {
|
||||||
|
self.gradientNode = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
self.patternImageLayer = EffectImageLayer.CloneLayer(parentLayer: parentNode.patternImageLayer)
|
||||||
|
|
||||||
self.containerNode = ASDisplayNode()
|
self.containerNode = ASDisplayNode()
|
||||||
self.containerNode.anchorPoint = CGPoint()
|
self.containerNode.anchorPoint = CGPoint()
|
||||||
self.containerNode.clipsToBounds = true
|
self.containerNode.clipsToBounds = true
|
||||||
|
|
@ -1776,6 +1835,11 @@ private final class WallpaperEdgeEffectNodeImpl: ASDisplayNode, WallpaperEdgeEff
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
if let gradientNode = self.gradientNode {
|
||||||
|
self.containerNode.addSubnode(gradientNode)
|
||||||
|
}
|
||||||
|
//self.layer.addSublayer(self.patternImageLayer)
|
||||||
|
|
||||||
self.addSubnode(self.containerMaskingNode)
|
self.addSubnode(self.containerMaskingNode)
|
||||||
self.containerMaskingNode.view.mask = self.maskView
|
self.containerMaskingNode.view.mask = self.maskView
|
||||||
|
|
||||||
|
|
@ -1790,6 +1854,25 @@ private final class WallpaperEdgeEffectNodeImpl: ASDisplayNode, WallpaperEdgeEff
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateGradientNode() {
|
||||||
|
if let gradientBackgroundNode = self.parentNode?.gradientBackgroundNode {
|
||||||
|
if self.gradientNode == nil {
|
||||||
|
let gradientNode = GradientBackgroundNode.CloneNode(parentNode: gradientBackgroundNode, isDimmed: false)
|
||||||
|
self.gradientNode = gradientNode
|
||||||
|
self.containerNode.insertSubnode(gradientNode, at: 0)
|
||||||
|
|
||||||
|
if let params = self.params {
|
||||||
|
self.updateImpl(rect: params.rect, edge: params.edge, containerSize: params.containerSize, transition: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let gradientNode = self.gradientNode {
|
||||||
|
self.gradientNode = nil
|
||||||
|
gradientNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func updatePattern(isInverted: Bool) {
|
func updatePattern(isInverted: Bool) {
|
||||||
if self.isInverted != isInverted {
|
if self.isInverted != isInverted {
|
||||||
self.isInverted = isInverted
|
self.isInverted = isInverted
|
||||||
|
|
@ -1844,9 +1927,10 @@ private final class WallpaperEdgeEffectNodeImpl: ASDisplayNode, WallpaperEdgeEff
|
||||||
|
|
||||||
transition.updateFrame(node: self.overlayNode, frame: CGRect(origin: CGPoint(), size: containerSize))
|
transition.updateFrame(node: self.overlayNode, frame: CGRect(origin: CGPoint(), size: containerSize))
|
||||||
|
|
||||||
if let cloneNode = self.cloneNode {
|
if let gradientNode = self.gradientNode {
|
||||||
transition.updateFrame(node: cloneNode, frame: CGRect(origin: CGPoint(), size: containerSize))
|
transition.updateFrame(node: gradientNode, frame: CGRect(origin: CGPoint(), size: containerSize))
|
||||||
}
|
}
|
||||||
|
transition.updateFrame(layer: self.patternImageLayer, frame: CGRect(origin: CGPoint(), size: containerSize))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue