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 BusinessIntroSetupScreenInitialData: AnyObject {
|
||||
}
|
||||
|
||||
public protocol CollectibleItemInfoScreenInitialData: AnyObject {
|
||||
var collectibleItemInfo: TelegramCollectibleItemInfo { get }
|
||||
}
|
||||
@ -960,6 +963,7 @@ public protocol SharedAccountContext: AnyObject {
|
||||
func makeAutomaticBusinessMessageSetupScreenInitialData(context: AccountContext) -> Signal<AutomaticBusinessMessageSetupScreenInitialData, NoError>
|
||||
func makeQuickReplySetupScreen(context: AccountContext, initialData: QuickReplySetupScreenInitialData) -> ViewController
|
||||
func makeQuickReplySetupScreenInitialData(context: AccountContext) -> Signal<QuickReplySetupScreenInitialData, NoError>
|
||||
func makeBusinessIntroSetupScreen(context: AccountContext) -> ViewController
|
||||
func makeCollectibleItemInfoScreen(context: AccountContext, initialData: CollectibleItemInfoScreenInitialData) -> ViewController
|
||||
func makeCollectibleItemInfoScreenInitialData(context: AccountContext, peerId: EnginePeer.Id, subject: CollectibleItemInfoScreenSubject) -> Signal<CollectibleItemInfoScreenInitialData?, NoError>
|
||||
func navigateToChatController(_ params: NavigateToChatControllerParams)
|
||||
|
@ -5,11 +5,13 @@ public final class Rectangle: Component {
|
||||
private let color: UIColor
|
||||
private let width: 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.width = width
|
||||
self.height = height
|
||||
self.tag = tag
|
||||
}
|
||||
|
||||
public static func ==(lhs: Rectangle, rhs: Rectangle) -> Bool {
|
||||
@ -25,7 +27,33 @@ public final class Rectangle: Component {
|
||||
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
|
||||
if let width = self.width {
|
||||
size.width = min(size.width, width)
|
||||
@ -35,6 +63,7 @@ public final class Rectangle: Component {
|
||||
}
|
||||
|
||||
view.backgroundColor = self.color
|
||||
view.componentTag = self.tag
|
||||
|
||||
return size
|
||||
}
|
||||
|
@ -13,11 +13,13 @@ public final class VStack<ChildEnvironment: Equatable>: CombinedComponent {
|
||||
private let items: [AnyComponentWithIdentity<ChildEnvironment>]
|
||||
private let alignment: VStackAlignment
|
||||
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.alignment = alignment
|
||||
self.spacing = spacing
|
||||
self.fillWidth = fillWidth
|
||||
}
|
||||
|
||||
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 {
|
||||
return false
|
||||
}
|
||||
if lhs.fillWidth != rhs.fillWidth {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -48,6 +53,9 @@ public final class VStack<ChildEnvironment: Equatable>: CombinedComponent {
|
||||
}
|
||||
|
||||
var size = CGSize(width: 0.0, height: 0.0)
|
||||
if context.component.fillWidth {
|
||||
size.width = context.availableSize.width
|
||||
}
|
||||
for child in updatedChildren {
|
||||
size.height += child.size.height
|
||||
size.width = max(size.width, child.size.width)
|
||||
|
@ -117,6 +117,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/EmojiStatusSelectionComponent",
|
||||
"//submodules/TelegramUI/Components/EntityKeyboard",
|
||||
"//submodules/TelegramUI/Components/PremiumPeerShortcutComponent",
|
||||
"//submodules/TelegramUI/Components/EmojiActionIconComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -32,6 +32,7 @@ import ListActionItemComponent
|
||||
import EmojiStatusSelectionComponent
|
||||
import EmojiStatusComponent
|
||||
import EntityKeyboard
|
||||
import EmojiActionIconComponent
|
||||
|
||||
public enum PremiumSource: Equatable {
|
||||
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))
|
||||
})
|
||||
case .businessIntro:
|
||||
push(accountContext.sharedContext.makeBusinessIntroSetupScreen(context: accountContext))
|
||||
default:
|
||||
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 {
|
||||
let color: UIColor
|
||||
let text: String
|
||||
|
@ -909,6 +909,34 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present
|
||||
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(
|
||||
context: AccountContext,
|
||||
kind: SelectivePrivacySettingsKind,
|
||||
@ -1041,7 +1069,41 @@ func selectivePrivacySettingsController(
|
||||
return state
|
||||
}
|
||||
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
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak controller] result in
|
||||
@ -1128,7 +1190,15 @@ func selectivePrivacySettingsController(
|
||||
}))
|
||||
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
} 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
|
||||
if enable {
|
||||
switch target {
|
||||
|
@ -248,7 +248,7 @@ private func selectivePrivacyPeersControllerEntries(presentationData: Presentati
|
||||
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 stateValue = Atomic(value: SelectivePrivacyPeersControllerState())
|
||||
let updateState: ((SelectivePrivacyPeersControllerState) -> SelectivePrivacyPeersControllerState) -> Void = { f in
|
||||
@ -307,7 +307,33 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri
|
||||
|
||||
removePeerDisposable.set(applyPeers.start())
|
||||
}, 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
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak controller] result in
|
||||
|
@ -436,9 +436,12 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/Settings/BusinessLocationSetupScreen",
|
||||
"//submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen",
|
||||
"//submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen",
|
||||
"//submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen",
|
||||
"//submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController",
|
||||
"//submodules/TelegramUI/Components/Settings/CollectibleItemInfoScreen",
|
||||
"//submodules/TelegramUI/Components/StickerPickerScreen",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatEmptyNode",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatMediaInputStickerGridItem",
|
||||
] + select({
|
||||
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
||||
"//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 Markdown
|
||||
import ReactionSelectionNode
|
||||
import ChatMediaInputStickerGridItem
|
||||
|
||||
private protocol ChatEmptyNodeContent {
|
||||
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 }
|
||||
}
|
||||
|
||||
final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeStickerContentNode, ChatEmptyNodeContent, UIGestureRecognizerDelegate {
|
||||
public final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeStickerContentNode, ChatEmptyNodeContent, UIGestureRecognizerDelegate {
|
||||
private let context: AccountContext
|
||||
private let interaction: ChatPanelInterfaceInteraction?
|
||||
|
||||
@ -91,15 +92,16 @@ final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeSticke
|
||||
private let textNode: ImmediateTextNode
|
||||
|
||||
private var stickerItem: ChatMediaInputStickerGridItem?
|
||||
let stickerNode: ChatMediaInputStickerGridItemNode
|
||||
public var stickerNode: ChatMediaInputStickerGridItemNode
|
||||
|
||||
private var currentTheme: PresentationTheme?
|
||||
private var currentStrings: PresentationStrings?
|
||||
|
||||
private var didSetupSticker = false
|
||||
private let disposable = MetaDisposable()
|
||||
private var currentCustomStickerFile: TelegramMediaFile?
|
||||
|
||||
init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) {
|
||||
public init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) {
|
||||
self.context = context
|
||||
self.interaction = interaction
|
||||
|
||||
@ -126,7 +128,7 @@ final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeSticke
|
||||
self.addSubnode(self.stickerNode)
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
override public func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.stickerTapGesture(_:)))
|
||||
@ -138,7 +140,7 @@ final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeSticke
|
||||
self.disposable.dispose()
|
||||
}
|
||||
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
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, [])
|
||||
}
|
||||
|
||||
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 {
|
||||
self.currentTheme = interfaceState.theme
|
||||
self.currentStrings = interfaceState.strings
|
||||
}
|
||||
|
||||
var customStickerFile: TelegramMediaFile?
|
||||
|
||||
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.textNode.attributedText = NSAttributedString(string: interfaceState.strings.Conversation_GreetingText, font: messageFont, textColor: serviceColor.primaryText)
|
||||
}
|
||||
|
||||
let previousCustomStickerFile = self.currentCustomStickerFile
|
||||
self.currentCustomStickerFile = customStickerFile
|
||||
|
||||
let stickerSize: CGSize
|
||||
let inset: CGFloat
|
||||
if size.width == 320.0 {
|
||||
@ -170,11 +183,13 @@ final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeSticke
|
||||
stickerSize = CGSize(width: 160.0, height: 160.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)
|
||||
} else if !self.didSetupSticker {
|
||||
} else if !self.didSetupSticker || previousCustomStickerFile != customStickerFile {
|
||||
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
|
||||
} else {
|
||||
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.disposable.set((sticker
|
||||
|> 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 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
|
||||
|
||||
if isFirstTime {
|
||||
|
||||
}
|
||||
strongSelf.stickerNode.updateLayout(item: item, size: stickerSize, isVisible: true, synchronousLoads: true)
|
||||
strongSelf.stickerNode.isVisibleInGrid = 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 interaction: ChatPanelInterfaceInteraction?
|
||||
|
||||
@ -260,7 +292,7 @@ final class ChatEmptyNodeNearbyChatContent: ASDisplayNode, ChatEmptyNodeStickerC
|
||||
private let textNode: ImmediateTextNode
|
||||
|
||||
private var stickerItem: ChatMediaInputStickerGridItem?
|
||||
let stickerNode: ChatMediaInputStickerGridItemNode
|
||||
public let stickerNode: ChatMediaInputStickerGridItemNode
|
||||
|
||||
private var currentTheme: PresentationTheme?
|
||||
private var currentStrings: PresentationStrings?
|
||||
@ -268,7 +300,7 @@ final class ChatEmptyNodeNearbyChatContent: ASDisplayNode, ChatEmptyNodeStickerC
|
||||
private var didSetupSticker = false
|
||||
private let disposable = MetaDisposable()
|
||||
|
||||
init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) {
|
||||
public init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) {
|
||||
self.context = context
|
||||
self.interaction = interaction
|
||||
|
||||
@ -295,7 +327,7 @@ final class ChatEmptyNodeNearbyChatContent: ASDisplayNode, ChatEmptyNodeStickerC
|
||||
self.addSubnode(self.stickerNode)
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
override public func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.stickerTapGesture(_:)))
|
||||
@ -307,7 +339,7 @@ final class ChatEmptyNodeNearbyChatContent: ASDisplayNode, ChatEmptyNodeStickerC
|
||||
self.disposable.dispose()
|
||||
}
|
||||
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
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, [])
|
||||
}
|
||||
|
||||
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 {
|
||||
self.currentTheme = interfaceState.theme
|
||||
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 titleNode: ImmediateTextNode
|
||||
@ -855,7 +887,7 @@ final class ChatEmptyNodeTopicChatContent: ASDisplayNode, ChatEmptyNodeContent,
|
||||
|
||||
private let iconView: ComponentView<Empty>
|
||||
|
||||
init(context: AccountContext) {
|
||||
public init(context: AccountContext) {
|
||||
self.context = context
|
||||
|
||||
self.titleNode = ImmediateTextNode()
|
||||
@ -880,7 +912,7 @@ final class ChatEmptyNodeTopicChatContent: ASDisplayNode, ChatEmptyNodeContent,
|
||||
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)
|
||||
if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings {
|
||||
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 interaction: ChatPanelInterfaceInteraction?
|
||||
|
||||
@ -969,7 +1001,7 @@ final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatEmptyNod
|
||||
private var currentTheme: PresentationTheme?
|
||||
private var currentStrings: PresentationStrings?
|
||||
|
||||
init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) {
|
||||
public init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) {
|
||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
|
||||
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 maxWidth = min(200.0, size.width)
|
||||
@ -1139,9 +1171,18 @@ private enum ChatEmptyNodeContentType: Equatable {
|
||||
case premiumRequired
|
||||
}
|
||||
|
||||
final class ChatEmptyNode: ASDisplayNode {
|
||||
enum Subject {
|
||||
case emptyChat(ChatHistoryNodeLoadState.EmptyType)
|
||||
public final class ChatEmptyNode: ASDisplayNode {
|
||||
public enum Subject {
|
||||
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
|
||||
}
|
||||
private let context: AccountContext
|
||||
@ -1159,7 +1200,7 @@ final class ChatEmptyNode: ASDisplayNode {
|
||||
|
||||
private var content: (ChatEmptyNodeContentType, ASDisplayNode & ChatEmptyNodeContent)?
|
||||
|
||||
init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) {
|
||||
public init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) {
|
||||
self.context = context
|
||||
self.interaction = interaction
|
||||
|
||||
@ -1172,14 +1213,14 @@ final class ChatEmptyNode: ASDisplayNode {
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func animateFromLoadingNode(_ loadingNode: ChatLoadingNode) {
|
||||
public func animateFromLoadingNode(_ loadingNode: ChatLoadingNode) {
|
||||
guard let (_, node) = self.content else {
|
||||
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
|
||||
|
||||
if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings {
|
||||
@ -1224,7 +1265,9 @@ final class ChatEmptyNode: ASDisplayNode {
|
||||
case .detailsPlaceholder:
|
||||
contentType = .regular
|
||||
case let .emptyChat(emptyType):
|
||||
if case .customChatContents = interfaceState.subject {
|
||||
if case .customGreeting = emptyType {
|
||||
contentType = .greeting
|
||||
} else if case .customChatContents = interfaceState.subject {
|
||||
contentType = .cloud
|
||||
} else if case .replyThread = interfaceState.chatLocation {
|
||||
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 ChatPresentationInterfaceState
|
||||
|
||||
enum ChatMediaInputStickerGridSectionAccessory {
|
||||
public enum ChatMediaInputStickerGridSectionAccessory {
|
||||
case none
|
||||
case setup
|
||||
case clear
|
||||
}
|
||||
|
||||
final class ChatMediaInputStickerGridSection: GridSection {
|
||||
let collectionId: ItemCollectionId
|
||||
let collectionInfo: StickerPackCollectionInfo?
|
||||
let accessory: ChatMediaInputStickerGridSectionAccessory
|
||||
let interaction: ChatMediaInputNodeInteraction
|
||||
let theme: PresentationTheme
|
||||
let height: CGFloat = 26.0
|
||||
public final class ChatMediaInputStickerGridSection: GridSection {
|
||||
public let collectionId: ItemCollectionId
|
||||
public let collectionInfo: StickerPackCollectionInfo?
|
||||
public let accessory: ChatMediaInputStickerGridSectionAccessory
|
||||
public let interaction: ChatMediaInputNodeInteraction
|
||||
public let theme: PresentationTheme
|
||||
public let height: CGFloat = 26.0
|
||||
|
||||
var hashValue: Int {
|
||||
public var hashValue: Int {
|
||||
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.collectionInfo = collectionInfo
|
||||
self.accessory = accessory
|
||||
@ -40,7 +40,7 @@ final class ChatMediaInputStickerGridSection: GridSection {
|
||||
self.interaction = interaction
|
||||
}
|
||||
|
||||
func isEqual(to: GridSection) -> Bool {
|
||||
public func isEqual(to: GridSection) -> Bool {
|
||||
if let to = to as? ChatMediaInputStickerGridSection {
|
||||
return self.collectionId == to.collectionId && self.theme === to.theme
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
|
||||
private let sectionTitleFont = Font.medium(12.0)
|
||||
|
||||
final class ChatMediaInputStickerGridSectionNode: ASDisplayNode {
|
||||
let titleNode: ASTextNode
|
||||
let setupNode: HighlightableButtonNode?
|
||||
let interaction: ChatMediaInputNodeInteraction
|
||||
let accessory: ChatMediaInputStickerGridSectionAccessory
|
||||
public final class ChatMediaInputStickerGridSectionNode: ASDisplayNode {
|
||||
public let titleNode: ASTextNode
|
||||
public let setupNode: HighlightableButtonNode?
|
||||
public let interaction: ChatMediaInputNodeInteraction
|
||||
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.titleNode = ASTextNode()
|
||||
self.titleNode.isUserInteractionEnabled = false
|
||||
@ -91,7 +91,7 @@ final class ChatMediaInputStickerGridSectionNode: ASDisplayNode {
|
||||
self.setupNode?.addTarget(self, action: #selector(self.setupPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
override func layout() {
|
||||
override public func layout() {
|
||||
super.layout()
|
||||
|
||||
let bounds = self.bounds
|
||||
@ -116,20 +116,20 @@ final class ChatMediaInputStickerGridSectionNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
final class ChatMediaInputStickerGridItem: GridItem {
|
||||
let context: AccountContext
|
||||
let index: ItemCollectionViewEntryIndex
|
||||
let stickerItem: StickerPackItem
|
||||
let selected: () -> Void
|
||||
let interfaceInteraction: ChatControllerInteraction?
|
||||
let inputNodeInteraction: ChatMediaInputNodeInteraction
|
||||
let theme: PresentationTheme
|
||||
let large: Bool
|
||||
let isLocked: Bool
|
||||
public final class ChatMediaInputStickerGridItem: GridItem {
|
||||
public let context: AccountContext
|
||||
public let index: ItemCollectionViewEntryIndex
|
||||
public let stickerItem: StickerPackItem
|
||||
public let selected: () -> Void
|
||||
public let interfaceInteraction: ChatControllerInteraction?
|
||||
public let inputNodeInteraction: ChatMediaInputNodeInteraction
|
||||
public let theme: PresentationTheme
|
||||
public let large: 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.index = index
|
||||
self.stickerItem = stickerItem
|
||||
@ -145,7 +145,7 @@ final class ChatMediaInputStickerGridItem: GridItem {
|
||||
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()
|
||||
node.interfaceInteraction = self.interfaceInteraction
|
||||
node.inputNodeInteraction = self.inputNodeInteraction
|
||||
@ -153,7 +153,7 @@ final class ChatMediaInputStickerGridItem: GridItem {
|
||||
return node
|
||||
}
|
||||
|
||||
func update(node: GridItemNode) {
|
||||
public func update(node: GridItemNode) {
|
||||
guard let node = node as? ChatMediaInputStickerGridItemNode else {
|
||||
assertionFailure()
|
||||
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 currentSize: CGSize?
|
||||
let imageNode: TransformImageNode
|
||||
private(set) var animationNode: AnimatedStickerNode?
|
||||
private(set) var placeholderNode: StickerShimmerEffectNode?
|
||||
public let imageNode: TransformImageNode
|
||||
public private(set) var animationNode: AnimatedStickerNode?
|
||||
public private(set) var placeholderNode: StickerShimmerEffectNode?
|
||||
|
||||
private var lockBackground: UIVisualEffectView?
|
||||
private var lockTintView: UIView?
|
||||
private var lockIconNode: ASImageNode?
|
||||
var isLocked: Bool?
|
||||
public var isLocked: Bool?
|
||||
|
||||
private var didSetUpAnimationNode = false
|
||||
private var item: ChatMediaInputStickerGridItem?
|
||||
|
||||
private let stickerFetchedDisposable = MetaDisposable()
|
||||
|
||||
var currentIsPreviewing = false
|
||||
public var currentIsPreviewing = false
|
||||
|
||||
override var isVisibleInGrid: Bool {
|
||||
override public var isVisibleInGrid: Bool {
|
||||
didSet {
|
||||
self.updateVisibility()
|
||||
}
|
||||
@ -192,15 +192,15 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
|
||||
private var isPanelVisible = false
|
||||
private var isPlaying = false
|
||||
|
||||
var interfaceInteraction: ChatControllerInteraction?
|
||||
var inputNodeInteraction: ChatMediaInputNodeInteraction?
|
||||
var selected: (() -> Void)?
|
||||
public var interfaceInteraction: ChatControllerInteraction?
|
||||
public var inputNodeInteraction: ChatMediaInputNodeInteraction?
|
||||
public var selected: (() -> Void)?
|
||||
|
||||
var stickerPackItem: StickerPackItem? {
|
||||
public var stickerPackItem: StickerPackItem? {
|
||||
return self.currentState?.1
|
||||
}
|
||||
|
||||
override init() {
|
||||
override public init() {
|
||||
self.imageNode = TransformImageNode()
|
||||
self.placeholderNode = StickerShimmerEffectNode()
|
||||
self.placeholderNode?.isUserInteractionEnabled = false
|
||||
@ -244,13 +244,13 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
|
||||
}
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
override public func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
return
|
||||
}
|
||||
@ -411,18 +411,18 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
|
||||
}
|
||||
}
|
||||
|
||||
func transitionNode() -> ASDisplayNode? {
|
||||
public func transitionNode() -> ASDisplayNode? {
|
||||
return self.imageNode
|
||||
}
|
||||
|
||||
func updateIsPanelVisible(_ isPanelVisible: Bool) {
|
||||
public func updateIsPanelVisible(_ isPanelVisible: Bool) {
|
||||
if self.isPanelVisible != isPanelVisible {
|
||||
self.isPanelVisible = isPanelVisible
|
||||
self.updateVisibility()
|
||||
}
|
||||
}
|
||||
|
||||
func updateVisibility() {
|
||||
public func updateVisibility() {
|
||||
guard let item = self.item else {
|
||||
return
|
||||
}
|
||||
@ -444,7 +444,7 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
|
||||
}
|
||||
}
|
||||
|
||||
func updatePreviewing(animated: Bool) {
|
||||
public func updatePreviewing(animated: Bool) {
|
||||
var isPreviewing = false
|
||||
if let (_, item, _) = self.currentState, let interaction = self.inputNodeInteraction {
|
||||
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 ExternalState {
|
||||
public fileprivate(set) var hasText: Bool = false
|
||||
public fileprivate(set) var text: NSAttributedString = NSAttributedString()
|
||||
|
||||
public init() {
|
||||
}
|
||||
@ -206,6 +207,7 @@ public final class ListMultilineTextFieldItemComponent: Component {
|
||||
transition: transition,
|
||||
component: AnyComponent(TextFieldComponent(
|
||||
context: component.context,
|
||||
theme: component.theme,
|
||||
strings: component.strings,
|
||||
externalState: self.textFieldExternalState,
|
||||
fontSize: 17.0,
|
||||
@ -266,6 +268,7 @@ public final class ListMultilineTextFieldItemComponent: Component {
|
||||
self.separatorInset = 16.0
|
||||
|
||||
component.externalState?.hasText = self.textFieldExternalState.hasText
|
||||
component.externalState?.text = self.textFieldExternalState.text
|
||||
|
||||
return size
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ public final class ListSectionComponent: Component {
|
||||
public let header: AnyComponent<Empty>?
|
||||
public let footer: AnyComponent<Empty>?
|
||||
public let items: [AnyComponentWithIdentity<Empty>]
|
||||
public let itemUpdateOrder: [AnyHashable]?
|
||||
public let displaySeparators: Bool
|
||||
public let extendsItemHighlightToSection: Bool
|
||||
|
||||
@ -33,6 +34,7 @@ public final class ListSectionComponent: Component {
|
||||
header: AnyComponent<Empty>?,
|
||||
footer: AnyComponent<Empty>?,
|
||||
items: [AnyComponentWithIdentity<Empty>],
|
||||
itemUpdateOrder: [AnyHashable]? = nil,
|
||||
displaySeparators: Bool = true,
|
||||
extendsItemHighlightToSection: Bool = false
|
||||
) {
|
||||
@ -41,6 +43,7 @@ public final class ListSectionComponent: Component {
|
||||
self.header = header
|
||||
self.footer = footer
|
||||
self.items = items
|
||||
self.itemUpdateOrder = itemUpdateOrder
|
||||
self.displaySeparators = displaySeparators
|
||||
self.extendsItemHighlightToSection = extendsItemHighlightToSection
|
||||
}
|
||||
@ -61,6 +64,9 @@ public final class ListSectionComponent: Component {
|
||||
if lhs.items != rhs.items {
|
||||
return false
|
||||
}
|
||||
if lhs.itemUpdateOrder != rhs.itemUpdateOrder {
|
||||
return false
|
||||
}
|
||||
if lhs.displaySeparators != rhs.displaySeparators {
|
||||
return false
|
||||
}
|
||||
@ -204,7 +210,41 @@ public final class ListSectionComponent: Component {
|
||||
|
||||
var innerContentHeight: CGFloat = 0.0
|
||||
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 {
|
||||
if !itemUpdateOrder.contains(i) {
|
||||
itemUpdateOrder.append(i)
|
||||
}
|
||||
}
|
||||
|
||||
for i in itemUpdateOrder {
|
||||
let item = component.items[i]
|
||||
let itemId = item.id
|
||||
validItemIds.append(itemId)
|
||||
@ -226,17 +266,29 @@ public final class ListSectionComponent: Component {
|
||||
environment: {},
|
||||
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 {
|
||||
itemComponentView.customUpdateIsHighlighted = { [weak self] isHighlighted in
|
||||
guard let self else {
|
||||
@ -250,20 +302,20 @@ public final class ListSectionComponent: Component {
|
||||
if let itemComponentView = itemComponentView as? ChildView {
|
||||
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))
|
||||
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))
|
||||
itemTransition.setFrame(layer: itemView.separatorLayer, frame: itemSeparatorFrame)
|
||||
readyItem.itemTransition.setFrame(layer: readyItem.itemView.separatorLayer, frame: itemSeparatorFrame)
|
||||
|
||||
let separatorAlpha: CGFloat
|
||||
if component.displaySeparators {
|
||||
if i != component.items.count - 1 {
|
||||
if readyItem.index != component.items.count - 1 {
|
||||
separatorAlpha = 1.0
|
||||
} else {
|
||||
separatorAlpha = 0.0
|
||||
@ -271,11 +323,12 @@ public final class ListSectionComponent: Component {
|
||||
} else {
|
||||
separatorAlpha = 0.0
|
||||
}
|
||||
itemTransition.setAlpha(layer: itemView.separatorLayer, alpha: separatorAlpha)
|
||||
itemView.separatorLayer.backgroundColor = component.theme.list.itemBlocksSeparatorColor.cgColor
|
||||
readyItem.itemTransition.setAlpha(layer: readyItem.itemView.separatorLayer, alpha: separatorAlpha)
|
||||
readyItem.itemView.separatorLayer.backgroundColor = component.theme.list.itemBlocksSeparatorColor.cgColor
|
||||
}
|
||||
innerContentHeight += itemSize.height
|
||||
innerContentHeight += readyItem.itemSize.height
|
||||
}
|
||||
|
||||
var removedItemIds: [AnyHashable] = []
|
||||
for (id, itemView) in self.itemViews {
|
||||
if !validItemIds.contains(id) {
|
||||
|
@ -776,6 +776,7 @@ public final class MessageInputPanelComponent: Component {
|
||||
transition: .immediate,
|
||||
component: AnyComponent(TextFieldComponent(
|
||||
context: component.context,
|
||||
theme: component.theme,
|
||||
strings: component.strings,
|
||||
externalState: self.textFieldExternalState,
|
||||
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
|
||||
|
||||
var deleteSectionHeight: CGFloat = 0.0
|
||||
|
||||
deleteSectionHeight += sectionSpacing
|
||||
let deleteSectionSize = self.deleteSection.update(
|
||||
transition: transition,
|
||||
|
@ -51,6 +51,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/GroupStickerPackSetupController",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatMessageItemImpl",
|
||||
"//submodules/TelegramUI/Components/Settings/PeerNameColorItem",
|
||||
"//submodules/TelegramUI/Components/EmojiActionIconComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -38,100 +38,7 @@ import BundleIconComponent
|
||||
import Markdown
|
||||
import GroupStickerPackSetupController
|
||||
import PeerNameColorItem
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
import EmojiActionIconComponent
|
||||
|
||||
final class ChannelAppearanceScreenComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
@ -27,6 +27,7 @@ public final class TextFieldComponent: Component {
|
||||
public final class ExternalState {
|
||||
public fileprivate(set) var isEditing: Bool = false
|
||||
public fileprivate(set) var hasText: Bool = false
|
||||
public fileprivate(set) var text: NSAttributedString = NSAttributedString()
|
||||
public fileprivate(set) var textLength: Int = 0
|
||||
public var initialText: NSAttributedString?
|
||||
|
||||
@ -87,6 +88,7 @@ public final class TextFieldComponent: Component {
|
||||
}
|
||||
|
||||
public let context: AccountContext
|
||||
public let theme: PresentationTheme
|
||||
public let strings: PresentationStrings
|
||||
public let externalState: ExternalState
|
||||
public let fontSize: CGFloat
|
||||
@ -105,6 +107,7 @@ public final class TextFieldComponent: Component {
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
externalState: ExternalState,
|
||||
fontSize: CGFloat,
|
||||
@ -122,6 +125,7 @@ public final class TextFieldComponent: Component {
|
||||
paste: @escaping (PasteData) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.externalState = externalState
|
||||
self.fontSize = fontSize
|
||||
@ -140,6 +144,12 @@ public final class TextFieldComponent: Component {
|
||||
}
|
||||
|
||||
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 {
|
||||
return false
|
||||
}
|
||||
@ -219,7 +229,6 @@ public final class TextFieldComponent: Component {
|
||||
self.textView.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.textView.backgroundColor = nil
|
||||
self.textView.layer.isOpaque = false
|
||||
self.textView.keyboardAppearance = .dark
|
||||
self.textView.indicatorStyle = .white
|
||||
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.addSubview(self.textView)
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
self.textView.overrideUserInterfaceStyle = .dark
|
||||
}
|
||||
|
||||
self.textView.typingAttributes = [
|
||||
NSAttributedString.Key.font: Font.regular(17.0),
|
||||
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 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 {
|
||||
@ -1048,9 +1053,17 @@ public final class TextFieldComponent: Component {
|
||||
self.isUpdating = false
|
||||
}
|
||||
|
||||
let previousComponent = self.component
|
||||
self.component = component
|
||||
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 {
|
||||
component.externalState.initialText = nil
|
||||
self.updateInputState { _ in
|
||||
@ -1128,6 +1141,7 @@ public final class TextFieldComponent: Component {
|
||||
component.externalState.hasText = self.textView.textStorage.length != 0
|
||||
component.externalState.isEditing = isEditing
|
||||
component.externalState.textLength = self.textView.textStorage.string.count
|
||||
component.externalState.text = NSAttributedString(attributedString: self.textView.textStorage)
|
||||
|
||||
if let inputView = component.customInputView {
|
||||
if self.textView.inputView == nil {
|
||||
|
@ -121,6 +121,8 @@ import TopMessageReactions
|
||||
import PeerInfoScreen
|
||||
import AudioWaveform
|
||||
import PeerNameColorScreen
|
||||
import ChatEmptyNode
|
||||
import ChatMediaInputStickerGridItem
|
||||
|
||||
public enum ChatControllerPeekActions {
|
||||
case standard
|
||||
|
@ -42,6 +42,7 @@ import UIKitRuntimeUtils
|
||||
import ChatInlineSearchResultsListComponent
|
||||
import ComponentDisplayAdapters
|
||||
import ComponentFlow
|
||||
import ChatEmptyNode
|
||||
|
||||
final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem {
|
||||
let itemNode: OverlayMediaItemNode
|
||||
@ -990,7 +991,20 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.emptyNode = emptyNode
|
||||
self.historyNodeContainer.supernode?.insertSubnode(emptyNode, aboveSubnode: self.historyNodeContainer)
|
||||
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 {
|
||||
emptyNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
@ -1842,7 +1856,20 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
emptyNodeInsets.bottom += inputPanelsHeight
|
||||
self.validEmptyNodeLayout = (contentBounds.size, emptyNodeInsets)
|
||||
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)
|
||||
emptyNode.update(rect: contentBounds, within: contentBounds.size, transition: transition)
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ import ChatMessageInstantVideoItemNode
|
||||
import ChatMessageAnimatedStickerItemNode
|
||||
import ChatMessageTransitionNode
|
||||
import ChatMessageBubbleItemNode
|
||||
import ChatEmptyNode
|
||||
import ChatMediaInputStickerGridItem
|
||||
|
||||
private func convertAnimatingSourceRect(_ rect: CGRect, fromView: UIView, toView: UIView?) -> CGRect {
|
||||
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 {
|
||||
switch strongSelf.mode {
|
||||
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:
|
||||
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))
|
||||
|
||||
if displayCountAlert {
|
||||
|
@ -59,6 +59,7 @@ import CollectibleItemInfoScreen
|
||||
import StickerPickerScreen
|
||||
import MediaEditor
|
||||
import MediaEditorScreen
|
||||
import BusinessIntroSetupScreen
|
||||
|
||||
private final class AccountUserInterfaceInUseContext {
|
||||
let subscribers = Bag<(Bool) -> Void>()
|
||||
@ -1927,6 +1928,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
return QuickReplySetupScreen.initialData(context: context)
|
||||
}
|
||||
|
||||
public func makeBusinessIntroSetupScreen(context: AccountContext) -> ViewController {
|
||||
return BusinessIntroSetupScreen(context: context)
|
||||
}
|
||||
|
||||
public func makeCollectibleItemInfoScreen(context: AccountContext, initialData: CollectibleItemInfoScreenInitialData) -> ViewController {
|
||||
return CollectibleItemInfoScreen(context: context, initialData: initialData as! CollectibleItemInfoScreen.InitialData)
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import MediaEditor
|
||||
import PeerInfoScreen
|
||||
import PeerInfoStoryGridScreen
|
||||
import ShareWithPeersScreen
|
||||
import ChatEmptyNode
|
||||
|
||||
private class DetailsChatPlaceholderNode: ASDisplayNode, NavigationDetailsPlaceholderNode {
|
||||
private var presentationData: PresentationData
|
||||
|
Loading…
x
Reference in New Issue
Block a user