chore: migrate to new version + fixed several critical bugs

- Migrated project to latest Telegram iOS base (v12.3.2+)
- Fixed circular dependency between GhostModeManager and MiscSettingsManager
- Fixed multiple Bazel build configuration errors (select() default conditions)
- Fixed duplicate type definitions in PeerInfoScreen
- Fixed swiftmodule directory resolution in build scripts
- Added Ghostgram Settings tab in main Settings menu with all 5 features
- Cleared sensitive credentials from config.json (template-only now)
- Excluded bazel-cache from version control
This commit is contained in:
ichmagmaus 812
2026-02-23 23:04:32 +01:00
parent 703e291bcb
commit db53826061
1017 changed files with 62337 additions and 40559 deletions
@@ -8,6 +8,9 @@ import TelegramCore
import ComponentDisplayAdapters
import BundleIconComponent
import GlassBackgroundComponent
import EdgeEffect
import LiquidLens
import TabSelectionRecognizer
private final class BottomPanelIconComponent: Component {
let title: String
@@ -57,22 +60,15 @@ private final class BottomPanelIconComponent: Component {
super.init(frame: frame)
self.addSubview(self.contentView)
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
self.component?.action()
}
}
func update(component: BottomPanelIconComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
if self.component?.title != component.title {
let text = NSAttributedString(string: component.title, font: Font.medium(15.0), textColor: .white)
let text = NSAttributedString(string: component.title, font: Font.medium(14.0), textColor: .white)
let textBounds = text.boundingRect(with: CGSize(width: 120.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil)
self.contentView.image = generateImage(CGSize(width: ceil(textBounds.width), height: ceil(textBounds.height)), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
@@ -89,19 +85,9 @@ private final class BottomPanelIconComponent: Component {
let textSize = self.contentView.image?.size ?? CGSize()
let size = CGSize(width: textSize.width + textInset * 2.0, height: 28.0)
let color = component.theme.chat.inputPanel.inputControlColor
self.contentView.tintColor = component.theme.chat.inputPanel.panelControlColor
if self.contentView.tintColor != color {
if !transition.animation.isImmediate {
UIView.animate(withDuration: 0.15, delay: 0.0, options: [], animations: {
self.contentView.tintColor = color
}, completion: nil)
} else {
self.contentView.tintColor = color
}
}
transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: (size.height - textSize.height) / 2.0 - 1.0), size: textSize))
transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: (size.height - textSize.height) / 2.0), size: textSize))
return size
}
@@ -163,17 +149,27 @@ final class EntityKeyboardBottomPanelComponent: Component {
private var leftAccessoryButton: AccessoryButtonView?
private var rightAccessoryButton: AccessoryButtonView?
private var iconViews: [AnyHashable: ComponentHostView<Empty>] = [:]
private var highlightedIconBackgroundView: UIView
private var highlightedTintIconBackgroundView: UIView
private let edgeEffectView: EdgeEffectView
private let backgroundContainer: GlassBackgroundContainerView
private let liquidLensView: LiquidLensView
private var itemViews: [AnyHashable: ComponentHostView<Empty>] = [:]
private var selectedItemViews: [AnyHashable: ComponentHostView<Empty>] = [:]
private var tabSelectionRecognizer: TabSelectionRecognizer?
private var selectionGestureState: (startX: CGFloat, currentX: CGFloat, itemId: AnyHashable)?
let tintContentMask: UIView
private var component: EntityKeyboardBottomPanelComponent?
private var state: EmptyComponentState?
private var environment: PagerComponentPanelEnvironment<EntityKeyboardTopContainerPanelEnvironment>?
override init(frame: CGRect) {
self.tintContentMask = UIView()
self.edgeEffectView = EdgeEffectView()
self.backgroundView = BlurredBackgroundView(color: .clear, enableBlur: true, customBlurRadius: 10.0)
self.separatorView = UIView()
@@ -182,51 +178,102 @@ final class EntityKeyboardBottomPanelComponent: Component {
self.tintSeparatorView.isUserInteractionEnabled = false
self.tintSeparatorView.backgroundColor = UIColor(white: 0.0, alpha: 0.7)
self.tintContentMask.addSubview(self.tintSeparatorView)
self.highlightedIconBackgroundView = UIView()
self.highlightedIconBackgroundView.isUserInteractionEnabled = false
self.highlightedIconBackgroundView.layer.cornerRadius = 10.0
self.highlightedIconBackgroundView.clipsToBounds = true
self.highlightedTintIconBackgroundView = UIView()
self.highlightedTintIconBackgroundView.isUserInteractionEnabled = false
self.highlightedTintIconBackgroundView.layer.cornerRadius = 10.0
self.highlightedTintIconBackgroundView.clipsToBounds = true
self.highlightedTintIconBackgroundView.backgroundColor = UIColor(white: 0.0, alpha: 0.1)
self.tintContentMask.addSubview(self.highlightedTintIconBackgroundView)
self.backgroundContainer = GlassBackgroundContainerView()
self.liquidLensView = LiquidLensView(kind: .externalContainer)
super.init(frame: frame)
self.addSubview(self.backgroundView)
self.addSubview(self.highlightedIconBackgroundView)
self.addSubview(self.separatorView)
self.addSubview(self.edgeEffectView)
self.addSubview(self.backgroundContainer)
self.backgroundContainer.contentView.addSubview(self.liquidLensView)
let tabSelectionRecognizer = TabSelectionRecognizer(target: self, action: #selector(self.onTabSelectionGesture(_:)))
self.tabSelectionRecognizer = tabSelectionRecognizer
self.liquidLensView.addGestureRecognizer(tabSelectionRecognizer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func item(at point: CGPoint) -> AnyHashable? {
var closestItem: (AnyHashable, CGFloat)?
for (id, itemView) in self.itemViews {
if itemView.frame.contains(point) {
return id
} else {
let distance = abs(point.x - itemView.center.x)
if let closestItemValue = closestItem {
if closestItemValue.1 > distance {
closestItem = (id, distance)
}
} else {
closestItem = (id, distance)
}
}
}
return closestItem?.0
}
@objc private func onTabSelectionGesture(_ recognizer: TabSelectionRecognizer) {
guard let environment = self.environment else {
return
}
let location = recognizer.location(in: self.liquidLensView.contentView)
switch recognizer.state {
case .began:
if let itemId = self.item(at: location), let itemView = self.itemViews[itemId] {
let startX = itemView.frame.minX - 4.0
self.selectionGestureState = (startX, startX, itemId)
self.state?.updated(transition: .spring(duration: 0.4), isLocal: true)
}
case .changed:
if var selectionGestureState = self.selectionGestureState {
selectionGestureState.currentX = selectionGestureState.startX + recognizer.translation(in: self).x
if let itemId = self.item(at: location) {
selectionGestureState.itemId = itemId
}
self.selectionGestureState = selectionGestureState
self.state?.updated(transition: .immediate, isLocal: true)
}
case .ended, .cancelled:
if let selectionGestureState = self.selectionGestureState {
self.selectionGestureState = nil
if case .ended = recognizer.state {
guard let item = environment.contentIcons.first(where: { $0.id == selectionGestureState.itemId }) else {
return
}
environment.navigateToContentId(item.id)
}
self.state?.updated(transition: .spring(duration: 0.4), isLocal: true)
}
default:
break
}
}
func update(component: EntityKeyboardBottomPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
if self.component?.theme !== component.theme {
self.separatorView.backgroundColor = component.theme.list.itemPlainSeparatorColor.withMultipliedAlpha(0.5)
self.backgroundView.updateColor(color: component.theme.chat.inputPanel.panelBackgroundColor.withMultipliedAlpha(1.0), transition: .immediate)
self.highlightedIconBackgroundView.backgroundColor = component.theme.chat.inputMediaPanel.panelHighlightedIconBackgroundColor
}
let intrinsicHeight: CGFloat = 34.0
let height = intrinsicHeight + component.containerInsets.bottom
let height = intrinsicHeight + component.containerInsets.bottom + 20.0
let accessoryButtonOffset: CGFloat
if component.containerInsets.bottom > 0.0 {
accessoryButtonOffset = 2.0
accessoryButtonOffset = 0.0
} else {
accessoryButtonOffset = -2.0
}
self.component = component
self.state = state
let panelEnvironment = environment[PagerComponentPanelEnvironment<EntityKeyboardTopContainerPanelEnvironment>.self].value
self.environment = panelEnvironment
let activeContentId = panelEnvironment.activeContentId
var leftAccessoryButtonComponent: AnyComponentWithIdentity<Empty>?
@@ -257,7 +304,7 @@ final class EntityKeyboardBottomPanelComponent: Component {
environment: {},
containerSize: CGSize(width: .greatestFiniteMagnitude, height: intrinsicHeight)
)
let leftAccessoryButtonFrame = CGRect(origin: CGPoint(x: component.containerInsets.left + 2.0, y: accessoryButtonOffset), size: leftAccessoryButtonSize)
let leftAccessoryButtonFrame = CGRect(origin: CGPoint(x: component.containerInsets.left + 18.0, y: accessoryButtonOffset), size: leftAccessoryButtonSize)
leftAccessoryButtonTransition.setFrame(view: leftAccessoryButton.view, frame: leftAccessoryButtonFrame)
if let leftAccessoryButtonView = leftAccessoryButton.view.componentView as? PagerTopPanelView {
if leftAccessoryButtonView.tintContentMask.superview == nil {
@@ -328,7 +375,7 @@ final class EntityKeyboardBottomPanelComponent: Component {
containerSize: CGSize(width: .greatestFiniteMagnitude, height: intrinsicHeight)
)
let rightAccessoryButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - component.containerInsets.right - 2.0 - rightAccessoryButtonSize.width, y: accessoryButtonOffset), size: rightAccessoryButtonSize)
let rightAccessoryButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - component.containerInsets.right - 18.0 - rightAccessoryButtonSize.width, y: accessoryButtonOffset), size: rightAccessoryButtonSize)
rightAccessoryButtonTransition.setFrame(view: rightAccessoryButton.view, frame: rightAccessoryButtonFrame)
if let rightAccessoryButtonView = rightAccessoryButton.view.componentView as? PagerTopPanelView {
if rightAccessoryButtonView.tintContentMask.superview == nil {
@@ -374,23 +421,32 @@ final class EntityKeyboardBottomPanelComponent: Component {
var iconInfos: [AnyHashable: (size: CGSize, transition: ComponentTransition)] = [:]
var iconTotalSize = CGSize()
let iconSpacing: CGFloat = 4.0
let iconSpacing: CGFloat = 0.0
let navigateToContentId = panelEnvironment.navigateToContentId
var lensSelection: (x: CGFloat, width: CGFloat) = (0.0, 0.0)
if panelEnvironment.contentIcons.count > 1 {
for icon in panelEnvironment.contentIcons {
validIconIds.append(icon.id)
var iconTransition = transition
let iconView: ComponentHostView<Empty>
if let current = self.iconViews[icon.id] {
let selectedIconView: ComponentHostView<Empty>
if let current = self.itemViews[icon.id], let currentSelected = self.selectedItemViews[icon.id] {
iconView = current
selectedIconView = currentSelected
} else {
iconTransition = .immediate
iconView = ComponentHostView<Empty>()
self.iconViews[icon.id] = iconView
self.addSubview(iconView)
iconView.isUserInteractionEnabled = false
selectedIconView = ComponentHostView<Empty>()
selectedIconView.isUserInteractionEnabled = false
self.itemViews[icon.id] = iconView
self.selectedItemViews[icon.id] = selectedIconView
self.liquidLensView.contentView.addSubview(iconView)
self.liquidLensView.selectedContentView.addSubview(selectedIconView)
}
let iconSize = iconView.update(
@@ -407,65 +463,70 @@ final class EntityKeyboardBottomPanelComponent: Component {
containerSize: CGSize(width: 28.0, height: 28.0)
)
let _ = selectedIconView.update(
transition: iconTransition,
component: AnyComponent(BottomPanelIconComponent(
title: icon.title,
isHighlighted: icon.id == activeContentId,
theme: component.theme,
action: {
navigateToContentId(icon.id)
}
)),
environment: {},
containerSize: CGSize(width: 28.0, height: 28.0)
)
iconInfos[icon.id] = (size: iconSize, transition: iconTransition)
if !iconTotalSize.width.isZero {
iconTotalSize.width += iconSpacing
iconTotalSize.width += iconSpacing - 8.0
}
iconTotalSize.width += iconSize.width
iconTotalSize.height = max(iconTotalSize.height, iconSize.height)
}
}
var nextIconOrigin = CGPoint(x: floor((availableSize.width - iconTotalSize.width) / 2.0), y: floor((intrinsicHeight - iconTotalSize.height) / 2.0))
if component.containerInsets.bottom > 0.0 {
nextIconOrigin.y += 3.0
}
let tabsSize = CGSize(width: iconTotalSize.width, height: 40.0)
var nextIconOrigin = CGPoint(x: floor((tabsSize.width - iconTotalSize.width) / 2.0), y: floor((tabsSize.height - iconTotalSize.height) / 2.0))
transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: .zero, size: availableSize))
self.backgroundContainer.update(size: availableSize, isDark: component.theme.overallDarkAppearance, transition: transition)
if panelEnvironment.contentIcons.count > 1 {
for icon in panelEnvironment.contentIcons {
guard let iconInfo = iconInfos[icon.id], let iconView = self.iconViews[icon.id] else {
guard let iconInfo = iconInfos[icon.id], let iconView = self.itemViews[icon.id], let selectedIconView = self.selectedItemViews[icon.id] else {
continue
}
let iconFrame = CGRect(origin: nextIconOrigin, size: iconInfo.size)
iconInfo.transition.setFrame(view: iconView, frame: iconFrame, completion: nil)
if let iconView = iconView.componentView as? BottomPanelIconComponent.View {
if iconView.tintMaskContainer.superview == nil {
self.tintContentMask.addSubview(iconView.tintMaskContainer)
}
iconInfo.transition.setFrame(view: iconView.tintMaskContainer, frame: iconFrame, completion: nil)
}
iconInfo.transition.setFrame(view: selectedIconView, frame: iconFrame, completion: nil)
if let activeContentId = activeContentId, activeContentId == icon.id {
self.highlightedIconBackgroundView.isHidden = false
self.highlightedTintIconBackgroundView.isHidden = false
transition.setFrame(view: self.highlightedIconBackgroundView, frame: iconFrame)
transition.setFrame(view: self.highlightedTintIconBackgroundView, frame: iconFrame)
let cornerRadius: CGFloat = min(iconFrame.width, iconFrame.height) / 2.0
transition.setCornerRadius(layer: self.highlightedIconBackgroundView.layer, cornerRadius: cornerRadius)
transition.setCornerRadius(layer: self.highlightedTintIconBackgroundView.layer, cornerRadius: cornerRadius)
lensSelection = (iconFrame.origin.x, iconFrame.width)
}
nextIconOrigin.x += iconInfo.size.width + iconSpacing
nextIconOrigin.x += iconInfo.size.width + iconSpacing - 8.0
}
}
if activeContentId == nil {
self.highlightedIconBackgroundView.isHidden = true
if let selectionGestureState = self.selectionGestureState {
lensSelection = (selectionGestureState.currentX, lensSelection.width)
}
transition.setFrame(view: self.liquidLensView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - tabsSize.width) / 2.0), y: 0.0), size: tabsSize))
self.liquidLensView.update(size: CGSize(width: tabsSize.width, height: tabsSize.height), selectionOrigin: CGPoint(x: max(0.0, min(tabsSize.width - lensSelection.width, lensSelection.x)), y: 0.0), selectionSize: CGSize(width: lensSelection.width, height: tabsSize.height), inset: 3.0, isDark: component.theme.overallDarkAppearance, isLifted: self.selectionGestureState != nil, isCollapsed: activeContentId == nil, transition: transition)
var removedIconViewIds: [AnyHashable] = []
for (id, iconView) in self.iconViews {
for (id, iconView) in self.itemViews {
if !validIconIds.contains(id) {
removedIconViewIds.append(id)
iconView.removeFromSuperview()
}
}
for id in removedIconViewIds {
self.iconViews.removeValue(forKey: id)
self.itemViews.removeValue(forKey: id)
}
transition.setFrame(view: self.separatorView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: UIScreenPixel)))
@@ -473,8 +534,11 @@ final class EntityKeyboardBottomPanelComponent: Component {
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: height)))
//self.backgroundView.update(size: CGSize(width: availableSize.width, height: height), transition: transition.containedViewLayoutTransition)
self.component = component
let edgeEffectHeight: CGFloat = 80.0
let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: height - edgeEffectHeight), size: CGSize(width: availableSize.width, height: edgeEffectHeight))
transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
self.edgeEffectView.update(content: component.theme.chat.inputMediaPanel.backgroundColor.withMultipliedAlpha(0.8), rect: edgeEffectFrame, edge: .bottom, edgeSize: min(edgeEffectHeight, 50.0), transition: transition)
return CGSize(width: availableSize.width, height: height)
}