mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
[WIP] Premium setup
This commit is contained in:
parent
ae3ee3d063
commit
d8026b4009
@ -856,6 +856,9 @@ public protocol AutomaticBusinessMessageSetupScreenInitialData: AnyObject {
|
|||||||
public protocol ChatbotSetupScreenInitialData: AnyObject {
|
public protocol ChatbotSetupScreenInitialData: AnyObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public protocol BusinessIntroSetupScreenInitialData: AnyObject {
|
||||||
|
}
|
||||||
|
|
||||||
public protocol CollectibleItemInfoScreenInitialData: AnyObject {
|
public protocol CollectibleItemInfoScreenInitialData: AnyObject {
|
||||||
var collectibleItemInfo: TelegramCollectibleItemInfo { get }
|
var collectibleItemInfo: TelegramCollectibleItemInfo { get }
|
||||||
}
|
}
|
||||||
@ -960,6 +963,7 @@ public protocol SharedAccountContext: AnyObject {
|
|||||||
func makeAutomaticBusinessMessageSetupScreenInitialData(context: AccountContext) -> Signal<AutomaticBusinessMessageSetupScreenInitialData, NoError>
|
func makeAutomaticBusinessMessageSetupScreenInitialData(context: AccountContext) -> Signal<AutomaticBusinessMessageSetupScreenInitialData, NoError>
|
||||||
func makeQuickReplySetupScreen(context: AccountContext, initialData: QuickReplySetupScreenInitialData) -> ViewController
|
func makeQuickReplySetupScreen(context: AccountContext, initialData: QuickReplySetupScreenInitialData) -> ViewController
|
||||||
func makeQuickReplySetupScreenInitialData(context: AccountContext) -> Signal<QuickReplySetupScreenInitialData, NoError>
|
func makeQuickReplySetupScreenInitialData(context: AccountContext) -> Signal<QuickReplySetupScreenInitialData, NoError>
|
||||||
|
func makeBusinessIntroSetupScreen(context: AccountContext) -> ViewController
|
||||||
func makeCollectibleItemInfoScreen(context: AccountContext, initialData: CollectibleItemInfoScreenInitialData) -> ViewController
|
func makeCollectibleItemInfoScreen(context: AccountContext, initialData: CollectibleItemInfoScreenInitialData) -> ViewController
|
||||||
func makeCollectibleItemInfoScreenInitialData(context: AccountContext, peerId: EnginePeer.Id, subject: CollectibleItemInfoScreenSubject) -> Signal<CollectibleItemInfoScreenInitialData?, NoError>
|
func makeCollectibleItemInfoScreenInitialData(context: AccountContext, peerId: EnginePeer.Id, subject: CollectibleItemInfoScreenSubject) -> Signal<CollectibleItemInfoScreenInitialData?, NoError>
|
||||||
func navigateToChatController(_ params: NavigateToChatControllerParams)
|
func navigateToChatController(_ params: NavigateToChatControllerParams)
|
||||||
|
@ -5,11 +5,13 @@ public final class Rectangle: Component {
|
|||||||
private let color: UIColor
|
private let color: UIColor
|
||||||
private let width: CGFloat?
|
private let width: CGFloat?
|
||||||
private let height: CGFloat?
|
private let height: CGFloat?
|
||||||
|
private let tag: NSObject?
|
||||||
|
|
||||||
public init(color: UIColor, width: CGFloat? = nil, height: CGFloat? = nil) {
|
public init(color: UIColor, width: CGFloat? = nil, height: CGFloat? = nil, tag: NSObject? = nil) {
|
||||||
self.color = color
|
self.color = color
|
||||||
self.width = width
|
self.width = width
|
||||||
self.height = height
|
self.height = height
|
||||||
|
self.tag = tag
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: Rectangle, rhs: Rectangle) -> Bool {
|
public static func ==(lhs: Rectangle, rhs: Rectangle) -> Bool {
|
||||||
@ -25,7 +27,33 @@ public final class Rectangle: Component {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update(view: UIView, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
public final class View: UIView, ComponentTaggedView {
|
||||||
|
fileprivate var componentTag: NSObject?
|
||||||
|
|
||||||
|
override public init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
required public init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
public func matches(tag: Any) -> Bool {
|
||||||
|
if let componentTag = self.componentTag {
|
||||||
|
let tag = tag as AnyObject
|
||||||
|
if componentTag === tag {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func makeView() -> View {
|
||||||
|
return View(frame: CGRect())
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
var size = availableSize
|
var size = availableSize
|
||||||
if let width = self.width {
|
if let width = self.width {
|
||||||
size.width = min(size.width, width)
|
size.width = min(size.width, width)
|
||||||
@ -35,6 +63,7 @@ public final class Rectangle: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
view.backgroundColor = self.color
|
view.backgroundColor = self.color
|
||||||
|
view.componentTag = self.tag
|
||||||
|
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
|
@ -13,11 +13,13 @@ public final class VStack<ChildEnvironment: Equatable>: CombinedComponent {
|
|||||||
private let items: [AnyComponentWithIdentity<ChildEnvironment>]
|
private let items: [AnyComponentWithIdentity<ChildEnvironment>]
|
||||||
private let alignment: VStackAlignment
|
private let alignment: VStackAlignment
|
||||||
private let spacing: CGFloat
|
private let spacing: CGFloat
|
||||||
|
private let fillWidth: Bool
|
||||||
|
|
||||||
public init(_ items: [AnyComponentWithIdentity<ChildEnvironment>], alignment: VStackAlignment = .center, spacing: CGFloat) {
|
public init(_ items: [AnyComponentWithIdentity<ChildEnvironment>], alignment: VStackAlignment = .center, spacing: CGFloat, fillWidth: Bool = false) {
|
||||||
self.items = items
|
self.items = items
|
||||||
self.alignment = alignment
|
self.alignment = alignment
|
||||||
self.spacing = spacing
|
self.spacing = spacing
|
||||||
|
self.fillWidth = fillWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: VStack<ChildEnvironment>, rhs: VStack<ChildEnvironment>) -> Bool {
|
public static func ==(lhs: VStack<ChildEnvironment>, rhs: VStack<ChildEnvironment>) -> Bool {
|
||||||
@ -30,6 +32,9 @@ public final class VStack<ChildEnvironment: Equatable>: CombinedComponent {
|
|||||||
if lhs.spacing != rhs.spacing {
|
if lhs.spacing != rhs.spacing {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.fillWidth != rhs.fillWidth {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,6 +53,9 @@ public final class VStack<ChildEnvironment: Equatable>: CombinedComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var size = CGSize(width: 0.0, height: 0.0)
|
var size = CGSize(width: 0.0, height: 0.0)
|
||||||
|
if context.component.fillWidth {
|
||||||
|
size.width = context.availableSize.width
|
||||||
|
}
|
||||||
for child in updatedChildren {
|
for child in updatedChildren {
|
||||||
size.height += child.size.height
|
size.height += child.size.height
|
||||||
size.width = max(size.width, child.size.width)
|
size.width = max(size.width, child.size.width)
|
||||||
|
@ -117,6 +117,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/EmojiStatusSelectionComponent",
|
"//submodules/TelegramUI/Components/EmojiStatusSelectionComponent",
|
||||||
"//submodules/TelegramUI/Components/EntityKeyboard",
|
"//submodules/TelegramUI/Components/EntityKeyboard",
|
||||||
"//submodules/TelegramUI/Components/PremiumPeerShortcutComponent",
|
"//submodules/TelegramUI/Components/PremiumPeerShortcutComponent",
|
||||||
|
"//submodules/TelegramUI/Components/EmojiActionIconComponent",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -32,6 +32,7 @@ import ListActionItemComponent
|
|||||||
import EmojiStatusSelectionComponent
|
import EmojiStatusSelectionComponent
|
||||||
import EmojiStatusComponent
|
import EmojiStatusComponent
|
||||||
import EntityKeyboard
|
import EntityKeyboard
|
||||||
|
import EmojiActionIconComponent
|
||||||
|
|
||||||
public enum PremiumSource: Equatable {
|
public enum PremiumSource: Equatable {
|
||||||
public static func == (lhs: PremiumSource, rhs: PremiumSource) -> Bool {
|
public static func == (lhs: PremiumSource, rhs: PremiumSource) -> Bool {
|
||||||
@ -2241,6 +2242,8 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
}
|
}
|
||||||
push(accountContext.sharedContext.makeChatbotSetupScreen(context: accountContext, initialData: initialData))
|
push(accountContext.sharedContext.makeChatbotSetupScreen(context: accountContext, initialData: initialData))
|
||||||
})
|
})
|
||||||
|
case .businessIntro:
|
||||||
|
push(accountContext.sharedContext.makeBusinessIntroSetupScreen(context: accountContext))
|
||||||
default:
|
default:
|
||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
@ -3715,89 +3718,6 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private final class EmojiActionIconComponent: Component {
|
|
||||||
let context: AccountContext
|
|
||||||
let color: UIColor
|
|
||||||
let fileId: Int64?
|
|
||||||
let file: TelegramMediaFile?
|
|
||||||
|
|
||||||
init(
|
|
||||||
context: AccountContext,
|
|
||||||
color: UIColor,
|
|
||||||
fileId: Int64?,
|
|
||||||
file: TelegramMediaFile?
|
|
||||||
) {
|
|
||||||
self.context = context
|
|
||||||
self.color = color
|
|
||||||
self.fileId = fileId
|
|
||||||
self.file = file
|
|
||||||
}
|
|
||||||
|
|
||||||
static func ==(lhs: EmojiActionIconComponent, rhs: EmojiActionIconComponent) -> Bool {
|
|
||||||
if lhs.context !== rhs.context {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.color != rhs.color {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.fileId != rhs.fileId {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.file != rhs.file {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
final class View: UIView {
|
|
||||||
private let icon = ComponentView<Empty>()
|
|
||||||
|
|
||||||
func update(component: EmojiActionIconComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
|
||||||
let size = CGSize(width: 24.0, height: 24.0)
|
|
||||||
|
|
||||||
let _ = self.icon.update(
|
|
||||||
transition: .immediate,
|
|
||||||
component: AnyComponent(EmojiStatusComponent(
|
|
||||||
context: component.context,
|
|
||||||
animationCache: component.context.animationCache,
|
|
||||||
animationRenderer: component.context.animationRenderer,
|
|
||||||
content: component.fileId.flatMap { .animation(
|
|
||||||
content: .customEmoji(fileId: $0),
|
|
||||||
size: CGSize(width: size.width * 2.0, height: size.height * 2.0),
|
|
||||||
placeholderColor: .lightGray,
|
|
||||||
themeColor: component.color,
|
|
||||||
loopMode: .forever
|
|
||||||
) } ?? .premium(color: component.color),
|
|
||||||
isVisibleForAnimations: false,
|
|
||||||
action: nil
|
|
||||||
)),
|
|
||||||
environment: {},
|
|
||||||
containerSize: size
|
|
||||||
)
|
|
||||||
let iconFrame = CGRect(origin: CGPoint(), size: size)
|
|
||||||
if let iconView = self.icon.view {
|
|
||||||
if iconView.superview == nil {
|
|
||||||
self.addSubview(iconView)
|
|
||||||
}
|
|
||||||
iconView.frame = iconFrame
|
|
||||||
}
|
|
||||||
|
|
||||||
return size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeView() -> View {
|
|
||||||
return View(frame: CGRect())
|
|
||||||
}
|
|
||||||
|
|
||||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
|
||||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class BadgeComponent: CombinedComponent {
|
private final class BadgeComponent: CombinedComponent {
|
||||||
let color: UIColor
|
let color: UIColor
|
||||||
let text: String
|
let text: String
|
||||||
|
@ -909,6 +909,34 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present
|
|||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func generatePremiumCategoryIcon(size: CGSize, cornerRadius: CGFloat) -> UIImage {
|
||||||
|
return generateImage(size, contextGenerator: { size, context in
|
||||||
|
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||||
|
context.clear(bounds)
|
||||||
|
|
||||||
|
let path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius)
|
||||||
|
context.addPath(path.cgPath)
|
||||||
|
context.clip()
|
||||||
|
|
||||||
|
let colorsArray: [CGColor] = [
|
||||||
|
UIColor(rgb: 0xF161DD).cgColor,
|
||||||
|
UIColor(rgb: 0xF161DD).cgColor,
|
||||||
|
UIColor(rgb: 0x8d77ff).cgColor,
|
||||||
|
UIColor(rgb: 0xb56eec).cgColor,
|
||||||
|
UIColor(rgb: 0xb56eec).cgColor
|
||||||
|
]
|
||||||
|
var locations: [CGFloat] = [0.0, 0.15, 0.5, 0.85, 1.0]
|
||||||
|
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)!
|
||||||
|
|
||||||
|
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: size.height), options: CGGradientDrawingOptions())
|
||||||
|
|
||||||
|
if let image = generateTintedImage(image: UIImage(bundleImageName: "Premium/ButtonIcon"), color: UIColor(rgb: 0xffffff)), let cgImage = image.cgImage {
|
||||||
|
let imageSize = image.size.aspectFitted(CGSize(width: floor(size.width * 0.6), height: floor(size.height * 0.6)))
|
||||||
|
context.draw(cgImage, in: CGRect(origin: CGPoint(x: floorToScreenPixels((bounds.width - imageSize.width) / 2.0), y: floorToScreenPixels((bounds.height - imageSize.height) / 2.0)), size: imageSize))
|
||||||
|
}
|
||||||
|
})!
|
||||||
|
}
|
||||||
|
|
||||||
func selectivePrivacySettingsController(
|
func selectivePrivacySettingsController(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
kind: SelectivePrivacySettingsKind,
|
kind: SelectivePrivacySettingsKind,
|
||||||
@ -1041,7 +1069,41 @@ func selectivePrivacySettingsController(
|
|||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
if peerIds.isEmpty {
|
if peerIds.isEmpty {
|
||||||
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .peerSelection(searchChatList: true, searchGroups: true, searchChannels: false), options: []))
|
enum AdditionalCategoryId: Int {
|
||||||
|
case premiumUsers
|
||||||
|
}
|
||||||
|
|
||||||
|
var displayPremiumCategory = false
|
||||||
|
switch kind {
|
||||||
|
case .groupInvitations:
|
||||||
|
displayPremiumCategory = true
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
var additionalCategories: [ChatListNodeAdditionalCategory] = []
|
||||||
|
|
||||||
|
if displayPremiumCategory {
|
||||||
|
additionalCategories = [
|
||||||
|
ChatListNodeAdditionalCategory(
|
||||||
|
id: AdditionalCategoryId.premiumUsers.rawValue,
|
||||||
|
icon: generatePremiumCategoryIcon(size: CGSize(width: 40.0, height: 40.0), cornerRadius: 12.0),
|
||||||
|
smallIcon: generatePremiumCategoryIcon(size: CGSize(width: 22.0, height: 22.0), cornerRadius: 6.0),
|
||||||
|
title: "Premium Users"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
let selectedCategories = Set<Int>()
|
||||||
|
|
||||||
|
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .chatSelection(ContactMultiselectionControllerMode.ChatSelection(
|
||||||
|
title: "Add Users",
|
||||||
|
searchPlaceholder: "Search users and groups",
|
||||||
|
selectedChats: Set(),
|
||||||
|
additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories),
|
||||||
|
chatListFilters: nil,
|
||||||
|
onlyUsers: false
|
||||||
|
)), options: []))
|
||||||
addPeerDisposable.set((controller.result
|
addPeerDisposable.set((controller.result
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue).start(next: { [weak controller] result in
|
|> deliverOnMainQueue).start(next: { [weak controller] result in
|
||||||
@ -1128,7 +1190,15 @@ func selectivePrivacySettingsController(
|
|||||||
}))
|
}))
|
||||||
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
} else {
|
} else {
|
||||||
let controller = selectivePrivacyPeersController(context: context, title: title, initialPeers: peerIds, updated: { updatedPeerIds in
|
var displayPremiumCategory = false
|
||||||
|
switch kind {
|
||||||
|
case .groupInvitations:
|
||||||
|
displayPremiumCategory = true
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
let controller = selectivePrivacyPeersController(context: context, title: title, initialPeers: peerIds, displayPremiumCategory: displayPremiumCategory, updated: { updatedPeerIds in
|
||||||
updateState { state in
|
updateState { state in
|
||||||
if enable {
|
if enable {
|
||||||
switch target {
|
switch target {
|
||||||
|
@ -248,7 +248,7 @@ private func selectivePrivacyPeersControllerEntries(presentationData: Presentati
|
|||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
public func selectivePrivacyPeersController(context: AccountContext, title: String, initialPeers: [EnginePeer.Id: SelectivePrivacyPeer], updated: @escaping ([EnginePeer.Id: SelectivePrivacyPeer]) -> Void) -> ViewController {
|
public func selectivePrivacyPeersController(context: AccountContext, title: String, initialPeers: [EnginePeer.Id: SelectivePrivacyPeer], displayPremiumCategory: Bool, updated: @escaping ([EnginePeer.Id: SelectivePrivacyPeer]) -> Void) -> ViewController {
|
||||||
let statePromise = ValuePromise(SelectivePrivacyPeersControllerState(), ignoreRepeated: true)
|
let statePromise = ValuePromise(SelectivePrivacyPeersControllerState(), ignoreRepeated: true)
|
||||||
let stateValue = Atomic(value: SelectivePrivacyPeersControllerState())
|
let stateValue = Atomic(value: SelectivePrivacyPeersControllerState())
|
||||||
let updateState: ((SelectivePrivacyPeersControllerState) -> SelectivePrivacyPeersControllerState) -> Void = { f in
|
let updateState: ((SelectivePrivacyPeersControllerState) -> SelectivePrivacyPeersControllerState) -> Void = { f in
|
||||||
@ -307,7 +307,33 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri
|
|||||||
|
|
||||||
removePeerDisposable.set(applyPeers.start())
|
removePeerDisposable.set(applyPeers.start())
|
||||||
}, addPeer: {
|
}, addPeer: {
|
||||||
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .peerSelection(searchChatList: true, searchGroups: true, searchChannels: false), options: []))
|
enum AdditionalCategoryId: Int {
|
||||||
|
case premiumUsers
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
var additionalCategories: [ChatListNodeAdditionalCategory] = []
|
||||||
|
|
||||||
|
if displayPremiumCategory {
|
||||||
|
additionalCategories = [
|
||||||
|
ChatListNodeAdditionalCategory(
|
||||||
|
id: AdditionalCategoryId.premiumUsers.rawValue,
|
||||||
|
icon: generatePremiumCategoryIcon(size: CGSize(width: 40.0, height: 40.0), cornerRadius: 12.0),
|
||||||
|
smallIcon: generatePremiumCategoryIcon(size: CGSize(width: 22.0, height: 22.0), cornerRadius: 6.0),
|
||||||
|
title: "Premium Users"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
let selectedCategories = Set<Int>()
|
||||||
|
|
||||||
|
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .chatSelection(ContactMultiselectionControllerMode.ChatSelection(
|
||||||
|
title: "Add Users",
|
||||||
|
searchPlaceholder: "Search users and groups",
|
||||||
|
selectedChats: Set(),
|
||||||
|
additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories),
|
||||||
|
chatListFilters: nil,
|
||||||
|
onlyUsers: false
|
||||||
|
)), options: []))
|
||||||
addPeerDisposable.set((controller.result
|
addPeerDisposable.set((controller.result
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue).start(next: { [weak controller] result in
|
|> deliverOnMainQueue).start(next: { [weak controller] result in
|
||||||
|
@ -436,9 +436,12 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/Settings/BusinessLocationSetupScreen",
|
"//submodules/TelegramUI/Components/Settings/BusinessLocationSetupScreen",
|
||||||
"//submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen",
|
"//submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen",
|
||||||
"//submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen",
|
"//submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen",
|
||||||
|
"//submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen",
|
||||||
"//submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController",
|
"//submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController",
|
||||||
"//submodules/TelegramUI/Components/Settings/CollectibleItemInfoScreen",
|
"//submodules/TelegramUI/Components/Settings/CollectibleItemInfoScreen",
|
||||||
"//submodules/TelegramUI/Components/StickerPickerScreen",
|
"//submodules/TelegramUI/Components/StickerPickerScreen",
|
||||||
|
"//submodules/TelegramUI/Components/Chat/ChatEmptyNode",
|
||||||
|
"//submodules/TelegramUI/Components/Chat/ChatMediaInputStickerGridItem",
|
||||||
] + select({
|
] + select({
|
||||||
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
||||||
"//build-system:ios_sim_arm64": [],
|
"//build-system:ios_sim_arm64": [],
|
||||||
|
37
submodules/TelegramUI/Components/Chat/ChatEmptyNode/BUILD
Normal file
37
submodules/TelegramUI/Components/Chat/ChatEmptyNode/BUILD
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "ChatEmptyNode",
|
||||||
|
module_name = "ChatEmptyNode",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
"-warnings-as-errors",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//submodules/AsyncDisplayKit",
|
||||||
|
"//submodules/Display",
|
||||||
|
"//submodules/SSignalKit/SwiftSignalKit",
|
||||||
|
"//submodules/Postbox",
|
||||||
|
"//submodules/TelegramCore",
|
||||||
|
"//submodules/TelegramPresentationData",
|
||||||
|
"//submodules/AppBundle",
|
||||||
|
"//submodules/LocalizedPeerData",
|
||||||
|
"//submodules/TelegramStringFormatting",
|
||||||
|
"//submodules/AccountContext",
|
||||||
|
"//submodules/ChatPresentationInterfaceState",
|
||||||
|
"//submodules/WallpaperBackgroundNode",
|
||||||
|
"//submodules/ComponentFlow",
|
||||||
|
"//submodules/TelegramUI/Components/EmojiStatusComponent",
|
||||||
|
"//submodules/TelegramUI/Components/Chat/ChatLoadingNode",
|
||||||
|
"//submodules/Components/MultilineTextComponent",
|
||||||
|
"//submodules/Components/BalancedTextComponent",
|
||||||
|
"//submodules/Markdown",
|
||||||
|
"//submodules/ReactionSelectionNode",
|
||||||
|
"//submodules/TelegramUI/Components/Chat/ChatMediaInputStickerGridItem",
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
@ -19,6 +19,7 @@ import MultilineTextComponent
|
|||||||
import BalancedTextComponent
|
import BalancedTextComponent
|
||||||
import Markdown
|
import Markdown
|
||||||
import ReactionSelectionNode
|
import ReactionSelectionNode
|
||||||
|
import ChatMediaInputStickerGridItem
|
||||||
|
|
||||||
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
|
||||||
@ -79,11 +80,11 @@ private final class ChatEmptyNodeRegularChatContent: ASDisplayNode, ChatEmptyNod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol ChatEmptyNodeStickerContentNode: ASDisplayNode {
|
public protocol ChatEmptyNodeStickerContentNode: ASDisplayNode {
|
||||||
var stickerNode: ChatMediaInputStickerGridItemNode { get }
|
var stickerNode: ChatMediaInputStickerGridItemNode { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeStickerContentNode, ChatEmptyNodeContent, UIGestureRecognizerDelegate {
|
public final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeStickerContentNode, ChatEmptyNodeContent, UIGestureRecognizerDelegate {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let interaction: ChatPanelInterfaceInteraction?
|
private let interaction: ChatPanelInterfaceInteraction?
|
||||||
|
|
||||||
@ -91,15 +92,16 @@ final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeSticke
|
|||||||
private let textNode: ImmediateTextNode
|
private let textNode: ImmediateTextNode
|
||||||
|
|
||||||
private var stickerItem: ChatMediaInputStickerGridItem?
|
private var stickerItem: ChatMediaInputStickerGridItem?
|
||||||
let stickerNode: ChatMediaInputStickerGridItemNode
|
public var stickerNode: ChatMediaInputStickerGridItemNode
|
||||||
|
|
||||||
private var currentTheme: PresentationTheme?
|
private var currentTheme: PresentationTheme?
|
||||||
private var currentStrings: PresentationStrings?
|
private var currentStrings: PresentationStrings?
|
||||||
|
|
||||||
private var didSetupSticker = false
|
private var didSetupSticker = false
|
||||||
private let disposable = MetaDisposable()
|
private let disposable = MetaDisposable()
|
||||||
|
private var currentCustomStickerFile: TelegramMediaFile?
|
||||||
|
|
||||||
init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) {
|
public init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.interaction = interaction
|
self.interaction = interaction
|
||||||
|
|
||||||
@ -126,7 +128,7 @@ final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeSticke
|
|||||||
self.addSubnode(self.stickerNode)
|
self.addSubnode(self.stickerNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didLoad() {
|
override public func didLoad() {
|
||||||
super.didLoad()
|
super.didLoad()
|
||||||
|
|
||||||
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.stickerTapGesture(_:)))
|
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.stickerTapGesture(_:)))
|
||||||
@ -138,7 +140,7 @@ final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeSticke
|
|||||||
self.disposable.dispose()
|
self.disposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,18 +151,29 @@ final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeSticke
|
|||||||
let _ = self.interaction?.sendSticker(.standalone(media: stickerItem.stickerItem.file), false, self.view, self.stickerNode.bounds, nil, [])
|
let _ = self.interaction?.sendSticker(.standalone(media: stickerItem.stickerItem.file), false, self.view, self.stickerNode.bounds, nil, [])
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
public func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||||
|
let isFirstTime = self.currentTheme == nil
|
||||||
|
|
||||||
if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings {
|
if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings {
|
||||||
self.currentTheme = interfaceState.theme
|
self.currentTheme = interfaceState.theme
|
||||||
self.currentStrings = interfaceState.strings
|
self.currentStrings = interfaceState.strings
|
||||||
|
}
|
||||||
|
|
||||||
|
var customStickerFile: TelegramMediaFile?
|
||||||
|
|
||||||
let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper)
|
let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper)
|
||||||
|
if case let .emptyChat(emptyChat) = subject, case let .customGreeting(stickerFile, title, text) = emptyChat {
|
||||||
|
customStickerFile = stickerFile
|
||||||
|
self.titleNode.attributedText = NSAttributedString(string: title, font: titleFont, textColor: serviceColor.primaryText)
|
||||||
|
self.textNode.attributedText = NSAttributedString(string: text, font: messageFont, textColor: serviceColor.primaryText)
|
||||||
|
} else {
|
||||||
self.titleNode.attributedText = NSAttributedString(string: interfaceState.strings.Conversation_EmptyPlaceholder, font: titleFont, textColor: serviceColor.primaryText)
|
self.titleNode.attributedText = NSAttributedString(string: interfaceState.strings.Conversation_EmptyPlaceholder, font: titleFont, textColor: serviceColor.primaryText)
|
||||||
|
|
||||||
self.textNode.attributedText = NSAttributedString(string: interfaceState.strings.Conversation_GreetingText, font: messageFont, textColor: serviceColor.primaryText)
|
self.textNode.attributedText = NSAttributedString(string: interfaceState.strings.Conversation_GreetingText, font: messageFont, textColor: serviceColor.primaryText)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let previousCustomStickerFile = self.currentCustomStickerFile
|
||||||
|
self.currentCustomStickerFile = customStickerFile
|
||||||
|
|
||||||
let stickerSize: CGSize
|
let stickerSize: CGSize
|
||||||
let inset: CGFloat
|
let inset: CGFloat
|
||||||
if size.width == 320.0 {
|
if size.width == 320.0 {
|
||||||
@ -170,11 +183,13 @@ final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeSticke
|
|||||||
stickerSize = CGSize(width: 160.0, height: 160.0)
|
stickerSize = CGSize(width: 160.0, height: 160.0)
|
||||||
inset = 15.0
|
inset = 15.0
|
||||||
}
|
}
|
||||||
if let item = self.stickerItem {
|
if let item = self.stickerItem, previousCustomStickerFile == customStickerFile {
|
||||||
self.stickerNode.updateLayout(item: item, size: stickerSize, isVisible: true, synchronousLoads: true)
|
self.stickerNode.updateLayout(item: item, size: stickerSize, isVisible: true, synchronousLoads: true)
|
||||||
} else if !self.didSetupSticker {
|
} else if !self.didSetupSticker || previousCustomStickerFile != customStickerFile {
|
||||||
let sticker: Signal<TelegramMediaFile?, NoError>
|
let sticker: Signal<TelegramMediaFile?, NoError>
|
||||||
if let preloadedSticker = interfaceState.greetingData?.sticker {
|
if let customStickerFile {
|
||||||
|
sticker = .single(customStickerFile)
|
||||||
|
} else if let preloadedSticker = interfaceState.greetingData?.sticker {
|
||||||
sticker = preloadedSticker
|
sticker = preloadedSticker
|
||||||
} else {
|
} else {
|
||||||
sticker = self.context.engine.stickers.randomGreetingSticker()
|
sticker = self.context.engine.stickers.randomGreetingSticker()
|
||||||
@ -183,6 +198,19 @@ final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeSticke
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !isFirstTime, case let .emptyChat(emptyChat) = subject, case .customGreeting = emptyChat {
|
||||||
|
let previousStickerNode = self.stickerNode
|
||||||
|
previousStickerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previousStickerNode] _ in
|
||||||
|
previousStickerNode?.removeFromSupernode()
|
||||||
|
})
|
||||||
|
previousStickerNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false)
|
||||||
|
|
||||||
|
self.stickerNode = ChatMediaInputStickerGridItemNode()
|
||||||
|
self.addSubnode(self.stickerNode)
|
||||||
|
self.stickerNode.layer.animateSpring(from: 0.001 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5)
|
||||||
|
self.stickerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
}
|
||||||
|
|
||||||
self.didSetupSticker = true
|
self.didSetupSticker = true
|
||||||
self.disposable.set((sticker
|
self.disposable.set((sticker
|
||||||
|> deliverOnMainQueue).startStrict(next: { [weak self] sticker in
|
|> deliverOnMainQueue).startStrict(next: { [weak self] sticker in
|
||||||
@ -216,6 +244,10 @@ final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeSticke
|
|||||||
let stickerPackItem = StickerPackItem(index: index, file: sticker, indexKeys: [])
|
let stickerPackItem = StickerPackItem(index: index, file: sticker, indexKeys: [])
|
||||||
let item = ChatMediaInputStickerGridItem(context: strongSelf.context, collectionId: collectionId, stickerPackInfo: nil, index: ItemCollectionViewEntryIndex(collectionIndex: 0, collectionId: collectionId, itemIndex: index), stickerItem: stickerPackItem, canManagePeerSpecificPack: nil, interfaceInteraction: nil, inputNodeInteraction: inputNodeInteraction, hasAccessory: false, theme: interfaceState.theme, large: true, selected: {})
|
let item = ChatMediaInputStickerGridItem(context: strongSelf.context, collectionId: collectionId, stickerPackInfo: nil, index: ItemCollectionViewEntryIndex(collectionIndex: 0, collectionId: collectionId, itemIndex: index), stickerItem: stickerPackItem, canManagePeerSpecificPack: nil, interfaceInteraction: nil, inputNodeInteraction: inputNodeInteraction, hasAccessory: false, theme: interfaceState.theme, large: true, selected: {})
|
||||||
strongSelf.stickerItem = item
|
strongSelf.stickerItem = item
|
||||||
|
|
||||||
|
if isFirstTime {
|
||||||
|
|
||||||
|
}
|
||||||
strongSelf.stickerNode.updateLayout(item: item, size: stickerSize, isVisible: true, synchronousLoads: true)
|
strongSelf.stickerNode.updateLayout(item: item, size: stickerSize, isVisible: true, synchronousLoads: true)
|
||||||
strongSelf.stickerNode.isVisibleInGrid = true
|
strongSelf.stickerNode.isVisibleInGrid = true
|
||||||
strongSelf.stickerNode.updateIsPanelVisible(true)
|
strongSelf.stickerNode.updateIsPanelVisible(true)
|
||||||
@ -252,7 +284,7 @@ final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeSticke
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class ChatEmptyNodeNearbyChatContent: ASDisplayNode, ChatEmptyNodeStickerContentNode, ChatEmptyNodeContent, UIGestureRecognizerDelegate {
|
public final class ChatEmptyNodeNearbyChatContent: ASDisplayNode, ChatEmptyNodeStickerContentNode, ChatEmptyNodeContent, UIGestureRecognizerDelegate {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let interaction: ChatPanelInterfaceInteraction?
|
private let interaction: ChatPanelInterfaceInteraction?
|
||||||
|
|
||||||
@ -260,7 +292,7 @@ final class ChatEmptyNodeNearbyChatContent: ASDisplayNode, ChatEmptyNodeStickerC
|
|||||||
private let textNode: ImmediateTextNode
|
private let textNode: ImmediateTextNode
|
||||||
|
|
||||||
private var stickerItem: ChatMediaInputStickerGridItem?
|
private var stickerItem: ChatMediaInputStickerGridItem?
|
||||||
let stickerNode: ChatMediaInputStickerGridItemNode
|
public let stickerNode: ChatMediaInputStickerGridItemNode
|
||||||
|
|
||||||
private var currentTheme: PresentationTheme?
|
private var currentTheme: PresentationTheme?
|
||||||
private var currentStrings: PresentationStrings?
|
private var currentStrings: PresentationStrings?
|
||||||
@ -268,7 +300,7 @@ final class ChatEmptyNodeNearbyChatContent: ASDisplayNode, ChatEmptyNodeStickerC
|
|||||||
private var didSetupSticker = false
|
private var didSetupSticker = false
|
||||||
private let disposable = MetaDisposable()
|
private let disposable = MetaDisposable()
|
||||||
|
|
||||||
init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) {
|
public init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.interaction = interaction
|
self.interaction = interaction
|
||||||
|
|
||||||
@ -295,7 +327,7 @@ final class ChatEmptyNodeNearbyChatContent: ASDisplayNode, ChatEmptyNodeStickerC
|
|||||||
self.addSubnode(self.stickerNode)
|
self.addSubnode(self.stickerNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didLoad() {
|
override public func didLoad() {
|
||||||
super.didLoad()
|
super.didLoad()
|
||||||
|
|
||||||
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.stickerTapGesture(_:)))
|
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.stickerTapGesture(_:)))
|
||||||
@ -307,7 +339,7 @@ final class ChatEmptyNodeNearbyChatContent: ASDisplayNode, ChatEmptyNodeStickerC
|
|||||||
self.disposable.dispose()
|
self.disposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,7 +350,7 @@ final class ChatEmptyNodeNearbyChatContent: ASDisplayNode, ChatEmptyNodeStickerC
|
|||||||
let _ = self.interaction?.sendSticker(.standalone(media: stickerItem.stickerItem.file), false, self.view, self.stickerNode.bounds, nil, [])
|
let _ = self.interaction?.sendSticker(.standalone(media: stickerItem.stickerItem.file), false, self.view, self.stickerNode.bounds, nil, [])
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
public func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||||
if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings {
|
if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings {
|
||||||
self.currentTheme = interfaceState.theme
|
self.currentTheme = interfaceState.theme
|
||||||
self.currentStrings = interfaceState.strings
|
self.currentStrings = interfaceState.strings
|
||||||
@ -844,7 +876,7 @@ private final class ChatEmptyNodeCloudChatContent: ASDisplayNode, ChatEmptyNodeC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class ChatEmptyNodeTopicChatContent: ASDisplayNode, ChatEmptyNodeContent, UIGestureRecognizerDelegate {
|
public final class ChatEmptyNodeTopicChatContent: ASDisplayNode, ChatEmptyNodeContent, UIGestureRecognizerDelegate {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
|
|
||||||
private let titleNode: ImmediateTextNode
|
private let titleNode: ImmediateTextNode
|
||||||
@ -855,7 +887,7 @@ final class ChatEmptyNodeTopicChatContent: ASDisplayNode, ChatEmptyNodeContent,
|
|||||||
|
|
||||||
private let iconView: ComponentView<Empty>
|
private let iconView: ComponentView<Empty>
|
||||||
|
|
||||||
init(context: AccountContext) {
|
public init(context: AccountContext) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
|
||||||
self.titleNode = ImmediateTextNode()
|
self.titleNode = ImmediateTextNode()
|
||||||
@ -880,7 +912,7 @@ final class ChatEmptyNodeTopicChatContent: ASDisplayNode, ChatEmptyNodeContent,
|
|||||||
self.addSubnode(self.textNode)
|
self.addSubnode(self.textNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
public func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||||
let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper)
|
let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper)
|
||||||
if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings {
|
if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings {
|
||||||
self.currentTheme = interfaceState.theme
|
self.currentTheme = interfaceState.theme
|
||||||
@ -955,7 +987,7 @@ final class ChatEmptyNodeTopicChatContent: ASDisplayNode, ChatEmptyNodeContent,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatEmptyNodeContent {
|
public final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatEmptyNodeContent {
|
||||||
private let isPremiumDisabled: Bool
|
private let isPremiumDisabled: Bool
|
||||||
private let interaction: ChatPanelInterfaceInteraction?
|
private let interaction: ChatPanelInterfaceInteraction?
|
||||||
|
|
||||||
@ -969,7 +1001,7 @@ final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatEmptyNod
|
|||||||
private var currentTheme: PresentationTheme?
|
private var currentTheme: PresentationTheme?
|
||||||
private var currentStrings: PresentationStrings?
|
private var currentStrings: PresentationStrings?
|
||||||
|
|
||||||
init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) {
|
public init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) {
|
||||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
|
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
|
||||||
self.isPremiumDisabled = premiumConfiguration.isPremiumDisabled
|
self.isPremiumDisabled = premiumConfiguration.isPremiumDisabled
|
||||||
|
|
||||||
@ -1016,7 +1048,7 @@ final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatEmptyNod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
public func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||||
let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper)
|
let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper)
|
||||||
|
|
||||||
let maxWidth = min(200.0, size.width)
|
let maxWidth = min(200.0, size.width)
|
||||||
@ -1139,9 +1171,18 @@ private enum ChatEmptyNodeContentType: Equatable {
|
|||||||
case premiumRequired
|
case premiumRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
final class ChatEmptyNode: ASDisplayNode {
|
public final class ChatEmptyNode: ASDisplayNode {
|
||||||
enum Subject {
|
public enum Subject {
|
||||||
case emptyChat(ChatHistoryNodeLoadState.EmptyType)
|
public enum EmptyType: Equatable {
|
||||||
|
case generic
|
||||||
|
case joined
|
||||||
|
case clearedHistory
|
||||||
|
case topic
|
||||||
|
case botInfo
|
||||||
|
case customGreeting(sticker: TelegramMediaFile?, title: String, text: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
case emptyChat(EmptyType)
|
||||||
case detailsPlaceholder
|
case detailsPlaceholder
|
||||||
}
|
}
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
@ -1159,7 +1200,7 @@ final class ChatEmptyNode: ASDisplayNode {
|
|||||||
|
|
||||||
private var content: (ChatEmptyNodeContentType, ASDisplayNode & ChatEmptyNodeContent)?
|
private var content: (ChatEmptyNodeContentType, ASDisplayNode & ChatEmptyNodeContent)?
|
||||||
|
|
||||||
init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) {
|
public init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.interaction = interaction
|
self.interaction = interaction
|
||||||
|
|
||||||
@ -1172,14 +1213,14 @@ final class ChatEmptyNode: ASDisplayNode {
|
|||||||
self.addSubnode(self.backgroundNode)
|
self.addSubnode(self.backgroundNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
guard let result = super.hitTest(point, with: event) else {
|
guard let result = super.hitTest(point, with: event) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateFromLoadingNode(_ loadingNode: ChatLoadingNode) {
|
public func animateFromLoadingNode(_ loadingNode: ChatLoadingNode) {
|
||||||
guard let (_, node) = self.content else {
|
guard let (_, node) = self.content else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1204,7 +1245,7 @@ final class ChatEmptyNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: Subject, loadingNode: ChatLoadingNode?, backgroundNode: WallpaperBackgroundNode?, size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) {
|
public func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: Subject, loadingNode: ChatLoadingNode?, backgroundNode: WallpaperBackgroundNode?, size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) {
|
||||||
self.wallpaperBackgroundNode = backgroundNode
|
self.wallpaperBackgroundNode = backgroundNode
|
||||||
|
|
||||||
if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings {
|
if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings {
|
||||||
@ -1224,7 +1265,9 @@ final class ChatEmptyNode: ASDisplayNode {
|
|||||||
case .detailsPlaceholder:
|
case .detailsPlaceholder:
|
||||||
contentType = .regular
|
contentType = .regular
|
||||||
case let .emptyChat(emptyType):
|
case let .emptyChat(emptyType):
|
||||||
if case .customChatContents = interfaceState.subject {
|
if case .customGreeting = emptyType {
|
||||||
|
contentType = .greeting
|
||||||
|
} else if case .customChatContents = interfaceState.subject {
|
||||||
contentType = .cloud
|
contentType = .cloud
|
||||||
} else if case .replyThread = interfaceState.chatLocation {
|
} else if case .replyThread = interfaceState.chatLocation {
|
||||||
if case .topic = emptyType {
|
if case .topic = emptyType {
|
@ -0,0 +1,30 @@
|
|||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "ChatMediaInputStickerGridItem",
|
||||||
|
module_name = "ChatMediaInputStickerGridItem",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
"-warnings-as-errors",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//submodules/Display",
|
||||||
|
"//submodules/TelegramCore",
|
||||||
|
"//submodules/SSignalKit/SwiftSignalKit",
|
||||||
|
"//submodules/AsyncDisplayKit",
|
||||||
|
"//submodules/Postbox",
|
||||||
|
"//submodules/TelegramPresentationData",
|
||||||
|
"//submodules/StickerResources",
|
||||||
|
"//submodules/AccountContext",
|
||||||
|
"//submodules/AnimatedStickerNode",
|
||||||
|
"//submodules/TelegramAnimatedStickerNode",
|
||||||
|
"//submodules/ShimmerEffect",
|
||||||
|
"//submodules/TelegramUI/Components/ChatControllerInteraction",
|
||||||
|
"//submodules/ChatPresentationInterfaceState",
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
@ -14,25 +14,25 @@ import ShimmerEffect
|
|||||||
import ChatControllerInteraction
|
import ChatControllerInteraction
|
||||||
import ChatPresentationInterfaceState
|
import ChatPresentationInterfaceState
|
||||||
|
|
||||||
enum ChatMediaInputStickerGridSectionAccessory {
|
public enum ChatMediaInputStickerGridSectionAccessory {
|
||||||
case none
|
case none
|
||||||
case setup
|
case setup
|
||||||
case clear
|
case clear
|
||||||
}
|
}
|
||||||
|
|
||||||
final class ChatMediaInputStickerGridSection: GridSection {
|
public final class ChatMediaInputStickerGridSection: GridSection {
|
||||||
let collectionId: ItemCollectionId
|
public let collectionId: ItemCollectionId
|
||||||
let collectionInfo: StickerPackCollectionInfo?
|
public let collectionInfo: StickerPackCollectionInfo?
|
||||||
let accessory: ChatMediaInputStickerGridSectionAccessory
|
public let accessory: ChatMediaInputStickerGridSectionAccessory
|
||||||
let interaction: ChatMediaInputNodeInteraction
|
public let interaction: ChatMediaInputNodeInteraction
|
||||||
let theme: PresentationTheme
|
public let theme: PresentationTheme
|
||||||
let height: CGFloat = 26.0
|
public let height: CGFloat = 26.0
|
||||||
|
|
||||||
var hashValue: Int {
|
public var hashValue: Int {
|
||||||
return self.collectionId.hashValue
|
return self.collectionId.hashValue
|
||||||
}
|
}
|
||||||
|
|
||||||
init(collectionId: ItemCollectionId, collectionInfo: StickerPackCollectionInfo?, accessory: ChatMediaInputStickerGridSectionAccessory, theme: PresentationTheme, interaction: ChatMediaInputNodeInteraction) {
|
public init(collectionId: ItemCollectionId, collectionInfo: StickerPackCollectionInfo?, accessory: ChatMediaInputStickerGridSectionAccessory, theme: PresentationTheme, interaction: ChatMediaInputNodeInteraction) {
|
||||||
self.collectionId = collectionId
|
self.collectionId = collectionId
|
||||||
self.collectionInfo = collectionInfo
|
self.collectionInfo = collectionInfo
|
||||||
self.accessory = accessory
|
self.accessory = accessory
|
||||||
@ -40,7 +40,7 @@ final class ChatMediaInputStickerGridSection: GridSection {
|
|||||||
self.interaction = interaction
|
self.interaction = interaction
|
||||||
}
|
}
|
||||||
|
|
||||||
func isEqual(to: GridSection) -> Bool {
|
public func isEqual(to: GridSection) -> Bool {
|
||||||
if let to = to as? ChatMediaInputStickerGridSection {
|
if let to = to as? ChatMediaInputStickerGridSection {
|
||||||
return self.collectionId == to.collectionId && self.theme === to.theme
|
return self.collectionId == to.collectionId && self.theme === to.theme
|
||||||
} else {
|
} else {
|
||||||
@ -48,20 +48,20 @@ final class ChatMediaInputStickerGridSection: GridSection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func node() -> ASDisplayNode {
|
public func node() -> ASDisplayNode {
|
||||||
return ChatMediaInputStickerGridSectionNode(collectionInfo: self.collectionInfo, accessory: self.accessory, theme: self.theme, interaction: self.interaction)
|
return ChatMediaInputStickerGridSectionNode(collectionInfo: self.collectionInfo, accessory: self.accessory, theme: self.theme, interaction: self.interaction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private let sectionTitleFont = Font.medium(12.0)
|
private let sectionTitleFont = Font.medium(12.0)
|
||||||
|
|
||||||
final class ChatMediaInputStickerGridSectionNode: ASDisplayNode {
|
public final class ChatMediaInputStickerGridSectionNode: ASDisplayNode {
|
||||||
let titleNode: ASTextNode
|
public let titleNode: ASTextNode
|
||||||
let setupNode: HighlightableButtonNode?
|
public let setupNode: HighlightableButtonNode?
|
||||||
let interaction: ChatMediaInputNodeInteraction
|
public let interaction: ChatMediaInputNodeInteraction
|
||||||
let accessory: ChatMediaInputStickerGridSectionAccessory
|
public let accessory: ChatMediaInputStickerGridSectionAccessory
|
||||||
|
|
||||||
init(collectionInfo: StickerPackCollectionInfo?, accessory: ChatMediaInputStickerGridSectionAccessory, theme: PresentationTheme, interaction: ChatMediaInputNodeInteraction) {
|
public init(collectionInfo: StickerPackCollectionInfo?, accessory: ChatMediaInputStickerGridSectionAccessory, theme: PresentationTheme, interaction: ChatMediaInputNodeInteraction) {
|
||||||
self.interaction = interaction
|
self.interaction = interaction
|
||||||
self.titleNode = ASTextNode()
|
self.titleNode = ASTextNode()
|
||||||
self.titleNode.isUserInteractionEnabled = false
|
self.titleNode.isUserInteractionEnabled = false
|
||||||
@ -91,7 +91,7 @@ final class ChatMediaInputStickerGridSectionNode: ASDisplayNode {
|
|||||||
self.setupNode?.addTarget(self, action: #selector(self.setupPressed), forControlEvents: .touchUpInside)
|
self.setupNode?.addTarget(self, action: #selector(self.setupPressed), forControlEvents: .touchUpInside)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func layout() {
|
override public func layout() {
|
||||||
super.layout()
|
super.layout()
|
||||||
|
|
||||||
let bounds = self.bounds
|
let bounds = self.bounds
|
||||||
@ -116,20 +116,20 @@ final class ChatMediaInputStickerGridSectionNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class ChatMediaInputStickerGridItem: GridItem {
|
public final class ChatMediaInputStickerGridItem: GridItem {
|
||||||
let context: AccountContext
|
public let context: AccountContext
|
||||||
let index: ItemCollectionViewEntryIndex
|
public let index: ItemCollectionViewEntryIndex
|
||||||
let stickerItem: StickerPackItem
|
public let stickerItem: StickerPackItem
|
||||||
let selected: () -> Void
|
public let selected: () -> Void
|
||||||
let interfaceInteraction: ChatControllerInteraction?
|
public let interfaceInteraction: ChatControllerInteraction?
|
||||||
let inputNodeInteraction: ChatMediaInputNodeInteraction
|
public let inputNodeInteraction: ChatMediaInputNodeInteraction
|
||||||
let theme: PresentationTheme
|
public let theme: PresentationTheme
|
||||||
let large: Bool
|
public let large: Bool
|
||||||
let isLocked: Bool
|
public let isLocked: Bool
|
||||||
|
|
||||||
let section: GridSection?
|
public let section: GridSection?
|
||||||
|
|
||||||
init(context: AccountContext, collectionId: ItemCollectionId, stickerPackInfo: StickerPackCollectionInfo?, index: ItemCollectionViewEntryIndex, stickerItem: StickerPackItem, canManagePeerSpecificPack: Bool?, interfaceInteraction: ChatControllerInteraction?, inputNodeInteraction: ChatMediaInputNodeInteraction, hasAccessory: Bool, theme: PresentationTheme, large: Bool = false, isLocked: Bool = false, selected: @escaping () -> Void) {
|
public init(context: AccountContext, collectionId: ItemCollectionId, stickerPackInfo: StickerPackCollectionInfo?, index: ItemCollectionViewEntryIndex, stickerItem: StickerPackItem, canManagePeerSpecificPack: Bool?, interfaceInteraction: ChatControllerInteraction?, inputNodeInteraction: ChatMediaInputNodeInteraction, hasAccessory: Bool, theme: PresentationTheme, large: Bool = false, isLocked: Bool = false, selected: @escaping () -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.index = index
|
self.index = index
|
||||||
self.stickerItem = stickerItem
|
self.stickerItem = stickerItem
|
||||||
@ -145,7 +145,7 @@ final class ChatMediaInputStickerGridItem: GridItem {
|
|||||||
self.section = ChatMediaInputStickerGridSection(collectionId: collectionId, collectionInfo: stickerPackInfo, accessory: accessory, theme: theme, interaction: inputNodeInteraction)
|
self.section = ChatMediaInputStickerGridSection(collectionId: collectionId, collectionInfo: stickerPackInfo, accessory: accessory, theme: theme, interaction: inputNodeInteraction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode {
|
public func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode {
|
||||||
let node = ChatMediaInputStickerGridItemNode()
|
let node = ChatMediaInputStickerGridItemNode()
|
||||||
node.interfaceInteraction = self.interfaceInteraction
|
node.interfaceInteraction = self.interfaceInteraction
|
||||||
node.inputNodeInteraction = self.inputNodeInteraction
|
node.inputNodeInteraction = self.inputNodeInteraction
|
||||||
@ -153,7 +153,7 @@ final class ChatMediaInputStickerGridItem: GridItem {
|
|||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(node: GridItemNode) {
|
public func update(node: GridItemNode) {
|
||||||
guard let node = node as? ChatMediaInputStickerGridItemNode else {
|
guard let node = node as? ChatMediaInputStickerGridItemNode else {
|
||||||
assertionFailure()
|
assertionFailure()
|
||||||
return
|
return
|
||||||
@ -164,26 +164,26 @@ final class ChatMediaInputStickerGridItem: GridItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class ChatMediaInputStickerGridItemNode: GridItemNode {
|
public final class ChatMediaInputStickerGridItemNode: GridItemNode {
|
||||||
private var currentState: (AccountContext, StickerPackItem, CGSize)?
|
private var currentState: (AccountContext, StickerPackItem, CGSize)?
|
||||||
private var currentSize: CGSize?
|
private var currentSize: CGSize?
|
||||||
let imageNode: TransformImageNode
|
public let imageNode: TransformImageNode
|
||||||
private(set) var animationNode: AnimatedStickerNode?
|
public private(set) var animationNode: AnimatedStickerNode?
|
||||||
private(set) var placeholderNode: StickerShimmerEffectNode?
|
public private(set) var placeholderNode: StickerShimmerEffectNode?
|
||||||
|
|
||||||
private var lockBackground: UIVisualEffectView?
|
private var lockBackground: UIVisualEffectView?
|
||||||
private var lockTintView: UIView?
|
private var lockTintView: UIView?
|
||||||
private var lockIconNode: ASImageNode?
|
private var lockIconNode: ASImageNode?
|
||||||
var isLocked: Bool?
|
public var isLocked: Bool?
|
||||||
|
|
||||||
private var didSetUpAnimationNode = false
|
private var didSetUpAnimationNode = false
|
||||||
private var item: ChatMediaInputStickerGridItem?
|
private var item: ChatMediaInputStickerGridItem?
|
||||||
|
|
||||||
private let stickerFetchedDisposable = MetaDisposable()
|
private let stickerFetchedDisposable = MetaDisposable()
|
||||||
|
|
||||||
var currentIsPreviewing = false
|
public var currentIsPreviewing = false
|
||||||
|
|
||||||
override var isVisibleInGrid: Bool {
|
override public var isVisibleInGrid: Bool {
|
||||||
didSet {
|
didSet {
|
||||||
self.updateVisibility()
|
self.updateVisibility()
|
||||||
}
|
}
|
||||||
@ -192,15 +192,15 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
|
|||||||
private var isPanelVisible = false
|
private var isPanelVisible = false
|
||||||
private var isPlaying = false
|
private var isPlaying = false
|
||||||
|
|
||||||
var interfaceInteraction: ChatControllerInteraction?
|
public var interfaceInteraction: ChatControllerInteraction?
|
||||||
var inputNodeInteraction: ChatMediaInputNodeInteraction?
|
public var inputNodeInteraction: ChatMediaInputNodeInteraction?
|
||||||
var selected: (() -> Void)?
|
public var selected: (() -> Void)?
|
||||||
|
|
||||||
var stickerPackItem: StickerPackItem? {
|
public var stickerPackItem: StickerPackItem? {
|
||||||
return self.currentState?.1
|
return self.currentState?.1
|
||||||
}
|
}
|
||||||
|
|
||||||
override init() {
|
override public init() {
|
||||||
self.imageNode = TransformImageNode()
|
self.imageNode = TransformImageNode()
|
||||||
self.placeholderNode = StickerShimmerEffectNode()
|
self.placeholderNode = StickerShimmerEffectNode()
|
||||||
self.placeholderNode?.isUserInteractionEnabled = false
|
self.placeholderNode?.isUserInteractionEnabled = false
|
||||||
@ -244,13 +244,13 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didLoad() {
|
override public func didLoad() {
|
||||||
super.didLoad()
|
super.didLoad()
|
||||||
|
|
||||||
self.imageNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:))))
|
self.imageNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:))))
|
||||||
}
|
}
|
||||||
|
|
||||||
override func updateLayout(item: GridItem, size: CGSize, isVisible: Bool, synchronousLoads: Bool) {
|
override public func updateLayout(item: GridItem, size: CGSize, isVisible: Bool, synchronousLoads: Bool) {
|
||||||
guard let item = item as? ChatMediaInputStickerGridItem else {
|
guard let item = item as? ChatMediaInputStickerGridItem else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -392,13 +392,13 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func updateAbsoluteRect(_ absoluteRect: CGRect, within containerSize: CGSize) {
|
override public func updateAbsoluteRect(_ absoluteRect: CGRect, within containerSize: CGSize) {
|
||||||
if let placeholderNode = self.placeholderNode {
|
if let placeholderNode = self.placeholderNode {
|
||||||
placeholderNode.updateAbsoluteRect(CGRect(origin: CGPoint(x: absoluteRect.minX + placeholderNode.frame.minX, y: absoluteRect.minY + placeholderNode.frame.minY), size: placeholderNode.frame.size), within: containerSize)
|
placeholderNode.updateAbsoluteRect(CGRect(origin: CGPoint(x: absoluteRect.minX + placeholderNode.frame.minX, y: absoluteRect.minY + placeholderNode.frame.minY), size: placeholderNode.frame.size), within: containerSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func imageNodeTap(_ recognizer: UITapGestureRecognizer) {
|
@objc private func imageNodeTap(_ recognizer: UITapGestureRecognizer) {
|
||||||
if self.imageNode.layer.animation(forKey: "opacity") != nil {
|
if self.imageNode.layer.animation(forKey: "opacity") != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -411,18 +411,18 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func transitionNode() -> ASDisplayNode? {
|
public func transitionNode() -> ASDisplayNode? {
|
||||||
return self.imageNode
|
return self.imageNode
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateIsPanelVisible(_ isPanelVisible: Bool) {
|
public func updateIsPanelVisible(_ isPanelVisible: Bool) {
|
||||||
if self.isPanelVisible != isPanelVisible {
|
if self.isPanelVisible != isPanelVisible {
|
||||||
self.isPanelVisible = isPanelVisible
|
self.isPanelVisible = isPanelVisible
|
||||||
self.updateVisibility()
|
self.updateVisibility()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateVisibility() {
|
public func updateVisibility() {
|
||||||
guard let item = self.item else {
|
guard let item = self.item else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -444,7 +444,7 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updatePreviewing(animated: Bool) {
|
public func updatePreviewing(animated: Bool) {
|
||||||
var isPreviewing = false
|
var isPreviewing = false
|
||||||
if let (_, item, _) = self.currentState, let interaction = self.inputNodeInteraction {
|
if let (_, item, _) = self.currentState, let interaction = self.inputNodeInteraction {
|
||||||
isPreviewing = interaction.previewedStickerPackItemFile?.id == item.file.id
|
isPreviewing = interaction.previewedStickerPackItemFile?.id == item.file.id
|
@ -0,0 +1,22 @@
|
|||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "EmojiActionIconComponent",
|
||||||
|
module_name = "EmojiActionIconComponent",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
"-warnings-as-errors",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//submodules/Display",
|
||||||
|
"//submodules/ComponentFlow",
|
||||||
|
"//submodules/TelegramCore",
|
||||||
|
"//submodules/TelegramUI/Components/EmojiStatusComponent",
|
||||||
|
"//submodules/AccountContext",
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
@ -0,0 +1,107 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import ComponentFlow
|
||||||
|
import TelegramCore
|
||||||
|
import EmojiStatusComponent
|
||||||
|
import AccountContext
|
||||||
|
|
||||||
|
public final class EmojiActionIconComponent: Component {
|
||||||
|
public let context: AccountContext
|
||||||
|
public let color: UIColor
|
||||||
|
public let fileId: Int64?
|
||||||
|
public let file: TelegramMediaFile?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
context: AccountContext,
|
||||||
|
color: UIColor,
|
||||||
|
fileId: Int64?,
|
||||||
|
file: TelegramMediaFile?
|
||||||
|
) {
|
||||||
|
self.context = context
|
||||||
|
self.color = color
|
||||||
|
self.fileId = fileId
|
||||||
|
self.file = file
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: EmojiActionIconComponent, rhs: EmojiActionIconComponent) -> Bool {
|
||||||
|
if lhs.context !== rhs.context {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.color != rhs.color {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.fileId != rhs.fileId {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.file != rhs.file {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class View: UIView {
|
||||||
|
private var icon: ComponentView<Empty>?
|
||||||
|
|
||||||
|
func update(component: EmojiActionIconComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
|
let size = CGSize(width: 24.0, height: 24.0)
|
||||||
|
|
||||||
|
if let fileId = component.fileId {
|
||||||
|
let icon: ComponentView<Empty>
|
||||||
|
if let current = self.icon {
|
||||||
|
icon = current
|
||||||
|
} else {
|
||||||
|
icon = ComponentView()
|
||||||
|
self.icon = icon
|
||||||
|
}
|
||||||
|
let content: EmojiStatusComponent.AnimationContent
|
||||||
|
if let file = component.file {
|
||||||
|
content = .file(file: file)
|
||||||
|
} else {
|
||||||
|
content = .customEmoji(fileId: fileId)
|
||||||
|
}
|
||||||
|
let _ = icon.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(EmojiStatusComponent(
|
||||||
|
context: component.context,
|
||||||
|
animationCache: component.context.animationCache,
|
||||||
|
animationRenderer: component.context.animationRenderer,
|
||||||
|
content: .animation(
|
||||||
|
content: content,
|
||||||
|
size: size,
|
||||||
|
placeholderColor: .lightGray,
|
||||||
|
themeColor: component.color,
|
||||||
|
loopMode: .forever
|
||||||
|
),
|
||||||
|
isVisibleForAnimations: false,
|
||||||
|
action: nil
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: size
|
||||||
|
)
|
||||||
|
let iconFrame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
if let iconView = icon.view {
|
||||||
|
if iconView.superview == nil {
|
||||||
|
self.addSubview(iconView)
|
||||||
|
}
|
||||||
|
iconView.frame = iconFrame
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let icon = self.icon {
|
||||||
|
self.icon = nil
|
||||||
|
icon.view?.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func makeView() -> View {
|
||||||
|
return View(frame: CGRect())
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
|
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@ import AccountContext
|
|||||||
public final class ListMultilineTextFieldItemComponent: Component {
|
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 init() {
|
public init() {
|
||||||
}
|
}
|
||||||
@ -206,6 +207,7 @@ public final class ListMultilineTextFieldItemComponent: Component {
|
|||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(TextFieldComponent(
|
component: AnyComponent(TextFieldComponent(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
|
theme: component.theme,
|
||||||
strings: component.strings,
|
strings: component.strings,
|
||||||
externalState: self.textFieldExternalState,
|
externalState: self.textFieldExternalState,
|
||||||
fontSize: 17.0,
|
fontSize: 17.0,
|
||||||
@ -266,6 +268,7 @@ public final class ListMultilineTextFieldItemComponent: Component {
|
|||||||
self.separatorInset = 16.0
|
self.separatorInset = 16.0
|
||||||
|
|
||||||
component.externalState?.hasText = self.textFieldExternalState.hasText
|
component.externalState?.hasText = self.textFieldExternalState.hasText
|
||||||
|
component.externalState?.text = self.textFieldExternalState.text
|
||||||
|
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ public final class ListSectionComponent: Component {
|
|||||||
public let header: AnyComponent<Empty>?
|
public let header: AnyComponent<Empty>?
|
||||||
public let footer: AnyComponent<Empty>?
|
public let footer: AnyComponent<Empty>?
|
||||||
public let items: [AnyComponentWithIdentity<Empty>]
|
public let items: [AnyComponentWithIdentity<Empty>]
|
||||||
|
public let itemUpdateOrder: [AnyHashable]?
|
||||||
public let displaySeparators: Bool
|
public let displaySeparators: Bool
|
||||||
public let extendsItemHighlightToSection: Bool
|
public let extendsItemHighlightToSection: Bool
|
||||||
|
|
||||||
@ -33,6 +34,7 @@ public final class ListSectionComponent: Component {
|
|||||||
header: AnyComponent<Empty>?,
|
header: AnyComponent<Empty>?,
|
||||||
footer: AnyComponent<Empty>?,
|
footer: AnyComponent<Empty>?,
|
||||||
items: [AnyComponentWithIdentity<Empty>],
|
items: [AnyComponentWithIdentity<Empty>],
|
||||||
|
itemUpdateOrder: [AnyHashable]? = nil,
|
||||||
displaySeparators: Bool = true,
|
displaySeparators: Bool = true,
|
||||||
extendsItemHighlightToSection: Bool = false
|
extendsItemHighlightToSection: Bool = false
|
||||||
) {
|
) {
|
||||||
@ -41,6 +43,7 @@ public final class ListSectionComponent: Component {
|
|||||||
self.header = header
|
self.header = header
|
||||||
self.footer = footer
|
self.footer = footer
|
||||||
self.items = items
|
self.items = items
|
||||||
|
self.itemUpdateOrder = itemUpdateOrder
|
||||||
self.displaySeparators = displaySeparators
|
self.displaySeparators = displaySeparators
|
||||||
self.extendsItemHighlightToSection = extendsItemHighlightToSection
|
self.extendsItemHighlightToSection = extendsItemHighlightToSection
|
||||||
}
|
}
|
||||||
@ -61,6 +64,9 @@ public final class ListSectionComponent: Component {
|
|||||||
if lhs.items != rhs.items {
|
if lhs.items != rhs.items {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.itemUpdateOrder != rhs.itemUpdateOrder {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.displaySeparators != rhs.displaySeparators {
|
if lhs.displaySeparators != rhs.displaySeparators {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -204,7 +210,41 @@ public final class ListSectionComponent: Component {
|
|||||||
|
|
||||||
var innerContentHeight: CGFloat = 0.0
|
var innerContentHeight: CGFloat = 0.0
|
||||||
var validItemIds: [AnyHashable] = []
|
var validItemIds: [AnyHashable] = []
|
||||||
|
|
||||||
|
struct ReadyItem {
|
||||||
|
var index: Int
|
||||||
|
var itemId: AnyHashable
|
||||||
|
var itemView: ItemView
|
||||||
|
var itemTransition: Transition
|
||||||
|
var itemSize: CGSize
|
||||||
|
|
||||||
|
init(index: Int, itemId: AnyHashable, itemView: ItemView, itemTransition: Transition, itemSize: CGSize) {
|
||||||
|
self.index = index
|
||||||
|
self.itemId = itemId
|
||||||
|
self.itemView = itemView
|
||||||
|
self.itemTransition = itemTransition
|
||||||
|
self.itemSize = itemSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var readyItems: [ReadyItem] = []
|
||||||
|
var itemUpdateOrder: [Int] = []
|
||||||
|
if let itemUpdateOrderValue = component.itemUpdateOrder {
|
||||||
|
for id in itemUpdateOrderValue {
|
||||||
|
if let index = component.items.firstIndex(where: { $0.id == id }) {
|
||||||
|
if !itemUpdateOrder.contains(index) {
|
||||||
|
itemUpdateOrder.append(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
for i in 0 ..< component.items.count {
|
for i in 0 ..< component.items.count {
|
||||||
|
if !itemUpdateOrder.contains(i) {
|
||||||
|
itemUpdateOrder.append(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in itemUpdateOrder {
|
||||||
let item = component.items[i]
|
let item = component.items[i]
|
||||||
let itemId = item.id
|
let itemId = item.id
|
||||||
validItemIds.append(itemId)
|
validItemIds.append(itemId)
|
||||||
@ -226,17 +266,29 @@ public final class ListSectionComponent: Component {
|
|||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: availableSize.width, height: availableSize.height)
|
containerSize: CGSize(width: availableSize.width, height: availableSize.height)
|
||||||
)
|
)
|
||||||
let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: innerContentHeight), size: itemSize)
|
|
||||||
if let itemComponentView = itemView.contents.view {
|
|
||||||
if itemComponentView.superview == nil {
|
|
||||||
itemView.addSubview(itemComponentView)
|
|
||||||
self.contentItemContainerView.addSubview(itemView)
|
|
||||||
self.contentSeparatorContainerLayer.addSublayer(itemView.separatorLayer)
|
|
||||||
self.contentHighlightContainerLayer.addSublayer(itemView.highlightLayer)
|
|
||||||
transition.animateAlpha(view: itemView, from: 0.0, to: 1.0)
|
|
||||||
transition.animateAlpha(layer: itemView.separatorLayer, from: 0.0, to: 1.0)
|
|
||||||
transition.animateAlpha(layer: itemView.highlightLayer, from: 0.0, to: 1.0)
|
|
||||||
|
|
||||||
|
readyItems.append(ReadyItem(
|
||||||
|
index: i,
|
||||||
|
itemId: itemId,
|
||||||
|
itemView: itemView,
|
||||||
|
itemTransition: itemTransition,
|
||||||
|
itemSize: itemSize
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
for readyItem in readyItems.sorted(by: { $0.index < $1.index }) {
|
||||||
|
let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: innerContentHeight), size: readyItem.itemSize)
|
||||||
|
if let itemComponentView = readyItem.itemView.contents.view {
|
||||||
|
if itemComponentView.superview == nil {
|
||||||
|
readyItem.itemView.addSubview(itemComponentView)
|
||||||
|
self.contentItemContainerView.addSubview(readyItem.itemView)
|
||||||
|
self.contentSeparatorContainerLayer.addSublayer(readyItem.itemView.separatorLayer)
|
||||||
|
self.contentHighlightContainerLayer.addSublayer(readyItem.itemView.highlightLayer)
|
||||||
|
transition.animateAlpha(view: readyItem.itemView, from: 0.0, to: 1.0)
|
||||||
|
transition.animateAlpha(layer: readyItem.itemView.separatorLayer, from: 0.0, to: 1.0)
|
||||||
|
transition.animateAlpha(layer: readyItem.itemView.highlightLayer, from: 0.0, to: 1.0)
|
||||||
|
|
||||||
|
let itemId = readyItem.itemId
|
||||||
if let itemComponentView = itemComponentView as? ChildView {
|
if let itemComponentView = itemComponentView as? ChildView {
|
||||||
itemComponentView.customUpdateIsHighlighted = { [weak self] isHighlighted in
|
itemComponentView.customUpdateIsHighlighted = { [weak self] isHighlighted in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@ -250,20 +302,20 @@ public final class ListSectionComponent: Component {
|
|||||||
if let itemComponentView = itemComponentView as? ChildView {
|
if let itemComponentView = itemComponentView as? ChildView {
|
||||||
separatorInset = itemComponentView.separatorInset
|
separatorInset = itemComponentView.separatorInset
|
||||||
}
|
}
|
||||||
itemTransition.setFrame(view: itemView, frame: itemFrame)
|
readyItem.itemTransition.setFrame(view: readyItem.itemView, frame: itemFrame)
|
||||||
|
|
||||||
let itemSeparatorTopOffset: CGFloat = i == 0 ? 0.0 : -UIScreenPixel
|
let itemSeparatorTopOffset: CGFloat = readyItem.index == 0 ? 0.0 : -UIScreenPixel
|
||||||
let itemHighlightFrame = CGRect(origin: CGPoint(x: itemFrame.minX, y: itemFrame.minY + itemSeparatorTopOffset), size: CGSize(width: itemFrame.width, height: itemFrame.height - itemSeparatorTopOffset))
|
let itemHighlightFrame = CGRect(origin: CGPoint(x: itemFrame.minX, y: itemFrame.minY + itemSeparatorTopOffset), size: CGSize(width: itemFrame.width, height: itemFrame.height - itemSeparatorTopOffset))
|
||||||
itemTransition.setFrame(layer: itemView.highlightLayer, frame: itemHighlightFrame)
|
readyItem.itemTransition.setFrame(layer: readyItem.itemView.highlightLayer, frame: itemHighlightFrame)
|
||||||
|
|
||||||
itemTransition.setFrame(view: itemComponentView, frame: CGRect(origin: CGPoint(), size: itemFrame.size))
|
readyItem.itemTransition.setFrame(view: itemComponentView, frame: CGRect(origin: CGPoint(), size: itemFrame.size))
|
||||||
|
|
||||||
let itemSeparatorFrame = CGRect(origin: CGPoint(x: separatorInset, y: itemFrame.maxY - UIScreenPixel), size: CGSize(width: availableSize.width - separatorInset, height: UIScreenPixel))
|
let itemSeparatorFrame = CGRect(origin: CGPoint(x: separatorInset, y: itemFrame.maxY - UIScreenPixel), size: CGSize(width: availableSize.width - separatorInset, height: UIScreenPixel))
|
||||||
itemTransition.setFrame(layer: itemView.separatorLayer, frame: itemSeparatorFrame)
|
readyItem.itemTransition.setFrame(layer: readyItem.itemView.separatorLayer, frame: itemSeparatorFrame)
|
||||||
|
|
||||||
let separatorAlpha: CGFloat
|
let separatorAlpha: CGFloat
|
||||||
if component.displaySeparators {
|
if component.displaySeparators {
|
||||||
if i != component.items.count - 1 {
|
if readyItem.index != component.items.count - 1 {
|
||||||
separatorAlpha = 1.0
|
separatorAlpha = 1.0
|
||||||
} else {
|
} else {
|
||||||
separatorAlpha = 0.0
|
separatorAlpha = 0.0
|
||||||
@ -271,11 +323,12 @@ public final class ListSectionComponent: Component {
|
|||||||
} else {
|
} else {
|
||||||
separatorAlpha = 0.0
|
separatorAlpha = 0.0
|
||||||
}
|
}
|
||||||
itemTransition.setAlpha(layer: itemView.separatorLayer, alpha: separatorAlpha)
|
readyItem.itemTransition.setAlpha(layer: readyItem.itemView.separatorLayer, alpha: separatorAlpha)
|
||||||
itemView.separatorLayer.backgroundColor = component.theme.list.itemBlocksSeparatorColor.cgColor
|
readyItem.itemView.separatorLayer.backgroundColor = component.theme.list.itemBlocksSeparatorColor.cgColor
|
||||||
}
|
}
|
||||||
innerContentHeight += itemSize.height
|
innerContentHeight += readyItem.itemSize.height
|
||||||
}
|
}
|
||||||
|
|
||||||
var removedItemIds: [AnyHashable] = []
|
var removedItemIds: [AnyHashable] = []
|
||||||
for (id, itemView) in self.itemViews {
|
for (id, itemView) in self.itemViews {
|
||||||
if !validItemIds.contains(id) {
|
if !validItemIds.contains(id) {
|
||||||
|
@ -776,6 +776,7 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(TextFieldComponent(
|
component: AnyComponent(TextFieldComponent(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
|
theme: component.theme,
|
||||||
strings: component.strings,
|
strings: component.strings,
|
||||||
externalState: self.textFieldExternalState,
|
externalState: self.textFieldExternalState,
|
||||||
fontSize: 17.0,
|
fontSize: 17.0,
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "BusinessIntroSetupScreen",
|
||||||
|
module_name = "BusinessIntroSetupScreen",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
"-warnings-as-errors",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//submodules/AsyncDisplayKit",
|
||||||
|
"//submodules/Display",
|
||||||
|
"//submodules/Postbox",
|
||||||
|
"//submodules/TelegramCore",
|
||||||
|
"//submodules/SSignalKit/SwiftSignalKit",
|
||||||
|
"//submodules/TelegramPresentationData",
|
||||||
|
"//submodules/TelegramUIPreferences",
|
||||||
|
"//submodules/AccountContext",
|
||||||
|
"//submodules/PresentationDataUtils",
|
||||||
|
"//submodules/Markdown",
|
||||||
|
"//submodules/ComponentFlow",
|
||||||
|
"//submodules/Components/ComponentDisplayAdapters",
|
||||||
|
"//submodules/Components/ViewControllerComponent",
|
||||||
|
"//submodules/Components/BundleIconComponent",
|
||||||
|
"//submodules/Components/MultilineTextComponent",
|
||||||
|
"//submodules/Components/BalancedTextComponent",
|
||||||
|
"//submodules/TelegramUI/Components/ListSectionComponent",
|
||||||
|
"//submodules/TelegramUI/Components/ListActionItemComponent",
|
||||||
|
"//submodules/TelegramUI/Components/ListMultilineTextFieldItemComponent",
|
||||||
|
"//submodules/TelegramUI/Components/LottieComponent",
|
||||||
|
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||||
|
"//submodules/LocationUI",
|
||||||
|
"//submodules/AppBundle",
|
||||||
|
"//submodules/Geocoding",
|
||||||
|
"//submodules/TelegramUI/Components/Chat/ChatEmptyNode",
|
||||||
|
"//submodules/WallpaperBackgroundNode",
|
||||||
|
"//submodules/ChatPresentationInterfaceState",
|
||||||
|
"//submodules/TelegramUI/Components/EntityKeyboard",
|
||||||
|
"//submodules/TelegramUI/Components/PeerAllowedReactionsScreen",
|
||||||
|
"//submodules/TelegramUI/Components/EmojiActionIconComponent",
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
@ -0,0 +1,662 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import SwiftSignalKit
|
||||||
|
import Postbox
|
||||||
|
import TelegramCore
|
||||||
|
import TelegramPresentationData
|
||||||
|
import TelegramUIPreferences
|
||||||
|
import PresentationDataUtils
|
||||||
|
import AccountContext
|
||||||
|
import ComponentFlow
|
||||||
|
import ViewControllerComponent
|
||||||
|
import MultilineTextComponent
|
||||||
|
import BalancedTextComponent
|
||||||
|
import ListSectionComponent
|
||||||
|
import ListActionItemComponent
|
||||||
|
import ListMultilineTextFieldItemComponent
|
||||||
|
import BundleIconComponent
|
||||||
|
import LottieComponent
|
||||||
|
import EntityKeyboard
|
||||||
|
import PeerAllowedReactionsScreen
|
||||||
|
import EmojiActionIconComponent
|
||||||
|
|
||||||
|
final class BusinessIntroSetupScreenComponent: Component {
|
||||||
|
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||||
|
|
||||||
|
let context: AccountContext
|
||||||
|
|
||||||
|
init(
|
||||||
|
context: AccountContext
|
||||||
|
) {
|
||||||
|
self.context = context
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: BusinessIntroSetupScreenComponent, rhs: BusinessIntroSetupScreenComponent) -> Bool {
|
||||||
|
if lhs.context !== rhs.context {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class ScrollView: UIScrollView {
|
||||||
|
override func touchesShouldCancel(in view: UIView) -> Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class View: UIView, UIScrollViewDelegate {
|
||||||
|
private let topOverscrollLayer = SimpleLayer()
|
||||||
|
private let scrollView: ScrollView
|
||||||
|
|
||||||
|
private let navigationTitle = ComponentView<Empty>()
|
||||||
|
private let introContent = ComponentView<Empty>()
|
||||||
|
private let introSection = ComponentView<Empty>()
|
||||||
|
private let deleteSection = ComponentView<Empty>()
|
||||||
|
|
||||||
|
private var isUpdating: Bool = false
|
||||||
|
|
||||||
|
private var component: BusinessIntroSetupScreenComponent?
|
||||||
|
private(set) weak var state: EmptyComponentState?
|
||||||
|
private var environment: EnvironmentType?
|
||||||
|
|
||||||
|
private let introPlaceholderTag = NSObject()
|
||||||
|
private let titleInputState = ListMultilineTextFieldItemComponent.ExternalState()
|
||||||
|
private let titleInputTag = NSObject()
|
||||||
|
private var resetTitle: String?
|
||||||
|
private let textInputState = ListMultilineTextFieldItemComponent.ExternalState()
|
||||||
|
private let textInputTag = NSObject()
|
||||||
|
private var resetText: String?
|
||||||
|
|
||||||
|
private var stickerFile: TelegramMediaFile?
|
||||||
|
private var stickerContent: EmojiPagerContentComponent?
|
||||||
|
private var stickerContentDisposable: Disposable?
|
||||||
|
private var displayStickerInput: Bool = false
|
||||||
|
private var stickerSelectionControl: ComponentView<Empty>?
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
self.scrollView = ScrollView()
|
||||||
|
self.scrollView.showsVerticalScrollIndicator = true
|
||||||
|
self.scrollView.showsHorizontalScrollIndicator = false
|
||||||
|
self.scrollView.scrollsToTop = false
|
||||||
|
self.scrollView.delaysContentTouches = false
|
||||||
|
self.scrollView.canCancelContentTouches = true
|
||||||
|
self.scrollView.contentInsetAdjustmentBehavior = .never
|
||||||
|
if #available(iOS 13.0, *) {
|
||||||
|
self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false
|
||||||
|
}
|
||||||
|
self.scrollView.alwaysBounceVertical = true
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.scrollView.delegate = self
|
||||||
|
self.addSubview(self.scrollView)
|
||||||
|
|
||||||
|
self.scrollView.layer.addSublayer(self.topOverscrollLayer)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.stickerContentDisposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
func scrollToTop() {
|
||||||
|
self.scrollView.setContentOffset(CGPoint(), animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func attemptNavigation(complete: @escaping () -> Void) -> Bool {
|
||||||
|
guard let component = self.component, let environment = self.environment else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
let _ = component
|
||||||
|
let _ = environment
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
|
self.updateScrolling(transition: .immediate)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var scrolledUp = true
|
||||||
|
private func updateScrolling(transition: Transition) {
|
||||||
|
let navigationRevealOffsetY: CGFloat = 0.0
|
||||||
|
|
||||||
|
let navigationAlphaDistance: CGFloat = 16.0
|
||||||
|
let navigationAlpha: CGFloat = max(0.0, min(1.0, (self.scrollView.contentOffset.y - navigationRevealOffsetY) / navigationAlphaDistance))
|
||||||
|
if let controller = self.environment?.controller(), let navigationBar = controller.navigationBar {
|
||||||
|
transition.setAlpha(layer: navigationBar.backgroundNode.layer, alpha: navigationAlpha)
|
||||||
|
transition.setAlpha(layer: navigationBar.stripeNode.layer, alpha: navigationAlpha)
|
||||||
|
}
|
||||||
|
|
||||||
|
var scrolledUp = false
|
||||||
|
if navigationAlpha < 0.5 {
|
||||||
|
scrolledUp = true
|
||||||
|
} else if navigationAlpha > 0.5 {
|
||||||
|
scrolledUp = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.scrolledUp != scrolledUp {
|
||||||
|
self.scrolledUp = scrolledUp
|
||||||
|
if !self.isUpdating {
|
||||||
|
self.state?.updated()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let navigationTitleView = self.navigationTitle.view {
|
||||||
|
transition.setAlpha(view: navigationTitleView, alpha: 1.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(component: BusinessIntroSetupScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||||
|
self.isUpdating = true
|
||||||
|
defer {
|
||||||
|
self.isUpdating = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.component == nil {
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.stickerContentDisposable == nil {
|
||||||
|
let stickerContent = EmojiPagerContentComponent.stickerInputData(
|
||||||
|
context: component.context,
|
||||||
|
animationCache: component.context.animationCache,
|
||||||
|
animationRenderer: component.context.animationRenderer,
|
||||||
|
stickerNamespaces: [Namespaces.ItemCollection.CloudStickerPacks],
|
||||||
|
stickerOrderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.CloudAllPremiumStickers],
|
||||||
|
chatPeerId: nil,
|
||||||
|
hasSearch: true,
|
||||||
|
hasTrending: true,
|
||||||
|
forceHasPremium: true
|
||||||
|
)
|
||||||
|
self.stickerContentDisposable = (stickerContent
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] stickerContent in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.stickerContent = stickerContent
|
||||||
|
|
||||||
|
stickerContent.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction(
|
||||||
|
performItemAction: { [weak self] _, item, _, _, _, _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let itemFile = item.itemFile else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.stickerFile = itemFile
|
||||||
|
self.displayStickerInput = false
|
||||||
|
|
||||||
|
if !self.isUpdating {
|
||||||
|
self.state?.updated(transition: .spring(duration: 0.25))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deleteBackwards: {
|
||||||
|
},
|
||||||
|
openStickerSettings: {
|
||||||
|
},
|
||||||
|
openFeatured: {
|
||||||
|
},
|
||||||
|
openSearch: {
|
||||||
|
},
|
||||||
|
addGroupAction: { _, _, _ in
|
||||||
|
},
|
||||||
|
clearGroup: { _ in
|
||||||
|
},
|
||||||
|
editAction: { _ in
|
||||||
|
},
|
||||||
|
pushController: { c in
|
||||||
|
},
|
||||||
|
presentController: { c in
|
||||||
|
},
|
||||||
|
presentGlobalOverlayController: { c in
|
||||||
|
},
|
||||||
|
navigationController: {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
requestUpdate: { _ in
|
||||||
|
},
|
||||||
|
updateSearchQuery: { _ in
|
||||||
|
},
|
||||||
|
updateScrollingToItemGroup: {
|
||||||
|
},
|
||||||
|
onScroll: {},
|
||||||
|
chatPeerId: nil,
|
||||||
|
peekBehavior: nil,
|
||||||
|
customLayout: nil,
|
||||||
|
externalBackground: nil,
|
||||||
|
externalExpansionView: nil,
|
||||||
|
customContentView: nil,
|
||||||
|
useOpaqueTheme: true,
|
||||||
|
hideBackground: false,
|
||||||
|
stateContext: nil,
|
||||||
|
addImage: nil
|
||||||
|
)
|
||||||
|
|
||||||
|
if !self.isUpdating {
|
||||||
|
self.state?.updated(transition: .immediate)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let environment = environment[EnvironmentType.self].value
|
||||||
|
let themeUpdated = self.environment?.theme !== environment.theme
|
||||||
|
self.environment = environment
|
||||||
|
|
||||||
|
self.component = component
|
||||||
|
self.state = state
|
||||||
|
|
||||||
|
let alphaTransition: Transition
|
||||||
|
if !transition.animation.isImmediate {
|
||||||
|
alphaTransition = .easeInOut(duration: 0.25)
|
||||||
|
} else {
|
||||||
|
alphaTransition = .immediate
|
||||||
|
}
|
||||||
|
|
||||||
|
if themeUpdated {
|
||||||
|
self.backgroundColor = environment.theme.list.blocksBackgroundColor
|
||||||
|
}
|
||||||
|
|
||||||
|
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
|
let _ = alphaTransition
|
||||||
|
let _ = presentationData
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
let navigationTitleSize = self.navigationTitle.update(
|
||||||
|
transition: transition,
|
||||||
|
component: AnyComponent(MultilineTextComponent(
|
||||||
|
text: .plain(NSAttributedString(string: "Intro", font: Font.semibold(17.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor)),
|
||||||
|
horizontalAlignment: .center
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: availableSize.width, height: 100.0)
|
||||||
|
)
|
||||||
|
let navigationTitleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - navigationTitleSize.width) / 2.0), y: environment.statusBarHeight + floor((environment.navigationHeight - environment.statusBarHeight - navigationTitleSize.height) / 2.0)), size: navigationTitleSize)
|
||||||
|
if let navigationTitleView = self.navigationTitle.view {
|
||||||
|
if navigationTitleView.superview == nil {
|
||||||
|
if let controller = self.environment?.controller(), let navigationBar = controller.navigationBar {
|
||||||
|
navigationBar.view.addSubview(navigationTitleView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
transition.setFrame(view: navigationTitleView, frame: navigationTitleFrame)
|
||||||
|
}
|
||||||
|
|
||||||
|
let bottomContentInset: CGFloat = 24.0
|
||||||
|
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
|
||||||
|
let sectionSpacing: CGFloat = 24.0
|
||||||
|
|
||||||
|
var contentHeight: CGFloat = 0.0
|
||||||
|
|
||||||
|
contentHeight += environment.navigationHeight
|
||||||
|
contentHeight += 26.0
|
||||||
|
|
||||||
|
var introSectionItems: [AnyComponentWithIdentity<Empty>] = []
|
||||||
|
introSectionItems.append(AnyComponentWithIdentity(id: introSectionItems.count, component: AnyComponent(Rectangle(color: .clear, height: 346.0, tag: self.introPlaceholderTag))))
|
||||||
|
introSectionItems.append(AnyComponentWithIdentity(id: introSectionItems.count, component: AnyComponent(ListMultilineTextFieldItemComponent(
|
||||||
|
externalState: self.titleInputState,
|
||||||
|
context: component.context,
|
||||||
|
theme: environment.theme,
|
||||||
|
strings: environment.strings,
|
||||||
|
initialText: "",
|
||||||
|
resetText: self.resetTitle.flatMap {
|
||||||
|
return ListMultilineTextFieldItemComponent.ResetText(value: $0)
|
||||||
|
},
|
||||||
|
placeholder: "Enter Title",
|
||||||
|
autocapitalizationType: .none,
|
||||||
|
autocorrectionType: .no,
|
||||||
|
characterLimit: 256,
|
||||||
|
allowEmptyLines: false,
|
||||||
|
updated: { _ in
|
||||||
|
},
|
||||||
|
textUpdateTransition: .spring(duration: 0.4),
|
||||||
|
tag: self.titleInputTag
|
||||||
|
))))
|
||||||
|
self.resetTitle = nil
|
||||||
|
introSectionItems.append(AnyComponentWithIdentity(id: introSectionItems.count, component: AnyComponent(ListMultilineTextFieldItemComponent(
|
||||||
|
externalState: self.textInputState,
|
||||||
|
context: component.context,
|
||||||
|
theme: environment.theme,
|
||||||
|
strings: environment.strings,
|
||||||
|
initialText: "",
|
||||||
|
resetText: self.resetText.flatMap {
|
||||||
|
return ListMultilineTextFieldItemComponent.ResetText(value: $0)
|
||||||
|
},
|
||||||
|
placeholder: "Enter Message",
|
||||||
|
autocapitalizationType: .none,
|
||||||
|
autocorrectionType: .no,
|
||||||
|
characterLimit: 256,
|
||||||
|
allowEmptyLines: false,
|
||||||
|
updated: { _ in
|
||||||
|
},
|
||||||
|
textUpdateTransition: .spring(duration: 0.4),
|
||||||
|
tag: self.textInputTag
|
||||||
|
))))
|
||||||
|
self.resetText = nil
|
||||||
|
|
||||||
|
let stickerIcon: ListActionItemComponent.Icon
|
||||||
|
if let stickerFile = self.stickerFile {
|
||||||
|
stickerIcon = ListActionItemComponent.Icon(component: AnyComponentWithIdentity(id: 0, component: AnyComponent(EmojiActionIconComponent(
|
||||||
|
context: component.context,
|
||||||
|
color: environment.theme.list.itemPrimaryTextColor,
|
||||||
|
fileId: stickerFile.fileId.id,
|
||||||
|
file: stickerFile
|
||||||
|
))))
|
||||||
|
} else {
|
||||||
|
stickerIcon = ListActionItemComponent.Icon(component: AnyComponentWithIdentity(id: 1, component: AnyComponent(MultilineTextComponent(
|
||||||
|
text: .plain(NSAttributedString(
|
||||||
|
string: "Random",
|
||||||
|
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
||||||
|
textColor: environment.theme.list.itemSecondaryTextColor
|
||||||
|
)),
|
||||||
|
maximumNumberOfLines: 1
|
||||||
|
))))
|
||||||
|
}
|
||||||
|
|
||||||
|
introSectionItems.append(AnyComponentWithIdentity(id: introSectionItems.count, component: AnyComponent(ListActionItemComponent(
|
||||||
|
theme: environment.theme,
|
||||||
|
title: AnyComponent(VStack([
|
||||||
|
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
|
||||||
|
text: .plain(NSAttributedString(
|
||||||
|
string: "Choose Sticker",
|
||||||
|
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
||||||
|
textColor: environment.theme.list.itemPrimaryTextColor
|
||||||
|
)),
|
||||||
|
maximumNumberOfLines: 1
|
||||||
|
))),
|
||||||
|
], alignment: .left, spacing: 2.0)),
|
||||||
|
icon: stickerIcon,
|
||||||
|
accessory: .none,
|
||||||
|
action: { [weak self] _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.displayStickerInput = true
|
||||||
|
if !self.isUpdating {
|
||||||
|
self.state?.updated(transition: .spring(duration: 0.5))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
))))
|
||||||
|
let introSectionSize = self.introSection.update(
|
||||||
|
transition: transition,
|
||||||
|
component: AnyComponent(ListSectionComponent(
|
||||||
|
theme: environment.theme,
|
||||||
|
header: AnyComponent(MultilineTextComponent(
|
||||||
|
text: .plain(NSAttributedString(
|
||||||
|
string: "CUSTOMIZE YOUR INTRO",
|
||||||
|
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
|
||||||
|
textColor: environment.theme.list.freeTextColor
|
||||||
|
)),
|
||||||
|
maximumNumberOfLines: 0
|
||||||
|
)),
|
||||||
|
footer: AnyComponent(MultilineTextComponent(
|
||||||
|
text: .plain(NSAttributedString(
|
||||||
|
string: "You can customize the message people see before they start a chat with you.",
|
||||||
|
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
|
||||||
|
textColor: environment.theme.list.freeTextColor
|
||||||
|
)),
|
||||||
|
maximumNumberOfLines: 0
|
||||||
|
)),
|
||||||
|
items: introSectionItems,
|
||||||
|
itemUpdateOrder: introSectionItems.map(\.id).reversed()
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
|
||||||
|
)
|
||||||
|
let introSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: introSectionSize)
|
||||||
|
if let introSectionView = self.introSection.view {
|
||||||
|
if introSectionView.superview == nil {
|
||||||
|
self.scrollView.addSubview(introSectionView)
|
||||||
|
self.introSection.parentState = state
|
||||||
|
}
|
||||||
|
transition.setFrame(view: introSectionView, frame: introSectionFrame)
|
||||||
|
}
|
||||||
|
contentHeight += introSectionSize.height
|
||||||
|
contentHeight += sectionSpacing
|
||||||
|
|
||||||
|
let titleText: String
|
||||||
|
if self.titleInputState.text.string.isEmpty {
|
||||||
|
titleText = "No messages here yet..."
|
||||||
|
} else {
|
||||||
|
titleText = self.titleInputState.text.string
|
||||||
|
}
|
||||||
|
|
||||||
|
let textText: String
|
||||||
|
if self.textInputState.text.string.isEmpty {
|
||||||
|
textText = "Send a message or tap on the greeting below"
|
||||||
|
} else {
|
||||||
|
textText = self.textInputState.text.string
|
||||||
|
}
|
||||||
|
|
||||||
|
let introContentSize = self.introContent.update(
|
||||||
|
transition: transition,
|
||||||
|
component: AnyComponent(ChatIntroItemComponent(
|
||||||
|
context: component.context,
|
||||||
|
theme: environment.theme,
|
||||||
|
strings: environment.strings,
|
||||||
|
stickerFile: stickerFile,
|
||||||
|
title: titleText,
|
||||||
|
text: textText
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
|
||||||
|
)
|
||||||
|
if let introContentView = self.introContent.view {
|
||||||
|
if introContentView.superview == nil {
|
||||||
|
if let placeholderView = self.introSection.findTaggedView(tag: self.introPlaceholderTag) {
|
||||||
|
placeholderView.addSubview(introContentView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
transition.setFrame(view: introContentView, frame: CGRect(origin: CGPoint(), size: introContentSize))
|
||||||
|
}
|
||||||
|
|
||||||
|
let displayDelete = !self.titleInputState.text.string.isEmpty || !self.textInputState.text.string.isEmpty || self.stickerFile != nil
|
||||||
|
|
||||||
|
var deleteSectionHeight: CGFloat = 0.0
|
||||||
|
deleteSectionHeight += sectionSpacing
|
||||||
|
let deleteSectionSize = self.deleteSection.update(
|
||||||
|
transition: transition,
|
||||||
|
component: AnyComponent(ListSectionComponent(
|
||||||
|
theme: environment.theme,
|
||||||
|
header: nil,
|
||||||
|
footer: nil,
|
||||||
|
items: [
|
||||||
|
AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent(
|
||||||
|
theme: environment.theme,
|
||||||
|
title: AnyComponent(VStack([
|
||||||
|
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
|
||||||
|
text: .plain(NSAttributedString(
|
||||||
|
string: "Reset to Default",
|
||||||
|
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
||||||
|
textColor: environment.theme.list.itemDestructiveColor
|
||||||
|
)),
|
||||||
|
maximumNumberOfLines: 1
|
||||||
|
))),
|
||||||
|
], alignment: .center, spacing: 2.0, fillWidth: true)),
|
||||||
|
accessory: nil,
|
||||||
|
action: { [weak self] _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.resetTitle = ""
|
||||||
|
self.resetText = ""
|
||||||
|
self.stickerFile = nil
|
||||||
|
self.state?.updated(transition: .spring(duration: 0.4))
|
||||||
|
}
|
||||||
|
)))
|
||||||
|
],
|
||||||
|
displaySeparators: false
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
|
||||||
|
)
|
||||||
|
let deleteSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight + deleteSectionHeight), size: deleteSectionSize)
|
||||||
|
if let deleteSectionView = self.deleteSection.view {
|
||||||
|
if deleteSectionView.superview == nil {
|
||||||
|
self.scrollView.addSubview(deleteSectionView)
|
||||||
|
}
|
||||||
|
transition.setFrame(view: deleteSectionView, frame: deleteSectionFrame)
|
||||||
|
|
||||||
|
if displayDelete {
|
||||||
|
alphaTransition.setAlpha(view: deleteSectionView, alpha: 1.0)
|
||||||
|
} else {
|
||||||
|
alphaTransition.setAlpha(view: deleteSectionView, alpha: 0.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deleteSectionHeight += deleteSectionSize.height
|
||||||
|
if displayDelete {
|
||||||
|
contentHeight += deleteSectionHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
contentHeight += bottomContentInset
|
||||||
|
|
||||||
|
var inputHeight: CGFloat = environment.inputHeight
|
||||||
|
if self.displayStickerInput, let stickerContent = self.stickerContent {
|
||||||
|
let stickerSelectionControl: ComponentView<Empty>
|
||||||
|
var animateIn = false
|
||||||
|
if let current = self.stickerSelectionControl {
|
||||||
|
stickerSelectionControl = current
|
||||||
|
} else {
|
||||||
|
animateIn = true
|
||||||
|
stickerSelectionControl = ComponentView()
|
||||||
|
self.stickerSelectionControl = stickerSelectionControl
|
||||||
|
}
|
||||||
|
var selectedItems = Set<MediaId>()
|
||||||
|
if let stickerFile = self.stickerFile {
|
||||||
|
selectedItems.insert(stickerFile.fileId)
|
||||||
|
}
|
||||||
|
let stickerSelectionControlSize = stickerSelectionControl.update(
|
||||||
|
transition: animateIn ? .immediate : transition,
|
||||||
|
component: AnyComponent(EmojiSelectionComponent(
|
||||||
|
theme: environment.theme,
|
||||||
|
strings: environment.strings,
|
||||||
|
sideInset: environment.safeInsets.left,
|
||||||
|
bottomInset: environment.safeInsets.bottom,
|
||||||
|
deviceMetrics: environment.deviceMetrics,
|
||||||
|
emojiContent: stickerContent.withSelectedItems(selectedItems),
|
||||||
|
backgroundIconColor: nil,
|
||||||
|
backgroundColor: environment.theme.list.itemBlocksBackgroundColor,
|
||||||
|
separatorColor: environment.theme.list.itemBlocksSeparatorColor,
|
||||||
|
backspace: nil
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: availableSize.width, height: min(340.0, max(50.0, availableSize.height - 200.0)))
|
||||||
|
)
|
||||||
|
let stickerSelectionControlFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - stickerSelectionControlSize.height), size: stickerSelectionControlSize)
|
||||||
|
if let stickerSelectionControlView = stickerSelectionControl.view {
|
||||||
|
if stickerSelectionControlView.superview == nil {
|
||||||
|
self.addSubview(stickerSelectionControlView)
|
||||||
|
}
|
||||||
|
if animateIn {
|
||||||
|
stickerSelectionControlView.frame = stickerSelectionControlFrame
|
||||||
|
transition.animatePosition(view: stickerSelectionControlView, from: CGPoint(x: 0.0, y: stickerSelectionControlFrame.height), to: CGPoint(), additive: true)
|
||||||
|
} else {
|
||||||
|
transition.setFrame(view: stickerSelectionControlView, frame: stickerSelectionControlFrame)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inputHeight = stickerSelectionControlSize.height
|
||||||
|
} else if let stickerSelectionControl = self.stickerSelectionControl {
|
||||||
|
self.stickerSelectionControl = nil
|
||||||
|
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
|
||||||
|
stickerSelectionControlView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contentHeight += max(inputHeight, environment.safeInsets.bottom)
|
||||||
|
|
||||||
|
let previousBounds = self.scrollView.bounds
|
||||||
|
|
||||||
|
let contentSize = CGSize(width: availableSize.width, height: contentHeight)
|
||||||
|
if self.scrollView.frame != CGRect(origin: CGPoint(), size: availableSize) {
|
||||||
|
self.scrollView.frame = CGRect(origin: CGPoint(), size: availableSize)
|
||||||
|
}
|
||||||
|
if self.scrollView.contentSize != contentSize {
|
||||||
|
self.scrollView.contentSize = contentSize
|
||||||
|
}
|
||||||
|
let scrollInsets = UIEdgeInsets(top: environment.navigationHeight, left: 0.0, bottom: 0.0, right: 0.0)
|
||||||
|
if self.scrollView.scrollIndicatorInsets != scrollInsets {
|
||||||
|
self.scrollView.scrollIndicatorInsets = scrollInsets
|
||||||
|
}
|
||||||
|
|
||||||
|
if !previousBounds.isEmpty, !transition.animation.isImmediate {
|
||||||
|
let bounds = self.scrollView.bounds
|
||||||
|
if bounds.maxY != previousBounds.maxY {
|
||||||
|
let offsetY = previousBounds.maxY - bounds.maxY
|
||||||
|
transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: offsetY), to: CGPoint(), additive: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.topOverscrollLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: -3000.0), size: CGSize(width: availableSize.width, height: 3000.0))
|
||||||
|
|
||||||
|
self.updateScrolling(transition: transition)
|
||||||
|
|
||||||
|
return availableSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeView() -> View {
|
||||||
|
return View()
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||||
|
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class BusinessIntroSetupScreen: ViewControllerComponentContainer {
|
||||||
|
private let context: AccountContext
|
||||||
|
|
||||||
|
public init(
|
||||||
|
context: AccountContext
|
||||||
|
) {
|
||||||
|
self.context = context
|
||||||
|
|
||||||
|
super.init(context: context, component: BusinessIntroSetupScreenComponent(
|
||||||
|
context: context
|
||||||
|
), navigationBarAppearance: .default, theme: .default, updatedPresentationData: nil)
|
||||||
|
|
||||||
|
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.scrollToTop = { [weak self] in
|
||||||
|
guard let self, let componentView = self.node.hostView.componentView as? BusinessIntroSetupScreenComponent.View else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
componentView.scrollToTop()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.attemptNavigation = { [weak self] complete in
|
||||||
|
guard let self, let componentView = self.node.hostView.componentView as? BusinessIntroSetupScreenComponent.View else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return componentView.attemptNavigation(complete: complete)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
required public init(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func cancelPressed() {
|
||||||
|
self.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
|
super.containerLayoutUpdated(layout, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,161 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import ComponentFlow
|
||||||
|
import ListSectionComponent
|
||||||
|
import TelegramPresentationData
|
||||||
|
import AppBundle
|
||||||
|
import AccountContext
|
||||||
|
import ChatEmptyNode
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import WallpaperBackgroundNode
|
||||||
|
import ComponentDisplayAdapters
|
||||||
|
import TelegramCore
|
||||||
|
import ChatPresentationInterfaceState
|
||||||
|
|
||||||
|
final class ChatIntroItemComponent: Component {
|
||||||
|
let context: AccountContext
|
||||||
|
let theme: PresentationTheme
|
||||||
|
let strings: PresentationStrings
|
||||||
|
let stickerFile: TelegramMediaFile?
|
||||||
|
let title: String
|
||||||
|
let text: String
|
||||||
|
|
||||||
|
init(
|
||||||
|
context: AccountContext,
|
||||||
|
theme: PresentationTheme,
|
||||||
|
strings: PresentationStrings,
|
||||||
|
stickerFile: TelegramMediaFile?,
|
||||||
|
title: String,
|
||||||
|
text: String
|
||||||
|
) {
|
||||||
|
self.context = context
|
||||||
|
self.theme = theme
|
||||||
|
self.strings = strings
|
||||||
|
self.stickerFile = stickerFile
|
||||||
|
self.title = title
|
||||||
|
self.text = text
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: ChatIntroItemComponent, rhs: ChatIntroItemComponent) -> Bool {
|
||||||
|
if lhs.context !== rhs.context {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.theme !== rhs.theme {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.strings !== rhs.strings {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.stickerFile != rhs.stickerFile {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.title != rhs.title {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.text != rhs.text {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
final class View: UIView, ListSectionComponent.ChildView {
|
||||||
|
private var component: ChatIntroItemComponent?
|
||||||
|
private weak var componentState: EmptyComponentState?
|
||||||
|
|
||||||
|
private var backgroundNode: WallpaperBackgroundNode?
|
||||||
|
private var emptyNode: ChatEmptyNode?
|
||||||
|
|
||||||
|
var customUpdateIsHighlighted: ((Bool) -> Void)?
|
||||||
|
private(set) var separatorInset: CGFloat = 0.0
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(component: ChatIntroItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
|
self.component = component
|
||||||
|
self.componentState = state
|
||||||
|
|
||||||
|
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
|
||||||
|
|
||||||
|
let size = CGSize(width: availableSize.width, height: 346.0)
|
||||||
|
|
||||||
|
let backgroundNode: WallpaperBackgroundNode
|
||||||
|
if let current = self.backgroundNode {
|
||||||
|
backgroundNode = current
|
||||||
|
} else {
|
||||||
|
backgroundNode = createWallpaperBackgroundNode(context: component.context, forChatDisplay: false)
|
||||||
|
self.backgroundNode = backgroundNode
|
||||||
|
self.addSubview(backgroundNode.view)
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.setFrame(view: backgroundNode.view, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
backgroundNode.update(wallpaper: presentationData.chatWallpaper, animated: false)
|
||||||
|
backgroundNode.updateLayout(size: size, displayMode: .aspectFill, transition: transition.containedViewLayoutTransition)
|
||||||
|
|
||||||
|
let emptyNode: ChatEmptyNode
|
||||||
|
if let current = self.emptyNode {
|
||||||
|
emptyNode = current
|
||||||
|
} else {
|
||||||
|
emptyNode = ChatEmptyNode(context: component.context, interaction: nil)
|
||||||
|
self.emptyNode = emptyNode
|
||||||
|
self.addSubview(emptyNode.view)
|
||||||
|
}
|
||||||
|
|
||||||
|
let interfaceState = ChatPresentationInterfaceState(
|
||||||
|
chatWallpaper: presentationData.chatWallpaper,
|
||||||
|
theme: component.theme,
|
||||||
|
strings: component.strings,
|
||||||
|
dateTimeFormat: presentationData.dateTimeFormat,
|
||||||
|
nameDisplayOrder: presentationData.nameDisplayOrder,
|
||||||
|
limitsConfiguration: component.context.currentLimitsConfiguration.with { $0 },
|
||||||
|
fontSize: presentationData.chatFontSize,
|
||||||
|
bubbleCorners: presentationData.chatBubbleCorners,
|
||||||
|
accountPeerId: component.context.account.peerId,
|
||||||
|
mode: .standard(.default),
|
||||||
|
chatLocation: .peer(id: component.context.account.peerId),
|
||||||
|
subject: nil,
|
||||||
|
peerNearbyData: nil,
|
||||||
|
greetingData: nil,
|
||||||
|
pendingUnpinnedAllMessages: false,
|
||||||
|
activeGroupCallInfo: nil,
|
||||||
|
hasActiveGroupCall: false,
|
||||||
|
importState: nil,
|
||||||
|
threadData: nil,
|
||||||
|
isGeneralThreadClosed: nil,
|
||||||
|
replyMessage: nil,
|
||||||
|
accountPeerColor: nil
|
||||||
|
)
|
||||||
|
|
||||||
|
transition.setFrame(view: emptyNode.view, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
emptyNode.updateLayout(
|
||||||
|
interfaceState: interfaceState,
|
||||||
|
subject: .emptyChat(.customGreeting(
|
||||||
|
sticker: component.stickerFile,
|
||||||
|
title: component.title,
|
||||||
|
text: component.text
|
||||||
|
)),
|
||||||
|
loadingNode: nil,
|
||||||
|
backgroundNode: backgroundNode,
|
||||||
|
size: size,
|
||||||
|
insets: UIEdgeInsets(),
|
||||||
|
transition: .immediate
|
||||||
|
)
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeView() -> View {
|
||||||
|
return View(frame: CGRect())
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
|
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
@ -509,7 +509,6 @@ final class BusinessLocationSetupScreenComponent: Component {
|
|||||||
contentHeight += mapSectionSize.height
|
contentHeight += mapSectionSize.height
|
||||||
|
|
||||||
var deleteSectionHeight: CGFloat = 0.0
|
var deleteSectionHeight: CGFloat = 0.0
|
||||||
|
|
||||||
deleteSectionHeight += sectionSpacing
|
deleteSectionHeight += sectionSpacing
|
||||||
let deleteSectionSize = self.deleteSection.update(
|
let deleteSectionSize = self.deleteSection.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
|
@ -51,6 +51,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/GroupStickerPackSetupController",
|
"//submodules/TelegramUI/Components/GroupStickerPackSetupController",
|
||||||
"//submodules/TelegramUI/Components/Chat/ChatMessageItemImpl",
|
"//submodules/TelegramUI/Components/Chat/ChatMessageItemImpl",
|
||||||
"//submodules/TelegramUI/Components/Settings/PeerNameColorItem",
|
"//submodules/TelegramUI/Components/Settings/PeerNameColorItem",
|
||||||
|
"//submodules/TelegramUI/Components/EmojiActionIconComponent",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -38,100 +38,7 @@ import BundleIconComponent
|
|||||||
import Markdown
|
import Markdown
|
||||||
import GroupStickerPackSetupController
|
import GroupStickerPackSetupController
|
||||||
import PeerNameColorItem
|
import PeerNameColorItem
|
||||||
|
import EmojiActionIconComponent
|
||||||
private final class EmojiActionIconComponent: Component {
|
|
||||||
let context: AccountContext
|
|
||||||
let color: UIColor
|
|
||||||
let fileId: Int64?
|
|
||||||
let file: TelegramMediaFile?
|
|
||||||
|
|
||||||
init(
|
|
||||||
context: AccountContext,
|
|
||||||
color: UIColor,
|
|
||||||
fileId: Int64?,
|
|
||||||
file: TelegramMediaFile?
|
|
||||||
) {
|
|
||||||
self.context = context
|
|
||||||
self.color = color
|
|
||||||
self.fileId = fileId
|
|
||||||
self.file = file
|
|
||||||
}
|
|
||||||
|
|
||||||
static func ==(lhs: EmojiActionIconComponent, rhs: EmojiActionIconComponent) -> Bool {
|
|
||||||
if lhs.context !== rhs.context {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.color != rhs.color {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.fileId != rhs.fileId {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.file != rhs.file {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
final class View: UIView {
|
|
||||||
private var icon: ComponentView<Empty>?
|
|
||||||
|
|
||||||
func update(component: EmojiActionIconComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
|
||||||
let size = CGSize(width: 24.0, height: 24.0)
|
|
||||||
|
|
||||||
if let fileId = component.fileId {
|
|
||||||
let icon: ComponentView<Empty>
|
|
||||||
if let current = self.icon {
|
|
||||||
icon = current
|
|
||||||
} else {
|
|
||||||
icon = ComponentView()
|
|
||||||
self.icon = icon
|
|
||||||
}
|
|
||||||
let _ = icon.update(
|
|
||||||
transition: .immediate,
|
|
||||||
component: AnyComponent(EmojiStatusComponent(
|
|
||||||
context: component.context,
|
|
||||||
animationCache: component.context.animationCache,
|
|
||||||
animationRenderer: component.context.animationRenderer,
|
|
||||||
content: .animation(
|
|
||||||
content: .customEmoji(fileId: fileId),
|
|
||||||
size: size,
|
|
||||||
placeholderColor: .lightGray,
|
|
||||||
themeColor: component.color,
|
|
||||||
loopMode: .forever
|
|
||||||
),
|
|
||||||
isVisibleForAnimations: false,
|
|
||||||
action: nil
|
|
||||||
)),
|
|
||||||
environment: {},
|
|
||||||
containerSize: size
|
|
||||||
)
|
|
||||||
let iconFrame = CGRect(origin: CGPoint(), size: size)
|
|
||||||
if let iconView = icon.view {
|
|
||||||
if iconView.superview == nil {
|
|
||||||
self.addSubview(iconView)
|
|
||||||
}
|
|
||||||
iconView.frame = iconFrame
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if let icon = self.icon {
|
|
||||||
self.icon = nil
|
|
||||||
icon.view?.removeFromSuperview()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeView() -> View {
|
|
||||||
return View(frame: CGRect())
|
|
||||||
}
|
|
||||||
|
|
||||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
|
||||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final class ChannelAppearanceScreenComponent: Component {
|
final class ChannelAppearanceScreenComponent: Component {
|
||||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||||
|
@ -27,6 +27,7 @@ public final class TextFieldComponent: Component {
|
|||||||
public final class ExternalState {
|
public final class ExternalState {
|
||||||
public fileprivate(set) var isEditing: Bool = false
|
public fileprivate(set) var isEditing: Bool = false
|
||||||
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 textLength: Int = 0
|
public fileprivate(set) var textLength: Int = 0
|
||||||
public var initialText: NSAttributedString?
|
public var initialText: NSAttributedString?
|
||||||
|
|
||||||
@ -87,6 +88,7 @@ public final class TextFieldComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public let context: AccountContext
|
public let context: AccountContext
|
||||||
|
public let theme: PresentationTheme
|
||||||
public let strings: PresentationStrings
|
public let strings: PresentationStrings
|
||||||
public let externalState: ExternalState
|
public let externalState: ExternalState
|
||||||
public let fontSize: CGFloat
|
public let fontSize: CGFloat
|
||||||
@ -105,6 +107,7 @@ public final class TextFieldComponent: Component {
|
|||||||
|
|
||||||
public init(
|
public init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
|
theme: PresentationTheme,
|
||||||
strings: PresentationStrings,
|
strings: PresentationStrings,
|
||||||
externalState: ExternalState,
|
externalState: ExternalState,
|
||||||
fontSize: CGFloat,
|
fontSize: CGFloat,
|
||||||
@ -122,6 +125,7 @@ public final class TextFieldComponent: Component {
|
|||||||
paste: @escaping (PasteData) -> Void
|
paste: @escaping (PasteData) -> Void
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.externalState = externalState
|
self.externalState = externalState
|
||||||
self.fontSize = fontSize
|
self.fontSize = fontSize
|
||||||
@ -140,6 +144,12 @@ public final class TextFieldComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: TextFieldComponent, rhs: TextFieldComponent) -> Bool {
|
public static func ==(lhs: TextFieldComponent, rhs: TextFieldComponent) -> Bool {
|
||||||
|
if lhs.context !== rhs.context {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.theme !== rhs.theme {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.strings !== rhs.strings {
|
if lhs.strings !== rhs.strings {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -219,7 +229,6 @@ public final class TextFieldComponent: Component {
|
|||||||
self.textView.translatesAutoresizingMaskIntoConstraints = false
|
self.textView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
self.textView.backgroundColor = nil
|
self.textView.backgroundColor = nil
|
||||||
self.textView.layer.isOpaque = false
|
self.textView.layer.isOpaque = false
|
||||||
self.textView.keyboardAppearance = .dark
|
|
||||||
self.textView.indicatorStyle = .white
|
self.textView.indicatorStyle = .white
|
||||||
self.textView.scrollIndicatorInsets = UIEdgeInsets(top: 9.0, left: 0.0, bottom: 9.0, right: 0.0)
|
self.textView.scrollIndicatorInsets = UIEdgeInsets(top: 9.0, left: 0.0, bottom: 9.0, right: 0.0)
|
||||||
|
|
||||||
@ -232,10 +241,6 @@ public final class TextFieldComponent: Component {
|
|||||||
self.textView.customDelegate = self
|
self.textView.customDelegate = self
|
||||||
self.addSubview(self.textView)
|
self.addSubview(self.textView)
|
||||||
|
|
||||||
if #available(iOS 13.0, *) {
|
|
||||||
self.textView.overrideUserInterfaceStyle = .dark
|
|
||||||
}
|
|
||||||
|
|
||||||
self.textView.typingAttributes = [
|
self.textView.typingAttributes = [
|
||||||
NSAttributedString.Key.font: Font.regular(17.0),
|
NSAttributedString.Key.font: Font.regular(17.0),
|
||||||
NSAttributedString.Key.foregroundColor: UIColor.white
|
NSAttributedString.Key.foregroundColor: UIColor.white
|
||||||
@ -724,7 +729,7 @@ public final class TextFieldComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme)
|
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: component.theme)
|
||||||
let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>) = (presentationData, .single(presentationData))
|
let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>) = (presentationData, .single(presentationData))
|
||||||
let controller = chatTextLinkEditController(sharedContext: component.context.sharedContext, updatedPresentationData: updatedPresentationData, account: component.context.account, text: text.string, link: link, apply: { [weak self] link in
|
let controller = chatTextLinkEditController(sharedContext: component.context.sharedContext, updatedPresentationData: updatedPresentationData, account: component.context.account, text: text.string, link: link, apply: { [weak self] link in
|
||||||
if let self {
|
if let self {
|
||||||
@ -1048,9 +1053,17 @@ public final class TextFieldComponent: Component {
|
|||||||
self.isUpdating = false
|
self.isUpdating = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let previousComponent = self.component
|
||||||
self.component = component
|
self.component = component
|
||||||
self.state = state
|
self.state = state
|
||||||
|
|
||||||
|
if previousComponent?.theme !== component.theme {
|
||||||
|
self.textView.keyboardAppearance = component.theme.overallDarkAppearance ? .dark : .light
|
||||||
|
if #available(iOS 13.0, *) {
|
||||||
|
self.textView.overrideUserInterfaceStyle = component.theme.overallDarkAppearance ? .dark : .light
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let initialText = component.externalState.initialText {
|
if let initialText = component.externalState.initialText {
|
||||||
component.externalState.initialText = nil
|
component.externalState.initialText = nil
|
||||||
self.updateInputState { _ in
|
self.updateInputState { _ in
|
||||||
@ -1128,6 +1141,7 @@ public final class TextFieldComponent: Component {
|
|||||||
component.externalState.hasText = self.textView.textStorage.length != 0
|
component.externalState.hasText = self.textView.textStorage.length != 0
|
||||||
component.externalState.isEditing = isEditing
|
component.externalState.isEditing = isEditing
|
||||||
component.externalState.textLength = self.textView.textStorage.string.count
|
component.externalState.textLength = self.textView.textStorage.string.count
|
||||||
|
component.externalState.text = NSAttributedString(attributedString: self.textView.textStorage)
|
||||||
|
|
||||||
if let inputView = component.customInputView {
|
if let inputView = component.customInputView {
|
||||||
if self.textView.inputView == nil {
|
if self.textView.inputView == nil {
|
||||||
|
@ -121,6 +121,8 @@ import TopMessageReactions
|
|||||||
import PeerInfoScreen
|
import PeerInfoScreen
|
||||||
import AudioWaveform
|
import AudioWaveform
|
||||||
import PeerNameColorScreen
|
import PeerNameColorScreen
|
||||||
|
import ChatEmptyNode
|
||||||
|
import ChatMediaInputStickerGridItem
|
||||||
|
|
||||||
public enum ChatControllerPeekActions {
|
public enum ChatControllerPeekActions {
|
||||||
case standard
|
case standard
|
||||||
|
@ -42,6 +42,7 @@ import UIKitRuntimeUtils
|
|||||||
import ChatInlineSearchResultsListComponent
|
import ChatInlineSearchResultsListComponent
|
||||||
import ComponentDisplayAdapters
|
import ComponentDisplayAdapters
|
||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
|
import ChatEmptyNode
|
||||||
|
|
||||||
final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem {
|
final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem {
|
||||||
let itemNode: OverlayMediaItemNode
|
let itemNode: OverlayMediaItemNode
|
||||||
@ -990,7 +991,20 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
self.emptyNode = emptyNode
|
self.emptyNode = emptyNode
|
||||||
self.historyNodeContainer.supernode?.insertSubnode(emptyNode, aboveSubnode: self.historyNodeContainer)
|
self.historyNodeContainer.supernode?.insertSubnode(emptyNode, aboveSubnode: self.historyNodeContainer)
|
||||||
if let (size, insets) = self.validEmptyNodeLayout {
|
if let (size, insets) = self.validEmptyNodeLayout {
|
||||||
emptyNode.updateLayout(interfaceState: self.chatPresentationInterfaceState, subject: .emptyChat(emptyType), loadingNode: wasLoading && self.loadingNode.supernode != nil ? self.loadingNode : nil, backgroundNode: self.backgroundNode, size: size, insets: insets, transition: .immediate)
|
let mappedType: ChatEmptyNode.Subject.EmptyType
|
||||||
|
switch emptyType {
|
||||||
|
case .generic:
|
||||||
|
mappedType = .generic
|
||||||
|
case .joined:
|
||||||
|
mappedType = .joined
|
||||||
|
case .clearedHistory:
|
||||||
|
mappedType = .clearedHistory
|
||||||
|
case .topic:
|
||||||
|
mappedType = .topic
|
||||||
|
case .botInfo:
|
||||||
|
mappedType = .botInfo
|
||||||
|
}
|
||||||
|
emptyNode.updateLayout(interfaceState: self.chatPresentationInterfaceState, subject: .emptyChat(mappedType), loadingNode: wasLoading && self.loadingNode.supernode != nil ? self.loadingNode : nil, backgroundNode: self.backgroundNode, size: size, insets: insets, transition: .immediate)
|
||||||
}
|
}
|
||||||
if animated {
|
if animated {
|
||||||
emptyNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
emptyNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
@ -1842,7 +1856,20 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
emptyNodeInsets.bottom += inputPanelsHeight
|
emptyNodeInsets.bottom += inputPanelsHeight
|
||||||
self.validEmptyNodeLayout = (contentBounds.size, emptyNodeInsets)
|
self.validEmptyNodeLayout = (contentBounds.size, emptyNodeInsets)
|
||||||
if let emptyNode = self.emptyNode, let emptyType = self.emptyType {
|
if let emptyNode = self.emptyNode, let emptyType = self.emptyType {
|
||||||
emptyNode.updateLayout(interfaceState: self.chatPresentationInterfaceState, subject: .emptyChat(emptyType), loadingNode: nil, backgroundNode: self.backgroundNode, size: contentBounds.size, insets: emptyNodeInsets, transition: transition)
|
let mappedType: ChatEmptyNode.Subject.EmptyType
|
||||||
|
switch emptyType {
|
||||||
|
case .generic:
|
||||||
|
mappedType = .generic
|
||||||
|
case .joined:
|
||||||
|
mappedType = .joined
|
||||||
|
case .clearedHistory:
|
||||||
|
mappedType = .clearedHistory
|
||||||
|
case .topic:
|
||||||
|
mappedType = .topic
|
||||||
|
case .botInfo:
|
||||||
|
mappedType = .botInfo
|
||||||
|
}
|
||||||
|
emptyNode.updateLayout(interfaceState: self.chatPresentationInterfaceState, subject: .emptyChat(mappedType), loadingNode: nil, backgroundNode: self.backgroundNode, size: contentBounds.size, insets: emptyNodeInsets, transition: transition)
|
||||||
transition.updateFrame(node: emptyNode, frame: contentBounds)
|
transition.updateFrame(node: emptyNode, frame: contentBounds)
|
||||||
emptyNode.update(rect: contentBounds, within: contentBounds.size, transition: transition)
|
emptyNode.update(rect: contentBounds, within: contentBounds.size, transition: transition)
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,8 @@ import ChatMessageInstantVideoItemNode
|
|||||||
import ChatMessageAnimatedStickerItemNode
|
import ChatMessageAnimatedStickerItemNode
|
||||||
import ChatMessageTransitionNode
|
import ChatMessageTransitionNode
|
||||||
import ChatMessageBubbleItemNode
|
import ChatMessageBubbleItemNode
|
||||||
|
import ChatEmptyNode
|
||||||
|
import ChatMediaInputStickerGridItem
|
||||||
|
|
||||||
private func convertAnimatingSourceRect(_ rect: CGRect, fromView: UIView, toView: UIView?) -> CGRect {
|
private func convertAnimatingSourceRect(_ rect: CGRect, fromView: UIView, toView: UIView?) -> CGRect {
|
||||||
if let presentationLayer = fromView.layer.presentation() {
|
if let presentationLayer = fromView.layer.presentation() {
|
||||||
|
@ -332,10 +332,18 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let addedToken = addedToken {
|
||||||
|
strongSelf.contactsNode.editableTokens.append(addedToken)
|
||||||
|
} else if let removedTokenId = removedTokenId {
|
||||||
|
strongSelf.contactsNode.editableTokens = strongSelf.contactsNode.editableTokens.filter { token in
|
||||||
|
return token.id != removedTokenId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let updatedCount = updatedCount {
|
if let updatedCount = updatedCount {
|
||||||
switch strongSelf.mode {
|
switch strongSelf.mode {
|
||||||
case .groupCreation, .peerSelection, .chatSelection:
|
case .groupCreation, .peerSelection, .chatSelection:
|
||||||
strongSelf.rightNavigationButton?.isEnabled = updatedCount != 0 || strongSelf.params.alwaysEnabled
|
strongSelf.rightNavigationButton?.isEnabled = updatedCount != 0 || !strongSelf.contactsNode.editableTokens.isEmpty || strongSelf.params.alwaysEnabled
|
||||||
case .channelCreation, .premiumGifting, .requestedUsersSelection:
|
case .channelCreation, .premiumGifting, .requestedUsersSelection:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -355,13 +363,6 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let addedToken = addedToken {
|
|
||||||
strongSelf.contactsNode.editableTokens.append(addedToken)
|
|
||||||
} else if let removedTokenId = removedTokenId {
|
|
||||||
strongSelf.contactsNode.editableTokens = strongSelf.contactsNode.editableTokens.filter { token in
|
|
||||||
return token.id != removedTokenId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
strongSelf.requestLayout(transition: ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring))
|
strongSelf.requestLayout(transition: ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring))
|
||||||
|
|
||||||
if displayCountAlert {
|
if displayCountAlert {
|
||||||
|
@ -59,6 +59,7 @@ import CollectibleItemInfoScreen
|
|||||||
import StickerPickerScreen
|
import StickerPickerScreen
|
||||||
import MediaEditor
|
import MediaEditor
|
||||||
import MediaEditorScreen
|
import MediaEditorScreen
|
||||||
|
import BusinessIntroSetupScreen
|
||||||
|
|
||||||
private final class AccountUserInterfaceInUseContext {
|
private final class AccountUserInterfaceInUseContext {
|
||||||
let subscribers = Bag<(Bool) -> Void>()
|
let subscribers = Bag<(Bool) -> Void>()
|
||||||
@ -1927,6 +1928,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
return QuickReplySetupScreen.initialData(context: context)
|
return QuickReplySetupScreen.initialData(context: context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func makeBusinessIntroSetupScreen(context: AccountContext) -> ViewController {
|
||||||
|
return BusinessIntroSetupScreen(context: context)
|
||||||
|
}
|
||||||
|
|
||||||
public func makeCollectibleItemInfoScreen(context: AccountContext, initialData: CollectibleItemInfoScreenInitialData) -> ViewController {
|
public func makeCollectibleItemInfoScreen(context: AccountContext, initialData: CollectibleItemInfoScreenInitialData) -> ViewController {
|
||||||
return CollectibleItemInfoScreen(context: context, initialData: initialData as! CollectibleItemInfoScreen.InitialData)
|
return CollectibleItemInfoScreen(context: context, initialData: initialData as! CollectibleItemInfoScreen.InitialData)
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ import MediaEditor
|
|||||||
import PeerInfoScreen
|
import PeerInfoScreen
|
||||||
import PeerInfoStoryGridScreen
|
import PeerInfoStoryGridScreen
|
||||||
import ShareWithPeersScreen
|
import ShareWithPeersScreen
|
||||||
|
import ChatEmptyNode
|
||||||
|
|
||||||
private class DetailsChatPlaceholderNode: ASDisplayNode, NavigationDetailsPlaceholderNode {
|
private class DetailsChatPlaceholderNode: ASDisplayNode, NavigationDetailsPlaceholderNode {
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
|
Loading…
x
Reference in New Issue
Block a user