mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-10 08:20:16 +00:00
Merge commit '81f6181fbfa2da0d95862134b47a0a8795745b83'
This commit is contained in:
commit
444d29b23f
@ -246,7 +246,7 @@ public enum ChatControllerSubject: Equatable {
|
|||||||
|
|
||||||
public enum ChatControllerPresentationMode: Equatable {
|
public enum ChatControllerPresentationMode: Equatable {
|
||||||
case standard(previewing: Bool)
|
case standard(previewing: Bool)
|
||||||
case overlay
|
case overlay(NavigationController?)
|
||||||
case inline(NavigationController?)
|
case inline(NavigationController?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -44,19 +44,19 @@ private func getCoveringViewSnaphot(window: Window1) -> UIImage? {
|
|||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
context.scaleBy(x: scale, y: scale)
|
context.scaleBy(x: scale, y: scale)
|
||||||
UIGraphicsPushContext(context)
|
UIGraphicsPushContext(context)
|
||||||
window.forEachViewController { controller in
|
window.forEachViewController({ controller in
|
||||||
if let controller = controller as? PasscodeEntryController {
|
if let controller = controller as? PasscodeEntryController {
|
||||||
controller.displayNode.alpha = 0.0
|
controller.displayNode.alpha = 0.0
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
})
|
||||||
window.hostView.containerView.drawHierarchy(in: CGRect(origin: CGPoint(), size: unscaledSize), afterScreenUpdates: false)
|
window.hostView.containerView.drawHierarchy(in: CGRect(origin: CGPoint(), size: unscaledSize), afterScreenUpdates: false)
|
||||||
window.forEachViewController { controller in
|
window.forEachViewController({ controller in
|
||||||
if let controller = controller as? PasscodeEntryController {
|
if let controller = controller as? PasscodeEntryController {
|
||||||
controller.displayNode.alpha = 1.0
|
controller.displayNode.alpha = 1.0
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
})
|
||||||
UIGraphicsPopContext()
|
UIGraphicsPopContext()
|
||||||
}).flatMap(applyScreenshotEffectToImage)
|
}).flatMap(applyScreenshotEffectToImage)
|
||||||
}
|
}
|
||||||
|
|||||||
29
submodules/ArchivedStickerPacksNotice/BUCK
Normal file
29
submodules/ArchivedStickerPacksNotice/BUCK
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
load("//Config:buck_rule_macros.bzl", "static_library")
|
||||||
|
|
||||||
|
static_library(
|
||||||
|
name = "ArchivedStickerPacksNotice",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
deps = [
|
||||||
|
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared",
|
||||||
|
"//submodules/AsyncDisplayKit:AsyncDisplayKit#shared",
|
||||||
|
"//submodules/Display:Display#shared",
|
||||||
|
"//submodules/Postbox:Postbox#shared",
|
||||||
|
"//submodules/TelegramCore:TelegramCore#shared",
|
||||||
|
"//submodules/SyncCore:SyncCore#shared",
|
||||||
|
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||||
|
"//submodules/AccountContext:AccountContext",
|
||||||
|
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
|
||||||
|
"//submodules/StickerResources:StickerResources",
|
||||||
|
"//submodules/AlertUI:AlertUI",
|
||||||
|
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||||
|
"//submodules/MergeLists:MergeLists",
|
||||||
|
"//submodules/ItemListUI:ItemListUI",
|
||||||
|
"//submodules/ItemListStickerPackItem:ItemListStickerPackItem",
|
||||||
|
],
|
||||||
|
frameworks = [
|
||||||
|
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||||
|
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
|
||||||
|
],
|
||||||
|
)
|
||||||
@ -0,0 +1,316 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import Display
|
||||||
|
import SwiftSignalKit
|
||||||
|
import Postbox
|
||||||
|
import TelegramCore
|
||||||
|
import SyncCore
|
||||||
|
import TelegramPresentationData
|
||||||
|
import ActivityIndicator
|
||||||
|
import AccountContext
|
||||||
|
import AlertUI
|
||||||
|
import PresentationDataUtils
|
||||||
|
import MergeLists
|
||||||
|
import ItemListUI
|
||||||
|
import ItemListStickerPackItem
|
||||||
|
|
||||||
|
private struct ArchivedStickersNoticeEntry: Comparable, Identifiable {
|
||||||
|
let index: Int
|
||||||
|
let info: StickerPackCollectionInfo
|
||||||
|
let topItem: StickerPackItem?
|
||||||
|
let count: String
|
||||||
|
|
||||||
|
var stableId: ItemCollectionId {
|
||||||
|
return info.id
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: ArchivedStickersNoticeEntry, rhs: ArchivedStickersNoticeEntry) -> Bool {
|
||||||
|
return lhs.index == rhs.index && lhs.info.id == rhs.info.id && lhs.count == rhs.count
|
||||||
|
}
|
||||||
|
|
||||||
|
static func <(lhs: ArchivedStickersNoticeEntry, rhs: ArchivedStickersNoticeEntry) -> Bool {
|
||||||
|
return lhs.index < rhs.index
|
||||||
|
}
|
||||||
|
|
||||||
|
func item(account: Account, presentationData: PresentationData) -> ListViewItem {
|
||||||
|
return ItemListStickerPackItem(presentationData: ItemListPresentationData(presentationData), account: account, packInfo: info, itemCount: self.count, topItem: topItem, unread: false, control: .none, editing: ItemListStickerPackItemEditing(editable: false, editing: false, revealed: false, reorderable: false), enabled: true, playAnimatedStickers: true, sectionId: 0, action: {
|
||||||
|
}, setPackIdWithRevealedOptions: { current, previous in
|
||||||
|
}, addPack: {
|
||||||
|
}, removePack: {
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct ArchivedStickersNoticeTransition {
|
||||||
|
let deletions: [ListViewDeleteItem]
|
||||||
|
let insertions: [ListViewInsertItem]
|
||||||
|
let updates: [ListViewUpdateItem]
|
||||||
|
}
|
||||||
|
|
||||||
|
private func preparedTransition(from fromEntries: [ArchivedStickersNoticeEntry], to toEntries: [ArchivedStickersNoticeEntry], account: Account, presentationData: PresentationData) -> ArchivedStickersNoticeTransition {
|
||||||
|
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||||
|
|
||||||
|
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
||||||
|
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, presentationData: presentationData), directionHint: nil) }
|
||||||
|
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, presentationData: presentationData), directionHint: nil) }
|
||||||
|
|
||||||
|
return ArchivedStickersNoticeTransition(deletions: deletions, insertions: insertions, updates: updates)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private final class ArchivedStickersNoticeAlertContentNode: AlertContentNode {
|
||||||
|
private let presentationData: PresentationData
|
||||||
|
private let archivedStickerPacks: [(StickerPackCollectionInfo, StickerPackItem?)]
|
||||||
|
|
||||||
|
private let textNode: ASTextNode
|
||||||
|
private let listView: ListView
|
||||||
|
|
||||||
|
private var enqueuedTransitions: [ArchivedStickersNoticeTransition] = []
|
||||||
|
|
||||||
|
private let actionNodesSeparator: ASDisplayNode
|
||||||
|
private let actionNodes: [TextAlertContentActionNode]
|
||||||
|
private let actionVerticalSeparators: [ASDisplayNode]
|
||||||
|
|
||||||
|
private let disposable = MetaDisposable()
|
||||||
|
|
||||||
|
private var validLayout: CGSize?
|
||||||
|
|
||||||
|
override var dismissOnOutsideTap: Bool {
|
||||||
|
return self.isUserInteractionEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
init(theme: AlertControllerTheme, account: Account, presentationData: PresentationData, archivedStickerPacks: [(StickerPackCollectionInfo, StickerPackItem?)], actions: [TextAlertAction]) {
|
||||||
|
self.presentationData = presentationData
|
||||||
|
self.archivedStickerPacks = archivedStickerPacks
|
||||||
|
|
||||||
|
self.textNode = ASTextNode()
|
||||||
|
self.textNode.maximumNumberOfLines = 4
|
||||||
|
|
||||||
|
self.listView = ListView()
|
||||||
|
self.listView.isOpaque = false
|
||||||
|
|
||||||
|
self.actionNodesSeparator = ASDisplayNode()
|
||||||
|
self.actionNodesSeparator.isLayerBacked = true
|
||||||
|
|
||||||
|
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
|
||||||
|
return TextAlertContentActionNode(theme: theme, action: action)
|
||||||
|
}
|
||||||
|
|
||||||
|
var actionVerticalSeparators: [ASDisplayNode] = []
|
||||||
|
if actions.count > 1 {
|
||||||
|
for _ in 0 ..< actions.count - 1 {
|
||||||
|
let separatorNode = ASDisplayNode()
|
||||||
|
separatorNode.isLayerBacked = true
|
||||||
|
actionVerticalSeparators.append(separatorNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.actionVerticalSeparators = actionVerticalSeparators
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.addSubnode(self.textNode)
|
||||||
|
self.addSubnode(self.listView)
|
||||||
|
|
||||||
|
self.addSubnode(self.actionNodesSeparator)
|
||||||
|
|
||||||
|
for actionNode in self.actionNodes {
|
||||||
|
self.addSubnode(actionNode)
|
||||||
|
}
|
||||||
|
self.actionNodes.last?.actionEnabled = false
|
||||||
|
|
||||||
|
for separatorNode in self.actionVerticalSeparators {
|
||||||
|
self.addSubnode(separatorNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.updateTheme(theme)
|
||||||
|
|
||||||
|
var index: Int = 0
|
||||||
|
var entries: [ArchivedStickersNoticeEntry] = []
|
||||||
|
for pack in archivedStickerPacks {
|
||||||
|
entries.append(ArchivedStickersNoticeEntry(index: index, info: pack.0, topItem: pack.1, count: presentationData.strings.StickerPack_StickerCount(pack.0.count)))
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
let transition = preparedTransition(from: [], to: entries, account: account, presentationData: presentationData)
|
||||||
|
self.enqueueTransition(transition)
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.disposable.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func enqueueTransition(_ transition: ArchivedStickersNoticeTransition) {
|
||||||
|
self.enqueuedTransitions.append(transition)
|
||||||
|
|
||||||
|
if let _ = self.validLayout {
|
||||||
|
while !self.enqueuedTransitions.isEmpty {
|
||||||
|
self.dequeueTransition()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func dequeueTransition() {
|
||||||
|
guard let layout = self.validLayout, let transition = self.enqueuedTransitions.first else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.enqueuedTransitions.remove(at: 0)
|
||||||
|
|
||||||
|
var options = ListViewDeleteAndInsertOptions()
|
||||||
|
|
||||||
|
self.listView.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override func updateTheme(_ theme: AlertControllerTheme) {
|
||||||
|
self.textNode.attributedText = NSAttributedString(string: self.presentationData.strings.ArchivedPacksAlert_Title, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center)
|
||||||
|
|
||||||
|
self.actionNodesSeparator.backgroundColor = theme.separatorColor
|
||||||
|
for actionNode in self.actionNodes {
|
||||||
|
actionNode.updateTheme(theme)
|
||||||
|
}
|
||||||
|
for separatorNode in self.actionVerticalSeparators {
|
||||||
|
separatorNode.backgroundColor = theme.separatorColor
|
||||||
|
}
|
||||||
|
|
||||||
|
if let size = self.validLayout {
|
||||||
|
_ = self.updateLayout(size: size, transition: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||||
|
var size = size
|
||||||
|
size.width = min(size.width, 270.0)
|
||||||
|
let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude)
|
||||||
|
|
||||||
|
let hadValidLayout = self.validLayout != nil
|
||||||
|
|
||||||
|
self.validLayout = size
|
||||||
|
|
||||||
|
var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
|
||||||
|
|
||||||
|
let textSize = self.textNode.measure(measureSize)
|
||||||
|
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize))
|
||||||
|
origin.y += textSize.height + 16.0
|
||||||
|
|
||||||
|
let actionButtonHeight: CGFloat = 44.0
|
||||||
|
var minActionsWidth: CGFloat = 0.0
|
||||||
|
let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
|
||||||
|
let actionTitleInsets: CGFloat = 8.0
|
||||||
|
|
||||||
|
var effectiveActionLayout = TextAlertContentActionLayout.horizontal
|
||||||
|
for actionNode in self.actionNodes {
|
||||||
|
let actionTitleSize = actionNode.titleNode.measure(CGSize(width: maxActionWidth, height: actionButtonHeight))
|
||||||
|
if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 {
|
||||||
|
effectiveActionLayout = .vertical
|
||||||
|
}
|
||||||
|
switch effectiveActionLayout {
|
||||||
|
case .horizontal:
|
||||||
|
minActionsWidth += actionTitleSize.width + actionTitleInsets
|
||||||
|
case .vertical:
|
||||||
|
minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0)
|
||||||
|
|
||||||
|
var contentWidth = max(textSize.width, minActionsWidth)
|
||||||
|
contentWidth = max(contentWidth, 234.0)
|
||||||
|
|
||||||
|
var actionsHeight: CGFloat = 0.0
|
||||||
|
switch effectiveActionLayout {
|
||||||
|
case .horizontal:
|
||||||
|
actionsHeight = actionButtonHeight
|
||||||
|
case .vertical:
|
||||||
|
actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
let resultWidth = contentWidth + insets.left + insets.right
|
||||||
|
|
||||||
|
let listHeight: CGFloat = CGFloat(min(3, self.archivedStickerPacks.count)) * 56.0
|
||||||
|
|
||||||
|
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
||||||
|
self.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: CGSize(width: resultWidth, height: listHeight), insets: UIEdgeInsets(top: -35.0, left: 0.0, bottom: 0.0, right: 0.0), headerInsets: UIEdgeInsets(), scrollIndicatorInsets: UIEdgeInsets(), duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||||
|
transition.updateFrame(node: self.listView, frame: CGRect(x: 0.0, y: origin.y, width: resultWidth, height: listHeight))
|
||||||
|
|
||||||
|
let resultSize = CGSize(width: resultWidth, height: textSize.height + actionsHeight + listHeight + 10.0 + insets.top + insets.bottom)
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||||
|
|
||||||
|
var actionOffset: CGFloat = 0.0
|
||||||
|
let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count))
|
||||||
|
var separatorIndex = -1
|
||||||
|
var nodeIndex = 0
|
||||||
|
for actionNode in self.actionNodes {
|
||||||
|
if separatorIndex >= 0 {
|
||||||
|
let separatorNode = self.actionVerticalSeparators[separatorIndex]
|
||||||
|
switch effectiveActionLayout {
|
||||||
|
case .horizontal:
|
||||||
|
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel)))
|
||||||
|
case .vertical:
|
||||||
|
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
separatorIndex += 1
|
||||||
|
|
||||||
|
let currentActionWidth: CGFloat
|
||||||
|
switch effectiveActionLayout {
|
||||||
|
case .horizontal:
|
||||||
|
if nodeIndex == self.actionNodes.count - 1 {
|
||||||
|
currentActionWidth = resultSize.width - actionOffset
|
||||||
|
} else {
|
||||||
|
currentActionWidth = actionWidth
|
||||||
|
}
|
||||||
|
case .vertical:
|
||||||
|
currentActionWidth = resultSize.width
|
||||||
|
}
|
||||||
|
|
||||||
|
let actionNodeFrame: CGRect
|
||||||
|
switch effectiveActionLayout {
|
||||||
|
case .horizontal:
|
||||||
|
actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
|
||||||
|
actionOffset += currentActionWidth
|
||||||
|
case .vertical:
|
||||||
|
actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
|
||||||
|
actionOffset += actionButtonHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.updateFrame(node: actionNode, frame: actionNodeFrame)
|
||||||
|
|
||||||
|
nodeIndex += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hadValidLayout {
|
||||||
|
while !self.enqueuedTransitions.isEmpty {
|
||||||
|
self.dequeueTransition()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func archivedStickerPacksNoticeController(context: AccountContext, archivedStickerPacks: [(StickerPackCollectionInfo, StickerPackItem?)]) -> ViewController {
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
|
var dismissImpl: (() -> Void)?
|
||||||
|
|
||||||
|
let disposable = MetaDisposable()
|
||||||
|
|
||||||
|
let contentNode = ArchivedStickersNoticeAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), account: context.account, presentationData: presentationData, archivedStickerPacks: archivedStickerPacks, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
|
||||||
|
dismissImpl?()
|
||||||
|
})])
|
||||||
|
|
||||||
|
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode)
|
||||||
|
let presentationDataDisposable = context.sharedContext.presentationData.start(next: { [weak controller, weak contentNode] presentationData in
|
||||||
|
controller?.theme = AlertControllerTheme(presentationData: presentationData)
|
||||||
|
})
|
||||||
|
controller.dismissed = {
|
||||||
|
presentationDataDisposable.dispose()
|
||||||
|
disposable.dispose()
|
||||||
|
}
|
||||||
|
dismissImpl = { [weak controller, weak contentNode] in
|
||||||
|
controller?.dismissAnimated()
|
||||||
|
}
|
||||||
|
return controller
|
||||||
|
}
|
||||||
@ -306,7 +306,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
|||||||
strongSelf.titleView.title = NetworkStatusTitle(text: defaultTitle, activity: false, hasProxy: isRoot && hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked)
|
strongSelf.titleView.title = NetworkStatusTitle(text: defaultTitle, activity: false, hasProxy: isRoot && hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked)
|
||||||
}
|
}
|
||||||
if case .root = groupId, checkProxy {
|
if case .root = groupId, checkProxy {
|
||||||
if strongSelf.proxyUnavailableTooltipController == nil && !strongSelf.didShowProxyUnavailableTooltipController && strongSelf.isNodeLoaded && strongSelf.displayNode.view.window != nil {
|
if strongSelf.proxyUnavailableTooltipController == nil && !strongSelf.didShowProxyUnavailableTooltipController && strongSelf.isNodeLoaded && strongSelf.displayNode.view.window != nil && strongSelf.navigationController?.topViewController === self {
|
||||||
strongSelf.didShowProxyUnavailableTooltipController = true
|
strongSelf.didShowProxyUnavailableTooltipController = true
|
||||||
let tooltipController = TooltipController(content: .text(strongSelf.presentationData.strings.Proxy_TooltipUnavailable), baseFontSize: strongSelf.presentationData.listsFontSize.baseDisplaySize, timeout: 60.0, dismissByTapOutside: true)
|
let tooltipController = TooltipController(content: .text(strongSelf.presentationData.strings.Proxy_TooltipUnavailable), baseFontSize: strongSelf.presentationData.listsFontSize.baseDisplaySize, timeout: 60.0, dismissByTapOutside: true)
|
||||||
strongSelf.proxyUnavailableTooltipController = tooltipController
|
strongSelf.proxyUnavailableTooltipController = tooltipController
|
||||||
|
|||||||
@ -1222,11 +1222,13 @@ public class Window1 {
|
|||||||
return hidden
|
return hidden
|
||||||
}
|
}
|
||||||
|
|
||||||
public func forEachViewController(_ f: (ContainableController) -> Bool) {
|
public func forEachViewController(_ f: (ContainableController) -> Bool, excludeNavigationSubControllers: Bool = false) {
|
||||||
if let navigationController = self._rootController as? NavigationController {
|
if let navigationController = self._rootController as? NavigationController {
|
||||||
|
if !excludeNavigationSubControllers {
|
||||||
for case let controller as ContainableController in navigationController.viewControllers {
|
for case let controller as ContainableController in navigationController.viewControllers {
|
||||||
!f(controller)
|
!f(controller)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if let controller = navigationController.topOverlayController {
|
if let controller = navigationController.topOverlayController {
|
||||||
!f(controller)
|
!f(controller)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,6 +48,13 @@ private final class LegacyComponentsAccessCheckerImpl: NSObject, LegacyComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func checkPhotoAuthorizationStatus(for intent: TGPhotoAccessIntent, alertDismissCompletion: (() -> Void)!) -> Bool {
|
public func checkPhotoAuthorizationStatus(for intent: TGPhotoAccessIntent, alertDismissCompletion: (() -> Void)!) -> Bool {
|
||||||
|
if let context = self.context {
|
||||||
|
DeviceAccess.authorizeAccess(to: .mediaLibrary(.send), presentationData: context.sharedContext.currentPresentationData.with { $0 }, present: context.sharedContext.presentGlobalController, openSettings: context.sharedContext.applicationBindings.openSettings, { value in
|
||||||
|
if !value {
|
||||||
|
alertDismissCompletion?()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -551,7 +551,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
|
|||||||
context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
|
context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: resolvedWallpaper))
|
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: resolvedWallpaper, creatorAccountId: context.account.id))
|
||||||
|
|
||||||
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
|
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
|
||||||
themeSpecificChatWallpapers[themeReference.index] = nil
|
themeSpecificChatWallpapers[themeReference.index] = nil
|
||||||
@ -585,7 +585,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
|
|||||||
context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
|
context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: resolvedWallpaper))
|
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: resolvedWallpaper, creatorAccountId: context.account.id))
|
||||||
|
|
||||||
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
|
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
|
||||||
themeSpecificChatWallpapers[themeReference.index] = nil
|
themeSpecificChatWallpapers[themeReference.index] = nil
|
||||||
|
|||||||
@ -260,7 +260,7 @@ final class ThemeAccentColorController: ViewController {
|
|||||||
if case let .result(resultTheme) = next {
|
if case let .result(resultTheme) = next {
|
||||||
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start()
|
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start()
|
||||||
return updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
|
return updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
|
||||||
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: wallpaper))
|
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: wallpaper, creatorAccountId: context.account.id))
|
||||||
|
|
||||||
var updatedTheme = current.theme
|
var updatedTheme = current.theme
|
||||||
var updatedAutomaticThemeSwitchSetting = current.automaticThemeSwitchSetting
|
var updatedAutomaticThemeSwitchSetting = current.automaticThemeSwitchSetting
|
||||||
@ -289,7 +289,7 @@ final class ThemeAccentColorController: ViewController {
|
|||||||
if case let .result(resultTheme) = next {
|
if case let .result(resultTheme) = next {
|
||||||
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start()
|
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start()
|
||||||
return updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
|
return updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
|
||||||
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: wallpaper))
|
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: wallpaper, creatorAccountId: context.account.id))
|
||||||
|
|
||||||
var updatedTheme = current.theme
|
var updatedTheme = current.theme
|
||||||
var updatedAutomaticThemeSwitchSetting = current.automaticThemeSwitchSetting
|
var updatedAutomaticThemeSwitchSetting = current.automaticThemeSwitchSetting
|
||||||
|
|||||||
@ -550,7 +550,7 @@ public func themeAutoNightSettingsController(context: AccountContext) -> ViewCon
|
|||||||
|> mapToSignal { resolvedWallpaper -> Signal<Void, NoError> in
|
|> mapToSignal { resolvedWallpaper -> Signal<Void, NoError> in
|
||||||
var updatedTheme = theme
|
var updatedTheme = theme
|
||||||
if case let .cloud(info) = theme {
|
if case let .cloud(info) = theme {
|
||||||
updatedTheme = .cloud(PresentationCloudTheme(theme: info.theme, resolvedWallpaper: resolvedWallpaper))
|
updatedTheme = .cloud(PresentationCloudTheme(theme: info.theme, resolvedWallpaper: resolvedWallpaper, creatorAccountId: info.theme.isCreator ? context.account.id : nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSettings { settings in
|
updateSettings { settings in
|
||||||
@ -572,7 +572,7 @@ public func themeAutoNightSettingsController(context: AccountContext) -> ViewCon
|
|||||||
let settings = (sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings] as? PresentationThemeSettings) ?? PresentationThemeSettings.defaultSettings
|
let settings = (sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings] as? PresentationThemeSettings) ?? PresentationThemeSettings.defaultSettings
|
||||||
|
|
||||||
let defaultThemes: [PresentationThemeReference] = [.builtin(.night), .builtin(.nightAccent)]
|
let defaultThemes: [PresentationThemeReference] = [.builtin(.night), .builtin(.nightAccent)]
|
||||||
let cloudThemes: [PresentationThemeReference] = cloudThemes.map { .cloud(PresentationCloudTheme(theme: $0, resolvedWallpaper: nil)) }
|
let cloudThemes: [PresentationThemeReference] = cloudThemes.map { .cloud(PresentationCloudTheme(theme: $0, resolvedWallpaper: nil, creatorAccountId: $0.isCreator ? context.account.id : nil)) }
|
||||||
|
|
||||||
var availableThemes = defaultThemes
|
var availableThemes = defaultThemes
|
||||||
availableThemes.append(contentsOf: cloudThemes)
|
availableThemes.append(contentsOf: cloudThemes)
|
||||||
|
|||||||
@ -234,9 +234,9 @@ public final class ThemePreviewController: ViewController {
|
|||||||
|> mapToSignal { theme, wallpaper -> Signal<PresentationThemeReference?, NoError> in
|
|> mapToSignal { theme, wallpaper -> Signal<PresentationThemeReference?, NoError> in
|
||||||
if let theme = theme {
|
if let theme = theme {
|
||||||
if case let .file(file) = wallpaper, file.id != 0 {
|
if case let .file(file) = wallpaper, file.id != 0 {
|
||||||
return .single(.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: wallpaper)))
|
return .single(.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: wallpaper, creatorAccountId: theme.isCreator ? context.account.id : nil)))
|
||||||
} else {
|
} else {
|
||||||
return .single(.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil)))
|
return .single(.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: theme.isCreator ? context.account.id : nil)))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return .complete()
|
return .complete()
|
||||||
@ -313,7 +313,7 @@ public final class ThemePreviewController: ViewController {
|
|||||||
}
|
}
|
||||||
if case let .result(theme) = result, let file = theme.file {
|
if case let .result(theme) = result, let file = theme.file {
|
||||||
context.sharedContext.accountManager.mediaBox.moveResourceData(from: info.resource.id, to: file.resource.id)
|
context.sharedContext.accountManager.mediaBox.moveResourceData(from: info.resource.id, to: file.resource.id)
|
||||||
return .single((.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: resolvedWallpaper)), true))
|
return .single((.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: resolvedWallpaper, creatorAccountId: theme.isCreator ? context.account.id : nil)), true))
|
||||||
} else {
|
} else {
|
||||||
return .complete()
|
return .complete()
|
||||||
}
|
}
|
||||||
@ -332,7 +332,7 @@ public final class ThemePreviewController: ViewController {
|
|||||||
}
|
}
|
||||||
if case let .result(updatedTheme) = result, let file = updatedTheme.file {
|
if case let .result(updatedTheme) = result, let file = updatedTheme.file {
|
||||||
context.sharedContext.accountManager.mediaBox.moveResourceData(from: info.resource.id, to: file.resource.id)
|
context.sharedContext.accountManager.mediaBox.moveResourceData(from: info.resource.id, to: file.resource.id)
|
||||||
return .single((.cloud(PresentationCloudTheme(theme: updatedTheme, resolvedWallpaper: resolvedWallpaper)), true))
|
return .single((.cloud(PresentationCloudTheme(theme: updatedTheme, resolvedWallpaper: resolvedWallpaper, creatorAccountId: updatedTheme.isCreator ? context.account.id : nil)), true))
|
||||||
} else {
|
} else {
|
||||||
return .complete()
|
return .complete()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -729,7 +729,8 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
|||||||
let previousThemeIndex = themes.prefix(upTo: currentThemeIndex).reversed().firstIndex(where: { $0.file != nil })
|
let previousThemeIndex = themes.prefix(upTo: currentThemeIndex).reversed().firstIndex(where: { $0.file != nil })
|
||||||
let newTheme: PresentationThemeReference
|
let newTheme: PresentationThemeReference
|
||||||
if let previousThemeIndex = previousThemeIndex {
|
if let previousThemeIndex = previousThemeIndex {
|
||||||
newTheme = .cloud(PresentationCloudTheme(theme: themes[themes.index(before: previousThemeIndex.base)], resolvedWallpaper: nil))
|
let theme = themes[themes.index(before: previousThemeIndex.base)]
|
||||||
|
newTheme = .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: theme.isCreator ? context.account.id : nil))
|
||||||
} else {
|
} else {
|
||||||
newTheme = .builtin(.nightAccent)
|
newTheme = .builtin(.nightAccent)
|
||||||
}
|
}
|
||||||
@ -953,7 +954,8 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
|||||||
let previousThemeIndex = themes.prefix(upTo: currentThemeIndex).reversed().firstIndex(where: { $0.file != nil })
|
let previousThemeIndex = themes.prefix(upTo: currentThemeIndex).reversed().firstIndex(where: { $0.file != nil })
|
||||||
let newTheme: PresentationThemeReference
|
let newTheme: PresentationThemeReference
|
||||||
if let previousThemeIndex = previousThemeIndex {
|
if let previousThemeIndex = previousThemeIndex {
|
||||||
selectThemeImpl?(.cloud(PresentationCloudTheme(theme: themes[themes.index(before: previousThemeIndex.base)], resolvedWallpaper: nil)))
|
let theme = themes[themes.index(before: previousThemeIndex.base)]
|
||||||
|
selectThemeImpl?(.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: theme.isCreator ? context.account.id : nil)))
|
||||||
} else {
|
} else {
|
||||||
if settings.baseTheme == .night {
|
if settings.baseTheme == .night {
|
||||||
selectAccentColorImpl?(PresentationThemeAccentColor(baseColor: .blue))
|
selectAccentColorImpl?(PresentationThemeAccentColor(baseColor: .blue))
|
||||||
@ -1014,7 +1016,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
|||||||
}
|
}
|
||||||
defaultThemes.append(contentsOf: [.builtin(.night), .builtin(.nightAccent)])
|
defaultThemes.append(contentsOf: [.builtin(.night), .builtin(.nightAccent)])
|
||||||
|
|
||||||
let cloudThemes: [PresentationThemeReference] = cloudThemes.map { .cloud(PresentationCloudTheme(theme: $0, resolvedWallpaper: nil)) }.filter { !removedThemeIndexes.contains($0.index) }
|
let cloudThemes: [PresentationThemeReference] = cloudThemes.map { .cloud(PresentationCloudTheme(theme: $0, resolvedWallpaper: nil, creatorAccountId: $0.isCreator ? context.account.id : nil)) }.filter { !removedThemeIndexes.contains($0.index) }
|
||||||
|
|
||||||
var availableThemes = defaultThemes
|
var availableThemes = defaultThemes
|
||||||
if defaultThemes.first(where: { $0.index == themeReference.index }) == nil && cloudThemes.first(where: { $0.index == themeReference.index }) == nil {
|
if defaultThemes.first(where: { $0.index == themeReference.index }) == nil && cloudThemes.first(where: { $0.index == themeReference.index }) == nil {
|
||||||
@ -1158,7 +1160,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
|||||||
var baseThemeIndex: Int64?
|
var baseThemeIndex: Int64?
|
||||||
var updatedThemeBaseIndex: Int64?
|
var updatedThemeBaseIndex: Int64?
|
||||||
if case let .cloud(info) = theme {
|
if case let .cloud(info) = theme {
|
||||||
updatedTheme = .cloud(PresentationCloudTheme(theme: info.theme, resolvedWallpaper: resolvedWallpaper))
|
updatedTheme = .cloud(PresentationCloudTheme(theme: info.theme, resolvedWallpaper: resolvedWallpaper, creatorAccountId: info.theme.isCreator ? context.account.id : nil))
|
||||||
if let settings = info.theme.settings {
|
if let settings = info.theme.settings {
|
||||||
baseThemeIndex = PresentationThemeReference.builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)).index
|
baseThemeIndex = PresentationThemeReference.builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)).index
|
||||||
updatedThemeBaseIndex = baseThemeIndex
|
updatedThemeBaseIndex = baseThemeIndex
|
||||||
|
|||||||
@ -24,6 +24,7 @@ static_library(
|
|||||||
"//submodules/ActivityIndicator:ActivityIndicator",
|
"//submodules/ActivityIndicator:ActivityIndicator",
|
||||||
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
||||||
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
||||||
|
"//submodules/ArchivedStickerPacksNotice:ArchivedStickerPacksNotice",
|
||||||
],
|
],
|
||||||
frameworks = [
|
frameworks = [
|
||||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||||
|
|||||||
@ -231,3 +231,20 @@ public func recentlyUsedInlineBots(postbox: Postbox) -> Signal<[(Peer, Double)],
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func removeRecentlyUsedInlineBot(account: Account, peerId: PeerId) -> Signal<Void, NoError> {
|
||||||
|
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||||
|
transaction.removeOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentInlineBots, itemId: RecentPeerItemId(peerId).rawValue)
|
||||||
|
|
||||||
|
if let peer = transaction.getPeer(peerId), let apiPeer = apiInputPeer(peer) {
|
||||||
|
return account.network.request(Api.functions.contacts.resetTopPeerRating(category: .topPeerCategoryBotsInline, peer: apiPeer))
|
||||||
|
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||||
|
return .single(.boolFalse)
|
||||||
|
}
|
||||||
|
|> mapToSignal { _ -> Signal<Void, NoError> in
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
} |> switchToLatest
|
||||||
|
}
|
||||||
|
|||||||
@ -816,12 +816,12 @@ final class SharedApplicationContext {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
var exists = false
|
var exists = false
|
||||||
strongSelf.mainWindow.forEachViewController { controller in
|
strongSelf.mainWindow.forEachViewController({ controller in
|
||||||
if controller is ThemeSettingsCrossfadeController || controller is ThemeSettingsController {
|
if controller is ThemeSettingsCrossfadeController || controller is ThemeSettingsController {
|
||||||
exists = true
|
exists = true
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
})
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
strongSelf.mainWindow.present(ThemeSettingsCrossfadeController(), on: .root)
|
strongSelf.mainWindow.present(ThemeSettingsCrossfadeController(), on: .root)
|
||||||
@ -1425,12 +1425,12 @@ final class SharedApplicationContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.mainWindow.forEachViewController { controller in
|
self.mainWindow.forEachViewController({ controller in
|
||||||
if let controller = controller as? UndoOverlayController {
|
if let controller = controller as? UndoOverlayController {
|
||||||
controller.dismissWithCommitAction()
|
controller.dismissWithCommitAction()
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func applicationDidEnterBackground(_ application: UIApplication) {
|
func applicationDidEnterBackground(_ application: UIApplication) {
|
||||||
|
|||||||
@ -333,7 +333,7 @@ final class AuthorizedApplicationContext {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
}, excludeNavigationSubControllers: true)
|
||||||
|
|
||||||
if foundOverlay {
|
if foundOverlay {
|
||||||
return true
|
return true
|
||||||
@ -362,7 +362,7 @@ final class AuthorizedApplicationContext {
|
|||||||
return false
|
return false
|
||||||
}, expandAction: { expandData in
|
}, expandAction: { expandData in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
let chatController = ChatControllerImpl(context: strongSelf.context, chatLocation: .peer(firstMessage.id.peerId), mode: .overlay)
|
let chatController = ChatControllerImpl(context: strongSelf.context, chatLocation: .peer(firstMessage.id.peerId), mode: .overlay(strongSelf.rootController))
|
||||||
//chatController.navigation_setNavigationController(strongSelf.rootController)
|
//chatController.navigation_setNavigationController(strongSelf.rootController)
|
||||||
chatController.presentationArguments = ChatControllerOverlayPresentationData(expandData: expandData())
|
chatController.presentationArguments = ChatControllerOverlayPresentationData(expandData: expandData())
|
||||||
//strongSelf.rootController.pushViewController(chatController)
|
//strongSelf.rootController.pushViewController(chatController)
|
||||||
|
|||||||
@ -8274,6 +8274,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
return navigationController
|
return navigationController
|
||||||
} else if case let .inline(navigationController) = self.presentationInterfaceState.mode {
|
} else if case let .inline(navigationController) = self.presentationInterfaceState.mode {
|
||||||
return navigationController
|
return navigationController
|
||||||
|
} else if case let .overlay(navigationController) = self.presentationInterfaceState.mode {
|
||||||
|
return navigationController
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -496,7 +496,14 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
self.navigationBar?.isHidden = true
|
self.navigationBar?.isHidden = true
|
||||||
}
|
}
|
||||||
if self.overlayNavigationBar == nil {
|
if self.overlayNavigationBar == nil {
|
||||||
let overlayNavigationBar = ChatOverlayNavigationBar(theme: self.chatPresentationInterfaceState.theme, strings: self.chatPresentationInterfaceState.strings, nameDisplayOrder: self.chatPresentationInterfaceState.nameDisplayOrder, close: { [weak self] in
|
let overlayNavigationBar = ChatOverlayNavigationBar(theme: self.chatPresentationInterfaceState.theme, strings: self.chatPresentationInterfaceState.strings, nameDisplayOrder: self.chatPresentationInterfaceState.nameDisplayOrder, tapped: { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.dismissAsOverlay()
|
||||||
|
if case let .peer(id) = strongSelf.chatPresentationInterfaceState.chatLocation {
|
||||||
|
strongSelf.interfaceInteraction?.navigateToChat(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, close: { [weak self] in
|
||||||
self?.dismissAsOverlay()
|
self?.dismissAsOverlay()
|
||||||
})
|
})
|
||||||
overlayNavigationBar.peerView = self.peerView
|
overlayNavigationBar.peerView = self.peerView
|
||||||
|
|||||||
@ -14,6 +14,7 @@ final class ChatOverlayNavigationBar: ASDisplayNode {
|
|||||||
private let theme: PresentationTheme
|
private let theme: PresentationTheme
|
||||||
private let strings: PresentationStrings
|
private let strings: PresentationStrings
|
||||||
private let nameDisplayOrder: PresentationPersonNameOrder
|
private let nameDisplayOrder: PresentationPersonNameOrder
|
||||||
|
private let tapped: () -> Void
|
||||||
private let close: () -> Void
|
private let close: () -> Void
|
||||||
|
|
||||||
private let separatorNode: ASDisplayNode
|
private let separatorNode: ASDisplayNode
|
||||||
@ -40,10 +41,11 @@ final class ChatOverlayNavigationBar: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, close: @escaping () -> Void) {
|
init(theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, tapped: @escaping () -> Void, close: @escaping () -> Void) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.nameDisplayOrder = nameDisplayOrder
|
self.nameDisplayOrder = nameDisplayOrder
|
||||||
|
self.tapped = tapped
|
||||||
self.close = close
|
self.close = close
|
||||||
|
|
||||||
self.separatorNode = ASDisplayNode()
|
self.separatorNode = ASDisplayNode()
|
||||||
@ -83,6 +85,13 @@ final class ChatOverlayNavigationBar: ASDisplayNode {
|
|||||||
self.closeButton.addTarget(self, action: #selector(self.closePressed), forControlEvents: [.touchUpInside])
|
self.closeButton.addTarget(self, action: #selector(self.closePressed), forControlEvents: [.touchUpInside])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func didLoad() {
|
||||||
|
super.didLoad()
|
||||||
|
|
||||||
|
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleTap))
|
||||||
|
self.view.addGestureRecognizer(gestureRecognizer)
|
||||||
|
}
|
||||||
|
|
||||||
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: size.height - UIScreenPixel), size: CGSize(width: size.width, height: UIScreenPixel)))
|
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: size.height - UIScreenPixel), size: CGSize(width: size.width, height: UIScreenPixel)))
|
||||||
|
|
||||||
@ -93,11 +102,15 @@ final class ChatOverlayNavigationBar: ASDisplayNode {
|
|||||||
let _ = titleApply()
|
let _ = titleApply()
|
||||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: sideInset, y: floor((size.height - titleLayout.size.height) / 2.0)), size: titleLayout.size))
|
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: sideInset, y: floor((size.height - titleLayout.size.height) / 2.0)), size: titleLayout.size))
|
||||||
|
|
||||||
let closeButtonSize = self.closeButton.measure(CGSize(width: 100.0, height: 100.0))
|
let closeButtonSize = CGSize(width: size.height, height: size.height)
|
||||||
transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: size.width - sideInset - closeButtonSize.width - 6.0, y: floor((size.height - closeButtonSize.height) / 2.0)), size: closeButtonSize))
|
transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: size.width - sideInset - closeButtonSize.width + 10.0, y: 0.0), size: closeButtonSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func closePressed() {
|
@objc private func handleTap() {
|
||||||
|
self.tapped()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func closePressed() {
|
||||||
self.close()
|
self.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import TelegramUIPreferences
|
|||||||
import MergeLists
|
import MergeLists
|
||||||
import AccountContext
|
import AccountContext
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import ItemListUI
|
||||||
|
|
||||||
private struct HashtagChatInputContextPanelEntryStableId: Hashable {
|
private struct HashtagChatInputContextPanelEntryStableId: Hashable {
|
||||||
let text: String
|
let text: String
|
||||||
@ -19,25 +20,26 @@ private struct HashtagChatInputContextPanelEntry: Comparable, Identifiable {
|
|||||||
let index: Int
|
let index: Int
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
let text: String
|
let text: String
|
||||||
|
let revealed: Bool
|
||||||
|
|
||||||
var stableId: HashtagChatInputContextPanelEntryStableId {
|
var stableId: HashtagChatInputContextPanelEntryStableId {
|
||||||
return HashtagChatInputContextPanelEntryStableId(text: self.text)
|
return HashtagChatInputContextPanelEntryStableId(text: self.text)
|
||||||
}
|
}
|
||||||
|
|
||||||
func withUpdatedTheme(_ theme: PresentationTheme) -> HashtagChatInputContextPanelEntry {
|
func withUpdatedTheme(_ theme: PresentationTheme) -> HashtagChatInputContextPanelEntry {
|
||||||
return HashtagChatInputContextPanelEntry(index: self.index, theme: theme, text: self.text)
|
return HashtagChatInputContextPanelEntry(index: self.index, theme: theme, text: self.text, revealed: self.revealed)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: HashtagChatInputContextPanelEntry, rhs: HashtagChatInputContextPanelEntry) -> Bool {
|
static func ==(lhs: HashtagChatInputContextPanelEntry, rhs: HashtagChatInputContextPanelEntry) -> Bool {
|
||||||
return lhs.index == rhs.index && lhs.text == rhs.text && lhs.theme === rhs.theme
|
return lhs.index == rhs.index && lhs.text == rhs.text && lhs.theme === rhs.theme && lhs.revealed == rhs.revealed
|
||||||
}
|
}
|
||||||
|
|
||||||
static func <(lhs: HashtagChatInputContextPanelEntry, rhs: HashtagChatInputContextPanelEntry) -> Bool {
|
static func <(lhs: HashtagChatInputContextPanelEntry, rhs: HashtagChatInputContextPanelEntry) -> Bool {
|
||||||
return lhs.index < rhs.index
|
return lhs.index < rhs.index
|
||||||
}
|
}
|
||||||
|
|
||||||
func item(account: Account, fontSize: PresentationFontSize, hashtagSelected: @escaping (String) -> Void) -> ListViewItem {
|
func item(account: Account, presentationData: PresentationData, setHashtagRevealed: @escaping (String?) -> Void, hashtagSelected: @escaping (String) -> Void, removeRequested: @escaping (String) -> Void) -> ListViewItem {
|
||||||
return HashtagChatInputPanelItem(theme: self.theme, fontSize: fontSize, text: self.text, hashtagSelected: hashtagSelected)
|
return HashtagChatInputPanelItem(presentationData: ItemListPresentationData(presentationData), text: self.text, revealed: self.revealed, setHashtagRevealed: setHashtagRevealed, hashtagSelected: hashtagSelected, removeRequested: removeRequested)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,12 +49,12 @@ private struct HashtagChatInputContextPanelTransition {
|
|||||||
let updates: [ListViewUpdateItem]
|
let updates: [ListViewUpdateItem]
|
||||||
}
|
}
|
||||||
|
|
||||||
private func preparedTransition(from fromEntries: [HashtagChatInputContextPanelEntry], to toEntries: [HashtagChatInputContextPanelEntry], account: Account, fontSize: PresentationFontSize, hashtagSelected: @escaping (String) -> Void) -> HashtagChatInputContextPanelTransition {
|
private func preparedTransition(from fromEntries: [HashtagChatInputContextPanelEntry], to toEntries: [HashtagChatInputContextPanelEntry], account: Account, presentationData: PresentationData, setHashtagRevealed: @escaping (String?) -> Void, hashtagSelected: @escaping (String) -> Void, removeRequested: @escaping (String) -> Void) -> HashtagChatInputContextPanelTransition {
|
||||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||||
|
|
||||||
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
||||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, fontSize: fontSize, hashtagSelected: hashtagSelected), directionHint: nil) }
|
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, presentationData: presentationData, setHashtagRevealed: setHashtagRevealed, hashtagSelected: hashtagSelected, removeRequested: removeRequested), directionHint: nil) }
|
||||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, fontSize: fontSize, hashtagSelected: hashtagSelected), directionHint: nil) }
|
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, presentationData: presentationData, setHashtagRevealed: setHashtagRevealed, hashtagSelected: hashtagSelected, removeRequested: removeRequested), directionHint: nil) }
|
||||||
|
|
||||||
return HashtagChatInputContextPanelTransition(deletions: deletions, insertions: insertions, updates: updates)
|
return HashtagChatInputContextPanelTransition(deletions: deletions, insertions: insertions, updates: updates)
|
||||||
}
|
}
|
||||||
@ -61,6 +63,9 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
|
|||||||
private let listView: ListView
|
private let listView: ListView
|
||||||
private var currentEntries: [HashtagChatInputContextPanelEntry]?
|
private var currentEntries: [HashtagChatInputContextPanelEntry]?
|
||||||
|
|
||||||
|
private var currentResults: [String] = []
|
||||||
|
private var revealedHashtag: String?
|
||||||
|
|
||||||
private var enqueuedTransitions: [(HashtagChatInputContextPanelTransition, Bool)] = []
|
private var enqueuedTransitions: [(HashtagChatInputContextPanelTransition, Bool)] = []
|
||||||
private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat)?
|
private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat)?
|
||||||
|
|
||||||
@ -81,11 +86,13 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateResults(_ results: [String]) {
|
func updateResults(_ results: [String]) {
|
||||||
|
self.currentResults = results
|
||||||
|
|
||||||
var entries: [HashtagChatInputContextPanelEntry] = []
|
var entries: [HashtagChatInputContextPanelEntry] = []
|
||||||
var index = 0
|
var index = 0
|
||||||
var stableIds = Set<HashtagChatInputContextPanelEntryStableId>()
|
var stableIds = Set<HashtagChatInputContextPanelEntryStableId>()
|
||||||
for text in results {
|
for text in results {
|
||||||
let entry = HashtagChatInputContextPanelEntry(index: index, theme: self.theme, text: text)
|
let entry = HashtagChatInputContextPanelEntry(index: index, theme: self.theme, text: text, revealed: text == self.revealedHashtag)
|
||||||
if stableIds.contains(entry.stableId) {
|
if stableIds.contains(entry.stableId) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -98,7 +105,12 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
|
|||||||
|
|
||||||
private func prepareTransition(from: [HashtagChatInputContextPanelEntry]? , to: [HashtagChatInputContextPanelEntry]) {
|
private func prepareTransition(from: [HashtagChatInputContextPanelEntry]? , to: [HashtagChatInputContextPanelEntry]) {
|
||||||
let firstTime = from == nil
|
let firstTime = from == nil
|
||||||
let transition = preparedTransition(from: from ?? [], to: to, account: self.context.account, fontSize: self.fontSize, hashtagSelected: { [weak self] text in
|
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
let transition = preparedTransition(from: from ?? [], to: to, account: self.context.account, presentationData: presentationData, setHashtagRevealed: { [weak self] text in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.revealedHashtag = text
|
||||||
|
}
|
||||||
|
}, hashtagSelected: { [weak self] text in
|
||||||
if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction {
|
if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction {
|
||||||
interfaceInteraction.updateTextInputStateAndMode { textInputState, inputMode in
|
interfaceInteraction.updateTextInputStateAndMode { textInputState, inputMode in
|
||||||
var hashtagQueryRange: NSRange?
|
var hashtagQueryRange: NSRange?
|
||||||
@ -123,6 +135,10 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
|
|||||||
return (textInputState, inputMode)
|
return (textInputState, inputMode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}, removeRequested: { [weak self] text in
|
||||||
|
if let strongSelf = self {
|
||||||
|
let _ = removeRecentlyUsedHashtag(postbox: strongSelf.context.account.postbox, string: text).start()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
self.currentEntries = to
|
self.currentEntries = to
|
||||||
self.enqueueTransition(transition, firstTime: firstTime)
|
self.enqueueTransition(transition, firstTime: firstTime)
|
||||||
|
|||||||
@ -8,20 +8,25 @@ import SwiftSignalKit
|
|||||||
import Postbox
|
import Postbox
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
|
import ItemListUI
|
||||||
|
|
||||||
final class HashtagChatInputPanelItem: ListViewItem {
|
final class HashtagChatInputPanelItem: ListViewItem {
|
||||||
fileprivate let theme: PresentationTheme
|
fileprivate let presentationData: ItemListPresentationData
|
||||||
fileprivate let fontSize: PresentationFontSize
|
|
||||||
fileprivate let text: String
|
fileprivate let text: String
|
||||||
|
fileprivate let revealed: Bool
|
||||||
|
fileprivate let setHashtagRevealed: (String?) -> Void
|
||||||
private let hashtagSelected: (String) -> Void
|
private let hashtagSelected: (String) -> Void
|
||||||
|
fileprivate let removeRequested: (String) -> Void
|
||||||
|
|
||||||
let selectable: Bool = true
|
let selectable: Bool = true
|
||||||
|
|
||||||
public init(theme: PresentationTheme, fontSize: PresentationFontSize, text: String, hashtagSelected: @escaping (String) -> Void) {
|
public init(presentationData: ItemListPresentationData, text: String, revealed: Bool, setHashtagRevealed: @escaping (String?) -> Void, hashtagSelected: @escaping (String) -> Void, removeRequested: @escaping (String) -> Void) {
|
||||||
self.theme = theme
|
self.presentationData = presentationData
|
||||||
self.fontSize = fontSize
|
|
||||||
self.text = text
|
self.text = text
|
||||||
|
self.revealed = revealed
|
||||||
|
self.setHashtagRevealed = setHashtagRevealed
|
||||||
self.hashtagSelected = hashtagSelected
|
self.hashtagSelected = hashtagSelected
|
||||||
|
self.removeRequested = removeRequested
|
||||||
}
|
}
|
||||||
|
|
||||||
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||||
@ -72,8 +77,12 @@ final class HashtagChatInputPanelItem: ListViewItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func selected(listView: ListView) {
|
func selected(listView: ListView) {
|
||||||
|
if self.revealed {
|
||||||
|
self.setHashtagRevealed(nil)
|
||||||
|
} else {
|
||||||
self.hashtagSelected(self.text)
|
self.hashtagSelected(self.text)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class HashtagChatInputPanelItemNode: ListViewItemNode {
|
final class HashtagChatInputPanelItemNode: ListViewItemNode {
|
||||||
@ -83,6 +92,17 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode {
|
|||||||
private let separatorNode: ASDisplayNode
|
private let separatorNode: ASDisplayNode
|
||||||
private let highlightedBackgroundNode: ASDisplayNode
|
private let highlightedBackgroundNode: ASDisplayNode
|
||||||
|
|
||||||
|
private var revealNode: ItemListRevealOptionsNode?
|
||||||
|
private var revealOptions: [ItemListRevealOption] = []
|
||||||
|
private var initialRevealOffset: CGFloat = 0.0
|
||||||
|
public private(set) var revealOffset: CGFloat = 0.0
|
||||||
|
private var recognizer: ItemListRevealOptionsGestureRecognizer?
|
||||||
|
private var hapticFeedback: HapticFeedback?
|
||||||
|
|
||||||
|
private var item: HashtagChatInputPanelItem?
|
||||||
|
|
||||||
|
private var validLayout: (CGSize, CGFloat, CGFloat)?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.textNode = TextNode()
|
self.textNode = TextNode()
|
||||||
|
|
||||||
@ -102,6 +122,15 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode {
|
|||||||
self.addSubnode(self.textNode)
|
self.addSubnode(self.textNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func didLoad() {
|
||||||
|
super.didLoad()
|
||||||
|
|
||||||
|
let recognizer = ItemListRevealOptionsGestureRecognizer(target: self, action: #selector(self.revealGesture(_:)))
|
||||||
|
self.recognizer = recognizer
|
||||||
|
recognizer.allowAnyDirection = false
|
||||||
|
self.view.addGestureRecognizer(recognizer)
|
||||||
|
}
|
||||||
|
|
||||||
override public func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
|
override public func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
|
||||||
if let item = item as? HashtagChatInputPanelItem {
|
if let item = item as? HashtagChatInputPanelItem {
|
||||||
let doLayout = self.asyncLayout()
|
let doLayout = self.asyncLayout()
|
||||||
@ -116,26 +145,31 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode {
|
|||||||
func asyncLayout() -> (_ item: HashtagChatInputPanelItem, _ params: ListViewItemLayoutParams, _ mergedTop: Bool, _ mergedBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) {
|
func asyncLayout() -> (_ item: HashtagChatInputPanelItem, _ params: ListViewItemLayoutParams, _ mergedTop: Bool, _ mergedBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) {
|
||||||
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
||||||
return { [weak self] item, params, mergedTop, mergedBottom in
|
return { [weak self] item, params, mergedTop, mergedBottom in
|
||||||
let textFont = Font.medium(floor(item.fontSize.baseDisplaySize * 14.0 / 17.0))
|
let textFont = Font.medium(floor(item.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0))
|
||||||
|
|
||||||
let baseWidth = params.width - params.leftInset - params.rightInset
|
let baseWidth = params.width - params.leftInset - params.rightInset
|
||||||
|
|
||||||
let leftInset: CGFloat = 15.0 + params.leftInset
|
let leftInset: CGFloat = 15.0 + params.leftInset
|
||||||
let rightInset: CGFloat = 10.0 + params.rightInset
|
let rightInset: CGFloat = 10.0 + params.rightInset
|
||||||
|
|
||||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "#\(item.text)", font: textFont, textColor: item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: baseWidth, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "#\(item.text)", font: textFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: baseWidth, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: HashtagChatInputPanelItemNode.itemHeight), insets: UIEdgeInsets())
|
let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: HashtagChatInputPanelItemNode.itemHeight), insets: UIEdgeInsets())
|
||||||
|
|
||||||
return (nodeLayout, { _ in
|
return (nodeLayout, { animation in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.separatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
|
strongSelf.item = item
|
||||||
strongSelf.topSeparatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
|
strongSelf.validLayout = (nodeLayout.contentSize, params.leftInset, params.rightInset)
|
||||||
strongSelf.backgroundColor = item.theme.list.plainBackgroundColor
|
|
||||||
strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor
|
let revealOffset = strongSelf.revealOffset
|
||||||
|
|
||||||
|
strongSelf.separatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor
|
||||||
|
strongSelf.topSeparatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor
|
||||||
|
strongSelf.backgroundColor = item.presentationData.theme.list.plainBackgroundColor
|
||||||
|
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
|
||||||
|
|
||||||
let _ = textApply()
|
let _ = textApply()
|
||||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floor((nodeLayout.contentSize.height - textLayout.size.height) / 2.0)), size: textLayout.size)
|
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: revealOffset + leftInset, y: floor((nodeLayout.contentSize.height - textLayout.size.height) / 2.0)), size: textLayout.size)
|
||||||
|
|
||||||
strongSelf.topSeparatorNode.isHidden = mergedTop
|
strongSelf.topSeparatorNode.isHidden = mergedTop
|
||||||
strongSelf.separatorNode.isHidden = !mergedBottom
|
strongSelf.separatorNode.isHidden = !mergedBottom
|
||||||
@ -144,14 +178,27 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode {
|
|||||||
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - UIScreenPixel), size: CGSize(width: params.width - leftInset, height: UIScreenPixel))
|
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - UIScreenPixel), size: CGSize(width: params.width - leftInset, height: UIScreenPixel))
|
||||||
|
|
||||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: nodeLayout.size.height + UIScreenPixel))
|
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: nodeLayout.size.height + UIScreenPixel))
|
||||||
|
|
||||||
|
strongSelf.setRevealOptions([ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)])
|
||||||
|
strongSelf.setRevealOptionsOpened(item.revealed, animated: animation.isAnimated)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
|
if let (size, leftInset, rightInset) = self.validLayout {
|
||||||
|
transition.updateFrameAdditive(node: self.textNode, frame: CGRect(origin: CGPoint(x: min(offset, 0.0) + 15.0 + leftInset, y: self.textNode.frame.minY), size: self.textNode.frame.size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) {
|
override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) {
|
||||||
super.setHighlighted(highlighted, at: point, animated: animated)
|
super.setHighlighted(highlighted, at: point, animated: animated)
|
||||||
|
|
||||||
|
if let revealNode = self.revealNode, self.revealOffset != 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if highlighted {
|
if highlighted {
|
||||||
self.highlightedBackgroundNode.alpha = 1.0
|
self.highlightedBackgroundNode.alpha = 1.0
|
||||||
if self.highlightedBackgroundNode.supernode == nil {
|
if self.highlightedBackgroundNode.supernode == nil {
|
||||||
@ -174,4 +221,199 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setRevealOptions(_ options: [ItemListRevealOption]) {
|
||||||
|
if self.revealOptions == options {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let previousOptions = self.revealOptions
|
||||||
|
let wasEmpty = self.revealOptions.isEmpty
|
||||||
|
self.revealOptions = options
|
||||||
|
let isEmpty = options.isEmpty
|
||||||
|
if options.isEmpty {
|
||||||
|
if let _ = self.revealNode {
|
||||||
|
self.recognizer?.becomeCancelled()
|
||||||
|
self.updateRevealOffsetInternal(offset: 0.0, transition: .animated(duration: 0.3, curve: .spring))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if wasEmpty != isEmpty {
|
||||||
|
self.recognizer?.isEnabled = !isEmpty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setRevealOptionsOpened(_ value: Bool, animated: Bool) {
|
||||||
|
if value != !self.revealOffset.isZero {
|
||||||
|
if !self.revealOffset.isZero {
|
||||||
|
self.recognizer?.becomeCancelled()
|
||||||
|
}
|
||||||
|
let transition: ContainedViewLayoutTransition
|
||||||
|
if animated {
|
||||||
|
transition = .animated(duration: 0.3, curve: .spring)
|
||||||
|
} else {
|
||||||
|
transition = .immediate
|
||||||
|
}
|
||||||
|
if value {
|
||||||
|
if self.revealNode == nil {
|
||||||
|
self.setupAndAddRevealNode()
|
||||||
|
if let revealNode = self.revealNode, revealNode.isNodeLoaded, let _ = self.validLayout {
|
||||||
|
revealNode.layout()
|
||||||
|
let revealSize = revealNode.bounds.size
|
||||||
|
self.updateRevealOffsetInternal(offset: -revealSize.width, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if !self.revealOffset.isZero {
|
||||||
|
self.updateRevealOffsetInternal(offset: 0.0, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
|
if let recognizer = self.recognizer, otherGestureRecognizer == recognizer {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func revealGesture(_ recognizer: ItemListRevealOptionsGestureRecognizer) {
|
||||||
|
guard let (size, _, _) = self.validLayout else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch recognizer.state {
|
||||||
|
case .began:
|
||||||
|
if let revealNode = self.revealNode {
|
||||||
|
let revealSize = revealNode.bounds.size
|
||||||
|
let location = recognizer.location(in: self.view)
|
||||||
|
if location.x > size.width - revealSize.width {
|
||||||
|
recognizer.becomeCancelled()
|
||||||
|
} else {
|
||||||
|
self.initialRevealOffset = self.revealOffset
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if self.revealOptions.isEmpty {
|
||||||
|
recognizer.becomeCancelled()
|
||||||
|
}
|
||||||
|
self.initialRevealOffset = self.revealOffset
|
||||||
|
}
|
||||||
|
case .changed:
|
||||||
|
var translation = recognizer.translation(in: self.view)
|
||||||
|
translation.x += self.initialRevealOffset
|
||||||
|
if self.revealNode == nil && translation.x.isLess(than: 0.0) {
|
||||||
|
self.setupAndAddRevealNode()
|
||||||
|
self.revealOptionsInteractivelyOpened()
|
||||||
|
}
|
||||||
|
self.updateRevealOffsetInternal(offset: translation.x, transition: .immediate)
|
||||||
|
if self.revealNode == nil {
|
||||||
|
self.revealOptionsInteractivelyClosed()
|
||||||
|
}
|
||||||
|
case .ended, .cancelled:
|
||||||
|
guard let recognizer = self.recognizer else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if let revealNode = self.revealNode {
|
||||||
|
let velocity = recognizer.velocity(in: self.view)
|
||||||
|
let revealSize = revealNode.bounds.size
|
||||||
|
var reveal = false
|
||||||
|
if abs(velocity.x) < 100.0 {
|
||||||
|
if self.initialRevealOffset.isZero && self.revealOffset < 0.0 {
|
||||||
|
reveal = true
|
||||||
|
} else if self.revealOffset < -revealSize.width {
|
||||||
|
reveal = true
|
||||||
|
} else {
|
||||||
|
reveal = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if velocity.x < 0.0 {
|
||||||
|
reveal = true
|
||||||
|
} else {
|
||||||
|
reveal = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.updateRevealOffsetInternal(offset: reveal ? -revealSize.width : 0.0, transition: .animated(duration: 0.3, curve: .spring))
|
||||||
|
if !reveal {
|
||||||
|
self.revealOptionsInteractivelyClosed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) {
|
||||||
|
guard let item = self.item else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
item.removeRequested(item.text)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupAndAddRevealNode() {
|
||||||
|
if !self.revealOptions.isEmpty {
|
||||||
|
let revealNode = ItemListRevealOptionsNode(optionSelected: { [weak self] option in
|
||||||
|
self?.revealOptionSelected(option, animated: false)
|
||||||
|
}, tapticAction: { [weak self] in
|
||||||
|
self?.hapticImpact()
|
||||||
|
})
|
||||||
|
revealNode.setOptions(self.revealOptions, isLeft: false)
|
||||||
|
self.revealNode = revealNode
|
||||||
|
|
||||||
|
if let (size, _, rightInset) = self.validLayout {
|
||||||
|
var revealSize = revealNode.measure(CGSize(width: CGFloat.greatestFiniteMagnitude, height: size.height))
|
||||||
|
revealSize.width += rightInset
|
||||||
|
|
||||||
|
revealNode.frame = CGRect(origin: CGPoint(x: size.width + max(self.revealOffset, -revealSize.width), y: 0.0), size: revealSize)
|
||||||
|
revealNode.updateRevealOffset(offset: 0.0, sideInset: -rightInset, transition: .immediate)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.addSubnode(revealNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateRevealOffsetInternal(offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
|
self.revealOffset = offset
|
||||||
|
guard let (size, leftInset, rightInset) = self.validLayout else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let revealNode = self.revealNode {
|
||||||
|
let revealSize = revealNode.bounds.size
|
||||||
|
|
||||||
|
let revealFrame = CGRect(origin: CGPoint(x: size.width + max(self.revealOffset, -revealSize.width), y: 0.0), size: revealSize)
|
||||||
|
let revealNodeOffset = -max(self.revealOffset, -revealSize.width)
|
||||||
|
revealNode.updateRevealOffset(offset: revealNodeOffset, sideInset: -rightInset, transition: transition)
|
||||||
|
|
||||||
|
if CGFloat(0.0).isLessThanOrEqualTo(offset) {
|
||||||
|
self.revealNode = nil
|
||||||
|
transition.updateFrame(node: revealNode, frame: revealFrame, completion: { [weak revealNode] _ in
|
||||||
|
revealNode?.removeFromSupernode()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
transition.updateFrame(node: revealNode, frame: revealFrame)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.updateRevealOffset(offset: offset, transition: transition)
|
||||||
|
}
|
||||||
|
|
||||||
|
func revealOptionsInteractivelyOpened() {
|
||||||
|
if let item = self.item {
|
||||||
|
item.setHashtagRevealed(item.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func revealOptionsInteractivelyClosed() {
|
||||||
|
if let item = self.item {
|
||||||
|
item.setHashtagRevealed(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func hapticImpact() {
|
||||||
|
if self.hapticFeedback == nil {
|
||||||
|
self.hapticFeedback = HapticFeedback()
|
||||||
|
}
|
||||||
|
self.hapticFeedback?.impact(.medium)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,25 +11,27 @@ import MergeLists
|
|||||||
import TextFormat
|
import TextFormat
|
||||||
import AccountContext
|
import AccountContext
|
||||||
import LocalizedPeerData
|
import LocalizedPeerData
|
||||||
|
import ItemListUI
|
||||||
|
|
||||||
private struct MentionChatInputContextPanelEntry: Comparable, Identifiable {
|
private struct MentionChatInputContextPanelEntry: Comparable, Identifiable {
|
||||||
let index: Int
|
let index: Int
|
||||||
let peer: Peer
|
let peer: Peer
|
||||||
|
let revealed: Bool
|
||||||
|
|
||||||
var stableId: Int64 {
|
var stableId: Int64 {
|
||||||
return self.peer.id.toInt64()
|
return self.peer.id.toInt64()
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: MentionChatInputContextPanelEntry, rhs: MentionChatInputContextPanelEntry) -> Bool {
|
static func ==(lhs: MentionChatInputContextPanelEntry, rhs: MentionChatInputContextPanelEntry) -> Bool {
|
||||||
return lhs.index == rhs.index && lhs.peer.isEqual(rhs.peer)
|
return lhs.index == rhs.index && lhs.peer.isEqual(rhs.peer) && lhs.revealed == rhs.revealed
|
||||||
}
|
}
|
||||||
|
|
||||||
static func <(lhs: MentionChatInputContextPanelEntry, rhs: MentionChatInputContextPanelEntry) -> Bool {
|
static func <(lhs: MentionChatInputContextPanelEntry, rhs: MentionChatInputContextPanelEntry) -> Bool {
|
||||||
return lhs.index < rhs.index
|
return lhs.index < rhs.index
|
||||||
}
|
}
|
||||||
|
|
||||||
func item(context: AccountContext, theme: PresentationTheme, fontSize: PresentationFontSize, inverted: Bool, peerSelected: @escaping (Peer) -> Void) -> ListViewItem {
|
func item(context: AccountContext, presentationData: PresentationData, inverted: Bool, setPeerIdRevealed: @escaping (PeerId?) -> Void, peerSelected: @escaping (Peer) -> Void, removeRequested: @escaping (PeerId) -> Void) -> ListViewItem {
|
||||||
return MentionChatInputPanelItem(context: context, theme: theme, fontSize: fontSize, inverted: inverted, peer: self.peer, peerSelected: peerSelected)
|
return MentionChatInputPanelItem(context: context, presentationData: ItemListPresentationData(presentationData), inverted: inverted, peer: self.peer, revealed: self.revealed, setPeerIdRevealed: setPeerIdRevealed, peerSelected: peerSelected, removeRequested: removeRequested)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,12 +41,12 @@ private struct CommandChatInputContextPanelTransition {
|
|||||||
let updates: [ListViewUpdateItem]
|
let updates: [ListViewUpdateItem]
|
||||||
}
|
}
|
||||||
|
|
||||||
private func preparedTransition(from fromEntries: [MentionChatInputContextPanelEntry], to toEntries: [MentionChatInputContextPanelEntry], context: AccountContext, theme: PresentationTheme, fontSize: PresentationFontSize, inverted: Bool, forceUpdate: Bool, peerSelected: @escaping (Peer) -> Void) -> CommandChatInputContextPanelTransition {
|
private func preparedTransition(from fromEntries: [MentionChatInputContextPanelEntry], to toEntries: [MentionChatInputContextPanelEntry], context: AccountContext, presentationData: PresentationData, inverted: Bool, forceUpdate: Bool, setPeerIdRevealed: @escaping (PeerId?) -> Void, peerSelected: @escaping (Peer) -> Void, removeRequested: @escaping (PeerId) -> Void) -> CommandChatInputContextPanelTransition {
|
||||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries, allUpdated: forceUpdate)
|
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries, allUpdated: forceUpdate)
|
||||||
|
|
||||||
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
||||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, theme: theme, fontSize: fontSize, inverted: inverted, peerSelected: peerSelected), directionHint: nil) }
|
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, inverted: inverted, setPeerIdRevealed: setPeerIdRevealed, peerSelected: peerSelected, removeRequested: removeRequested), directionHint: nil) }
|
||||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, theme: theme, fontSize: fontSize, inverted: inverted, peerSelected: peerSelected), directionHint: nil) }
|
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, inverted: inverted, setPeerIdRevealed: setPeerIdRevealed, peerSelected: peerSelected, removeRequested: removeRequested), directionHint: nil) }
|
||||||
|
|
||||||
return CommandChatInputContextPanelTransition(deletions: deletions, insertions: insertions, updates: updates)
|
return CommandChatInputContextPanelTransition(deletions: deletions, insertions: insertions, updates: updates)
|
||||||
}
|
}
|
||||||
@ -60,6 +62,8 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode {
|
|||||||
private let listView: ListView
|
private let listView: ListView
|
||||||
private var currentEntries: [MentionChatInputContextPanelEntry]?
|
private var currentEntries: [MentionChatInputContextPanelEntry]?
|
||||||
|
|
||||||
|
private var revealedPeerId: PeerId?
|
||||||
|
|
||||||
private var enqueuedTransitions: [(CommandChatInputContextPanelTransition, Bool)] = []
|
private var enqueuedTransitions: [(CommandChatInputContextPanelTransition, Bool)] = []
|
||||||
private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat)?
|
private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat)?
|
||||||
|
|
||||||
@ -95,7 +99,7 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
peerIdSet.insert(peerId)
|
peerIdSet.insert(peerId)
|
||||||
entries.append(MentionChatInputContextPanelEntry(index: index, peer: peer))
|
entries.append(MentionChatInputContextPanelEntry(index: index, peer: peer, revealed: revealedPeerId == peer.id))
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
self.updateToEntries(entries: entries, forceUpdate: false)
|
self.updateToEntries(entries: entries, forceUpdate: false)
|
||||||
@ -103,7 +107,10 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode {
|
|||||||
|
|
||||||
private func updateToEntries(entries: [MentionChatInputContextPanelEntry], forceUpdate: Bool) {
|
private func updateToEntries(entries: [MentionChatInputContextPanelEntry], forceUpdate: Bool) {
|
||||||
let firstTime = self.currentEntries == nil
|
let firstTime = self.currentEntries == nil
|
||||||
let transition = preparedTransition(from: self.currentEntries ?? [], to: entries, context: self.context, theme: self.theme, fontSize: self.fontSize, inverted: self.mode == .search, forceUpdate: forceUpdate, peerSelected: { [weak self] peer in
|
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
let transition = preparedTransition(from: self.currentEntries ?? [], to: entries, context: self.context, presentationData: presentationData, inverted: self.mode == .search, forceUpdate: forceUpdate, setPeerIdRevealed: { [weak self] peerId in
|
||||||
|
|
||||||
|
}, peerSelected: { [weak self] peer in
|
||||||
if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction {
|
if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction {
|
||||||
switch strongSelf.mode {
|
switch strongSelf.mode {
|
||||||
case .input:
|
case .input:
|
||||||
@ -147,6 +154,10 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode {
|
|||||||
interfaceInteraction.beginMessageSearch(.member(peer), "")
|
interfaceInteraction.beginMessageSearch(.member(peer), "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}, removeRequested: { [weak self] peerId in
|
||||||
|
if let strongSelf = self {
|
||||||
|
let _ = removeRecentlyUsedInlineBot(account: strongSelf.context.account, peerId: peerId).start()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
self.currentEntries = entries
|
self.currentEntries = entries
|
||||||
self.enqueueTransition(transition, firstTime: firstTime)
|
self.enqueueTransition(transition, firstTime: firstTime)
|
||||||
|
|||||||
@ -10,24 +10,29 @@ import TelegramPresentationData
|
|||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import AvatarNode
|
import AvatarNode
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import ItemListUI
|
||||||
|
|
||||||
final class MentionChatInputPanelItem: ListViewItem {
|
final class MentionChatInputPanelItem: ListViewItem {
|
||||||
fileprivate let context: AccountContext
|
fileprivate let context: AccountContext
|
||||||
fileprivate let theme: PresentationTheme
|
fileprivate let presentationData: ItemListPresentationData
|
||||||
fileprivate let fontSize: PresentationFontSize
|
fileprivate let revealed: Bool
|
||||||
fileprivate let inverted: Bool
|
fileprivate let inverted: Bool
|
||||||
fileprivate let peer: Peer
|
fileprivate let peer: Peer
|
||||||
private let peerSelected: (Peer) -> Void
|
private let peerSelected: (Peer) -> Void
|
||||||
|
fileprivate let setPeerIdRevealed: (PeerId?) -> Void
|
||||||
|
fileprivate let removeRequested: (PeerId) -> Void
|
||||||
|
|
||||||
let selectable: Bool = true
|
let selectable: Bool = true
|
||||||
|
|
||||||
public init(context: AccountContext, theme: PresentationTheme, fontSize: PresentationFontSize, inverted: Bool, peer: Peer, peerSelected: @escaping (Peer) -> Void) {
|
public init(context: AccountContext, presentationData: ItemListPresentationData, inverted: Bool, peer: Peer, revealed: Bool, setPeerIdRevealed: @escaping (PeerId?) -> Void, peerSelected: @escaping (Peer) -> Void, removeRequested: @escaping (PeerId) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.theme = theme
|
self.presentationData = presentationData
|
||||||
self.fontSize = fontSize
|
|
||||||
self.inverted = inverted
|
self.inverted = inverted
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
|
self.revealed = revealed
|
||||||
|
self.setPeerIdRevealed = setPeerIdRevealed
|
||||||
self.peerSelected = peerSelected
|
self.peerSelected = peerSelected
|
||||||
|
self.removeRequested = removeRequested
|
||||||
}
|
}
|
||||||
|
|
||||||
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||||
@ -87,14 +92,23 @@ private let avatarFont = avatarPlaceholderFont(size: 16.0)
|
|||||||
final class MentionChatInputPanelItemNode: ListViewItemNode {
|
final class MentionChatInputPanelItemNode: ListViewItemNode {
|
||||||
static let itemHeight: CGFloat = 42.0
|
static let itemHeight: CGFloat = 42.0
|
||||||
|
|
||||||
private var item: MentionChatInputPanelItem?
|
|
||||||
|
|
||||||
private let avatarNode: AvatarNode
|
private let avatarNode: AvatarNode
|
||||||
private let textNode: TextNode
|
private let textNode: TextNode
|
||||||
private let topSeparatorNode: ASDisplayNode
|
private let topSeparatorNode: ASDisplayNode
|
||||||
private let separatorNode: ASDisplayNode
|
private let separatorNode: ASDisplayNode
|
||||||
private let highlightedBackgroundNode: ASDisplayNode
|
private let highlightedBackgroundNode: ASDisplayNode
|
||||||
|
|
||||||
|
private var revealNode: ItemListRevealOptionsNode?
|
||||||
|
private var revealOptions: [ItemListRevealOption] = []
|
||||||
|
private var initialRevealOffset: CGFloat = 0.0
|
||||||
|
public private(set) var revealOffset: CGFloat = 0.0
|
||||||
|
private var recognizer: ItemListRevealOptionsGestureRecognizer?
|
||||||
|
private var hapticFeedback: HapticFeedback?
|
||||||
|
|
||||||
|
private var item: MentionChatInputPanelItem?
|
||||||
|
|
||||||
|
private var validLayout: (CGSize, CGFloat, CGFloat)?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.avatarNode = AvatarNode(font: avatarFont)
|
self.avatarNode = AvatarNode(font: avatarFont)
|
||||||
self.textNode = TextNode()
|
self.textNode = TextNode()
|
||||||
@ -117,6 +131,15 @@ final class MentionChatInputPanelItemNode: ListViewItemNode {
|
|||||||
self.addSubnode(self.textNode)
|
self.addSubnode(self.textNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func didLoad() {
|
||||||
|
super.didLoad()
|
||||||
|
|
||||||
|
let recognizer = ItemListRevealOptionsGestureRecognizer(target: self, action: #selector(self.revealGesture(_:)))
|
||||||
|
self.recognizer = recognizer
|
||||||
|
recognizer.allowAnyDirection = false
|
||||||
|
self.view.addGestureRecognizer(recognizer)
|
||||||
|
}
|
||||||
|
|
||||||
override public func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
|
override public func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
|
||||||
if let item = item as? MentionChatInputPanelItem {
|
if let item = item as? MentionChatInputPanelItem {
|
||||||
let doLayout = self.asyncLayout()
|
let doLayout = self.asyncLayout()
|
||||||
@ -134,8 +157,8 @@ final class MentionChatInputPanelItemNode: ListViewItemNode {
|
|||||||
let previousItem = self.item
|
let previousItem = self.item
|
||||||
|
|
||||||
return { [weak self] item, params, mergedTop, mergedBottom in
|
return { [weak self] item, params, mergedTop, mergedBottom in
|
||||||
let primaryFont = Font.medium(floor(item.fontSize.baseDisplaySize * 14.0 / 17.0))
|
let primaryFont = Font.medium(floor(item.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0))
|
||||||
let secondaryFont = Font.regular(floor(item.fontSize.baseDisplaySize * 14.0 / 17.0))
|
let secondaryFont = Font.regular(floor(item.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0))
|
||||||
|
|
||||||
let leftInset: CGFloat = 55.0 + params.leftInset
|
let leftInset: CGFloat = 55.0 + params.leftInset
|
||||||
let rightInset: CGFloat = 10.0 + params.rightInset
|
let rightInset: CGFloat = 10.0 + params.rightInset
|
||||||
@ -146,19 +169,23 @@ final class MentionChatInputPanelItemNode: ListViewItemNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let string = NSMutableAttributedString()
|
let string = NSMutableAttributedString()
|
||||||
string.append(NSAttributedString(string: item.peer.debugDisplayTitle, font: primaryFont, textColor: item.theme.list.itemPrimaryTextColor))
|
string.append(NSAttributedString(string: item.peer.debugDisplayTitle, font: primaryFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor))
|
||||||
if let addressName = item.peer.addressName, !addressName.isEmpty {
|
if let addressName = item.peer.addressName, !addressName.isEmpty {
|
||||||
string.append(NSAttributedString(string: " @\(addressName)", font: secondaryFont, textColor: item.theme.list.itemSecondaryTextColor))
|
string.append(NSAttributedString(string: " @\(addressName)", font: secondaryFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor))
|
||||||
}
|
}
|
||||||
|
|
||||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: HashtagChatInputPanelItemNode.itemHeight), insets: UIEdgeInsets())
|
let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: HashtagChatInputPanelItemNode.itemHeight), insets: UIEdgeInsets())
|
||||||
|
|
||||||
return (nodeLayout, { _ in
|
return (nodeLayout, { animation in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.item = item
|
strongSelf.item = item
|
||||||
|
|
||||||
|
strongSelf.validLayout = (nodeLayout.contentSize, params.leftInset, params.rightInset)
|
||||||
|
|
||||||
|
let revealOffset = strongSelf.revealOffset
|
||||||
|
|
||||||
if let updatedInverted = updatedInverted {
|
if let updatedInverted = updatedInverted {
|
||||||
if updatedInverted {
|
if updatedInverted {
|
||||||
strongSelf.transform = CATransform3DMakeRotation(CGFloat(Double.pi), 0.0, 0.0, 1.0)
|
strongSelf.transform = CATransform3DMakeRotation(CGFloat(Double.pi), 0.0, 0.0, 1.0)
|
||||||
@ -167,12 +194,12 @@ final class MentionChatInputPanelItemNode: ListViewItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.separatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
|
strongSelf.separatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor
|
||||||
strongSelf.topSeparatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
|
strongSelf.topSeparatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor
|
||||||
strongSelf.backgroundColor = item.theme.list.plainBackgroundColor
|
strongSelf.backgroundColor = item.presentationData.theme.list.plainBackgroundColor
|
||||||
strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor
|
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
|
||||||
|
|
||||||
strongSelf.avatarNode.setPeer(context: item.context, theme: item.theme, peer: item.peer, emptyColor: item.theme.list.mediaPlaceholderColor)
|
strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: item.peer, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor)
|
||||||
|
|
||||||
let _ = textApply()
|
let _ = textApply()
|
||||||
|
|
||||||
@ -186,14 +213,33 @@ final class MentionChatInputPanelItemNode: ListViewItemNode {
|
|||||||
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: !item.inverted ? (nodeLayout.contentSize.height - UIScreenPixel) : 0.0), size: CGSize(width: params.width - leftInset, height: UIScreenPixel))
|
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: !item.inverted ? (nodeLayout.contentSize.height - UIScreenPixel) : 0.0), size: CGSize(width: params.width - leftInset, height: UIScreenPixel))
|
||||||
|
|
||||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: nodeLayout.size.height + UIScreenPixel))
|
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: nodeLayout.size.height + UIScreenPixel))
|
||||||
|
|
||||||
|
if let peer = item.peer as? TelegramUser, let _ = peer.botInfo {
|
||||||
|
strongSelf.setRevealOptions([ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)])
|
||||||
|
strongSelf.setRevealOptionsOpened(item.revealed, animated: animation.isAnimated)
|
||||||
|
} else {
|
||||||
|
strongSelf.setRevealOptions([])
|
||||||
|
strongSelf.setRevealOptionsOpened(false, animated: animation.isAnimated)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
|
if let (size, leftInset, rightInset) = self.validLayout {
|
||||||
|
transition.updateFrameAdditive(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: min(offset, 0.0) + 12.0 + leftInset, y: self.avatarNode.frame.minY), size: self.avatarNode.frame.size))
|
||||||
|
transition.updateFrameAdditive(node: self.textNode, frame: CGRect(origin: CGPoint(x: min(offset, 0.0) + 55.0 + leftInset, y: self.textNode.frame.minY), size: self.textNode.frame.size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) {
|
override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) {
|
||||||
super.setHighlighted(highlighted, at: point, animated: animated)
|
super.setHighlighted(highlighted, at: point, animated: animated)
|
||||||
|
|
||||||
|
if let revealNode = self.revealNode, self.revealOffset != 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if highlighted {
|
if highlighted {
|
||||||
self.highlightedBackgroundNode.alpha = 1.0
|
self.highlightedBackgroundNode.alpha = 1.0
|
||||||
if self.highlightedBackgroundNode.supernode == nil {
|
if self.highlightedBackgroundNode.supernode == nil {
|
||||||
@ -216,4 +262,199 @@ final class MentionChatInputPanelItemNode: ListViewItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setRevealOptions(_ options: [ItemListRevealOption]) {
|
||||||
|
if self.revealOptions == options {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let previousOptions = self.revealOptions
|
||||||
|
let wasEmpty = self.revealOptions.isEmpty
|
||||||
|
self.revealOptions = options
|
||||||
|
let isEmpty = options.isEmpty
|
||||||
|
if options.isEmpty {
|
||||||
|
if let _ = self.revealNode {
|
||||||
|
self.recognizer?.becomeCancelled()
|
||||||
|
self.updateRevealOffsetInternal(offset: 0.0, transition: .animated(duration: 0.3, curve: .spring))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if wasEmpty != isEmpty {
|
||||||
|
self.recognizer?.isEnabled = !isEmpty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setRevealOptionsOpened(_ value: Bool, animated: Bool) {
|
||||||
|
if value != !self.revealOffset.isZero {
|
||||||
|
if !self.revealOffset.isZero {
|
||||||
|
self.recognizer?.becomeCancelled()
|
||||||
|
}
|
||||||
|
let transition: ContainedViewLayoutTransition
|
||||||
|
if animated {
|
||||||
|
transition = .animated(duration: 0.3, curve: .spring)
|
||||||
|
} else {
|
||||||
|
transition = .immediate
|
||||||
|
}
|
||||||
|
if value {
|
||||||
|
if self.revealNode == nil {
|
||||||
|
self.setupAndAddRevealNode()
|
||||||
|
if let revealNode = self.revealNode, revealNode.isNodeLoaded, let _ = self.validLayout {
|
||||||
|
revealNode.layout()
|
||||||
|
let revealSize = revealNode.bounds.size
|
||||||
|
self.updateRevealOffsetInternal(offset: -revealSize.width, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if !self.revealOffset.isZero {
|
||||||
|
self.updateRevealOffsetInternal(offset: 0.0, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
|
if let recognizer = self.recognizer, otherGestureRecognizer == recognizer {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func revealGesture(_ recognizer: ItemListRevealOptionsGestureRecognizer) {
|
||||||
|
guard let (size, _, _) = self.validLayout else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch recognizer.state {
|
||||||
|
case .began:
|
||||||
|
if let revealNode = self.revealNode {
|
||||||
|
let revealSize = revealNode.bounds.size
|
||||||
|
let location = recognizer.location(in: self.view)
|
||||||
|
if location.x > size.width - revealSize.width {
|
||||||
|
recognizer.becomeCancelled()
|
||||||
|
} else {
|
||||||
|
self.initialRevealOffset = self.revealOffset
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if self.revealOptions.isEmpty {
|
||||||
|
recognizer.becomeCancelled()
|
||||||
|
}
|
||||||
|
self.initialRevealOffset = self.revealOffset
|
||||||
|
}
|
||||||
|
case .changed:
|
||||||
|
var translation = recognizer.translation(in: self.view)
|
||||||
|
translation.x += self.initialRevealOffset
|
||||||
|
if self.revealNode == nil && translation.x.isLess(than: 0.0) {
|
||||||
|
self.setupAndAddRevealNode()
|
||||||
|
self.revealOptionsInteractivelyOpened()
|
||||||
|
}
|
||||||
|
self.updateRevealOffsetInternal(offset: translation.x, transition: .immediate)
|
||||||
|
if self.revealNode == nil {
|
||||||
|
self.revealOptionsInteractivelyClosed()
|
||||||
|
}
|
||||||
|
case .ended, .cancelled:
|
||||||
|
guard let recognizer = self.recognizer else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if let revealNode = self.revealNode {
|
||||||
|
let velocity = recognizer.velocity(in: self.view)
|
||||||
|
let revealSize = revealNode.bounds.size
|
||||||
|
var reveal = false
|
||||||
|
if abs(velocity.x) < 100.0 {
|
||||||
|
if self.initialRevealOffset.isZero && self.revealOffset < 0.0 {
|
||||||
|
reveal = true
|
||||||
|
} else if self.revealOffset < -revealSize.width {
|
||||||
|
reveal = true
|
||||||
|
} else {
|
||||||
|
reveal = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if velocity.x < 0.0 {
|
||||||
|
reveal = true
|
||||||
|
} else {
|
||||||
|
reveal = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.updateRevealOffsetInternal(offset: reveal ? -revealSize.width : 0.0, transition: .animated(duration: 0.3, curve: .spring))
|
||||||
|
if !reveal {
|
||||||
|
self.revealOptionsInteractivelyClosed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) {
|
||||||
|
guard let item = self.item else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
item.removeRequested(item.peer.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupAndAddRevealNode() {
|
||||||
|
if !self.revealOptions.isEmpty {
|
||||||
|
let revealNode = ItemListRevealOptionsNode(optionSelected: { [weak self] option in
|
||||||
|
self?.revealOptionSelected(option, animated: false)
|
||||||
|
}, tapticAction: { [weak self] in
|
||||||
|
self?.hapticImpact()
|
||||||
|
})
|
||||||
|
revealNode.setOptions(self.revealOptions, isLeft: false)
|
||||||
|
self.revealNode = revealNode
|
||||||
|
|
||||||
|
if let (size, _, rightInset) = self.validLayout {
|
||||||
|
var revealSize = revealNode.measure(CGSize(width: CGFloat.greatestFiniteMagnitude, height: size.height))
|
||||||
|
revealSize.width += rightInset
|
||||||
|
|
||||||
|
revealNode.frame = CGRect(origin: CGPoint(x: size.width + max(self.revealOffset, -revealSize.width), y: 0.0), size: revealSize)
|
||||||
|
revealNode.updateRevealOffset(offset: 0.0, sideInset: -rightInset, transition: .immediate)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.addSubnode(revealNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateRevealOffsetInternal(offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
|
self.revealOffset = offset
|
||||||
|
guard let (size, leftInset, rightInset) = self.validLayout else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let revealNode = self.revealNode {
|
||||||
|
let revealSize = revealNode.bounds.size
|
||||||
|
|
||||||
|
let revealFrame = CGRect(origin: CGPoint(x: size.width + max(self.revealOffset, -revealSize.width), y: 0.0), size: revealSize)
|
||||||
|
let revealNodeOffset = -max(self.revealOffset, -revealSize.width)
|
||||||
|
revealNode.updateRevealOffset(offset: revealNodeOffset, sideInset: -rightInset, transition: transition)
|
||||||
|
|
||||||
|
if CGFloat(0.0).isLessThanOrEqualTo(offset) {
|
||||||
|
self.revealNode = nil
|
||||||
|
transition.updateFrame(node: revealNode, frame: revealFrame, completion: { [weak revealNode] _ in
|
||||||
|
revealNode?.removeFromSupernode()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
transition.updateFrame(node: revealNode, frame: revealFrame)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.updateRevealOffset(offset: offset, transition: transition)
|
||||||
|
}
|
||||||
|
|
||||||
|
func revealOptionsInteractivelyOpened() {
|
||||||
|
if let item = self.item {
|
||||||
|
item.setPeerIdRevealed(item.peer.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func revealOptionsInteractivelyClosed() {
|
||||||
|
if let item = self.item {
|
||||||
|
item.setPeerIdRevealed(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func hapticImpact() {
|
||||||
|
if self.hapticFeedback == nil {
|
||||||
|
self.hapticFeedback = HapticFeedback()
|
||||||
|
}
|
||||||
|
self.hapticFeedback?.impact(.medium)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -111,10 +111,10 @@ final class ThemeUpdateManagerImpl: ThemeUpdateManager {
|
|||||||
return .complete()
|
return .complete()
|
||||||
}
|
}
|
||||||
accountManager.mediaBox.storeResourceData(file.file.resource.id, data: fullSizeData, synchronous: true)
|
accountManager.mediaBox.storeResourceData(file.file.resource.id, data: fullSizeData, synchronous: true)
|
||||||
return .single((.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: wallpaper)), presentationTheme))
|
return .single((.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: wallpaper, creatorAccountId: theme.isCreator ? account.id : nil)), presentationTheme))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return .single((.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil)), presentationTheme))
|
return .single((.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: theme.isCreator ? account.id : nil)), presentationTheme))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -97,15 +97,18 @@ public struct PresentationLocalTheme: PostboxCoding, Equatable {
|
|||||||
public struct PresentationCloudTheme: PostboxCoding, Equatable {
|
public struct PresentationCloudTheme: PostboxCoding, Equatable {
|
||||||
public let theme: TelegramTheme
|
public let theme: TelegramTheme
|
||||||
public let resolvedWallpaper: TelegramWallpaper?
|
public let resolvedWallpaper: TelegramWallpaper?
|
||||||
|
public let creatorAccountId: AccountRecordId?
|
||||||
|
|
||||||
public init(theme: TelegramTheme, resolvedWallpaper: TelegramWallpaper?) {
|
public init(theme: TelegramTheme, resolvedWallpaper: TelegramWallpaper?, creatorAccountId: AccountRecordId?) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.resolvedWallpaper = resolvedWallpaper
|
self.resolvedWallpaper = resolvedWallpaper
|
||||||
|
self.creatorAccountId = creatorAccountId
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(decoder: PostboxDecoder) {
|
public init(decoder: PostboxDecoder) {
|
||||||
self.theme = decoder.decodeObjectForKey("theme", decoder: { TelegramTheme(decoder: $0) }) as! TelegramTheme
|
self.theme = decoder.decodeObjectForKey("theme", decoder: { TelegramTheme(decoder: $0) }) as! TelegramTheme
|
||||||
self.resolvedWallpaper = decoder.decodeObjectForKey("wallpaper", decoder: { TelegramWallpaper(decoder: $0) }) as? TelegramWallpaper
|
self.resolvedWallpaper = decoder.decodeObjectForKey("wallpaper", decoder: { TelegramWallpaper(decoder: $0) }) as? TelegramWallpaper
|
||||||
|
self.creatorAccountId = decoder.decodeOptionalInt64ForKey("account").flatMap { AccountRecordId(rawValue: $0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(_ encoder: PostboxEncoder) {
|
public func encode(_ encoder: PostboxEncoder) {
|
||||||
@ -115,6 +118,11 @@ public struct PresentationCloudTheme: PostboxCoding, Equatable {
|
|||||||
} else {
|
} else {
|
||||||
encoder.encodeNil(forKey: "wallpaper")
|
encoder.encodeNil(forKey: "wallpaper")
|
||||||
}
|
}
|
||||||
|
if let accountId = self.creatorAccountId {
|
||||||
|
encoder.encodeInt64(accountId.int64, forKey: "account")
|
||||||
|
} else {
|
||||||
|
encoder.encodeNil(forKey: "account")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: PresentationCloudTheme, rhs: PresentationCloudTheme) -> Bool {
|
public static func ==(lhs: PresentationCloudTheme, rhs: PresentationCloudTheme) -> Bool {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user