mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-01 20:28:05 +00:00
Business features
This commit is contained in:
parent
3d6c9b1745
commit
ac9c6a5f7f
@ -30,6 +30,7 @@ swift_library(
|
|||||||
"//submodules/Markdown",
|
"//submodules/Markdown",
|
||||||
"//submodules/ReactionSelectionNode",
|
"//submodules/ReactionSelectionNode",
|
||||||
"//submodules/TelegramUI/Components/Chat/ChatMediaInputStickerGridItem",
|
"//submodules/TelegramUI/Components/Chat/ChatMediaInputStickerGridItem",
|
||||||
|
"//submodules/PremiumUI",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import Markdown
|
|||||||
import ReactionSelectionNode
|
import ReactionSelectionNode
|
||||||
import ChatMediaInputStickerGridItem
|
import ChatMediaInputStickerGridItem
|
||||||
import UndoUI
|
import UndoUI
|
||||||
|
import PremiumUI
|
||||||
|
|
||||||
private protocol ChatEmptyNodeContent {
|
private protocol ChatEmptyNodeContent {
|
||||||
func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize
|
func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize
|
||||||
@ -1594,7 +1595,7 @@ private final class EmptyAttachedDescriptionNode: HighlightTrackingButtonNode {
|
|||||||
self.badgeTextNode.attributedText = NSAttributedString(string: "how?", font: Font.regular(11.0), textColor: serviceColor.primaryText)
|
self.badgeTextNode.attributedText = NSAttributedString(string: "how?", font: Font.regular(11.0), textColor: serviceColor.primaryText)
|
||||||
let badgeTextSize = self.badgeTextNode.updateLayout(CGSize(width: 200.0, height: 100.0))
|
let badgeTextSize = self.badgeTextNode.updateLayout(CGSize(width: 200.0, height: 100.0))
|
||||||
if let lastLineFrame = labelRects.last {
|
if let lastLineFrame = labelRects.last {
|
||||||
let badgeTextFrame = CGRect(origin: CGPoint(x: lastLineFrame.maxX - badgeTextSize.width - 2.0, y: textFrame.maxY - badgeTextSize.height), size: badgeTextSize)
|
let badgeTextFrame = CGRect(origin: CGPoint(x: lastLineFrame.maxX - badgeTextSize.width - 3.0, y: textFrame.maxY - badgeTextSize.height), size: badgeTextSize)
|
||||||
self.badgeTextNode.frame = badgeTextFrame
|
self.badgeTextNode.frame = badgeTextFrame
|
||||||
|
|
||||||
let badgeBackgroundFrame = badgeTextFrame.insetBy(dx: -4.0, dy: -1.0)
|
let badgeBackgroundFrame = badgeTextFrame.insetBy(dx: -4.0, dy: -1.0)
|
||||||
@ -1857,6 +1858,7 @@ public final class ChatEmptyNode: ASDisplayNode {
|
|||||||
self.backgroundNode.update(size: self.backgroundNode.bounds.size, cornerRadius: min(20.0, self.backgroundNode.bounds.height / 2.0), transition: transition)
|
self.backgroundNode.update(size: self.backgroundNode.bounds.size, cornerRadius: min(20.0, self.backgroundNode.bounds.height / 2.0), transition: transition)
|
||||||
|
|
||||||
if displayAttachedDescription, let peer = interfaceState.renderedPeer?.chatMainPeer {
|
if displayAttachedDescription, let peer = interfaceState.renderedPeer?.chatMainPeer {
|
||||||
|
let isPremium = interfaceState.isPremium
|
||||||
let attachedDescriptionNode: EmptyAttachedDescriptionNode
|
let attachedDescriptionNode: EmptyAttachedDescriptionNode
|
||||||
if let current = self.attachedDescriptionNode {
|
if let current = self.attachedDescriptionNode {
|
||||||
attachedDescriptionNode = current
|
attachedDescriptionNode = current
|
||||||
@ -1869,7 +1871,32 @@ public final class ChatEmptyNode: ASDisplayNode {
|
|||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let controller = self.context.sharedContext.makePremiumIntroController(context: self.context, source: .settings, forceDark: false, dismissed: nil)
|
|
||||||
|
//TODO:localize
|
||||||
|
let context = self.context
|
||||||
|
var replaceImpl: ((ViewController) -> Void)?
|
||||||
|
var dismissImpl: (() -> Void)?
|
||||||
|
let controller = PremiumLimitsListScreen(context: context, subject: .business, source: .other, order: [.business], buttonText: "OK", isPremium: false, forceDark: false)
|
||||||
|
controller.action = {
|
||||||
|
if isPremium {
|
||||||
|
dismissImpl?()
|
||||||
|
} else {
|
||||||
|
let controller = PremiumIntroScreen(context: context, source: .settings, forceDark: false)
|
||||||
|
replaceImpl?(controller)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
replaceImpl = { [weak self, weak controller] c in
|
||||||
|
controller?.dismiss(animated: true, completion: {
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.interaction?.chatController()?.push(c)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
dismissImpl = { [weak controller] in
|
||||||
|
controller?.dismiss(animated: true, completion: {
|
||||||
|
})
|
||||||
|
}
|
||||||
self.interaction?.chatController()?.push(controller)
|
self.interaction?.chatController()?.push(controller)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1425,7 +1425,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
|
|
||||||
var version = 0
|
var version = 0
|
||||||
strongSelf.stickerSearchDisposable.set((resultSignal
|
strongSelf.stickerSearchDisposable.set((resultSignal
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
|> deliverOnMainQueue).start(next: { result in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -272,7 +272,7 @@ public final class EntityKeyboardComponent: Component {
|
|||||||
private let pagerView: ComponentHostView<EntityKeyboardChildEnvironment>
|
private let pagerView: ComponentHostView<EntityKeyboardChildEnvironment>
|
||||||
|
|
||||||
private var component: EntityKeyboardComponent?
|
private var component: EntityKeyboardComponent?
|
||||||
private weak var state: EmptyComponentState?
|
public private(set) weak var state: EmptyComponentState?
|
||||||
|
|
||||||
private var searchView: ComponentHostView<EntitySearchContentEnvironment>?
|
private var searchView: ComponentHostView<EntitySearchContentEnvironment>?
|
||||||
private var searchComponent: EntitySearchContentComponent?
|
private var searchComponent: EntitySearchContentComponent?
|
||||||
|
|||||||
@ -12,6 +12,7 @@ public final class ListMultilineTextFieldItemComponent: Component {
|
|||||||
public final class ExternalState {
|
public final class ExternalState {
|
||||||
public fileprivate(set) var hasText: Bool = false
|
public fileprivate(set) var hasText: Bool = false
|
||||||
public fileprivate(set) var text: NSAttributedString = NSAttributedString()
|
public fileprivate(set) var text: NSAttributedString = NSAttributedString()
|
||||||
|
public fileprivate(set) var isEditing: Bool = false
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
}
|
}
|
||||||
@ -39,6 +40,7 @@ public final class ListMultilineTextFieldItemComponent: Component {
|
|||||||
public let autocapitalizationType: UITextAutocapitalizationType
|
public let autocapitalizationType: UITextAutocapitalizationType
|
||||||
public let autocorrectionType: UITextAutocorrectionType
|
public let autocorrectionType: UITextAutocorrectionType
|
||||||
public let characterLimit: Int?
|
public let characterLimit: Int?
|
||||||
|
public let displayCharacterLimit: Bool
|
||||||
public let allowEmptyLines: Bool
|
public let allowEmptyLines: Bool
|
||||||
public let updated: ((String) -> Void)?
|
public let updated: ((String) -> Void)?
|
||||||
public let textUpdateTransition: Transition
|
public let textUpdateTransition: Transition
|
||||||
@ -55,6 +57,7 @@ public final class ListMultilineTextFieldItemComponent: Component {
|
|||||||
autocapitalizationType: UITextAutocapitalizationType = .sentences,
|
autocapitalizationType: UITextAutocapitalizationType = .sentences,
|
||||||
autocorrectionType: UITextAutocorrectionType = .default,
|
autocorrectionType: UITextAutocorrectionType = .default,
|
||||||
characterLimit: Int? = nil,
|
characterLimit: Int? = nil,
|
||||||
|
displayCharacterLimit: Bool = false,
|
||||||
allowEmptyLines: Bool = true,
|
allowEmptyLines: Bool = true,
|
||||||
updated: ((String) -> Void)?,
|
updated: ((String) -> Void)?,
|
||||||
textUpdateTransition: Transition = .immediate,
|
textUpdateTransition: Transition = .immediate,
|
||||||
@ -70,6 +73,7 @@ public final class ListMultilineTextFieldItemComponent: Component {
|
|||||||
self.autocapitalizationType = autocapitalizationType
|
self.autocapitalizationType = autocapitalizationType
|
||||||
self.autocorrectionType = autocorrectionType
|
self.autocorrectionType = autocorrectionType
|
||||||
self.characterLimit = characterLimit
|
self.characterLimit = characterLimit
|
||||||
|
self.displayCharacterLimit = displayCharacterLimit
|
||||||
self.allowEmptyLines = allowEmptyLines
|
self.allowEmptyLines = allowEmptyLines
|
||||||
self.updated = updated
|
self.updated = updated
|
||||||
self.textUpdateTransition = textUpdateTransition
|
self.textUpdateTransition = textUpdateTransition
|
||||||
@ -107,6 +111,9 @@ public final class ListMultilineTextFieldItemComponent: Component {
|
|||||||
if lhs.characterLimit != rhs.characterLimit {
|
if lhs.characterLimit != rhs.characterLimit {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.displayCharacterLimit != rhs.displayCharacterLimit {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.allowEmptyLines != rhs.allowEmptyLines {
|
if lhs.allowEmptyLines != rhs.allowEmptyLines {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -134,6 +141,9 @@ public final class ListMultilineTextFieldItemComponent: Component {
|
|||||||
|
|
||||||
private let placeholder = ComponentView<Empty>()
|
private let placeholder = ComponentView<Empty>()
|
||||||
|
|
||||||
|
private var measureTextLimitLabel: ComponentView<Empty>?
|
||||||
|
private var textLimitLabel: ComponentView<Empty>?
|
||||||
|
|
||||||
private var component: ListMultilineTextFieldItemComponent?
|
private var component: ListMultilineTextFieldItemComponent?
|
||||||
private weak var state: EmptyComponentState?
|
private weak var state: EmptyComponentState?
|
||||||
private var isUpdating: Bool = false
|
private var isUpdating: Bool = false
|
||||||
@ -192,6 +202,29 @@ public final class ListMultilineTextFieldItemComponent: Component {
|
|||||||
let verticalInset: CGFloat = 12.0
|
let verticalInset: CGFloat = 12.0
|
||||||
let sideInset: CGFloat = 16.0
|
let sideInset: CGFloat = 16.0
|
||||||
|
|
||||||
|
let textLimitFont = Font.regular(15.0)
|
||||||
|
var measureTextLimitInset: CGFloat = 0.0
|
||||||
|
if component.characterLimit != nil && component.displayCharacterLimit {
|
||||||
|
let measureTextLimitLabel: ComponentView<Empty>
|
||||||
|
if let current = self.measureTextLimitLabel {
|
||||||
|
measureTextLimitLabel = current
|
||||||
|
} else {
|
||||||
|
measureTextLimitLabel = ComponentView()
|
||||||
|
self.measureTextLimitLabel = measureTextLimitLabel
|
||||||
|
}
|
||||||
|
let measureTextLimitSize = measureTextLimitLabel.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(MultilineTextComponent(
|
||||||
|
text: .plain(NSAttributedString(string: "000", font: textLimitFont))
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||||
|
)
|
||||||
|
measureTextLimitInset = measureTextLimitSize.width + 4.0
|
||||||
|
} else {
|
||||||
|
self.measureTextLimitLabel = nil
|
||||||
|
}
|
||||||
|
|
||||||
let textFieldSize = self.textField.update(
|
let textFieldSize = self.textField.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(TextFieldComponent(
|
component: AnyComponent(TextFieldComponent(
|
||||||
@ -201,7 +234,7 @@ public final class ListMultilineTextFieldItemComponent: Component {
|
|||||||
externalState: self.textFieldExternalState,
|
externalState: self.textFieldExternalState,
|
||||||
fontSize: 17.0,
|
fontSize: 17.0,
|
||||||
textColor: component.theme.list.itemPrimaryTextColor,
|
textColor: component.theme.list.itemPrimaryTextColor,
|
||||||
insets: UIEdgeInsets(top: verticalInset, left: sideInset - 8.0, bottom: verticalInset, right: sideInset - 8.0),
|
insets: UIEdgeInsets(top: verticalInset, left: sideInset - 8.0, bottom: verticalInset, right: sideInset - 8.0 + measureTextLimitInset),
|
||||||
hideKeyboard: false,
|
hideKeyboard: false,
|
||||||
customInputView: nil,
|
customInputView: nil,
|
||||||
resetText: component.resetText.flatMap { resetText in
|
resetText: component.resetText.flatMap { resetText in
|
||||||
@ -258,6 +291,51 @@ public final class ListMultilineTextFieldItemComponent: Component {
|
|||||||
|
|
||||||
component.externalState?.hasText = self.textFieldExternalState.hasText
|
component.externalState?.hasText = self.textFieldExternalState.hasText
|
||||||
component.externalState?.text = self.textFieldExternalState.text
|
component.externalState?.text = self.textFieldExternalState.text
|
||||||
|
component.externalState?.isEditing = self.textFieldExternalState.isEditing
|
||||||
|
|
||||||
|
var displayRemainingLimit: Int?
|
||||||
|
if let characterLimit = component.characterLimit, component.displayCharacterLimit {
|
||||||
|
let remainingLimit = characterLimit - self.textFieldExternalState.text.length
|
||||||
|
let displayThreshold = max(10, Int(Double(characterLimit) * 0.15))
|
||||||
|
if remainingLimit <= displayThreshold {
|
||||||
|
displayRemainingLimit = remainingLimit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let displayRemainingLimit {
|
||||||
|
let textLimitLabel: ComponentView<Empty>
|
||||||
|
var textLimitLabelTransition = transition
|
||||||
|
if let current = self.textLimitLabel {
|
||||||
|
textLimitLabel = current
|
||||||
|
} else {
|
||||||
|
textLimitLabelTransition = textLimitLabelTransition.withAnimation(.none)
|
||||||
|
textLimitLabel = ComponentView()
|
||||||
|
self.textLimitLabel = textLimitLabel
|
||||||
|
}
|
||||||
|
|
||||||
|
let textLimitLabelSize = textLimitLabel.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(MultilineTextComponent(
|
||||||
|
text: .plain(NSAttributedString(string: "\(displayRemainingLimit)", font: textLimitFont, textColor: component.theme.list.itemSecondaryTextColor))
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||||
|
)
|
||||||
|
let textLimitLabelFrame = CGRect(origin: CGPoint(x: availableSize.width - textLimitLabelSize.width - sideInset, y: verticalInset + 2.0), size: textLimitLabelSize)
|
||||||
|
if let textLimitLabelView = textLimitLabel.view {
|
||||||
|
if textLimitLabelView.superview == nil {
|
||||||
|
textLimitLabelView.isUserInteractionEnabled = false
|
||||||
|
textLimitLabelView.layer.anchorPoint = CGPoint(x: 1.0, y: 0.0)
|
||||||
|
self.addSubview(textLimitLabelView)
|
||||||
|
}
|
||||||
|
textLimitLabelTransition.setPosition(view: textLimitLabelView, position: CGPoint(x: textLimitLabelFrame.maxX, y: textLimitLabelFrame.minY))
|
||||||
|
textLimitLabelView.bounds = CGRect(origin: CGPoint(), size: textLimitLabelFrame.size)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let textLimitLabel = self.textLimitLabel {
|
||||||
|
self.textLimitLabel = nil
|
||||||
|
textLimitLabel.view?.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,8 @@ public final class EmojiSelectionComponent: Component {
|
|||||||
public let sideInset: CGFloat
|
public let sideInset: CGFloat
|
||||||
public let bottomInset: CGFloat
|
public let bottomInset: CGFloat
|
||||||
public let deviceMetrics: DeviceMetrics
|
public let deviceMetrics: DeviceMetrics
|
||||||
public let emojiContent: EmojiPagerContentComponent
|
public let emojiContent: EmojiPagerContentComponent?
|
||||||
|
public let stickerContent: EmojiPagerContentComponent?
|
||||||
public let backgroundIconColor: UIColor?
|
public let backgroundIconColor: UIColor?
|
||||||
public let backgroundColor: UIColor
|
public let backgroundColor: UIColor
|
||||||
public let separatorColor: UIColor
|
public let separatorColor: UIColor
|
||||||
@ -29,7 +30,8 @@ public final class EmojiSelectionComponent: Component {
|
|||||||
sideInset: CGFloat,
|
sideInset: CGFloat,
|
||||||
bottomInset: CGFloat,
|
bottomInset: CGFloat,
|
||||||
deviceMetrics: DeviceMetrics,
|
deviceMetrics: DeviceMetrics,
|
||||||
emojiContent: EmojiPagerContentComponent,
|
emojiContent: EmojiPagerContentComponent?,
|
||||||
|
stickerContent: EmojiPagerContentComponent?,
|
||||||
backgroundIconColor: UIColor?,
|
backgroundIconColor: UIColor?,
|
||||||
backgroundColor: UIColor,
|
backgroundColor: UIColor,
|
||||||
separatorColor: UIColor,
|
separatorColor: UIColor,
|
||||||
@ -41,6 +43,7 @@ public final class EmojiSelectionComponent: Component {
|
|||||||
self.bottomInset = bottomInset
|
self.bottomInset = bottomInset
|
||||||
self.deviceMetrics = deviceMetrics
|
self.deviceMetrics = deviceMetrics
|
||||||
self.emojiContent = emojiContent
|
self.emojiContent = emojiContent
|
||||||
|
self.stickerContent = stickerContent
|
||||||
self.backgroundIconColor = backgroundIconColor
|
self.backgroundIconColor = backgroundIconColor
|
||||||
self.backgroundColor = backgroundColor
|
self.backgroundColor = backgroundColor
|
||||||
self.separatorColor = separatorColor
|
self.separatorColor = separatorColor
|
||||||
@ -66,6 +69,9 @@ public final class EmojiSelectionComponent: Component {
|
|||||||
if lhs.emojiContent != rhs.emojiContent {
|
if lhs.emojiContent != rhs.emojiContent {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.stickerContent != rhs.stickerContent {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.backgroundIconColor != rhs.backgroundIconColor {
|
if lhs.backgroundIconColor != rhs.backgroundIconColor {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -96,6 +102,8 @@ public final class EmojiSelectionComponent: Component {
|
|||||||
private var component: EmojiSelectionComponent?
|
private var component: EmojiSelectionComponent?
|
||||||
private weak var state: EmptyComponentState?
|
private weak var state: EmptyComponentState?
|
||||||
|
|
||||||
|
private var isSearchActive: Bool = false
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
self.keyboardView = ComponentView<Empty>()
|
self.keyboardView = ComponentView<Empty>()
|
||||||
self.keyboardClippingView = UIView()
|
self.keyboardClippingView = UIView()
|
||||||
@ -144,6 +152,12 @@ public final class EmojiSelectionComponent: Component {
|
|||||||
deinit {
|
deinit {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func internalRequestUpdate(transition: Transition) {
|
||||||
|
if let keyboardComponentView = self.keyboardView.view as? EntityKeyboardComponent.View {
|
||||||
|
keyboardComponentView.state?.updated(transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func update(component: EmojiSelectionComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
func update(component: EmojiSelectionComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||||
self.backgroundColor = component.backgroundColor
|
self.backgroundColor = component.backgroundColor
|
||||||
let panelBackgroundColor = component.backgroundColor.withMultipliedAlpha(0.85)
|
let panelBackgroundColor = component.backgroundColor.withMultipliedAlpha(0.85)
|
||||||
@ -154,6 +168,11 @@ public final class EmojiSelectionComponent: Component {
|
|||||||
self.component = component
|
self.component = component
|
||||||
self.state = state
|
self.state = state
|
||||||
|
|
||||||
|
var resolvedHeight: CGFloat = min(340.0, max(50.0, availableSize.height - 200.0))
|
||||||
|
if self.isSearchActive {
|
||||||
|
resolvedHeight = min(availableSize.height, resolvedHeight + 200.0)
|
||||||
|
}
|
||||||
|
|
||||||
self.cornersView.tintColor = component.theme.list.blocksBackgroundColor
|
self.cornersView.tintColor = component.theme.list.blocksBackgroundColor
|
||||||
transition.setFrame(view: self.cornersView, frame: CGRect(origin: CGPoint(x: 0.0, y: -8.0), size: CGSize(width: availableSize.width, height: 16.0)))
|
transition.setFrame(view: self.cornersView, frame: CGRect(origin: CGPoint(x: 0.0, y: -8.0), size: CGSize(width: availableSize.width, height: 16.0)))
|
||||||
|
|
||||||
@ -201,7 +220,7 @@ public final class EmojiSelectionComponent: Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
self.backspaceBackgroundView.frame = CGRect(origin: CGPoint(), size: backspaceButtonSize).insetBy(dx: -12.0, dy: -12.0)
|
self.backspaceBackgroundView.frame = CGRect(origin: CGPoint(), size: backspaceButtonSize).insetBy(dx: -12.0, dy: -12.0)
|
||||||
let backspaceButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - component.sideInset - backspaceButtonInset.right - backspaceButtonSize.width, y: availableSize.height - component.bottomInset - backspaceButtonInset.bottom), size: backspaceButtonSize)
|
let backspaceButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - component.sideInset - backspaceButtonInset.right - backspaceButtonSize.width, y: resolvedHeight - component.bottomInset - backspaceButtonInset.bottom), size: backspaceButtonSize)
|
||||||
|
|
||||||
if let backspaceButtonView = self.backspaceButton.view {
|
if let backspaceButtonView = self.backspaceButton.view {
|
||||||
if backspaceButtonView.superview == nil {
|
if backspaceButtonView.superview == nil {
|
||||||
@ -220,6 +239,7 @@ public final class EmojiSelectionComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.keyboardView.parentState = state
|
||||||
let keyboardSize = self.keyboardView.update(
|
let keyboardSize = self.keyboardView.update(
|
||||||
transition: transition.withUserData(EmojiPagerContentComponent.SynchronousLoadBehavior(isDisabled: true)),
|
transition: transition.withUserData(EmojiPagerContentComponent.SynchronousLoadBehavior(isDisabled: true)),
|
||||||
component: AnyComponent(EntityKeyboardComponent(
|
component: AnyComponent(EntityKeyboardComponent(
|
||||||
@ -228,8 +248,8 @@ public final class EmojiSelectionComponent: Component {
|
|||||||
isContentInFocus: true,
|
isContentInFocus: true,
|
||||||
containerInsets: UIEdgeInsets(top: topPanelHeight - 34.0, left: component.sideInset, bottom: component.bottomInset + 16.0, right: component.sideInset),
|
containerInsets: UIEdgeInsets(top: topPanelHeight - 34.0, left: component.sideInset, bottom: component.bottomInset + 16.0, right: component.sideInset),
|
||||||
topPanelInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0),
|
topPanelInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0),
|
||||||
emojiContent: component.emojiContent.withCustomTintColor(component.theme.list.itemPrimaryTextColor),
|
emojiContent: component.emojiContent?.withCustomTintColor(component.theme.list.itemPrimaryTextColor),
|
||||||
stickerContent: nil,
|
stickerContent: component.stickerContent?.withCustomTintColor(component.theme.list.itemPrimaryTextColor),
|
||||||
maskContent: nil,
|
maskContent: nil,
|
||||||
gifContent: nil,
|
gifContent: nil,
|
||||||
hasRecentGifs: false,
|
hasRecentGifs: false,
|
||||||
@ -241,7 +261,15 @@ public final class EmojiSelectionComponent: Component {
|
|||||||
topPanelExtensionUpdated: { _, _ in },
|
topPanelExtensionUpdated: { _, _ in },
|
||||||
topPanelScrollingOffset: { _, _ in },
|
topPanelScrollingOffset: { _, _ in },
|
||||||
hideInputUpdated: { _, _, _ in },
|
hideInputUpdated: { _, _, _ in },
|
||||||
hideTopPanelUpdated: { _, _ in },
|
hideTopPanelUpdated: { [weak self] hideTopPanel, transition in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if self.isSearchActive != hideTopPanel {
|
||||||
|
self.isSearchActive = hideTopPanel
|
||||||
|
self.state?.updated(transition: transition)
|
||||||
|
}
|
||||||
|
},
|
||||||
switchToTextInput: {},
|
switchToTextInput: {},
|
||||||
switchToGifSubject: { _ in },
|
switchToGifSubject: { _ in },
|
||||||
reorderItems: { _, _ in },
|
reorderItems: { _, _ in },
|
||||||
@ -257,7 +285,7 @@ public final class EmojiSelectionComponent: Component {
|
|||||||
customTintColor: component.backgroundIconColor
|
customTintColor: component.backgroundIconColor
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: availableSize
|
containerSize: CGSize(width: availableSize.width, height: resolvedHeight)
|
||||||
)
|
)
|
||||||
if let keyboardComponentView = self.keyboardView.view {
|
if let keyboardComponentView = self.keyboardView.view {
|
||||||
if keyboardComponentView.superview == nil {
|
if keyboardComponentView.superview == nil {
|
||||||
@ -270,7 +298,7 @@ public final class EmojiSelectionComponent: Component {
|
|||||||
self.keyboardClippingView.clipsToBounds = false
|
self.keyboardClippingView.clipsToBounds = false
|
||||||
}
|
}
|
||||||
|
|
||||||
transition.setFrame(view: self.keyboardClippingView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight), size: CGSize(width: availableSize.width, height: availableSize.height - topPanelHeight)))
|
transition.setFrame(view: self.keyboardClippingView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight), size: CGSize(width: availableSize.width, height: resolvedHeight - topPanelHeight)))
|
||||||
|
|
||||||
transition.setFrame(view: keyboardComponentView, frame: CGRect(origin: CGPoint(x: 0.0, y: -topPanelHeight), size: keyboardSize))
|
transition.setFrame(view: keyboardComponentView, frame: CGRect(origin: CGPoint(x: 0.0, y: -topPanelHeight), size: keyboardSize))
|
||||||
transition.setFrame(view: self.panelHostView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight - 34.0), size: CGSize(width: keyboardSize.width, height: 0.0)))
|
transition.setFrame(view: self.panelHostView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight - 34.0), size: CGSize(width: keyboardSize.width, height: 0.0)))
|
||||||
@ -282,7 +310,7 @@ public final class EmojiSelectionComponent: Component {
|
|||||||
transition.setAlpha(view: self.panelSeparatorView, alpha: 1.0)
|
transition.setAlpha(view: self.panelSeparatorView, alpha: 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
return availableSize
|
return CGSize(width: availableSize.width, height: resolvedHeight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -884,6 +884,7 @@ final class PeerAllowedReactionsScreenComponent: Component {
|
|||||||
bottomInset: environment.safeInsets.bottom,
|
bottomInset: environment.safeInsets.bottom,
|
||||||
deviceMetrics: environment.deviceMetrics,
|
deviceMetrics: environment.deviceMetrics,
|
||||||
emojiContent: emojiContent.withSelectedItems(Set(enabledReactions.map(\.file.fileId))),
|
emojiContent: emojiContent.withSelectedItems(Set(enabledReactions.map(\.file.fileId))),
|
||||||
|
stickerContent: nil,
|
||||||
backgroundIconColor: nil,
|
backgroundIconColor: nil,
|
||||||
backgroundColor: environment.theme.list.itemBlocksBackgroundColor,
|
backgroundColor: environment.theme.list.itemBlocksBackgroundColor,
|
||||||
separatorColor: environment.theme.list.itemBlocksSeparatorColor,
|
separatorColor: environment.theme.list.itemBlocksSeparatorColor,
|
||||||
|
|||||||
@ -51,6 +51,23 @@ final class BusinessIntroSetupScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct EmojiSearchResult {
|
||||||
|
var groups: [EmojiPagerContentComponent.ItemGroup]
|
||||||
|
var id: AnyHashable
|
||||||
|
var version: Int
|
||||||
|
var isPreset: Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct EmojiSearchState {
|
||||||
|
var result: EmojiSearchResult?
|
||||||
|
var isSearching: Bool
|
||||||
|
|
||||||
|
init(result: EmojiSearchResult?, isSearching: Bool) {
|
||||||
|
self.result = result
|
||||||
|
self.isSearching = isSearching
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final class View: UIView, UIScrollViewDelegate {
|
final class View: UIView, UIScrollViewDelegate {
|
||||||
private let topOverscrollLayer = SimpleLayer()
|
private let topOverscrollLayer = SimpleLayer()
|
||||||
private let scrollView: ScrollView
|
private let scrollView: ScrollView
|
||||||
@ -74,12 +91,18 @@ final class BusinessIntroSetupScreenComponent: Component {
|
|||||||
private let textInputTag = NSObject()
|
private let textInputTag = NSObject()
|
||||||
private var resetText: String?
|
private var resetText: String?
|
||||||
|
|
||||||
|
private var previousHadInputHeight: Bool = false
|
||||||
private var recenterOnTag: NSObject?
|
private var recenterOnTag: NSObject?
|
||||||
|
|
||||||
private var stickerFile: TelegramMediaFile?
|
private var stickerFile: TelegramMediaFile?
|
||||||
|
|
||||||
private var stickerContent: EmojiPagerContentComponent?
|
private var stickerContent: EmojiPagerContentComponent?
|
||||||
private var stickerContentDisposable: Disposable?
|
private var stickerContentDisposable: Disposable?
|
||||||
|
private let stickerSearchDisposable = MetaDisposable()
|
||||||
|
private var stickerSearchState = EmojiSearchState(result: nil, isSearching: false)
|
||||||
|
|
||||||
private var displayStickerInput: Bool = false
|
private var displayStickerInput: Bool = false
|
||||||
|
private var stickerSelectionControlDimView: UIView?
|
||||||
private var stickerSelectionControl: ComponentView<Empty>?
|
private var stickerSelectionControl: ComponentView<Empty>?
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
@ -171,6 +194,13 @@ final class BusinessIntroSetupScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func stickerSelectionControlDimTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||||
|
if case .ended = recognizer.state {
|
||||||
|
self.displayStickerInput = false
|
||||||
|
self.state?.updated(transition: .spring(duration: 0.4))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func update(component: BusinessIntroSetupScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
func update(component: BusinessIntroSetupScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||||
self.isUpdating = true
|
self.isUpdating = true
|
||||||
defer {
|
defer {
|
||||||
@ -193,9 +223,10 @@ final class BusinessIntroSetupScreenComponent: Component {
|
|||||||
stickerNamespaces: [Namespaces.ItemCollection.CloudStickerPacks],
|
stickerNamespaces: [Namespaces.ItemCollection.CloudStickerPacks],
|
||||||
stickerOrderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.CloudAllPremiumStickers],
|
stickerOrderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.CloudAllPremiumStickers],
|
||||||
chatPeerId: nil,
|
chatPeerId: nil,
|
||||||
hasSearch: false,
|
hasSearch: true,
|
||||||
hasTrending: false,
|
hasTrending: false,
|
||||||
forceHasPremium: true
|
forceHasPremium: true,
|
||||||
|
searchIsPlaceholderOnly: false
|
||||||
)
|
)
|
||||||
self.stickerContentDisposable = (stickerContent
|
self.stickerContentDisposable = (stickerContent
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] stickerContent in
|
|> deliverOnMainQueue).start(next: { [weak self] stickerContent in
|
||||||
@ -216,16 +247,16 @@ final class BusinessIntroSetupScreenComponent: Component {
|
|||||||
self.stickerFile = itemFile
|
self.stickerFile = itemFile
|
||||||
self.displayStickerInput = false
|
self.displayStickerInput = false
|
||||||
|
|
||||||
|
self.stickerSearchDisposable.set(nil)
|
||||||
|
self.stickerSearchState = EmojiSearchState(result: nil, isSearching: false)
|
||||||
|
|
||||||
if !self.isUpdating {
|
if !self.isUpdating {
|
||||||
self.state?.updated(transition: .spring(duration: 0.25))
|
self.state?.updated(transition: .spring(duration: 0.4))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
deleteBackwards: {
|
deleteBackwards: nil,
|
||||||
},
|
openStickerSettings: nil,
|
||||||
openStickerSettings: {
|
openFeatured: nil,
|
||||||
},
|
|
||||||
openFeatured: {
|
|
||||||
},
|
|
||||||
openSearch: {
|
openSearch: {
|
||||||
},
|
},
|
||||||
addGroupAction: { _, _, _ in
|
addGroupAction: { _, _, _ in
|
||||||
@ -243,9 +274,202 @@ final class BusinessIntroSetupScreenComponent: Component {
|
|||||||
navigationController: {
|
navigationController: {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
requestUpdate: { _ in
|
requestUpdate: { [weak self] transition in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let stickerSelectionControlView = self.stickerSelectionControl?.view as? EmojiSelectionComponent.View {
|
||||||
|
stickerSelectionControlView.internalRequestUpdate(transition: transition)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
updateSearchQuery: { _ in
|
updateSearchQuery: { [weak self] query in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch query {
|
||||||
|
case .none:
|
||||||
|
self.stickerSearchDisposable.set(nil)
|
||||||
|
self.stickerSearchState = EmojiSearchState(result: nil, isSearching: false)
|
||||||
|
if !self.isUpdating {
|
||||||
|
self.state?.updated(transition: .immediate)
|
||||||
|
}
|
||||||
|
case let .text(rawQuery, _):
|
||||||
|
let query = rawQuery.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
|
||||||
|
if query.isEmpty {
|
||||||
|
self.stickerSearchDisposable.set(nil)
|
||||||
|
self.stickerSearchState = EmojiSearchState(result: nil, isSearching: false)
|
||||||
|
self.state?.updated(transition: .immediate)
|
||||||
|
} else {
|
||||||
|
let context = component.context
|
||||||
|
|
||||||
|
let localSets = context.engine.stickers.searchStickerSets(query: query)
|
||||||
|
let remoteSets: Signal<FoundStickerSets?, NoError> = .single(nil) |> then(
|
||||||
|
context.engine.stickers.searchStickerSetsRemotely(query: query)
|
||||||
|
|> map(Optional.init)
|
||||||
|
)
|
||||||
|
|
||||||
|
let resultSignal = combineLatest(
|
||||||
|
localSets,
|
||||||
|
remoteSets
|
||||||
|
)
|
||||||
|
|> mapToSignal { localSets, remoteSets -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in
|
||||||
|
if localSets.infos.isEmpty && remoteSets == nil {
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
var items: [EmojiPagerContentComponent.Item] = []
|
||||||
|
|
||||||
|
var mergedSets = localSets
|
||||||
|
if let remoteSets {
|
||||||
|
mergedSets = mergedSets.merge(with: remoteSets)
|
||||||
|
}
|
||||||
|
|
||||||
|
var existingIds = Set<MediaId>()
|
||||||
|
for entry in mergedSets.entries {
|
||||||
|
guard let stickerPackItem = entry.item as? StickerPackItem else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let itemFile = stickerPackItem.file
|
||||||
|
|
||||||
|
if existingIds.contains(itemFile.fileId) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
existingIds.insert(itemFile.fileId)
|
||||||
|
|
||||||
|
let animationData = EntityKeyboardAnimationData(file: itemFile)
|
||||||
|
let item = EmojiPagerContentComponent.Item(
|
||||||
|
animationData: animationData,
|
||||||
|
content: .animation(animationData),
|
||||||
|
itemFile: itemFile,
|
||||||
|
subgroupId: nil,
|
||||||
|
icon: .none,
|
||||||
|
tintMode: animationData.isTemplate ? .primary : .none
|
||||||
|
)
|
||||||
|
items.append(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return .single([EmojiPagerContentComponent.ItemGroup(
|
||||||
|
supergroupId: "search",
|
||||||
|
groupId: "search",
|
||||||
|
title: nil,
|
||||||
|
subtitle: nil,
|
||||||
|
badge: nil,
|
||||||
|
actionButtonTitle: nil,
|
||||||
|
isFeatured: false,
|
||||||
|
isPremiumLocked: false,
|
||||||
|
isEmbedded: false,
|
||||||
|
hasClear: false,
|
||||||
|
hasEdit: false,
|
||||||
|
collapsedLineCount: nil,
|
||||||
|
displayPremiumBadges: false,
|
||||||
|
headerItem: nil,
|
||||||
|
fillWithLoadingPlaceholders: false,
|
||||||
|
items: items
|
||||||
|
)])
|
||||||
|
}
|
||||||
|
|
||||||
|
var version = 0
|
||||||
|
self.stickerSearchState.isSearching = true
|
||||||
|
self.state?.updated(transition: .immediate)
|
||||||
|
|
||||||
|
self.stickerSearchDisposable.set((resultSignal
|
||||||
|
|> delay(0.15, queue: .mainQueue())
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.stickerSearchState = EmojiSearchState(result: EmojiSearchResult(groups: result, id: AnyHashable(query), version: version, isPreset: false), isSearching: false)
|
||||||
|
version += 1
|
||||||
|
self.state?.updated(transition: .immediate)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
case let .category(value):
|
||||||
|
let resultSignal = component.context.engine.stickers.searchStickers(query: value, scope: [.installed, .remote])
|
||||||
|
|> mapToSignal { files -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in
|
||||||
|
var items: [EmojiPagerContentComponent.Item] = []
|
||||||
|
|
||||||
|
var existingIds = Set<MediaId>()
|
||||||
|
for item in files.items {
|
||||||
|
let itemFile = item.file
|
||||||
|
if existingIds.contains(itemFile.fileId) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
existingIds.insert(itemFile.fileId)
|
||||||
|
let animationData = EntityKeyboardAnimationData(file: itemFile)
|
||||||
|
let item = EmojiPagerContentComponent.Item(
|
||||||
|
animationData: animationData,
|
||||||
|
content: .animation(animationData),
|
||||||
|
itemFile: itemFile, subgroupId: nil,
|
||||||
|
icon: itemFile.isPremiumSticker ? .premium : .none,
|
||||||
|
tintMode: animationData.isTemplate ? .primary : .none
|
||||||
|
)
|
||||||
|
items.append(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return .single(([EmojiPagerContentComponent.ItemGroup(
|
||||||
|
supergroupId: "search",
|
||||||
|
groupId: "search",
|
||||||
|
title: nil,
|
||||||
|
subtitle: nil,
|
||||||
|
badge: nil,
|
||||||
|
actionButtonTitle: nil,
|
||||||
|
isFeatured: false,
|
||||||
|
isPremiumLocked: false,
|
||||||
|
isEmbedded: false,
|
||||||
|
hasClear: false,
|
||||||
|
hasEdit: false,
|
||||||
|
collapsedLineCount: nil,
|
||||||
|
displayPremiumBadges: false,
|
||||||
|
headerItem: nil,
|
||||||
|
fillWithLoadingPlaceholders: false,
|
||||||
|
items: items
|
||||||
|
)], files.isFinalResult))
|
||||||
|
}
|
||||||
|
|
||||||
|
var version = 0
|
||||||
|
self.stickerSearchDisposable.set((resultSignal
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let group = result.items.first else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if group.items.isEmpty && !result.isFinalResult {
|
||||||
|
self.stickerSearchState = EmojiSearchState(result: EmojiSearchResult(groups: [
|
||||||
|
EmojiPagerContentComponent.ItemGroup(
|
||||||
|
supergroupId: "search",
|
||||||
|
groupId: "search",
|
||||||
|
title: nil,
|
||||||
|
subtitle: nil,
|
||||||
|
badge: nil,
|
||||||
|
actionButtonTitle: nil,
|
||||||
|
isFeatured: false,
|
||||||
|
isPremiumLocked: false,
|
||||||
|
isEmbedded: false,
|
||||||
|
hasClear: false,
|
||||||
|
hasEdit: false,
|
||||||
|
collapsedLineCount: nil,
|
||||||
|
displayPremiumBadges: false,
|
||||||
|
headerItem: nil,
|
||||||
|
fillWithLoadingPlaceholders: true,
|
||||||
|
items: []
|
||||||
|
)
|
||||||
|
], id: AnyHashable(value), version: version, isPreset: true), isSearching: false)
|
||||||
|
if !self.isUpdating {
|
||||||
|
self.state?.updated(transition: .immediate)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.stickerSearchState = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value), version: version, isPreset: true), isSearching: false)
|
||||||
|
version += 1
|
||||||
|
if !self.isUpdating {
|
||||||
|
self.state?.updated(transition: .immediate)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
updateScrollingToItemGroup: {
|
updateScrollingToItemGroup: {
|
||||||
},
|
},
|
||||||
@ -349,6 +573,7 @@ final class BusinessIntroSetupScreenComponent: Component {
|
|||||||
autocapitalizationType: .none,
|
autocapitalizationType: .none,
|
||||||
autocorrectionType: .no,
|
autocorrectionType: .no,
|
||||||
characterLimit: 32,
|
characterLimit: 32,
|
||||||
|
displayCharacterLimit: true,
|
||||||
allowEmptyLines: false,
|
allowEmptyLines: false,
|
||||||
updated: { _ in
|
updated: { _ in
|
||||||
},
|
},
|
||||||
@ -369,6 +594,7 @@ final class BusinessIntroSetupScreenComponent: Component {
|
|||||||
autocapitalizationType: .none,
|
autocapitalizationType: .none,
|
||||||
autocorrectionType: .no,
|
autocorrectionType: .no,
|
||||||
characterLimit: 70,
|
characterLimit: 70,
|
||||||
|
displayCharacterLimit: true,
|
||||||
allowEmptyLines: false,
|
allowEmptyLines: false,
|
||||||
updated: { _ in
|
updated: { _ in
|
||||||
},
|
},
|
||||||
@ -416,6 +642,8 @@ final class BusinessIntroSetupScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.displayStickerInput = true
|
self.displayStickerInput = true
|
||||||
|
self.endEditing(true)
|
||||||
|
|
||||||
if !self.isUpdating {
|
if !self.isUpdating {
|
||||||
self.state?.updated(transition: .spring(duration: 0.5))
|
self.state?.updated(transition: .spring(duration: 0.5))
|
||||||
}
|
}
|
||||||
@ -494,6 +722,15 @@ final class BusinessIntroSetupScreenComponent: Component {
|
|||||||
transition.setFrame(view: introContentView, frame: CGRect(origin: CGPoint(), size: introContentSize))
|
transition.setFrame(view: introContentView, frame: CGRect(origin: CGPoint(), size: introContentSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.recenterOnTag == nil && self.previousHadInputHeight != (environment.inputHeight > 0.0) {
|
||||||
|
if self.titleInputState.isEditing {
|
||||||
|
self.recenterOnTag = self.titleInputTag
|
||||||
|
} else if self.textInputState.isEditing {
|
||||||
|
self.recenterOnTag = self.textInputTag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.previousHadInputHeight = environment.inputHeight > 0.0
|
||||||
|
|
||||||
let displayDelete = !self.titleInputState.text.string.isEmpty || !self.textInputState.text.string.isEmpty || self.stickerFile != nil
|
let displayDelete = !self.titleInputState.text.string.isEmpty || !self.textInputState.text.string.isEmpty || self.stickerFile != nil
|
||||||
|
|
||||||
var deleteSectionHeight: CGFloat = 0.0
|
var deleteSectionHeight: CGFloat = 0.0
|
||||||
@ -557,6 +794,16 @@ final class BusinessIntroSetupScreenComponent: Component {
|
|||||||
|
|
||||||
var inputHeight: CGFloat = environment.inputHeight
|
var inputHeight: CGFloat = environment.inputHeight
|
||||||
if self.displayStickerInput, let stickerContent = self.stickerContent {
|
if self.displayStickerInput, let stickerContent = self.stickerContent {
|
||||||
|
let stickerSelectionControlDimView: UIView
|
||||||
|
if let current = self.stickerSelectionControlDimView {
|
||||||
|
stickerSelectionControlDimView = current
|
||||||
|
} else {
|
||||||
|
stickerSelectionControlDimView = UIView()
|
||||||
|
self.stickerSelectionControlDimView = stickerSelectionControlDimView
|
||||||
|
self.addSubview(stickerSelectionControlDimView)
|
||||||
|
stickerSelectionControlDimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.stickerSelectionControlDimTapGesture(_:))))
|
||||||
|
}
|
||||||
|
|
||||||
let stickerSelectionControl: ComponentView<Empty>
|
let stickerSelectionControl: ComponentView<Empty>
|
||||||
var animateIn = false
|
var animateIn = false
|
||||||
if let current = self.stickerSelectionControl {
|
if let current = self.stickerSelectionControl {
|
||||||
@ -570,22 +817,45 @@ final class BusinessIntroSetupScreenComponent: Component {
|
|||||||
if let stickerFile = self.stickerFile {
|
if let stickerFile = self.stickerFile {
|
||||||
selectedItems.insert(stickerFile.fileId)
|
selectedItems.insert(stickerFile.fileId)
|
||||||
}
|
}
|
||||||
|
stickerSelectionControl.parentState = state
|
||||||
|
|
||||||
|
var stickerContent = stickerContent
|
||||||
|
|
||||||
|
if let stickerSearchResult = self.stickerSearchState.result {
|
||||||
|
var stickerSearchResults: EmojiPagerContentComponent.EmptySearchResults?
|
||||||
|
if !stickerSearchResult.groups.contains(where: { !$0.items.isEmpty || $0.fillWithLoadingPlaceholders }) {
|
||||||
|
stickerSearchResults = EmojiPagerContentComponent.EmptySearchResults(
|
||||||
|
text: environment.strings.EmojiSearch_SearchStickersEmptyResult,
|
||||||
|
iconFile: nil
|
||||||
|
)
|
||||||
|
}
|
||||||
|
let defaultSearchState: EmojiPagerContentComponent.SearchState = stickerSearchResult.isPreset ? .active : .empty(hasResults: true)
|
||||||
|
stickerContent = stickerContent.withUpdatedItemGroups(panelItemGroups: stickerContent.panelItemGroups, contentItemGroups: stickerSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: stickerSearchResult.id, version: stickerSearchResult.version), emptySearchResults: stickerSearchResults, searchState: self.stickerSearchState.isSearching ? .searching : defaultSearchState)
|
||||||
|
} else if self.stickerSearchState.isSearching {
|
||||||
|
stickerContent = stickerContent.withUpdatedItemGroups(panelItemGroups: stickerContent.panelItemGroups, contentItemGroups: stickerContent.contentItemGroups, itemContentUniqueId: stickerContent.itemContentUniqueId, emptySearchResults: stickerContent.emptySearchResults, searchState: .searching)
|
||||||
|
}
|
||||||
|
|
||||||
|
let stickerSelectionControlTransition = animateIn ? .immediate : transition
|
||||||
|
|
||||||
|
stickerSelectionControlTransition.setFrame(view: stickerSelectionControlDimView, frame: CGRect(origin: CGPoint(x: 0.0, y: environment.navigationHeight), size: CGSize(width: availableSize.width, height: availableSize.height - environment.navigationHeight)))
|
||||||
|
|
||||||
let stickerSelectionControlSize = stickerSelectionControl.update(
|
let stickerSelectionControlSize = stickerSelectionControl.update(
|
||||||
transition: animateIn ? .immediate : transition,
|
transition: stickerSelectionControlTransition,
|
||||||
component: AnyComponent(EmojiSelectionComponent(
|
component: AnyComponent(EmojiSelectionComponent(
|
||||||
theme: environment.theme,
|
theme: environment.theme,
|
||||||
strings: environment.strings,
|
strings: environment.strings,
|
||||||
sideInset: environment.safeInsets.left,
|
sideInset: environment.safeInsets.left,
|
||||||
bottomInset: environment.safeInsets.bottom,
|
bottomInset: environment.safeInsets.bottom,
|
||||||
deviceMetrics: environment.deviceMetrics,
|
deviceMetrics: environment.deviceMetrics,
|
||||||
emojiContent: stickerContent.withSelectedItems(selectedItems),
|
emojiContent: nil,
|
||||||
|
stickerContent: stickerContent.withSelectedItems(selectedItems),
|
||||||
backgroundIconColor: nil,
|
backgroundIconColor: nil,
|
||||||
backgroundColor: environment.theme.list.itemBlocksBackgroundColor,
|
backgroundColor: environment.theme.list.itemBlocksBackgroundColor,
|
||||||
separatorColor: environment.theme.list.itemBlocksSeparatorColor,
|
separatorColor: environment.theme.list.itemBlocksSeparatorColor,
|
||||||
backspace: nil
|
backspace: nil
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: availableSize.width, height: min(340.0, max(50.0, availableSize.height - 200.0)))
|
containerSize: CGSize(width: availableSize.width, height: availableSize.height - environment.navigationHeight)
|
||||||
)
|
)
|
||||||
let stickerSelectionControlFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - stickerSelectionControlSize.height), size: stickerSelectionControlSize)
|
let stickerSelectionControlFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - stickerSelectionControlSize.height), size: stickerSelectionControlSize)
|
||||||
if let stickerSelectionControlView = stickerSelectionControl.view {
|
if let stickerSelectionControlView = stickerSelectionControl.view {
|
||||||
@ -600,7 +870,8 @@ final class BusinessIntroSetupScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
inputHeight = stickerSelectionControlSize.height
|
inputHeight = stickerSelectionControlSize.height
|
||||||
} else if let stickerSelectionControl = self.stickerSelectionControl {
|
} else {
|
||||||
|
if let stickerSelectionControl = self.stickerSelectionControl {
|
||||||
self.stickerSelectionControl = nil
|
self.stickerSelectionControl = nil
|
||||||
if let stickerSelectionControlView = stickerSelectionControl.view {
|
if let stickerSelectionControlView = stickerSelectionControl.view {
|
||||||
transition.setPosition(view: stickerSelectionControlView, position: CGPoint(x: stickerSelectionControlView.center.x, y: availableSize.height + stickerSelectionControlView.bounds.height * 0.5), completion: { [weak stickerSelectionControlView] _ in
|
transition.setPosition(view: stickerSelectionControlView, position: CGPoint(x: stickerSelectionControlView.center.x, y: availableSize.height + stickerSelectionControlView.bounds.height * 0.5), completion: { [weak stickerSelectionControlView] _ in
|
||||||
@ -608,6 +879,11 @@ final class BusinessIntroSetupScreenComponent: Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let stickerSelectionControlDimView = self.stickerSelectionControlDimView {
|
||||||
|
self.stickerSelectionControlDimView = nil
|
||||||
|
stickerSelectionControlDimView.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let combinedBottomInset = max(inputHeight, environment.safeInsets.bottom)
|
let combinedBottomInset = max(inputHeight, environment.safeInsets.bottom)
|
||||||
contentHeight += combinedBottomInset
|
contentHeight += combinedBottomInset
|
||||||
|
|||||||
@ -257,9 +257,15 @@ final class ChatbotSetupScreenComponent: Component {
|
|||||||
if !query.isEmpty {
|
if !query.isEmpty {
|
||||||
if self.botResolutionState?.query != query {
|
if self.botResolutionState?.query != query {
|
||||||
let previousState = self.botResolutionState?.state
|
let previousState = self.botResolutionState?.state
|
||||||
|
let updatedState: BotResolutionState.State
|
||||||
|
if let current = self.botResolutionState?.state, case .found = current {
|
||||||
|
updatedState = current
|
||||||
|
} else {
|
||||||
|
updatedState = .searching
|
||||||
|
}
|
||||||
self.botResolutionState = BotResolutionState(
|
self.botResolutionState = BotResolutionState(
|
||||||
query: query,
|
query: query,
|
||||||
state: self.botResolutionState?.state ?? .searching
|
state: updatedState
|
||||||
)
|
)
|
||||||
self.botResolutionDisposable?.dispose()
|
self.botResolutionDisposable?.dispose()
|
||||||
|
|
||||||
@ -267,7 +273,19 @@ final class ChatbotSetupScreenComponent: Component {
|
|||||||
self.state?.updated(transition: .spring(duration: 0.35))
|
self.state?.updated(transition: .spring(duration: 0.35))
|
||||||
}
|
}
|
||||||
|
|
||||||
self.botResolutionDisposable = (component.context.engine.peers.resolvePeerByName(name: query)
|
var cleanQuery = query
|
||||||
|
if let url = URL(string: cleanQuery), url.host == "t.me" {
|
||||||
|
if url.pathComponents.count > 1 {
|
||||||
|
cleanQuery = url.pathComponents[1]
|
||||||
|
}
|
||||||
|
} else if let url = URL(string: "https://\(cleanQuery)"), url.host == "t.me" {
|
||||||
|
if url.pathComponents.count > 1 {
|
||||||
|
cleanQuery = url.pathComponents[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.botResolutionDisposable = (component.context.engine.peers.resolvePeerByName(name: cleanQuery)
|
||||||
|
|> delay(0.4, queue: .mainQueue())
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
@ -661,10 +679,20 @@ final class ChatbotSetupScreenComponent: Component {
|
|||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
self.endEditing(true)
|
||||||
|
|
||||||
if var botResolutionState = self.botResolutionState, case let .found(peer, isInstalled) = botResolutionState.state, !isInstalled {
|
if var botResolutionState = self.botResolutionState, case let .found(peer, isInstalled) = botResolutionState.state, !isInstalled {
|
||||||
|
if case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.isBusiness) {
|
||||||
botResolutionState.state = .found(peer: peer, isInstalled: true)
|
botResolutionState.state = .found(peer: peer, isInstalled: true)
|
||||||
self.botResolutionState = botResolutionState
|
self.botResolutionState = botResolutionState
|
||||||
self.state?.updated(transition: .spring(duration: 0.3))
|
self.state?.updated(transition: .spring(duration: 0.3))
|
||||||
|
} else {
|
||||||
|
//TODO:localize
|
||||||
|
self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: "This bot doesn't support Telegram Business yet.", actions: [
|
||||||
|
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
|
||||||
|
})
|
||||||
|
]), in: .window(.root))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
removeAction: { [weak self] in
|
removeAction: { [weak self] in
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user