mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various UI improvements
This commit is contained in:
parent
78ccc864b9
commit
bae930ec20
@ -246,7 +246,7 @@ public enum ChatControllerSubject: Equatable {
|
||||
|
||||
public enum ChatControllerPresentationMode: Equatable {
|
||||
case standard(previewing: Bool)
|
||||
case overlay
|
||||
case overlay(NavigationController?)
|
||||
case inline(NavigationController?)
|
||||
}
|
||||
|
||||
|
@ -44,19 +44,19 @@ private func getCoveringViewSnaphot(window: Window1) -> UIImage? {
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.scaleBy(x: scale, y: scale)
|
||||
UIGraphicsPushContext(context)
|
||||
window.forEachViewController { controller in
|
||||
window.forEachViewController({ controller in
|
||||
if let controller = controller as? PasscodeEntryController {
|
||||
controller.displayNode.alpha = 0.0
|
||||
}
|
||||
return true
|
||||
}
|
||||
})
|
||||
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 {
|
||||
controller.displayNode.alpha = 1.0
|
||||
}
|
||||
return true
|
||||
}
|
||||
})
|
||||
UIGraphicsPopContext()
|
||||
}).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)
|
||||
}
|
||||
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
|
||||
let tooltipController = TooltipController(content: .text(strongSelf.presentationData.strings.Proxy_TooltipUnavailable), baseFontSize: strongSelf.presentationData.listsFontSize.baseDisplaySize, timeout: 60.0, dismissByTapOutside: true)
|
||||
strongSelf.proxyUnavailableTooltipController = tooltipController
|
||||
|
@ -1222,10 +1222,12 @@ public class Window1 {
|
||||
return hidden
|
||||
}
|
||||
|
||||
public func forEachViewController(_ f: (ContainableController) -> Bool) {
|
||||
public func forEachViewController(_ f: (ContainableController) -> Bool, excludeNavigationSubControllers: Bool = false) {
|
||||
if let navigationController = self._rootController as? NavigationController {
|
||||
for case let controller as ContainableController in navigationController.viewControllers {
|
||||
!f(controller)
|
||||
if !excludeNavigationSubControllers {
|
||||
for case let controller as ContainableController in navigationController.viewControllers {
|
||||
!f(controller)
|
||||
}
|
||||
}
|
||||
if let controller = navigationController.topOverlayController {
|
||||
!f(controller)
|
||||
|
@ -48,6 +48,13 @@ private final class LegacyComponentsAccessCheckerImpl: NSObject, LegacyComponent
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -548,7 +548,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
|
||||
if foundVenues == nil && !state.searchingVenuesAround {
|
||||
displayingPlacesButton = true
|
||||
} else if let previousLocation = foundVenuesLocation {
|
||||
let currentLocation = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
|
||||
let currentLocation = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
|
||||
if currentLocation.distance(from: previousLocation) > 300 {
|
||||
displayingPlacesButton = true
|
||||
}
|
||||
|
@ -551,7 +551,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
|
||||
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
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
themeSpecificChatWallpapers[themeReference.index] = nil
|
||||
|
@ -260,7 +260,7 @@ final class ThemeAccentColorController: ViewController {
|
||||
if case let .result(resultTheme) = next {
|
||||
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start()
|
||||
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 updatedAutomaticThemeSwitchSetting = current.automaticThemeSwitchSetting
|
||||
@ -289,7 +289,7 @@ final class ThemeAccentColorController: ViewController {
|
||||
if case let .result(resultTheme) = next {
|
||||
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start()
|
||||
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 updatedAutomaticThemeSwitchSetting = current.automaticThemeSwitchSetting
|
||||
|
@ -550,7 +550,7 @@ public func themeAutoNightSettingsController(context: AccountContext) -> ViewCon
|
||||
|> mapToSignal { resolvedWallpaper -> Signal<Void, NoError> in
|
||||
var updatedTheme = 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
|
||||
@ -572,7 +572,7 @@ public func themeAutoNightSettingsController(context: AccountContext) -> ViewCon
|
||||
let settings = (sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings] as? PresentationThemeSettings) ?? PresentationThemeSettings.defaultSettings
|
||||
|
||||
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
|
||||
availableThemes.append(contentsOf: cloudThemes)
|
||||
|
@ -234,9 +234,9 @@ public final class ThemePreviewController: ViewController {
|
||||
|> mapToSignal { theme, wallpaper -> Signal<PresentationThemeReference?, NoError> in
|
||||
if let theme = theme {
|
||||
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 {
|
||||
return .single(.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil)))
|
||||
return .single(.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: theme.isCreator ? context.account.id : nil)))
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
@ -313,7 +313,7 @@ public final class ThemePreviewController: ViewController {
|
||||
}
|
||||
if case let .result(theme) = result, let file = theme.file {
|
||||
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 {
|
||||
return .complete()
|
||||
}
|
||||
@ -332,7 +332,7 @@ public final class ThemePreviewController: ViewController {
|
||||
}
|
||||
if case let .result(updatedTheme) = result, let file = updatedTheme.file {
|
||||
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 {
|
||||
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 newTheme: PresentationThemeReference
|
||||
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 {
|
||||
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 newTheme: PresentationThemeReference
|
||||
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 {
|
||||
if settings.baseTheme == .night {
|
||||
selectAccentColorImpl?(PresentationThemeAccentColor(baseColor: .blue))
|
||||
@ -1014,7 +1016,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
}
|
||||
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
|
||||
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 updatedThemeBaseIndex: Int64?
|
||||
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 {
|
||||
baseThemeIndex = PresentationThemeReference.builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)).index
|
||||
updatedThemeBaseIndex = baseThemeIndex
|
||||
|
@ -24,6 +24,7 @@ static_library(
|
||||
"//submodules/ActivityIndicator:ActivityIndicator",
|
||||
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
||||
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
||||
"//submodules/ArchivedStickerPacksNotice:ArchivedStickerPacksNotice",
|
||||
],
|
||||
frameworks = [
|
||||
"$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
|
||||
}
|
||||
var exists = false
|
||||
strongSelf.mainWindow.forEachViewController { controller in
|
||||
strongSelf.mainWindow.forEachViewController({ controller in
|
||||
if controller is ThemeSettingsCrossfadeController || controller is ThemeSettingsController {
|
||||
exists = true
|
||||
}
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
if !exists {
|
||||
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 {
|
||||
controller.dismissWithCommitAction()
|
||||
}
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func applicationDidEnterBackground(_ application: UIApplication) {
|
||||
|
@ -333,7 +333,7 @@ final class AuthorizedApplicationContext {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}, excludeNavigationSubControllers: true)
|
||||
|
||||
if foundOverlay {
|
||||
return true
|
||||
@ -362,7 +362,7 @@ final class AuthorizedApplicationContext {
|
||||
return false
|
||||
}, expandAction: { expandData in
|
||||
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.presentationArguments = ChatControllerOverlayPresentationData(expandData: expandData())
|
||||
//strongSelf.rootController.pushViewController(chatController)
|
||||
|
@ -8254,6 +8254,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return navigationController
|
||||
} else if case let .inline(navigationController) = self.presentationInterfaceState.mode {
|
||||
return navigationController
|
||||
} else if case let .overlay(navigationController) = self.presentationInterfaceState.mode {
|
||||
return navigationController
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
@ -496,7 +496,14 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.navigationBar?.isHidden = true
|
||||
}
|
||||
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()
|
||||
})
|
||||
overlayNavigationBar.peerView = self.peerView
|
||||
|
@ -14,6 +14,7 @@ final class ChatOverlayNavigationBar: ASDisplayNode {
|
||||
private let theme: PresentationTheme
|
||||
private let strings: PresentationStrings
|
||||
private let nameDisplayOrder: PresentationPersonNameOrder
|
||||
private let tapped: () -> Void
|
||||
private let close: () -> Void
|
||||
|
||||
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.strings = strings
|
||||
self.nameDisplayOrder = nameDisplayOrder
|
||||
self.tapped = tapped
|
||||
self.close = close
|
||||
|
||||
self.separatorNode = ASDisplayNode()
|
||||
@ -83,6 +85,13 @@ final class ChatOverlayNavigationBar: ASDisplayNode {
|
||||
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) {
|
||||
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()
|
||||
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))
|
||||
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))
|
||||
let closeButtonSize = CGSize(width: size.height, height: size.height)
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import TelegramUIPreferences
|
||||
import MergeLists
|
||||
import AccountContext
|
||||
import AccountContext
|
||||
import ItemListUI
|
||||
|
||||
private struct HashtagChatInputContextPanelEntryStableId: Hashable {
|
||||
let text: String
|
||||
@ -19,25 +20,26 @@ private struct HashtagChatInputContextPanelEntry: Comparable, Identifiable {
|
||||
let index: Int
|
||||
let theme: PresentationTheme
|
||||
let text: String
|
||||
let revealed: Bool
|
||||
|
||||
var stableId: HashtagChatInputContextPanelEntryStableId {
|
||||
return HashtagChatInputContextPanelEntryStableId(text: self.text)
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
return lhs.index < rhs.index
|
||||
}
|
||||
|
||||
func item(account: Account, fontSize: PresentationFontSize, hashtagSelected: @escaping (String) -> Void) -> ListViewItem {
|
||||
return HashtagChatInputPanelItem(theme: self.theme, fontSize: fontSize, text: self.text, hashtagSelected: hashtagSelected)
|
||||
func item(account: Account, presentationData: PresentationData, setHashtagRevealed: @escaping (String?) -> Void, hashtagSelected: @escaping (String) -> Void, removeRequested: @escaping (String) -> Void) -> ListViewItem {
|
||||
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]
|
||||
}
|
||||
|
||||
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 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 updates = updateIndices.map { ListViewUpdateItem(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, presentationData: presentationData, setHashtagRevealed: setHashtagRevealed, hashtagSelected: hashtagSelected, removeRequested: removeRequested), directionHint: nil) }
|
||||
|
||||
return HashtagChatInputContextPanelTransition(deletions: deletions, insertions: insertions, updates: updates)
|
||||
}
|
||||
@ -61,6 +63,9 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
|
||||
private let listView: ListView
|
||||
private var currentEntries: [HashtagChatInputContextPanelEntry]?
|
||||
|
||||
private var currentResults: [String] = []
|
||||
private var revealedHashtag: String?
|
||||
|
||||
private var enqueuedTransitions: [(HashtagChatInputContextPanelTransition, Bool)] = []
|
||||
private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat)?
|
||||
|
||||
@ -81,11 +86,13 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
|
||||
}
|
||||
|
||||
func updateResults(_ results: [String]) {
|
||||
self.currentResults = results
|
||||
|
||||
var entries: [HashtagChatInputContextPanelEntry] = []
|
||||
var index = 0
|
||||
var stableIds = Set<HashtagChatInputContextPanelEntryStableId>()
|
||||
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) {
|
||||
continue
|
||||
}
|
||||
@ -98,7 +105,12 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
|
||||
|
||||
private func prepareTransition(from: [HashtagChatInputContextPanelEntry]? , to: [HashtagChatInputContextPanelEntry]) {
|
||||
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 {
|
||||
interfaceInteraction.updateTextInputStateAndMode { textInputState, inputMode in
|
||||
var hashtagQueryRange: NSRange?
|
||||
@ -123,6 +135,10 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
|
||||
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.enqueueTransition(transition, firstTime: firstTime)
|
||||
|
@ -8,20 +8,25 @@ import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import ItemListUI
|
||||
|
||||
final class HashtagChatInputPanelItem: ListViewItem {
|
||||
fileprivate let theme: PresentationTheme
|
||||
fileprivate let fontSize: PresentationFontSize
|
||||
fileprivate let presentationData: ItemListPresentationData
|
||||
fileprivate let text: String
|
||||
fileprivate let revealed: Bool
|
||||
fileprivate let setHashtagRevealed: (String?) -> Void
|
||||
private let hashtagSelected: (String) -> Void
|
||||
fileprivate let removeRequested: (String) -> Void
|
||||
|
||||
let selectable: Bool = true
|
||||
|
||||
public init(theme: PresentationTheme, fontSize: PresentationFontSize, text: String, hashtagSelected: @escaping (String) -> Void) {
|
||||
self.theme = theme
|
||||
self.fontSize = fontSize
|
||||
public init(presentationData: ItemListPresentationData, text: String, revealed: Bool, setHashtagRevealed: @escaping (String?) -> Void, hashtagSelected: @escaping (String) -> Void, removeRequested: @escaping (String) -> Void) {
|
||||
self.presentationData = presentationData
|
||||
self.text = text
|
||||
self.revealed = revealed
|
||||
self.setHashtagRevealed = setHashtagRevealed
|
||||
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) {
|
||||
@ -72,7 +77,11 @@ final class HashtagChatInputPanelItem: ListViewItem {
|
||||
}
|
||||
|
||||
func selected(listView: ListView) {
|
||||
self.hashtagSelected(self.text)
|
||||
if self.revealed {
|
||||
self.setHashtagRevealed(nil)
|
||||
} else {
|
||||
self.hashtagSelected(self.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,6 +91,17 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode {
|
||||
private let topSeparatorNode: ASDisplayNode
|
||||
private let separatorNode: 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() {
|
||||
self.textNode = TextNode()
|
||||
@ -102,6 +122,15 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode {
|
||||
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?) {
|
||||
if let item = item as? HashtagChatInputPanelItem {
|
||||
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) {
|
||||
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
||||
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 leftInset: CGFloat = 15.0 + params.leftInset
|
||||
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())
|
||||
|
||||
return (nodeLayout, { _ in
|
||||
return (nodeLayout, { animation in
|
||||
if let strongSelf = self {
|
||||
strongSelf.separatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
|
||||
strongSelf.topSeparatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
|
||||
strongSelf.backgroundColor = item.theme.list.plainBackgroundColor
|
||||
strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor
|
||||
strongSelf.item = item
|
||||
strongSelf.validLayout = (nodeLayout.contentSize, params.leftInset, params.rightInset)
|
||||
|
||||
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()
|
||||
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.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.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) {
|
||||
super.setHighlighted(highlighted, at: point, animated: animated)
|
||||
|
||||
if let revealNode = self.revealNode, self.revealOffset != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if highlighted {
|
||||
self.highlightedBackgroundNode.alpha = 1.0
|
||||
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 AccountContext
|
||||
import LocalizedPeerData
|
||||
import ItemListUI
|
||||
|
||||
private struct MentionChatInputContextPanelEntry: Comparable, Identifiable {
|
||||
let index: Int
|
||||
let peer: Peer
|
||||
let revealed: Bool
|
||||
|
||||
var stableId: Int64 {
|
||||
return self.peer.id.toInt64()
|
||||
}
|
||||
|
||||
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 {
|
||||
return lhs.index < rhs.index
|
||||
}
|
||||
|
||||
func item(context: AccountContext, theme: PresentationTheme, fontSize: PresentationFontSize, inverted: Bool, peerSelected: @escaping (Peer) -> Void) -> ListViewItem {
|
||||
return MentionChatInputPanelItem(context: context, theme: theme, fontSize: fontSize, inverted: inverted, peer: self.peer, peerSelected: peerSelected)
|
||||
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, 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]
|
||||
}
|
||||
|
||||
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 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 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 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, presentationData: presentationData, inverted: inverted, setPeerIdRevealed: setPeerIdRevealed, peerSelected: peerSelected, removeRequested: removeRequested), directionHint: nil) }
|
||||
|
||||
return CommandChatInputContextPanelTransition(deletions: deletions, insertions: insertions, updates: updates)
|
||||
}
|
||||
@ -60,6 +62,8 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode {
|
||||
private let listView: ListView
|
||||
private var currentEntries: [MentionChatInputContextPanelEntry]?
|
||||
|
||||
private var revealedPeerId: PeerId?
|
||||
|
||||
private var enqueuedTransitions: [(CommandChatInputContextPanelTransition, Bool)] = []
|
||||
private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat)?
|
||||
|
||||
@ -95,7 +99,7 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode {
|
||||
continue
|
||||
}
|
||||
peerIdSet.insert(peerId)
|
||||
entries.append(MentionChatInputContextPanelEntry(index: index, peer: peer))
|
||||
entries.append(MentionChatInputContextPanelEntry(index: index, peer: peer, revealed: revealedPeerId == peer.id))
|
||||
index += 1
|
||||
}
|
||||
self.updateToEntries(entries: entries, forceUpdate: false)
|
||||
@ -103,7 +107,10 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode {
|
||||
|
||||
private func updateToEntries(entries: [MentionChatInputContextPanelEntry], forceUpdate: Bool) {
|
||||
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 {
|
||||
switch strongSelf.mode {
|
||||
case .input:
|
||||
@ -147,6 +154,10 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode {
|
||||
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.enqueueTransition(transition, firstTime: firstTime)
|
||||
|
@ -10,24 +10,29 @@ import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import AvatarNode
|
||||
import AccountContext
|
||||
import ItemListUI
|
||||
|
||||
final class MentionChatInputPanelItem: ListViewItem {
|
||||
fileprivate let context: AccountContext
|
||||
fileprivate let theme: PresentationTheme
|
||||
fileprivate let fontSize: PresentationFontSize
|
||||
fileprivate let presentationData: ItemListPresentationData
|
||||
fileprivate let revealed: Bool
|
||||
fileprivate let inverted: Bool
|
||||
fileprivate let peer: Peer
|
||||
private let peerSelected: (Peer) -> Void
|
||||
fileprivate let setPeerIdRevealed: (PeerId?) -> Void
|
||||
fileprivate let removeRequested: (PeerId) -> Void
|
||||
|
||||
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.theme = theme
|
||||
self.fontSize = fontSize
|
||||
self.presentationData = presentationData
|
||||
self.inverted = inverted
|
||||
self.peer = peer
|
||||
self.revealed = revealed
|
||||
self.setPeerIdRevealed = setPeerIdRevealed
|
||||
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) {
|
||||
@ -86,15 +91,24 @@ private let avatarFont = avatarPlaceholderFont(size: 16.0)
|
||||
|
||||
final class MentionChatInputPanelItemNode: ListViewItemNode {
|
||||
static let itemHeight: CGFloat = 42.0
|
||||
|
||||
private var item: MentionChatInputPanelItem?
|
||||
|
||||
|
||||
private let avatarNode: AvatarNode
|
||||
private let textNode: TextNode
|
||||
private let topSeparatorNode: ASDisplayNode
|
||||
private let separatorNode: 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() {
|
||||
self.avatarNode = AvatarNode(font: avatarFont)
|
||||
self.textNode = TextNode()
|
||||
@ -117,6 +131,15 @@ final class MentionChatInputPanelItemNode: ListViewItemNode {
|
||||
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?) {
|
||||
if let item = item as? MentionChatInputPanelItem {
|
||||
let doLayout = self.asyncLayout()
|
||||
@ -134,8 +157,8 @@ final class MentionChatInputPanelItemNode: ListViewItemNode {
|
||||
let previousItem = self.item
|
||||
|
||||
return { [weak self] item, params, mergedTop, mergedBottom in
|
||||
let primaryFont = Font.medium(floor(item.fontSize.baseDisplaySize * 14.0 / 17.0))
|
||||
let secondaryFont = Font.regular(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.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0))
|
||||
|
||||
let leftInset: CGFloat = 55.0 + params.leftInset
|
||||
let rightInset: CGFloat = 10.0 + params.rightInset
|
||||
@ -146,19 +169,23 @@ final class MentionChatInputPanelItemNode: ListViewItemNode {
|
||||
}
|
||||
|
||||
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 {
|
||||
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 nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: HashtagChatInputPanelItemNode.itemHeight), insets: UIEdgeInsets())
|
||||
|
||||
return (nodeLayout, { _ in
|
||||
return (nodeLayout, { animation in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
|
||||
strongSelf.validLayout = (nodeLayout.contentSize, params.leftInset, params.rightInset)
|
||||
|
||||
let revealOffset = strongSelf.revealOffset
|
||||
|
||||
if let updatedInverted = updatedInverted {
|
||||
if updatedInverted {
|
||||
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.topSeparatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
|
||||
strongSelf.backgroundColor = item.theme.list.plainBackgroundColor
|
||||
strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor
|
||||
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
|
||||
|
||||
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()
|
||||
|
||||
@ -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.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) {
|
||||
super.setHighlighted(highlighted, at: point, animated: animated)
|
||||
|
||||
if let revealNode = self.revealNode, self.revealOffset != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if highlighted {
|
||||
self.highlightedBackgroundNode.alpha = 1.0
|
||||
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()
|
||||
}
|
||||
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 {
|
||||
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 let theme: TelegramTheme
|
||||
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.resolvedWallpaper = resolvedWallpaper
|
||||
self.creatorAccountId = creatorAccountId
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.theme = decoder.decodeObjectForKey("theme", decoder: { TelegramTheme(decoder: $0) }) as! TelegramTheme
|
||||
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) {
|
||||
@ -115,6 +118,11 @@ public struct PresentationCloudTheme: PostboxCoding, Equatable {
|
||||
} else {
|
||||
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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user