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 62323 additions and 40545 deletions
@@ -391,7 +391,7 @@ private final class OldChannelsSearchItemNode: ItemListControllerSearchNode {
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: OldChannelsSearchContainerNode(context: self.context, peers: self.peers, selectedPeerIds: self.selectedPeerIds, togglePeer: self.togglePeer), cancel: { [weak self] in
self?.cancel()
})
}, fieldStyle: placeholderNode.fieldStyle)
self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate)
self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in
@@ -18,11 +18,12 @@ swift_library(
"//submodules/PresentationDataUtils",
"//submodules/AccountContext",
"//submodules/TextFormat",
"//submodules/AlertUI",
"//submodules/PasswordSetupUI",
"//submodules/Markdown",
"//submodules/ActivityIndicator",
"//submodules/TelegramUI/Components/PeerManagement/OldChannelsController",
"//submodules/TelegramUI/Components/AlertComponent",
"//submodules/TelegramUI/Components/AlertComponent/AlertInputFieldComponent",
],
visibility = [
"//visibility:public",
@@ -5,460 +5,92 @@ import Display
import SwiftSignalKit
import TelegramCore
import TelegramPresentationData
import ActivityIndicator
import TextFormat
import AccountContext
import AlertUI
import PresentationDataUtils
import PasswordSetupUI
import Markdown
import OldChannelsController
import ComponentFlow
import AlertComponent
import AlertInputFieldComponent
private final class ChannelOwnershipTransferPasswordFieldNode: ASDisplayNode, UITextFieldDelegate {
private var theme: PresentationTheme
private let backgroundNode: ASImageNode
private let textInputNode: TextFieldNode
private let placeholderNode: ASTextNode
private var clearOnce: Bool = false
private let inputActivityNode: ActivityIndicator
private var isChecking = false
var complete: (() -> Void)?
var textChanged: ((String) -> Void)?
private let backgroundInsets = UIEdgeInsets(top: 8.0, left: 22.0, bottom: 15.0, right: 22.0)
private let inputInsets = UIEdgeInsets(top: 5.0, left: 11.0, bottom: 5.0, right: 11.0)
var password: String {
get {
return self.textInputNode.textField.text ?? ""
}
set {
self.textInputNode.textField.text = newValue
self.placeholderNode.isHidden = !newValue.isEmpty
}
}
var placeholder: String = "" {
didSet {
self.placeholderNode.attributedText = NSAttributedString(string: self.placeholder, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
}
}
init(theme: PresentationTheme, placeholder: String) {
self.theme = theme
self.backgroundNode = ASImageNode()
self.backgroundNode.isLayerBacked = true
self.backgroundNode.displaysAsynchronously = false
self.backgroundNode.displayWithoutProcessing = true
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 16.0, color: theme.actionSheet.inputHollowBackgroundColor, strokeColor: theme.actionSheet.inputBorderColor, strokeWidth: UIScreenPixel)
self.textInputNode = TextFieldNode()
private func commitChannelOwnershipTransferController(
context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
peer: EnginePeer,
member: TelegramUser,
present: @escaping (ViewController, Any?) -> Void,
push: @escaping (ViewController) -> Void,
completion: @escaping (EnginePeer.Id?) -> Void
) -> ViewController {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let strings = presentationData.strings
self.placeholderNode = ASTextNode()
self.placeholderNode.isUserInteractionEnabled = false
self.placeholderNode.displaysAsynchronously = false
self.placeholderNode.attributedText = NSAttributedString(string: placeholder, font: Font.regular(14.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
self.inputActivityNode = ActivityIndicator(type: .custom(theme.list.itemAccentColor, 18.0, 1.5, false))
super.init()
self.addSubnode(self.backgroundNode)
self.addSubnode(self.textInputNode)
self.addSubnode(self.placeholderNode)
self.addSubnode(self.inputActivityNode)
self.inputActivityNode.isHidden = true
}
override func didLoad() {
super.didLoad()
self.textInputNode.textField.typingAttributes = [NSAttributedString.Key.font: Font.regular(14.0), NSAttributedString.Key.foregroundColor: self.theme.actionSheet.inputTextColor]
self.textInputNode.textField.font = Font.regular(14.0)
self.textInputNode.textField.textColor = self.theme.list.itemPrimaryTextColor
self.textInputNode.textField.isSecureTextEntry = true
self.textInputNode.textField.returnKeyType = .done
self.textInputNode.textField.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance
self.textInputNode.clipsToBounds = true
self.textInputNode.textField.delegate = self
self.textInputNode.textField.addTarget(self, action: #selector(self.textFieldTextChanged(_:)), for: .editingChanged)
self.textInputNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0)
self.textInputNode.textField.tintColor = self.theme.list.itemAccentColor
}
func updateTheme(_ theme: PresentationTheme) {
self.theme = theme
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 16.0, color: theme.actionSheet.inputHollowBackgroundColor, strokeColor: theme.actionSheet.inputBorderColor, strokeWidth: UIScreenPixel)
self.textInputNode.textField.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance
self.textInputNode.textField.textColor = theme.list.itemPrimaryTextColor
self.textInputNode.textField.typingAttributes = [NSAttributedString.Key.font: Font.regular(14.0), NSAttributedString.Key.foregroundColor: theme.actionSheet.inputTextColor]
self.textInputNode.textField.tintColor = theme.list.itemAccentColor
self.placeholderNode.attributedText = NSAttributedString(string: self.placeholderNode.attributedText?.string ?? "", font: Font.regular(14.0), textColor: theme.actionSheet.inputPlaceholderColor)
}
func updateIsChecking(_ isChecking: Bool) {
self.isChecking = isChecking
self.inputActivityNode.isHidden = !isChecking
}
func updateIsInvalid() {
self.clearOnce = true
}
func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
let backgroundInsets = self.backgroundInsets
let inputInsets = self.inputInsets
let textFieldHeight: CGFloat = 30.0
let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom
let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top), size: CGSize(width: width - backgroundInsets.left - backgroundInsets.right, height: panelHeight - backgroundInsets.top - backgroundInsets.bottom))
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
let placeholderSize = self.placeholderNode.measure(backgroundFrame.size)
transition.updateFrame(node: self.placeholderNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY + floor((backgroundFrame.size.height - placeholderSize.height) / 2.0)), size: placeholderSize))
transition.updateFrame(node: self.textInputNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.size.width - inputInsets.left - inputInsets.right, height: backgroundFrame.size.height)))
let activitySize = CGSize(width: 18.0, height: 18.0)
transition.updateFrame(node: self.inputActivityNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX - activitySize.width - 6.0, y: backgroundFrame.minY + floor((backgroundFrame.height - activitySize.height) / 2.0)), size: activitySize))
return panelHeight
}
func activateInput() {
self.textInputNode.becomeFirstResponder()
}
func deactivateInput() {
self.textInputNode.resignFirstResponder()
}
@objc func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) {
self.textChanged?(editableTextNode.textView.text)
self.placeholderNode.isHidden = !(editableTextNode.textView.text ?? "").isEmpty
}
@objc func textFieldTextChanged(_ textField: UITextField) {
let text = textField.text ?? ""
self.textChanged?(text)
self.placeholderNode.isHidden = !text.isEmpty
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if self.isChecking {
return false
}
if string == "\n" {
self.complete?()
return false
}
if self.clearOnce {
self.clearOnce = false
if range.length > string.count {
textField.text = ""
return false
}
}
return true
}
}
let inputState = AlertInputFieldComponent.ExternalState()
public final class ChannelOwnershipTransferAlertContentNode: AlertContentNode {
private let strings: PresentationStrings
private let title: String
private let text: String
private let titleNode: ASTextNode
private let textNode: ASTextNode
fileprivate let inputFieldNode: ChannelOwnershipTransferPasswordFieldNode
private let actionNodesSeparator: ASDisplayNode
private let actionNodes: [TextAlertContentActionNode]
private let actionVerticalSeparators: [ASDisplayNode]
private let disposable = MetaDisposable()
private var validLayout: CGSize?
private let hapticFeedback = HapticFeedback()
public var complete: (() -> Void)? {
didSet {
self.inputFieldNode.complete = self.complete
}
let doneIsEnabled: Signal<Bool, NoError> = inputState.valueSignal
|> map { value in
return !value.isEmpty
}
public var theme: PresentationTheme {
didSet {
self.inputFieldNode.updateTheme(self.theme)
}
}
let doneInProgressPromise = ValuePromise<Bool>(false)
public override var dismissOnOutsideTap: Bool {
return self.isUserInteractionEnabled
}
public init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, title: String, text: String, actions: [TextAlertAction]) {
self.strings = strings
self.theme = ptheme
self.title = title
self.text = text
self.titleNode = ASTextNode()
self.titleNode.maximumNumberOfLines = 2
self.textNode = ASTextNode()
self.textNode.maximumNumberOfLines = 4
self.inputFieldNode = ChannelOwnershipTransferPasswordFieldNode(theme: ptheme, placeholder: strings.Channel_OwnershipTransfer_PasswordPlaceholder)
self.actionNodesSeparator = ASDisplayNode()
self.actionNodesSeparator.isLayerBacked = true
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
return TextAlertContentActionNode(theme: theme, action: action)
}
var actionVerticalSeparators: [ASDisplayNode] = []
if actions.count > 1 {
for _ in 0 ..< actions.count - 1 {
let separatorNode = ASDisplayNode()
separatorNode.isLayerBacked = true
actionVerticalSeparators.append(separatorNode)
}
}
self.actionVerticalSeparators = actionVerticalSeparators
super.init()
self.addSubnode(self.titleNode)
self.addSubnode(self.textNode)
self.addSubnode(self.inputFieldNode)
self.addSubnode(self.actionNodesSeparator)
for actionNode in self.actionNodes {
self.addSubnode(actionNode)
}
self.actionNodes.last?.actionEnabled = false
for separatorNode in self.actionVerticalSeparators {
self.addSubnode(separatorNode)
}
self.inputFieldNode.textChanged = { [weak self] text in
if let strongSelf = self, let lastNode = strongSelf.actionNodes.last {
lastNode.actionEnabled = !text.isEmpty
}
}
self.updateTheme(theme)
}
deinit {
self.disposable.dispose()
}
public func dismissInput() {
self.inputFieldNode.deactivateInput()
}
public var password: String {
return self.inputFieldNode.password
}
public func updateIsChecking(_ checking: Bool) {
self.inputFieldNode.updateIsChecking(checking)
}
public override func updateTheme(_ theme: AlertControllerTheme) {
self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
self.textNode.attributedText = NSAttributedString(string: self.text, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center)
self.actionNodesSeparator.backgroundColor = theme.separatorColor
for actionNode in self.actionNodes {
actionNode.updateTheme(theme)
}
for separatorNode in self.actionVerticalSeparators {
separatorNode.backgroundColor = theme.separatorColor
}
if let size = self.validLayout {
_ = self.updateLayout(size: size, transition: .immediate)
}
}
public override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
var size = size
size.width = min(size.width, 270.0)
let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude)
let hadValidLayout = self.validLayout != nil
self.validLayout = size
var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
let titleSize = self.titleNode.measure(measureSize)
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize))
origin.y += titleSize.height + 4.0
let textSize = self.textNode.measure(measureSize)
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize))
origin.y += textSize.height + 6.0
let actionButtonHeight: CGFloat = 44.0
var minActionsWidth: CGFloat = 0.0
let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
let actionTitleInsets: CGFloat = 8.0
var effectiveActionLayout = TextAlertContentActionLayout.horizontal
for actionNode in self.actionNodes {
let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight))
if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 {
effectiveActionLayout = .vertical
}
switch effectiveActionLayout {
case .horizontal:
minActionsWidth += actionTitleSize.width + actionTitleInsets
case .vertical:
minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets)
}
}
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0)
var contentWidth = max(titleSize.width, minActionsWidth)
contentWidth = max(contentWidth, 234.0)
var actionsHeight: CGFloat = 0.0
switch effectiveActionLayout {
case .horizontal:
actionsHeight = actionButtonHeight
case .vertical:
actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
}
let resultWidth = contentWidth + insets.left + insets.right
let inputFieldWidth = resultWidth
let inputFieldHeight = self.inputFieldNode.updateLayout(width: inputFieldWidth, transition: transition)
let inputHeight = inputFieldHeight
transition.updateFrame(node: self.inputFieldNode, frame: CGRect(x: 0.0, y: origin.y, width: resultWidth, height: inputFieldHeight))
transition.updateAlpha(node: self.inputFieldNode, alpha: inputHeight > 0.0 ? 1.0 : 0.0)
let resultSize = CGSize(width: resultWidth, height: titleSize.height + textSize.height + actionsHeight + inputHeight + insets.top + insets.bottom)
transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
var actionOffset: CGFloat = 0.0
let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count))
var separatorIndex = -1
var nodeIndex = 0
for actionNode in self.actionNodes {
if separatorIndex >= 0 {
let separatorNode = self.actionVerticalSeparators[separatorIndex]
switch effectiveActionLayout {
case .horizontal:
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel)))
case .vertical:
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
var content: [AnyComponentWithIdentity<AlertComponentEnvironment>] = []
content.append(AnyComponentWithIdentity(
id: "title",
component: AnyComponent(
AlertTitleComponent(title: strings.Channel_OwnershipTransfer_EnterPassword)
)
))
content.append(AnyComponentWithIdentity(
id: "text",
component: AnyComponent(
AlertTextComponent(content: .plain(strings.Channel_OwnershipTransfer_EnterPasswordText))
)
))
var applyImpl: (() -> Void)?
content.append(AnyComponentWithIdentity(
id: "input",
component: AnyComponent(
AlertInputFieldComponent(
context: context,
placeholder: strings.Channel_OwnershipTransfer_PasswordPlaceholder,
isSecureTextEntry: true,
isInitiallyFocused: true,
externalState: inputState,
returnKeyAction: {
applyImpl?()
}
}
separatorIndex += 1
let currentActionWidth: CGFloat
switch effectiveActionLayout {
case .horizontal:
if nodeIndex == self.actionNodes.count - 1 {
currentActionWidth = resultSize.width - actionOffset
} else {
currentActionWidth = actionWidth
}
case .vertical:
currentActionWidth = resultSize.width
}
let actionNodeFrame: CGRect
switch effectiveActionLayout {
case .horizontal:
actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
actionOffset += currentActionWidth
case .vertical:
actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
actionOffset += actionButtonHeight
}
transition.updateFrame(node: actionNode, frame: actionNodeFrame)
nodeIndex += 1
}
if !hadValidLayout {
self.inputFieldNode.activateInput()
}
return resultSize
}
)
)
))
public func animateError() {
self.inputFieldNode.updateIsInvalid()
self.inputFieldNode.layer.addShakeAnimation()
self.hapticFeedback.error()
var effectiveUpdatedPresentationData: (PresentationData, Signal<PresentationData, NoError>)
if let updatedPresentationData {
effectiveUpdatedPresentationData = updatedPresentationData
} else {
effectiveUpdatedPresentationData = (presentationData, context.sharedContext.presentationData)
}
}
private func commitChannelOwnershipTransferController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peer: EnginePeer, member: TelegramUser, present: @escaping (ViewController, Any?) -> Void, completion: @escaping (EnginePeer.Id?) -> Void) -> ViewController {
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
var dismissImpl: (() -> Void)?
var proceedImpl: (() -> Void)?
var pushControllerImpl: ((ViewController) -> Void)?
let disposable = MetaDisposable()
let contentNode = ChannelOwnershipTransferAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, title: presentationData.strings.Channel_OwnershipTransfer_EnterPassword, text: presentationData.strings.Channel_OwnershipTransfer_EnterPasswordText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
dismissImpl?()
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.OwnershipTransfer_Transfer, action: {
proceedImpl?()
})])
contentNode.complete = {
proceedImpl?()
}
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode)
let presentationDataDisposable = (updatedPresentationData?.signal ?? context.sharedContext.presentationData).start(next: { [weak controller, weak contentNode] presentationData in
controller?.theme = AlertControllerTheme(presentationData: presentationData)
contentNode?.inputFieldNode.updateTheme(presentationData.theme)
})
controller.dismissed = { _ in
presentationDataDisposable.dispose()
disposable.dispose()
}
dismissImpl = { [weak controller, weak contentNode] in
contentNode?.dismissInput()
controller?.dismissAnimated()
}
proceedImpl = { [weak contentNode] in
guard let contentNode = contentNode else {
return
}
contentNode.updateIsChecking(true)
let alertController = AlertScreen(
configuration: AlertScreen.Configuration(allowInputInset: true),
content: content,
actions: [
.init(title: strings.Common_Cancel),
.init(title: strings.OwnershipTransfer_Transfer, type: .default, action: {
applyImpl?()
}, autoDismiss: false, isEnabled: doneIsEnabled, progress: doneInProgressPromise.get())
],
updatedPresentationData: effectiveUpdatedPresentationData
)
applyImpl = {
doneInProgressPromise.set(true)
let signal: Signal<EnginePeer.Id?, ChannelOwnershipTransferError>
if case let .channel(peer) = peer {
signal = context.peerChannelMemberCategoriesContextsManager.transferOwnership(engine: context.engine, peerId: peer.id, memberId: member.id, password: contentNode.password) |> mapToSignal { _ in
signal = context.peerChannelMemberCategoriesContextsManager.transferOwnership(engine: context.engine, peerId: peer.id, memberId: member.id, password: inputState.value) |> mapToSignal { _ in
return .complete()
}
|> then(.single(nil))
@@ -478,7 +110,7 @@ private func commitChannelOwnershipTransferController(context: AccountContext, u
guard let upgradedPeerId = upgradedPeerId else {
return .fail(.generic)
}
return context.peerChannelMemberCategoriesContextsManager.transferOwnership(engine: context.engine, peerId: upgradedPeerId, memberId: member.id, password: contentNode.password) |> mapToSignal { _ in
return context.peerChannelMemberCategoriesContextsManager.transferOwnership(engine: context.engine, peerId: upgradedPeerId, memberId: member.id, password: inputState.value) |> mapToSignal { _ in
return .complete()
}
|> then(.single(upgradedPeerId))
@@ -487,54 +119,61 @@ private func commitChannelOwnershipTransferController(context: AccountContext, u
signal = .never()
}
disposable.set((signal |> deliverOnMainQueue).start(next: { upgradedPeerId in
let _ = (signal
|> deliverOnMainQueue).start(next: { upgradedPeerId in
dismissImpl?()
completion(upgradedPeerId)
}, error: { [weak contentNode] error in
}, error: { error in
var isGroup = true
if case let .channel(channel) = peer, case .broadcast = channel.info {
isGroup = false
}
doneInProgressPromise.set(false)
var errorTextAndActions: (String, [TextAlertAction])?
switch error {
case .tooMuchJoined:
pushControllerImpl?(oldChannelsController(context: context, intent: .upgrade))
return
case .invalidPassword:
contentNode?.animateError()
case .limitExceeded:
errorTextAndActions = (presentationData.strings.TwoStepAuth_FloodError, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
case .adminsTooMuch:
errorTextAndActions = (isGroup ? presentationData.strings.Group_OwnershipTransfer_ErrorAdminsTooMuch : presentationData.strings.Channel_OwnershipTransfer_ErrorAdminsTooMuch, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
case .userPublicChannelsTooMuch:
errorTextAndActions = (presentationData.strings.Channel_OwnershipTransfer_ErrorPublicChannelsTooMuch, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
case .userLocatedGroupsTooMuch:
errorTextAndActions = (presentationData.strings.Group_OwnershipTransfer_ErrorLocatedGroupsTooMuch, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
case .userBlocked, .restricted:
errorTextAndActions = (isGroup ? presentationData.strings.Group_OwnershipTransfer_ErrorPrivacyRestricted : presentationData.strings.Channel_OwnershipTransfer_ErrorPrivacyRestricted, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
default:
errorTextAndActions = (presentationData.strings.Login_UnknownError, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
case .tooMuchJoined:
push(oldChannelsController(context: context, intent: .upgrade))
return
case .invalidPassword:
inputState.animateError()
case .limitExceeded:
errorTextAndActions = (strings.TwoStepAuth_FloodError, [TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})])
case .adminsTooMuch:
errorTextAndActions = (isGroup ? strings.Group_OwnershipTransfer_ErrorAdminsTooMuch : strings.Channel_OwnershipTransfer_ErrorAdminsTooMuch, [TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})])
case .userPublicChannelsTooMuch:
errorTextAndActions = (strings.Channel_OwnershipTransfer_ErrorPublicChannelsTooMuch, [TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})])
case .userLocatedGroupsTooMuch:
errorTextAndActions = (strings.Group_OwnershipTransfer_ErrorLocatedGroupsTooMuch, [TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})])
case .userBlocked, .restricted:
errorTextAndActions = (isGroup ? strings.Group_OwnershipTransfer_ErrorPrivacyRestricted : strings.Channel_OwnershipTransfer_ErrorPrivacyRestricted, [TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})])
default:
errorTextAndActions = (strings.Login_UnknownError, [TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})])
}
contentNode?.updateIsChecking(false)
if let (text, actions) = errorTextAndActions {
dismissImpl?()
present(textAlertController(context: context, title: nil, text: text, actions: actions), nil)
}
}))
})
}
pushControllerImpl = { [weak controller] c in
controller?.push(c)
dismissImpl = { [weak alertController] in
alertController?.dismiss(completion: nil)
}
return controller
return alertController
}
private func confirmChannelOwnershipTransferController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peer: EnginePeer, member: TelegramUser, present: @escaping (ViewController, Any?) -> Void, completion: @escaping (EnginePeer.Id?) -> Void) -> ViewController {
private func confirmChannelOwnershipTransferController(
context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
peer: EnginePeer,
member: TelegramUser,
present: @escaping (ViewController, Any?) -> Void,
push: @escaping (ViewController) -> Void,
completion: @escaping (EnginePeer.Id?) -> Void
) -> ViewController {
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
let theme = AlertControllerTheme(presentationData: presentationData)
var isGroup = true
if case let .channel(channel) = peer, case .broadcast = channel.info {
@@ -551,69 +190,82 @@ private func confirmChannelOwnershipTransferController(context: AccountContext,
text = presentationData.strings.Channel_OwnershipTransfer_DescriptionInfo(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), EnginePeer.user(member).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string
}
let attributedTitle = NSAttributedString(string: title, font: Font.semibold(presentationData.listsFontSize.baseDisplaySize), textColor: theme.primaryColor, paragraphAlignment: .center)
let body = MarkdownAttributeSet(font: Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), textColor: theme.primaryColor)
let bold = MarkdownAttributeSet(font: Font.semibold(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), textColor: theme.primaryColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .center)
let controller = richTextAlertController(context: context, title: attributedTitle, text: attributedText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Channel_OwnershipTransfer_ChangeOwner, action: {
present(commitChannelOwnershipTransferController(context: context, peer: peer, member: member, present: present, completion: completion), nil)
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {
})], actionLayout: .vertical)
let controller = textAlertController(
context: context,
updatedPresentationData: updatedPresentationData,
title: title,
text: text,
actions: [
TextAlertAction(type: .genericAction, title: presentationData.strings.Channel_OwnershipTransfer_ChangeOwner, action: {
present(commitChannelOwnershipTransferController(context: context, peer: peer, member: member, present: present, push: push, completion: completion), nil)
}),
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {})
],
actionLayout: .vertical
)
return controller
}
public func channelOwnershipTransferController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peer: EnginePeer, member: TelegramUser, initialError: ChannelOwnershipTransferError, present: @escaping (ViewController, Any?) -> Void, completion: @escaping (EnginePeer.Id?) -> Void) -> ViewController {
public func channelOwnershipTransferController(
context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
peer: EnginePeer,
member: TelegramUser,
initialError: ChannelOwnershipTransferError,
present: @escaping (ViewController, Any?) -> Void,
push: @escaping (ViewController) -> Void,
completion: @escaping (EnginePeer.Id?) -> Void
) -> ViewController {
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
let theme = AlertControllerTheme(presentationData: presentationData)
let strings = presentationData.strings
var title: NSAttributedString? = NSAttributedString(string: presentationData.strings.OwnershipTransfer_SecurityCheck, font: Font.semibold(presentationData.listsFontSize.itemListBaseFontSize), textColor: theme.primaryColor, paragraphAlignment: .center)
var text = presentationData.strings.OwnershipTransfer_SecurityRequirements
let textFontSize = presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0
var title: String? = strings.OwnershipTransfer_SecurityCheck
var text = strings.OwnershipTransfer_SecurityRequirements
var isGroup = true
if case let .channel(channel) = peer, case .broadcast = channel.info {
isGroup = false
}
var actions: [TextAlertAction] = []
var actions: [AlertScreen.Action] = [
.init(title: strings.Common_OK, type: .default)
]
switch initialError {
case .requestPassword:
return confirmChannelOwnershipTransferController(context: context, updatedPresentationData: updatedPresentationData, peer: peer, member: member, present: present, completion: completion)
case .twoStepAuthTooFresh, .authSessionTooFresh:
text = text + presentationData.strings.OwnershipTransfer_ComeBackLater
actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]
case .twoStepAuthMissing:
actions = [TextAlertAction(type: .genericAction, title: presentationData.strings.OwnershipTransfer_SetupTwoStepAuth, action: {
case .requestPassword:
return confirmChannelOwnershipTransferController(context: context, updatedPresentationData: updatedPresentationData, peer: peer, member: member, present: present, push: push, completion: completion)
case .twoStepAuthTooFresh, .authSessionTooFresh:
text = text + strings.OwnershipTransfer_ComeBackLater
case .twoStepAuthMissing:
actions = [
.init(title: strings.OwnershipTransfer_SetupTwoStepAuth, type: .default, action: {
let controller = SetupTwoStepVerificationController(context: context, initialState: .automatic, stateUpdated: { update, shouldDismiss, controller in
if shouldDismiss {
controller.dismiss()
}
})
present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {})]
case .adminsTooMuch:
title = nil
text = isGroup ? presentationData.strings.Group_OwnershipTransfer_ErrorAdminsTooMuch : presentationData.strings.Channel_OwnershipTransfer_ErrorAdminsTooMuch
actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]
case .userPublicChannelsTooMuch:
title = nil
text = presentationData.strings.Channel_OwnershipTransfer_ErrorPublicChannelsTooMuch
actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]
case .userBlocked, .restricted:
title = nil
text = isGroup ? presentationData.strings.Group_OwnershipTransfer_ErrorPrivacyRestricted : presentationData.strings.Channel_OwnershipTransfer_ErrorPrivacyRestricted
actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]
default:
title = nil
text = presentationData.strings.Login_UnknownError
actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]
}),
.init(title: strings.Common_Cancel)
]
case .adminsTooMuch:
title = nil
text = isGroup ? strings.Group_OwnershipTransfer_ErrorAdminsTooMuch : strings.Channel_OwnershipTransfer_ErrorAdminsTooMuch
case .userPublicChannelsTooMuch:
title = nil
text = strings.Channel_OwnershipTransfer_ErrorPublicChannelsTooMuch
case .userBlocked, .restricted:
title = nil
text = isGroup ? strings.Group_OwnershipTransfer_ErrorPrivacyRestricted : strings.Channel_OwnershipTransfer_ErrorPrivacyRestricted
default:
title = nil
text = strings.Login_UnknownError
}
let body = MarkdownAttributeSet(font: Font.regular(textFontSize), textColor: theme.primaryColor)
let bold = MarkdownAttributeSet(font: Font.semibold(textFontSize), textColor: theme.primaryColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .center)
return richTextAlertController(context: context, title: title, text: attributedText, actions: actions)
return AlertScreen(
context: context,
configuration: AlertScreen.Configuration(actionAlignment: .vertical),
title: title,
text: text,
actions: actions
)
}
@@ -8,113 +8,162 @@ import TelegramPresentationData
import ActivityIndicator
import TextFormat
import AccountContext
import AlertUI
import PresentationDataUtils
import PasswordSetupUI
import Markdown
import ComponentFlow
import AlertComponent
import AlertInputFieldComponent
private func commitOwnershipTransferController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, present: @escaping (ViewController, Any?) -> Void, commit: @escaping (String) -> Signal<MessageActionCallbackResult, MessageActionCallbackError>, completion: @escaping (MessageActionCallbackResult) -> Void) -> ViewController {
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
private func commitOwnershipTransferController(
context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
present: @escaping (ViewController, Any?) -> Void,
commit: @escaping (String) -> Signal<MessageActionCallbackResult, MessageActionCallbackError>,
completion: @escaping (MessageActionCallbackResult) -> Void
) -> ViewController {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let strings = presentationData.strings
let inputState = AlertInputFieldComponent.ExternalState()
let doneIsEnabled: Signal<Bool, NoError> = inputState.valueSignal
|> map { value in
return !value.isEmpty
}
let doneInProgressPromise = ValuePromise<Bool>(false)
var content: [AnyComponentWithIdentity<AlertComponentEnvironment>] = []
content.append(AnyComponentWithIdentity(
id: "title",
component: AnyComponent(
AlertTitleComponent(title: strings.OwnershipTransfer_EnterPassword)
)
))
content.append(AnyComponentWithIdentity(
id: "text",
component: AnyComponent(
AlertTextComponent(content: .plain(strings.OwnershipTransfer_EnterPasswordText))
)
))
var applyImpl: (() -> Void)?
content.append(AnyComponentWithIdentity(
id: "input",
component: AnyComponent(
AlertInputFieldComponent(
context: context,
placeholder: strings.Channel_OwnershipTransfer_PasswordPlaceholder,
isSecureTextEntry: true,
isInitiallyFocused: true,
externalState: inputState,
returnKeyAction: {
applyImpl?()
}
)
)
))
var effectiveUpdatedPresentationData: (PresentationData, Signal<PresentationData, NoError>)
if let updatedPresentationData {
effectiveUpdatedPresentationData = updatedPresentationData
} else {
effectiveUpdatedPresentationData = (presentationData, context.sharedContext.presentationData)
}
var dismissImpl: (() -> Void)?
var proceedImpl: (() -> Void)?
let disposable = MetaDisposable()
let contentNode = ChannelOwnershipTransferAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, title: presentationData.strings.OwnershipTransfer_EnterPassword, text: presentationData.strings.OwnershipTransfer_EnterPasswordText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
dismissImpl?()
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.OwnershipTransfer_Transfer, action: {
proceedImpl?()
})])
contentNode.complete = {
proceedImpl?()
}
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode)
let presentationDataDisposable = (updatedPresentationData?.signal ?? context.sharedContext.presentationData).start(next: { [weak controller, weak contentNode] presentationData in
controller?.theme = AlertControllerTheme(presentationData: presentationData)
contentNode?.theme = presentationData.theme
})
controller.dismissed = { _ in
presentationDataDisposable.dispose()
disposable.dispose()
}
dismissImpl = { [weak controller, weak contentNode] in
contentNode?.dismissInput()
controller?.dismissAnimated()
}
proceedImpl = { [weak contentNode] in
guard let contentNode = contentNode else {
return
}
contentNode.updateIsChecking(true)
disposable.set((commit(contentNode.password) |> deliverOnMainQueue).start(next: { result in
completion(result)
let alertController = AlertScreen(
configuration: AlertScreen.Configuration(allowInputInset: true),
content: content,
actions: [
.init(title: strings.Common_Cancel),
.init(title: strings.OwnershipTransfer_Transfer, type: .default, action: {
applyImpl?()
}, autoDismiss: false, isEnabled: doneIsEnabled, progress: doneInProgressPromise.get())
],
updatedPresentationData: effectiveUpdatedPresentationData
)
applyImpl = {
doneInProgressPromise.set(true)
let _ = (commit(inputState.value)
|> deliverOnMainQueue).start(next: { result in
dismissImpl?()
}, error: { [weak contentNode] error in
completion(result)
}, error: { error in
var errorTextAndActions: (String, [TextAlertAction])?
switch error {
case .invalidPassword:
contentNode?.animateError()
case .limitExceeded:
errorTextAndActions = (presentationData.strings.TwoStepAuth_FloodError, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
case .userBlocked, .restricted:
errorTextAndActions = (presentationData.strings.Group_OwnershipTransfer_ErrorPrivacyRestricted, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
default:
errorTextAndActions = (presentationData.strings.Login_UnknownError, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
case .invalidPassword:
inputState.animateError()
case .limitExceeded:
errorTextAndActions = (strings.TwoStepAuth_FloodError, [TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})])
case .userBlocked, .restricted:
errorTextAndActions = (presentationData.strings.Group_OwnershipTransfer_ErrorPrivacyRestricted, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
default:
errorTextAndActions = (strings.Login_UnknownError, [TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})])
}
contentNode?.updateIsChecking(false)
doneInProgressPromise.set(false)
if let (text, actions) = errorTextAndActions {
dismissImpl?()
present(textAlertController(context: context, title: nil, text: text, actions: actions), nil)
}
}))
})
}
return controller
dismissImpl = { [weak alertController] in
alertController?.dismiss(completion: nil)
}
return alertController
}
public func ownershipTransferController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, initialError: MessageActionCallbackError, present: @escaping (ViewController, Any?) -> Void, commit: @escaping (String) -> Signal<MessageActionCallbackResult, MessageActionCallbackError>, completion: @escaping (MessageActionCallbackResult) -> Void) -> ViewController {
public func ownershipTransferController(
context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
initialError: MessageActionCallbackError,
present: @escaping (ViewController, Any?) -> Void,
commit: @escaping (String) -> Signal<MessageActionCallbackResult, MessageActionCallbackError>,
completion: @escaping (MessageActionCallbackResult) -> Void
) -> ViewController {
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
let theme = AlertControllerTheme(presentationData: presentationData)
let strings = presentationData.strings
var title: NSAttributedString? = NSAttributedString(string: presentationData.strings.OwnershipTransfer_SecurityCheck, font: Font.semibold(presentationData.listsFontSize.itemListBaseFontSize), textColor: theme.primaryColor, paragraphAlignment: .center)
var title: String? = strings.OwnershipTransfer_SecurityCheck
var text = strings.OwnershipTransfer_SecurityRequirements
var text = presentationData.strings.OwnershipTransfer_SecurityRequirements
var actions: [TextAlertAction] = []
let textFontSize = presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0
var actions: [AlertScreen.Action] = [
.init(title: strings.Common_OK, type: .default)
]
switch initialError {
case .requestPassword:
return commitOwnershipTransferController(context: context, updatedPresentationData: updatedPresentationData, present: present, commit: commit, completion: completion)
case .twoStepAuthTooFresh, .authSessionTooFresh:
text = text + presentationData.strings.OwnershipTransfer_ComeBackLater
actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]
case .twoStepAuthMissing:
actions = [TextAlertAction(type: .genericAction, title: presentationData.strings.OwnershipTransfer_SetupTwoStepAuth, action: {
let controller = SetupTwoStepVerificationController(context: context, initialState: .automatic, stateUpdated: { update, shouldDismiss, controller in
if shouldDismiss {
controller.dismiss()
}
})
present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {})]
actions = [
.init(title: strings.OwnershipTransfer_SetupTwoStepAuth, type: .default, action: {
let controller = SetupTwoStepVerificationController(context: context, initialState: .automatic, stateUpdated: { update, shouldDismiss, controller in
if shouldDismiss {
controller.dismiss()
}
})
present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}),
.init(title: strings.Common_Cancel)
]
case .userBlocked, .restricted:
title = nil
text = presentationData.strings.Group_OwnershipTransfer_ErrorPrivacyRestricted
actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]
default:
title = nil
text = presentationData.strings.Login_UnknownError
actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]
}
let body = MarkdownAttributeSet(font: Font.regular(textFontSize), textColor: theme.primaryColor)
let bold = MarkdownAttributeSet(font: Font.semibold(textFontSize), textColor: theme.primaryColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .center)
return richTextAlertController(context: context, title: title, text: attributedText, actions: actions)
return AlertScreen(
context: context,
configuration: AlertScreen.Configuration(actionAlignment: .vertical),
title: title,
text: text,
actions: actions
)
}