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
@@ -49,7 +49,6 @@ private final class SheetContent: CombinedComponent {
final class State: ComponentState {
var cachedChevronImage: (UIImage, PresentationTheme)?
var cachedCloseImage: (UIImage, PresentationTheme)?
let playOnce = ActionSlot<Void>()
private var didPlayAnimation = false
@@ -189,14 +188,15 @@ private final class SheetContent: CombinedComponent {
contentSize.height += list.size.height
contentSize.height += spacing + 2.0
let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0)
let buttonAttributedString = NSMutableAttributedString(string: strings.FrozenAccount_SubmitAppeal, font: Font.semibold(17.0), textColor: environment.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
let actionButton = actionButton.update(
component: 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.9),
cornerRadius: 10.0
),
content: AnyComponentWithIdentity(
id: AnyHashable(0),
@@ -209,7 +209,7 @@ private final class SheetContent: CombinedComponent {
component.dismiss()
}
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0),
availableSize: CGSize(width: context.availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0),
transition: context.transition
)
context.add(actionButton
@@ -223,10 +223,10 @@ private final class SheetContent: CombinedComponent {
let closeButton = closeButton.update(
component: ButtonComponent(
background: ButtonComponent.Background(
color: .clear,
foreground: .clear,
pressedColor: .clear,
cornerRadius: 10.0
style: .glass,
color: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.1),
foreground: theme.list.itemCheckColors.fillColor,
pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8)
),
content: AnyComponentWithIdentity(
id: AnyHashable(1),
@@ -238,19 +238,14 @@ private final class SheetContent: CombinedComponent {
component.dismiss()
}
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0),
availableSize: CGSize(width: context.availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0),
transition: context.transition
)
context.add(closeButton
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + actionButton.size.height / 2.0))
)
contentSize.height += closeButton.size.height
if environment.safeInsets.bottom > 0 {
contentSize.height += environment.safeInsets.bottom + 5.0
} else {
contentSize.height += 12.0
}
contentSize.height += buttonInsets.bottom
state.playAnimationIfNeeded()
@@ -312,6 +307,7 @@ private final class SheetContainerComponent: CombinedComponent {
})
}
)),
style: .glass,
backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor),
followContentSizeChanges: true,
externalState: sheetExternalState,
@@ -507,8 +503,8 @@ private final class ParagraphComponent: CombinedComponent {
textColor: component.titleColor,
paragraphAlignment: .natural
)),
horizontalAlignment: .center,
maximumNumberOfLines: 1
horizontalAlignment: .natural,
maximumNumberOfLines: 2
),
availableSize: CGSize(width: context.availableSize.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate
@@ -576,24 +572,3 @@ private final class ParagraphComponent: CombinedComponent {
}
}
}
private func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor) -> UIImage? {
return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(backgroundColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
context.setLineWidth(2.0)
context.setLineCap(.round)
context.setStrokeColor(foregroundColor.cgColor)
context.move(to: CGPoint(x: 10.0, y: 10.0))
context.addLine(to: CGPoint(x: 20.0, y: 20.0))
context.strokePath()
context.move(to: CGPoint(x: 20.0, y: 10.0))
context.addLine(to: CGPoint(x: 10.0, y: 20.0))
context.strokePath()
})
}
@@ -205,8 +205,6 @@ final class GreetingMessageListItemComponent: Component {
},
openStarsTopup: { _ in
},
dismissNotice: { _ in
},
editPeer: { _ in
},
openWebApp: { _ in
@@ -195,7 +195,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component {
if self.isOn {
if !self.hasAccessToAllChatsByDefault && self.additionalPeerList.categories.isEmpty && self.additionalPeerList.peers.isEmpty {
self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.BusinessMessageSetup_ErrorNoRecipients_Text, actions: [
self.environment?.controller()?.present(textAlertController(context: component.context, title: nil, text: presentationData.strings.BusinessMessageSetup_ErrorNoRecipients_Text, actions: [
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
}),
TextAlertAction(type: .defaultAction, title: presentationData.strings.BusinessMessageSetup_ErrorNoRecipients_ResetAction, action: {
@@ -223,7 +223,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component {
}
if let errorText {
self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [
self.environment?.controller()?.present(textAlertController(context: component.context, title: nil, text: errorText, actions: [
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
}),
TextAlertAction(type: .defaultAction, title: presentationData.strings.BusinessMessageSetup_ErrorScheduleTime_ResetAction, action: {
@@ -4,6 +4,7 @@ import Display
import ComponentFlow
import ListSectionComponent
import TelegramPresentationData
import PresentationDataUtils
import AppBundle
import AccountContext
import ViewControllerComponent
@@ -185,7 +186,7 @@ final class BusinessLinksSetupScreenComponent: Component {
errorText = presentationData.strings.Business_Links_ErrorTooManyLinks
}
environment.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [
environment.controller()?.present(textAlertController(context: component.context, title: nil, text: errorText, actions: [
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
})
]), in: .window(.root))
@@ -219,8 +219,6 @@ final class QuickReplySetupScreenComponent: Component {
},
openStarsTopup: { _ in
},
dismissNotice: { _ in
},
editPeer: { [weak listNode] _ in
guard let listNode, let parentView = listNode.parentView else {
return
@@ -798,8 +796,7 @@ final class QuickReplySetupScreenComponent: Component {
}
))) : nil,
rightButtons: rightButtons,
backTitle: isModal ? nil : strings.Common_Back,
backPressed: { [weak self] in
backPressed: isModal ? nil :{ [weak self] in
guard let self else {
return
}
@@ -818,14 +815,15 @@ final class QuickReplySetupScreenComponent: Component {
strings: strings,
statusBarHeight: statusBarHeight,
sideInset: insets.left,
isSearchActive: self.isSearchDisplayControllerActive,
isSearchEnabled: !self.isEditing,
search: ChatListNavigationBar.Search(isEnabled: !self.isEditing),
activeSearch: self.isSearchDisplayControllerActive ? ChatListNavigationBar.ActiveSearch(isExternal: false) : nil,
primaryContent: headerContent,
secondaryContent: nil,
secondaryTransition: 0.0,
storySubscriptions: nil,
storiesIncludeHidden: false,
uploadProgress: [:],
headerPanels: nil,
tabsNode: nil,
tabsNodeIsSearch: false,
accessoryPanelContainer: nil,
@@ -1022,8 +1020,9 @@ final class QuickReplySetupScreenComponent: Component {
let searchBarTheme = SearchBarNodeTheme(theme: environment.theme, hasSeparator: false)
searchBarNode = SearchBarNode(
theme: searchBarTheme,
presentationTheme: environment.theme,
strings: environment.strings,
fieldStyle: .modern,
fieldStyle: .glass,
displayBackground: false
)
searchBarNode.placeholderString = NSAttributedString(string: environment.strings.Common_Search, font: Font.regular(17.0), textColor: searchBarTheme.placeholder)
@@ -127,7 +127,7 @@ private final class BirthdayPickerSheetContentComponent: Component {
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
BundleIconComponent(
name: "Navigation/Close",
tintColor: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor
tintColor: environment.theme.chat.inputPanel.panelControlColor
)
)),
action: { [weak self] _ in
@@ -127,8 +127,7 @@ final class BusinessDaySetupScreenComponent: Component {
}
}
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: enviroment.strings.BusinessHoursSetup_ErrorIntersectingHours_Text, actions: [
self.environment?.controller()?.present(textAlertController(context: component.context, title: nil, text: enviroment.strings.BusinessHoursSetup_ErrorIntersectingHours_Text, actions: [
TextAlertAction(type: .genericAction, title: enviroment.strings.Common_Cancel, action: {
}),
TextAlertAction(type: .defaultAction, title: enviroment.strings.BusinessHoursSetup_ErrorIntersectingHours_ResetAction, action: {
@@ -307,8 +307,7 @@ final class BusinessHoursSetupScreenComponent: Component {
let _ = component.context.engine.accountData.updateAccountBusinessHours(businessHours: businessHours).startStandalone()
return true
} catch _ {
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: environment.strings.BusinessHoursSetup_ErrorIntersectingDays_Text, actions: [
self.environment?.controller()?.present(textAlertController(context: component.context, title: nil, text: environment.strings.BusinessHoursSetup_ErrorIntersectingDays_Text, actions: [
TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {
}),
TextAlertAction(type: .defaultAction, title: environment.strings.BusinessHoursSetup_ErrorIntersectingDays_ResetAction, action: { [weak self] in
@@ -126,7 +126,6 @@ final class ChatIntroItemComponent: Component {
pendingUnpinnedAllMessages: false,
activeGroupCallInfo: nil,
hasActiveGroupCall: false,
importState: nil,
threadData: nil,
isGeneralThreadClosed: nil,
replyMessage: nil,
@@ -20,6 +20,8 @@ swift_library(
"//submodules/ComponentFlow",
"//submodules/Components/MultilineTextComponent",
"//submodules/Components/BalancedTextComponent",
"//submodules/TelegramUI/Components/AlertComponent",
"//submodules/TelegramUI/Components/AlertComponent/AlertInputFieldComponent",
],
visibility = [
"//visibility:public",
@@ -10,533 +10,73 @@ import AccountContext
import ComponentFlow
import MultilineTextComponent
import BalancedTextComponent
import AlertComponent
import AlertInputFieldComponent
private final class PromptInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegate {
private var theme: PresentationTheme
private let backgroundNode: ASImageNode
private let textInputNode: EditableTextNode
private let placeholderNode: ASTextNode
private let characterLimitView = ComponentView<Empty>()
private let characterLimit: Int
var updateHeight: (() -> Void)?
var complete: (() -> Void)?
var textChanged: ((String) -> Void)?
private let backgroundInsets = UIEdgeInsets(top: 8.0, left: 16.0, bottom: 15.0, right: 16.0)
private let inputInsets: UIEdgeInsets
private let validCharacterSets: [CharacterSet]
var text: String {
get {
return self.textInputNode.attributedText?.string ?? ""
}
set {
self.textInputNode.attributedText = NSAttributedString(string: newValue, font: Font.regular(13.0), textColor: self.theme.actionSheet.inputTextColor)
self.placeholderNode.isHidden = !newValue.isEmpty
}
}
var placeholder: String = "" {
didSet {
self.placeholderNode.attributedText = NSAttributedString(string: self.placeholder, font: Font.regular(13.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
}
}
init(theme: PresentationTheme, placeholder: String, characterLimit: Int) {
self.theme = theme
self.characterLimit = characterLimit
self.inputInsets = UIEdgeInsets(top: 9.0, left: 6.0, bottom: 9.0, right: 16.0)
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: 1.0)
self.textInputNode = EditableTextNode()
self.textInputNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(13.0), NSAttributedString.Key.foregroundColor.rawValue: theme.actionSheet.inputTextColor]
self.textInputNode.clipsToBounds = true
self.textInputNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0)
self.textInputNode.textContainerInset = UIEdgeInsets(top: self.inputInsets.top, left: self.inputInsets.left, bottom: self.inputInsets.bottom, right: self.inputInsets.right)
self.textInputNode.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance
self.textInputNode.keyboardType = .default
self.textInputNode.autocapitalizationType = .none
self.textInputNode.returnKeyType = .done
self.textInputNode.autocorrectionType = .no
self.textInputNode.tintColor = theme.actionSheet.controlAccentColor
self.placeholderNode = ASTextNode()
self.placeholderNode.isUserInteractionEnabled = false
self.placeholderNode.displaysAsynchronously = false
self.placeholderNode.attributedText = NSAttributedString(string: placeholder, font: Font.regular(13.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
self.validCharacterSets = [
CharacterSet.alphanumerics,
CharacterSet(charactersIn: "0123456789_ "),
]
super.init()
self.textInputNode.delegate = self
self.addSubnode(self.backgroundNode)
self.addSubnode(self.textInputNode)
self.addSubnode(self.placeholderNode)
}
func updateTheme(_ theme: PresentationTheme) {
self.theme = theme
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 16.0, color: self.theme.actionSheet.inputHollowBackgroundColor, strokeColor: self.theme.actionSheet.inputBorderColor, strokeWidth: 1.0)
self.textInputNode.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance
self.placeholderNode.attributedText = NSAttributedString(string: self.placeholderNode.attributedText?.string ?? "", font: Font.regular(13.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
self.textInputNode.tintColor = self.theme.actionSheet.controlAccentColor
}
func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
let backgroundInsets = self.backgroundInsets
let inputInsets = self.inputInsets
let textFieldHeight = self.calculateTextFieldMetrics(width: width)
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 + 5.0, 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 characterLimitString: String
let characterLimitColor: UIColor
if self.text.count <= self.characterLimit {
let remaining = self.characterLimit - self.text.count
if remaining < 5 {
characterLimitString = "\(remaining)"
} else {
characterLimitString = " "
}
characterLimitColor = self.theme.list.itemPlaceholderTextColor
} else {
characterLimitString = "\(self.characterLimit - self.text.count)"
characterLimitColor = self.theme.list.itemDestructiveColor
}
let characterLimitSize = self.characterLimitView.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: characterLimitString, font: Font.regular(13.0), textColor: characterLimitColor))
)),
environment: {},
containerSize: CGSize(width: 100.0, height: 100.0)
public func businessLinkNameAlertController(
context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
value: String?,
apply: @escaping (String?) -> Void
) -> ViewController {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let strings = presentationData.strings
let inputState = AlertInputFieldComponent.ExternalState()
var content: [AnyComponentWithIdentity<AlertComponentEnvironment>] = []
content.append(AnyComponentWithIdentity(
id: "title",
component: AnyComponent(
AlertTitleComponent(title: strings.Business_Links_LinkNameTitle)
)
if let characterLimitComponentView = self.characterLimitView.view {
if characterLimitComponentView.superview == nil {
self.view.addSubview(characterLimitComponentView)
}
characterLimitComponentView.frame = CGRect(origin: CGPoint(x: width - 23.0 - characterLimitSize.width, y: 18.0), size: characterLimitSize)
}
return panelHeight
}
func activateInput() {
self.textInputNode.becomeFirstResponder()
}
func deactivateInput() {
self.textInputNode.resignFirstResponder()
}
@objc func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) {
self.updateTextNodeText(animated: true)
self.textChanged?(editableTextNode.textView.text)
self.placeholderNode.isHidden = !(editableTextNode.textView.text ?? "").isEmpty
}
func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if text == "\n" {
self.complete?()
return false
}
if text.unicodeScalars.contains(where: { c in
return !self.validCharacterSets.contains(where: { set in
return set.contains(c)
})
}) {
return false
}
return true
}
private func calculateTextFieldMetrics(width: CGFloat) -> CGFloat {
let backgroundInsets = self.backgroundInsets
let inputInsets = self.inputInsets
let unboundTextFieldHeight = max(34.0, ceil(self.textInputNode.measure(CGSize(width: width - backgroundInsets.left - backgroundInsets.right - inputInsets.left - inputInsets.right, height: CGFloat.greatestFiniteMagnitude)).height))
return min(61.0, max(34.0, unboundTextFieldHeight))
}
private func updateTextNodeText(animated: Bool) {
let backgroundInsets = self.backgroundInsets
let textFieldHeight = self.calculateTextFieldMetrics(width: self.bounds.size.width)
let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom
if !self.bounds.size.height.isEqual(to: panelHeight) {
self.updateHeight?()
}
}
@objc func clearPressed() {
self.textInputNode.attributedText = nil
self.deactivateInput()
}
}
public final class BusinessLinkNameAlertContentNode: AlertContentNode {
private let context: AccountContext
private var theme: AlertControllerTheme
private let strings: PresentationStrings
private let text: String
private let subtext: String
private let titleFont: PromptControllerTitleFont
private let textView = ComponentView<Empty>()
private let subtextView = ComponentView<Empty>()
fileprivate let inputFieldNode: PromptInputFieldNode
private let actionNodesSeparator: ASDisplayNode
private let actionNodes: [TextAlertContentActionNode]
private let actionVerticalSeparators: [ASDisplayNode]
private let disposable = MetaDisposable()
private var validLayout: CGSize?
private var errorText: String?
private let hapticFeedback = HapticFeedback()
var complete: (() -> Void)?
override public var dismissOnOutsideTap: Bool {
return self.isUserInteractionEnabled
}
init(context: AccountContext, theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], text: String, subtext: String, titleFont: PromptControllerTitleFont, value: String?, characterLimit: Int) {
self.context = context
self.theme = theme
self.strings = strings
self.text = text
self.subtext = subtext
self.titleFont = titleFont
self.inputFieldNode = PromptInputFieldNode(theme: ptheme, placeholder: strings.Business_Links_LinkNameInputPlaceholder, characterLimit: characterLimit)
self.inputFieldNode.text = value ?? ""
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.inputFieldNode)
self.addSubnode(self.actionNodesSeparator)
for actionNode in self.actionNodes {
self.addSubnode(actionNode)
}
self.actionNodes.last?.actionEnabled = true
for separatorNode in self.actionVerticalSeparators {
self.addSubnode(separatorNode)
}
self.inputFieldNode.updateHeight = { [weak self] in
if let strongSelf = self {
if let _ = strongSelf.validLayout {
strongSelf.requestLayout?(.immediate)
}
}
}
self.inputFieldNode.textChanged = { [weak self] text in
if let strongSelf = self, let lastNode = strongSelf.actionNodes.last {
lastNode.actionEnabled = text.count <= characterLimit
strongSelf.requestLayout?(.immediate)
}
}
self.updateTheme(theme)
self.inputFieldNode.complete = { [weak self] in
guard let self else {
return
}
if let lastNode = self.actionNodes.last, lastNode.actionEnabled {
self.complete?()
}
}
}
deinit {
self.disposable.dispose()
}
var value: String {
return self.inputFieldNode.text
}
public func setErrorText(errorText: String?) {
if self.errorText != errorText {
self.errorText = errorText
self.requestLayout?(.immediate)
}
if errorText != nil {
HapticFeedback().error()
self.inputFieldNode.layer.addShakeAnimation()
}
}
override public func updateTheme(_ theme: AlertControllerTheme) {
self.theme = theme
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)
}
}
override public 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: 16.0)
let spacing: CGFloat = 5.0
let subtextSpacing: CGFloat = -1.0
let textSize = self.textView.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: self.text, font: Font.semibold(17.0), textColor: self.theme.primaryColor)),
horizontalAlignment: .center,
maximumNumberOfLines: 0
)),
environment: {},
containerSize: CGSize(width: measureSize.width, height: 1000.0)
))
content.append(AnyComponentWithIdentity(
id: "text",
component: AnyComponent(
AlertTextComponent(content: .plain(strings.Business_Links_LinkNameText))
)
let textFrame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) * 0.5), y: origin.y), size: textSize)
if let textComponentView = self.textView.view {
if textComponentView.superview == nil {
textComponentView.layer.anchorPoint = CGPoint()
self.view.addSubview(textComponentView)
}
textComponentView.bounds = CGRect(origin: CGPoint(), size: textFrame.size)
transition.updatePosition(layer: textComponentView.layer, position: textFrame.origin)
}
origin.y += textSize.height + 6.0 + subtextSpacing
let subtextSize = self.subtextView.update(
transition: .immediate,
component: AnyComponent(BalancedTextComponent(
text: .plain(NSAttributedString(string: self.errorText ?? self.subtext, font: Font.regular(13.0), textColor: self.errorText != nil ? self.theme.destructiveColor : self.theme.primaryColor)),
horizontalAlignment: .center,
maximumNumberOfLines: 0
)),
environment: {},
containerSize: CGSize(width: measureSize.width, height: 1000.0)
)
let subtextFrame = CGRect(origin: CGPoint(x: floor((size.width - subtextSize.width) * 0.5), y: origin.y), size: subtextSize)
if let subtextComponentView = self.subtextView.view {
if subtextComponentView.superview == nil {
subtextComponentView.layer.anchorPoint = CGPoint()
self.view.addSubview(subtextComponentView)
}
subtextComponentView.bounds = CGRect(origin: CGPoint(), size: subtextFrame.size)
transition.updatePosition(layer: subtextComponentView.layer, position: subtextFrame.origin)
}
origin.y += subtextSize.height + 6.0 + spacing
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: 9.0, right: 18.0)
var contentWidth = max(textSize.width, minActionsWidth)
contentWidth = max(subtextSize.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
let inputFieldFrame = CGRect(x: 0.0, y: origin.y, width: resultWidth, height: inputFieldHeight)
transition.updateFrame(node: self.inputFieldNode, frame: inputFieldFrame)
transition.updateAlpha(node: self.inputFieldNode, alpha: inputHeight > 0.0 ? 1.0 : 0.0)
let resultSize = CGSize(width: resultWidth, height: textSize.height + subtextSpacing + subtextSize.height + spacing + inputHeight + actionsHeight + 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)))
}
}
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
}
func animateError() {
self.inputFieldNode.layer.addShakeAnimation()
self.hapticFeedback.error()
}
}
))
public enum PromptControllerTitleFont {
case regular
case bold
}
public func businessLinkNameAlertController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, text: String, subtext: String, titleFont: PromptControllerTitleFont = .regular, value: String?, characterLimit: Int = 1000, apply: @escaping (String?) -> Void) -> AlertController {
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
var dismissImpl: ((Bool) -> Void)?
var applyImpl: (() -> Void)?
let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
dismissImpl?(true)
apply(nil)
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Done, action: {
applyImpl?()
})]
let contentNode = BusinessLinkNameAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, text: text, subtext: subtext, titleFont: titleFont, value: value, characterLimit: characterLimit)
contentNode.complete = {
applyImpl?()
content.append(AnyComponentWithIdentity(
id: "input",
component: AnyComponent(
AlertInputFieldComponent(
context: context,
initialValue: value,
placeholder: strings.Business_Links_LinkNameInputPlaceholder,
characterLimit: 32,
hasClearButton: false,
isInitiallyFocused: true,
externalState: inputState,
returnKeyAction: {
applyImpl?()
}
)
)
))
var effectiveUpdatedPresentationData: (PresentationData, Signal<PresentationData, NoError>)
if let updatedPresentationData {
effectiveUpdatedPresentationData = updatedPresentationData
} else {
effectiveUpdatedPresentationData = (presentationData, context.sharedContext.presentationData)
}
applyImpl = { [weak contentNode] in
guard let contentNode = contentNode else {
return
}
apply(contentNode.value)
let alertController = AlertScreen(
configuration: AlertScreen.Configuration(allowInputInset: true),
content: content,
actions: [
.init(title: strings.Common_Cancel),
.init(title: strings.Common_Done, type: .default, action: {
applyImpl?()
})
],
updatedPresentationData: effectiveUpdatedPresentationData
)
applyImpl = {
apply(inputState.value)
}
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()
}
dismissImpl = { [weak controller] animated in
contentNode.inputFieldNode.deactivateInput()
if animated {
controller?.dismissAnimated()
} else {
controller?.dismiss()
}
}
return controller
return alertController
}
@@ -127,8 +127,7 @@ final class BusinessLocationSetupScreenComponent: Component {
let businessLocation = self.currentBusinessLocation()
if businessLocation != component.initialValue {
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: environment.strings.BusinessLocationSetup_AlertUnsavedChanges_Text, actions: [
self.environment?.controller()?.present(textAlertController(context: component.context, title: nil, text: environment.strings.BusinessLocationSetup_AlertUnsavedChanges_Text, actions: [
TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {
}),
TextAlertAction(type: .destructiveAction, title: environment.strings.BusinessLocationSetup_AlertUnsavedChanges_ResetAction, action: {
@@ -250,8 +249,7 @@ final class BusinessLocationSetupScreenComponent: Component {
let businessLocation = self.currentBusinessLocation()
if businessLocation != nil && address.isEmpty {
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: environment.strings.BusinessLocationSetup_ErrorAddressEmpty_Text, actions: [
self.environment?.controller()?.present(textAlertController(context: component.context, title: nil, text: environment.strings.BusinessLocationSetup_ErrorAddressEmpty_Text, actions: [
TextAlertAction(type: .genericAction, title: environment.strings.Common_OK, action: {
})
]), in: .window(.root))
@@ -34,6 +34,7 @@ swift_library(
"//submodules/AvatarNode",
"//submodules/TelegramUI/Components/PlainButtonComponent",
"//submodules/TelegramUI/Components/Stories/PeerListItemComponent",
"//submodules/TelegramUI/Components/AlertComponent",
"//submodules/ShimmerEffect",
],
visibility = [
@@ -23,6 +23,7 @@ import LottieComponent
import Markdown
import PeerListItemComponent
import AvatarNode
import AlertComponent
private let checkIcon: UIImage = {
return generateImage(CGSize(width: 12.0, height: 10.0), rotatedContext: { size, context in
@@ -527,14 +528,20 @@ final class ChatbotSetupScreenComponent: Component {
} else {
text = environment.strings.ChatbotSetup_Gift_Warning_CombinedText(botUsername).string
}
let alertController = textAlertController(context: component.context, title: environment.strings.ChatbotSetup_Gift_Warning_Title, text: text, actions: [
TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {
completion(false)
}),
TextAlertAction(type: .defaultAction, title: environment.strings.ChatbotSetup_Gift_Warning_Proceed, action: {
completion(true)
})
], parseMarkdown: true)
let alertController = AlertScreen(
context: component.context,
title: environment.strings.ChatbotSetup_Gift_Warning_Title,
text: text,
actions: [
.init(title: environment.strings.Common_Cancel, action: {
completion(false)
}),
.init(title: environment.strings.ChatbotSetup_Gift_Warning_Proceed, type: .default, action: {
completion(true)
}),
]
)
alertController.dismissed = { byOutsideTap in
if byOutsideTap {
completion(false)
@@ -785,7 +792,7 @@ final class ChatbotSetupScreenComponent: Component {
self.botRights = [.reply, .readMessages, .deleteSentMessages, .deleteReceivedMessages]
self.state?.updated(transition: .spring(duration: 0.3))
} else {
self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.ChatbotSetup_ErrorBotNotBusinessCapable, actions: [
self.environment?.controller()?.present(textAlertController(context: component.context, title: nil, text: presentationData.strings.ChatbotSetup_ErrorBotNotBusinessCapable, actions: [
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
})
]), in: .window(.root))
@@ -522,7 +522,7 @@ private final class CollectibleItemInfoScreenContentComponent: Component {
if environment.safeInsets.bottom.isZero {
contentHeight += 16.0
} else {
contentHeight += environment.safeInsets.bottom + 14.0
contentHeight += environment.safeInsets.bottom + 1.0
}
return CGSize(width: availableSize.width, height: contentHeight)
@@ -85,7 +85,7 @@ public class LanguageSelectionScreen: ViewController {
private func updateThemeAndStrings() {
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData), transition: .immediate)
self.searchContentNode?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search)
self.title = self.presentationData.strings.Settings_AppLanguage
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
@@ -540,7 +540,8 @@ final class LanguageSelectionScreenNode: ViewControllerTracingNode {
inline: true,
cancel: { [weak self] in
self?.requestDeactivateSearch()
}
},
fieldStyle: placeholderNode.fieldStyle
)
self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate)
@@ -179,14 +179,21 @@ final class PasskeysScreenComponent: Component {
guard let component = self.component, let environment = self.environment, let controller = environment.controller() else {
return
}
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: environment.strings.Passkeys_DeleteAlert_Title, text: environment.strings.Passkeys_DeleteAlert_Text, actions: [TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {
}), TextAlertAction(type: .destructiveAction, title: environment.strings.Passkeys_DeleteAlert_Action, action: { [weak self] in
guard let self else {
return
}
self.deletePasskey(id: id)
})]), in: .window(.root))
let alertController = textAlertController(
context: component.context,
title: environment.strings.Passkeys_DeleteAlert_Title,
text: environment.strings.Passkeys_DeleteAlert_Text,
actions: [
TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {}),
TextAlertAction(type: .destructiveAction, title: environment.strings.Passkeys_DeleteAlert_Action, action: { [weak self] in
guard let self else {
return
}
self.deletePasskey(id: id)
})
]
)
controller.present(alertController, in: .window(.root))
}
private func deletePasskey(id: String) {
@@ -370,7 +370,7 @@ public final class PeerNameColorItemNode: ListViewItemNode, ItemListItemNode {
self.maskNode = ASImageNode()
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false)
self.addSubnode(self.containerNode)
}
@@ -149,7 +149,7 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode {
case .back:
text = presentationData.strings.Common_Back
accessibilityText = presentationData.strings.Common_Back
icon = NavigationBar.backArrowImage(color: .white)
icon = navigationBarBackArrowImage(color: .white)
case .edit:
text = presentationData.strings.Common_Edit
accessibilityText = text
@@ -39,6 +39,7 @@ import Markdown
import GroupStickerPackSetupController
import PeerNameColorItem
import EmojiActionIconComponent
import EdgeEffect
final class ChannelAppearanceScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
@@ -169,13 +170,13 @@ final class ChannelAppearanceScreenComponent: Component {
}
final class View: UIView, UIScrollViewDelegate {
private let edgeEffectView: EdgeEffectView
private let topOverscrollLayer = SimpleLayer()
private let scrollView: ScrollView
private let actionButton = ComponentView<Empty>()
private let bottomPanelBackgroundView: BlurredBackgroundView
private let bottomPanelSeparator: SimpleLayer
private let backButton = PeerInfoHeaderNavigationButton()
private let navigationTitle = ComponentView<Empty>()
private let previewSection = ComponentView<Empty>()
@@ -227,6 +228,8 @@ final class ChannelAppearanceScreenComponent: Component {
private weak var emojiStatusSelectionController: ViewController?
override init(frame: CGRect) {
self.edgeEffectView = EdgeEffectView()
self.scrollView = ScrollView()
self.scrollView.showsVerticalScrollIndicator = true
self.scrollView.showsHorizontalScrollIndicator = false
@@ -249,14 +252,10 @@ final class ChannelAppearanceScreenComponent: Component {
self.scrollView.layer.addSublayer(self.topOverscrollLayer)
self.addSubview(self.edgeEffectView)
self.addSubview(self.bottomPanelBackgroundView)
self.layer.addSublayer(self.bottomPanelSeparator)
self.backButton.action = { [weak self] _, _ in
if let self, let controller = self.environment?.controller() {
controller.navigationController?.popViewController(animated: true)
}
}
}
required init?(coder: NSCoder) {
@@ -291,20 +290,26 @@ final class ChannelAppearanceScreenComponent: Component {
}
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.Channel_Appearance_UnsavedChangesAlertTitle, text: presentationData.strings.Channel_Appearance_UnsavedChangesAlertText, actions: [
TextAlertAction(type: .genericAction, title: presentationData.strings.Channel_Appearance_UnsavedChangesAlertDiscard, action: { [weak self] in
guard let self else {
return
}
self.environment?.controller()?.dismiss()
}),
TextAlertAction(type: .defaultAction, title: presentationData.strings.Channel_Appearance_UnsavedChangesAlertApply, action: { [weak self] in
guard let self else {
return
}
self.applySettings()
})
]), in: .window(.root))
let alertController = textAlertController(
context: component.context,
title: presentationData.strings.Channel_Appearance_UnsavedChangesAlertTitle,
text: presentationData.strings.Channel_Appearance_UnsavedChangesAlertText,
actions: [
TextAlertAction(type: .genericAction, title: presentationData.strings.Channel_Appearance_UnsavedChangesAlertDiscard, action: { [weak self] in
guard let self else {
return
}
self.environment?.controller()?.dismiss()
}),
TextAlertAction(type: .defaultAction, title: presentationData.strings.Channel_Appearance_UnsavedChangesAlertApply, action: { [weak self] in
guard let self else {
return
}
self.applySettings()
})
]
)
self.environment?.controller()?.present(alertController, in: .window(.root))
return false
}
@@ -343,6 +348,8 @@ final class ChannelAppearanceScreenComponent: Component {
transition.setAlpha(view: navigationTitleView, alpha: navigationAlpha)
}
transition.setAlpha(view: self.edgeEffectView, alpha: navigationAlpha)
let bottomNavigationAlphaDistance: CGFloat = 16.0
let bottomNavigationAlpha: CGFloat = max(0.0, min(1.0, (self.scrollView.contentSize.height - self.scrollView.bounds.maxY) / bottomNavigationAlphaDistance))
@@ -534,7 +541,15 @@ final class ChannelAppearanceScreenComponent: Component {
}
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
let alertController = textAlertController(
context: component.context,
title: nil,
text: presentationData.strings.Login_UnknownError,
actions: [
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})
]
)
self.environment?.controller()?.present(alertController, in: .window(.root))
self.isApplyingSettings = false
self.state?.updated(transition: .immediate)
@@ -992,6 +1007,10 @@ final class ChannelAppearanceScreenComponent: Component {
}
self.requiredBoostSubject = requiredBoostSubject
let edgeEffectHeight: CGFloat = environment.navigationHeight + 8.0
let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: edgeEffectHeight))
transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
self.edgeEffectView.update(content: environment.theme.list.blocksBackgroundColor, blur: true, rect: edgeEffectFrame, edge: .top, edgeSize: min(30, edgeEffectFrame.height), transition: transition)
let headerColor: UIColor
if let profileColor {
@@ -1002,7 +1021,6 @@ final class ChannelAppearanceScreenComponent: Component {
}
self.topOverscrollLayer.backgroundColor = headerColor.cgColor
let backSize = self.backButton.update(key: .back, presentationData: component.context.sharedContext.currentPresentationData.with { $0 }, height: 44.0)
var scrolledUp = self.scrolledUp
if profileColor == nil {
scrolledUp = false
@@ -1011,14 +1029,6 @@ final class ChannelAppearanceScreenComponent: Component {
if let controller = self.environment?.controller() as? ChannelAppearanceScreen {
controller.statusBar.updateStatusBarStyle(scrolledUp ? .White : .Ignore, animated: true)
}
self.backButton.updateContentsColor(backgroundColor: scrolledUp ? UIColor(white: 0.0, alpha: 0.1) : .clear, contentsColor: scrolledUp ? .white : environment.theme.rootController.navigationBar.accentTextColor, canBeExpanded: !scrolledUp, transition: .animated(duration: 0.2, curve: .easeInOut))
self.backButton.frame = CGRect(origin: CGPoint(x: environment.safeInsets.left + 16.0, y: environment.navigationHeight - 44.0), size: backSize)
if self.backButton.view.superview == nil {
if let controller = self.environment?.controller(), let navigationBar = controller.navigationBar {
navigationBar.view.addSubview(self.backButton.view)
}
}
let navigationTitleSize = self.navigationTitle.update(
transition: transition,
@@ -1897,12 +1907,11 @@ public class ChannelAppearanceScreen: ViewControllerComponentContainer {
context: context,
peerId: peerId,
boostStatus: boostStatus
), navigationBarAppearance: .default, theme: .default, updatedPresentationData: updatedPresentationData)
), navigationBarAppearance: .transparent, theme: .default, updatedPresentationData: updatedPresentationData)
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.title = ""
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView())
self.ready.set(.never())
@@ -99,7 +99,7 @@ final class EmojiPickerItemNode: ListViewItemNode {
self.maskNode = ASImageNode()
self.maskNode.isUserInteractionEnabled = false
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false)
self.clipsToBounds = true
}
@@ -218,7 +218,7 @@ final class GiftListItemComponent: Component {
))
for gift in component.starGifts {
guard case let .generic(gift) = gift, let title = gift.title else {
guard case let .generic(gift) = gift, let title = gift.title, let resale = gift.availability?.resale, resale > 0 else {
continue
}
tabSelectorItems.append(TabSelectorComponent.Item(
@@ -186,7 +186,7 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
self.containerNode = ASDisplayNode()
self.containerNode.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false)
self.clipsToBounds = true
self.isUserInteractionEnabled = false
@@ -142,7 +142,7 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode {
let avatarFont = avatarPlaceholderFont(size: floor(100.0 * 16.0 / 37.0))
self.avatarNode = AvatarNode(font: avatarFont)
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false)
self.clipsToBounds = true
self.isUserInteractionEnabled = false
@@ -55,17 +55,17 @@ final class UserAppearanceScreenComponent: Component {
}
let context: AccountContext
let overNavigationContainer: UIView
init(
context: AccountContext
context: AccountContext,
overNavigationContainer: UIView
) {
self.context = context
self.overNavigationContainer = overNavigationContainer
}
static func ==(lhs: UserAppearanceScreenComponent, rhs: UserAppearanceScreenComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
return true
}
@@ -166,8 +166,6 @@ final class UserAppearanceScreenComponent: Component {
private let actionButton = ComponentView<Empty>()
private let edgeEffectView: EdgeEffectView
private let backButton = PeerInfoHeaderNavigationButton()
private let tabSelector = ComponentView<Empty>()
enum Section: Int32 {
case profile
@@ -263,12 +261,6 @@ final class UserAppearanceScreenComponent: Component {
self.containerView.addSubview(self.previewShadowView)
self.addSubview(self.edgeEffectView)
self.backButton.action = { [weak self] _, _ in
if let self, let controller = self.environment?.controller() as? UserAppearanceScreen {
controller.backPressed()
}
}
}
required init?(coder: NSCoder) {
@@ -298,21 +290,28 @@ final class UserAppearanceScreenComponent: Component {
if !resolvedState.changes.isEmpty {
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.Channel_Appearance_UnsavedChangesAlertTitle, text: presentationData.strings.Channel_Appearance_UnsavedChangesAlertText, actions: [
TextAlertAction(type: .genericAction, title: presentationData.strings.Channel_Appearance_UnsavedChangesAlertDiscard, action: { [weak self] in
guard let self else {
return
}
self.environment?.controller()?.dismiss()
}),
TextAlertAction(type: .defaultAction, title: presentationData.strings.Channel_Appearance_UnsavedChangesAlertApply, action: { [weak self] in
guard let self else {
return
}
self.applySettings()
})
]), in: .window(.root))
let alertController = textAlertController(
context: component.context,
title: presentationData.strings.Channel_Appearance_UnsavedChangesAlertTitle,
text: presentationData.strings.Channel_Appearance_UnsavedChangesAlertText,
actions: [
TextAlertAction(type: .genericAction, title: presentationData.strings.Channel_Appearance_UnsavedChangesAlertDiscard, action: { [weak self] in
guard let self else {
return
}
self.environment?.controller()?.dismiss()
}),
TextAlertAction(type: .defaultAction, title: presentationData.strings.Channel_Appearance_UnsavedChangesAlertApply, action: { [weak self] in
guard let self else {
return
}
self.applySettings()
})
]
)
self.environment?.controller()?.present(alertController, in: .window(.root))
return false
}
@@ -564,7 +563,7 @@ final class UserAppearanceScreenComponent: Component {
self.isApplyingSettings = false
self.applySettings()
Queue.mainQueue().after(0.5) {
Queue.mainQueue().after(2.5) {
switch finalPrice.currency {
case .stars:
component.context.starsContext?.load(force: true)
@@ -800,7 +799,16 @@ final class UserAppearanceScreenComponent: Component {
}
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
let alertController = textAlertController(
context: component.context,
title: nil,
text: presentationData.strings.Login_UnknownError,
actions: [
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})
]
)
self.environment?.controller()?.present(alertController, in: .window(.root))
self.isApplyingSettings = false
self.state?.updated(transition: .immediate)
@@ -963,6 +971,8 @@ final class UserAppearanceScreenComponent: Component {
self.component = component
self.state = state
transition.setFrame(view: component.overNavigationContainer, frame: CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: environment.navigationHeight)))
let theme = environment.theme
var animateTabChange = false
@@ -1072,16 +1082,6 @@ final class UserAppearanceScreenComponent: Component {
.withUpdatedProfileBackgroundEmojiId(resolvedState.backgroundFileId)
)
}
let backSize = self.backButton.update(key: .back, presentationData: component.context.sharedContext.currentPresentationData.with { $0 }, height: 44.0)
self.backButton.updateContentsColor(backgroundColor: .clear, contentsColor: environment.theme.rootController.navigationBar.accentTextColor, canBeExpanded: true, transition: .animated(duration: 0.2, curve: .easeInOut))
self.backButton.frame = CGRect(origin: CGPoint(x: environment.safeInsets.left + 16.0, y: environment.navigationHeight - 44.0), size: backSize)
if self.backButton.view.superview == nil {
if let controller = self.environment?.controller(), let navigationBar = controller.navigationBar {
navigationBar.view.addSubview(self.backButton.view)
}
}
var previewTransition = transition
let transitionScale = (availableSize.height - 3.0) / availableSize.height
@@ -1148,10 +1148,10 @@ final class UserAppearanceScreenComponent: Component {
environment: {},
containerSize: CGSize(width: availableSize.width, height: 44.0)
)
let tabSelectorFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - tabSelectorSize.width) / 2.0), y: environment.statusBarHeight + floorToScreenPixels((environment.navigationHeight - environment.statusBarHeight - tabSelectorSize.height) / 2.0)), size: tabSelectorSize)
let tabSelectorFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - tabSelectorSize.width) / 2.0), y: environment.statusBarHeight + 2.0 + floorToScreenPixels((environment.navigationHeight - environment.statusBarHeight - tabSelectorSize.height) / 2.0)), size: tabSelectorSize)
if let tabSelectorView = self.tabSelector.view {
if tabSelectorView.superview == nil {
self.addSubview(tabSelectorView)
component.overNavigationContainer.addSubview(tabSelectorView)
}
transition.setFrame(view: tabSelectorView, frame: tabSelectorFrame)
}
@@ -1932,6 +1932,8 @@ final class UserAppearanceScreenComponent: Component {
public class UserAppearanceScreen: ViewControllerComponentContainer {
private let context: AccountContext
private let overNavigationContainer: UIView
private var didSetReady: Bool = false
public init(
@@ -1940,8 +1942,11 @@ public class UserAppearanceScreen: ViewControllerComponentContainer {
) {
self.context = context
self.overNavigationContainer = SparseContainerView()
super.init(context: context, component: UserAppearanceScreenComponent(
context: context
context: context,
overNavigationContainer: self.overNavigationContainer
), navigationBarAppearance: .default, theme: .default, updatedPresentationData: updatedPresentationData)
self.automaticallyControlPresentationContextLayout = false
@@ -1951,7 +1956,6 @@ public class UserAppearanceScreen: ViewControllerComponentContainer {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.title = ""
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView())
self.ready.set(.never())
@@ -1969,6 +1973,10 @@ public class UserAppearanceScreen: ViewControllerComponentContainer {
return componentView.attemptNavigation(complete: complete)
}
if let navigationBar = self.navigationBar {
navigationBar.customOverBackgroundContentView.insertSubview(self.overNavigationContainer, at: 0)
}
}
required public init(coder aDecoder: NSCoder) {
@@ -218,7 +218,7 @@ final class PeerSelectionScreenComponent: Component {
private var channels: [PeerSelectionScreen.ChannelInfo]?
private var channelsDisposable: Disposable?
private var isSearchDisplayControllerActive: Bool = false
private var isSearchDisplayControllerActive: ChatListNavigationBar.ActiveSearch?
private var searchQuery: String = ""
private let searchQueryComponentSeparationCharacterSet: CharacterSet
@@ -298,8 +298,7 @@ final class PeerSelectionScreenComponent: Component {
}
))) : nil,
rightButtons: rightButtons,
backTitle: isModal ? nil : strings.Common_Back,
backPressed: { [weak self] in
backPressed: isModal ? nil : { [weak self] in
guard let self else {
return
}
@@ -318,14 +317,15 @@ final class PeerSelectionScreenComponent: Component {
strings: strings,
statusBarHeight: statusBarHeight,
sideInset: insets.left,
isSearchActive: self.isSearchDisplayControllerActive,
isSearchEnabled: true,
search: ChatListNavigationBar.Search(isEnabled: true),
activeSearch: self.isSearchDisplayControllerActive,
primaryContent: headerContent,
secondaryContent: nil,
secondaryTransition: 0.0,
storySubscriptions: nil,
storiesIncludeHidden: false,
uploadProgress: [:],
headerPanels: nil,
tabsNode: nil,
tabsNodeIsSearch: false,
accessoryPanelContainer: nil,
@@ -335,7 +335,7 @@ final class PeerSelectionScreenComponent: Component {
return
}
self.isSearchDisplayControllerActive = true
self.isSearchDisplayControllerActive = ChatListNavigationBar.ActiveSearch(isExternal: false)
self.state?.updated(transition: .spring(duration: 0.4))
},
openStatusSetup: { _ in
@@ -385,7 +385,7 @@ final class PeerSelectionScreenComponent: Component {
let resultingOffset = mainOffset
var offset = resultingOffset
if self.isSearchDisplayControllerActive {
if self.isSearchDisplayControllerActive != nil {
offset = 0.0
}
@@ -468,7 +468,7 @@ final class PeerSelectionScreenComponent: Component {
self.navigationHeight = navigationHeight
var removedSearchBar: SearchBarNode?
if self.isSearchDisplayControllerActive {
if self.isSearchDisplayControllerActive != nil {
let searchBarNode: SearchBarNode
var searchBarTransition = transition
if let current = self.searchBarNode {
@@ -478,8 +478,9 @@ final class PeerSelectionScreenComponent: Component {
let searchBarTheme = SearchBarNodeTheme(theme: environment.theme, hasSeparator: false)
searchBarNode = SearchBarNode(
theme: searchBarTheme,
presentationTheme: environment.theme,
strings: environment.strings,
fieldStyle: .modern,
fieldStyle: .glass,
displayBackground: false
)
searchBarNode.placeholderString = NSAttributedString(string: environment.strings.Common_Search, font: Font.regular(17.0), textColor: searchBarTheme.placeholder)
@@ -488,7 +489,7 @@ final class PeerSelectionScreenComponent: Component {
guard let self else {
return
}
self.isSearchDisplayControllerActive = false
self.isSearchDisplayControllerActive = nil
self.state?.updated(transition: .spring(duration: 0.4))
}
searchBarNode.textUpdated = { [weak self] query, _ in
@@ -151,7 +151,7 @@ public class ItemListReactionItemNode: ListViewItemNode, ItemListItemNode {
self.activateArea = AccessibilityAreaNode()
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false)
self.addSubnode(self.titleNode)
self.view.addSubview(self.iconView)
@@ -372,7 +372,7 @@ public class ItemListReactionItemNode: ListViewItemNode, ItemListItemNode {
if let animationContent = animationContent {
let iconBoundingSize = CGSize(width: 28.0, height: 28.0)
let iconOffsetX: CGFloat = 0.0
let iconOffsetX: CGFloat = -6.0
let iconSize = strongSelf.iconView.update(
transition: .immediate,
component: AnyComponent(EmojiStatusComponent(
@@ -116,7 +116,7 @@ class ReactionChatPreviewItemNode: ListViewItemNode {
self.containerNode = ASDisplayNode()
self.containerNode.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false)
self.addSubnode(self.clippingNode)
self.clippingNode.addSubnode(self.containerNode)
@@ -21,6 +21,8 @@ swift_library(
"//submodules/Components/MultilineTextComponent",
"//submodules/Components/BalancedTextComponent",
"//submodules/TelegramUI/Components/EmojiStatusComponent",
"//submodules/TelegramUI/Components/AlertComponent",
"//submodules/TelegramUI/Components/AlertComponent/AlertInputFieldComponent",
],
visibility = [
"//visibility:public",
@@ -11,6 +11,8 @@ import ComponentFlow
import MultilineTextComponent
import BalancedTextComponent
import EmojiStatusComponent
import AlertComponent
import AlertInputFieldComponent
private final class PromptInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegate {
private var theme: PresentationTheme
@@ -500,6 +502,100 @@ public enum PromptControllerTitleFont {
}
public func quickReplyNameAlertController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, text: String, subtext: String, titleFont: PromptControllerTitleFont = .regular, value: String?, characterLimit: Int = 1000, apply: @escaping (String?) -> Void) -> AlertController {
// 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.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
// }
//
// let doneInProgressValuePromise = ValuePromise<Bool>(false)
// let doneInProgress = doneInProgressValuePromise.get()
//
// var content: [AnyComponentWithIdentity<AlertComponentEnvironment>] = []
// content.append(AnyComponentWithIdentity(
// id: "title",
// component: AnyComponent(
// AlertTitleComponent(title: strings.WebBrowser_Exceptions_Create_Title)
// )
// ))
// content.append(AnyComponentWithIdentity(
// id: "text",
// component: AnyComponent(
// AlertTextComponent(content: .plain(strings.WebBrowser_Exceptions_Create_Text))
// )
// ))
//
// let domainRegex = try? NSRegularExpression(pattern: "^(https?://)?([a-zA-Z0-9-]+\\.?)*([a-zA-Z]*)?(:)?(/)?$", options: [])
// let pathRegex = try? NSRegularExpression(pattern: "^(https?://)?([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}/", options: [])
// var applyImpl: (() -> Void)?
// content.append(AnyComponentWithIdentity(
// id: "input",
// component: AnyComponent(
// AlertInputFieldComponent(
// context: context,
// initialValue: nil,
// placeholder: strings.QuickReply_ShortcutPlaceholder,
// characterLimit: characterLimit,
// hasClearButton: false,
// keyboardType: .URL,
// autocapitalizationType: .none,
// autocorrectionType: .no,
// isInitiallyFocused: true,
// externalState: inputState,
// shouldChangeText: { updatedText in
// guard let domainRegex, let pathRegex else {
// return true
// }
// let domainMatches = domainRegex.matches(in: updatedText, options: [], range: NSRange(location: 0, length: updatedText.utf16.count))
// let pathMatches = pathRegex.matches(in: updatedText, options: [], range: NSRange(location: 0, length: updatedText.utf16.count))
// if domainMatches.count > 0, pathMatches.count == 0 {
// return true
// } else {
// return false
// }
// },
// returnKeyAction: {
// applyImpl?()
// }
// )
// )
// ))
//
// var effectiveUpdatedPresentationData: (PresentationData, Signal<PresentationData, NoError>)
// if let updatedPresentationData {
// effectiveUpdatedPresentationData = updatedPresentationData
// } else {
// effectiveUpdatedPresentationData = (presentationData, context.sharedContext.presentationData)
// }
//
// let alertController = AlertScreen(
// configuration: AlertScreen.Configuration(allowInputInset: true),
// content: content,
// actions: [
// .init(title: strings.Common_Cancel, action: {
// apply(nil)
// }),
// .init(title: strings.Common_Done, type: .default, action: {
// applyImpl?()
// }, autoDismiss: false, isEnabled: doneIsEnabled, progress: doneInProgress)
// ],
// updatedPresentationData: effectiveUpdatedPresentationData
// )
// applyImpl = {
// let updatedLink = explicitUrl(inputState.value)
// if !updatedLink.isEmpty && isValidUrl(updatedLink, validSchemes: ["http": true, "https": true]) {
// doneInProgressValuePromise.set(true)
// apply(updatedLink)
// } else {
// inputState.animateError()
// }
// }
// return alertController
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
var dismissImpl: ((Bool) -> Void)?
@@ -374,7 +374,7 @@ public final class ThemeAccentColorController: ViewController {
}, ready: self._ready)
self.controllerNode.themeUpdated = { [weak self] theme in
if let strongSelf = self {
strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationTheme: theme, presentationStrings: strongSelf.presentationData.strings))
strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationTheme: theme, presentationStrings: strongSelf.presentationData.strings), transition: .immediate)
strongSelf.segmentedTitleView.theme = theme
}
}
@@ -871,7 +871,6 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, ASScrollViewDelegate
}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
}, openStories: { _, _ in
}, openStarsTopup: { _ in
}, dismissNotice: { _ in
}, editPeer: { _ in
}, openWebApp: { _ in
}, openPhotoSetup: {
@@ -259,7 +259,7 @@ private final class ThemeCarouselThemeItemIconNode: ListViewItemNode {
self.activateAreaNode = AccessibilityAreaNode()
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
super.init(layerBacked: false, rotated: false, seeThrough: false)
self.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.imageNode)
@@ -725,7 +725,7 @@ public class ThemeCarouselThemeItemNode: ListViewItemNode, ItemListItemNode {
self.listNode = ListView()
self.listNode.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false)
self.addSubnode(self.containerNode)
self.addSubnode(self.listNode)
@@ -229,7 +229,7 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
self.activateAreaNode = AccessibilityAreaNode()
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
super.init(layerBacked: false, rotated: false, seeThrough: false)
self.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.imageNode)
@@ -549,7 +549,7 @@ public class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
self.listNode = ListView()
self.listNode.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false)
self.addSubnode(self.containerNode)
self.addSubnode(self.listNode)
@@ -35,7 +35,9 @@ public class TimezoneSelectionScreen: ViewController {
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass))
self._hasGlassStyle = true
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
@@ -82,7 +84,7 @@ public class TimezoneSelectionScreen: ViewController {
private func updateThemeAndStrings() {
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData, style: .glass), transition: .immediate)
self.searchContentNode?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search)
self.title = self.presentationData.strings.Settings_AppLanguage
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
@@ -109,7 +109,7 @@ private final class TimezoneListSearchContainerNode: SearchDisplayControllerCont
self.presentationDataPromise = Promise(self.presentationData)
self.dimNode = ASDisplayNode()
self.dimNode.backgroundColor = UIColor.black.withAlphaComponent(0.5)
self.dimNode.backgroundColor = .clear
self.listNode = ListView()
self.listNode.accessibilityPageScrolledString = { row, count in
@@ -523,7 +523,7 @@ final class TimezoneSelectionScreenNode: ViewControllerTracingNode {
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: TimezoneListSearchContainerNode(context: self.context, timeZoneList: timeZoneList, action: self.action), inline: true, cancel: { [weak self] in
self?.requestDeactivateSearch()
})
}, fieldStyle: placeholderNode.fieldStyle)
self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate)
self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in
@@ -396,7 +396,7 @@ public class WallpaperGalleryController: ViewController {
self.title = self.presentationData.strings.WallpaperPreview_Title
}
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData), transition: .immediate)
self.toolbarNode?.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings)
self.patternPanelNode?.updateTheme(self.presentationData.theme)
@@ -131,7 +131,7 @@ private final class WallpaperPatternItemNode : ListViewItemNode {
init() {
self.wallpaperNode = SettingsThemeWallpaperNode(displayLoading: true)
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
super.init(layerBacked: false, rotated: false, seeThrough: false)
self.addSubnode(self.wallpaperNode)
}
@@ -158,7 +158,7 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina
self.mode = mode
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass))
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
@@ -216,7 +216,7 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina
private func updateThemeAndStrings() {
self.title = self.presentationData.strings.WallpaperColors_Title
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData, style: .glass), transition: .immediate)
if self.isNodeLoaded {
self.controllerNode.updatePresentationData(self.presentationData)
@@ -45,8 +45,6 @@ public final class ThemeGridController: ViewController {
private let presentationDataPromise = Promise<PresentationData>()
private var presentationDataDisposable: Disposable?
private var searchContentNode: NavigationBarSearchContentNode?
private var isEmpty: Bool?
private var editingMode: Bool = false
@@ -67,7 +65,7 @@ public final class ThemeGridController: ViewController {
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.presentationDataPromise.set(.single(self.presentationData))
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass))
switch mode {
case .generic:
@@ -81,9 +79,6 @@ public final class ThemeGridController: ViewController {
self.scrollToTop = { [weak self] in
if let strongSelf = self {
if let searchContentNode = strongSelf.searchContentNode {
searchContentNode.updateExpansionProgress(1.0, animated: true)
}
strongSelf.controllerNode.scrollToTop()
}
}
@@ -128,8 +123,7 @@ public final class ThemeGridController: ViewController {
}
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
self.searchContentNode?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Wallpaper_Search)
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData, style: .glass), transition: .immediate)
if self.isNodeLoaded {
self.controllerNode.updatePresentationData(self.presentationData)
@@ -182,7 +176,6 @@ public final class ThemeGridController: ViewController {
} else {
uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness, completion: { [weak self, weak controller] in
if let strongSelf = self {
strongSelf.deactivateSearch(animated: false)
strongSelf.controllerNode.scrollToTop(animated: false)
}
if let controller = controller {
@@ -392,9 +385,6 @@ public final class ThemeGridController: ViewController {
}
})
self.controllerNode.navigationBar = self.navigationBar
self.controllerNode.requestDeactivateSearch = { [weak self] in
self?.deactivateSearch(animated: true)
}
self.controllerNode.requestWallpaperRemoval = { [weak self] in
if let self {
self.completion(.remove)
@@ -403,10 +393,6 @@ public final class ThemeGridController: ViewController {
}
self.controllerNode.gridNode.visibleContentOffsetChanged = { [weak self] offset in
if let strongSelf = self {
if let searchContentNode = strongSelf.searchContentNode {
searchContentNode.updateGridVisibleContentOffset(offset)
}
var previousContentOffsetValue: CGFloat?
if let previousContentOffset = strongSelf.previousContentOffset, case let .known(value) = previousContentOffset {
previousContentOffsetValue = value
@@ -427,12 +413,6 @@ public final class ThemeGridController: ViewController {
strongSelf.previousContentOffset = offset
}
}
self.controllerNode.gridNode.scrollingCompleted = { [weak self] in
if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode {
let _ = strongSelf.controllerNode.fixNavigationSearchableGridNodeScrolling(searchNode: searchContentNode)
}
}
self._ready.set(self.controllerNode.ready.get())
@@ -492,38 +472,9 @@ public final class ThemeGridController: ViewController {
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.cleanNavigationHeight, transition: transition)
}
func activateSearch() {
if self.displayNavigationBar {
let _ = (self.controllerNode.ready.get()
|> take(1)
|> deliverOnMainQueue).start(completed: { [weak self] in
guard let strongSelf = self else {
return
}
if let scrollToTop = strongSelf.scrollToTop {
scrollToTop()
}
if let searchContentNode = strongSelf.searchContentNode {
strongSelf.controllerNode.activateSearch(placeholderNode: searchContentNode.placeholderNode)
}
strongSelf.setDisplayNavigationBar(false, transition: .animated(duration: 0.5, curve: .spring))
})
}
}
func deactivateSearch(animated: Bool) {
if !self.displayNavigationBar {
self.setDisplayNavigationBar(true, transition: animated ? .animated(duration: 0.5, curve: .spring) : .immediate)
if let searchContentNode = self.searchContentNode {
self.controllerNode.deactivateSearch(placeholderNode: searchContentNode.placeholderNode, animated: animated)
}
}
}
@objc func editPressed() {
self.editingMode = true
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed))
self.searchContentNode?.setIsEnabled(false, animated: true)
self.controllerNode.updateState { state in
var state = state
state.editing = true
@@ -534,7 +485,6 @@ public final class ThemeGridController: ViewController {
@objc func donePressed() {
self.editingMode = false
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed))
self.searchContentNode?.setIsEnabled(true, animated: true)
self.controllerNode.updateState { state in
var state = state
state.editing = false
@@ -162,7 +162,6 @@ final class ThemeGridControllerNode: ASDisplayNode {
private let emptyStateUpdated: (Bool) -> Void
private let resetWallpapers: () -> Void
var requestDeactivateSearch: (() -> Void)?
var requestWallpaperRemoval: (() -> Void)?
let ready = ValuePromise<Bool>()
@@ -958,75 +957,6 @@ final class ThemeGridControllerNode: ASDisplayNode {
}
}
func activateSearch(placeholderNode: SearchBarPlaceholderNode) {
guard let (containerLayout, navigationBarHeight) = self.validLayout, let navigationBar = self.navigationBar, self.searchDisplayController == nil else {
return
}
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ThemeGridSearchContentNode(context: context, openResult: { [weak self] result in
if let strongSelf = self {
strongSelf.presentPreviewController(.contextResult(result))
}
}), cancel: { [weak self] in
self?.requestDeactivateSearch?()
})
self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate)
self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in
if let strongSelf = self, let strongPlaceholderNode = placeholderNode {
if isSearchBar {
strongPlaceholderNode.supernode?.insertSubnode(subnode, aboveSubnode: strongPlaceholderNode)
} else {
strongSelf.insertSubnode(subnode, belowSubnode: navigationBar)
}
}
}, placeholder: placeholderNode)
}
func deactivateSearch(placeholderNode: SearchBarPlaceholderNode, animated: Bool) {
if let searchDisplayController = self.searchDisplayController {
searchDisplayController.deactivate(placeholder: placeholderNode, animated: animated)
self.searchDisplayController = nil
}
}
func fixNavigationSearchableGridNodeScrolling(searchNode: NavigationBarSearchContentNode) -> Bool {
if searchNode.expansionProgress > 0.0 && searchNode.expansionProgress < 1.0 {
let scrollToItem: GridNodeScrollToItem
let targetProgress: CGFloat
let duration: Double = 0.3
let curve = ContainedViewLayoutTransitionCurve.slide
let transition: ContainedViewLayoutTransition = .animated(duration: duration, curve: curve)
let timingFunction = curve.timingFunction
let mediaTimingFunction = curve.mediaTimingFunction
if searchNode.expansionProgress < 0.6 {
scrollToItem = GridNodeScrollToItem(index: 0, position: .top(navigationBarSearchContentHeight), transition: transition, directionHint: .up, adjustForSection: true, adjustForTopInset: true)
targetProgress = 0.0
} else {
scrollToItem = GridNodeScrollToItem(index: 0, position: .top(0.0), transition: transition, directionHint: .up, adjustForSection: true, adjustForTopInset: true)
targetProgress = 1.0
}
let previousOffset = (self.gridNode.scrollView.contentOffset.y + self.gridNode.scrollView.contentInset.top)
searchNode.updateExpansionProgress(targetProgress, animated: true)
self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: scrollToItem, updateLayout: nil, itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil, updateOpaqueState: nil, synchronousLoads: false), completion: { _ in })
let offset = (self.gridNode.scrollView.contentOffset.y + self.gridNode.scrollView.contentInset.top) - previousOffset
self.backgroundNode.layer.animatePosition(from: self.backgroundNode.layer.position.offsetBy(dx: 0.0, dy: offset), to: self.backgroundNode.layer.position, duration: duration, timingFunction: timingFunction, mediaTimingFunction: mediaTimingFunction)
self.separatorNode.layer.animatePosition(from: self.separatorNode.layer.position.offsetBy(dx: 0.0, dy: offset), to: self.separatorNode.layer.position, duration: duration, timingFunction: timingFunction, mediaTimingFunction: mediaTimingFunction)
self.colorItemNode.layer.animatePosition(from: self.colorItemNode.layer.position.offsetBy(dx: 0.0, dy: offset), to: self.colorItemNode.layer.position, duration: duration, timingFunction: timingFunction, mediaTimingFunction: mediaTimingFunction)
self.galleryItemNode.layer.animatePosition(from: self.galleryItemNode.layer.position.offsetBy(dx: 0.0, dy: offset), to: self.galleryItemNode.layer.position, duration: duration, timingFunction: timingFunction, mediaTimingFunction: mediaTimingFunction)
self.descriptionItemNode.layer.animatePosition(from: self.descriptionItemNode.layer.position.offsetBy(dx: 0.0, dy: offset), to: self.descriptionItemNode.layer.position, duration: duration, timingFunction: timingFunction, mediaTimingFunction: mediaTimingFunction)
return true
}
return false
}
func scrollToTop(animated: Bool = true) {
if let searchDisplayController = self.searchDisplayController {
searchDisplayController.contentNode.scrollToTop()
@@ -180,7 +180,7 @@ class ThemeGridSearchColorsItemNode: ListViewItemNode {
self.separatorNode = ASDisplayNode()
self.separatorNode.isLayerBacked = true
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false)
self.addSubnode(self.backgroundNode)
self.addSubnode(self.separatorNode)