This commit is contained in:
Isaac 2025-09-12 10:54:22 +02:00
parent 6ee1edc8b0
commit ebcd0557e5
76 changed files with 1352 additions and 1090 deletions

View file

@ -16,7 +16,7 @@ private func generateIndefiniteActivityIndicatorImage(color: UIColor, diameter:
}
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
} else if color.isEqual(UIColor(rgb: 0x2ea6ff)) {
return .white

View file

@ -461,7 +461,7 @@ final class BlobNode: ASDisplayNode {
if let backgroundView = self.backgroundView, let color = self.color {
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))
}
}

View file

@ -117,6 +117,7 @@ swift_library(
"//submodules/TelegramUI/Components/Ads/AdsReportScreen",
"//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/TelegramUI/Components/AnimatedTextComponent",
"//submodules/TelegramUI/Components/EdgeEffect",
],
visibility = [
"//visibility:public",

View file

@ -13,6 +13,8 @@ import Postbox
import ChatListHeaderComponent
import ActionPanelComponent
import ChatFolderLinkPreviewScreen
import EdgeEffect
import ComponentDisplayAdapters
final class ChatListContainerItemNode: ASDisplayNode {
private final class TopPanelItem {
@ -37,6 +39,8 @@ final class ChatListContainerItemNode: ASDisplayNode {
private var floatingHeaderOffset: CGFloat?
private let edgeEffectView: EdgeEffectView
private(set) var emptyNode: ChatListEmptyNode?
var emptyShimmerEffectNode: ChatListShimmerNode?
private var shimmerNodeOffset: CGFloat = 0.0
@ -74,9 +78,12 @@ final class ChatListContainerItemNode: ASDisplayNode {
self.listNode.scrollHeightTopInset = ChatListNavigationBar.searchScrollHeight + ChatListNavigationBar.storiesScrollHeight
}
self.edgeEffectView = EdgeEffectView()
super.init()
self.addSubnode(self.listNode)
self.view.addSubview(self.edgeEffectView)
self.listNode.isEmptyUpdated = { [weak self] isEmptyState, _, transition in
guard let strongSelf = self else {
@ -442,6 +449,11 @@ final class ChatListContainerItemNode: ASDisplayNode {
}
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) {

View file

@ -21,6 +21,7 @@ import ChatFolderLinkPreviewScreen
import ChatListHeaderComponent
import StoryPeerListComponent
import TelegramNotices
import EdgeEffect
public enum ChatListContainerNodeFilter: Equatable {
case all

View file

@ -46,6 +46,7 @@ swift_library(
"//submodules/UndoUI",
"//submodules/TelegramIntents",
"//submodules/ContextUI",
"//submodules/TelegramUI/Components/EdgeEffect",
],
visibility = [
"//visibility:public",

View file

@ -17,6 +17,7 @@ import ChatListTitleView
import ComponentFlow
import SwiftUI
import ContactsUI
import EdgeEffect
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
let controller: ViewController
@ -48,6 +49,7 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent
final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
let contactListNode: ContactListNode
private let edgeEffectView: EdgeEffectView
private let context: AccountContext
private(set) var searchDisplayController: SearchDisplayController?
@ -116,6 +118,8 @@ final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
contextAction?(peer, node, gesture, location, isStories)
})
self.edgeEffectView = EdgeEffectView()
super.init()
self.setViewBlock({
@ -125,6 +129,7 @@ final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
self.backgroundColor = self.presentationData.theme.chatList.backgroundColor
self.addSubnode(self.contactListNode)
self.view.addSubview(self.edgeEffectView)
self.presentationDataDisposable = (context.sharedContext.presentationData
|> 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)
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)
if let navigationBarComponentView = self.navigationBarView.view as? ChatListNavigationBar.View {

View file

@ -31,7 +31,7 @@ public class NavigationBackButtonNode: ASControlNode {
}
}
public var color: UIColor = UIColor(rgb: 0x007aff) {
public var color: UIColor = UIColor(rgb: 0x0088ff) {
didSet {
self.label.attributedText = NSAttributedString(string: self._text, attributes: self.attributesForCurrentState())
}

View file

@ -107,7 +107,7 @@ private final class NavigationButtonItemNode: ImmediateTextNode {
}
}
public var color: UIColor = UIColor(rgb: 0x007aff) {
public var color: UIColor = UIColor(rgb: 0x0088ff) {
didSet {
if let text = self._text {
self.attributedText = NSAttributedString(string: text, attributes: self.attributesForCurrentState())
@ -347,7 +347,7 @@ public final class NavigationButtonNode: ContextControllerSourceNode {
public var pressed: (Int) -> () = { _ in }
public var highlightChanged: (Int, Bool) -> () = { _, _ in }
public var color: UIColor = UIColor(rgb: 0x007aff) {
public var color: UIColor = UIColor(rgb: 0x0088ff) {
didSet {
if !self.color.isEqual(oldValue) {
for node in self.nodes {

View file

@ -13,16 +13,12 @@ public protocol TabBarController: ViewController {
var controllers: [ViewController] { get }
var selectedIndex: Int { get set }
var cameraItemAndAction: (item: UITabBarItem, action: () -> Void)? { get set }
func setControllers(_ controllers: [ViewController], selectedIndex: Int?)
func updateBackgroundAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition)
func viewForCameraItem() -> UIView?
func frameForControllerTab(controller: ViewController) -> CGRect?
func isPointInsideContentArea(point: CGPoint) -> Bool
func sourceNodesForController(at index: Int) -> [ASDisplayNode]?
func updateIsTabBarEnabled(_ value: Bool, transition: ContainedViewLayoutTransition)
func updateIsTabBarHidden(_ value: Bool, transition: ContainedViewLayoutTransition)

View file

@ -193,11 +193,7 @@ public final class WindowHostView {
}
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
} else {
return nil
}
return self.eventView.safeAreaInsets.bottom.isLessThanOrEqualTo(0.0) ? nil : self.eventView.safeAreaInsets.bottom
}
}

View file

@ -544,7 +544,7 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
self.textView.frameColor = nil
case .filled:
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.frameColor = color
case .semi:

View file

@ -571,7 +571,7 @@ private func galleryEntriesForMessageHistoryEntries(_ entries: [MessageHistoryEn
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 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 {
return self.displayNode as! GalleryControllerNode

View file

@ -776,7 +776,7 @@ final class SettingsHeaderButton: HighlightableButtonNode {
component: AnyComponent(BadgeComponent(
text: badgeText,
font: self.badgeFont,
cornerRadius: 3.0,
cornerRadius: .custom(3.0),
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)
)),
@ -818,7 +818,7 @@ final class SettingsHeaderButton: HighlightableButtonNode {
component: AnyComponent(BadgeComponent(
text: badgeText,
font: self.badgeFont,
cornerRadius: 3.0,
cornerRadius: .custom(3.0),
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)
)),

View file

@ -71,10 +71,10 @@ class ChartStackSection: UIView, ChartThemeContainer {
backButton.addTarget(self, action: #selector(self.didTapBackButton), for: .touchUpInside)
backButton.setTitle("Zoom Out", for: .normal)
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.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.setVisible(false, animated: false)

View file

@ -911,7 +911,7 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
if let current = self.linkHighlightingNode {
linkHighlightingNode = current
} 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.isUserInteractionEnabled = false
self.linkHighlightingNode = linkHighlightingNode

View file

@ -69,7 +69,7 @@ private let lightTheme = InstantPageSettingsItemTheme(
separatorColor: UIColor(rgb: 0xc8c7cc),
primaryColor: .black,
secondaryColor: UIColor(rgb: 0xa8a8a8),
accentColor: UIColor(rgb: 0x007aff)
accentColor: UIColor(rgb: 0x0088ff)
)
private let sepiaTheme = InstantPageSettingsItemTheme(

View file

@ -101,7 +101,7 @@ final class InstantPageSettingsThemeItemNode: InstantPageSettingsItemNode {
selectedIndex = 3
}
let selectionColor = UIColor(rgb: 0x007aff)
let selectionColor = UIColor(rgb: 0x0088ff)
self.themeNodes = [
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),

View file

@ -153,15 +153,15 @@ private let lightTheme = InstantPageTheme(
),
serif: false,
codeBlockBackgroundColor: UIColor(rgb: 0xf5f8fc),
linkColor: UIColor(rgb: 0x007aff),
linkColor: UIColor(rgb: 0x0088ff),
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),
panelBackgroundColor: UIColor(rgb: 0xf3f4f5),
panelHighlightedBackgroundColor: UIColor(rgb: 0xe7e7e7),
panelPrimaryColor: .black,
panelSecondaryColor: UIColor(rgb: 0x79828b),
panelAccentColor: UIColor(rgb: 0x007aff),
panelAccentColor: UIColor(rgb: 0x0088ff),
tableBorderColor: UIColor(rgb: 0xe2e2e2),
tableHeaderColor: UIColor(rgb: 0xf4f4f4),
controlColor: UIColor(rgb: 0xc7c7cd),

View file

@ -60,7 +60,7 @@
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];
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];
hex = 0xcacacf;

View file

@ -16,7 +16,7 @@ UIColor *TGAccentColor()
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
color = TGColorWithHex(0x007aff);
color = TGColorWithHex(0x0088ff);
});
return color;
}

View file

@ -466,7 +466,7 @@
_saveCoverButton.clipsToBounds = true;
_saveCoverButton.layer.cornerRadius = 10.0;
_saveCoverButton.hidden = true;
[_saveCoverButton setBackgroundColor:UIColorRGB(0x007aff)];
[_saveCoverButton setBackgroundColor:UIColorRGB(0x0088ff)];
_saveCoverButton.titleLabel.font = TGBoldSystemFontOfSize(17.0);
[_saveCoverButton setTitle:TGLocalized(@"Media.SaveCover") forState:UIControlStateNormal];
[_saveCoverButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];

View file

@ -719,7 +719,7 @@ private class SendStarsButtonView: HighlightTrackingButton, TGPhotoSendStarsButt
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))
self.backgroundView.layer.cornerRadius = backgroundSize.height / 2.0
self.backgroundView.backgroundColor = UIColor(rgb: 0x007aff)
self.backgroundView.backgroundColor = UIColor(rgb: 0x0088ff)
return buttonSize;
}

View file

@ -62,7 +62,7 @@ private func generateHeadingArrowImage() -> UIImage? {
context.clip()
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 gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
@ -184,7 +184,7 @@ public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
let pulseImage: UIImage?
let arrowImage: UIImage?
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 {
pulseImage = nil
}

View file

@ -197,7 +197,7 @@ public class LocationPinAnnotationView: MKAnnotationView {
self.pulseNode = ASImageNode()
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.arrowNode = ASImageNode()

View file

@ -101,7 +101,7 @@ func generateHeadingArrowImage() -> UIImage? {
context.clip()
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 gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!

View file

@ -310,7 +310,7 @@ private final class BusinessListComponent: CombinedComponent {
UIColor(rgb: 0x9b4fed),
UIColor(rgb: 0x8958ff),
UIColor(rgb: 0x676bff),
UIColor(rgb: 0x007aff)
UIColor(rgb: 0x0088ff)
]
let titleColor = theme.list.itemPrimaryTextColor

View file

@ -2306,7 +2306,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
UIColor(rgb: 0x9b4fed),
UIColor(rgb: 0x8958ff),
UIColor(rgb: 0x676bff),
UIColor(rgb: 0x007aff)
UIColor(rgb: 0x0088ff)
]
var i = 0

View file

@ -313,7 +313,7 @@ private final class StoriesListComponent: CombinedComponent {
let strings = context.component.context.sharedContext.currentPresentationData.with { $0 }.strings
let colors = [
UIColor(rgb: 0x007aff),
UIColor(rgb: 0x0088ff),
UIColor(rgb: 0x798aff),
UIColor(rgb: 0xac64f3),
UIColor(rgb: 0xc456ae),

View file

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

View file

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

View file

@ -31,14 +31,14 @@ var dayClassicColorPresets: [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: 103, baseColor: .preset, accentColor: 0xd33213, bubbleColors: [0xf9db00, 0xd33213], wallpaper: nil),
PresentationThemeAccentColor(index: 104, baseColor: .preset, accentColor: 0xea8ced, bubbleColors: [0xea8ced, 0x00c2ed], wallpaper: nil)
]
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: 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))

View file

@ -385,7 +385,7 @@ private final class ThemeSettingsAccentColorIconItemNode : ListViewItemNode {
topColor = bubbleColor
bottomColor = bubbleColor
} else {
fillColor = UIColor(rgb: 0x007aff)
fillColor = UIColor(rgb: 0x0088ff)
strokeColor = fillColor
topColor = UIColor(rgb: 0xe1ffc7)
bottomColor = topColor

View file

@ -10,12 +10,15 @@ swift_library(
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display",
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/AnimatedStickerNode",
"//submodules/TelegramAnimatedStickerNode",
"//submodules/TelegramPresentationData",
"//submodules/ComponentFlow",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/TelegramUI/Components/TabBarComponent",
],
visibility = [
"//visibility:public",

View file

@ -2,24 +2,72 @@ import Foundation
import UIKit
import AsyncDisplayKit
import Display
import TelegramPresentationData
import ComponentFlow
import ComponentDisplayAdapters
import TabBarComponent
private extension ToolbarTheme {
convenience init(tabBarTheme theme: TabBarControllerTheme) {
self.init(barBackgroundColor: theme.tabBarBackgroundColor, barSeparatorColor: theme.tabBarSeparatorColor, barTextColor: theme.tabBarTextColor, barSelectedTextColor: theme.tabBarSelectedTextColor)
convenience init(theme: PresentationTheme) {
self.init(barBackgroundColor: theme.rootController.tabBar.backgroundColor, barSeparatorColor: .clear, barTextColor: theme.rootController.tabBar.textColor, barSelectedTextColor: theme.rootController.tabBar.selectedTextColor)
}
}
final class TabBarControllerNode: ASDisplayNode {
private var navigationBarPresentationData: NavigationBarPresentationData
private var theme: TabBarControllerTheme
let tabBarNode: TabBarNode
private struct Params: Equatable {
let layout: ContainerViewLayout
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 var toolbarNode: ToolbarNode?
private let toolbarActionSelected: (ToolbarActionOption) -> Void
private let disabledPressed: () -> Void
private(set) var tabBarItems: [TabBarNodeItem] = []
private(set) var selectedIndex: Int = 0
var currentControllerNode: ASDisplayNode?
private var layoutResult: LayoutResult?
private var isUpdateRequested: Bool = false
private var isChangingSelectedIndex: Bool = false
func setCurrentControllerNode(_ node: ASDisplayNode?) -> () -> Void {
guard node !== self.currentControllerNode else {
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.navigationBarPresentationData = navigationBarPresentationData
self.itemSelected = itemSelected
self.contextAction = contextAction
self.tabBarNode = TabBarNode(theme: theme, itemSelected: itemSelected, contextAction: contextAction, swipeAction: swipeAction)
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.toolbarActionSelected = toolbarActionSelected
self.disabledPressed = disabledPressed
@ -55,13 +104,25 @@ final class TabBarControllerNode: ASDisplayNode {
super.init()
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.addSubnode(self.disabledOverlayNode)
self.backgroundColor = theme.list.plainBackgroundColor
//self.addSubnode(self.tabBarNode)
//self.addSubnode(self.disabledOverlayNode)
}
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.navigationBarPresentationData = navigationBarPresentationData
self.backgroundColor = theme.backgroundColor
self.backgroundColor = theme.list.plainBackgroundColor
self.tabBarNode.updateTheme(theme)
self.disabledOverlayNode.backgroundColor = theme.backgroundColor.withAlphaComponent(0.5)
self.toolbarNode?.updateTheme(ToolbarTheme(tabBarTheme: theme))
self.disabledOverlayNode.backgroundColor = theme.rootController.tabBar.backgroundColor.withAlphaComponent(0.5)
self.toolbarNode?.updateTheme(ToolbarTheme(theme: theme))
self.requestUpdate()
}
func updateIsTabBarEnabled(_ value: Bool, transition: ContainedViewLayoutTransition) {
@ -92,32 +153,91 @@ final class TabBarControllerNode: ASDisplayNode {
var tabBarHidden = false
func containerLayoutUpdated(_ layout: ContainerViewLayout, toolbar: Toolbar?, transition: ContainedViewLayoutTransition) {
var tabBarHeight: CGFloat
func containerLayoutUpdated(_ layout: ContainerViewLayout, toolbar: Toolbar?, transition: ContainedViewLayoutTransition) -> 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 = []
if layout.metrics.widthClass == .regular {
if params.layout.metrics.widthClass == .regular {
options.insert(.input)
}
let bottomInset: CGFloat = layout.insets(options: options).bottom
if !layout.safeInsets.left.isZero {
tabBarHeight = 34.0 + bottomInset
var bottomInset: CGFloat = params.layout.insets(options: options).bottom
if bottomInset == 0.0 {
bottomInset = 8.0
} 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: layout.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: bottomInset, transition: transition)
//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.disabledOverlayNode, frame: tabBarFrame)
if let toolbar = toolbar {
if let toolbar = params.toolbar {
if let toolbarNode = self.toolbarNode {
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 {
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)
}, right: { [weak self] in
self?.toolbarActionSelected(.right)
@ -125,7 +245,7 @@ final class TabBarControllerNode: ASDisplayNode {
self?.toolbarActionSelected(.middle)
})
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.toolbarNode = toolbarNode
if transition.isAnimated {
@ -138,5 +258,31 @@ final class TabBarControllerNode: ASDisplayNode {
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()
}
}

View file

@ -5,41 +5,6 @@ import SwiftSignalKit
import Display
import TelegramPresentationData
public final class TabBarControllerTheme {
public let backgroundColor: UIColor
public let tabBarBackgroundColor: UIColor
public let tabBarSeparatorColor: UIColor
public let tabBarIconColor: UIColor
public let tabBarSelectedIconColor: UIColor
public let tabBarTextColor: UIColor
public let tabBarSelectedTextColor: UIColor
public let tabBarBadgeBackgroundColor: UIColor
public let tabBarBadgeStrokeColor: UIColor
public let tabBarBadgeTextColor: UIColor
public let tabBarExtractedIconColor: UIColor
public let tabBarExtractedTextColor: UIColor
public init(backgroundColor: UIColor, tabBarBackgroundColor: UIColor, tabBarSeparatorColor: UIColor, tabBarIconColor: UIColor, tabBarSelectedIconColor: UIColor, tabBarTextColor: UIColor, tabBarSelectedTextColor: UIColor, tabBarBadgeBackgroundColor: UIColor, tabBarBadgeStrokeColor: UIColor, tabBarBadgeTextColor: UIColor, tabBarExtractedIconColor: UIColor, tabBarExtractedTextColor: UIColor) {
self.backgroundColor = backgroundColor
self.tabBarBackgroundColor = tabBarBackgroundColor
self.tabBarSeparatorColor = tabBarSeparatorColor
self.tabBarIconColor = tabBarIconColor
self.tabBarSelectedIconColor = tabBarSelectedIconColor
self.tabBarTextColor = tabBarTextColor
self.tabBarSelectedTextColor = tabBarSelectedTextColor
self.tabBarBadgeBackgroundColor = tabBarBadgeBackgroundColor
self.tabBarBadgeStrokeColor = tabBarBadgeStrokeColor
self.tabBarBadgeTextColor = tabBarBadgeTextColor
self.tabBarExtractedIconColor = tabBarExtractedIconColor
self.tabBarExtractedTextColor = tabBarExtractedTextColor
}
public convenience init(rootControllerTheme: PresentationTheme) {
let theme = rootControllerTheme.rootController.tabBar
self.init(backgroundColor: rootControllerTheme.list.plainBackgroundColor, tabBarBackgroundColor: theme.backgroundColor, tabBarSeparatorColor: theme.separatorColor, tabBarIconColor: theme.iconColor, tabBarSelectedIconColor: theme.selectedIconColor, tabBarTextColor: theme.textColor, tabBarSelectedTextColor: theme.selectedTextColor, tabBarBadgeBackgroundColor: theme.badgeBackgroundColor, tabBarBadgeStrokeColor: theme.badgeStrokeColor, tabBarBadgeTextColor: theme.badgeTextColor, tabBarExtractedIconColor: rootControllerTheme.contextMenu.extractedContentTintColor, tabBarExtractedTextColor: rootControllerTheme.contextMenu.extractedContentTintColor)
}
}
public final class TabBarItemInfo: NSObject {
public let previewing: Bool
@ -127,13 +92,9 @@ open class TabBarControllerImpl: ViewController, TabBarController {
private let pendingControllerDisposable = MetaDisposable()
private var navigationBarPresentationData: NavigationBarPresentationData
private var theme: TabBarControllerTheme
private var theme: PresentationTheme
public var cameraItemAndAction: (item: UITabBarItem, action: () -> Void)?
public init(navigationBarPresentationData: NavigationBarPresentationData, theme: TabBarControllerTheme) {
self.navigationBarPresentationData = navigationBarPresentationData
public init(theme: PresentationTheme) {
self.theme = theme
super.init(navigationBarPresentationData: nil)
@ -156,54 +117,27 @@ open class TabBarControllerImpl: ViewController, TabBarController {
self.pendingControllerDisposable.dispose()
}
public func updateTheme(navigationBarPresentationData: NavigationBarPresentationData, theme: TabBarControllerTheme) {
public func updateTheme(theme: PresentationTheme) {
if self.theme !== theme {
self.theme = theme
self.navigationBarPresentationData = navigationBarPresentationData
if self.isNodeLoaded {
self.tabBarControllerNode.updateTheme(theme, navigationBarPresentationData: navigationBarPresentationData)
self.tabBarControllerNode.updateTheme(theme)
}
}
}
private var debugTapCounter: (Double, Int) = (0.0, 0)
public func sourceNodesForController(at index: Int) -> [ASDisplayNode]? {
return self.tabBarControllerNode.tabBarNode.sourceNodesForController(at: index)
}
public func 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? {
if let index = self.controllers.firstIndex(of: controller) {
var index = 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) }
return self.tabBarControllerNode.frameForControllerTab(at: index)
} else {
return nil
}
}
public func isPointInsideContentArea(point: CGPoint) -> Bool {
if point.y < self.tabBarControllerNode.tabBarNode.frame.minY {
return true
}
return false
return self.tabBarControllerNode.isPointInsideContentArea(point: point)
}
public func updateIsTabBarEnabled(_ value: Bool, transition: ContainedViewLayoutTransition) {
@ -218,19 +152,8 @@ open class TabBarControllerImpl: ViewController, TabBarController {
}
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 {
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 {
controller.presentTabBarPreviewingController(sourceNodes: itemNodes)
return
@ -298,34 +221,14 @@ open class TabBarControllerImpl: ViewController, TabBarController {
guard let strongSelf = self else {
return
}
if index >= 0 && index < strongSelf.tabBarControllerNode.tabBarNode.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
}
}
}
if index >= 0 && index < strongSelf.tabBarControllerNode.tabBarItems.count {
strongSelf.controllers[index].tabBarItemContextAction(sourceNode: node, gesture: gesture)
}
}, swipeAction: { [weak self] index, direction in
guard let strongSelf = self else {
return
}
if index >= 0 && index < strongSelf.tabBarControllerNode.tabBarNode.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
}
}
}
if index >= 0 && index < strongSelf.tabBarControllerNode.tabBarItems.count {
strongSelf.controllers[index].tabBarItemSwipeAction(direction: direction)
}
}, toolbarActionSelected: { [weak self] action in
@ -339,9 +242,6 @@ open class TabBarControllerImpl: ViewController, TabBarController {
}
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) {
@ -354,15 +254,8 @@ open class TabBarControllerImpl: ViewController, TabBarController {
animated = false
}
var tabBarSelectedIndex = self.selectedIndex
if let (cameraItem, _) = self.cameraItemAndAction {
if let cameraItemIndex = self.tabBarControllerNode.tabBarNode.tabBarItems.firstIndex(where: { $0.item === cameraItem }) {
if tabBarSelectedIndex >= cameraItemIndex {
tabBarSelectedIndex += 1
}
}
}
self.tabBarControllerNode.tabBarNode.selectedIndex = tabBarSelectedIndex
let tabBarSelectedIndex = self.selectedIndex
self.tabBarControllerNode.updateSelectedIndex(index: tabBarSelectedIndex)
var transitionScale: CGFloat = 0.998
if let currentView = self.currentController?.view {
@ -428,26 +321,14 @@ open class TabBarControllerImpl: ViewController, TabBarController {
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 {
currentController.view.frame = CGRect(origin: CGPoint(), size: layout.size)
var updatedLayout = layout
var tabBarHeight: CGFloat
var options: ContainerViewLayoutInsetOptions = []
if updatedLayout.metrics.widthClass == .regular {
options.insert(.input)
}
let bottomInset: CGFloat = updatedLayout.insets(options: options).bottom
if !updatedLayout.safeInsets.left.isZero {
tabBarHeight = 34.0 + bottomInset
} else {
tabBarHeight = 49.0 + bottomInset
}
if !self.tabBarControllerNode.tabBarHidden {
updatedLayout.intrinsicInsets.bottom = tabBarHeight
updatedLayout.intrinsicInsets.bottom = bottomInset
}
currentController.containerLayoutUpdated(updatedLayout, transition: transition)
@ -525,12 +406,9 @@ open class TabBarControllerImpl: ViewController, TabBarController {
}
self.controllers = controllers
var 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)))
}
let tabBarItems = self.controllers.map({ TabBarNodeItem(item: $0.tabBarItem, contextActionType: $0.tabBarItemContextActionType) })
self.tabBarControllerNode.tabBarNode.tabBarItems = tabBarItems
self.tabBarControllerNode.updateTabBarItems(items: tabBarItems)
let signals = combineLatest(self.controllers.map({ $0.tabBarItem }).map { tabBarItem -> Signal<Bool, NoError> in
if let tabBarItem = tabBarItem, tabBarItem.image == nil {

View file

@ -6,6 +6,7 @@ import Display
import UIKitRuntimeUtils
import AnimatedStickerNode
import TelegramAnimatedStickerNode
import TelegramPresentationData
private extension CGRect {
var center: CGPoint {
@ -343,7 +344,7 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
private let contextAction: (Int, ContextExtractedContentContainingNode, ContextGesture) -> 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 horizontal: Bool = false
private var centered: Bool = false
@ -351,25 +352,19 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
private var badgeImage: UIImage
let backgroundNode: NavigationBackgroundNode
let separatorNode: ASDisplayNode
private var tabBarNodeContainers: [TabBarNodeContainer] = []
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.contextAction = contextAction
self.swipeAction = swipeAction
self.theme = theme
self.backgroundNode = NavigationBackgroundNode(color: theme.tabBarBackgroundColor)
self.backgroundNode = NavigationBackgroundNode(color: theme.rootController.tabBar.backgroundColor)
self.separatorNode = ASDisplayNode()
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)!
self.badgeImage = generateStretchableFilledCircleImage(diameter: 18.0, color: theme.rootController.tabBar.badgeBackgroundColor, strokeColor: theme.rootController.tabBar.badgeStrokeColor, strokeWidth: 1.0, backgroundColor: nil)!
super.init()
@ -382,7 +377,6 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
self.isExclusiveTouch = true
self.addSubnode(self.backgroundNode)
self.addSubnode(self.separatorNode)
}
override func didLoad() {
@ -417,17 +411,16 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
}
}
func updateTheme(_ theme: TabBarControllerTheme) {
func updateTheme(_ theme: PresentationTheme) {
if self.theme !== theme {
self.theme = theme
self.separatorNode.backgroundColor = theme.tabBarSeparatorColor
self.backgroundNode.updateColor(color: theme.tabBarBackgroundColor, transition: .immediate)
self.backgroundNode.updateColor(color: theme.rootController.tabBar.backgroundColor, 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 {
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? {
let container = self.tabBarNodeContainers[index]
return container.imageNode.frame
@ -463,7 +451,7 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
node.imageNode.removeFromSupernode()
}
self.centered = self.theme.tabBarTextColor == .clear
self.centered = self.theme.rootController.tabBar.textColor == .clear
var tabBarNodeContainers: [TabBarNodeContainer] = []
for i in 0 ..< self.tabBarItems.count {
@ -484,13 +472,13 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
self?.swipeAction(i, direction)
})
if item.item.ringSelection {
node.ringColor = self.theme.tabBarSelectedIconColor
node.ringColor = self.theme.rootController.tabBar.selectedIconColor
} else {
node.ringColor = nil
}
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)
if let _ = item.item.animationName {
@ -502,17 +490,17 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
if !node.isSelected {
node.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: item.item.animationName ?? ""), width: animationSize, height: animationSize, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
}
node.animationNode.setOverlayColor(self.theme.tabBarSelectedIconColor, replace: true, animated: false)
node.animationNode.setOverlayColor(self.theme.rootController.tabBar.selectedIconColor, replace: true, animated: false)
node.animationNode.updateLayout(size: CGSize(width: 51.0, height: 51.0))
} else {
(image, imageContentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
(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.visibility = false
}
let (contextTextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
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.contextMenu.extractedContentTintColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
node.textImageNode.image = textImage
node.imageNode.image = image
node.contextTextImageNode.image = contextTextImage
@ -522,10 +510,10 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
node.contentWidth = max(contentWidth, imageContentWidth)
node.isSelected = true
} 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 (image, imageContentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
let (contextTextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
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.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.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.contextMenu.extractedContentTintColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
node.animationNode.isHidden = true
node.animationNode.visibility = false
@ -555,10 +543,10 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
let node = self.tabBarNodeContainers[index].imageNode
let item = self.tabBarItems[index]
self.centered = self.theme.tabBarTextColor == .clear
self.centered = self.theme.rootController.tabBar.textColor == .clear
if item.item.ringSelection {
node.ringColor = self.theme.tabBarSelectedIconColor
node.ringColor = self.theme.rootController.tabBar.selectedIconColor
} else {
node.ringColor = nil
}
@ -566,7 +554,7 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
let previousImageSize = node.imageNode.image?.size ?? CGSize()
let previousTextImageSize = node.textImageNode.image?.size ?? CGSize()
if let selectedIndex = self.selectedIndex, selectedIndex == index {
let (textImage, contentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
let (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)
if let _ = item.item.animationName {
(image, imageContentWidth) = (UIImage(), 0.0)
@ -577,21 +565,21 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
if !node.isSelected {
node.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: item.item.animationName ?? ""), width: animationSize, height: animationSize, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
}
node.animationNode.setOverlayColor(self.theme.tabBarSelectedIconColor, replace: true, animated: false)
node.animationNode.setOverlayColor(self.theme.rootController.tabBar.selectedIconColor, replace: true, animated: false)
node.animationNode.updateLayout(size: CGSize(width: 51.0, height: 51.0))
} else {
if item.item.ringSelection {
(image, imageContentWidth) = (item.item.selectedImage ?? UIImage(), item.item.selectedImage?.size.width ?? 0.0)
} else {
(image, imageContentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
(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.visibility = false
}
let (contextTextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
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.contextMenu.extractedContentTintColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
node.textImageNode.image = textImage
node.accessibilityLabel = item.item.title
node.accessibilityTraits = [.button, .selected]
@ -610,16 +598,16 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
})
}
} 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)
if item.item.ringSelection {
(image, imageContentWidth) = (item.item.image ?? UIImage(), item.item.image?.size.width ?? 0.0)
} 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 (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedIconColor, horizontal: self.horizontal, imageMode: true, 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.contextMenu.extractedContentTintColor, horizontal: self.horizontal, imageMode: true, centered: self.centered)
node.animationNode.stop()
node.animationNode.isHidden = true
@ -672,8 +660,6 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size))
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
if self.horizontal != horizontal {
self.horizontal = horizontal
@ -759,7 +745,7 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
if container.badgeValue != container.appliedBadgeValue {
container.appliedBadgeValue = container.badgeValue
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
} else {
container.badgeContainerNode.isHidden = true
@ -797,7 +783,6 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate {
return
}
var closestNode: (Int, CGFloat)?
for i in 0 ..< self.tabBarNodeContainers.count {
let node = self.tabBarNodeContainers[i].imageNode
if !node.isUserInteractionEnabled {

View file

@ -12,7 +12,7 @@ import PresentationDataUtils
import UIKitRuntimeUtils
import ReplayKit
private let accentColor: UIColor = UIColor(rgb: 0x007aff)
private let accentColor: UIColor = UIColor(rgb: 0x0088ff)
protocol PreviewVideoNode: ASDisplayNode {
var ready: Signal<Bool, NoError> { get }

View file

@ -26,7 +26,7 @@ private let videoCornerRadius: CGFloat = 23.0
private let avatarSize: CGFloat = 50.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 destructiveColor: UIColor = UIColor(rgb: 0xff3b30)

View file

@ -135,7 +135,7 @@ private let tileSize = CGSize(width: 84.0, height: 84.0)
private let backgroundCornerRadius: CGFloat = 14.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 destructiveColor: UIColor = UIColor(rgb: 0xff3b30)

View file

@ -10,7 +10,7 @@ import SolidRoundedButtonNode
import PresentationDataUtils
import VoiceChatActionButton
private let accentColor: UIColor = UIColor(rgb: 0x007aff)
private let accentColor: UIColor = UIColor(rgb: 0x0088ff)
final class VoiceChatRecordingSetupController: ViewController {
private var controllerNode: VoiceChatRecordingSetupControllerNode {

View file

@ -561,7 +561,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
outgoing: PresentationThemePartedColors(
bubble: PresentationThemeBubbleColor(
withWallpaper: PresentationThemeBubbleColorComponents(
fill: [UIColor(rgb: 0x61BCF9), UIColor(rgb: 0x007AFF)],
fill: [UIColor(rgb: 0x61BCF9), UIColor(rgb: 0x0088ff)],
highlightedFill: UIColor(rgb: 0x61BCF9),
stroke: .clear,
shadow: nil,
@ -577,7 +577,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
reactionActiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1)
),
withoutWallpaper: PresentationThemeBubbleColorComponents(
fill: [UIColor(rgb: 0x61BCF9), UIColor(rgb: 0x007AFF)],
fill: [UIColor(rgb: 0x61BCF9), UIColor(rgb: 0x0088ff)],
highlightedFill: UIColor(rgb: 0x61BCF9),
stroke: .clear,
shadow: nil,

View file

@ -54,7 +54,7 @@ public func dateFillNeedsBlur(theme: PresentationTheme, wallpaper: TelegramWallp
public let defaultServiceBackgroundColor = UIColor(rgb: 0x000000, alpha: 0.2)
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 {
if (theme.referenceTheme != .day && theme.referenceTheme != .dayClassic) {
@ -436,7 +436,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio
separatorColor: UIColor(rgb: 0xb2b2b2),
iconColor: UIColor(rgb: 0x959595),
selectedIconColor: defaultDayAccentColor,
textColor: UIColor(rgb: 0x959595),
textColor: UIColor(rgb: 0x000000, alpha: 0.8),
selectedTextColor: defaultDayAccentColor,
badgeBackgroundColor: UIColor(rgb: 0xff3b30),
badgeStrokeColor: UIColor(rgb: 0xff3b30),

View file

@ -164,7 +164,7 @@ public struct PresentationResourcesSettings {
context.addPath(path.cgPath)
context.clip()
context.setFillColor(UIColor(rgb: 0x007aff).cgColor)
context.setFillColor(UIColor(rgb: 0x0088ff).cgColor)
context.fill(bounds)
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Bot"), color: UIColor(rgb: 0xffffff)), let cgImage = image.cgImage {

View file

@ -5,16 +5,21 @@ import RasterizedCompositionComponent
import ComponentFlow
public final class BadgeComponent: Component {
public enum CornerRadius: Equatable {
case automatic
case custom(CGFloat)
}
public let text: String
public let font: UIFont
public let cornerRadius: CGFloat
public let cornerRadius: CornerRadius
public let insets: UIEdgeInsets
public let outerInsets: UIEdgeInsets
public init(
text: String,
font: UIFont,
cornerRadius: CGFloat,
cornerRadius: CornerRadius,
insets: 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 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)
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
}
}

View file

@ -113,9 +113,9 @@ final class PlaceholderComponent: Component {
component: AnyComponent(
ButtonComponent(
background: ButtonComponent.Background(
color: UIColor(rgb: 0x007aff),
color: UIColor(rgb: 0x0088ff),
foreground: .white,
pressedColor: UIColor(rgb: 0x007aff, alpha: 0.55)
pressedColor: UIColor(rgb: 0x0088ff, alpha: 0.55)
),
content: AnyComponentWithIdentity(
id: buttonTitle,

View file

@ -718,7 +718,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
if self.visibility != oldValue {
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)
private var currentInputParams: Params?
private var currentApplyParams: ListViewItemApply?
private var contentLayoutInsets = UIEdgeInsets()
required public init(rotated: Bool) {
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)
@ -3364,6 +3367,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
params: params,
applyInfo: applyInfo,
layout: layout,
layoutInsets: layoutInsets,
item: item,
forwardSource: forwardSource,
forwardAuthorSignature: forwardAuthorSignature,
@ -3377,12 +3381,12 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
graphics: graphics,
presentationContext: item.controllerInteraction.presentationContext,
bubbleContentWidth: bubbleContentWidth,
backgroundFrame: backgroundFrame,
backgroundFrame: backgroundFrame.offsetBy(dx: 0.0, dy: layoutInsets.top),
deliveryFailedInset: deliveryFailedInset,
nameNodeSizeApply: nameNodeSizeApply,
viaWidth: viaWidth,
contentOrigin: contentOrigin,
nameNodeOriginY: nameNodeOriginY + detachedContentNodesHeight + additionalTopHeight,
contentOrigin: contentOrigin.offsetBy(dx: 0.0, dy: layoutInsets.top),
nameNodeOriginY: layoutInsets.top + nameNodeOriginY + detachedContentNodesHeight + additionalTopHeight,
hasTitleAvatar: hasTitleAvatar,
hasTitleTopicNavigation: hasTitleTopicNavigation,
authorNameColor: authorNameColor,
@ -3392,25 +3396,27 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
boostNodeSizeApply: boostNodeSizeApply,
contentUpperRightCorner: contentUpperRightCorner,
threadInfoSizeApply: threadInfoSizeApply,
threadInfoOriginY: threadInfoOriginY + detachedContentNodesHeight + additionalTopHeight,
threadInfoOriginY: layoutInsets.top + threadInfoOriginY + detachedContentNodesHeight + additionalTopHeight,
forwardInfoSizeApply: forwardInfoSizeApply,
forwardInfoOriginY: forwardInfoOriginY + detachedContentNodesHeight + additionalTopHeight,
forwardInfoOriginY: layoutInsets.top + forwardInfoOriginY + detachedContentNodesHeight + additionalTopHeight,
replyInfoSizeApply: replyInfoSizeApply,
replyInfoOriginY: replyInfoOriginY + detachedContentNodesHeight + additionalTopHeight,
replyInfoOriginY: layoutInsets.top + replyInfoOriginY + detachedContentNodesHeight + additionalTopHeight,
removedContentNodeIndices: removedContentNodeIndices,
updatedContentNodeOrder: updatedContentNodeOrder,
addedContentNodes: addedContentNodes,
contentNodeMessagesAndClasses: contentNodeMessagesAndClasses,
contentNodeFramesPropertiesAndApply: contentNodeFramesPropertiesAndApply,
contentContainerNodeFrames: contentContainerNodeFrames,
mosaicStatusOrigin: mosaicStatusOrigin,
contentContainerNodeFrames: contentContainerNodeFrames.map { containerGroupId, containerFrame, currentItemSelection, currentContainerGroupOverlap in
return (containerGroupId, containerFrame.offsetBy(dx: 0.0, dy: layoutInsets.top), currentItemSelection, currentContainerGroupOverlap)
},
mosaicStatusOrigin: mosaicStatusOrigin?.offsetBy(dx: 0.0, dy: layoutInsets.top),
mosaicStatusSizeAndApply: mosaicStatusSizeAndApply,
unlockButtonPosition: unlockButtonPosition,
unlockButtonPosition: unlockButtonPosition?.offsetBy(dx: 0.0, dy: layoutInsets.top),
unlockButtonSizeAndApply: unlockButtonSizeApply,
mediaInfoOrigin: mediaInfoOrigin,
mediaInfoOrigin: mediaInfoOrigin?.offsetBy(dx: 0.0, dy: layoutInsets.top),
mediaInfoSizeAndApply: mediaInfoSizeApply,
needsShareButton: needsShareButton,
shareButtonOffset: shareButtonOffset,
shareButtonOffset: shareButtonOffset?.offsetBy(dx: 0.0, dy: layoutInsets.top),
avatarOffset: avatarOffset,
hidesHeaders: hidesHeaders,
disablesComments: disablesComments,
@ -3428,6 +3434,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
params: ListViewItemLayoutParams,
applyInfo: ListViewItemApply,
layout: ListViewItemNodeLayout,
layoutInsets: UIEdgeInsets,
item: ChatMessageItem,
forwardSource: Peer?,
forwardAuthorSignature: String?,
@ -3488,6 +3495,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
strongSelf.currentInputParams = inputParams
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 {
strongSelf.wasPending = true
@ -3570,7 +3578,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
strongSelf.suggestedPostInfoNode = 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
} else if let suggestedPostInfoNode = strongSelf.suggestedPostInfoNode {
strongSelf.suggestedPostInfoNode = nil
@ -4999,7 +5007,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
strongSelf.updateSearchTextHighlightState()
strongSelf.updateVisibility()
strongSelf.updateVisibility(isScroll: false)
if let (_, f) = strongSelf.awaitingAppliedReaction {
strongSelf.awaitingAppliedReaction = nil
@ -6332,10 +6340,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
if let swipeToReplyNode = self.swipeToReplyNode {
if translation.x < 0.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 {
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 {
@ -6561,7 +6569,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
contentNode.unreadMessageRangeUpdated()
}
self.updateVisibility()
self.updateVisibility(isScroll: false)
}
public func animateQuizInvalidOptionSelected() {
@ -6761,10 +6769,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
override public func updateStickerSettings(forceStopAnimations: Bool) {
self.forceStopAnimations = forceStopAnimations
self.updateVisibility()
self.updateVisibility(isScroll: false)
}
private func updateVisibility() {
private func updateVisibility(isScroll: Bool) {
guard let item = self.item else {
return
}
@ -6841,6 +6849,15 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
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? {

View file

@ -118,7 +118,7 @@ final class PlayButtonNode: ASDisplayNode {
let backgroundFrame = buttonSize.centered(in: CGRect(origin: .zero, size: size))
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))

View file

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

View file

@ -334,13 +334,13 @@ public final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessag
}
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))
self.micButton.layoutItems()
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(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.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 {
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)

View file

@ -66,6 +66,7 @@ final class AccessoryItemIconButton: HighlightTrackingButton, GlassBackgroundVie
if let text {
if self.textView == nil {
let textView = ImmediateTextView()
textView.isUserInteractionEnabled = false
self.textView = textView
self.addSubview(textView)
}

View file

@ -222,6 +222,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
public let textInputNodeClippingContainer: ASDisplayNode
public let textInputSeparator: GlassBackgroundView.ContentColorView
public var textInputNode: ChatInputTextNode?
private var textInputNodeLayout: (frame: CGRect, insets: UIEdgeInsets)?
public var dustNode: InvisibleInkDustNode?
public var customEmojiContainerView: CustomEmojiContainerView?
@ -925,11 +926,10 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
textInputNode.textContainerInset = calculateTextFieldRealInsets(presentationInterfaceState: presentationInterfaceState, accessoryButtonsWidth: accessoryButtonsWidth)
}
if !self.textInputContainer.bounds.size.width.isZero {
let textInputFrame = self.textInputContainer.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: textInputNode.bounds.size)
if let textInputNodeLayout = self.textInputNodeLayout {
textInputNode.textContainerInset = textInputNodeLayout.insets
textInputNode.frame = textInputNodeLayout.frame
textInputNode.updateLayout(size: textInputNodeLayout.frame.size)
textInputNode.view.layoutIfNeeded()
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)
transition.updateFrameAsPositionAndBounds(node: self.menuButton, frame: menuButtonFrame)
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)))
var menuButtonTitleTransition = transition
if buttonTitleUpdated {
@ -2334,7 +2334,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
transition.updateFrame(node: self.textInputContainer, frame: textInputContainerBackgroundFrame)
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 = 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 {
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))
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.frame = actualTextFieldFrame
textInputNode.updateLayout(size: textFieldFrame.size)
self.updateInputField(textInputFrame: textFieldFrame, transition: ComponentTransition(transition))
if shouldUpdateLayout {
@ -2463,15 +2467,21 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
let textPlaceholderSize: CGSize
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 {
let currentPlaceholder = updatedPlaceholder ?? self.currentPlaceholder ?? ""
self.currentPlaceholder = currentPlaceholder
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: "#") {
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))
}
@ -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))
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(node: self.attachmentButtonDisabledNode, frame: self.attachmentButton.frame)

View file

@ -8,8 +8,8 @@ import ObjCRuntimeUtils
private let innerCircleDiameter: CGFloat = 110.0
private let outerCircleDiameter = innerCircleDiameter + 50.0
private let outerCircleMinScale = innerCircleDiameter / outerCircleDiameter
private let innerCircleImage = generateFilledCircleImage(diameter: innerCircleDiameter, color: UIColor(rgb: 0x007aff))
private let outerCircleImage = generateFilledCircleImage(diameter: outerCircleDiameter, color: UIColor(rgb: 0x007aff, alpha: 0.2))
private let innerCircleImage = generateFilledCircleImage(diameter: innerCircleDiameter, color: UIColor(rgb: 0x0088ff))
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 final class ChatTextInputAudioRecordingOverlayDisplayLinkTarget: NSObject {

View file

@ -634,7 +634,7 @@ private class WrapperBlurrredBackgroundView: UIView, TGModernConversationInputMi
let view = GlassBackgroundView()
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
super.init(frame: CGRect(origin: CGPoint(), size: size))
@ -652,13 +652,13 @@ private class WrapperBlurrredBackgroundView: UIView, TGModernConversationInputMi
} set {
super.frame = newValue
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) {
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
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))
}
}

View 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",
],
)

View file

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

View file

@ -1050,7 +1050,7 @@ private let tonImage: UIImage? = {
generateImage(CGSize(width: 32.0, height: 32.0), contextGenerator: { size, context in
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)
}
})?.withRenderingMode(.alwaysTemplate)

View file

@ -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 {
let cornerRadius: CGFloat
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.isDark = isDark
self.tintColor = tintColor
@ -329,12 +344,18 @@ public final class GlassBackgroundView: UIView {
private let nativeView: UIVisualEffectView?
private let foregroundView: UIImageView?
private let shadowView: UIImageView?
private let shadowMaskView: UIImageView?
public let maskContentView: UIView
private let contentContainer: ContentContainer
public var contentView: UIView {
return self.contentContainer
if let nativeView = self.nativeView {
return nativeView.contentView
} else {
return self.contentContainer
}
}
private var params: Params?
@ -350,10 +371,14 @@ public final class GlassBackgroundView: UIView {
nativeView.traitOverrides.userInterfaceStyle = .light
//self.foregroundView = UIImageView()
self.foregroundView = nil
self.shadowView = nil
self.shadowMaskView = nil
} else {
self.backgroundNode = NavigationBackgroundNode(color: .black, enableBlur: true, customBlurRadius: 5.0)
self.nativeView = nil
self.foregroundView = UIImageView()
self.shadowView = UIImageView()
self.shadowMaskView = UIImageView()
}
self.maskContentView = UIView()
@ -366,6 +391,12 @@ public final class GlassBackgroundView: UIView {
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 {
self.addSubview(nativeView)
}
@ -383,7 +414,7 @@ public final class GlassBackgroundView: UIView {
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 {
let previousFrame = nativeView.frame
@ -399,7 +430,7 @@ public final class GlassBackgroundView: UIView {
}
}
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)
transition.setFrame(view: backgroundNode.view, frame: CGRect(origin: CGPoint(), size: size))
}
@ -408,13 +439,38 @@ public final class GlassBackgroundView: UIView {
if 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 {
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 {
if let nativeView {
if #available(iOS 26.0, *) {
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
nativeView.effect = glassEffect
@ -424,9 +480,19 @@ public final class GlassBackgroundView: UIView {
}
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))
}
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))
}
}

View file

@ -888,7 +888,7 @@ final class MediaEditorScreenComponent: Component {
transition: transition,
component: AnyComponent(PlainButtonComponent(
content: AnyComponent(DoneButtonContentComponent(
backgroundColor: UIColor(rgb: 0x007aff),
backgroundColor: UIColor(rgb: 0x0088ff),
icon: doneButtonIcon,
title: doneButtonTitle)),
effectAlignment: .center,

View file

@ -238,7 +238,7 @@ public final class MediaScrubberComponent: Component {
self.coverDotWrapper.isUserInteractionEnabled = false
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.clipsToBounds = true

View file

@ -442,7 +442,7 @@ public final class HashtagListItemComponent: Component {
}
if themeUpdated {
let accentColor = UIColor(rgb: 0x007aff)
let accentColor = UIColor(rgb: 0x0088ff)
self.separatorLayer.backgroundColor = component.theme.list.itemPlainSeparatorColor.cgColor
self.iconBackgroundLayer.backgroundColor = accentColor.cgColor
self.iconLayer.layerTintColor = UIColor.white.cgColor

View file

@ -165,6 +165,7 @@ swift_library(
"//submodules/TelegramUI/Components/BottomButtonPanelComponent",
"//submodules/TelegramUI/Components/MarqueeComponent",
"//submodules/TelegramUI/Components/MediaManager/PeerMessagesMediaPlaylist",
"//submodules/TelegramUI/Components/EdgeEffect",
],
visibility = [
"//visibility:public",

View file

@ -113,6 +113,7 @@ import UrlHandling
import VerifyAlertController
import GiftViewScreen
import PeerMessagesMediaPlaylist
import EdgeEffect
public enum PeerInfoAvatarEditingMode {
case generic
@ -2927,6 +2928,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
fileprivate let cachedDataPromise = Promise<CachedPeerData?>()
let scrollNode: ASScrollNode
private let edgeEffectView: EdgeEffectView
let headerNode: PeerInfoHeaderNode
private var regularSections: [AnyHashable: PeerInfoScreenItemSectionContainerNode] = [:]
@ -3063,6 +3065,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
self.scrollNode.view.delaysContentTouches = false
self.scrollNode.canCancelAllTouchesInViews = true
self.edgeEffectView = EdgeEffectView()
var forumTopicThreadId: Int64?
if case let .replyThread(message) = chatLocation {
forumTopicThreadId = message.threadId
@ -3930,6 +3934,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
self.addSubnode(self.scrollNode)
self.scrollNode.addSubnode(self.paneContainerNode)
self.view.addSubview(self.edgeEffectView)
self.addSubnode(self.headerNode)
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))
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
var contentHeight: CGFloat = 0.0

View file

@ -13,7 +13,7 @@ import MediaResources
import WallpaperGalleryScreen
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 {
convenience init(baseTheme: TelegramBaseTheme, accentColor: UIColor, outgoingAccentColor: UIColor?, messageColors: [UInt32], animateMessageColors: Bool, wallpaper: TelegramWallpaper?) {

View file

@ -1521,7 +1521,7 @@ final class ShareWithPeersScreenComponent: Component {
)),
maximumNumberOfLines: 0,
lineSpacing: 0.1,
highlightColor: UIColor(rgb: 0x007aff, alpha: 0.2),
highlightColor: UIColor(rgb: 0x0088ff, alpha: 0.2),
highlightAction: { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] {
return NSAttributedString.Key(rawValue: "URL")

View file

@ -211,7 +211,7 @@ final class DataUsageScreenComponent: Component {
case .photos:
return UIColor(rgb: 0x5AC8FA)
case .videos:
return UIColor(rgb: 0x007AFF)
return UIColor(rgb: 0x0088ff)
case .files:
return UIColor(rgb: 0x34C759)
case .music:

View 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",
],
)

View file

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

View file

@ -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]
(context.context.sharedContext as? SharedAccountContextImpl)?.notificationController = context.notificationController
var authorizeNotifications = true

View file

@ -916,7 +916,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
}
self.dynamicBounceEnabled = !self.currentPresentationData.disableAnimations
self.experimentalSnapScrollToItem = true
self.experimentalSnapScrollToItem = false
//self.debugInfo = true

View file

@ -94,7 +94,7 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
self.buttonNode.view.addSubview(self.backgroundView)
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.backgroundView.contentView.addSubview(self.imageView)
@ -110,7 +110,7 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
if 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
switch self.type {
@ -124,7 +124,7 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
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] = []
if let value = Int(self.badge) {
@ -174,7 +174,7 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
}
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)

View file

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

View file

@ -35,7 +35,7 @@ private final class LegacyDataImportSplashImpl: WindowCoveringView, LegacyDataIm
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: {})
}
}

View file

@ -114,7 +114,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
let previousTheme = strongSelf.presentationData.theme
strongSelf.presentationData = presentationData
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
}
}
@ -188,7 +188,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
}
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
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 {

View file

@ -407,7 +407,7 @@ public enum PresentationThemeBaseColor: Int32, CaseIterable {
let value: UInt32
switch self {
case .blue:
value = 0x007aff
value = 0x0088ff
case .cyan:
value = 0x00c2ed
case .green:

View file

@ -123,6 +123,39 @@ public protocol WallpaperBackgroundNode: ASDisplayNode {
}
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 {
case whileAnimating
case always
@ -141,6 +174,12 @@ private final class EffectImageLayer: SimpleLayer, GradientBackgroundPatternOver
} else {
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
private var needsCompositionUpdate: Bool = false
fileprivate let cloneLayers = SparseBag<Weak<CloneLayer>>()
private func updateFilters() {
let useSoftlight: Bool
let useFilter: Bool
@ -208,6 +249,12 @@ private final class EffectImageLayer: SimpleLayer, GradientBackgroundPatternOver
self.compositingFilter = nil
}
for cloneLayer in self.cloneLayers {
if let value = cloneLayer.value {
value.compositingFilter = self.compositingFilter
}
}
self.updateContents()
self.updateOpacity()
}
@ -330,6 +377,13 @@ private final class EffectImageLayer: SimpleLayer, GradientBackgroundPatternOver
self.allowSettingContents = false
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.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
fileprivate let edgeEffectNodes = SparseBag<Weak<WallpaperEdgeEffectNodeImpl>>()
private var blurredBackgroundContents: UIImage?
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 let patternImageLayer: EffectImageLayer
fileprivate let patternImageLayer: EffectImageLayer
private let dimLayer: SimpleLayer
private var isGeneratingPatternImage: Bool = false
@ -786,8 +849,6 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
private var modelStickerNode: DefaultAnimatedStickerNodeImpl?
fileprivate let edgeEffectNodes = SparseBag<Weak<WallpaperEdgeEffectNodeImpl>>()
private var isSettingUpWallpaper: Bool = false
private struct CachedValidPatternImage {
@ -1057,6 +1118,12 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
if self.isLooping {
scheduleLoopingEvent = true
}
for edgeEffectNode in self.edgeEffectNodes {
if let edgeEffectNode = edgeEffectNode.value {
edgeEffectNode.updateGradientNode()
}
}
}
self.gradientBackgroundNode?.updateColors(colors: mappedColors)
@ -1712,31 +1779,12 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
}
public func makeEdgeEffectNode() -> WallpaperEdgeEffectNode? {
if let gradientBackgroundNode = self.gradientBackgroundNode {
let node = WallpaperEdgeEffectNodeImpl(parentNode: self)
node.cloneNode = GradientBackgroundNode.CloneNode(parentNode: gradientBackgroundNode, isDimmed: false)
return node
} else {
return nil
}
let node = WallpaperEdgeEffectNodeImpl(parentNode: self)
return node
}
}
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 {
let rect: CGRect
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 containerMaskingNode: ASDisplayNode
private let overlayNode: ASDisplayNode
@ -1763,6 +1814,14 @@ private final class WallpaperEdgeEffectNodeImpl: ASDisplayNode, WallpaperEdgeEff
init(parentNode: WallpaperBackgroundNodeImpl) {
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.anchorPoint = CGPoint()
self.containerNode.clipsToBounds = true
@ -1776,6 +1835,11 @@ private final class WallpaperEdgeEffectNodeImpl: ASDisplayNode, WallpaperEdgeEff
super.init()
if let gradientNode = self.gradientNode {
self.containerNode.addSubnode(gradientNode)
}
//self.layer.addSublayer(self.patternImageLayer)
self.addSubnode(self.containerMaskingNode)
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) {
if 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))
if let cloneNode = self.cloneNode {
transition.updateFrame(node: cloneNode, frame: CGRect(origin: CGPoint(), size: containerSize))
if let gradientNode = self.gradientNode {
transition.updateFrame(node: gradientNode, frame: CGRect(origin: CGPoint(), size: containerSize))
}
transition.updateFrame(layer: self.patternImageLayer, frame: CGRect(origin: CGPoint(), size: containerSize))
}
}