GLEGram 12.5 — Initial public release

Based on Swiftgram 12.5 (Telegram iOS 12.5).
All GLEGram features ported and organized in GLEGram/ folder.

Features: Ghost Mode, Saved Deleted Messages, Content Protection Bypass,
Font Replacement, Fake Profile, Chat Export, Plugin System, and more.

See CHANGELOG_12.5.md for full details.
This commit is contained in:
Leeksov
2026-04-06 09:48:12 +03:00
commit 4647310322
39685 changed files with 11052678 additions and 0 deletions
@@ -0,0 +1,35 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "BirthdayPickerScreen",
module_name = "BirthdayPickerScreen",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/Postbox",
"//submodules/TelegramCore",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/ComponentFlow",
"//submodules/Components/ViewControllerComponent",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/Components/MultilineTextComponent",
"//submodules/Components/BundleIconComponent",
"//submodules/TelegramPresentationData",
"//submodules/AccountContext",
"//submodules/AppBundle",
"//submodules/Components/SheetComponent",
"//submodules/PresentationDataUtils",
"//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/TelegramUI/Components/PlainButtonComponent",
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
],
visibility = [
"//visibility:public",
],
)
@@ -0,0 +1,271 @@
import Foundation
import UIKit
import Display
import SwiftSignalKit
import TelegramPresentationData
import TelegramCore
import AccountContext
import ComponentFlow
public final class BirthdayPickerComponent: Component {
public final class TransitionHint {
public let animate: Bool
public init(animate: Bool) {
self.animate = animate
}
}
public struct Theme: Equatable {
let backgroundColor: UIColor
let textColor: UIColor
let selectionColor: UIColor
public init(presentationTheme: PresentationTheme) {
self.backgroundColor = presentationTheme.list.itemBlocksBackgroundColor
self.textColor = presentationTheme.list.itemPrimaryTextColor
self.selectionColor = presentationTheme.list.itemHighlightedBackgroundColor
}
}
public let theme: Theme
public let strings: PresentationStrings
public let value: TelegramBirthday
public let valueUpdated: (TelegramBirthday) -> Void
public init(
theme: Theme,
strings: PresentationStrings,
value: TelegramBirthday,
valueUpdated: @escaping (TelegramBirthday) -> Void
) {
self.theme = theme
self.strings = strings
self.value = value
self.valueUpdated = valueUpdated
}
public static func ==(lhs: BirthdayPickerComponent, rhs: BirthdayPickerComponent) -> Bool {
if lhs.theme != rhs.theme {
return false
}
if lhs.value != rhs.value {
return false
}
return true
}
public final class View: UIView, UIPickerViewDelegate, UIPickerViewDataSource {
private var component: BirthdayPickerComponent?
private weak var componentState: EmptyComponentState?
private let pickerView = UIPickerView()
enum PickerComponent: Int {
case day = 0
case month = 1
case year = 2
}
private let calendar = Calendar(identifier: .gregorian)
private var value = TelegramBirthday(day: 1, month: 1, year: nil)
private var minYear: Int32 = 1900
private let maxYear: Int32
override init(frame: CGRect) {
self.maxYear = Int32(self.calendar.component(.year, from: Date()))
super.init(frame: frame)
self.pickerView.delegate = self
self.pickerView.dataSource = self
self.addSubview(self.pickerView)
}
required init(coder: NSCoder) {
preconditionFailure()
}
func update(component: BirthdayPickerComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
let isFirstTime = self.component == nil
self.component = component
self.componentState = state
if let year = component.value.year, year < self.minYear {
self.minYear = year
}
self.pickerView.frame = CGRect(origin: .zero, size: availableSize)
if isFirstTime || self.value != component.value {
self.value = component.value
self.pickerView.reloadAllComponents()
if let year = component.value.year {
self.pickerView.selectRow(Int(year - self.minYear), inComponent: PickerComponent.year.rawValue, animated: false)
} else {
var animate = false
if let hint = transition.userData(TransitionHint.self), hint.animate {
animate = true
}
self.pickerView.selectRow(Int(self.maxYear - self.minYear + 1), inComponent: PickerComponent.year.rawValue, animated: animate)
}
self.pickerView.selectRow(Int(component.value.month) - 1, inComponent: PickerComponent.month.rawValue, animated: false)
self.pickerView.selectRow(Int(component.value.day) - 1, inComponent: PickerComponent.day.rawValue, animated: false)
}
return availableSize
}
public func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 3
}
public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
switch component {
case PickerComponent.day.rawValue:
let year = self.value.year ?? 2024
let month = self.value.month
let range = Calendar.current.range(of: .day, in: .month, for: Calendar.current.date(from: DateComponents(year: Int(year), month: Int(month)))!)!
var maxDay = range.upperBound
if let year = self.value.year, year == self.maxYear {
let dateComponents = self.calendar.dateComponents(Set([.month, .day]), from: Date())
if dateComponents.month == Int(month), let currentDay = dateComponents.day {
maxDay = currentDay + 1
}
}
return maxDay - range.lowerBound
case PickerComponent.month.rawValue:
var maxMonth = 12
if let year = self.value.year, year == self.maxYear {
maxMonth = self.calendar.component(.month, from: Date())
}
return maxMonth
case PickerComponent.year.rawValue:
return Int(self.maxYear - self.minYear + 2)
default:
return 0
}
}
public func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {
var string = ""
switch component {
case PickerComponent.day.rawValue:
string = "\(row + 1)"
case PickerComponent.month.rawValue:
guard let strings = self.component?.strings else {
break
}
switch row {
case 0:
string = strings.Month_GenJanuary
case 1:
string = strings.Month_GenFebruary
case 2:
string = strings.Month_GenMarch
case 3:
string = strings.Month_GenApril
case 4:
string = strings.Month_GenMay
case 5:
string = strings.Month_GenJune
case 6:
string = strings.Month_GenJuly
case 7:
string = strings.Month_GenAugust
case 8:
string = strings.Month_GenSeptember
case 9:
string = strings.Month_GenOctober
case 10:
string = strings.Month_GenNovember
case 11:
string = strings.Month_GenDecember
default:
break
}
case PickerComponent.year.rawValue:
if row == self.maxYear - self.minYear + 1 {
string = ""
} else {
string = "\(self.minYear + Int32(row))"
}
default:
break
}
let textColor = self.component?.theme.textColor ?? .black
return NSAttributedString(string: string, attributes: [NSAttributedString.Key.foregroundColor: textColor])
}
public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
switch component {
case PickerComponent.day.rawValue:
self.value = self.value.withUpdated(day: Int32(row) + 1)
case PickerComponent.month.rawValue:
self.value = self.value.withUpdated(month: Int32(row) + 1)
case PickerComponent.year.rawValue:
if row == self.maxYear - self.minYear + 1 {
self.value = self.value.withUpdated(year: nil)
} else {
self.value = self.value.withUpdated(year: self.minYear + Int32(row))
}
default:
break
}
if [PickerComponent.month.rawValue, PickerComponent.year.rawValue].contains(component) {
self.pickerView.reloadComponent(PickerComponent.month.rawValue)
Queue.mainQueue().justDispatch {
self.value = self.value.withUpdated(month: Int32(pickerView.selectedRow(inComponent: 1) + 1))
self.pickerView.reloadComponent(PickerComponent.day.rawValue)
Queue.mainQueue().justDispatch {
self.value = self.value.withUpdated(day: Int32(pickerView.selectedRow(inComponent: 0) + 1))
self.component?.valueUpdated(self.value)
}
}
} else {
self.component?.valueUpdated(self.value)
}
}
public func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat {
switch component {
case PickerComponent.day.rawValue:
return 50.0
case PickerComponent.month.rawValue:
return 145.0
case PickerComponent.year.rawValue:
return 75.0
default:
return 0
}
}
public func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
return 40.0
}
}
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 extension TelegramBirthday {
func withUpdated(day: Int32) -> TelegramBirthday {
return TelegramBirthday(day: day, month: self.month, year: self.year)
}
func withUpdated(month: Int32) -> TelegramBirthday {
return TelegramBirthday(day: self.day, month: month, year: self.year)
}
func withUpdated(year: Int32?) -> TelegramBirthday {
return TelegramBirthday(day: self.day, month: self.month, year: year)
}
}
@@ -0,0 +1,317 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import SwiftSignalKit
import MultilineTextComponent
import TelegramPresentationData
import AppBundle
import BundleIconComponent
import PlainButtonComponent
import Markdown
import TelegramCore
import AccountContext
public final class BirthdayPickerContentComponent: Component {
public let context: AccountContext
public let mode: BirthdayPickerScreen.Mode
public let theme: PresentationTheme
public let strings: PresentationStrings
public let settings: Signal<AccountPrivacySettings?, NoError>
public let value: TelegramBirthday
public let canHideYear: Bool
public let updateValue: (TelegramBirthday) -> Void
public let dismiss: () -> Void
public let openSettings: () -> Void
public let requestHideYear: (ComponentTransition) -> Void
public init(
context: AccountContext,
mode: BirthdayPickerScreen.Mode,
theme: PresentationTheme,
strings: PresentationStrings,
settings: Signal<AccountPrivacySettings?, NoError>,
value: TelegramBirthday,
canHideYear: Bool,
updateValue: @escaping (TelegramBirthday) -> Void,
dismiss: @escaping () -> Void,
openSettings: @escaping () -> Void,
requestHideYear: @escaping (ComponentTransition) -> Void
) {
self.context = context
self.mode = mode
self.theme = theme
self.strings = strings
self.settings = settings
self.value = value
self.canHideYear = canHideYear
self.updateValue = updateValue
self.dismiss = dismiss
self.openSettings = openSettings
self.requestHideYear = requestHideYear
}
public static func ==(lhs: BirthdayPickerContentComponent, rhs: BirthdayPickerContentComponent) -> Bool {
if lhs.mode != rhs.mode {
return false
}
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings !== rhs.strings {
return false
}
if lhs.value != rhs.value {
return false
}
if lhs.canHideYear != rhs.canHideYear {
return false
}
return true
}
public final class View: UIView {
private let title = ComponentView<Empty>()
private let picker = ComponentView<Empty>()
private let mainText = ComponentView<Empty>()
private let hideYear = ComponentView<Empty>()
private var chevronImage: UIImage?
private var component: BirthdayPickerContentComponent?
private var state: EmptyComponentState?
private var disposable: Disposable?
private var settings: AccountPrivacySettings?
private var peer: EnginePeer?
private var isUpdating = false
public override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder: NSCoder) {
preconditionFailure()
}
deinit {
self.disposable?.dispose()
}
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let result = super.hitTest(point, with: event) {
return result
} else {
return nil
}
}
func update(component: BirthdayPickerContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.isUpdating = true
defer {
self.isUpdating = false
}
self.component = component
self.state = state
if self.disposable == nil {
let peer: Signal<EnginePeer?, NoError>
if case let .suggest(peerId) = component.mode {
peer = component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
} else {
peer = .single(nil)
}
self.disposable = combineLatest(queue: Queue.mainQueue(),
component.settings,
peer
).start(next: { [weak self, weak state] settings, peer in
if let self {
self.settings = settings
self.peer = peer
if !self.isUpdating {
state?.updated()
}
}
})
}
let sideInset: CGFloat = 16.0
var contentHeight: CGFloat = 8.0
let titleString = NSMutableAttributedString()
let titleRawString: String
if let peer = self.peer {
var peerName = peer.compactDisplayTitle
if peerName.count > 20 {
peerName = "\(peerName.prefix(20))"
}
titleRawString = component.strings.SuggestBirthdate_Suggest_Title(peerName).string
} else {
titleRawString = component.strings.Birthday_Title
}
titleString.append(NSAttributedString(string: titleRawString, font: Font.semibold(17.0), textColor: component.theme.list.itemPrimaryTextColor))
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(titleString),
maximumNumberOfLines: 1
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0)
)
if let titleView = self.title.view {
if titleView.superview == nil {
self.addSubview(titleView)
}
transition.setFrame(view: titleView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: contentHeight), size: titleSize))
}
contentHeight += titleSize.height
contentHeight += 16.0
let pickerSize = self.picker.update(
transition: transition,
component: AnyComponent(BirthdayPickerComponent(
theme: BirthdayPickerComponent.Theme(presentationTheme: component.theme),
strings: component.strings,
value: component.value,
valueUpdated: { [weak self] value in
guard let self, let component = self.component else {
return
}
component.updateValue(value)
if component.canHideYear && value.year == nil {
component.requestHideYear(.spring(duration: 0.4))
}
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 226.0)
)
if let pickerView = self.picker.view {
if pickerView.superview == nil {
self.addSubview(pickerView)
}
transition.setFrame(view: pickerView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - pickerSize.width) * 0.5), y: contentHeight), size: pickerSize))
}
contentHeight += pickerSize.height
contentHeight += 8.0
switch component.mode {
case .generic:
var isContacts = true
if let settings = self.settings {
if case .enableContacts = settings.birthday {
} else {
isContacts = false
}
}
let mainText = NSMutableAttributedString()
mainText.append(parseMarkdownIntoAttributedString(isContacts ? component.strings.Birthday_HelpContacts : component.strings.Birthday_Help, attributes: MarkdownAttributes(
body: MarkdownAttributeSet(
font: Font.regular(13.0),
textColor: component.theme.list.itemSecondaryTextColor
),
bold: MarkdownAttributeSet(
font: Font.semibold(13.0),
textColor: component.theme.list.itemSecondaryTextColor
),
link: MarkdownAttributeSet(
font: Font.regular(13.0),
textColor: component.theme.list.itemAccentColor,
additionalAttributes: [:]
),
linkAttribute: { attributes in
return ("URL", "")
}
)))
if self.chevronImage == nil {
self.chevronImage = UIImage(bundleImageName: "Contact List/SubtitleArrow")
}
if let range = mainText.string.range(of: ">"), let chevronImage = self.chevronImage {
mainText.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: mainText.string))
}
let mainTextSize = self.mainText.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(mainText),
horizontalAlignment: .center,
maximumNumberOfLines: 0,
lineSpacing: 0.2,
highlightColor: component.theme.list.itemAccentColor.withMultipliedAlpha(0.1),
highlightInset: mainText.string.contains(">") ? UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: -8.0) : .zero,
highlightAction: { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] {
return NSAttributedString.Key(rawValue: "URL")
} else {
return nil
}
},
tapAction: { [weak self] _, _ in
guard let self, let component = self.component else {
return
}
component.openSettings()
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0)
)
if let mainTextView = self.mainText.view {
if mainTextView.superview == nil {
self.addSubview(mainTextView)
}
transition.setFrame(view: mainTextView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - mainTextSize.width) * 0.5), y: contentHeight), size: mainTextSize))
}
contentHeight += mainTextSize.height
contentHeight += 12.0
case .acceptSuggestion:
if component.canHideYear {
contentHeight += 21.0
let hideYearSize = self.hideYear.update(
transition: .immediate,
component: AnyComponent(PlainButtonComponent(content: AnyComponent(Text(text: component.strings.SuggestBirthdate_Accept_HideYear, font: Font.regular(17.0), color: component.theme.list.itemAccentColor)), action: { [weak self] in
guard let self, let component = self.component else {
return
}
component.updateValue(TelegramBirthday(day: component.value.day, month: component.value.month, year: nil))
component.requestHideYear(.spring(duration: 0.4).withUserData(BirthdayPickerComponent.TransitionHint(animate: true)))
}, animateScale: false)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0)
)
if let hideYearView = self.hideYear.view {
if hideYearView.superview == nil {
self.addSubview(hideYearView)
}
transition.setFrame(view: hideYearView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - hideYearSize.width) * 0.5), y: contentHeight), size: hideYearSize))
}
contentHeight += hideYearSize.height
contentHeight += 20.0
} else if let hideYearView = self.hideYear.view {
transition.setAlpha(view: hideYearView, alpha: 0.0, completion: { _ in
hideYearView.removeFromSuperview()
})
}
case .suggest:
break
}
return CGSize(width: availableSize.width, height: contentHeight)
}
}
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)
}
}
@@ -0,0 +1,404 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import SwiftSignalKit
import ViewControllerComponent
import TelegramCore
import AccountContext
import SheetComponent
import ButtonComponent
import GlassBarButtonComponent
import BundleIconComponent
private final class BirthdayPickerSheetContentComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let mode: BirthdayPickerScreen.Mode
let settings: Signal<AccountPrivacySettings?, NoError>
let canHideYear: Bool
let openSettings: () -> Void
let dismiss: () -> Void
let action: (TelegramBirthday) -> Void
let requestHideYear: (ComponentTransition) -> Void
init(
context: AccountContext,
mode: BirthdayPickerScreen.Mode,
settings: Signal<AccountPrivacySettings?, NoError>,
canHideYear: Bool,
openSettings: @escaping () -> Void,
dismiss: @escaping () -> Void,
action: @escaping (TelegramBirthday) -> Void,
requestHideYear: @escaping (ComponentTransition) -> Void
) {
self.context = context
self.mode = mode
self.settings = settings
self.canHideYear = canHideYear
self.openSettings = openSettings
self.dismiss = dismiss
self.action = action
self.requestHideYear = requestHideYear
}
static func ==(lhs: BirthdayPickerSheetContentComponent, rhs: BirthdayPickerSheetContentComponent) -> Bool {
if lhs.canHideYear != rhs.canHideYear {
return false
}
return true
}
final class View: UIView {
private let cancel = ComponentView<Empty>()
private let content = ComponentView<Empty>()
private let button = ComponentView<Empty>()
private var birthday = TelegramBirthday(day: 1, month: 1, year: nil)
private var component: BirthdayPickerSheetContentComponent?
private var state: EmptyComponentState?
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(component: BirthdayPickerSheetContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
if self.component == nil {
if case let .acceptSuggestion(birthday) = component.mode {
self.birthday = birthday
}
}
self.component = component
self.state = state
let environment = environment[EnvironmentType.self].value
let sideInset: CGFloat = 16.0
var contentHeight: CGFloat = 0.0
contentHeight += 18.0
let contentSize = self.content.update(
transition: transition,
component: AnyComponent(BirthdayPickerContentComponent(
context: component.context,
mode: component.mode,
theme: environment.theme,
strings: environment.strings,
settings: component.settings,
value: self.birthday,
canHideYear: component.canHideYear,
updateValue: { [weak self] value in
if let self {
self.birthday = value
}
},
dismiss: component.dismiss,
openSettings: component.openSettings,
requestHideYear: component.requestHideYear
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
)
if let contentView = self.content.view {
if contentView.superview == nil {
self.addSubview(contentView)
}
transition.setFrame(view: contentView, frame: CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: contentSize))
}
contentHeight += contentSize.height
contentHeight += 8.0
let cancelSize = self.cancel.update(
transition: transition,
component: AnyComponent(
GlassBarButtonComponent(
size: CGSize(width: 44.0, height: 44.0),
backgroundColor: nil,
isDark: environment.theme.overallDarkAppearance,
state: .glass,
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
BundleIconComponent(
name: "Navigation/Close",
tintColor: environment.theme.chat.inputPanel.panelControlColor
)
)),
action: { [weak self] _ in
if let self, let component = self.component {
component.dismiss()
}
}
)
),
environment: {},
containerSize: CGSize(width: 44.0, height: 44.0)
)
let cancelFrame = CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: cancelSize)
if let cancelView = self.cancel.view {
if cancelView.superview == nil {
self.addSubview(cancelView)
}
transition.setFrame(view: cancelView, frame: cancelFrame)
}
let buttonTitle: String
switch component.mode {
case .generic:
buttonTitle = environment.strings.Birthday_Save
case .suggest:
buttonTitle = environment.strings.SuggestBirthdate_Suggest_Action
case .acceptSuggestion:
buttonTitle = environment.strings.SuggestBirthdate_Accept_Action
}
let bottomInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0)
let buttonSize = self.button.update(
transition: transition,
component: AnyComponent(ButtonComponent(
background: ButtonComponent.Background(
style: .glass,
color: environment.theme.list.itemCheckColors.fillColor,
foreground: environment.theme.list.itemCheckColors.foregroundColor,
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8)
),
content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(
Text(text: buttonTitle, font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.foregroundColor)
)),
isEnabled: true,
displaysProgress: false,
action: { [weak self] in
guard let self, let component = self.component else {
return
}
component.action(self.birthday)
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width - bottomInsets.left - bottomInsets.right, height: 52.0)
)
let buttonFrame = CGRect(origin: CGPoint(x: bottomInsets.left, y: contentHeight), size: buttonSize)
if let buttonView = self.button.view {
if buttonView.superview == nil {
self.addSubview(buttonView)
}
transition.setFrame(view: buttonView, frame: buttonFrame)
}
contentHeight += buttonSize.height
contentHeight += bottomInsets.bottom
return CGSize(width: availableSize.width, height: contentHeight)
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
private final class BirthdayPickerScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let mode: BirthdayPickerScreen.Mode
let settings: Signal<AccountPrivacySettings?, NoError>
let openSettings: (() -> Void)?
let completion: ((TelegramBirthday) -> Void)?
init(
context: AccountContext,
mode: BirthdayPickerScreen.Mode,
settings: Signal<AccountPrivacySettings?, NoError>,
openSettings: (() -> Void)?,
completion: ((TelegramBirthday) -> Void)?
) {
self.context = context
self.mode = mode
self.settings = settings
self.openSettings = openSettings
self.completion = completion
}
static func ==(lhs: BirthdayPickerScreenComponent, rhs: BirthdayPickerScreenComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
return true
}
final class View: UIView {
private let sheet = ComponentView<(ViewControllerComponentContainer.Environment, SheetComponentEnvironment)>()
private let sheetAnimateOut = ActionSlot<Action<Void>>()
private var canHideYear = false
private var component: BirthdayPickerScreenComponent?
private var environment: EnvironmentType?
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private var didAppear = false
func update(component: BirthdayPickerScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
if self.component == nil {
if case let .acceptSuggestion(birthday) = component.mode, let _ = birthday.year {
self.canHideYear = true
}
}
self.component = component
let environment = environment[ViewControllerComponentContainer.Environment.self].value
self.environment = environment
if environment.isVisible {
self.didAppear = true
}
let sheetEnvironment = SheetComponentEnvironment(
metrics: environment.metrics,
deviceMetrics: environment.deviceMetrics,
isDisplaying: self.didAppear || environment.isVisible,
isCentered: environment.metrics.widthClass == .regular,
hasInputHeight: !environment.inputHeight.isZero,
regularMetricsSize: CGSize(width: 430.0, height: 900.0),
dismiss: { [weak self] _ in
guard let self, let environment = self.environment else {
return
}
self.sheetAnimateOut.invoke(Action { _ in
if let controller = environment.controller() {
controller.dismiss(completion: nil)
}
})
}
)
let _ = self.sheet.update(
transition: transition,
component: AnyComponent(SheetComponent(
content: AnyComponent(BirthdayPickerSheetContentComponent(
context: component.context,
mode: component.mode,
settings: component.settings,
canHideYear: self.canHideYear,
openSettings: { [weak self] in
guard let self, let component = self.component else {
return
}
component.openSettings?()
},
dismiss: { [weak self] in
guard let self else {
return
}
self.sheetAnimateOut.invoke(Action { _ in
if let controller = environment.controller() {
controller.dismiss(completion: nil)
}
})
},
action: { [weak self] value in
guard let self else {
return
}
self.sheetAnimateOut.invoke(Action { [weak self] _ in
if let controller = environment.controller() {
controller.dismiss(completion: nil)
}
guard let self else {
return
}
self.component?.completion?(value)
})
},
requestHideYear: { [weak self] transition in
guard let self, let controller = self.environment?.controller() as? BirthdayPickerScreen else {
return
}
self.canHideYear = false
controller.requestLayout(forceUpdate: true, transition: transition)
}
)),
style: .glass,
backgroundColor: .color(environment.theme.list.modalPlainBackgroundColor),
followContentSizeChanges: true,
isScrollEnabled: false,
animateOut: self.sheetAnimateOut
)),
environment: {
environment
sheetEnvironment
},
containerSize: availableSize
)
if let sheetView = self.sheet.view {
if sheetView.superview == nil {
self.addSubview(sheetView)
}
transition.setFrame(view: sheetView, frame: CGRect(origin: CGPoint(), size: availableSize))
}
return availableSize
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
public class BirthdayPickerScreen: ViewControllerComponentContainer {
public enum Mode: Equatable {
case generic
case suggest(EnginePeer.Id)
case acceptSuggestion(TelegramBirthday)
}
public init(context: AccountContext, mode: Mode = .generic, settings: Signal<AccountPrivacySettings?, NoError>, openSettings: (() -> Void)?, completion: ((TelegramBirthday) -> Void)? = nil) {
super.init(context: context, component: BirthdayPickerScreenComponent(
context: context,
mode: mode,
settings: settings,
openSettings: openSettings,
completion: completion
), navigationBarAppearance: .none)
self.statusBar.statusBarStyle = .Ignore
self.navigationPresentation = .flatModal
self.blocksBackgroundWhenInOverlay = true
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
}
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.view.disablesInteractiveModalDismiss = true
}
}