mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit '1750bc5a737db31d458de66d8c6462fb26b338f7'
This commit is contained in:
commit
dc085a2fe9
@ -8299,3 +8299,6 @@ Sorry for the inconvenience.";
|
||||
"Notification.ForumTopicUnhiddenAuthor" = "%1$@ unhidden topic";
|
||||
"Notification.OverviewTopicHidden" = "%1$@ hidden %2$@ %3$@";
|
||||
"Notification.OverviewTopicUnhidden" = "%1$@ unhidden %2$@ %3$@";
|
||||
|
||||
"CreateTopic.ShowGeneral" = "Show in Topics";
|
||||
"CreateTopic.ShowGeneralInfo" = "If the 'General' topic is hidden, group members can pull down in the topic list to view it.";
|
||||
|
@ -793,6 +793,7 @@ public protocol SharedAccountContext: AnyObject {
|
||||
func makeChatQrCodeScreen(context: AccountContext, peer: Peer, threadId: Int64?) -> ViewController
|
||||
|
||||
func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource) -> ViewController
|
||||
func makePremiumDemoController(context: AccountContext, subject: PremiumDemoSubject, action: @escaping () -> Void) -> ViewController
|
||||
|
||||
func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?) -> ViewController
|
||||
|
||||
@ -832,6 +833,22 @@ public enum PremiumIntroSource {
|
||||
case fasterDownload
|
||||
}
|
||||
|
||||
public enum PremiumDemoSubject {
|
||||
case doubleLimits
|
||||
case moreUpload
|
||||
case fasterDownload
|
||||
case voiceToText
|
||||
case noAds
|
||||
case uniqueReactions
|
||||
case premiumStickers
|
||||
case advancedChatManagement
|
||||
case profileBadge
|
||||
case animatedUserpics
|
||||
case appIcons
|
||||
case animatedEmoji
|
||||
case emojiStatus
|
||||
}
|
||||
|
||||
public protocol ComposeController: ViewController {
|
||||
}
|
||||
|
||||
|
@ -705,6 +705,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
|
||||
}, insertText: { _ in
|
||||
}, backwardsDeleteText: {
|
||||
}, restartTopic: {
|
||||
}, requestLayout: { _ in
|
||||
}, chatController: {
|
||||
return nil
|
||||
}, statuses: nil)
|
||||
|
@ -1185,7 +1185,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
let controller = ForumCreateTopicScreen(context: context, peerId: peerId, mode: .create)
|
||||
controller.navigationPresentation = .modal
|
||||
|
||||
controller.completion = { [weak controller] title, fileId in
|
||||
controller.completion = { [weak controller] title, fileId, _ in
|
||||
controller?.isInProgress = true
|
||||
|
||||
let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: ForumCreateTopicScreen.iconColors.randomElement()!, iconFileId: fileId)
|
||||
@ -2431,7 +2431,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
let controller = ForumCreateTopicScreen(context: context, peerId: peerId, mode: .create)
|
||||
controller.navigationPresentation = .modal
|
||||
|
||||
controller.completion = { [weak controller] title, fileId in
|
||||
controller.completion = { [weak controller] title, fileId, _ in
|
||||
controller?.isInProgress = true
|
||||
|
||||
let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: ForumCreateTopicScreen.iconColors.randomElement()!, iconFileId: fileId)
|
||||
|
@ -148,6 +148,7 @@ public final class ChatPanelInterfaceInteraction {
|
||||
public let insertText: (NSAttributedString) -> Void
|
||||
public let backwardsDeleteText: () -> Void
|
||||
public let restartTopic: () -> Void
|
||||
public let requestLayout: (ContainedViewLayoutTransition) -> Void
|
||||
public let chatController: () -> ViewController?
|
||||
public let statuses: ChatPanelInterfaceInteractionStatuses?
|
||||
|
||||
@ -245,6 +246,7 @@ public final class ChatPanelInterfaceInteraction {
|
||||
insertText: @escaping (NSAttributedString) -> Void,
|
||||
backwardsDeleteText: @escaping () -> Void,
|
||||
restartTopic: @escaping () -> Void,
|
||||
requestLayout: @escaping (ContainedViewLayoutTransition) -> Void,
|
||||
chatController: @escaping () -> ViewController?,
|
||||
statuses: ChatPanelInterfaceInteractionStatuses?
|
||||
) {
|
||||
@ -341,6 +343,7 @@ public final class ChatPanelInterfaceInteraction {
|
||||
self.insertText = insertText
|
||||
self.backwardsDeleteText = backwardsDeleteText
|
||||
self.restartTopic = restartTopic
|
||||
self.requestLayout = requestLayout
|
||||
|
||||
self.chatController = chatController
|
||||
self.statuses = statuses
|
||||
@ -445,6 +448,7 @@ public final class ChatPanelInterfaceInteraction {
|
||||
}, insertText: { _ in
|
||||
}, backwardsDeleteText: {
|
||||
}, restartTopic: {
|
||||
}, requestLayout: { _ in
|
||||
}, chatController: {
|
||||
return nil
|
||||
}, statuses: nil)
|
||||
|
@ -941,7 +941,11 @@ private final class LimitSheetContent: CombinedComponent {
|
||||
|
||||
contentSize = CGSize(width: context.availableSize.width, height: buttonFrame.maxY + 5.0 + environment.safeInsets.bottom)
|
||||
} else {
|
||||
contentSize = CGSize(width: context.availableSize.width, height: 351.0 + environment.safeInsets.bottom)
|
||||
var height: CGFloat = 351.0
|
||||
if isPremiumDisabled {
|
||||
height -= 78.0
|
||||
}
|
||||
contentSize = CGSize(width: context.availableSize.width, height: height + environment.safeInsets.bottom)
|
||||
}
|
||||
|
||||
return contentSize
|
||||
|
@ -1262,6 +1262,8 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
},
|
||||
openFeatured: {
|
||||
},
|
||||
openSearch: {
|
||||
},
|
||||
addGroupAction: { [weak self] groupId, isPremiumLocked in
|
||||
guard let strongSelf = self, let collectionId = groupId.base as? ItemCollectionId else {
|
||||
return
|
||||
|
@ -439,7 +439,7 @@ final class LocalizationListControllerNode: ViewControllerTracingNode {
|
||||
}
|
||||
var existingIds = Set<String>()
|
||||
|
||||
var showTranslate = true
|
||||
var showTranslate = false
|
||||
var ignoredLanguages: [String] = []
|
||||
if let translationSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.translationSettings]?.get(TranslationSettings.self) {
|
||||
showTranslate = translationSettings.showTranslate
|
||||
|
@ -456,7 +456,8 @@ public func usernameSetupController(context: AccountContext) -> ViewController {
|
||||
dismissInputImpl?()
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.Username_ActivateAlertTitle, text: presentationData.strings.Username_ActivateAlertText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Username_ActivateAlertShow, action: {
|
||||
let _ = context.engine.peers.toggleAddressNameActive(domain: .account, name: name, active: true).start(error: { error in
|
||||
let _ = (context.engine.peers.toggleAddressNameActive(domain: .account, name: name, active: true)
|
||||
|> deliverOnMainQueue).start(error: { error in
|
||||
let errorText: String
|
||||
switch error {
|
||||
case .activeLimitReached:
|
||||
|
@ -341,6 +341,12 @@ func _internal_setForumChannelTopicClosed(account: Account, id: EnginePeer.Id, t
|
||||
var flags: Int32 = 0
|
||||
flags |= (1 << 2)
|
||||
|
||||
var hidden: Api.Bool? = nil
|
||||
if threadId == 1, !isClosed {
|
||||
flags |= (1 << 3)
|
||||
hidden = .boolFalse
|
||||
}
|
||||
|
||||
return account.network.request(Api.functions.channels.editForumTopic(
|
||||
flags: flags,
|
||||
channel: inputChannel,
|
||||
@ -348,7 +354,7 @@ func _internal_setForumChannelTopicClosed(account: Account, id: EnginePeer.Id, t
|
||||
title: nil,
|
||||
iconEmojiId: nil,
|
||||
closed: isClosed ? .boolTrue : .boolFalse,
|
||||
hidden: nil
|
||||
hidden: hidden
|
||||
))
|
||||
|> mapError { _ -> EditForumChannelTopicError in
|
||||
return .generic
|
||||
@ -361,6 +367,9 @@ func _internal_setForumChannelTopicClosed(account: Account, id: EnginePeer.Id, t
|
||||
var data = initialData
|
||||
|
||||
data.isClosed = isClosed
|
||||
if let _ = hidden {
|
||||
data.isHidden = false
|
||||
}
|
||||
|
||||
if data != initialData {
|
||||
if let entry = StoredMessageHistoryThreadInfo(data) {
|
||||
|
@ -350,7 +350,7 @@ public final class EmojiStatusSelectionController: ViewController {
|
||||
return
|
||||
}
|
||||
strongSelf.controller?._ready.set(.single(true))
|
||||
|
||||
|
||||
var emojiContent = emojiContent
|
||||
if let emojiSearchResult = emojiSearchResult {
|
||||
var emptySearchResults: EmojiPagerContentComponent.EmptySearchResults?
|
||||
@ -387,6 +387,8 @@ public final class EmojiStatusSelectionController: ViewController {
|
||||
},
|
||||
openFeatured: {
|
||||
},
|
||||
openSearch: {
|
||||
},
|
||||
addGroupAction: { groupId, isPremiumLocked in
|
||||
guard let strongSelf = self, let collectionId = groupId.base as? ItemCollectionId else {
|
||||
return
|
||||
|
@ -1517,6 +1517,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
var useOpaqueTheme: Bool
|
||||
var isActive: Bool
|
||||
var size: CGSize
|
||||
var canFocus: Bool
|
||||
|
||||
static func ==(lhs: Params, rhs: Params) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
@ -1537,6 +1538,9 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
if lhs.size != rhs.size {
|
||||
return false
|
||||
}
|
||||
if lhs.canFocus != rhs.canFocus {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -1673,7 +1677,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
|
||||
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
if self.textField == nil, let textComponentView = self.textView.view {
|
||||
if self.textField == nil, let textComponentView = self.textView.view, self.params?.canFocus == true {
|
||||
let backgroundFrame = self.backgroundLayer.frame
|
||||
let textFieldFrame = CGRect(origin: CGPoint(x: textComponentView.frame.minX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textComponentView.frame.minX, height: backgroundFrame.height))
|
||||
|
||||
@ -1697,13 +1701,15 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
self.clearIconTintView.isHidden = true
|
||||
self.clearIconButton.isHidden = true
|
||||
|
||||
|
||||
self.deactivated()
|
||||
|
||||
if let textField = self.textField {
|
||||
self.textField = nil
|
||||
|
||||
textField.resignFirstResponder()
|
||||
textField.removeFromSuperview()
|
||||
}
|
||||
self.deactivated()
|
||||
}
|
||||
|
||||
@objc private func clearPressed() {
|
||||
@ -1746,17 +1752,18 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
return
|
||||
}
|
||||
self.params = nil
|
||||
self.update(theme: params.theme, strings: params.strings, text: params.text, useOpaqueTheme: params.useOpaqueTheme, isActive: params.isActive, size: params.size, transition: transition)
|
||||
self.update(theme: params.theme, strings: params.strings, text: params.text, useOpaqueTheme: params.useOpaqueTheme, isActive: params.isActive, size: params.size, canFocus: params.canFocus, transition: transition)
|
||||
}
|
||||
|
||||
public func update(theme: PresentationTheme, strings: PresentationStrings, text: String, useOpaqueTheme: Bool, isActive: Bool, size: CGSize, transition: Transition) {
|
||||
public func update(theme: PresentationTheme, strings: PresentationStrings, text: String, useOpaqueTheme: Bool, isActive: Bool, size: CGSize, canFocus: Bool, transition: Transition) {
|
||||
let params = Params(
|
||||
theme: theme,
|
||||
strings: strings,
|
||||
text: text,
|
||||
useOpaqueTheme: useOpaqueTheme,
|
||||
isActive: isActive,
|
||||
size: size
|
||||
size: size,
|
||||
canFocus: canFocus
|
||||
)
|
||||
|
||||
if self.params == params {
|
||||
@ -1931,7 +1938,7 @@ private final class EmptySearchResultsView: UIView {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(context: AccountContext, theme: PresentationTheme, useOpaqueTheme: Bool, text: String, file: TelegramMediaFile?, size: CGSize, transition: Transition) {
|
||||
func update(context: AccountContext, theme: PresentationTheme, useOpaqueTheme: Bool, text: String, file: TelegramMediaFile?, size: CGSize, searchInitiallyHidden: Bool, transition: Transition) {
|
||||
let titleColor: UIColor
|
||||
if useOpaqueTheme {
|
||||
titleColor = theme.chat.inputMediaPanel.panelContentControlOpaqueOverlayColor
|
||||
@ -1973,7 +1980,7 @@ private final class EmptySearchResultsView: UIView {
|
||||
|
||||
let spacing: CGFloat = 4.0
|
||||
let contentHeight = iconSize.height + spacing + titleSize.height
|
||||
let contentOriginY = floor((size.height - contentHeight) / 2.0)
|
||||
let contentOriginY = searchInitiallyHidden ? floor((size.height - contentHeight) / 2.0) : 10.0
|
||||
let iconFrame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: contentOriginY), size: iconSize)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: iconFrame.maxY + spacing), size: titleSize)
|
||||
|
||||
@ -2069,6 +2076,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
public let deleteBackwards: () -> Void
|
||||
public let openStickerSettings: () -> Void
|
||||
public let openFeatured: () -> Void
|
||||
public let openSearch: () -> Void
|
||||
public let addGroupAction: (AnyHashable, Bool) -> Void
|
||||
public let clearGroup: (AnyHashable) -> Void
|
||||
public let pushController: (ViewController) -> Void
|
||||
@ -2089,6 +2097,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
deleteBackwards: @escaping () -> Void,
|
||||
openStickerSettings: @escaping () -> Void,
|
||||
openFeatured: @escaping () -> Void,
|
||||
openSearch: @escaping () -> Void,
|
||||
addGroupAction: @escaping (AnyHashable, Bool) -> Void,
|
||||
clearGroup: @escaping (AnyHashable) -> Void,
|
||||
pushController: @escaping (ViewController) -> Void,
|
||||
@ -2108,6 +2117,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
self.deleteBackwards = deleteBackwards
|
||||
self.openStickerSettings = openStickerSettings
|
||||
self.openFeatured = openFeatured
|
||||
self.openSearch = openSearch
|
||||
self.addGroupAction = addGroupAction
|
||||
self.clearGroup = clearGroup
|
||||
self.pushController = pushController
|
||||
@ -2349,6 +2359,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
public let itemContentUniqueId: AnyHashable?
|
||||
public let warpContentsOnEdges: Bool
|
||||
public let displaySearchWithPlaceholder: String?
|
||||
public let searchInitiallyHidden: Bool
|
||||
public let searchIsPlaceholderOnly: Bool
|
||||
public let emptySearchResults: EmptySearchResults?
|
||||
public let enableLongPress: Bool
|
||||
public let selectedItems: Set<MediaId>
|
||||
@ -2365,6 +2377,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
itemContentUniqueId: AnyHashable?,
|
||||
warpContentsOnEdges: Bool,
|
||||
displaySearchWithPlaceholder: String?,
|
||||
searchInitiallyHidden: Bool,
|
||||
searchIsPlaceholderOnly: Bool,
|
||||
emptySearchResults: EmptySearchResults?,
|
||||
enableLongPress: Bool,
|
||||
selectedItems: Set<MediaId>
|
||||
@ -2380,6 +2394,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
self.itemContentUniqueId = itemContentUniqueId
|
||||
self.warpContentsOnEdges = warpContentsOnEdges
|
||||
self.displaySearchWithPlaceholder = displaySearchWithPlaceholder
|
||||
self.searchInitiallyHidden = searchInitiallyHidden
|
||||
self.searchIsPlaceholderOnly = searchIsPlaceholderOnly
|
||||
self.emptySearchResults = emptySearchResults
|
||||
self.enableLongPress = enableLongPress
|
||||
self.selectedItems = selectedItems
|
||||
@ -2398,6 +2414,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
itemContentUniqueId: itemContentUniqueId,
|
||||
warpContentsOnEdges: self.warpContentsOnEdges,
|
||||
displaySearchWithPlaceholder: self.displaySearchWithPlaceholder,
|
||||
searchInitiallyHidden: self.searchInitiallyHidden,
|
||||
searchIsPlaceholderOnly: self.searchIsPlaceholderOnly,
|
||||
emptySearchResults: emptySearchResults,
|
||||
enableLongPress: self.enableLongPress,
|
||||
selectedItems: self.selectedItems
|
||||
@ -2438,6 +2456,12 @@ public final class EmojiPagerContentComponent: Component {
|
||||
if lhs.displaySearchWithPlaceholder != rhs.displaySearchWithPlaceholder {
|
||||
return false
|
||||
}
|
||||
if lhs.searchInitiallyHidden != rhs.searchInitiallyHidden {
|
||||
return false
|
||||
}
|
||||
if lhs.searchIsPlaceholderOnly != rhs.searchIsPlaceholderOnly {
|
||||
return false
|
||||
}
|
||||
if lhs.emptySearchResults != rhs.emptySearchResults {
|
||||
return false
|
||||
}
|
||||
@ -3233,6 +3257,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
private let mirrorContentScrollView: UIView
|
||||
private var warpView: WarpView?
|
||||
private var mirrorContentWarpView: WarpView?
|
||||
|
||||
private let scrollViewClippingView: UIView
|
||||
private let scrollView: ContentScrollView
|
||||
private var scrollGradientLayer: SimpleGradientLayer?
|
||||
private let boundsChangeTrackerLayer = SimpleLayer()
|
||||
@ -3276,6 +3302,9 @@ public final class EmojiPagerContentComponent: Component {
|
||||
self.standaloneShimmerEffect = nil
|
||||
}
|
||||
|
||||
self.scrollViewClippingView = UIView()
|
||||
self.scrollViewClippingView.clipsToBounds = true
|
||||
|
||||
self.mirrorContentScrollView = UIView()
|
||||
self.mirrorContentScrollView.layer.anchorPoint = CGPoint()
|
||||
self.mirrorContentScrollView.clipsToBounds = true
|
||||
@ -3311,7 +3340,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
self.scrollView.delegate = self
|
||||
self.scrollView.clipsToBounds = false
|
||||
self.scrollView.scrollsToTop = false
|
||||
self.addSubview(self.scrollView)
|
||||
self.addSubview(self.scrollViewClippingView)
|
||||
self.scrollViewClippingView.addSubview(self.scrollView)
|
||||
|
||||
self.scrollView.addSubview(self.placeholdersContainerView)
|
||||
|
||||
@ -4750,6 +4780,10 @@ public final class EmojiPagerContentComponent: Component {
|
||||
self.updateVisibleItems(transition: .immediate, attemptSynchronousLoads: false, previousItemPositions: nil, updatedItemPositions: nil)
|
||||
|
||||
self.updateScrollingOffset(isReset: false, transition: .immediate)
|
||||
|
||||
if self.isSearchActivated {
|
||||
self.visibleSearchHeader?.endEditing(true)
|
||||
}
|
||||
}
|
||||
|
||||
public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
|
||||
@ -5899,9 +5933,15 @@ public final class EmojiPagerContentComponent: Component {
|
||||
self.ignoreScrolling = true
|
||||
|
||||
let scrollOriginY: CGFloat = 0.0
|
||||
let scrollSize = CGSize(width: availableSize.width, height: availableSize.height)
|
||||
|
||||
|
||||
let scrollSize = CGSize(width: availableSize.width, height: availableSize.height)
|
||||
transition.setPosition(view: self.scrollView, position: CGPoint(x: 0.0, y: scrollOriginY))
|
||||
|
||||
transition.setFrame(view: self.scrollViewClippingView, frame: CGRect(origin: CGPoint(x: 0.0, y: self.isSearchActivated ? itemLayout.searchHeight : 0.0), size: availableSize))
|
||||
|
||||
transition.setBounds(view: self.scrollViewClippingView, bounds: CGRect(origin: CGPoint(x: 0.0, y: self.isSearchActivated ? itemLayout.searchHeight : 0.0), size: availableSize))
|
||||
|
||||
let previousSize = self.scrollView.bounds.size
|
||||
var resetScrolling = false
|
||||
if self.scrollView.bounds.isEmpty && component.displaySearchWithPlaceholder != nil {
|
||||
@ -5944,8 +5984,13 @@ public final class EmojiPagerContentComponent: Component {
|
||||
})
|
||||
}
|
||||
|
||||
if self.scrollView.contentSize != itemLayout.contentSize {
|
||||
self.scrollView.contentSize = itemLayout.contentSize
|
||||
var effectiveContentSize = itemLayout.contentSize
|
||||
if self.isSearchActivated {
|
||||
effectiveContentSize.height = max(itemLayout.contentSize.height, availableSize.height + 1.0)
|
||||
}
|
||||
|
||||
if self.scrollView.contentSize != effectiveContentSize {
|
||||
self.scrollView.contentSize = effectiveContentSize
|
||||
}
|
||||
var scrollIndicatorInsets = pagerEnvironment.containerInsets
|
||||
if self.warpView != nil {
|
||||
@ -6003,7 +6048,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
|
||||
if resetScrolling {
|
||||
if component.displaySearchWithPlaceholder != nil && !self.isSearchActivated {
|
||||
if component.displaySearchWithPlaceholder != nil && !self.isSearchActivated && component.searchInitiallyHidden {
|
||||
self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 50.0), size: scrollSize)
|
||||
} else {
|
||||
self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: scrollSize)
|
||||
@ -6063,7 +6108,16 @@ public final class EmojiPagerContentComponent: Component {
|
||||
if self.isSearchActivated {
|
||||
if visibleSearchHeader.superview != self {
|
||||
self.addSubview(visibleSearchHeader)
|
||||
self.mirrorContentClippingView?.addSubview(visibleSearchHeader.tintContainerView)
|
||||
if self.mirrorContentClippingView != nil {
|
||||
self.mirrorContentClippingView?.addSubview(visibleSearchHeader.tintContainerView)
|
||||
} else {
|
||||
self.mirrorContentScrollView.superview?.addSubview(visibleSearchHeader.tintContainerView)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if visibleSearchHeader.superview != self.scrollView {
|
||||
self.scrollView.addSubview(visibleSearchHeader)
|
||||
self.mirrorContentScrollView.addSubview(visibleSearchHeader.tintContainerView)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -6071,9 +6125,14 @@ public final class EmojiPagerContentComponent: Component {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.isSearchActivated = true
|
||||
strongSelf.pagerEnvironment?.onWantsExclusiveModeUpdated(true)
|
||||
strongSelf.component?.inputInteractionHolder.inputInteraction?.requestUpdate(.immediate)
|
||||
|
||||
if let component = strongSelf.component, component.searchIsPlaceholderOnly {
|
||||
component.inputInteractionHolder.inputInteraction?.openSearch()
|
||||
} else {
|
||||
strongSelf.isSearchActivated = true
|
||||
strongSelf.pagerEnvironment?.onWantsExclusiveModeUpdated(true)
|
||||
strongSelf.component?.inputInteractionHolder.inputInteraction?.requestUpdate(.immediate)
|
||||
}
|
||||
}, deactivated: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -6083,7 +6142,11 @@ public final class EmojiPagerContentComponent: Component {
|
||||
|
||||
strongSelf.isSearchActivated = false
|
||||
strongSelf.pagerEnvironment?.onWantsExclusiveModeUpdated(false)
|
||||
strongSelf.component?.inputInteractionHolder.inputInteraction?.requestUpdate(.immediate)
|
||||
if strongSelf.component?.searchInitiallyHidden == false {
|
||||
strongSelf.component?.inputInteractionHolder.inputInteraction?.requestUpdate(.easeInOut(duration: 0.5))
|
||||
} else {
|
||||
strongSelf.component?.inputInteractionHolder.inputInteraction?.requestUpdate(.immediate)
|
||||
}
|
||||
}, updateQuery: { [weak self] query, languageCode in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -6101,7 +6164,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
|
||||
let searchHeaderFrame = CGRect(origin: CGPoint(x: itemLayout.searchInsets.left, y: itemLayout.searchInsets.top), size: CGSize(width: itemLayout.width - itemLayout.searchInsets.left - itemLayout.searchInsets.right, height: itemLayout.searchHeight))
|
||||
visibleSearchHeader.update(theme: keyboardChildEnvironment.theme, strings: keyboardChildEnvironment.strings, text: displaySearchWithPlaceholder, useOpaqueTheme: useOpaqueTheme, isActive: self.isSearchActivated, size: searchHeaderFrame.size, transition: transition)
|
||||
visibleSearchHeader.update(theme: keyboardChildEnvironment.theme, strings: keyboardChildEnvironment.strings, text: displaySearchWithPlaceholder, useOpaqueTheme: useOpaqueTheme, isActive: self.isSearchActivated, size: searchHeaderFrame.size, canFocus: !component.searchIsPlaceholderOnly, transition: transition)
|
||||
transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame, completion: { [weak self] completed in
|
||||
guard let strongSelf = self, completed, let visibleSearchHeader = strongSelf.visibleSearchHeader else {
|
||||
return
|
||||
@ -6120,6 +6183,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if let emptySearchResults = component.emptySearchResults {
|
||||
let visibleEmptySearchResultsView: EmptySearchResultsView
|
||||
var emptySearchResultsTransition = transition
|
||||
@ -6140,6 +6204,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
text: emptySearchResults.text,
|
||||
file: emptySearchResults.iconFile,
|
||||
size: emptySearchResultsSize,
|
||||
searchInitiallyHidden: component.searchInitiallyHidden,
|
||||
transition: emptySearchResultsTransition
|
||||
)
|
||||
emptySearchResultsTransition.setFrame(view: visibleEmptySearchResultsView, frame: CGRect(origin: CGPoint(x: 0.0, y: itemLayout.searchInsets.top + itemLayout.searchHeight), size: emptySearchResultsSize))
|
||||
@ -6899,12 +6964,16 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
|
||||
var displaySearchWithPlaceholder: String?
|
||||
var searchInitiallyHidden = true
|
||||
if isReactionSelection {
|
||||
displaySearchWithPlaceholder = strings.EmojiSearch_SearchReactionsPlaceholder
|
||||
} else if isStatusSelection {
|
||||
displaySearchWithPlaceholder = strings.EmojiSearch_SearchStatusesPlaceholder
|
||||
} else if isTopicIconSelection {
|
||||
displaySearchWithPlaceholder = strings.EmojiSearch_SearchTopicIconsPlaceholder
|
||||
} else {
|
||||
displaySearchWithPlaceholder = "Search Emoji"
|
||||
searchInitiallyHidden = false
|
||||
}
|
||||
|
||||
return EmojiPagerContentComponent(
|
||||
@ -6952,6 +7021,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
itemContentUniqueId: nil,
|
||||
warpContentsOnEdges: isReactionSelection || isStatusSelection,
|
||||
displaySearchWithPlaceholder: displaySearchWithPlaceholder,
|
||||
searchInitiallyHidden: searchInitiallyHidden,
|
||||
searchIsPlaceholderOnly: false,
|
||||
emptySearchResults: nil,
|
||||
enableLongPress: (isReactionSelection && !isQuickReactionSelection) || isStatusSelection,
|
||||
selectedItems: selectedItems
|
||||
|
@ -337,16 +337,16 @@ public final class EntityKeyboardComponent: Component {
|
||||
}
|
||||
))))
|
||||
contentIcons.append(PagerComponentContentIcon(id: "gifs", imageName: "Chat/Input/Media/EntityInputGifsIcon"))
|
||||
contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(Button(
|
||||
content: AnyComponent(BundleIconComponent(
|
||||
name: "Chat/Input/Media/EntityInputSearchIcon",
|
||||
tintColor: component.theme.chat.inputMediaPanel.panelIconColor,
|
||||
maxSize: nil
|
||||
)),
|
||||
action: { [weak self] in
|
||||
self?.openSearch()
|
||||
}
|
||||
).minSize(CGSize(width: 38.0, height: 38.0)))))
|
||||
// contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(Button(
|
||||
// content: AnyComponent(BundleIconComponent(
|
||||
// name: "Chat/Input/Media/EntityInputSearchIcon",
|
||||
// tintColor: component.theme.chat.inputMediaPanel.panelIconColor,
|
||||
// maxSize: nil
|
||||
// )),
|
||||
// action: { [weak self] in
|
||||
// self?.openSearch()
|
||||
// }
|
||||
// ).minSize(CGSize(width: 38.0, height: 38.0)))))
|
||||
}
|
||||
|
||||
if let stickerContent = component.stickerContent {
|
||||
@ -451,16 +451,16 @@ public final class EntityKeyboardComponent: Component {
|
||||
}
|
||||
))))
|
||||
contentIcons.append(PagerComponentContentIcon(id: "stickers", imageName: "Chat/Input/Media/EntityInputStickersIcon"))
|
||||
contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(Button(
|
||||
content: AnyComponent(BundleIconComponent(
|
||||
name: "Chat/Input/Media/EntityInputSearchIcon",
|
||||
tintColor: component.theme.chat.inputMediaPanel.panelIconColor,
|
||||
maxSize: nil
|
||||
)),
|
||||
action: { [weak self] in
|
||||
self?.openSearch()
|
||||
}
|
||||
).minSize(CGSize(width: 38.0, height: 38.0)))))
|
||||
// contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(Button(
|
||||
// content: AnyComponent(BundleIconComponent(
|
||||
// name: "Chat/Input/Media/EntityInputSearchIcon",
|
||||
// tintColor: component.theme.chat.inputMediaPanel.panelIconColor,
|
||||
// maxSize: nil
|
||||
// )),
|
||||
// action: { [weak self] in
|
||||
// self?.openSearch()
|
||||
// }
|
||||
// ).minSize(CGSize(width: 38.0, height: 38.0)))))
|
||||
contentAccessoryRightButtons.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(Button(
|
||||
content: AnyComponent(BundleIconComponent(
|
||||
name: "Chat/Input/Media/EntityInputSettingsIcon",
|
||||
@ -749,7 +749,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
component.hideTopPanelUpdated(self.isTopPanelHidden, transition)
|
||||
}
|
||||
|
||||
private func openSearch() {
|
||||
public func openSearch() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
|
@ -136,15 +136,18 @@ public final class GifPagerContentComponent: Component {
|
||||
public let performItemAction: (Item, UIView, CGRect) -> Void
|
||||
public let openGifContextMenu: (Item, UIView, CGRect, ContextGesture, Bool) -> Void
|
||||
public let loadMore: (String) -> Void
|
||||
public let openSearch: () -> Void
|
||||
|
||||
public init(
|
||||
performItemAction: @escaping (Item, UIView, CGRect) -> Void,
|
||||
openGifContextMenu: @escaping (Item, UIView, CGRect, ContextGesture, Bool) -> Void,
|
||||
loadMore: @escaping (String) -> Void
|
||||
loadMore: @escaping (String) -> Void,
|
||||
openSearch: @escaping () -> Void
|
||||
) {
|
||||
self.performItemAction = performItemAction
|
||||
self.openGifContextMenu = openGifContextMenu
|
||||
self.loadMore = loadMore
|
||||
self.openSearch = openSearch
|
||||
}
|
||||
}
|
||||
|
||||
@ -178,6 +181,8 @@ public final class GifPagerContentComponent: Component {
|
||||
public let items: [Item]
|
||||
public let isLoading: Bool
|
||||
public let loadMoreToken: String?
|
||||
public let displaySearchWithPlaceholder: String?
|
||||
public let searchInitiallyHidden: Bool
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
@ -185,7 +190,9 @@ public final class GifPagerContentComponent: Component {
|
||||
subject: Subject,
|
||||
items: [Item],
|
||||
isLoading: Bool,
|
||||
loadMoreToken: String?
|
||||
loadMoreToken: String?,
|
||||
displaySearchWithPlaceholder: String?,
|
||||
searchInitiallyHidden: Bool
|
||||
) {
|
||||
self.context = context
|
||||
self.inputInteraction = inputInteraction
|
||||
@ -193,6 +200,8 @@ public final class GifPagerContentComponent: Component {
|
||||
self.items = items
|
||||
self.isLoading = isLoading
|
||||
self.loadMoreToken = loadMoreToken
|
||||
self.displaySearchWithPlaceholder = displaySearchWithPlaceholder
|
||||
self.searchInitiallyHidden = searchInitiallyHidden
|
||||
}
|
||||
|
||||
public static func ==(lhs: GifPagerContentComponent, rhs: GifPagerContentComponent) -> Bool {
|
||||
@ -214,7 +223,12 @@ public final class GifPagerContentComponent: Component {
|
||||
if lhs.loadMoreToken != rhs.loadMoreToken {
|
||||
return false
|
||||
}
|
||||
|
||||
if lhs.displaySearchWithPlaceholder != rhs.displaySearchWithPlaceholder {
|
||||
return false
|
||||
}
|
||||
if lhs.searchInitiallyHidden != rhs.searchInitiallyHidden {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -240,6 +254,9 @@ public final class GifPagerContentComponent: Component {
|
||||
let itemsPerRow: Int
|
||||
let contentSize: CGSize
|
||||
|
||||
var searchInsets: UIEdgeInsets
|
||||
var searchHeight: CGFloat
|
||||
|
||||
init(width: CGFloat, containerInsets: UIEdgeInsets, itemCount: Int) {
|
||||
self.width = width
|
||||
self.containerInsets = containerInsets
|
||||
@ -247,6 +264,9 @@ public final class GifPagerContentComponent: Component {
|
||||
self.horizontalSpacing = 1.0
|
||||
self.verticalSpacing = 1.0
|
||||
|
||||
self.searchHeight = 54.0
|
||||
self.searchInsets = UIEdgeInsets(top: max(0.0, containerInsets.top + 1.0), left: containerInsets.left, bottom: 0.0, right: containerInsets.right)
|
||||
|
||||
let defaultItemSize: CGFloat = 120.0
|
||||
|
||||
let itemHorizontalSpace = width - self.containerInsets.left - self.containerInsets.right
|
||||
@ -408,17 +428,103 @@ public final class GifPagerContentComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
private final class ContentScrollView: UIScrollView, PagerExpandableScrollView {
|
||||
public final class ContentScrollLayer: CALayer {
|
||||
public var mirrorLayer: CALayer?
|
||||
|
||||
override public init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
override public init(layer: Any) {
|
||||
super.init(layer: layer)
|
||||
}
|
||||
|
||||
required public init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override public var position: CGPoint {
|
||||
get {
|
||||
return super.position
|
||||
} set(value) {
|
||||
if let mirrorLayer = self.mirrorLayer {
|
||||
mirrorLayer.position = value
|
||||
}
|
||||
super.position = value
|
||||
}
|
||||
}
|
||||
|
||||
override public var bounds: CGRect {
|
||||
get {
|
||||
return super.bounds
|
||||
} set(value) {
|
||||
if let mirrorLayer = self.mirrorLayer {
|
||||
mirrorLayer.bounds = value
|
||||
}
|
||||
super.bounds = value
|
||||
}
|
||||
}
|
||||
|
||||
override public func add(_ animation: CAAnimation, forKey key: String?) {
|
||||
if let mirrorLayer = self.mirrorLayer {
|
||||
mirrorLayer.add(animation, forKey: key)
|
||||
}
|
||||
|
||||
super.add(animation, forKey: key)
|
||||
}
|
||||
|
||||
override public func removeAllAnimations() {
|
||||
if let mirrorLayer = self.mirrorLayer {
|
||||
mirrorLayer.removeAllAnimations()
|
||||
}
|
||||
|
||||
super.removeAllAnimations()
|
||||
}
|
||||
|
||||
override public func removeAnimation(forKey: String) {
|
||||
if let mirrorLayer = self.mirrorLayer {
|
||||
mirrorLayer.removeAnimation(forKey: forKey)
|
||||
}
|
||||
|
||||
super.removeAnimation(forKey: forKey)
|
||||
}
|
||||
}
|
||||
|
||||
private let backgroundView: BlurredBackgroundView
|
||||
private final class ContentScrollView: UIScrollView, PagerExpandableScrollView {
|
||||
override static var layerClass: AnyClass {
|
||||
return ContentScrollLayer.self
|
||||
}
|
||||
|
||||
private let mirrorView: UIView
|
||||
|
||||
init(mirrorView: UIView) {
|
||||
self.mirrorView = mirrorView
|
||||
|
||||
super.init(frame: CGRect())
|
||||
|
||||
(self.layer as? ContentScrollLayer)?.mirrorLayer = mirrorView.layer
|
||||
self.canCancelContentTouches = true
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func touchesShouldCancel(in view: UIView) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private let shimmerHostView: PortalSourceView
|
||||
private let standaloneShimmerEffect: StandaloneShimmerEffect
|
||||
|
||||
private let backgroundView: BlurredBackgroundView
|
||||
private var vibrancyEffectView: UIVisualEffectView?
|
||||
private let mirrorContentScrollView: UIView
|
||||
private let scrollView: ContentScrollView
|
||||
|
||||
private let placeholdersContainerView: UIView
|
||||
private var visibleSearchHeader: EmojiSearchHeaderView?
|
||||
private var visibleItemPlaceholderViews: [ItemKey: ItemPlaceholderView] = [:]
|
||||
private var visibleItemLayers: [ItemKey: ItemLayer] = [:]
|
||||
private var ignoreScrolling: Bool = false
|
||||
@ -438,7 +544,11 @@ public final class GifPagerContentComponent: Component {
|
||||
|
||||
self.placeholdersContainerView = UIView()
|
||||
|
||||
self.scrollView = ContentScrollView()
|
||||
self.mirrorContentScrollView = UIView()
|
||||
self.mirrorContentScrollView.layer.anchorPoint = CGPoint()
|
||||
self.mirrorContentScrollView.clipsToBounds = true
|
||||
self.scrollView = ContentScrollView(mirrorView: self.mirrorContentScrollView)
|
||||
self.scrollView.layer.anchorPoint = CGPoint()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
@ -636,6 +746,11 @@ public final class GifPagerContentComponent: Component {
|
||||
|
||||
var validIds = Set<ItemKey>()
|
||||
|
||||
var searchInset: CGFloat = 0.0
|
||||
if let _ = component.displaySearchWithPlaceholder {
|
||||
searchInset += itemLayout.searchHeight
|
||||
}
|
||||
|
||||
if let itemRange = itemLayout.visibleItems(for: self.scrollView.bounds) {
|
||||
for index in itemRange.lowerBound ..< itemRange.upperBound {
|
||||
var item: Item?
|
||||
@ -657,7 +772,7 @@ public final class GifPagerContentComponent: Component {
|
||||
|
||||
validIds.insert(itemId)
|
||||
|
||||
let itemFrame = itemLayout.frame(at: index)
|
||||
let itemFrame = itemLayout.frame(at: index).offsetBy(dx: 0.0, dy: searchInset)
|
||||
|
||||
let itemTransition: Transition = .immediate
|
||||
var updateItemLayerPlaceholder = false
|
||||
@ -768,9 +883,23 @@ public final class GifPagerContentComponent: Component {
|
||||
guard let theme = self.theme else {
|
||||
return
|
||||
}
|
||||
if self.vibrancyEffectView == nil {
|
||||
let style: UIBlurEffect.Style
|
||||
style = .extraLight
|
||||
let blurEffect = UIBlurEffect(style: style)
|
||||
let vibrancyEffect = UIVibrancyEffect(blurEffect: blurEffect)
|
||||
let vibrancyEffectView = UIVisualEffectView(effect: vibrancyEffect)
|
||||
self.vibrancyEffectView = vibrancyEffectView
|
||||
self.backgroundView.addSubview(vibrancyEffectView)
|
||||
vibrancyEffectView.contentView.addSubview(self.mirrorContentScrollView)
|
||||
}
|
||||
self.backgroundView.updateColor(color: theme.chat.inputMediaPanel.backgroundColor, enableBlur: true, forceKeepBlur: false, transition: transition.containedViewLayoutTransition)
|
||||
transition.setFrame(view: self.backgroundView, frame: backgroundFrame)
|
||||
self.backgroundView.update(size: backgroundFrame.size, transition: transition.containedViewLayoutTransition)
|
||||
|
||||
if let vibrancyEffectView = self.vibrancyEffectView {
|
||||
transition.setFrame(view: vibrancyEffectView, frame: CGRect(origin: CGPoint(x: 0.0, y: -backgroundFrame.minY), size: CGSize(width: backgroundFrame.width, height: backgroundFrame.height + backgroundFrame.minY)))
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: GifPagerContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
@ -802,7 +931,12 @@ public final class GifPagerContentComponent: Component {
|
||||
self.itemLayout = itemLayout
|
||||
|
||||
self.ignoreScrolling = true
|
||||
transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
let scrollOriginY: CGFloat = 0.0
|
||||
let scrollSize = CGSize(width: availableSize.width, height: availableSize.height)
|
||||
|
||||
transition.setPosition(view: self.scrollView, position: CGPoint(x: 0.0, y: scrollOriginY))
|
||||
self.scrollView.bounds = CGRect(origin: self.scrollView.bounds.origin, size: scrollSize)
|
||||
|
||||
if self.scrollView.contentSize != itemLayout.contentSize {
|
||||
self.scrollView.contentSize = itemLayout.contentSize
|
||||
}
|
||||
@ -817,6 +951,44 @@ public final class GifPagerContentComponent: Component {
|
||||
self.previousScrollingOffset = self.scrollView.contentOffset.y
|
||||
self.ignoreScrolling = false
|
||||
|
||||
if let displaySearchWithPlaceholder = component.displaySearchWithPlaceholder {
|
||||
let visibleSearchHeader: EmojiSearchHeaderView
|
||||
if let current = self.visibleSearchHeader {
|
||||
visibleSearchHeader = current
|
||||
} else {
|
||||
visibleSearchHeader = EmojiSearchHeaderView(activated: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.component?.inputInteraction.openSearch()
|
||||
}, deactivated: {
|
||||
}, updateQuery: {_, _ in
|
||||
})
|
||||
self.visibleSearchHeader = visibleSearchHeader
|
||||
self.scrollView.addSubview(visibleSearchHeader)
|
||||
self.mirrorContentScrollView.addSubview(visibleSearchHeader.tintContainerView)
|
||||
}
|
||||
|
||||
let searchHeaderFrame = CGRect(origin: CGPoint(x: itemLayout.searchInsets.left, y: itemLayout.searchInsets.top), size: CGSize(width: itemLayout.width - itemLayout.searchInsets.left - itemLayout.searchInsets.right, height: itemLayout.searchHeight))
|
||||
visibleSearchHeader.update(theme: keyboardChildEnvironment.theme, strings: keyboardChildEnvironment.strings, text: displaySearchWithPlaceholder, useOpaqueTheme: false, isActive: false, size: searchHeaderFrame.size, canFocus: false, transition: transition)
|
||||
transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame, completion: { [weak self] completed in
|
||||
guard let strongSelf = self, completed, let visibleSearchHeader = strongSelf.visibleSearchHeader else {
|
||||
return
|
||||
}
|
||||
|
||||
if visibleSearchHeader.superview != strongSelf.scrollView {
|
||||
strongSelf.scrollView.addSubview(visibleSearchHeader)
|
||||
strongSelf.mirrorContentScrollView.addSubview(visibleSearchHeader.tintContainerView)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if let visibleSearchHeader = self.visibleSearchHeader {
|
||||
self.visibleSearchHeader = nil
|
||||
visibleSearchHeader.removeFromSuperview()
|
||||
visibleSearchHeader.tintContainerView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
self.updateVisibleItems(attemptSynchronousLoads: true)
|
||||
|
||||
return availableSize
|
||||
|
@ -17,6 +17,72 @@ import Postbox
|
||||
import PremiumUI
|
||||
import ProgressNavigationButtonNode
|
||||
|
||||
private final class SwitchComponent: Component {
|
||||
typealias EnvironmentType = Empty
|
||||
|
||||
let value: Bool
|
||||
let valueUpdated: (Bool) -> Void
|
||||
|
||||
init(
|
||||
value: Bool,
|
||||
valueUpdated: @escaping (Bool) -> Void
|
||||
) {
|
||||
self.value = value
|
||||
self.valueUpdated = valueUpdated
|
||||
}
|
||||
|
||||
static func ==(lhs: SwitchComponent, rhs: SwitchComponent) -> Bool {
|
||||
if lhs.value != rhs.value {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let switchView: UISwitch
|
||||
|
||||
private var component: SwitchComponent?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.switchView = UISwitch()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.switchView)
|
||||
|
||||
self.switchView.addTarget(self, action: #selector(self.valueChanged(_:)), for: .valueChanged)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc func valueChanged(_ sender: Any) {
|
||||
self.component?.valueUpdated(self.switchView.isOn)
|
||||
}
|
||||
|
||||
func update(component: SwitchComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
|
||||
self.switchView.setOn(component.value, animated: !transition.animation.isImmediate)
|
||||
|
||||
self.switchView.sizeToFit()
|
||||
self.switchView.frame = CGRect(origin: .zero, size: self.switchView.frame.size)
|
||||
|
||||
return self.switchView.frame.size
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final class TitleFieldComponent: Component {
|
||||
typealias EnvironmentType = Empty
|
||||
|
||||
@ -384,14 +450,16 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
|
||||
let mode: ForumCreateTopicScreen.Mode
|
||||
let titleUpdated: (String) -> Void
|
||||
let iconUpdated: (Int64?) -> Void
|
||||
let isHiddenUpdated: (Bool) -> Void
|
||||
let openPremium: () -> Void
|
||||
|
||||
init(context: AccountContext, peerId: EnginePeer.Id, mode: ForumCreateTopicScreen.Mode, titleUpdated: @escaping (String) -> Void, iconUpdated: @escaping (Int64?) -> Void, openPremium: @escaping () -> Void) {
|
||||
init(context: AccountContext, peerId: EnginePeer.Id, mode: ForumCreateTopicScreen.Mode, titleUpdated: @escaping (String) -> Void, iconUpdated: @escaping (Int64?) -> Void, isHiddenUpdated: @escaping (Bool) -> Void, openPremium: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
self.mode = mode
|
||||
self.titleUpdated = titleUpdated
|
||||
self.iconUpdated = iconUpdated
|
||||
self.isHiddenUpdated = isHiddenUpdated
|
||||
self.openPremium = openPremium
|
||||
}
|
||||
|
||||
@ -412,6 +480,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
|
||||
private let context: AccountContext
|
||||
private let titleUpdated: (String) -> Void
|
||||
private let iconUpdated: (Int64?) -> Void
|
||||
private let isHiddenUpdated: (Bool) -> Void
|
||||
private let openPremium: () -> Void
|
||||
|
||||
var emojiContent: EmojiPagerContentComponent?
|
||||
@ -426,13 +495,15 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
|
||||
var title: String
|
||||
var fileId: Int64
|
||||
var iconColor: Int32
|
||||
var isHidden: Bool
|
||||
|
||||
private var hasPremium: Bool = false
|
||||
|
||||
init(context: AccountContext, mode: ForumCreateTopicScreen.Mode, titleUpdated: @escaping (String) -> Void, iconUpdated: @escaping (Int64?) -> Void, openPremium: @escaping () -> Void) {
|
||||
init(context: AccountContext, mode: ForumCreateTopicScreen.Mode, titleUpdated: @escaping (String) -> Void, iconUpdated: @escaping (Int64?) -> Void, isHiddenUpdated: @escaping (Bool) -> Void, openPremium: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.titleUpdated = titleUpdated
|
||||
self.iconUpdated = iconUpdated
|
||||
self.isHiddenUpdated = isHiddenUpdated
|
||||
self.openPremium = openPremium
|
||||
|
||||
switch mode {
|
||||
@ -440,13 +511,14 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
|
||||
self.isGeneral = false
|
||||
self.title = ""
|
||||
self.fileId = 0
|
||||
|
||||
self.iconColor = ForumCreateTopicScreen.iconColors.randomElement() ?? 0x0
|
||||
case let .edit(threadId, info):
|
||||
self.isHidden = false
|
||||
case let .edit(threadId, info, isHidden):
|
||||
self.isGeneral = threadId == 1
|
||||
self.title = info.title
|
||||
self.fileId = info.icon ?? 0
|
||||
self.iconColor = info.iconColor
|
||||
self.isHidden = isHidden
|
||||
}
|
||||
|
||||
super.init()
|
||||
@ -511,6 +583,12 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
|
||||
self.updateEmojiContent()
|
||||
}
|
||||
|
||||
func updateIsHidden(_ isHidden: Bool) {
|
||||
self.isHidden = isHidden
|
||||
self.updated(transition: .immediate)
|
||||
self.isHiddenUpdated(isHidden)
|
||||
}
|
||||
|
||||
func updateEmojiContent() {
|
||||
self.emojiContentDisposable.set((
|
||||
EmojiPagerContentComponent.emojiInputData(
|
||||
@ -575,6 +653,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
|
||||
mode: self.mode,
|
||||
titleUpdated: self.titleUpdated,
|
||||
iconUpdated: self.iconUpdated,
|
||||
isHiddenUpdated: self.isHiddenUpdated,
|
||||
openPremium: self.openPremium
|
||||
)
|
||||
}
|
||||
@ -585,6 +664,11 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
|
||||
let titleBackground = Child(RoundedRectangle.self)
|
||||
let titleField = Child(TitleFieldComponent.self)
|
||||
|
||||
let hideBackground = Child(RoundedRectangle.self)
|
||||
let hideTitle = Child(MultilineTextComponent.self)
|
||||
let hideSwitch = Child(SwitchComponent.self)
|
||||
let hideInfo = Child(MultilineTextComponent.self)
|
||||
|
||||
let iconHeader = Child(MultilineTextComponent.self)
|
||||
let iconBackground = Child(RoundedRectangle.self)
|
||||
let iconSelector = Child(TopicIconSelectionComponent.self)
|
||||
@ -624,7 +708,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
|
||||
horizontalAlignment: .natural,
|
||||
maximumNumberOfLines: 1
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right, height: CGFloat.greatestFiniteMagnitude),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(titleHeader
|
||||
@ -637,7 +721,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
|
||||
color: environment.theme.list.itemBlocksBackgroundColor,
|
||||
cornerRadius: 10.0
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 44.0),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right, height: 44.0),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(titleBackground
|
||||
@ -662,7 +746,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
|
||||
state?.switchIcon()
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 44.0),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right, height: 44.0),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(titleField
|
||||
@ -671,8 +755,79 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
|
||||
|
||||
contentHeight += titleBackground.size.height + sectionSpacing
|
||||
|
||||
if case let .edit(threadId, _) = context.component.mode, threadId == 1 {
|
||||
if case let .edit(threadId, _, _) = context.component.mode, threadId == 1 {
|
||||
let hideBackground = hideBackground.update(
|
||||
component: RoundedRectangle(
|
||||
color: environment.theme.list.itemBlocksBackgroundColor,
|
||||
cornerRadius: 10.0
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right, height: 44.0),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(hideBackground
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + hideBackground.size.height / 2.0))
|
||||
)
|
||||
|
||||
let hideTitle = hideTitle.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: environment.strings.CreateTopic_ShowGeneral,
|
||||
font: Font.regular(17.0),
|
||||
textColor: environment.theme.list.itemPrimaryTextColor,
|
||||
paragraphAlignment: .natural)
|
||||
),
|
||||
horizontalAlignment: .natural,
|
||||
maximumNumberOfLines: 0
|
||||
),
|
||||
availableSize: CGSize(
|
||||
width: context.availableSize.width - sideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right,
|
||||
height: CGFloat.greatestFiniteMagnitude
|
||||
),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(hideTitle
|
||||
.position(CGPoint(x: environment.safeInsets.left + sideInset + 16.0 + hideTitle.size.width / 2.0, y: contentHeight + hideBackground.size.height / 2.0))
|
||||
)
|
||||
|
||||
let hideSwitch = hideSwitch.update(
|
||||
component: SwitchComponent(
|
||||
value: !state.isHidden,
|
||||
valueUpdated: { [weak state] newValue in
|
||||
state?.updateIsHidden(!newValue)
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(
|
||||
width: context.availableSize.width - sideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right,
|
||||
height: CGFloat.greatestFiniteMagnitude
|
||||
),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(hideSwitch
|
||||
.position(CGPoint(x: context.availableSize.width - environment.safeInsets.right - sideInset - 16.0 - hideSwitch.size.width / 2.0, y: contentHeight + hideBackground.size.height / 2.0))
|
||||
)
|
||||
|
||||
contentHeight += hideBackground.size.height
|
||||
|
||||
let hideInfo = hideInfo.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: environment.strings.CreateTopic_ShowGeneralInfo,
|
||||
font: Font.regular(13.0),
|
||||
textColor: environment.theme.list.freeTextColor,
|
||||
paragraphAlignment: .natural)
|
||||
),
|
||||
horizontalAlignment: .natural,
|
||||
maximumNumberOfLines: 0
|
||||
),
|
||||
availableSize: CGSize(
|
||||
width: context.availableSize.width - sideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right,
|
||||
height: CGFloat.greatestFiniteMagnitude
|
||||
),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(hideInfo
|
||||
.position(CGPoint(x: environment.safeInsets.left + sideInset + 16.0 + hideInfo.size.width / 2.0, y: contentHeight + 7.0 + hideInfo.size.height / 2.0))
|
||||
)
|
||||
} else {
|
||||
let iconHeader = iconHeader.update(
|
||||
component: MultilineTextComponent(
|
||||
@ -686,13 +841,13 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
|
||||
maximumNumberOfLines: 1
|
||||
),
|
||||
availableSize: CGSize(
|
||||
width: context.availableSize.width - sideInset * 2.0,
|
||||
width: context.availableSize.width - sideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right,
|
||||
height: CGFloat.greatestFiniteMagnitude
|
||||
),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(iconHeader
|
||||
.position(CGPoint(x: sideInset * 2.0 + iconHeader.size.width / 2.0, y: contentHeight + iconHeader.size.height / 2.0))
|
||||
.position(CGPoint(x: environment.safeInsets.left + sideInset + 16.0 + iconHeader.size.width / 2.0, y: contentHeight + iconHeader.size.height / 2.0))
|
||||
)
|
||||
contentHeight += iconHeader.size.height + headerSpacing
|
||||
|
||||
@ -704,7 +859,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
|
||||
cornerRadius: 10.0
|
||||
),
|
||||
availableSize: CGSize(
|
||||
width: context.availableSize.width - sideInset * 2.0,
|
||||
width: context.availableSize.width - sideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right,
|
||||
height: context.availableSize.height - contentHeight - bottomInset
|
||||
),
|
||||
transition: context.transition
|
||||
@ -726,7 +881,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
|
||||
separatorColor: environment.theme.list.blocksBackgroundColor
|
||||
),
|
||||
environment: {},
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: availableHeight),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right, height: availableHeight),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(iconSelector
|
||||
@ -746,6 +901,8 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
|
||||
},
|
||||
openFeatured: {
|
||||
},
|
||||
openSearch: {
|
||||
},
|
||||
addGroupAction: { groupId, isPremiumLocked in
|
||||
guard let collectionId = groupId.base as? ItemCollectionId else {
|
||||
return
|
||||
@ -805,7 +962,7 @@ public class ForumCreateTopicScreen: ViewControllerComponentContainer {
|
||||
|
||||
public enum Mode: Equatable {
|
||||
case create
|
||||
case edit(threadId: Int64, threadInfo: EngineMessageHistoryThread.Info)
|
||||
case edit(threadId: Int64, threadInfo: EngineMessageHistoryThread.Info, isHidden: Bool)
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
@ -813,8 +970,8 @@ public class ForumCreateTopicScreen: ViewControllerComponentContainer {
|
||||
|
||||
private var doneBarItem: UIBarButtonItem?
|
||||
|
||||
private var state: (String, Int64?) = ("", nil)
|
||||
public var completion: (String, Int64?) -> Void = { _, _ in }
|
||||
private var state: (String, Int64?, Bool?) = ("", nil, nil)
|
||||
public var completion: (String, Int64?, Bool?) -> Void = { _, _, _ in }
|
||||
|
||||
public var isInProgress: Bool = false {
|
||||
didSet {
|
||||
@ -835,12 +992,15 @@ public class ForumCreateTopicScreen: ViewControllerComponentContainer {
|
||||
|
||||
var titleUpdatedImpl: ((String) -> Void)?
|
||||
var iconUpdatedImpl: ((Int64?) -> Void)?
|
||||
var isHiddenUpdatedImpl: ((Bool) -> Void)?
|
||||
var openPremiumImpl: (() -> Void)?
|
||||
|
||||
super.init(context: context, component: ForumCreateTopicScreenComponent(context: context, peerId: peerId, mode: mode, titleUpdated: { title in
|
||||
titleUpdatedImpl?(title)
|
||||
}, iconUpdated: { fileId in
|
||||
iconUpdatedImpl?(fileId)
|
||||
}, isHiddenUpdated: { isHidden in
|
||||
isHiddenUpdatedImpl?(isHidden)
|
||||
}, openPremium: {
|
||||
openPremiumImpl?()
|
||||
}), navigationBarAppearance: .transparent)
|
||||
@ -852,11 +1012,11 @@ public class ForumCreateTopicScreen: ViewControllerComponentContainer {
|
||||
case .create:
|
||||
title = presentationData.strings.CreateTopic_CreateTitle
|
||||
doneTitle = presentationData.strings.CreateTopic_Create
|
||||
case let .edit(_, topic):
|
||||
case let .edit(threadId, topic, isHidden):
|
||||
title = presentationData.strings.CreateTopic_EditTitle
|
||||
doneTitle = presentationData.strings.Common_Done
|
||||
|
||||
self.state = (topic.title, topic.icon)
|
||||
self.state = (topic.title, topic.icon, threadId == 1 ? isHidden : nil)
|
||||
}
|
||||
|
||||
self.title = title
|
||||
@ -872,12 +1032,12 @@ public class ForumCreateTopicScreen: ViewControllerComponentContainer {
|
||||
}
|
||||
|
||||
titleUpdatedImpl = { [weak self] title in
|
||||
guard let self else {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
self.doneBarItem?.isEnabled = !title.isEmpty
|
||||
strongSelf.doneBarItem?.isEnabled = !title.isEmpty
|
||||
|
||||
self.state = (title, self.state.1)
|
||||
strongSelf.state = (title, strongSelf.state.1, strongSelf.state.2)
|
||||
}
|
||||
|
||||
iconUpdatedImpl = { [weak self] fileId in
|
||||
@ -885,7 +1045,15 @@ public class ForumCreateTopicScreen: ViewControllerComponentContainer {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.state = (strongSelf.state.0, fileId)
|
||||
strongSelf.state = (strongSelf.state.0, fileId, strongSelf.state.2)
|
||||
}
|
||||
|
||||
isHiddenUpdatedImpl = { [weak self] isHidden in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.state = (strongSelf.state.0, strongSelf.state.1, isHidden)
|
||||
}
|
||||
|
||||
openPremiumImpl = { [weak self] in
|
||||
@ -916,6 +1084,6 @@ public class ForumCreateTopicScreen: ViewControllerComponentContainer {
|
||||
}
|
||||
|
||||
@objc private func createPressed() {
|
||||
self.completion(self.state.0, self.state.1)
|
||||
self.completion(self.state.0, self.state.1, self.state.2)
|
||||
}
|
||||
}
|
||||
|
@ -9588,6 +9588,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return
|
||||
}
|
||||
let _ = strongSelf.context.engine.peers.setForumChannelTopicClosed(id: peerId, threadId: threadId, isClosed: false).start()
|
||||
}, requestLayout: { [weak self] transition in
|
||||
if let strongSelf = self, let layout = strongSelf.validLayout {
|
||||
strongSelf.containerLayoutUpdated(layout, transition: transition)
|
||||
}
|
||||
}, chatController: { [weak self] in
|
||||
return self
|
||||
}, statuses: ChatPanelInterfaceInteractionStatuses(editingMessage: self.editingMessage.get(), startingBot: self.startingBot.get(), unblockingPeer: self.unblockingPeer.get(), searching: self.searching.get(), loadingMessage: self.loadingMessage.get(), inlineSearch: self.performingInlineSearch.get()))
|
||||
|
@ -1349,7 +1349,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
let heightAndOverflow = inputNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: cleanInsets.bottom, standardInputHeight: inputHeight, inputHeight: layout.inputHeight ?? 0.0, maximumHeight: maximumInputNodeHeight, inputPanelHeight: inputPanelNodeBaseHeight, transition: immediatelyLayoutInputNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState, deviceMetrics: layout.deviceMetrics, isVisible: self.isInFocus, isExpanded: self.inputPanelContainerNode.stableIsExpanded)
|
||||
|
||||
let boundedHeight = min(heightAndOverflow.0, layout.standardInputHeight)
|
||||
let boundedHeight = inputNode.followsDefaultHeight ? min(heightAndOverflow.0, layout.standardInputHeight) : heightAndOverflow.0
|
||||
|
||||
inputNodeHeightAndOverflow = (
|
||||
boundedHeight,
|
||||
|
@ -537,7 +537,9 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
itemLayoutType: .detailed,
|
||||
itemContentUniqueId: nil,
|
||||
warpContentsOnEdges: false,
|
||||
displaySearchWithPlaceholder: nil,
|
||||
displaySearchWithPlaceholder: "Search Stickers",
|
||||
searchInitiallyHidden: false,
|
||||
searchIsPlaceholderOnly: true,
|
||||
emptySearchResults: nil,
|
||||
enableLongPress: false,
|
||||
selectedItems: Set()
|
||||
@ -585,6 +587,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
openGifContextMenu: { _, _, _, _, _ in
|
||||
},
|
||||
loadMore: { _ in
|
||||
},
|
||||
openSearch: {
|
||||
}
|
||||
)
|
||||
|
||||
@ -597,7 +601,9 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
subject: .recent,
|
||||
items: [],
|
||||
isLoading: false,
|
||||
loadMoreToken: nil
|
||||
loadMoreToken: nil,
|
||||
displaySearchWithPlaceholder: nil,
|
||||
searchInitiallyHidden: false
|
||||
)
|
||||
))
|
||||
|
||||
@ -645,7 +651,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
availableGifSearchEmojies.append(EntityKeyboardComponent.GifSearchEmoji(emoji: reaction, file: file, title: title))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return InputData(
|
||||
emoji: emoji,
|
||||
stickers: stickers,
|
||||
@ -664,6 +670,9 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
private var inputDataDisposable: Disposable?
|
||||
private var hasRecentGifsDisposable: Disposable?
|
||||
|
||||
private let emojiSearchDisposable = MetaDisposable()
|
||||
private let emojiSearchResult = Promise<(groups: [EmojiPagerContentComponent.ItemGroup], id: AnyHashable)?>(nil)
|
||||
|
||||
private let controllerInteraction: ChatControllerInteraction?
|
||||
|
||||
private var inputNodeInteraction: ChatMediaInputNodeInteraction?
|
||||
@ -672,6 +681,12 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
|
||||
private var isMarkInputCollapsed: Bool = false
|
||||
|
||||
private var isEmojiSearchActive: Bool = false {
|
||||
didSet {
|
||||
self.followsDefaultHeight = !self.isEmojiSearchActive
|
||||
}
|
||||
}
|
||||
|
||||
var externalTopPanelContainerImpl: PagerExternalTopPanelContainer?
|
||||
override var externalTopPanelContainer: UIView? {
|
||||
return self.externalTopPanelContainerImpl
|
||||
@ -753,7 +768,9 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
subject: subject,
|
||||
items: items,
|
||||
isLoading: false,
|
||||
loadMoreToken: nil
|
||||
loadMoreToken: nil,
|
||||
displaySearchWithPlaceholder: "Search GIFs",
|
||||
searchInitiallyHidden: false
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -782,7 +799,9 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
subject: subject,
|
||||
items: items,
|
||||
isLoading: isLoading,
|
||||
loadMoreToken: nil
|
||||
loadMoreToken: nil,
|
||||
displaySearchWithPlaceholder: nil,
|
||||
searchInitiallyHidden: false
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -813,7 +832,9 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
subject: subject,
|
||||
items: items,
|
||||
isLoading: isLoading,
|
||||
loadMoreToken: loadMoreToken
|
||||
loadMoreToken: loadMoreToken,
|
||||
displaySearchWithPlaceholder: nil,
|
||||
searchInitiallyHidden: false
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -892,7 +913,9 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
subject: subject,
|
||||
items: items,
|
||||
isLoading: isLoading,
|
||||
loadMoreToken: loadMoreToken
|
||||
loadMoreToken: loadMoreToken,
|
||||
displaySearchWithPlaceholder: nil,
|
||||
searchInitiallyHidden: false
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -918,6 +941,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
|
||||
private weak var currentUndoOverlayController: UndoOverlayController?
|
||||
|
||||
|
||||
init(context: AccountContext, currentInputData: InputData, updatedInputData: Signal<InputData, NoError>, defaultToEmojiTab: Bool, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?, chatPeerId: PeerId?) {
|
||||
self.context = context
|
||||
self.currentInputData = currentInputData
|
||||
@ -1049,6 +1073,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
},
|
||||
openFeatured: {
|
||||
},
|
||||
openSearch: {
|
||||
},
|
||||
addGroupAction: { [weak self, weak controllerInteraction] groupId, isPremiumLocked in
|
||||
guard let controllerInteraction = controllerInteraction, let collectionId = groupId.base as? ItemCollectionId else {
|
||||
return
|
||||
@ -1092,6 +1118,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
return
|
||||
}
|
||||
if groupId == AnyHashable("recent") {
|
||||
controllerInteraction.dismissTextInput()
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme, fontSize: presentationData.listsFontSize))
|
||||
var items: [ActionSheetItem] = []
|
||||
@ -1128,9 +1155,136 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
navigationController: { [weak controllerInteraction] in
|
||||
return controllerInteraction?.navigationController()
|
||||
},
|
||||
requestUpdate: { _ in
|
||||
requestUpdate: { [weak self] transition in
|
||||
guard let _ = self else {
|
||||
return
|
||||
}
|
||||
// if !transition.animation.isImmediate {
|
||||
// strongSelf.interfaceInteraction?.requestLayout(transition.containedViewLayoutTransition)
|
||||
// }
|
||||
},
|
||||
updateSearchQuery: { _, _ in
|
||||
updateSearchQuery: { [weak self] rawQuery, languageCode in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let query = rawQuery.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
||||
if query.isEmpty {
|
||||
strongSelf.emojiSearchDisposable.set(nil)
|
||||
strongSelf.emojiSearchResult.set(.single(nil))
|
||||
} else {
|
||||
let context = strongSelf.context
|
||||
|
||||
var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query, completeMatch: false)
|
||||
if !languageCode.lowercased().hasPrefix("en") {
|
||||
signal = signal
|
||||
|> mapToSignal { keywords in
|
||||
return .single(keywords)
|
||||
|> then(
|
||||
context.engine.stickers.searchEmojiKeywords(inputLanguageCode: "en-US", query: query, completeMatch: query.count < 3)
|
||||
|> map { englishKeywords in
|
||||
return keywords + englishKeywords
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let hasPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
|> map { peer -> Bool in
|
||||
guard case let .user(user) = peer else {
|
||||
return false
|
||||
}
|
||||
return user.isPremium
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|
||||
let resultSignal = signal
|
||||
|> mapToSignal { keywords -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in
|
||||
return combineLatest(
|
||||
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000),
|
||||
context.engine.stickers.availableReactions(),
|
||||
hasPremium
|
||||
)
|
||||
|> take(1)
|
||||
|> map { view, availableReactions, hasPremium -> [EmojiPagerContentComponent.ItemGroup] in
|
||||
var result: [(String, TelegramMediaFile?, String)] = []
|
||||
|
||||
var allEmoticons: [String: String] = [:]
|
||||
for keyword in keywords {
|
||||
for emoticon in keyword.emoticons {
|
||||
allEmoticons[emoticon] = keyword.keyword
|
||||
}
|
||||
}
|
||||
|
||||
for entry in view.entries {
|
||||
guard let item = entry.item as? StickerPackItem else {
|
||||
continue
|
||||
}
|
||||
for attribute in item.file.attributes {
|
||||
switch attribute {
|
||||
case let .CustomEmoji(_, alt, _):
|
||||
if !item.file.isPremiumEmoji || hasPremium {
|
||||
if !alt.isEmpty, let keyword = allEmoticons[alt] {
|
||||
result.append((alt, item.file, keyword))
|
||||
} else if alt == query {
|
||||
result.append((alt, item.file, alt))
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var items: [EmojiPagerContentComponent.Item] = []
|
||||
|
||||
var existingIds = Set<MediaId>()
|
||||
for item in result {
|
||||
if let itemFile = item.1 {
|
||||
if existingIds.contains(itemFile.fileId) {
|
||||
continue
|
||||
}
|
||||
existingIds.insert(itemFile.fileId)
|
||||
let animationData = EntityKeyboardAnimationData(file: itemFile)
|
||||
let item = EmojiPagerContentComponent.Item(
|
||||
animationData: animationData,
|
||||
content: .animation(animationData),
|
||||
itemFile: itemFile, subgroupId: nil,
|
||||
icon: .none,
|
||||
accentTint: false
|
||||
)
|
||||
items.append(item)
|
||||
}
|
||||
}
|
||||
|
||||
return [EmojiPagerContentComponent.ItemGroup(
|
||||
supergroupId: "search",
|
||||
groupId: "search",
|
||||
title: nil,
|
||||
subtitle: nil,
|
||||
actionButtonTitle: nil,
|
||||
isFeatured: false,
|
||||
isPremiumLocked: false,
|
||||
isEmbedded: false,
|
||||
hasClear: false,
|
||||
collapsedLineCount: nil,
|
||||
displayPremiumBadges: false,
|
||||
headerItem: nil,
|
||||
items: items
|
||||
)]
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.emojiSearchDisposable.set((resultSignal
|
||||
|> delay(0.15, queue: .mainQueue())
|
||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.emojiSearchResult.set(.single((result, AnyHashable(query))))
|
||||
}))
|
||||
}
|
||||
},
|
||||
chatPeerId: chatPeerId,
|
||||
peekBehavior: nil,
|
||||
@ -1231,6 +1385,11 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
}
|
||||
))
|
||||
},
|
||||
openSearch: { [weak self] in
|
||||
if let strongSelf = self, let pagerView = strongSelf.entityKeyboardView.componentView as? EntityKeyboardComponent.View {
|
||||
pagerView.openSearch()
|
||||
}
|
||||
},
|
||||
addGroupAction: { groupId, isPremiumLocked in
|
||||
guard let controllerInteraction = controllerInteraction, let collectionId = groupId.base as? ItemCollectionId else {
|
||||
return
|
||||
@ -1281,6 +1440,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
return
|
||||
}
|
||||
if groupId == AnyHashable("recent") {
|
||||
controllerInteraction.dismissTextInput()
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme, fontSize: presentationData.listsFontSize))
|
||||
var items: [ActionSheetItem] = []
|
||||
@ -1346,15 +1506,27 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
|
||||
self.inputDataDisposable = (combineLatest(queue: .mainQueue(),
|
||||
updatedInputData,
|
||||
self.gifComponent.get()
|
||||
self.gifComponent.get(),
|
||||
self.emojiSearchResult.get()
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] inputData, gifs in
|
||||
|> deliverOnMainQueue).start(next: { [weak self] inputData, gifs, emojiSearchResult in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
var inputData = inputData
|
||||
inputData.gifs = gifs
|
||||
|
||||
if let emojiSearchResult = emojiSearchResult {
|
||||
var emptySearchResults: EmojiPagerContentComponent.EmptySearchResults?
|
||||
if !emojiSearchResult.groups.contains(where: { !$0.items.isEmpty }) {
|
||||
emptySearchResults = EmojiPagerContentComponent.EmptySearchResults(
|
||||
text: "No emoji found", //strongSelf.presentationData.strings.EmojiSearch_SearchStatusesEmptyResult,
|
||||
iconFile: nil
|
||||
)
|
||||
}
|
||||
inputData.emoji = inputData.emoji.withUpdatedItemGroups(itemGroups: emojiSearchResult.groups, itemContentUniqueId: emojiSearchResult.id, emptySearchResults: emptySearchResults)
|
||||
}
|
||||
|
||||
var transition: Transition = .immediate
|
||||
var useAnimation = false
|
||||
|
||||
@ -1436,6 +1608,11 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
return
|
||||
}
|
||||
gifContext.loadMore(token: token)
|
||||
},
|
||||
openSearch: { [weak self] in
|
||||
if let strongSelf = self, let pagerView = strongSelf.entityKeyboardView.componentView as? EntityKeyboardComponent.View {
|
||||
pagerView.openSearch()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@ -1474,6 +1651,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
deinit {
|
||||
self.inputDataDisposable?.dispose()
|
||||
self.hasRecentGifsDisposable?.dispose()
|
||||
self.emojiSearchDisposable.dispose()
|
||||
}
|
||||
|
||||
private func reloadGifContext() {
|
||||
@ -1515,7 +1693,10 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
let wasMarkedInputCollapsed = self.isMarkInputCollapsed
|
||||
self.isMarkInputCollapsed = false
|
||||
|
||||
let expandedHeight = standardInputHeight
|
||||
var expandedHeight = standardInputHeight
|
||||
if self.isEmojiSearchActive && !isExpanded {
|
||||
expandedHeight += 118.0
|
||||
}
|
||||
|
||||
var hiddenInputHeight: CGFloat = 0.0
|
||||
if self.hideInput && !self.adjustLayoutForHiddenInput {
|
||||
@ -1563,7 +1744,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
theme: interfaceState.theme,
|
||||
strings: interfaceState.strings,
|
||||
isContentInFocus: isVisible,
|
||||
containerInsets: UIEdgeInsets(top: 0.0, left: leftInset, bottom: bottomInset, right: rightInset),
|
||||
containerInsets: UIEdgeInsets(top: self.isEmojiSearchActive ? -34.0 : 0.0, left: leftInset, bottom: bottomInset, right: rightInset),
|
||||
topPanelInsets: UIEdgeInsets(),
|
||||
emojiContent: self.currentInputData.emoji,
|
||||
stickerContent: stickerContent,
|
||||
@ -1591,7 +1772,12 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
strongSelf.hideInputUpdated?(transition.containedViewLayoutTransition)
|
||||
}
|
||||
},
|
||||
hideTopPanelUpdated: { _, _ in
|
||||
hideTopPanelUpdated: { [weak self] hideTopPanel, transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.isEmojiSearchActive = hideTopPanel
|
||||
strongSelf.performLayout(transition: transition)
|
||||
},
|
||||
switchToTextInput: { [weak self] in
|
||||
self?.switchToTextInput?()
|
||||
@ -1637,7 +1823,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
deviceMetrics: deviceMetrics,
|
||||
hiddenInputHeight: hiddenInputHeight,
|
||||
displayBottomPanel: true,
|
||||
isExpanded: isExpanded
|
||||
isExpanded: isExpanded && !self.isEmojiSearchActive
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: width, height: expandedHeight)
|
||||
@ -1722,7 +1908,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
|
||||
private func processInputData(inputData: InputData) -> InputData {
|
||||
return InputData(
|
||||
emoji: inputData.emoji.withUpdatedItemGroups(itemGroups: self.processStableItemGroupList(category: .emoji, itemGroups: inputData.emoji.itemGroups), itemContentUniqueId: nil, emptySearchResults: nil),
|
||||
emoji: inputData.emoji.withUpdatedItemGroups(itemGroups: self.processStableItemGroupList(category: .emoji, itemGroups: inputData.emoji.itemGroups), itemContentUniqueId: inputData.emoji.itemContentUniqueId, emptySearchResults: inputData.emoji.emptySearchResults),
|
||||
stickers: inputData.stickers.flatMap { stickers in
|
||||
return stickers.withUpdatedItemGroups(itemGroups: self.processStableItemGroupList(category: .stickers, itemGroups: stickers.itemGroups), itemContentUniqueId: nil, emptySearchResults: nil)
|
||||
},
|
||||
@ -2027,6 +2213,8 @@ final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputV
|
||||
},
|
||||
openFeatured: {
|
||||
},
|
||||
openSearch: {
|
||||
},
|
||||
addGroupAction: { _, _ in
|
||||
},
|
||||
clearGroup: { [weak self] groupId in
|
||||
@ -2034,6 +2222,7 @@ final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputV
|
||||
return
|
||||
}
|
||||
if groupId == AnyHashable("recent") {
|
||||
strongSelf.window?.endEditing(true)
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme, fontSize: presentationData.listsFontSize))
|
||||
var items: [ActionSheetItem] = []
|
||||
|
@ -2822,7 +2822,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
}
|
||||
}
|
||||
let previousReplyInfoNodeFrame = replyInfoNode.frame
|
||||
replyInfoNode.frame = CGRect(origin: CGPoint(x: contentOrigin.x + layoutConstants.text.bubbleInsets.left, y: layoutConstants.bubble.contentInsets.top + replyInfoOriginY), size: replyInfoSizeApply.0)
|
||||
replyInfoNode.frame = CGRect(origin: CGPoint(x: contentOrigin.x + layoutConstants.text.bubbleInsets.left, y: layoutConstants.bubble.contentInsets.top + replyInfoOriginY), size: CGSize(width: backgroundFrame.width - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: replyInfoSizeApply.0.height))
|
||||
if case let .System(duration, _) = animation {
|
||||
if animateFrame {
|
||||
replyInfoNode.layer.animateFrame(from: previousReplyInfoNodeFrame, to: replyInfoNode.frame, duration: duration, timingFunction: timingFunction)
|
||||
|
@ -186,7 +186,7 @@ class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
|
||||
let automaticDownload = shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, authorPeerId: item.message.author?.id, contactsPeerIds: item.associatedData.contactsPeerIds, media: selectedFile!)
|
||||
|
||||
let (_, refineLayout) = interactiveFileLayout(ChatMessageInteractiveFileNode.Arguments(
|
||||
let (initialWidth, refineLayout) = interactiveFileLayout(ChatMessageInteractiveFileNode.Arguments(
|
||||
context: item.context,
|
||||
presentationData: item.presentationData,
|
||||
message: item.message,
|
||||
@ -239,19 +239,19 @@ class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
|
||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none, shareButtonOffset: isExpanded ? nil : CGPoint(x: displaySize.width + 4.0, y: -25.0), hidesHeaders: !isExpanded, avatarOffset: !isExpanded && isPlaying ? -100.0 : 0.0)
|
||||
|
||||
let width = videoFrame.width + 2.0
|
||||
let videoFrameWidth = videoFrame.width + 2.0
|
||||
|
||||
return (contentProperties, nil, width, { constrainedSize, position in
|
||||
var refinedWidth = videoFrame.width + 2.0
|
||||
return (contentProperties, nil, initialWidth, { constrainedSize, position in
|
||||
var refinedWidth = videoFrameWidth
|
||||
var finishLayout: ((CGFloat) -> (CGSize, (Bool, ListViewItemUpdateAnimation, ListViewItemApply?) -> Void))?
|
||||
|
||||
if isExpanded || !didSetupFileNode {
|
||||
(refinedWidth, finishLayout) = refineLayout(CGSize(width: constrainedSize.width - layoutConstants.file.bubbleInsets.left - layoutConstants.file.bubbleInsets.right, height: constrainedSize.height))
|
||||
(refinedWidth, finishLayout) = refineLayout(CGSize(width: constrainedSize.width - layoutConstants.file.bubbleInsets.left - layoutConstants.file.bubbleInsets.right - 44.0, height: constrainedSize.height))
|
||||
refinedWidth += layoutConstants.file.bubbleInsets.left + layoutConstants.file.bubbleInsets.right
|
||||
}
|
||||
|
||||
if !isExpanded {
|
||||
refinedWidth = videoFrame.width + 2.0
|
||||
refinedWidth = videoFrameWidth
|
||||
}
|
||||
|
||||
return (refinedWidth, { boundingWidth in
|
||||
|
@ -224,6 +224,8 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
}
|
||||
private var isWaitingForCollapse: Bool = false
|
||||
|
||||
private var hapticFeedback: HapticFeedback?
|
||||
|
||||
override init() {
|
||||
self.titleNode = TextNode()
|
||||
self.titleNode.displaysAsynchronously = false
|
||||
@ -355,11 +357,23 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
guard arguments.associatedData.isPremium else {
|
||||
if self.hapticFeedback == nil {
|
||||
self.hapticFeedback = HapticFeedback()
|
||||
}
|
||||
self.hapticFeedback?.impact(.medium)
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let tipController = UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_voiceToText", scale: 0.065, colors: [:], title: nil, text: presentationData.strings.Message_AudioTranscription_SubscribeToPremium, customUndoText: presentationData.strings.Message_AudioTranscription_SubscribeToPremiumAction), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { action in
|
||||
if case .undo = action {
|
||||
let introController = context.sharedContext.makePremiumIntroController(context: context, source: .settings)
|
||||
arguments.controllerInteraction.navigationController()?.pushViewController(introController, animated: true)
|
||||
var replaceImpl: ((ViewController) -> Void)?
|
||||
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .voiceToText, action: {
|
||||
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .settings)
|
||||
replaceImpl?(controller)
|
||||
})
|
||||
replaceImpl = { [weak controller] c in
|
||||
controller?.replace(with: c)
|
||||
}
|
||||
arguments.controllerInteraction.navigationController()?.pushViewController(controller, animated: true)
|
||||
|
||||
let _ = ApplicationSpecificNotice.incrementAudioTranscriptionSuggestion(accountManager: context.sharedContext.accountManager).start()
|
||||
}
|
||||
|
@ -141,6 +141,8 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
}
|
||||
private var isWaitingForCollapse: Bool = false
|
||||
|
||||
private var hapticFeedback: HapticFeedback?
|
||||
|
||||
var requestUpdateLayout: (Bool) -> Void = { _ in }
|
||||
|
||||
override init() {
|
||||
@ -1486,11 +1488,24 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
guard item.associatedData.isPremium else {
|
||||
if self.hapticFeedback == nil {
|
||||
self.hapticFeedback = HapticFeedback()
|
||||
}
|
||||
self.hapticFeedback?.impact(.medium)
|
||||
|
||||
let presentationData = item.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let tipController = UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_voiceToText", scale: 0.065, colors: [:], title: nil, text: presentationData.strings.Message_AudioTranscription_SubscribeToPremium, customUndoText: presentationData.strings.Message_AudioTranscription_SubscribeToPremiumAction), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { action in
|
||||
if case .undo = action {
|
||||
let introController = item.context.sharedContext.makePremiumIntroController(context: item.context, source: .settings)
|
||||
item.controllerInteraction.navigationController()?.pushViewController(introController, animated: true)
|
||||
let context = item.context
|
||||
var replaceImpl: ((ViewController) -> Void)?
|
||||
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .voiceToText, action: {
|
||||
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .settings)
|
||||
replaceImpl?(controller)
|
||||
})
|
||||
replaceImpl = { [weak controller] c in
|
||||
controller?.replace(with: c)
|
||||
}
|
||||
item.controllerInteraction.navigationController()?.pushViewController(controller, animated: true)
|
||||
|
||||
let _ = ApplicationSpecificNotice.incrementAudioTranscriptionSuggestion(accountManager: item.context.sharedContext.accountManager).start()
|
||||
}
|
||||
|
@ -156,6 +156,7 @@ final class ChatRecentActionsController: TelegramBaseController {
|
||||
}, insertText: { _ in
|
||||
}, backwardsDeleteText: {
|
||||
}, restartTopic: {
|
||||
}, requestLayout: { _ in
|
||||
}, chatController: {
|
||||
return nil
|
||||
}, statuses: nil)
|
||||
|
@ -374,6 +374,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
|
||||
}, insertText: { _ in
|
||||
}, backwardsDeleteText: {
|
||||
}, restartTopic: {
|
||||
}, requestLayout: { _ in
|
||||
}, chatController: {
|
||||
return nil
|
||||
}, statuses: nil)
|
||||
@ -2906,15 +2907,22 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
case .edit:
|
||||
if case let .replyThread(message) = strongSelf.chatLocation {
|
||||
let threadId = Int64(message.messageId.id)
|
||||
if let threadInfo = strongSelf.data?.threadData?.info {
|
||||
let controller = ForumCreateTopicScreen(context: strongSelf.context, peerId: strongSelf.peerId, mode: .edit(threadId: threadId, threadInfo: threadInfo))
|
||||
if let threadData = strongSelf.data?.threadData {
|
||||
let controller = ForumCreateTopicScreen(context: strongSelf.context, peerId: strongSelf.peerId, mode: .edit(threadId: threadId, threadInfo: threadData.info, isHidden: threadData.isHidden))
|
||||
controller.navigationPresentation = .modal
|
||||
let context = strongSelf.context
|
||||
controller.completion = { [weak controller] title, fileId in
|
||||
controller.completion = { [weak controller] title, fileId, isHidden in
|
||||
let _ = (context.engine.peers.editForumChannelTopic(id: peerId, threadId: threadId, title: title, iconFileId: fileId)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
controller?.dismiss()
|
||||
})
|
||||
|
||||
if let isHidden = isHidden {
|
||||
let _ = (context.engine.peers.setForumChannelTopicHidden(id: peerId, threadId: threadId, isHidden: isHidden)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
controller?.dismiss()
|
||||
})
|
||||
}
|
||||
}
|
||||
strongSelf.controller?.push(controller)
|
||||
}
|
||||
|
@ -370,6 +370,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
}, insertText: { _ in
|
||||
}, backwardsDeleteText: {
|
||||
}, restartTopic: {
|
||||
}, requestLayout: { _ in
|
||||
}, chatController: {
|
||||
return nil
|
||||
}, statuses: nil)
|
||||
|
@ -1424,50 +1424,83 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
public func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource) -> ViewController {
|
||||
let mappedSource: PremiumSource
|
||||
switch source {
|
||||
case .settings:
|
||||
mappedSource = .settings
|
||||
case .stickers:
|
||||
mappedSource = .stickers
|
||||
case .reactions:
|
||||
mappedSource = .reactions
|
||||
case .ads:
|
||||
mappedSource = .ads
|
||||
case .upload:
|
||||
mappedSource = .upload
|
||||
case .groupsAndChannels:
|
||||
mappedSource = .groupsAndChannels
|
||||
case .pinnedChats:
|
||||
mappedSource = .pinnedChats
|
||||
case .publicLinks:
|
||||
mappedSource = .publicLinks
|
||||
case .savedGifs:
|
||||
mappedSource = .savedGifs
|
||||
case .savedStickers:
|
||||
mappedSource = .savedStickers
|
||||
case .folders:
|
||||
mappedSource = .folders
|
||||
case .chatsPerFolder:
|
||||
mappedSource = .chatsPerFolder
|
||||
case .appIcons:
|
||||
mappedSource = .appIcons
|
||||
case .accounts:
|
||||
mappedSource = .accounts
|
||||
case .about:
|
||||
mappedSource = .about
|
||||
case let .deeplink(reference):
|
||||
mappedSource = .deeplink(reference)
|
||||
case let .profile(peerId):
|
||||
mappedSource = .profile(peerId)
|
||||
case let .emojiStatus(peerId, fileId, file, packTitle):
|
||||
mappedSource = .emojiStatus(peerId, fileId, file, packTitle)
|
||||
case .voiceToText:
|
||||
mappedSource = .voiceToText
|
||||
case .fasterDownload:
|
||||
mappedSource = .fasterDownload
|
||||
case .settings:
|
||||
mappedSource = .settings
|
||||
case .stickers:
|
||||
mappedSource = .stickers
|
||||
case .reactions:
|
||||
mappedSource = .reactions
|
||||
case .ads:
|
||||
mappedSource = .ads
|
||||
case .upload:
|
||||
mappedSource = .upload
|
||||
case .groupsAndChannels:
|
||||
mappedSource = .groupsAndChannels
|
||||
case .pinnedChats:
|
||||
mappedSource = .pinnedChats
|
||||
case .publicLinks:
|
||||
mappedSource = .publicLinks
|
||||
case .savedGifs:
|
||||
mappedSource = .savedGifs
|
||||
case .savedStickers:
|
||||
mappedSource = .savedStickers
|
||||
case .folders:
|
||||
mappedSource = .folders
|
||||
case .chatsPerFolder:
|
||||
mappedSource = .chatsPerFolder
|
||||
case .appIcons:
|
||||
mappedSource = .appIcons
|
||||
case .accounts:
|
||||
mappedSource = .accounts
|
||||
case .about:
|
||||
mappedSource = .about
|
||||
case let .deeplink(reference):
|
||||
mappedSource = .deeplink(reference)
|
||||
case let .profile(peerId):
|
||||
mappedSource = .profile(peerId)
|
||||
case let .emojiStatus(peerId, fileId, file, packTitle):
|
||||
mappedSource = .emojiStatus(peerId, fileId, file, packTitle)
|
||||
case .voiceToText:
|
||||
mappedSource = .voiceToText
|
||||
case .fasterDownload:
|
||||
mappedSource = .fasterDownload
|
||||
}
|
||||
return PremiumIntroScreen(context: context, source: mappedSource)
|
||||
}
|
||||
|
||||
public func makePremiumDemoController(context: AccountContext, subject: PremiumDemoSubject, action: @escaping () -> Void) -> ViewController {
|
||||
let mappedSubject: PremiumDemoScreen.Subject
|
||||
switch subject {
|
||||
case .doubleLimits:
|
||||
mappedSubject = .doubleLimits
|
||||
case .moreUpload:
|
||||
mappedSubject = .moreUpload
|
||||
case .fasterDownload:
|
||||
mappedSubject = .fasterDownload
|
||||
case .voiceToText:
|
||||
mappedSubject = .voiceToText
|
||||
case .noAds:
|
||||
mappedSubject = .noAds
|
||||
case .uniqueReactions:
|
||||
mappedSubject = .uniqueReactions
|
||||
case .premiumStickers:
|
||||
mappedSubject = .premiumStickers
|
||||
case .advancedChatManagement:
|
||||
mappedSubject = .advancedChatManagement
|
||||
case .profileBadge:
|
||||
mappedSubject = .profileBadge
|
||||
case .animatedUserpics:
|
||||
mappedSubject = .animatedUserpics
|
||||
case .appIcons:
|
||||
mappedSubject = .appIcons
|
||||
case .animatedEmoji:
|
||||
mappedSubject = .animatedEmoji
|
||||
case .emojiStatus:
|
||||
mappedSubject = .emojiStatus
|
||||
}
|
||||
return PremiumDemoScreen(context: context, subject: mappedSubject, action: action)
|
||||
}
|
||||
|
||||
public func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?) -> ViewController {
|
||||
return StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: mainStickerPack, stickerPacks: stickerPacks, loadedStickerPacks: loadedStickerPacks, parentNavigationController: parentNavigationController, sendSticker: sendSticker)
|
||||
}
|
||||
|
@ -390,7 +390,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
|
||||
}
|
||||
subscriber.putNext(result)
|
||||
}, completed: {
|
||||
subscriber.putCompletion()
|
||||
// subscriber.putCompletion()
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -287,7 +287,6 @@ final class YoutubeEmbedImplementation: WebEmbedImplementation {
|
||||
}
|
||||
|
||||
let _ = download
|
||||
let _ = failed
|
||||
|
||||
if let position = position {
|
||||
if self.ignoreEarlierTimestamps {
|
||||
@ -307,8 +306,14 @@ final class YoutubeEmbedImplementation: WebEmbedImplementation {
|
||||
}
|
||||
|
||||
if let updateStatus = self.updateStatus, let playback = playback, let duration = duration {
|
||||
let playbackStatus: MediaPlayerPlaybackStatus
|
||||
switch playback {
|
||||
if let failed, failed {
|
||||
self.status = MediaPlayerStatus(generationTimestamp: self.status.generationTimestamp, duration: 0.0, dimensions: self.status.dimensions, timestamp: 0.0, baseRate: 0.0, seekId: self.status.seekId, status: .playing, soundEnabled: false)
|
||||
updateStatus(self.status)
|
||||
|
||||
self.onPlaybackStarted?()
|
||||
} else {
|
||||
let playbackStatus: MediaPlayerPlaybackStatus
|
||||
switch playback {
|
||||
case 0:
|
||||
if newTimestamp > Double(duration) - 1.0 {
|
||||
self.isPlaying = false
|
||||
@ -325,17 +330,18 @@ final class YoutubeEmbedImplementation: WebEmbedImplementation {
|
||||
playbackStatus = .buffering(initial: !self.started, whilePlaying: self.isPlaying, progress: 0.0, display: false)
|
||||
default:
|
||||
playbackStatus = .buffering(initial: true, whilePlaying: true, progress: 0.0, display: false)
|
||||
}
|
||||
|
||||
if case .playing = playbackStatus, !self.started {
|
||||
self.started = true
|
||||
print("YT started in \(CFAbsoluteTimeGetCurrent() - self.benchmarkStartTime)")
|
||||
}
|
||||
|
||||
self.onPlaybackStarted?()
|
||||
if case .playing = playbackStatus, !self.started {
|
||||
self.started = true
|
||||
print("YT started in \(CFAbsoluteTimeGetCurrent() - self.benchmarkStartTime)")
|
||||
|
||||
self.onPlaybackStarted?()
|
||||
}
|
||||
|
||||
self.status = MediaPlayerStatus(generationTimestamp: self.status.generationTimestamp, duration: Double(duration), dimensions: self.status.dimensions, timestamp: newTimestamp, baseRate: self.status.baseRate, seekId: self.status.seekId, status: playbackStatus, soundEnabled: true)
|
||||
updateStatus(self.status)
|
||||
}
|
||||
|
||||
self.status = MediaPlayerStatus(generationTimestamp: self.status.generationTimestamp, duration: Double(duration), dimensions: self.status.dimensions, timestamp: newTimestamp, baseRate: self.status.baseRate, seekId: self.status.seekId, status: playbackStatus, soundEnabled: true)
|
||||
updateStatus(self.status)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user