mirror of
https://github.com/ichmagmaus111/ghostgram.git
synced 2026-08-09 15:56:02 +02:00
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:
+12
-37
@@ -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()
|
||||
})
|
||||
}
|
||||
|
||||
-2
@@ -205,8 +205,6 @@ final class GreetingMessageListItemComponent: Component {
|
||||
},
|
||||
openStarsTopup: { _ in
|
||||
},
|
||||
dismissNotice: { _ in
|
||||
},
|
||||
editPeer: { _ in
|
||||
},
|
||||
openWebApp: { _ in
|
||||
|
||||
+2
-2
@@ -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: {
|
||||
|
||||
+2
-1
@@ -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))
|
||||
|
||||
+6
-7
@@ -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)
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+1
-2
@@ -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: {
|
||||
|
||||
+1
-2
@@ -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
|
||||
|
||||
-1
@@ -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",
|
||||
|
||||
+62
-522
@@ -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
|
||||
}
|
||||
|
||||
+2
-4
@@ -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 = [
|
||||
|
||||
+16
-9
@@ -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))
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
+2
-1
@@ -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)
|
||||
|
||||
+15
-8
@@ -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) {
|
||||
|
||||
+1
-1
@@ -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)
|
||||
}
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+42
-33
@@ -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())
|
||||
|
||||
|
||||
+1
-1
@@ -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
|
||||
}
|
||||
|
||||
+1
-1
@@ -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(
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+50
-42
@@ -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) {
|
||||
|
||||
+11
-10
@@ -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
|
||||
|
||||
+2
-2
@@ -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(
|
||||
|
||||
+1
-1
@@ -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",
|
||||
|
||||
+96
@@ -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)?
|
||||
|
||||
+1
-1
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
-1
@@ -871,7 +871,6 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, ASScrollViewDelegate
|
||||
}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
|
||||
}, openStories: { _, _ in
|
||||
}, openStarsTopup: { _ in
|
||||
}, dismissNotice: { _ in
|
||||
}, editPeer: { _ in
|
||||
}, openWebApp: { _ in
|
||||
}, openPhotoSetup: {
|
||||
|
||||
+2
-2
@@ -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)
|
||||
|
||||
+2
-2
@@ -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)
|
||||
|
||||
+4
-2
@@ -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)
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
|
||||
+1
-1
@@ -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)
|
||||
}
|
||||
|
||||
+2
-2
@@ -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)
|
||||
|
||||
+2
-52
@@ -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
|
||||
|
||||
-70
@@ -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()
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user