mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge commit '75c773e694'
This commit is contained in:
commit
290d955b59
51 changed files with 2194 additions and 741 deletions
|
|
@ -1180,7 +1180,7 @@ public protocol SharedAccountContext: AnyObject {
|
|||
func navigateToChat(accountId: AccountRecordId, peerId: PeerId, messageId: MessageId?)
|
||||
func openChatMessage(_ params: OpenChatMessageParams) -> Bool
|
||||
func messageFromPreloadedChatHistoryViewForLocation(id: MessageId, location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, tag: HistoryViewInputTag?) -> Signal<(MessageIndex?, Bool), NoError>
|
||||
func makeOverlayAudioPlayerController(context: AccountContext, chatLocation: ChatLocation, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, playlistLocation: SharedMediaPlaylistLocation?, parentNavigationController: NavigationController?) -> ViewController & OverlayAudioPlayerController
|
||||
func makeOverlayAudioPlayerController(context: AccountContext, chatLocation: ChatLocation, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, playlistLocation: SharedMediaPlaylistLocation?, parentNavigationController: NavigationController?, updateMusicSaved: ((FileMediaReference, Bool) -> Void)?, reorderSavedMusic: ((FileMediaReference, FileMediaReference?) -> Void)?) -> ViewController & OverlayAudioPlayerController
|
||||
func makePeerInfoController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, fromChat: Bool, requestsContext: PeerInvitationImportersContext?) -> ViewController?
|
||||
func makeChannelAdminController(context: AccountContext, peerId: PeerId, adminId: PeerId, initialParticipant: ChannelParticipant) -> ViewController?
|
||||
func makeDeviceContactInfoController(context: ShareControllerAccountContext, environment: ShareControllerEnvironment, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController
|
||||
|
|
|
|||
|
|
@ -1179,7 +1179,7 @@ public enum ChatHistoryListSource {
|
|||
}
|
||||
|
||||
case `default`
|
||||
case custom(messages: Signal<([Message], Int32, Bool), NoError>, messageId: MessageId?, quote: Quote?, loadMore: (() -> Void)?)
|
||||
case custom(messages: Signal<([Message], Int32, Bool), NoError>, messageId: MessageId?, quote: Quote?, updateAll: Bool, canReorder: Bool, loadMore: (() -> Void)?)
|
||||
case customView(historyView: Signal<(MessageHistoryView, ViewUpdateType), NoError>)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ public enum PeerMessagesPlaylistLocation: Equatable, SharedMediaPlaylistLocation
|
|||
case messages(chatLocation: ChatLocation, tagMask: MessageTags, at: MessageId)
|
||||
case singleMessage(MessageId)
|
||||
case recentActions(Message)
|
||||
case custom(messages: Signal<([Message], Int32, Bool), NoError>, at: MessageId, loadMore: (() -> Void)?)
|
||||
case custom(messages: Signal<([Message], Int32, Bool), NoError>, canReorder: Bool, at: MessageId, loadMore: (() -> Void)?)
|
||||
|
||||
public var playlistId: PeerMessagesMediaPlaylistId {
|
||||
switch self {
|
||||
|
|
@ -50,7 +50,7 @@ public enum PeerMessagesPlaylistLocation: Equatable, SharedMediaPlaylistLocation
|
|||
|
||||
public var messageId: MessageId? {
|
||||
switch self {
|
||||
case let .messages(_, _, messageId), let .singleMessage(messageId), let .custom(_, messageId, _):
|
||||
case let .messages(_, _, messageId), let .singleMessage(messageId), let .custom(_, _, messageId, _):
|
||||
return messageId
|
||||
default:
|
||||
return nil
|
||||
|
|
@ -85,8 +85,8 @@ public enum PeerMessagesPlaylistLocation: Equatable, SharedMediaPlaylistLocation
|
|||
} else {
|
||||
return false
|
||||
}
|
||||
case let .custom(_, lhsAt, _):
|
||||
if case let .custom(_, rhsAt, _) = rhs, lhsAt == rhsAt {
|
||||
case let .custom(_, _, lhsAt, _):
|
||||
if case let .custom(_, _, rhsAt, _) = rhs, lhsAt == rhsAt {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -192,6 +192,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
|
|||
return
|
||||
}
|
||||
strongSelf.account = updatedAccount
|
||||
strongSelf.inAppPurchaseManager = InAppPurchaseManager(engine: .unauthorized(strongSelf.engine))
|
||||
}
|
||||
controller.loginWithNumber = { [weak self, weak controller] number, syncContacts in
|
||||
guard let self else {
|
||||
|
|
@ -217,6 +218,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
|
|||
case let .sentCode(account):
|
||||
controller?.inProgress = false
|
||||
strongSelf.account = account
|
||||
strongSelf.inAppPurchaseManager = InAppPurchaseManager(engine: .unauthorized(strongSelf.engine))
|
||||
case .loggedIn:
|
||||
break
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import PremiumCoinComponent
|
|||
import Markdown
|
||||
import CountrySelectionUI
|
||||
import AccountContext
|
||||
import AlertUI
|
||||
|
||||
final class AuthorizationSequencePaymentScreenComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
|
@ -142,12 +143,9 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
|
|||
}
|
||||
|
||||
if let errorText {
|
||||
//addAppLogEvent(postbox: component.engine.account.postbox, type: "premium_gift.promo_screen_fail")
|
||||
|
||||
let _ = errorText
|
||||
let _ = controller
|
||||
//let alertController = textAlertController(context: component.context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
|
||||
//controller.present(alertController, in: .window(.root))
|
||||
let theme = AlertControllerTheme(presentationData: presentationData)
|
||||
let alertController = textAlertController(alertContext: AlertControllerContext(theme: theme, themeSignal: .single(theme)), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
|
||||
controller.present(alertController, in: .window(.root))
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -3529,7 +3529,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
} else {
|
||||
playlistLocation = .custom(messages: foundMessages |> map { message, a, b in
|
||||
return (message.map { $0._asMessage() }, a, b)
|
||||
}, at: message.id, loadMore: {
|
||||
}, canReorder: false, at: message.id, loadMore: {
|
||||
loadMore()
|
||||
})
|
||||
}
|
||||
|
|
@ -4934,7 +4934,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
} else {
|
||||
controllerContext = strongSelf.context.sharedContext.makeTempAccountContext(account: account)
|
||||
}
|
||||
let controller = strongSelf.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, chatLocation: .peer(id: id.messageId.peerId), type: type, initialMessageId: id.messageId, initialOrder: order, playlistLocation: playlistLocation, parentNavigationController: navigationController)
|
||||
let controller = strongSelf.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, chatLocation: .peer(id: id.messageId.peerId), type: type, initialMessageId: id.messageId, initialOrder: order, playlistLocation: playlistLocation, parentNavigationController: navigationController, updateMusicSaved: nil, reorderSavedMusic: nil)
|
||||
strongSelf.interaction.dismissInput()
|
||||
strongSelf.interaction.present(controller, nil)
|
||||
} else if case let .messages(chatLocation, _, _) = playlistLocation {
|
||||
|
|
@ -4975,7 +4975,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
} else {
|
||||
controllerContext = strongSelf.context.sharedContext.makeTempAccountContext(account: account)
|
||||
}
|
||||
let controller = strongSelf.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, chatLocation: chatLocation, type: type, initialMessageId: id.messageId, initialOrder: order, playlistLocation: nil, parentNavigationController: navigationController)
|
||||
let controller = strongSelf.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, chatLocation: chatLocation, type: type, initialMessageId: id.messageId, initialOrder: order, playlistLocation: nil, parentNavigationController: navigationController, updateMusicSaved: nil, reorderSavedMusic: nil)
|
||||
strongSelf.interaction.dismissInput()
|
||||
strongSelf.interaction.present(controller, nil)
|
||||
} else if index.1 {
|
||||
|
|
|
|||
|
|
@ -384,6 +384,8 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
|||
private var contentSizeValue: CGSize?
|
||||
private var currentLeftOffset: CGFloat = 0.0
|
||||
|
||||
var reorderControlNode: ItemListEditableReorderControlNode?
|
||||
|
||||
private var currentIsRestricted = false
|
||||
private var cachedSearchResult: CachedChatListSearchResult?
|
||||
|
||||
|
|
@ -568,6 +570,13 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
|||
}
|
||||
}
|
||||
|
||||
override public func isReorderable(at point: CGPoint) -> Bool {
|
||||
if let reorderControlNode = self.reorderControlNode, reorderControlNode.frame.contains(point) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override public func asyncLayout() -> (_ item: ListMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: Bool, _ mergedBottom: Bool, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) {
|
||||
let titleNodeMakeLayout = self.titleNode.asyncLayout()
|
||||
let textNodeMakeLayout = TextNode.asyncLayout(self.textNode)
|
||||
|
|
@ -575,6 +584,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
|||
let extensionIconTextMakeLayout = TextNode.asyncLayout(self.extensionIconText)
|
||||
let dateNodeMakeLayout = TextNode.asyncLayout(self.dateNode)
|
||||
let iconImageLayout = self.iconImageNode.asyncLayout()
|
||||
let reorderControlLayout = ItemListEditableReorderControlNode.asyncLayout(self.reorderControlNode)
|
||||
|
||||
let currentMedia = self.currentMedia
|
||||
let currentMessage = self.message
|
||||
|
|
@ -618,6 +628,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
|||
var updatedStatusSignal: Signal<FileMediaResourceStatus, NoError>?
|
||||
var updatedPlaybackStatusSignal: Signal<MediaPlayerStatus, NoError>?
|
||||
var updatedFetchControls: FetchControls?
|
||||
var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool, ContainedViewLayoutTransition) -> ItemListEditableReorderControlNode)?
|
||||
|
||||
var isAudio = false
|
||||
var isVoice = false
|
||||
|
|
@ -896,7 +907,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
|||
|
||||
if statusUpdated && item.displayFileInfo {
|
||||
if let file = selectedMedia as? TelegramMediaFile {
|
||||
updatedStatusSignal = messageFileMediaResourceStatus(context: item.context, file: file, message: EngineMessage(message), isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult, isDownloadList: item.isDownloadList)
|
||||
updatedStatusSignal = messageFileMediaResourceStatus(context: item.context, file: file, message: EngineMessage(message), isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult || message.id.namespace == Namespaces.Message.Local, isDownloadList: item.isDownloadList)
|
||||
|> mapToSignal { value -> Signal<FileMediaResourceStatus, NoError> in
|
||||
if case .Fetching = value.fetchStatus, !item.isDownloadList {
|
||||
return .single(value) |> delay(0.1, queue: Queue.concurrentDefaultQueue())
|
||||
|
|
@ -978,17 +989,25 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
|||
captionText = text
|
||||
}
|
||||
|
||||
var reorderInset: CGFloat = 0.0
|
||||
|
||||
if item.canReorder {
|
||||
let sizeAndApply = reorderControlLayout(item.presentationData.theme.theme)
|
||||
reorderControlSizeAndApply = sizeAndApply
|
||||
reorderInset = sizeAndApply.0
|
||||
}
|
||||
|
||||
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
||||
let dateText = stringForRelativeTimestamp(strings: item.presentationData.strings, relativeTimestamp: item.message?.timestamp ?? 0, relativeTo: timestamp, dateTimeFormat: item.presentationData.dateTimeFormat)
|
||||
let dateAttributedString = NSAttributedString(string: dateText, font: dateFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor)
|
||||
|
||||
let (dateNodeLayout, dateNodeApply) = dateNodeMakeLayout(TextNodeLayoutArguments(attributedString: dateAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 12.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let (titleNodeLayout, titleNodeApply) = titleNodeMakeLayout(item.context, params.width - leftInset - leftOffset - rightInset - dateNodeLayout.size.width - 4.0, item.presentationData.theme.theme, titleText, titleExtraData)
|
||||
let (titleNodeLayout, titleNodeApply) = titleNodeMakeLayout(item.context, params.width - leftInset - leftOffset - rightInset - dateNodeLayout.size.width - 4.0 - reorderInset, item.presentationData.theme.theme, titleText, titleExtraData)
|
||||
|
||||
let (textNodeLayout, textNodeApply) = textNodeMakeLayout(TextNodeLayoutArguments(attributedString: captionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 30.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (textNodeLayout, textNodeApply) = textNodeMakeLayout(TextNodeLayoutArguments(attributedString: captionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 30.0 - reorderInset, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let (descriptionNodeLayout, descriptionNodeApply) = descriptionNodeMakeLayout(item.context, params.width - leftInset - rightInset - 30.0, item.presentationData.theme.theme, descriptionText, descriptionExtraData)
|
||||
let (descriptionNodeLayout, descriptionNodeApply) = descriptionNodeMakeLayout(item.context, params.width - leftInset - rightInset - 30.0 - reorderInset, item.presentationData.theme.theme, descriptionText, descriptionExtraData)
|
||||
|
||||
var (extensionTextLayout, extensionTextApply) = extensionIconTextMakeLayout(TextNodeLayoutArguments(attributedString: extensionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 38.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
if extensionTextLayout.truncated, let text = extensionText?.string {
|
||||
|
|
@ -1094,7 +1113,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
|||
strongSelf.currentLeftOffset = leftOffset
|
||||
|
||||
if let _ = updatedTheme {
|
||||
if item.displayBackground {
|
||||
if item.displayBackground || item.canReorder {
|
||||
let backgroundNode: ASDisplayNode
|
||||
if let current = strongSelf.backgroundNode {
|
||||
backgroundNode = current
|
||||
|
|
@ -1103,7 +1122,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
|||
strongSelf.backgroundNode = backgroundNode
|
||||
strongSelf.insertSubnode(backgroundNode, at: 0)
|
||||
}
|
||||
backgroundNode.backgroundColor = item.presentationData.theme.theme.list.itemBlocksBackgroundColor
|
||||
backgroundNode.backgroundColor = item.canReorder ? item.presentationData.theme.theme.list.plainBackgroundColor : item.presentationData.theme.theme.list.itemBlocksBackgroundColor
|
||||
}
|
||||
|
||||
strongSelf.separatorNode.backgroundColor = item.presentationData.theme.theme.list.itemPlainSeparatorColor
|
||||
|
|
@ -1135,6 +1154,24 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
|||
})
|
||||
}
|
||||
|
||||
if let reorderControlSizeAndApply = reorderControlSizeAndApply {
|
||||
let reorderControlFrame = CGRect(origin: CGPoint(x: params.width - params.rightInset - reorderControlSizeAndApply.0, y: 0.0), size: CGSize(width: reorderControlSizeAndApply.0, height: nodeLayout.contentSize.height))
|
||||
if strongSelf.reorderControlNode == nil {
|
||||
let reorderControlNode = reorderControlSizeAndApply.1(nodeLayout.contentSize.height, false, .immediate)
|
||||
strongSelf.reorderControlNode = reorderControlNode
|
||||
strongSelf.addSubnode(reorderControlNode)
|
||||
reorderControlNode.frame = reorderControlFrame
|
||||
} else if let reorderControlNode = strongSelf.reorderControlNode {
|
||||
let _ = reorderControlSizeAndApply.1(nodeLayout.contentSize.height, false, .immediate)
|
||||
transition.updateFrame(node: reorderControlNode, frame: reorderControlFrame)
|
||||
}
|
||||
} else if let reorderControlNode = strongSelf.reorderControlNode {
|
||||
strongSelf.reorderControlNode = nil
|
||||
transition.updateAlpha(node: reorderControlNode, alpha: 0.0, completion: { [weak reorderControlNode] _ in
|
||||
reorderControlNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
|
||||
transition.updateFrame(node: strongSelf.separatorNode, frame: CGRect(origin: CGPoint(x: leftInset + leftOffset, y: nodeLayout.contentSize.height - UIScreenPixel), size: CGSize(width: params.width - leftInset - leftOffset, height: UIScreenPixel)))
|
||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - UIScreenPixel), size: CGSize(width: params.width, height: nodeLayout.size.height + UIScreenPixel - nodeLayout.insets.bottom))
|
||||
|
||||
|
|
|
|||
|
|
@ -57,13 +57,14 @@ public final class ListMessageItem: ListViewItem {
|
|||
let isDownloadList: Bool
|
||||
let displayFileInfo: Bool
|
||||
let displayBackground: Bool
|
||||
let canReorder: Bool
|
||||
let style: ItemListStyle
|
||||
|
||||
let header: ListViewItemHeader?
|
||||
|
||||
public let selectable: Bool = true
|
||||
|
||||
public init(presentationData: ChatPresentationData, context: AccountContext, chatLocation: ChatLocation, interaction: ListMessageItemInteraction, message: Message?, translateToLanguage: String? = nil, selection: ChatHistoryMessageSelection, displayHeader: Bool, customHeader: ListViewItemHeader? = nil, hintIsLink: Bool = false, isGlobalSearchResult: Bool = false, isDownloadList: Bool = false, displayFileInfo: Bool = true, displayBackground: Bool = false, style: ItemListStyle = .plain) {
|
||||
public init(presentationData: ChatPresentationData, context: AccountContext, chatLocation: ChatLocation, interaction: ListMessageItemInteraction, message: Message?, translateToLanguage: String? = nil, selection: ChatHistoryMessageSelection, displayHeader: Bool, customHeader: ListViewItemHeader? = nil, hintIsLink: Bool = false, isGlobalSearchResult: Bool = false, isDownloadList: Bool = false, displayFileInfo: Bool = true, displayBackground: Bool = false, canReorder: Bool = false, style: ItemListStyle = .plain) {
|
||||
self.presentationData = presentationData
|
||||
self.context = context
|
||||
self.chatLocation = chatLocation
|
||||
|
|
@ -83,6 +84,7 @@ public final class ListMessageItem: ListViewItem {
|
|||
self.isDownloadList = isDownloadList
|
||||
self.displayFileInfo = displayFileInfo
|
||||
self.displayBackground = displayBackground
|
||||
self.canReorder = canReorder
|
||||
self.style = style
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -496,7 +496,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||
dict[-1502273946] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentPremiumSubscription($0) }
|
||||
dict[494149367] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentStarsGift($0) }
|
||||
dict[1964968186] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentStarsGiveaway($0) }
|
||||
dict[-572715178] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentStarsTopup($0) }
|
||||
dict[-106780981] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentStarsTopup($0) }
|
||||
dict[1012306921] = { return Api.InputTheme.parse_inputTheme($0) }
|
||||
dict[-175567375] = { return Api.InputTheme.parse_inputThemeSlug($0) }
|
||||
dict[-1881255857] = { return Api.InputThemeSettings.parse_inputThemeSettings($0) }
|
||||
|
|
@ -954,7 +954,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||
dict[2109703795] = { return Api.SponsoredMessage.parse_sponsoredMessage($0) }
|
||||
dict[1124938064] = { return Api.SponsoredMessageReportOption.parse_sponsoredMessageReportOption($0) }
|
||||
dict[-963180333] = { return Api.SponsoredPeer.parse_sponsoredPeer($0) }
|
||||
dict[12386139] = { return Api.StarGift.parse_starGift($0) }
|
||||
dict[-2136190013] = { return Api.StarGift.parse_starGift($0) }
|
||||
dict[648369470] = { return Api.StarGift.parse_starGiftUnique($0) }
|
||||
dict[-650279524] = { return Api.StarGiftAttribute.parse_starGiftAttributeBackdrop($0) }
|
||||
dict[970559507] = { return Api.StarGiftAttribute.parse_starGiftAttributeModel($0) }
|
||||
|
|
@ -1430,8 +1430,10 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||
dict[870003448] = { return Api.messages.TranslatedText.parse_translateResult($0) }
|
||||
dict[1218005070] = { return Api.messages.VotesList.parse_votesList($0) }
|
||||
dict[-44166467] = { return Api.messages.WebPage.parse_webPage($0) }
|
||||
dict[-1254192351] = { return Api.messages.WebPagePreview.parse_webPagePreview($0) }
|
||||
dict[-1936029524] = { return Api.messages.WebPagePreview.parse_webPagePreview($0) }
|
||||
dict[1042605427] = { return Api.payments.BankCardData.parse_bankCardData($0) }
|
||||
dict[-706379148] = { return Api.payments.CheckCanSendGiftResult.parse_checkCanSendGiftResultFail($0) }
|
||||
dict[927967149] = { return Api.payments.CheckCanSendGiftResult.parse_checkCanSendGiftResultOk($0) }
|
||||
dict[675942550] = { return Api.payments.CheckedGiftCode.parse_checkedGiftCode($0) }
|
||||
dict[-1730811363] = { return Api.payments.ConnectedStarRefBots.parse_connectedStarRefBots($0) }
|
||||
dict[-1362048039] = { return Api.payments.ExportedInvoice.parse_exportedInvoice($0) }
|
||||
|
|
@ -1458,7 +1460,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||
dict[497778871] = { return Api.payments.StarsRevenueWithdrawalUrl.parse_starsRevenueWithdrawalUrl($0) }
|
||||
dict[1822222573] = { return Api.payments.StarsStatus.parse_starsStatus($0) }
|
||||
dict[-1261053863] = { return Api.payments.SuggestedStarRefBots.parse_suggestedStarRefBots($0) }
|
||||
dict[-895289845] = { return Api.payments.UniqueStarGift.parse_uniqueStarGift($0) }
|
||||
dict[1097619176] = { return Api.payments.UniqueStarGift.parse_uniqueStarGift($0) }
|
||||
dict[1362093126] = { return Api.payments.UniqueStarGiftValueInfo.parse_uniqueStarGiftValueInfo($0) }
|
||||
dict[-784000893] = { return Api.payments.ValidatedRequestedInfo.parse_validatedRequestedInfo($0) }
|
||||
dict[541839704] = { return Api.phone.ExportedGroupCallInvite.parse_exportedGroupCallInvite($0) }
|
||||
|
|
@ -2558,6 +2560,8 @@ public extension Api {
|
|||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.payments.BankCardData:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.payments.CheckCanSendGiftResult:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.payments.CheckedGiftCode:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.payments.ConnectedStarRefBots:
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ public extension Api {
|
|||
case inputStorePaymentPremiumSubscription(flags: Int32)
|
||||
case inputStorePaymentStarsGift(userId: Api.InputUser, stars: Int64, currency: String, amount: Int64)
|
||||
case inputStorePaymentStarsGiveaway(flags: Int32, stars: Int64, boostPeer: Api.InputPeer, additionalPeers: [Api.InputPeer]?, countriesIso2: [String]?, prizeDescription: String?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64, users: Int32)
|
||||
case inputStorePaymentStarsTopup(stars: Int64, currency: String, amount: Int64)
|
||||
case inputStorePaymentStarsTopup(flags: Int32, stars: Int64, currency: String, amount: Int64, spendPurposePeer: Api.InputPeer?)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
|
|
@ -221,13 +221,15 @@ public extension Api {
|
|||
serializeInt64(amount, buffer: buffer, boxed: false)
|
||||
serializeInt32(users, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .inputStorePaymentStarsTopup(let stars, let currency, let amount):
|
||||
case .inputStorePaymentStarsTopup(let flags, let stars, let currency, let amount, let spendPurposePeer):
|
||||
if boxed {
|
||||
buffer.appendInt32(-572715178)
|
||||
buffer.appendInt32(-106780981)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt64(stars, buffer: buffer, boxed: false)
|
||||
serializeString(currency, buffer: buffer, boxed: false)
|
||||
serializeInt64(amount, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 0) != 0 {spendPurposePeer!.serialize(buffer, true)}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
@ -248,8 +250,8 @@ public extension Api {
|
|||
return ("inputStorePaymentStarsGift", [("userId", userId as Any), ("stars", stars as Any), ("currency", currency as Any), ("amount", amount as Any)])
|
||||
case .inputStorePaymentStarsGiveaway(let flags, let stars, let boostPeer, let additionalPeers, let countriesIso2, let prizeDescription, let randomId, let untilDate, let currency, let amount, let users):
|
||||
return ("inputStorePaymentStarsGiveaway", [("flags", flags as Any), ("stars", stars as Any), ("boostPeer", boostPeer as Any), ("additionalPeers", additionalPeers as Any), ("countriesIso2", countriesIso2 as Any), ("prizeDescription", prizeDescription as Any), ("randomId", randomId as Any), ("untilDate", untilDate as Any), ("currency", currency as Any), ("amount", amount as Any), ("users", users as Any)])
|
||||
case .inputStorePaymentStarsTopup(let stars, let currency, let amount):
|
||||
return ("inputStorePaymentStarsTopup", [("stars", stars as Any), ("currency", currency as Any), ("amount", amount as Any)])
|
||||
case .inputStorePaymentStarsTopup(let flags, let stars, let currency, let amount, let spendPurposePeer):
|
||||
return ("inputStorePaymentStarsTopup", [("flags", flags as Any), ("stars", stars as Any), ("currency", currency as Any), ("amount", amount as Any), ("spendPurposePeer", spendPurposePeer as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -449,17 +451,25 @@ public extension Api {
|
|||
}
|
||||
}
|
||||
public static func parse_inputStorePaymentStarsTopup(_ reader: BufferReader) -> InputStorePaymentPurpose? {
|
||||
var _1: Int64?
|
||||
_1 = reader.readInt64()
|
||||
var _2: String?
|
||||
_2 = parseString(reader)
|
||||
var _3: Int64?
|
||||
_3 = reader.readInt64()
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int64?
|
||||
_2 = reader.readInt64()
|
||||
var _3: String?
|
||||
_3 = parseString(reader)
|
||||
var _4: Int64?
|
||||
_4 = reader.readInt64()
|
||||
var _5: Api.InputPeer?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
|
||||
_5 = Api.parse(reader, signature: signature) as? Api.InputPeer
|
||||
} }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
if _c1 && _c2 && _c3 {
|
||||
return Api.InputStorePaymentPurpose.inputStorePaymentStarsTopup(stars: _1!, currency: _2!, amount: _3!)
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
||||
return Api.InputStorePaymentPurpose.inputStorePaymentStarsTopup(flags: _1!, stars: _2!, currency: _3!, amount: _4!, spendPurposePeer: _5)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -288,14 +288,14 @@ public extension Api {
|
|||
}
|
||||
public extension Api {
|
||||
enum StarGift: TypeConstructorDescription {
|
||||
case starGift(flags: Int32, id: Int64, sticker: Api.Document, stars: Int64, availabilityRemains: Int32?, availabilityTotal: Int32?, availabilityResale: Int64?, convertStars: Int64, firstSaleDate: Int32?, lastSaleDate: Int32?, upgradeStars: Int64?, resellMinStars: Int64?, title: String?, releasedBy: Api.Peer?, perUserTotal: Int32?, perUserRemains: Int32?)
|
||||
case starGift(flags: Int32, id: Int64, sticker: Api.Document, stars: Int64, availabilityRemains: Int32?, availabilityTotal: Int32?, availabilityResale: Int64?, convertStars: Int64, firstSaleDate: Int32?, lastSaleDate: Int32?, upgradeStars: Int64?, resellMinStars: Int64?, title: String?, releasedBy: Api.Peer?, perUserTotal: Int32?, perUserRemains: Int32?, lockedUntilDate: Int32?)
|
||||
case starGiftUnique(flags: Int32, id: Int64, giftId: Int64, title: String, slug: String, num: Int32, ownerId: Api.Peer?, ownerName: String?, ownerAddress: String?, attributes: [Api.StarGiftAttribute], availabilityIssued: Int32, availabilityTotal: Int32, giftAddress: String?, resellAmount: [Api.StarsAmount]?, releasedBy: Api.Peer?, valueAmount: Int64?, valueCurrency: String?)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let availabilityResale, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars, let resellMinStars, let title, let releasedBy, let perUserTotal, let perUserRemains):
|
||||
case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let availabilityResale, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars, let resellMinStars, let title, let releasedBy, let perUserTotal, let perUserRemains, let lockedUntilDate):
|
||||
if boxed {
|
||||
buffer.appendInt32(12386139)
|
||||
buffer.appendInt32(-2136190013)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt64(id, buffer: buffer, boxed: false)
|
||||
|
|
@ -313,6 +313,7 @@ public extension Api {
|
|||
if Int(flags) & Int(1 << 6) != 0 {releasedBy!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 8) != 0 {serializeInt32(perUserTotal!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 8) != 0 {serializeInt32(perUserRemains!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 9) != 0 {serializeInt32(lockedUntilDate!, buffer: buffer, boxed: false)}
|
||||
break
|
||||
case .starGiftUnique(let flags, let id, let giftId, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellAmount, let releasedBy, let valueAmount, let valueCurrency):
|
||||
if boxed {
|
||||
|
|
@ -349,8 +350,8 @@ public extension Api {
|
|||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let availabilityResale, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars, let resellMinStars, let title, let releasedBy, let perUserTotal, let perUserRemains):
|
||||
return ("starGift", [("flags", flags as Any), ("id", id as Any), ("sticker", sticker as Any), ("stars", stars as Any), ("availabilityRemains", availabilityRemains as Any), ("availabilityTotal", availabilityTotal as Any), ("availabilityResale", availabilityResale as Any), ("convertStars", convertStars as Any), ("firstSaleDate", firstSaleDate as Any), ("lastSaleDate", lastSaleDate as Any), ("upgradeStars", upgradeStars as Any), ("resellMinStars", resellMinStars as Any), ("title", title as Any), ("releasedBy", releasedBy as Any), ("perUserTotal", perUserTotal as Any), ("perUserRemains", perUserRemains as Any)])
|
||||
case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let availabilityResale, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars, let resellMinStars, let title, let releasedBy, let perUserTotal, let perUserRemains, let lockedUntilDate):
|
||||
return ("starGift", [("flags", flags as Any), ("id", id as Any), ("sticker", sticker as Any), ("stars", stars as Any), ("availabilityRemains", availabilityRemains as Any), ("availabilityTotal", availabilityTotal as Any), ("availabilityResale", availabilityResale as Any), ("convertStars", convertStars as Any), ("firstSaleDate", firstSaleDate as Any), ("lastSaleDate", lastSaleDate as Any), ("upgradeStars", upgradeStars as Any), ("resellMinStars", resellMinStars as Any), ("title", title as Any), ("releasedBy", releasedBy as Any), ("perUserTotal", perUserTotal as Any), ("perUserRemains", perUserRemains as Any), ("lockedUntilDate", lockedUntilDate as Any)])
|
||||
case .starGiftUnique(let flags, let id, let giftId, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellAmount, let releasedBy, let valueAmount, let valueCurrency):
|
||||
return ("starGiftUnique", [("flags", flags as Any), ("id", id as Any), ("giftId", giftId as Any), ("title", title as Any), ("slug", slug as Any), ("num", num as Any), ("ownerId", ownerId as Any), ("ownerName", ownerName as Any), ("ownerAddress", ownerAddress as Any), ("attributes", attributes as Any), ("availabilityIssued", availabilityIssued as Any), ("availabilityTotal", availabilityTotal as Any), ("giftAddress", giftAddress as Any), ("resellAmount", resellAmount as Any), ("releasedBy", releasedBy as Any), ("valueAmount", valueAmount as Any), ("valueCurrency", valueCurrency as Any)])
|
||||
}
|
||||
|
|
@ -393,6 +394,8 @@ public extension Api {
|
|||
if Int(_1!) & Int(1 << 8) != 0 {_15 = reader.readInt32() }
|
||||
var _16: Int32?
|
||||
if Int(_1!) & Int(1 << 8) != 0 {_16 = reader.readInt32() }
|
||||
var _17: Int32?
|
||||
if Int(_1!) & Int(1 << 9) != 0 {_17 = reader.readInt32() }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
|
|
@ -409,8 +412,9 @@ public extension Api {
|
|||
let _c14 = (Int(_1!) & Int(1 << 6) == 0) || _14 != nil
|
||||
let _c15 = (Int(_1!) & Int(1 << 8) == 0) || _15 != nil
|
||||
let _c16 = (Int(_1!) & Int(1 << 8) == 0) || _16 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 {
|
||||
return Api.StarGift.starGift(flags: _1!, id: _2!, sticker: _3!, stars: _4!, availabilityRemains: _5, availabilityTotal: _6, availabilityResale: _7, convertStars: _8!, firstSaleDate: _9, lastSaleDate: _10, upgradeStars: _11, resellMinStars: _12, title: _13, releasedBy: _14, perUserTotal: _15, perUserRemains: _16)
|
||||
let _c17 = (Int(_1!) & Int(1 << 9) == 0) || _17 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 {
|
||||
return Api.StarGift.starGift(flags: _1!, id: _2!, sticker: _3!, stars: _4!, availabilityRemains: _5, availabilityTotal: _6, availabilityResale: _7, convertStars: _8!, firstSaleDate: _9, lastSaleDate: _10, upgradeStars: _11, resellMinStars: _12, title: _13, releasedBy: _14, perUserTotal: _15, perUserRemains: _16, lockedUntilDate: _17)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -812,16 +812,21 @@ public extension Api.messages {
|
|||
}
|
||||
public extension Api.messages {
|
||||
indirect enum WebPagePreview: TypeConstructorDescription {
|
||||
case webPagePreview(media: Api.MessageMedia, users: [Api.User])
|
||||
case webPagePreview(media: Api.MessageMedia, chats: [Api.Chat], users: [Api.User])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .webPagePreview(let media, let users):
|
||||
case .webPagePreview(let media, let chats, let users):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1254192351)
|
||||
buffer.appendInt32(-1936029524)
|
||||
}
|
||||
media.serialize(buffer, true)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(chats.count))
|
||||
for item in chats {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(users.count))
|
||||
for item in users {
|
||||
item.serialize(buffer, true)
|
||||
|
|
@ -832,8 +837,8 @@ public extension Api.messages {
|
|||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .webPagePreview(let media, let users):
|
||||
return ("webPagePreview", [("media", media as Any), ("users", users as Any)])
|
||||
case .webPagePreview(let media, let chats, let users):
|
||||
return ("webPagePreview", [("media", media as Any), ("chats", chats as Any), ("users", users as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -842,14 +847,19 @@ public extension Api.messages {
|
|||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.MessageMedia
|
||||
}
|
||||
var _2: [Api.User]?
|
||||
var _2: [Api.Chat]?
|
||||
if let _ = reader.readInt32() {
|
||||
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
|
||||
}
|
||||
var _3: [Api.User]?
|
||||
if let _ = reader.readInt32() {
|
||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.messages.WebPagePreview.webPagePreview(media: _1!, users: _2!)
|
||||
let _c3 = _3 != nil
|
||||
if _c1 && _c2 && _c3 {
|
||||
return Api.messages.WebPagePreview.webPagePreview(media: _1!, chats: _2!, users: _3!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
|
@ -904,6 +914,56 @@ public extension Api.payments {
|
|||
|
||||
}
|
||||
}
|
||||
public extension Api.payments {
|
||||
enum CheckCanSendGiftResult: TypeConstructorDescription {
|
||||
case checkCanSendGiftResultFail(reason: Api.TextWithEntities)
|
||||
case checkCanSendGiftResultOk
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .checkCanSendGiftResultFail(let reason):
|
||||
if boxed {
|
||||
buffer.appendInt32(-706379148)
|
||||
}
|
||||
reason.serialize(buffer, true)
|
||||
break
|
||||
case .checkCanSendGiftResultOk:
|
||||
if boxed {
|
||||
buffer.appendInt32(927967149)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .checkCanSendGiftResultFail(let reason):
|
||||
return ("checkCanSendGiftResultFail", [("reason", reason as Any)])
|
||||
case .checkCanSendGiftResultOk:
|
||||
return ("checkCanSendGiftResultOk", [])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_checkCanSendGiftResultFail(_ reader: BufferReader) -> CheckCanSendGiftResult? {
|
||||
var _1: Api.TextWithEntities?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.TextWithEntities
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.payments.CheckCanSendGiftResult.checkCanSendGiftResultFail(reason: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_checkCanSendGiftResultOk(_ reader: BufferReader) -> CheckCanSendGiftResult? {
|
||||
return Api.payments.CheckCanSendGiftResult.checkCanSendGiftResultOk
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.payments {
|
||||
enum CheckedGiftCode: TypeConstructorDescription {
|
||||
case checkedGiftCode(flags: Int32, fromId: Api.Peer?, giveawayMsgId: Int32?, toId: Int64?, date: Int32, months: Int32, usedDate: Int32?, chats: [Api.Chat], users: [Api.User])
|
||||
|
|
@ -1552,61 +1612,3 @@ public extension Api.payments {
|
|||
|
||||
}
|
||||
}
|
||||
public extension Api.payments {
|
||||
indirect enum PaymentResult: TypeConstructorDescription {
|
||||
case paymentResult(updates: Api.Updates)
|
||||
case paymentVerificationNeeded(url: String)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .paymentResult(let updates):
|
||||
if boxed {
|
||||
buffer.appendInt32(1314881805)
|
||||
}
|
||||
updates.serialize(buffer, true)
|
||||
break
|
||||
case .paymentVerificationNeeded(let url):
|
||||
if boxed {
|
||||
buffer.appendInt32(-666824391)
|
||||
}
|
||||
serializeString(url, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .paymentResult(let updates):
|
||||
return ("paymentResult", [("updates", updates as Any)])
|
||||
case .paymentVerificationNeeded(let url):
|
||||
return ("paymentVerificationNeeded", [("url", url as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_paymentResult(_ reader: BufferReader) -> PaymentResult? {
|
||||
var _1: Api.Updates?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.Updates
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.payments.PaymentResult.paymentResult(updates: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_paymentVerificationNeeded(_ reader: BufferReader) -> PaymentResult? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.payments.PaymentResult.paymentVerificationNeeded(url: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,61 @@
|
|||
public extension Api.payments {
|
||||
indirect enum PaymentResult: TypeConstructorDescription {
|
||||
case paymentResult(updates: Api.Updates)
|
||||
case paymentVerificationNeeded(url: String)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .paymentResult(let updates):
|
||||
if boxed {
|
||||
buffer.appendInt32(1314881805)
|
||||
}
|
||||
updates.serialize(buffer, true)
|
||||
break
|
||||
case .paymentVerificationNeeded(let url):
|
||||
if boxed {
|
||||
buffer.appendInt32(-666824391)
|
||||
}
|
||||
serializeString(url, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .paymentResult(let updates):
|
||||
return ("paymentResult", [("updates", updates as Any)])
|
||||
case .paymentVerificationNeeded(let url):
|
||||
return ("paymentVerificationNeeded", [("url", url as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_paymentResult(_ reader: BufferReader) -> PaymentResult? {
|
||||
var _1: Api.Updates?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.Updates
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.payments.PaymentResult.paymentResult(updates: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_paymentVerificationNeeded(_ reader: BufferReader) -> PaymentResult? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.payments.PaymentResult.paymentVerificationNeeded(url: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.payments {
|
||||
enum ResaleStarGifts: TypeConstructorDescription {
|
||||
case resaleStarGifts(flags: Int32, count: Int32, gifts: [Api.StarGift], nextOffset: String?, attributes: [Api.StarGiftAttribute]?, attributesHash: Int64?, chats: [Api.Chat], counters: [Api.StarGiftAttributeCounter]?, users: [Api.User])
|
||||
|
|
@ -718,16 +776,21 @@ public extension Api.payments {
|
|||
}
|
||||
public extension Api.payments {
|
||||
enum UniqueStarGift: TypeConstructorDescription {
|
||||
case uniqueStarGift(gift: Api.StarGift, users: [Api.User])
|
||||
case uniqueStarGift(gift: Api.StarGift, chats: [Api.Chat], users: [Api.User])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .uniqueStarGift(let gift, let users):
|
||||
case .uniqueStarGift(let gift, let chats, let users):
|
||||
if boxed {
|
||||
buffer.appendInt32(-895289845)
|
||||
buffer.appendInt32(1097619176)
|
||||
}
|
||||
gift.serialize(buffer, true)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(chats.count))
|
||||
for item in chats {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(users.count))
|
||||
for item in users {
|
||||
item.serialize(buffer, true)
|
||||
|
|
@ -738,8 +801,8 @@ public extension Api.payments {
|
|||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .uniqueStarGift(let gift, let users):
|
||||
return ("uniqueStarGift", [("gift", gift as Any), ("users", users as Any)])
|
||||
case .uniqueStarGift(let gift, let chats, let users):
|
||||
return ("uniqueStarGift", [("gift", gift as Any), ("chats", chats as Any), ("users", users as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -748,14 +811,19 @@ public extension Api.payments {
|
|||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.StarGift
|
||||
}
|
||||
var _2: [Api.User]?
|
||||
var _2: [Api.Chat]?
|
||||
if let _ = reader.readInt32() {
|
||||
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
|
||||
}
|
||||
var _3: [Api.User]?
|
||||
if let _ = reader.readInt32() {
|
||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.payments.UniqueStarGift.uniqueStarGift(gift: _1!, users: _2!)
|
||||
let _c3 = _3 != nil
|
||||
if _c1 && _c2 && _c3 {
|
||||
return Api.payments.UniqueStarGift.uniqueStarGift(gift: _1!, chats: _2!, users: _3!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
|
@ -1624,107 +1692,3 @@ public extension Api.premium {
|
|||
|
||||
}
|
||||
}
|
||||
public extension Api.smsjobs {
|
||||
enum EligibilityToJoin: TypeConstructorDescription {
|
||||
case eligibleToJoin(termsUrl: String, monthlySentSms: Int32)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .eligibleToJoin(let termsUrl, let monthlySentSms):
|
||||
if boxed {
|
||||
buffer.appendInt32(-594852657)
|
||||
}
|
||||
serializeString(termsUrl, buffer: buffer, boxed: false)
|
||||
serializeInt32(monthlySentSms, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .eligibleToJoin(let termsUrl, let monthlySentSms):
|
||||
return ("eligibleToJoin", [("termsUrl", termsUrl as Any), ("monthlySentSms", monthlySentSms as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_eligibleToJoin(_ reader: BufferReader) -> EligibilityToJoin? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.smsjobs.EligibilityToJoin.eligibleToJoin(termsUrl: _1!, monthlySentSms: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.smsjobs {
|
||||
enum Status: TypeConstructorDescription {
|
||||
case status(flags: Int32, recentSent: Int32, recentSince: Int32, recentRemains: Int32, totalSent: Int32, totalSince: Int32, lastGiftSlug: String?, termsUrl: String)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .status(let flags, let recentSent, let recentSince, let recentRemains, let totalSent, let totalSince, let lastGiftSlug, let termsUrl):
|
||||
if boxed {
|
||||
buffer.appendInt32(720277905)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt32(recentSent, buffer: buffer, boxed: false)
|
||||
serializeInt32(recentSince, buffer: buffer, boxed: false)
|
||||
serializeInt32(recentRemains, buffer: buffer, boxed: false)
|
||||
serializeInt32(totalSent, buffer: buffer, boxed: false)
|
||||
serializeInt32(totalSince, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 1) != 0 {serializeString(lastGiftSlug!, buffer: buffer, boxed: false)}
|
||||
serializeString(termsUrl, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .status(let flags, let recentSent, let recentSince, let recentRemains, let totalSent, let totalSince, let lastGiftSlug, let termsUrl):
|
||||
return ("status", [("flags", flags as Any), ("recentSent", recentSent as Any), ("recentSince", recentSince as Any), ("recentRemains", recentRemains as Any), ("totalSent", totalSent as Any), ("totalSince", totalSince as Any), ("lastGiftSlug", lastGiftSlug as Any), ("termsUrl", termsUrl as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_status(_ reader: BufferReader) -> Status? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
var _3: Int32?
|
||||
_3 = reader.readInt32()
|
||||
var _4: Int32?
|
||||
_4 = reader.readInt32()
|
||||
var _5: Int32?
|
||||
_5 = reader.readInt32()
|
||||
var _6: Int32?
|
||||
_6 = reader.readInt32()
|
||||
var _7: String?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {_7 = parseString(reader) }
|
||||
var _8: String?
|
||||
_8 = parseString(reader)
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
let _c6 = _6 != nil
|
||||
let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil
|
||||
let _c8 = _8 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
|
||||
return Api.smsjobs.Status.status(flags: _1!, recentSent: _2!, recentSince: _3!, recentRemains: _4!, totalSent: _5!, totalSince: _6!, lastGiftSlug: _7, termsUrl: _8!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,107 @@
|
|||
public extension Api.smsjobs {
|
||||
enum EligibilityToJoin: TypeConstructorDescription {
|
||||
case eligibleToJoin(termsUrl: String, monthlySentSms: Int32)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .eligibleToJoin(let termsUrl, let monthlySentSms):
|
||||
if boxed {
|
||||
buffer.appendInt32(-594852657)
|
||||
}
|
||||
serializeString(termsUrl, buffer: buffer, boxed: false)
|
||||
serializeInt32(monthlySentSms, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .eligibleToJoin(let termsUrl, let monthlySentSms):
|
||||
return ("eligibleToJoin", [("termsUrl", termsUrl as Any), ("monthlySentSms", monthlySentSms as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_eligibleToJoin(_ reader: BufferReader) -> EligibilityToJoin? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.smsjobs.EligibilityToJoin.eligibleToJoin(termsUrl: _1!, monthlySentSms: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.smsjobs {
|
||||
enum Status: TypeConstructorDescription {
|
||||
case status(flags: Int32, recentSent: Int32, recentSince: Int32, recentRemains: Int32, totalSent: Int32, totalSince: Int32, lastGiftSlug: String?, termsUrl: String)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .status(let flags, let recentSent, let recentSince, let recentRemains, let totalSent, let totalSince, let lastGiftSlug, let termsUrl):
|
||||
if boxed {
|
||||
buffer.appendInt32(720277905)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt32(recentSent, buffer: buffer, boxed: false)
|
||||
serializeInt32(recentSince, buffer: buffer, boxed: false)
|
||||
serializeInt32(recentRemains, buffer: buffer, boxed: false)
|
||||
serializeInt32(totalSent, buffer: buffer, boxed: false)
|
||||
serializeInt32(totalSince, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 1) != 0 {serializeString(lastGiftSlug!, buffer: buffer, boxed: false)}
|
||||
serializeString(termsUrl, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .status(let flags, let recentSent, let recentSince, let recentRemains, let totalSent, let totalSince, let lastGiftSlug, let termsUrl):
|
||||
return ("status", [("flags", flags as Any), ("recentSent", recentSent as Any), ("recentSince", recentSince as Any), ("recentRemains", recentRemains as Any), ("totalSent", totalSent as Any), ("totalSince", totalSince as Any), ("lastGiftSlug", lastGiftSlug as Any), ("termsUrl", termsUrl as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_status(_ reader: BufferReader) -> Status? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
var _3: Int32?
|
||||
_3 = reader.readInt32()
|
||||
var _4: Int32?
|
||||
_4 = reader.readInt32()
|
||||
var _5: Int32?
|
||||
_5 = reader.readInt32()
|
||||
var _6: Int32?
|
||||
_6 = reader.readInt32()
|
||||
var _7: String?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {_7 = parseString(reader) }
|
||||
var _8: String?
|
||||
_8 = parseString(reader)
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
let _c6 = _6 != nil
|
||||
let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil
|
||||
let _c8 = _8 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
|
||||
return Api.smsjobs.Status.status(flags: _1!, recentSent: _2!, recentSince: _3!, recentRemains: _4!, totalSent: _5!, totalSince: _6!, lastGiftSlug: _7, termsUrl: _8!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.stats {
|
||||
enum BroadcastStats: TypeConstructorDescription {
|
||||
case broadcastStats(period: Api.StatsDateRangeDays, followers: Api.StatsAbsValueAndPrev, viewsPerPost: Api.StatsAbsValueAndPrev, sharesPerPost: Api.StatsAbsValueAndPrev, reactionsPerPost: Api.StatsAbsValueAndPrev, viewsPerStory: Api.StatsAbsValueAndPrev, sharesPerStory: Api.StatsAbsValueAndPrev, reactionsPerStory: Api.StatsAbsValueAndPrev, enabledNotifications: Api.StatsPercentValue, growthGraph: Api.StatsGraph, followersGraph: Api.StatsGraph, muteGraph: Api.StatsGraph, topHoursGraph: Api.StatsGraph, interactionsGraph: Api.StatsGraph, ivInteractionsGraph: Api.StatsGraph, viewsBySourceGraph: Api.StatsGraph, newFollowersBySourceGraph: Api.StatsGraph, languagesGraph: Api.StatsGraph, reactionsByEmotionGraph: Api.StatsGraph, storyInteractionsGraph: Api.StatsGraph, storyReactionsByEmotionGraph: Api.StatsGraph, recentPostsInteractions: [Api.PostInteractionCounters])
|
||||
|
|
@ -1450,207 +1554,3 @@ public extension Api.updates {
|
|||
|
||||
}
|
||||
}
|
||||
public extension Api.updates {
|
||||
enum Difference: TypeConstructorDescription {
|
||||
case difference(newMessages: [Api.Message], newEncryptedMessages: [Api.EncryptedMessage], otherUpdates: [Api.Update], chats: [Api.Chat], users: [Api.User], state: Api.updates.State)
|
||||
case differenceEmpty(date: Int32, seq: Int32)
|
||||
case differenceSlice(newMessages: [Api.Message], newEncryptedMessages: [Api.EncryptedMessage], otherUpdates: [Api.Update], chats: [Api.Chat], users: [Api.User], intermediateState: Api.updates.State)
|
||||
case differenceTooLong(pts: Int32)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .difference(let newMessages, let newEncryptedMessages, let otherUpdates, let chats, let users, let state):
|
||||
if boxed {
|
||||
buffer.appendInt32(16030880)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(newMessages.count))
|
||||
for item in newMessages {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(newEncryptedMessages.count))
|
||||
for item in newEncryptedMessages {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(otherUpdates.count))
|
||||
for item in otherUpdates {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(chats.count))
|
||||
for item in chats {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(users.count))
|
||||
for item in users {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
state.serialize(buffer, true)
|
||||
break
|
||||
case .differenceEmpty(let date, let seq):
|
||||
if boxed {
|
||||
buffer.appendInt32(1567990072)
|
||||
}
|
||||
serializeInt32(date, buffer: buffer, boxed: false)
|
||||
serializeInt32(seq, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .differenceSlice(let newMessages, let newEncryptedMessages, let otherUpdates, let chats, let users, let intermediateState):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1459938943)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(newMessages.count))
|
||||
for item in newMessages {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(newEncryptedMessages.count))
|
||||
for item in newEncryptedMessages {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(otherUpdates.count))
|
||||
for item in otherUpdates {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(chats.count))
|
||||
for item in chats {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(users.count))
|
||||
for item in users {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
intermediateState.serialize(buffer, true)
|
||||
break
|
||||
case .differenceTooLong(let pts):
|
||||
if boxed {
|
||||
buffer.appendInt32(1258196845)
|
||||
}
|
||||
serializeInt32(pts, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .difference(let newMessages, let newEncryptedMessages, let otherUpdates, let chats, let users, let state):
|
||||
return ("difference", [("newMessages", newMessages as Any), ("newEncryptedMessages", newEncryptedMessages as Any), ("otherUpdates", otherUpdates as Any), ("chats", chats as Any), ("users", users as Any), ("state", state as Any)])
|
||||
case .differenceEmpty(let date, let seq):
|
||||
return ("differenceEmpty", [("date", date as Any), ("seq", seq as Any)])
|
||||
case .differenceSlice(let newMessages, let newEncryptedMessages, let otherUpdates, let chats, let users, let intermediateState):
|
||||
return ("differenceSlice", [("newMessages", newMessages as Any), ("newEncryptedMessages", newEncryptedMessages as Any), ("otherUpdates", otherUpdates as Any), ("chats", chats as Any), ("users", users as Any), ("intermediateState", intermediateState as Any)])
|
||||
case .differenceTooLong(let pts):
|
||||
return ("differenceTooLong", [("pts", pts as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_difference(_ reader: BufferReader) -> Difference? {
|
||||
var _1: [Api.Message]?
|
||||
if let _ = reader.readInt32() {
|
||||
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self)
|
||||
}
|
||||
var _2: [Api.EncryptedMessage]?
|
||||
if let _ = reader.readInt32() {
|
||||
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.EncryptedMessage.self)
|
||||
}
|
||||
var _3: [Api.Update]?
|
||||
if let _ = reader.readInt32() {
|
||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self)
|
||||
}
|
||||
var _4: [Api.Chat]?
|
||||
if let _ = reader.readInt32() {
|
||||
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
|
||||
}
|
||||
var _5: [Api.User]?
|
||||
if let _ = reader.readInt32() {
|
||||
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||
}
|
||||
var _6: Api.updates.State?
|
||||
if let signature = reader.readInt32() {
|
||||
_6 = Api.parse(reader, signature: signature) as? Api.updates.State
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
let _c6 = _6 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
|
||||
return Api.updates.Difference.difference(newMessages: _1!, newEncryptedMessages: _2!, otherUpdates: _3!, chats: _4!, users: _5!, state: _6!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_differenceEmpty(_ reader: BufferReader) -> Difference? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.updates.Difference.differenceEmpty(date: _1!, seq: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_differenceSlice(_ reader: BufferReader) -> Difference? {
|
||||
var _1: [Api.Message]?
|
||||
if let _ = reader.readInt32() {
|
||||
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self)
|
||||
}
|
||||
var _2: [Api.EncryptedMessage]?
|
||||
if let _ = reader.readInt32() {
|
||||
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.EncryptedMessage.self)
|
||||
}
|
||||
var _3: [Api.Update]?
|
||||
if let _ = reader.readInt32() {
|
||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self)
|
||||
}
|
||||
var _4: [Api.Chat]?
|
||||
if let _ = reader.readInt32() {
|
||||
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
|
||||
}
|
||||
var _5: [Api.User]?
|
||||
if let _ = reader.readInt32() {
|
||||
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||
}
|
||||
var _6: Api.updates.State?
|
||||
if let signature = reader.readInt32() {
|
||||
_6 = Api.parse(reader, signature: signature) as? Api.updates.State
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
let _c6 = _6 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
|
||||
return Api.updates.Difference.differenceSlice(newMessages: _1!, newEncryptedMessages: _2!, otherUpdates: _3!, chats: _4!, users: _5!, intermediateState: _6!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_differenceTooLong(_ reader: BufferReader) -> Difference? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.updates.Difference.differenceTooLong(pts: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,207 @@
|
|||
public extension Api.updates {
|
||||
enum Difference: TypeConstructorDescription {
|
||||
case difference(newMessages: [Api.Message], newEncryptedMessages: [Api.EncryptedMessage], otherUpdates: [Api.Update], chats: [Api.Chat], users: [Api.User], state: Api.updates.State)
|
||||
case differenceEmpty(date: Int32, seq: Int32)
|
||||
case differenceSlice(newMessages: [Api.Message], newEncryptedMessages: [Api.EncryptedMessage], otherUpdates: [Api.Update], chats: [Api.Chat], users: [Api.User], intermediateState: Api.updates.State)
|
||||
case differenceTooLong(pts: Int32)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .difference(let newMessages, let newEncryptedMessages, let otherUpdates, let chats, let users, let state):
|
||||
if boxed {
|
||||
buffer.appendInt32(16030880)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(newMessages.count))
|
||||
for item in newMessages {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(newEncryptedMessages.count))
|
||||
for item in newEncryptedMessages {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(otherUpdates.count))
|
||||
for item in otherUpdates {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(chats.count))
|
||||
for item in chats {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(users.count))
|
||||
for item in users {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
state.serialize(buffer, true)
|
||||
break
|
||||
case .differenceEmpty(let date, let seq):
|
||||
if boxed {
|
||||
buffer.appendInt32(1567990072)
|
||||
}
|
||||
serializeInt32(date, buffer: buffer, boxed: false)
|
||||
serializeInt32(seq, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .differenceSlice(let newMessages, let newEncryptedMessages, let otherUpdates, let chats, let users, let intermediateState):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1459938943)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(newMessages.count))
|
||||
for item in newMessages {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(newEncryptedMessages.count))
|
||||
for item in newEncryptedMessages {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(otherUpdates.count))
|
||||
for item in otherUpdates {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(chats.count))
|
||||
for item in chats {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(users.count))
|
||||
for item in users {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
intermediateState.serialize(buffer, true)
|
||||
break
|
||||
case .differenceTooLong(let pts):
|
||||
if boxed {
|
||||
buffer.appendInt32(1258196845)
|
||||
}
|
||||
serializeInt32(pts, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .difference(let newMessages, let newEncryptedMessages, let otherUpdates, let chats, let users, let state):
|
||||
return ("difference", [("newMessages", newMessages as Any), ("newEncryptedMessages", newEncryptedMessages as Any), ("otherUpdates", otherUpdates as Any), ("chats", chats as Any), ("users", users as Any), ("state", state as Any)])
|
||||
case .differenceEmpty(let date, let seq):
|
||||
return ("differenceEmpty", [("date", date as Any), ("seq", seq as Any)])
|
||||
case .differenceSlice(let newMessages, let newEncryptedMessages, let otherUpdates, let chats, let users, let intermediateState):
|
||||
return ("differenceSlice", [("newMessages", newMessages as Any), ("newEncryptedMessages", newEncryptedMessages as Any), ("otherUpdates", otherUpdates as Any), ("chats", chats as Any), ("users", users as Any), ("intermediateState", intermediateState as Any)])
|
||||
case .differenceTooLong(let pts):
|
||||
return ("differenceTooLong", [("pts", pts as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_difference(_ reader: BufferReader) -> Difference? {
|
||||
var _1: [Api.Message]?
|
||||
if let _ = reader.readInt32() {
|
||||
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self)
|
||||
}
|
||||
var _2: [Api.EncryptedMessage]?
|
||||
if let _ = reader.readInt32() {
|
||||
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.EncryptedMessage.self)
|
||||
}
|
||||
var _3: [Api.Update]?
|
||||
if let _ = reader.readInt32() {
|
||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self)
|
||||
}
|
||||
var _4: [Api.Chat]?
|
||||
if let _ = reader.readInt32() {
|
||||
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
|
||||
}
|
||||
var _5: [Api.User]?
|
||||
if let _ = reader.readInt32() {
|
||||
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||
}
|
||||
var _6: Api.updates.State?
|
||||
if let signature = reader.readInt32() {
|
||||
_6 = Api.parse(reader, signature: signature) as? Api.updates.State
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
let _c6 = _6 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
|
||||
return Api.updates.Difference.difference(newMessages: _1!, newEncryptedMessages: _2!, otherUpdates: _3!, chats: _4!, users: _5!, state: _6!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_differenceEmpty(_ reader: BufferReader) -> Difference? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.updates.Difference.differenceEmpty(date: _1!, seq: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_differenceSlice(_ reader: BufferReader) -> Difference? {
|
||||
var _1: [Api.Message]?
|
||||
if let _ = reader.readInt32() {
|
||||
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self)
|
||||
}
|
||||
var _2: [Api.EncryptedMessage]?
|
||||
if let _ = reader.readInt32() {
|
||||
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.EncryptedMessage.self)
|
||||
}
|
||||
var _3: [Api.Update]?
|
||||
if let _ = reader.readInt32() {
|
||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self)
|
||||
}
|
||||
var _4: [Api.Chat]?
|
||||
if let _ = reader.readInt32() {
|
||||
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
|
||||
}
|
||||
var _5: [Api.User]?
|
||||
if let _ = reader.readInt32() {
|
||||
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||
}
|
||||
var _6: Api.updates.State?
|
||||
if let signature = reader.readInt32() {
|
||||
_6 = Api.parse(reader, signature: signature) as? Api.updates.State
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
let _c6 = _6 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
|
||||
return Api.updates.Difference.differenceSlice(newMessages: _1!, newEncryptedMessages: _2!, otherUpdates: _3!, chats: _4!, users: _5!, intermediateState: _6!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_differenceTooLong(_ reader: BufferReader) -> Difference? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.updates.Difference.differenceTooLong(pts: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.updates {
|
||||
enum State: TypeConstructorDescription {
|
||||
case state(pts: Int32, qts: Int32, date: Int32, seq: Int32, unreadCount: Int32)
|
||||
|
|
|
|||
|
|
@ -9362,6 +9362,21 @@ public extension Api.functions.payments {
|
|||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.payments {
|
||||
static func checkCanSendGift(giftId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.CheckCanSendGiftResult>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-1060835895)
|
||||
serializeInt64(giftId, buffer: buffer, boxed: false)
|
||||
return (FunctionDescription(name: "payments.checkCanSendGift", parameters: [("giftId", String(describing: giftId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.CheckCanSendGiftResult? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.payments.CheckCanSendGiftResult?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.payments.CheckCanSendGiftResult
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.payments {
|
||||
static func checkGiftCode(slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.CheckedGiftCode>) {
|
||||
let buffer = Buffer()
|
||||
|
|
@ -12323,6 +12338,26 @@ public extension Api.functions.users {
|
|||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.users {
|
||||
static func getSavedMusicByID(id: Api.InputUser, documents: [Api.InputDocument]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.users.SavedMusic>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(1970513129)
|
||||
id.serialize(buffer, true)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(documents.count))
|
||||
for item in documents {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
return (FunctionDescription(name: "users.getSavedMusicByID", parameters: [("id", String(describing: id)), ("documents", String(describing: documents))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.users.SavedMusic? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.users.SavedMusic?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.users.SavedMusic
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.users {
|
||||
static func getUsers(id: [Api.InputUser]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.User]>) {
|
||||
let buffer = Buffer()
|
||||
|
|
|
|||
|
|
@ -860,7 +860,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
|||
} else {
|
||||
controllerContext = strongSelf.context.sharedContext.makeTempAccountContext(account: account)
|
||||
}
|
||||
let controller = strongSelf.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, chatLocation: .peer(id: id.messageId.peerId), type: type, initialMessageId: id.messageId, initialOrder: order, playlistLocation: playlistLocation, parentNavigationController: strongSelf.navigationController as? NavigationController)
|
||||
let controller = strongSelf.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, chatLocation: .peer(id: id.messageId.peerId), type: type, initialMessageId: id.messageId, initialOrder: order, playlistLocation: playlistLocation, parentNavigationController: strongSelf.navigationController as? NavigationController, updateMusicSaved: nil, reorderSavedMusic: nil)
|
||||
strongSelf.displayNode.view.window?.endEditing(true)
|
||||
strongSelf.present(controller, in: .window(.root))
|
||||
} else if case let .messages(chatLocation, _, _) = playlistLocation {
|
||||
|
|
@ -901,7 +901,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
|||
} else {
|
||||
controllerContext = strongSelf.context.sharedContext.makeTempAccountContext(account: account)
|
||||
}
|
||||
let controller = strongSelf.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, chatLocation: chatLocation, type: type, initialMessageId: id.messageId, initialOrder: order, playlistLocation: nil, parentNavigationController: strongSelf.navigationController as? NavigationController)
|
||||
let controller = strongSelf.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, chatLocation: chatLocation, type: type, initialMessageId: id.messageId, initialOrder: order, playlistLocation: nil, parentNavigationController: strongSelf.navigationController as? NavigationController, updateMusicSaved: nil, reorderSavedMusic: nil)
|
||||
strongSelf.displayNode.view.window?.endEditing(true)
|
||||
strongSelf.present(controller, in: .window(.root))
|
||||
} else if index.1 {
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
|
|||
guard let gift = StarGift(apiStarGift: apiGift) else {
|
||||
return nil
|
||||
}
|
||||
return TelegramMediaAction(action: .starGift(gift: gift, convertStars: convertStars, text: text, entities: entities, nameHidden: (flags & (1 << 0)) != 0, savedToProfile: (flags & (1 << 2)) != 0, converted: (flags & (1 << 3)) != 0, upgraded: (flags & (1 << 5)) != 0, canUpgrade: (flags & (1 << 10)) != 0, upgradeStars: upgradeStars, isRefunded: (flags & (1 << 9)) != 0, isPrepaidUpgrade: (flags & (1 << 13)) != 0, upgradeMessageId: upgradeMessageId, peerId: peer?.peerId, senderId: fromId?.peerId, savedId: savedId, prepaidUpgradeHash: prepaidUpgradeHash, giftMessageId: giftMessageId))
|
||||
return TelegramMediaAction(action: .starGift(gift: gift, convertStars: convertStars, text: text, entities: entities, nameHidden: (flags & (1 << 0)) != 0, savedToProfile: (flags & (1 << 2)) != 0, converted: (flags & (1 << 3)) != 0, upgraded: (flags & (1 << 5)) != 0, canUpgrade: (flags & (1 << 10)) != 0, upgradeStars: upgradeStars, isRefunded: (flags & (1 << 9)) != 0, isPrepaidUpgrade: (flags & (1 << 13)) != 0, upgradeMessageId: upgradeMessageId, peerId: peer?.peerId, senderId: fromId?.peerId, savedId: savedId, prepaidUpgradeHash: prepaidUpgradeHash, giftMessageId: giftMessageId, upgradeSeparate: (flags & (1 << 16)) != 0))
|
||||
case let .messageActionStarGiftUnique(flags, apiGift, canExportAt, transferStars, fromId, peer, savedId, resaleAmount, canTransferDate, canResaleDate):
|
||||
guard let gift = StarGift(apiStarGift: apiGift) else {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -334,6 +334,7 @@ private enum MediaReferenceRevalidationKey: Hashable {
|
|||
case customEmoji(fileId: Int64)
|
||||
case story(peer: PeerReference, id: Int32)
|
||||
case starsTransaction(transaction: StarsTransactionReference)
|
||||
case savedMusic(peer: PeerReference, fileId: Int64)
|
||||
}
|
||||
|
||||
private final class MediaReferenceRevalidationItemContext {
|
||||
|
|
@ -794,6 +795,33 @@ final class MediaReferenceRevalidationContext {
|
|||
}
|
||||
}
|
||||
|
||||
func savedMusic(accountPeerId: PeerId, postbox: Postbox, network: Network, background: Bool, peer: PeerReference, media: Media) -> Signal<TelegramMediaFile, RevalidateMediaReferenceError> {
|
||||
guard let file = media as? TelegramMediaFile else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
return self.genericItem(key: .savedMusic(peer: peer, fileId: file.fileId.id), background: background, request: { next, error in
|
||||
return (_internal_getSavedMusicById(postbox: postbox, network: network, peer: peer, file: file)
|
||||
|> castError(RevalidateMediaReferenceError.self)
|
||||
|> mapToSignal { result -> Signal<TelegramMediaFile, RevalidateMediaReferenceError> in
|
||||
if let result {
|
||||
return .single(result)
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
}).start(next: { value in
|
||||
next(value)
|
||||
}, error: { _ in
|
||||
error(.generic)
|
||||
})
|
||||
}) |> mapToSignal { next -> Signal<TelegramMediaFile, RevalidateMediaReferenceError> in
|
||||
if let next = next as? TelegramMediaFile {
|
||||
return .single(next)
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func notificationSoundList(postbox: Postbox, network: Network, background: Bool) -> Signal<[TelegramMediaFile], RevalidateMediaReferenceError> {
|
||||
return self.genericItem(key: .notificationSoundList, background: background, request: { next, error in
|
||||
return (requestNotificationSoundList(network: network, hash: 0)
|
||||
|
|
@ -993,6 +1021,14 @@ func revalidateMediaResourceReference(accountPeerId: PeerId, postbox: Postbox, n
|
|||
}
|
||||
return .fail(.generic)
|
||||
}
|
||||
case let .savedMusic(peer, media):
|
||||
return revalidationContext.savedMusic(accountPeerId: accountPeerId, postbox: postbox, network: network, background: info.preferBackgroundReferenceRevalidation, peer: peer, media: media)
|
||||
|> mapToSignal { result -> Signal<RevalidatedMediaResource, RevalidateMediaReferenceError> in
|
||||
if let updatedResource = findUpdatedMediaResource(media: result, previousMedia: media, resource: resource) {
|
||||
return .single(RevalidatedMediaResource(updatedResource: updatedResource, updatedReference: nil))
|
||||
}
|
||||
return .fail(.generic)
|
||||
}
|
||||
case let .standalone(media):
|
||||
if let file = media as? TelegramMediaFile {
|
||||
for attribute in file.attributes {
|
||||
|
|
|
|||
|
|
@ -1089,6 +1089,15 @@ public final class CachedUserData: CachedPeerData {
|
|||
public enum LinkedBotChannelId: Equatable {
|
||||
case unknown
|
||||
case known(PeerId?)
|
||||
|
||||
public var value: PeerId? {
|
||||
switch self {
|
||||
case .unknown:
|
||||
return nil
|
||||
case let .known(peerId):
|
||||
return peerId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public let about: String?
|
||||
|
|
|
|||
|
|
@ -277,6 +277,7 @@ public enum AnyMediaReference: Equatable {
|
|||
case customEmoji(media: Media)
|
||||
case story(peer: PeerReference, id: Int32, media: Media)
|
||||
case starsTransaction(transaction: StarsTransactionReference, media: Media)
|
||||
case savedMusic(peer: PeerReference, media: Media)
|
||||
|
||||
public static func ==(lhs: AnyMediaReference, rhs: AnyMediaReference) -> Bool {
|
||||
switch lhs {
|
||||
|
|
@ -352,6 +353,12 @@ public enum AnyMediaReference: Equatable {
|
|||
} else {
|
||||
return false
|
||||
}
|
||||
case let .savedMusic(lhsPeer, lhsMedia):
|
||||
if case let .savedMusic(rhsPeer, rhsMedia) = rhs, lhsPeer == rhsPeer, lhsMedia.isEqual(to: rhsMedia) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -381,6 +388,8 @@ public enum AnyMediaReference: Equatable {
|
|||
return nil
|
||||
case .starsTransaction:
|
||||
return nil
|
||||
case .savedMusic:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -434,6 +443,10 @@ public enum AnyMediaReference: Equatable {
|
|||
if let media = media as? T {
|
||||
return .starsTransaction(transaction: transaction, media: media)
|
||||
}
|
||||
case let .savedMusic(peer, media):
|
||||
if let media = media as? T {
|
||||
return .savedMusic(peer: peer, media: media)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -464,6 +477,8 @@ public enum AnyMediaReference: Equatable {
|
|||
return media
|
||||
case let .starsTransaction(_, media):
|
||||
return media
|
||||
case let .savedMusic(_, media):
|
||||
return media
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -493,6 +508,8 @@ public enum AnyMediaReference: Equatable {
|
|||
return .story(peer: peer, id: id, media: media)
|
||||
case let .starsTransaction(transaction, _):
|
||||
return .starsTransaction(transaction: transaction, media: media)
|
||||
case let .savedMusic(peer, _):
|
||||
return .savedMusic(peer: peer, media: media)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -593,6 +610,7 @@ public enum MediaReference<T: Media> {
|
|||
case customEmoji
|
||||
case story
|
||||
case starsTransaction
|
||||
case savedMusic
|
||||
}
|
||||
|
||||
case standalone(media: T)
|
||||
|
|
@ -607,6 +625,7 @@ public enum MediaReference<T: Media> {
|
|||
case customEmoji(media: T)
|
||||
case story(peer: PeerReference, id: Int32, media: T)
|
||||
case starsTransaction(transaction: StarsTransactionReference, media: T)
|
||||
case savedMusic(peer: PeerReference, media: T)
|
||||
|
||||
public init?(decoder: PostboxDecoder) {
|
||||
guard let caseIdValue = decoder.decodeOptionalInt32ForKey("_r"), let caseId = CodingCase(rawValue: caseIdValue) else {
|
||||
|
|
@ -681,6 +700,12 @@ public enum MediaReference<T: Media> {
|
|||
return nil
|
||||
}
|
||||
self = .starsTransaction(transaction: transaction, media: media)
|
||||
case .savedMusic:
|
||||
let peer = decoder.decodeObjectForKey("pr", decoder: { PeerReference(decoder: $0) }) as! PeerReference
|
||||
guard let media = decoder.decodeObjectForKey("m") as? T else {
|
||||
return nil
|
||||
}
|
||||
self = .savedMusic(peer: peer, media: media)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -730,9 +755,12 @@ public enum MediaReference<T: Media> {
|
|||
encoder.encodeInt32(CodingCase.starsTransaction.rawValue, forKey: "_r")
|
||||
encoder.encodeObject(transaction, forKey: "tr")
|
||||
encoder.encodeObject(media, forKey: "m")
|
||||
case let .savedMusic(peer, media):
|
||||
encoder.encodeInt32(CodingCase.savedMusic.rawValue, forKey: "_r")
|
||||
encoder.encodeObject(peer, forKey: "pr")
|
||||
encoder.encodeObject(media, forKey: "m")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public var abstract: AnyMediaReference {
|
||||
switch self {
|
||||
|
|
@ -760,6 +788,8 @@ public enum MediaReference<T: Media> {
|
|||
return .story(peer: peer, id: id, media: media)
|
||||
case let .starsTransaction(transaction, media):
|
||||
return .starsTransaction(transaction: transaction, media: media)
|
||||
case let .savedMusic(peer, media):
|
||||
return .savedMusic(peer: peer, media: media)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -793,6 +823,8 @@ public enum MediaReference<T: Media> {
|
|||
return media
|
||||
case let .starsTransaction(_, media):
|
||||
return media
|
||||
case let .savedMusic(_, media):
|
||||
return media
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -822,6 +854,8 @@ public enum MediaReference<T: Media> {
|
|||
return .story(peer: peer, id: id, media: media)
|
||||
case let .starsTransaction(transaction, _):
|
||||
return .starsTransaction(transaction: transaction, media: media)
|
||||
case let .savedMusic(peer, _):
|
||||
return .savedMusic(peer: peer, media: media)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -243,7 +243,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
case paymentRefunded(peerId: PeerId, currency: String, totalAmount: Int64, payload: Data?, transactionId: String)
|
||||
case giftStars(currency: String, amount: Int64, count: Int64, cryptoCurrency: String?, cryptoAmount: Int64?, transactionId: String?)
|
||||
case prizeStars(amount: Int64, isUnclaimed: Bool, boostPeerId: PeerId?, transactionId: String?, giveawayMessageId: MessageId?)
|
||||
case starGift(gift: StarGift, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool, upgraded: Bool, canUpgrade: Bool, upgradeStars: Int64?, isRefunded: Bool, isPrepaidUpgrade: Bool, upgradeMessageId: Int32?, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?, prepaidUpgradeHash: String?, giftMessageId: Int32?)
|
||||
case starGift(gift: StarGift, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool, upgraded: Bool, canUpgrade: Bool, upgradeStars: Int64?, isRefunded: Bool, isPrepaidUpgrade: Bool, upgradeMessageId: Int32?, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?, prepaidUpgradeHash: String?, giftMessageId: Int32?, upgradeSeparate: Bool)
|
||||
case starGiftUnique(gift: StarGift, isUpgrade: Bool, isTransferred: Bool, savedToProfile: Bool, canExportDate: Int32?, transferStars: Int64?, isRefunded: Bool, isPrepaidUpgrade: Bool, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?, resaleAmount: CurrencyAmount?, canTransferDate: Int32?, canResaleDate: Int32?)
|
||||
case paidMessagesRefunded(count: Int32, stars: Int64)
|
||||
case paidMessagesPriceEdited(stars: Int64, broadcastMessagesAllowed: Bool)
|
||||
|
|
@ -375,7 +375,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
}
|
||||
self = .prizeStars(amount: decoder.decodeInt64ForKey("amount", orElse: 0), isUnclaimed: decoder.decodeBoolForKey("unclaimed", orElse: false), boostPeerId: boostPeerId, transactionId: decoder.decodeOptionalStringForKey("transactionId"), giveawayMessageId: giveawayMessageId)
|
||||
case 44:
|
||||
self = .starGift(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, convertStars: decoder.decodeOptionalInt64ForKey("convertStars"), text: decoder.decodeOptionalStringForKey("text"), entities: decoder.decodeOptionalObjectArrayWithDecoderForKey("entities"), nameHidden: decoder.decodeBoolForKey("nameHidden", orElse: false), savedToProfile: decoder.decodeBoolForKey("savedToProfile", orElse: false), converted: decoder.decodeBoolForKey("converted", orElse: false), upgraded: decoder.decodeBoolForKey("upgraded", orElse: false), canUpgrade: decoder.decodeBoolForKey("canUpgrade", orElse: false), upgradeStars: decoder.decodeOptionalInt64ForKey("upgradeStars"), isRefunded: decoder.decodeBoolForKey("isRefunded", orElse: false), isPrepaidUpgrade: decoder.decodeBoolForKey("isPrepaidUpgrade", orElse: false), upgradeMessageId: decoder.decodeOptionalInt32ForKey("upgradeMessageId"), peerId: decoder.decodeOptionalInt64ForKey("peerId").flatMap { EnginePeer.Id($0) }, senderId: decoder.decodeOptionalInt64ForKey("senderId").flatMap { EnginePeer.Id($0) }, savedId: decoder.decodeOptionalInt64ForKey("savedId"), prepaidUpgradeHash: decoder.decodeOptionalStringForKey("prepaidUpgradeHash"), giftMessageId: decoder.decodeOptionalInt32ForKey("giftMessageId"))
|
||||
self = .starGift(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, convertStars: decoder.decodeOptionalInt64ForKey("convertStars"), text: decoder.decodeOptionalStringForKey("text"), entities: decoder.decodeOptionalObjectArrayWithDecoderForKey("entities"), nameHidden: decoder.decodeBoolForKey("nameHidden", orElse: false), savedToProfile: decoder.decodeBoolForKey("savedToProfile", orElse: false), converted: decoder.decodeBoolForKey("converted", orElse: false), upgraded: decoder.decodeBoolForKey("upgraded", orElse: false), canUpgrade: decoder.decodeBoolForKey("canUpgrade", orElse: false), upgradeStars: decoder.decodeOptionalInt64ForKey("upgradeStars"), isRefunded: decoder.decodeBoolForKey("isRefunded", orElse: false), isPrepaidUpgrade: decoder.decodeBoolForKey("isPrepaidUpgrade", orElse: false), upgradeMessageId: decoder.decodeOptionalInt32ForKey("upgradeMessageId"), peerId: decoder.decodeOptionalInt64ForKey("peerId").flatMap { EnginePeer.Id($0) }, senderId: decoder.decodeOptionalInt64ForKey("senderId").flatMap { EnginePeer.Id($0) }, savedId: decoder.decodeOptionalInt64ForKey("savedId"), prepaidUpgradeHash: decoder.decodeOptionalStringForKey("prepaidUpgradeHash"), giftMessageId: decoder.decodeOptionalInt32ForKey("giftMessageId"), upgradeSeparate: decoder.decodeOptionalBoolForKey("upgradeSeparate") ?? false)
|
||||
case 45:
|
||||
var resaleAmount: CurrencyAmount?
|
||||
if let amount = decoder.decodeCodable(CurrencyAmount.self, forKey: "resaleAmount") {
|
||||
|
|
@ -705,7 +705,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
} else {
|
||||
encoder.encodeNil(forKey: "giveawayMsgId")
|
||||
}
|
||||
case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, isRefunded, isPrepaidUpgrade, upgradeMessageId, peerId, senderId, savedId, prepaidUpgradeHash, giftMessageId):
|
||||
case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, isRefunded, isPrepaidUpgrade, upgradeMessageId, peerId, senderId, savedId, prepaidUpgradeHash, giftMessageId, upgradeSeparate):
|
||||
encoder.encodeInt32(44, forKey: "_rawValue")
|
||||
encoder.encodeObject(gift, forKey: "gift")
|
||||
if let convertStars {
|
||||
|
|
@ -762,6 +762,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
} else {
|
||||
encoder.encodeNil(forKey: "giftMessageId")
|
||||
}
|
||||
encoder.encodeBool(upgradeSeparate, forKey: "upgradeSeparate")
|
||||
case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded, isPrepaidUpgrade, peerId, senderId, savedId, resaleAmount, canTransferDate, canResaleDate):
|
||||
encoder.encodeInt32(45, forKey: "_rawValue")
|
||||
encoder.encodeObject(gift, forKey: "gift")
|
||||
|
|
@ -887,7 +888,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
return [peerId]
|
||||
case let .prizeStars(_, _, boostPeerId, _, _):
|
||||
return boostPeerId.flatMap { [$0] } ?? []
|
||||
case let .starGift(gift, _, _, _, _, _, _, _, _, _, _, _, _, peerId, senderId, _, _, _):
|
||||
case let .starGift(gift, _, _, _, _, _, _, _, _, _, _, _, _, peerId, senderId, _, _, _, _):
|
||||
var peerIds: [PeerId] = []
|
||||
if let peerId {
|
||||
peerIds.append(peerId)
|
||||
|
|
|
|||
|
|
@ -2415,6 +2415,36 @@ public extension TelegramEngine.EngineData.Item {
|
|||
}
|
||||
}
|
||||
|
||||
public struct ProfileMainTab: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
|
||||
public typealias Result = Optional<TelegramProfileTab>
|
||||
|
||||
fileprivate var id: EnginePeer.Id
|
||||
public var mapKey: EnginePeer.Id {
|
||||
return self.id
|
||||
}
|
||||
|
||||
public init(id: EnginePeer.Id) {
|
||||
self.id = id
|
||||
}
|
||||
|
||||
var key: PostboxViewKey {
|
||||
return .cachedPeerData(peerId: self.id)
|
||||
}
|
||||
|
||||
func extract(view: PostboxView) -> Result {
|
||||
guard let view = view as? CachedPeerDataView else {
|
||||
preconditionFailure()
|
||||
}
|
||||
if let cachedData = view.cachedPeerData as? CachedUserData {
|
||||
return cachedData.mainProfileTab
|
||||
} else if let cachedData = view.cachedPeerData as? CachedChannelData {
|
||||
return cachedData.mainProfileTab
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct BotPrivacyPolicyUrl: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
|
||||
public typealias Result = Optional<String>
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ public enum AppStoreTransactionPurpose {
|
|||
case gift(peerId: EnginePeer.Id, currency: String, amount: Int64)
|
||||
case giftCode(peerIds: [EnginePeer.Id], boostPeer: EnginePeer.Id?, currency: String, amount: Int64, text: String?, entities: [MessageTextEntity]?)
|
||||
case giveaway(boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64)
|
||||
case stars(count: Int64, currency: String, amount: Int64)
|
||||
case stars(count: Int64, currency: String, amount: Int64, peerId: EnginePeer.Id?)
|
||||
case starsGift(peerId: EnginePeer.Id, count: Int64, currency: String, amount: Int64)
|
||||
case starsGiveaway(stars: Int64, boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64, users: Int32)
|
||||
case authCode(restore: Bool, phoneNumber: String, phoneCodeHash: String, currency: String, amount: Int64)
|
||||
|
|
@ -99,10 +99,26 @@ private func apiInputStorePaymentPurpose(postbox: Postbox, purpose: AppStoreTran
|
|||
return .single(.inputStorePaymentPremiumGiveaway(flags: flags, boostPeer: apiBoostPeer, additionalPeers: additionalPeers, countriesIso2: countries, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount))
|
||||
}
|
||||
|> switchToLatest
|
||||
case let .stars(count, currency, amount):
|
||||
return .single(.inputStorePaymentStarsTopup(stars: count, currency: currency, amount: amount))
|
||||
case let .stars(count, currency, amount, peerId):
|
||||
let peerSignal: Signal<Api.InputPeer?, NoError>
|
||||
if let peerId {
|
||||
peerSignal = postbox.loadedPeerWithId(peerId)
|
||||
|> map { peer in
|
||||
return apiInputPeer(peer)
|
||||
}
|
||||
} else {
|
||||
peerSignal = .single(nil)
|
||||
}
|
||||
return peerSignal
|
||||
|> map { spendPurposePeer in
|
||||
var flags: Int32 = 0
|
||||
if let _ = spendPurposePeer {
|
||||
flags |= (1 << 0)
|
||||
}
|
||||
return .inputStorePaymentStarsTopup(flags: flags, stars: count, currency: currency, amount: amount, spendPurposePeer: spendPurposePeer)
|
||||
}
|
||||
case let .starsGift(peerId, count, currency, amount):
|
||||
return postbox.loadedPeerWithId(peerId)
|
||||
return postbox.loadedPeerWithId(peerId)
|
||||
|> mapToSignal { peer -> Signal<Api.InputStorePaymentPurpose, NoError> in
|
||||
guard let inputUser = apiInputUser(peer) else {
|
||||
return .complete()
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ public enum BotPaymentInvoiceSource {
|
|||
case slug(String)
|
||||
case premiumGiveaway(boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64, option: PremiumGiftCodeOption)
|
||||
case giftCode(users: [PeerId], currency: String, amount: Int64, option: PremiumGiftCodeOption, text: String?, entities: [MessageTextEntity]?)
|
||||
case stars(option: StarsTopUpOption)
|
||||
case stars(option: StarsTopUpOption, peerId: EnginePeer.Id?)
|
||||
case starsGift(peerId: EnginePeer.Id, count: Int64, currency: String, amount: Int64)
|
||||
case starsChatSubscription(hash: String)
|
||||
case starsGiveaway(stars: Int64, boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64, users: Int32)
|
||||
|
|
@ -325,12 +325,14 @@ func _internal_parseInputInvoice(transaction: Transaction, source: BotPaymentInv
|
|||
let option: Api.PremiumGiftCodeOption = .premiumGiftCodeOption(flags: flags, users: option.users, months: option.months, storeProduct: option.storeProductId, storeQuantity: option.storeQuantity, currency: option.currency, amount: option.amount)
|
||||
|
||||
return .inputInvoicePremiumGiftCode(purpose: inputPurpose, option: option)
|
||||
case let .stars(option):
|
||||
case let .stars(option, peerId):
|
||||
var flags: Int32 = 0
|
||||
if let _ = option.storeProductId {
|
||||
var spendPurposePeer: Api.InputPeer?
|
||||
if let peerId, let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||
flags |= (1 << 0)
|
||||
spendPurposePeer = inputPeer
|
||||
}
|
||||
return .inputInvoiceStars(purpose: .inputStorePaymentStarsTopup(stars: option.count, currency: option.currency, amount: option.amount))
|
||||
return .inputInvoiceStars(purpose: .inputStorePaymentStarsTopup(flags: flags, stars: option.count, currency: option.currency, amount: option.amount, spendPurposePeer: spendPurposePeer))
|
||||
case let .starsGift(peerId, count, currency, amount):
|
||||
guard let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) else {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
case upgradeStars
|
||||
case releasedBy
|
||||
case perUserLimit
|
||||
case lockedUntilDate
|
||||
}
|
||||
|
||||
public struct Availability: Equatable, Codable, PostboxCoding {
|
||||
|
|
@ -179,8 +180,9 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
public let upgradeStars: Int64?
|
||||
public let releasedBy: EnginePeer.Id?
|
||||
public let perUserLimit: PerUserLimit?
|
||||
public let lockedUntilDate: Int32?
|
||||
|
||||
public init(id: Int64, title: String?, file: TelegramMediaFile, price: Int64, convertStars: Int64, availability: Availability?, soldOut: SoldOut?, flags: Flags, upgradeStars: Int64?, releasedBy: EnginePeer.Id?, perUserLimit: PerUserLimit?) {
|
||||
public init(id: Int64, title: String?, file: TelegramMediaFile, price: Int64, convertStars: Int64, availability: Availability?, soldOut: SoldOut?, flags: Flags, upgradeStars: Int64?, releasedBy: EnginePeer.Id?, perUserLimit: PerUserLimit?, lockedUntilDate: Int32?) {
|
||||
self.id = id
|
||||
self.title = title
|
||||
self.file = file
|
||||
|
|
@ -192,6 +194,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
self.upgradeStars = upgradeStars
|
||||
self.releasedBy = releasedBy
|
||||
self.perUserLimit = perUserLimit
|
||||
self.lockedUntilDate = lockedUntilDate
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
|
|
@ -213,6 +216,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
self.upgradeStars = try container.decodeIfPresent(Int64.self, forKey: .upgradeStars)
|
||||
self.releasedBy = try container.decodeIfPresent(EnginePeer.Id.self, forKey: .releasedBy)
|
||||
self.perUserLimit = try container.decodeIfPresent(PerUserLimit.self, forKey: .perUserLimit)
|
||||
self.lockedUntilDate = try container.decodeIfPresent(Int32.self, forKey: .lockedUntilDate)
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
|
|
@ -227,6 +231,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
self.upgradeStars = decoder.decodeOptionalInt64ForKey(CodingKeys.upgradeStars.rawValue)
|
||||
self.releasedBy = decoder.decodeOptionalInt64ForKey(CodingKeys.releasedBy.rawValue).flatMap { EnginePeer.Id($0) }
|
||||
self.perUserLimit = decoder.decodeObjectForKey(CodingKeys.perUserLimit.rawValue, decoder: { StarGift.Gift.PerUserLimit(decoder: $0) }) as? StarGift.Gift.PerUserLimit
|
||||
self.lockedUntilDate = decoder.decodeOptionalInt32ForKey(CodingKeys.lockedUntilDate.rawValue)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
|
|
@ -247,6 +252,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
try container.encodeIfPresent(self.upgradeStars, forKey: .upgradeStars)
|
||||
try container.encodeIfPresent(self.releasedBy, forKey: .releasedBy)
|
||||
try container.encodeIfPresent(self.perUserLimit, forKey: .perUserLimit)
|
||||
try container.encodeIfPresent(self.lockedUntilDate, forKey: .lockedUntilDate)
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
|
|
@ -285,6 +291,11 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
} else {
|
||||
encoder.encodeNil(forKey: CodingKeys.perUserLimit.rawValue)
|
||||
}
|
||||
if let lockedUntilDate = self.lockedUntilDate {
|
||||
encoder.encodeInt32(lockedUntilDate, forKey: CodingKeys.lockedUntilDate.rawValue)
|
||||
} else {
|
||||
encoder.encodeNil(forKey: CodingKeys.lockedUntilDate.rawValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -843,7 +854,7 @@ public extension StarGift {
|
|||
extension StarGift {
|
||||
init?(apiStarGift: Api.StarGift) {
|
||||
switch apiStarGift {
|
||||
case let .starGift(apiFlags, id, sticker, stars, availabilityRemains, availabilityTotal, availabilityResale, convertStars, firstSale, lastSale, upgradeStars, minResaleStars, title, releasedBy, perUserTotal, perUserRemains):
|
||||
case let .starGift(apiFlags, id, sticker, stars, availabilityRemains, availabilityTotal, availabilityResale, convertStars, firstSale, lastSale, upgradeStars, minResaleStars, title, releasedBy, perUserTotal, perUserRemains, lockedUntilDate):
|
||||
var flags = StarGift.Gift.Flags()
|
||||
if (apiFlags & (1 << 2)) != 0 {
|
||||
flags.insert(.isBirthdayGift)
|
||||
|
|
@ -872,7 +883,7 @@ extension StarGift {
|
|||
guard let file = telegramMediaFileFromApiDocument(sticker, altDocuments: nil) else {
|
||||
return nil
|
||||
}
|
||||
self = .generic(StarGift.Gift(id: id, title: title, file: file, price: stars, convertStars: convertStars, availability: availability, soldOut: soldOut, flags: flags, upgradeStars: upgradeStars, releasedBy: releasedBy?.peerId, perUserLimit: perUserLimit))
|
||||
self = .generic(StarGift.Gift(id: id, title: title, file: file, price: stars, convertStars: convertStars, availability: availability, soldOut: soldOut, flags: flags, upgradeStars: upgradeStars, releasedBy: releasedBy?.peerId, perUserLimit: perUserLimit, lockedUntilDate: lockedUntilDate))
|
||||
case let .starGiftUnique(flags, id, giftId, title, slug, num, ownerPeerId, ownerName, ownerAddress, attributes, availabilityIssued, availabilityTotal, giftAddress, resellAmounts, releasedBy, valueAmount, valueCurrency):
|
||||
let owner: StarGift.UniqueGift.Owner
|
||||
if let ownerAddress {
|
||||
|
|
@ -1182,7 +1193,8 @@ func _internal_upgradeStarGift(account: Account, formId: Int64?, reference: Star
|
|||
canTransferDate: canTransferDate,
|
||||
canResaleDate: canResaleDate,
|
||||
collectionIds: nil,
|
||||
prepaidUpgradeHash: nil
|
||||
prepaidUpgradeHash: nil,
|
||||
upgradeSeparate: false
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
@ -1214,6 +1226,34 @@ func _internal_starGiftUpgradePreview(account: Account, giftId: Int64) -> Signal
|
|||
}
|
||||
}
|
||||
|
||||
public enum CanSendGiftResult {
|
||||
case available
|
||||
case unavailable(text: String, entities: [MessageTextEntity])
|
||||
case failed
|
||||
}
|
||||
|
||||
func _internal_checkCanSendStarGift(account: Account, giftId: Int64) -> Signal<CanSendGiftResult, NoError> {
|
||||
return account.network.request(Api.functions.payments.checkCanSendGift(giftId: giftId))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.payments.CheckCanSendGiftResult?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> map { result in
|
||||
guard let result else {
|
||||
return .unavailable(text: "", entities: [])
|
||||
}
|
||||
switch result {
|
||||
case .checkCanSendGiftResultOk:
|
||||
return .available
|
||||
case let .checkCanSendGiftResultFail(reason):
|
||||
switch reason {
|
||||
case let .textWithEntities(text, entities):
|
||||
return .unavailable(text: text, entities: messageTextEntitiesFromApiEntities(entities))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class CachedProfileGifts: Codable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case gifts
|
||||
|
|
@ -2037,6 +2077,7 @@ public final class ProfileGiftsContext {
|
|||
case canResaleDate
|
||||
case collectionIds
|
||||
case prepaidUpgradeHash
|
||||
case upgradeSeparate
|
||||
}
|
||||
|
||||
public let gift: TelegramCore.StarGift
|
||||
|
|
@ -2057,6 +2098,7 @@ public final class ProfileGiftsContext {
|
|||
public let canResaleDate: Int32?
|
||||
public let collectionIds: [Int32]?
|
||||
public let prepaidUpgradeHash: String?
|
||||
public let upgradeSeparate: Bool
|
||||
|
||||
fileprivate let _fromPeerId: EnginePeer.Id?
|
||||
|
||||
|
|
@ -2082,7 +2124,8 @@ public final class ProfileGiftsContext {
|
|||
canTransferDate: Int32?,
|
||||
canResaleDate: Int32?,
|
||||
collectionIds: [Int32]?,
|
||||
prepaidUpgradeHash: String?
|
||||
prepaidUpgradeHash: String?,
|
||||
upgradeSeparate: Bool
|
||||
) {
|
||||
self.gift = gift
|
||||
self.reference = reference
|
||||
|
|
@ -2103,6 +2146,7 @@ public final class ProfileGiftsContext {
|
|||
self.canResaleDate = canResaleDate
|
||||
self.collectionIds = collectionIds
|
||||
self.prepaidUpgradeHash = prepaidUpgradeHash
|
||||
self.upgradeSeparate = upgradeSeparate
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
|
|
@ -2133,6 +2177,7 @@ public final class ProfileGiftsContext {
|
|||
self.canResaleDate = try container.decodeIfPresent(Int32.self, forKey: .canResaleDate)
|
||||
self.collectionIds = try container.decodeIfPresent([Int32].self, forKey: .collectionIds)
|
||||
self.prepaidUpgradeHash = try container.decodeIfPresent(String.self, forKey: .prepaidUpgradeHash)
|
||||
self.upgradeSeparate = try container.decodeIfPresent(Bool.self, forKey: .upgradeSeparate) ?? false
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
|
|
@ -2156,6 +2201,7 @@ public final class ProfileGiftsContext {
|
|||
try container.encodeIfPresent(self.canResaleDate, forKey: .canResaleDate)
|
||||
try container.encodeIfPresent(self.collectionIds, forKey: .collectionIds)
|
||||
try container.encodeIfPresent(self.prepaidUpgradeHash, forKey: .prepaidUpgradeHash)
|
||||
try container.encode(self.upgradeSeparate, forKey: .upgradeSeparate)
|
||||
}
|
||||
|
||||
public func withGift(_ gift: TelegramCore.StarGift) -> StarGift {
|
||||
|
|
@ -2177,7 +2223,8 @@ public final class ProfileGiftsContext {
|
|||
canTransferDate: self.canTransferDate,
|
||||
canResaleDate: self.canResaleDate,
|
||||
collectionIds: self.collectionIds,
|
||||
prepaidUpgradeHash: self.prepaidUpgradeHash
|
||||
prepaidUpgradeHash: self.prepaidUpgradeHash,
|
||||
upgradeSeparate: self.upgradeSeparate
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -2200,7 +2247,8 @@ public final class ProfileGiftsContext {
|
|||
canTransferDate: self.canTransferDate,
|
||||
canResaleDate: self.canResaleDate,
|
||||
collectionIds: self.collectionIds,
|
||||
prepaidUpgradeHash: self.prepaidUpgradeHash
|
||||
prepaidUpgradeHash: self.prepaidUpgradeHash,
|
||||
upgradeSeparate: self.upgradeSeparate
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -2223,7 +2271,8 @@ public final class ProfileGiftsContext {
|
|||
canTransferDate: self.canTransferDate,
|
||||
canResaleDate: self.canResaleDate,
|
||||
collectionIds: self.collectionIds,
|
||||
prepaidUpgradeHash: self.prepaidUpgradeHash
|
||||
prepaidUpgradeHash: self.prepaidUpgradeHash,
|
||||
upgradeSeparate: self.upgradeSeparate
|
||||
)
|
||||
}
|
||||
fileprivate func withFromPeer(_ fromPeer: EnginePeer?) -> StarGift {
|
||||
|
|
@ -2245,7 +2294,8 @@ public final class ProfileGiftsContext {
|
|||
canTransferDate: self.canTransferDate,
|
||||
canResaleDate: self.canResaleDate,
|
||||
collectionIds: self.collectionIds,
|
||||
prepaidUpgradeHash: self.prepaidUpgradeHash
|
||||
prepaidUpgradeHash: self.prepaidUpgradeHash,
|
||||
upgradeSeparate: self.upgradeSeparate
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -2268,7 +2318,8 @@ public final class ProfileGiftsContext {
|
|||
canTransferDate: self.canTransferDate,
|
||||
canResaleDate: self.canResaleDate,
|
||||
collectionIds: collectionIds,
|
||||
prepaidUpgradeHash: self.prepaidUpgradeHash
|
||||
prepaidUpgradeHash: self.prepaidUpgradeHash,
|
||||
upgradeSeparate: self.upgradeSeparate
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2519,6 +2570,7 @@ extension ProfileGiftsContext.State.StarGift {
|
|||
self.canResaleDate = canResaleAt
|
||||
self.collectionIds = collectionIds
|
||||
self.prepaidUpgradeHash = prepaidUpgradeHash
|
||||
self.upgradeSeparate = (flags & (1 << 17)) != 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2560,9 +2612,9 @@ func _internal_getUniqueStarGift(account: Account, slug: String) -> Signal<StarG
|
|||
|> mapToSignal { result -> Signal<StarGift.UniqueGift?, NoError> in
|
||||
if let result = result {
|
||||
switch result {
|
||||
case let .uniqueStarGift(gift, users):
|
||||
case let .uniqueStarGift(gift, chats, users):
|
||||
return account.postbox.transaction { transaction in
|
||||
let parsedPeers = AccumulatedPeers(users: users)
|
||||
let parsedPeers = AccumulatedPeers(chats: chats, users: users)
|
||||
updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: parsedPeers)
|
||||
guard case let .unique(uniqueGift) = StarGift(apiStarGift: gift) else {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -1654,7 +1654,8 @@ func _internal_sendStarsPaymentForm(account: Account, formId: Int64, source: Bot
|
|||
canTransferDate: canTransferDate,
|
||||
canResaleDate: canResaleDate,
|
||||
collectionIds: nil,
|
||||
prepaidUpgradeHash: nil
|
||||
prepaidUpgradeHash: nil,
|
||||
upgradeSeparate: false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,6 +141,10 @@ public extension TelegramEngine {
|
|||
return _internal_starGiftUpgradePreview(account: self.account, giftId: giftId)
|
||||
}
|
||||
|
||||
public func checkCanSendStarGift(giftId: Int64) -> Signal<CanSendGiftResult, NoError> {
|
||||
return _internal_checkCanSendStarGift(account: self.account, giftId: giftId)
|
||||
}
|
||||
|
||||
public func getUniqueStarGift(slug: String) -> Signal<StarGift.UniqueGift?, NoError> {
|
||||
return _internal_getUniqueStarGift(account: self.account, slug: slug)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,31 @@ public final class SavedMusicIdsList: Codable, Equatable {
|
|||
}
|
||||
}
|
||||
|
||||
func _internal_getSavedMusicById(postbox: Postbox, network: Network, peer: PeerReference, file: TelegramMediaFile) -> Signal<TelegramMediaFile?, NoError> {
|
||||
let inputUser = peer.inputUser
|
||||
guard let inputUser, let resource = file.resource as? CloudDocumentMediaResource else {
|
||||
return .single(nil)
|
||||
}
|
||||
return network.request(Api.functions.users.getSavedMusicByID(id: inputUser, documents: [.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference))]))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.users.SavedMusic?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> map { result -> TelegramMediaFile? in
|
||||
if let result {
|
||||
switch result {
|
||||
case let .savedMusic(_, documents):
|
||||
if let file = documents.first.flatMap({ telegramMediaFileFromApiDocument($0, altDocuments: nil) }) {
|
||||
return file
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_savedMusicIds(postbox: Postbox) -> Signal<Set<Int64>?, NoError> {
|
||||
let viewKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.savedMusicIds()]))
|
||||
return postbox.combinedView(keys: [viewKey])
|
||||
|
|
@ -102,6 +127,7 @@ func _internal_addSavedMusic(account: Account, file: FileMediaReference, afterFi
|
|||
return account.postbox.transaction { transaction in
|
||||
if let cachedSavedMusic = transaction.retrieveItemCacheEntry(id: entryId(peerId: account.peerId))?.get(CachedProfileSavedMusic.self) {
|
||||
var updatedFiles = cachedSavedMusic.files
|
||||
updatedFiles.removeAll(where: { $0.fileId == file.media.fileId })
|
||||
if let afterFile, let index = updatedFiles.firstIndex(where: { $0.fileId == afterFile.media.fileId }) {
|
||||
updatedFiles.insert(file.media, at: index + 1)
|
||||
} else {
|
||||
|
|
@ -119,15 +145,17 @@ func _internal_addSavedMusic(account: Account, file: FileMediaReference, afterFi
|
|||
transaction.setPreferencesEntry(key: PreferencesKeys.savedMusicIds(), value: PreferencesEntry(savedMusicIdsList))
|
||||
}
|
||||
|
||||
transaction.updatePeerCachedData(peerIds: Set([account.peerId]), update: { _, cachedData -> CachedPeerData? in
|
||||
if let cachedData = cachedData as? CachedUserData {
|
||||
var updatedData = cachedData
|
||||
updatedData = updatedData.withUpdatedSavedMusic(file.media)
|
||||
return updatedData
|
||||
} else {
|
||||
return cachedData
|
||||
}
|
||||
})
|
||||
if afterFile == nil {
|
||||
transaction.updatePeerCachedData(peerIds: Set([account.peerId]), update: { _, cachedData -> CachedPeerData? in
|
||||
if let cachedData = cachedData as? CachedUserData {
|
||||
var updatedData = cachedData
|
||||
updatedData = updatedData.withUpdatedSavedMusic(file.media)
|
||||
return updatedData
|
||||
} else {
|
||||
return cachedData
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return revalidatedMusic(account: account, file: file, signal: { resource in
|
||||
var flags: Int32 = 0
|
||||
|
|
@ -258,7 +286,7 @@ public final class ProfileSavedMusicContext {
|
|||
self.account = account
|
||||
self.peerId = peerId
|
||||
|
||||
self.reload()
|
||||
self.loadMore()
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
|
@ -342,36 +370,33 @@ public final class ProfileSavedMusicContext {
|
|||
}))
|
||||
}
|
||||
|
||||
public func addMusic(file: FileMediaReference, afterFile: FileMediaReference? = nil) -> Signal<Never, AddSavedMusicError> {
|
||||
return _internal_addSavedMusic(account: self.account, file: file, afterFile: nil)
|
||||
|> afterCompleted { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if let afterFile, let index = self.files.firstIndex(where: { $0.fileId == afterFile.media.fileId }) {
|
||||
self.files.insert(file.media, at: index + 1)
|
||||
} else {
|
||||
self.files.insert(file.media, at: 0)
|
||||
}
|
||||
if let count = self.count {
|
||||
self.count = count + 1
|
||||
}
|
||||
self.pushState()
|
||||
public func addMusic(file: FileMediaReference, afterFile: FileMediaReference? = nil, apply: Bool = true) -> Signal<Never, AddSavedMusicError> {
|
||||
self.files.removeAll(where: { $0.fileId == file.media.fileId })
|
||||
if let afterFile, let index = self.files.firstIndex(where: { $0.fileId == afterFile.media.fileId }) {
|
||||
self.files.insert(file.media, at: index + 1)
|
||||
} else {
|
||||
self.files.insert(file.media, at: 0)
|
||||
}
|
||||
if let count = self.count {
|
||||
self.count = count + 1
|
||||
}
|
||||
self.pushState()
|
||||
|
||||
if apply {
|
||||
return _internal_addSavedMusic(account: self.account, file: file, afterFile: afterFile)
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
|
||||
public func removeMusic(file: FileMediaReference) -> Signal<Never, NoError> {
|
||||
return _internal_removeSavedMusic(account: self.account, file: file)
|
||||
|> afterCompleted { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.files.removeAll(where: { $0.fileId == file.media.id })
|
||||
if let count = self.count {
|
||||
self.count = max(0, count - 1)
|
||||
}
|
||||
self.pushState()
|
||||
self.files.removeAll(where: { $0.fileId == file.media.id })
|
||||
if let count = self.count {
|
||||
self.count = max(0, count - 1)
|
||||
}
|
||||
self.pushState()
|
||||
|
||||
return _internal_removeSavedMusic(account: self.account, file: file)
|
||||
}
|
||||
|
||||
private func pushState() {
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ public func webpagePreviewWithProgress(account: Account, urls: [String], webpage
|
|||
|
||||
return account.network.requestWithAdditionalInfo(Api.functions.messages.getWebPagePreview(flags: 0, message: urls.joined(separator: " "), entities: nil), info: .progress)
|
||||
|> `catch` { _ -> Signal<NetworkRequestResult<Api.messages.WebPagePreview>, NoError> in
|
||||
return .single(.result(.webPagePreview(media: .messageMediaEmpty, users: [])))
|
||||
return .single(.result(.webPagePreview(media: .messageMediaEmpty, chats: [], users: [])))
|
||||
}
|
||||
|> mapToSignal { result -> Signal<WebpagePreviewWithProgressResult, NoError> in
|
||||
switch result {
|
||||
|
|
@ -170,17 +170,17 @@ public func webpagePreviewWithProgress(account: Account, urls: [String], webpage
|
|||
return .complete()
|
||||
}
|
||||
case let .result(result):
|
||||
if case let .webPagePreview(result, _) = result, let preCachedResources = result.preCachedResources {
|
||||
if case let .webPagePreview(result, _, _) = result, let preCachedResources = result.preCachedResources {
|
||||
for (resource, data) in preCachedResources {
|
||||
account.postbox.mediaBox.storeResourceData(resource.id, data: data)
|
||||
}
|
||||
}
|
||||
switch result {
|
||||
case let .webPagePreview(media, users):
|
||||
case let .webPagePreview(media, chats, users):
|
||||
switch media {
|
||||
case let .messageMediaWebPage(_, webpage):
|
||||
return account.postbox.transaction { transaction -> Signal<WebpagePreviewWithProgressResult, NoError> in
|
||||
let peers = AccumulatedPeers(users: users)
|
||||
let peers = AccumulatedPeers(chats: chats, users: users)
|
||||
updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: peers)
|
||||
|
||||
if let media = telegramMediaWebpageFromApiWebpage(webpage), let url = media.content.url {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,27 @@ private let animationDelay: TimeInterval = 2.5
|
|||
private let spacing: CGFloat = 20.0
|
||||
|
||||
public final class MarqueeComponent: Component {
|
||||
let attributedText: NSAttributedString
|
||||
let maxWidth: CGFloat?
|
||||
|
||||
public init(
|
||||
attributedText: NSAttributedString,
|
||||
maxWidth: CGFloat? = nil
|
||||
) {
|
||||
self.attributedText = attributedText
|
||||
self.maxWidth = maxWidth
|
||||
}
|
||||
|
||||
public static func ==(lhs: MarqueeComponent, rhs: MarqueeComponent) -> Bool {
|
||||
if lhs.attributedText != rhs.attributedText {
|
||||
return false
|
||||
}
|
||||
if lhs.maxWidth != rhs.maxWidth {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public static let innerPadding: CGFloat = 16.0
|
||||
|
||||
private final class MeasureState: Equatable {
|
||||
|
|
@ -41,7 +62,9 @@ public final class MarqueeComponent: Component {
|
|||
private let containerLayer = SimpleLayer()
|
||||
private let textLayer = SimpleLayer()
|
||||
private let duplicateTextLayer = SimpleLayer()
|
||||
private let maskContainerLayer = SimpleLayer()
|
||||
private let gradientMaskLayer = SimpleGradientLayer()
|
||||
private let solidEdgeMaskLayer = SimpleLayer()
|
||||
private var isAnimating = false
|
||||
private var isOverflowing = false
|
||||
|
||||
|
|
@ -56,6 +79,9 @@ public final class MarqueeComponent: Component {
|
|||
|
||||
self.containerLayer.addSublayer(self.textLayer)
|
||||
self.containerLayer.addSublayer(self.duplicateTextLayer)
|
||||
|
||||
self.maskContainerLayer.addSublayer(self.gradientMaskLayer)
|
||||
self.maskContainerLayer.addSublayer(self.solidEdgeMaskLayer)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
|
|
@ -66,6 +92,11 @@ public final class MarqueeComponent: Component {
|
|||
let previousComponent = self.component
|
||||
self.component = component
|
||||
|
||||
var availableSize = availableSize
|
||||
if let maxWidth = component.maxWidth {
|
||||
availableSize.width = maxWidth
|
||||
}
|
||||
|
||||
let attributedText = component.attributedText
|
||||
if let measureState = self.measureState {
|
||||
if measureState.attributedText.isEqual(to: attributedText) && measureState.availableSize == availableSize {
|
||||
|
|
@ -97,27 +128,34 @@ public final class MarqueeComponent: Component {
|
|||
self.startAnimation(force: previousComponent?.attributedText != attributedText)
|
||||
} else {
|
||||
self.stopAnimation()
|
||||
self.textLayer.frame = CGRect(origin: CGPoint(x: innerPadding, y: 0.0), size: boundingRect.size)
|
||||
self.textLayer.frame = CGRect(origin: .zero, size: boundingRect.size)
|
||||
self.textLayer.contents = image.cgImage
|
||||
self.duplicateTextLayer.frame = .zero
|
||||
self.duplicateTextLayer.contents = nil
|
||||
self.layer.mask = nil
|
||||
}
|
||||
|
||||
return CGSize(width: min(measureState.size.width + innerPadding * 2.0, availableSize.width), height: measureState.size.height)
|
||||
return CGSize(width: min(measureState.size.width, availableSize.width), height: measureState.size.height)
|
||||
}
|
||||
|
||||
private func setupMarqueeTextLayers(textImage: CGImage, textWidth: CGFloat, containerWidth: CGFloat) {
|
||||
self.textLayer.frame = CGRect(x: innerPadding, y: 0, width: textWidth, height: self.containerLayer.bounds.height)
|
||||
self.textLayer.frame = CGRect(x: 0.0, y: 0, width: textWidth, height: self.containerLayer.bounds.height)
|
||||
self.textLayer.contents = textImage
|
||||
|
||||
self.duplicateTextLayer.frame = CGRect(x: innerPadding + textWidth + spacing, y: 0, width: textWidth, height: self.containerLayer.bounds.height)
|
||||
self.duplicateTextLayer.frame = CGRect(x: textWidth + spacing, y: 0, width: textWidth, height: self.containerLayer.bounds.height)
|
||||
self.duplicateTextLayer.contents = textImage
|
||||
|
||||
self.containerLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: textWidth * 2.0 + spacing, height: self.containerLayer.bounds.height))
|
||||
}
|
||||
|
||||
private func setupGradientMask(size: CGSize) {
|
||||
let edgePercentage = innerPadding / size.width
|
||||
|
||||
self.maskContainerLayer.frame = CGRect(origin: .zero, size: size)
|
||||
|
||||
self.solidEdgeMaskLayer.frame = CGRect(origin: .zero, size: CGSize(width: innerPadding, height: size.height))
|
||||
self.solidEdgeMaskLayer.backgroundColor = UIColor.black.cgColor
|
||||
|
||||
self.gradientMaskLayer.frame = CGRect(origin: .zero, size: size)
|
||||
self.gradientMaskLayer.colors = [
|
||||
UIColor.clear.cgColor,
|
||||
|
|
@ -125,22 +163,20 @@ public final class MarqueeComponent: Component {
|
|||
UIColor.black.cgColor,
|
||||
UIColor.black.cgColor,
|
||||
UIColor.clear.cgColor,
|
||||
UIColor.clear.cgColor
|
||||
UIColor.clear.cgColor,
|
||||
]
|
||||
self.gradientMaskLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
|
||||
self.gradientMaskLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
|
||||
|
||||
let edgePercentage = innerPadding / size.width
|
||||
self.gradientMaskLayer.locations = [
|
||||
0.0,
|
||||
NSNumber(value: edgePercentage * 0.4),
|
||||
NSNumber(value: edgePercentage * 0.1),
|
||||
NSNumber(value: edgePercentage),
|
||||
NSNumber(value: 1.0 - edgePercentage),
|
||||
NSNumber(value: 1.0 - edgePercentage * 0.4),
|
||||
NSNumber(value: 1.0 - edgePercentage * 0.1),
|
||||
1.0
|
||||
]
|
||||
|
||||
self.layer.mask = self.gradientMaskLayer
|
||||
|
||||
self.layer.mask = self.maskContainerLayer
|
||||
}
|
||||
|
||||
private func startAnimation(force: Bool = false) {
|
||||
|
|
@ -157,6 +193,14 @@ public final class MarqueeComponent: Component {
|
|||
guard self.isAnimating else {
|
||||
return
|
||||
}
|
||||
let values: [NSNumber] = [1.0, 0.0, 0.0, 1.0]
|
||||
let keyTimes: [NSNumber] = [0.0, 0.02, 0.98, 1.0]
|
||||
self.solidEdgeMaskLayer.animateKeyframes(
|
||||
values: values,
|
||||
keyTimes: keyTimes,
|
||||
duration: duration,
|
||||
keyPath: "opacity"
|
||||
)
|
||||
self.containerLayer.animateBoundsOriginXAdditive(from: 0.0, to: distance, duration: duration, delay: 0.0, timingFunction: CAMediaTimingFunctionName.linear.rawValue, completion: { finished in
|
||||
if finished {
|
||||
self.isAnimating = false
|
||||
|
|
@ -171,20 +215,7 @@ public final class MarqueeComponent: Component {
|
|||
self.isAnimating = false
|
||||
}
|
||||
}
|
||||
|
||||
public let attributedText: NSAttributedString
|
||||
|
||||
public init(attributedText: NSAttributedString) {
|
||||
self.attributedText = attributedText
|
||||
}
|
||||
|
||||
public static func ==(lhs: MarqueeComponent, rhs: MarqueeComponent) -> Bool {
|
||||
if lhs.attributedText != rhs.attributedText {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
public func makeView() -> View {
|
||||
return View()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -406,7 +406,7 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
|||
} else {
|
||||
controllerContext = strongSelf.context.sharedContext.makeTempAccountContext(account: account)
|
||||
}
|
||||
let controller = strongSelf.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, chatLocation: chatLocation, type: type, initialMessageId: id.messageId, initialOrder: order, playlistLocation: nil, parentNavigationController: strongSelf.chatControllerInteraction.navigationController())
|
||||
let controller = strongSelf.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, chatLocation: chatLocation, type: type, initialMessageId: id.messageId, initialOrder: order, playlistLocation: nil, parentNavigationController: strongSelf.chatControllerInteraction.navigationController(), updateMusicSaved: nil, reorderSavedMusic: nil)
|
||||
strongSelf.view.window?.endEditing(true)
|
||||
strongSelf.chatControllerInteraction.presentController(controller, nil)
|
||||
} else if index.1 {
|
||||
|
|
|
|||
|
|
@ -391,6 +391,8 @@ final class PeerInfoScreenData {
|
|||
let profileGiftsCollectionsContext: ProfileGiftsCollectionsContext?
|
||||
let premiumGiftOptions: [PremiumGiftCodeOption]
|
||||
let webAppPermissions: WebAppPermissionsState?
|
||||
let savedMusicContext: ProfileSavedMusicContext?
|
||||
let savedMusicState: ProfileSavedMusicContext.State?
|
||||
|
||||
let _isContact: Bool
|
||||
var forceIsContact: Bool = false
|
||||
|
|
@ -443,7 +445,9 @@ final class PeerInfoScreenData {
|
|||
profileGiftsContext: ProfileGiftsContext?,
|
||||
profileGiftsCollectionsContext: ProfileGiftsCollectionsContext?,
|
||||
premiumGiftOptions: [PremiumGiftCodeOption],
|
||||
webAppPermissions: WebAppPermissionsState?
|
||||
webAppPermissions: WebAppPermissionsState?,
|
||||
savedMusicContext: ProfileSavedMusicContext?,
|
||||
savedMusicState: ProfileSavedMusicContext.State?
|
||||
) {
|
||||
self.peer = peer
|
||||
self.chatPeer = chatPeer
|
||||
|
|
@ -485,6 +489,8 @@ final class PeerInfoScreenData {
|
|||
self.profileGiftsCollectionsContext = profileGiftsCollectionsContext
|
||||
self.premiumGiftOptions = premiumGiftOptions
|
||||
self.webAppPermissions = webAppPermissions
|
||||
self.savedMusicContext = savedMusicContext
|
||||
self.savedMusicState = savedMusicState
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1005,12 +1011,30 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id,
|
|||
profileGiftsContext: profileGiftsContext,
|
||||
profileGiftsCollectionsContext: nil,
|
||||
premiumGiftOptions: [],
|
||||
webAppPermissions: nil
|
||||
webAppPermissions: nil,
|
||||
savedMusicContext: nil,
|
||||
savedMusicState: nil
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, isSettings: Bool, isMyProfile: Bool, hintGroupInCommon: PeerId?, existingRequestsContext: PeerInvitationImportersContext?, existingProfileGiftsContext: ProfileGiftsContext?, existingProfileGiftsCollectionsContext: ProfileGiftsCollectionsContext?, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, sharedMediaFromForumTopic: (EnginePeer.Id, Int64)?, privacySettings: Signal<AccountPrivacySettings?, NoError>, forceHasGifts: Bool) -> Signal<PeerInfoScreenData, NoError> {
|
||||
func peerInfoScreenData(
|
||||
context: AccountContext,
|
||||
peerId: PeerId,
|
||||
strings: PresentationStrings,
|
||||
dateTimeFormat: PresentationDateTimeFormat,
|
||||
isSettings: Bool,
|
||||
isMyProfile: Bool,
|
||||
hintGroupInCommon: PeerId?,
|
||||
existingRequestsContext: PeerInvitationImportersContext?,
|
||||
existingProfileGiftsContext: ProfileGiftsContext?,
|
||||
existingProfileGiftsCollectionsContext: ProfileGiftsCollectionsContext?,
|
||||
chatLocation: ChatLocation,
|
||||
chatLocationContextHolder: Atomic<ChatLocationContextHolder?>,
|
||||
sharedMediaFromForumTopic: (EnginePeer.Id, Int64)?,
|
||||
privacySettings: Signal<AccountPrivacySettings?, NoError>,
|
||||
forceHasGifts: Bool
|
||||
) -> Signal<PeerInfoScreenData, NoError> {
|
||||
return peerInfoScreenInputData(context: context, peerId: peerId, isSettings: isSettings)
|
||||
|> mapToSignal { inputData -> Signal<PeerInfoScreenData, NoError> in
|
||||
let wasUpgradedGroup = Atomic<Bool?>(value: nil)
|
||||
|
|
@ -1057,7 +1081,9 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||
profileGiftsContext: nil,
|
||||
profileGiftsCollectionsContext: nil,
|
||||
premiumGiftOptions: [],
|
||||
webAppPermissions: nil
|
||||
webAppPermissions: nil,
|
||||
savedMusicContext: nil,
|
||||
savedMusicState: nil
|
||||
))
|
||||
case let .user(userPeerId, secretChatId, kind):
|
||||
let groupsInCommon: GroupsInCommonContext?
|
||||
|
|
@ -1377,6 +1403,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||
return .single(nil)
|
||||
}
|
||||
}
|
||||
|
||||
let savedMusicContext = ProfileSavedMusicContext(account: context.account, peerId: peerId)
|
||||
|
||||
return combineLatest(
|
||||
context.account.viewTracker.peerView(peerId, updateData: true),
|
||||
|
|
@ -1398,9 +1426,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||
starsRevenueContextAndState,
|
||||
revenueContextAndState,
|
||||
premiumGiftOptions,
|
||||
webAppPermissions
|
||||
webAppPermissions,
|
||||
savedMusicContext.state
|
||||
)
|
||||
|> map { peerView, availablePanes, globalNotificationSettings, encryptionKeyFingerprint, status, hasStories, hasStoryArchive, recommendedBots, accountIsPremium, savedMessagesPeer, hasSavedMessagesChats, hasSavedMessages, hasSavedMessageTags, hasBotPreviewItems, personalChannel, privacySettings, starsRevenueContextAndState, revenueContextAndState, premiumGiftOptions, webAppPermissions -> PeerInfoScreenData in
|
||||
|> map { peerView, availablePanes, globalNotificationSettings, encryptionKeyFingerprint, status, hasStories, hasStoryArchive, recommendedBots, accountIsPremium, savedMessagesPeer, hasSavedMessagesChats, hasSavedMessages, hasSavedMessageTags, hasBotPreviewItems, personalChannel, privacySettings, starsRevenueContextAndState, revenueContextAndState, premiumGiftOptions, webAppPermissions, savedMusicState -> PeerInfoScreenData in
|
||||
var availablePanes = availablePanes
|
||||
if isMyProfile {
|
||||
availablePanes?.insert(.stories, at: 0)
|
||||
|
|
@ -1536,7 +1565,9 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||
profileGiftsContext: profileGiftsContext,
|
||||
profileGiftsCollectionsContext: profileGiftsCollectionsContext,
|
||||
premiumGiftOptions: premiumGiftOptions,
|
||||
webAppPermissions: webAppPermissions
|
||||
webAppPermissions: webAppPermissions,
|
||||
savedMusicContext: savedMusicContext,
|
||||
savedMusicState: savedMusicState
|
||||
)
|
||||
}
|
||||
case .channel:
|
||||
|
|
@ -1779,7 +1810,9 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||
profileGiftsContext: profileGiftsContext,
|
||||
profileGiftsCollectionsContext: profileGiftsCollectionsContext,
|
||||
premiumGiftOptions: [],
|
||||
webAppPermissions: nil
|
||||
webAppPermissions: nil,
|
||||
savedMusicContext: nil,
|
||||
savedMusicState: nil
|
||||
)
|
||||
}
|
||||
case let .group(groupId):
|
||||
|
|
@ -2113,7 +2146,9 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||
profileGiftsContext: nil,
|
||||
profileGiftsCollectionsContext: nil,
|
||||
premiumGiftOptions: [],
|
||||
webAppPermissions: nil
|
||||
webAppPermissions: nil,
|
||||
savedMusicContext: nil,
|
||||
savedMusicState: nil
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ import MultilineTextComponent
|
|||
import PeerInfoRatingComponent
|
||||
import UndoUI
|
||||
import ProfileLevelInfoScreen
|
||||
import PlainButtonComponent
|
||||
import BundleIconComponent
|
||||
import MarqueeComponent
|
||||
|
||||
final class PeerInfoHeaderNavigationTransition {
|
||||
let sourceNavigationBar: NavigationBar
|
||||
|
|
@ -160,6 +163,9 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
let editingNavigationBackgroundNode: NavigationBackgroundNode
|
||||
let editingNavigationBackgroundSeparator: ASDisplayNode
|
||||
|
||||
var musicBackground: UIView?
|
||||
var music: ComponentView<Empty>?
|
||||
|
||||
var performButtonAction: ((PeerInfoHeaderButtonKey, ContextGesture?) -> Void)?
|
||||
var requestAvatarExpansion: ((Bool, [AvatarGalleryEntry], AvatarGalleryEntry?, (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?) -> Void)?
|
||||
var requestOpenAvatarForEditing: ((Bool) -> Void)?
|
||||
|
|
@ -171,6 +177,8 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
var displayCopyContextMenu: ((ASDisplayNode, Bool, Bool) -> Void)?
|
||||
var displayEmojiPackTooltip: (() -> Void)?
|
||||
|
||||
var displaySavedMusic: (() -> Void)?
|
||||
|
||||
var displayPremiumIntro: ((UIView, PeerEmojiStatus?, Signal<(TelegramMediaFile, LoadedStickerPack)?, NoError>, Bool) -> Void)?
|
||||
var displayStatusPremiumIntro: (() -> Void)?
|
||||
var displayUniqueGiftInfo: ((UIView, String) -> Void)?
|
||||
|
|
@ -307,7 +315,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
self.animationRenderer = context.animationRenderer
|
||||
|
||||
super.init()
|
||||
|
||||
|
||||
requestUpdateLayoutImpl = { [weak self] in
|
||||
self?.requestUpdateLayout?(false)
|
||||
}
|
||||
|
|
@ -501,7 +509,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
private var currentStatusIcon: CredibilityIcon?
|
||||
|
||||
private var currentPanelStatusData: PeerInfoStatusData?
|
||||
func update(width: CGFloat, containerHeight: CGFloat, containerInset: CGFloat, statusBarHeight: CGFloat, navigationHeight: CGFloat, isModalOverlay: Bool, isMediaOnly: Bool, contentOffset: CGFloat, paneContainerY: CGFloat, presentationData: PresentationData, peer: Peer?, cachedData: CachedPeerData?, threadData: MessageHistoryThreadData?, peerNotificationSettings: TelegramPeerNotificationSettings?, threadNotificationSettings: TelegramPeerNotificationSettings?, globalNotificationSettings: EngineGlobalNotificationSettings?, statusData: PeerInfoStatusData?, panelStatusData: (PeerInfoStatusData?, PeerInfoStatusData?, CGFloat?), isSecretChat: Bool, isContact: Bool, isSettings: Bool, state: PeerInfoState, profileGiftsContext: ProfileGiftsContext?, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition, additive: Bool, animateHeader: Bool) -> CGFloat {
|
||||
func update(width: CGFloat, containerHeight: CGFloat, containerInset: CGFloat, statusBarHeight: CGFloat, navigationHeight: CGFloat, isModalOverlay: Bool, isMediaOnly: Bool, contentOffset: CGFloat, paneContainerY: CGFloat, presentationData: PresentationData, peer: Peer?, cachedData: CachedPeerData?, threadData: MessageHistoryThreadData?, peerNotificationSettings: TelegramPeerNotificationSettings?, threadNotificationSettings: TelegramPeerNotificationSettings?, globalNotificationSettings: EngineGlobalNotificationSettings?, statusData: PeerInfoStatusData?, panelStatusData: (PeerInfoStatusData?, PeerInfoStatusData?, CGFloat?), isSecretChat: Bool, isContact: Bool, isSettings: Bool, state: PeerInfoState, profileGiftsContext: ProfileGiftsContext?, screenData: PeerInfoScreenData?, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition, additive: Bool, animateHeader: Bool) -> CGFloat {
|
||||
if self.appliedCustomNavigationContentNode !== self.customNavigationContentNode {
|
||||
if let previous = self.appliedCustomNavigationContentNode {
|
||||
transition.updateAlpha(node: previous, alpha: 0.0, completion: { [weak previous] _ in
|
||||
|
|
@ -549,6 +557,18 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
var currentSavedMusic: TelegramMediaFile?
|
||||
if !self.isSettings, let screenData {
|
||||
if let savedMusicState = screenData.savedMusicState {
|
||||
currentSavedMusic = savedMusicState.files.first
|
||||
} else if let cachedUserData = screenData.cachedData as? CachedUserData {
|
||||
currentSavedMusic = cachedUserData.savedMusic
|
||||
}
|
||||
}
|
||||
let musicHeight: CGFloat = 24.0
|
||||
let bottomInset: CGFloat = currentSavedMusic != nil ? 24.0 : 0.0
|
||||
|
||||
let isLandscape = containerInset > 16.0
|
||||
|
||||
let themeUpdated = self.presentationData?.theme !== presentationData.theme
|
||||
|
|
@ -620,11 +640,11 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
|
||||
let regularContentButtonBackgroundColor: UIColor
|
||||
let collapsedHeaderContentButtonBackgroundColor = presentationData.theme.list.itemBlocksBackgroundColor
|
||||
let expandedAvatarContentButtonBackgroundColor: UIColor = UIColor(white: 0.0, alpha: 0.1)
|
||||
let expandedAvatarContentButtonBackgroundColor: UIColor = UIColor(white: 1.0, alpha: 0.1)
|
||||
|
||||
let regularHeaderButtonBackgroundColor: UIColor
|
||||
let collapsedHeaderButtonBackgroundColor: UIColor = .clear
|
||||
let expandedAvatarHeaderButtonBackgroundColor: UIColor = UIColor(white: 0.0, alpha: 0.1)
|
||||
let expandedAvatarHeaderButtonBackgroundColor: UIColor = UIColor(white: 1.0, alpha: 0.1)
|
||||
|
||||
let regularContentButtonForegroundColor: UIColor = peer?.profileColor != nil ? UIColor.white : presentationData.theme.list.itemAccentColor
|
||||
let collapsedHeaderContentButtonForegroundColor = presentationData.theme.list.itemAccentColor
|
||||
|
|
@ -1590,7 +1610,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
|
||||
if self.isAvatarExpanded {
|
||||
let minTitleSize = CGSize(width: titleSize.width * expandedTitleScale, height: titleSize.height * expandedTitleScale)
|
||||
var minTitleFrame = CGRect(origin: CGPoint(x: 16.0, y: expandedAvatarHeight - 58.0 - UIScreenPixel + (subtitleSize.height.isZero ? 10.0 : 0.0)), size: minTitleSize)
|
||||
var minTitleFrame = CGRect(origin: CGPoint(x: 16.0, y: expandedAvatarHeight - bottomInset - 58.0 - UIScreenPixel + (subtitleSize.height.isZero ? 10.0 : 0.0)), size: minTitleSize)
|
||||
if !self.isSettings && !self.isMyProfile {
|
||||
minTitleFrame.origin.y -= 83.0
|
||||
}
|
||||
|
|
@ -1963,6 +1983,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
expandablePart += 99.0
|
||||
}
|
||||
}
|
||||
expandablePart += bottomInset
|
||||
height = navigationHeight + max(0.0, expandablePart)
|
||||
maxY = navigationHeight + panelWithAvatarHeight - contentOffset
|
||||
backgroundHeight = height
|
||||
|
|
@ -2271,12 +2292,12 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
|
||||
let buttonWidth = (width - buttonSideInset * 2.0 + buttonSpacing) / CGFloat(buttonKeys.count) - buttonSpacing
|
||||
let buttonSize = CGSize(width: buttonWidth, height: 58.0)
|
||||
var buttonRightOrigin = CGPoint(x: width - buttonSideInset, y: backgroundHeight - 16.0 - buttonSize.height)
|
||||
var buttonRightOrigin = CGPoint(x: width - buttonSideInset, y: backgroundHeight - bottomInset - 16.0 - buttonSize.height)
|
||||
if !actionButtonKeys.isEmpty {
|
||||
buttonRightOrigin.y += actionButtonSize.height + 24.0
|
||||
}
|
||||
|
||||
transition.updateFrameAdditive(node: self.buttonsBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: buttonRightOrigin.y), size: CGSize(width: width, height: buttonSize.height)))
|
||||
transition.updateFrameAdditive(node: self.buttonsBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: buttonRightOrigin.y), size: CGSize(width: width, height: buttonSize.height + 40.0)))
|
||||
self.buttonsBackgroundNode.update(size: self.buttonsBackgroundNode.bounds.size, transition: transition)
|
||||
self.buttonsBackgroundNode.updateColor(color: contentButtonBackgroundColor, enableBlur: true, transition: transition)
|
||||
|
||||
|
|
@ -2398,7 +2419,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
if self.isAvatarExpanded {
|
||||
resolvedRegularHeight = expandedAvatarListSize.height
|
||||
} else {
|
||||
resolvedRegularHeight = panelWithAvatarHeight + navigationHeight
|
||||
resolvedRegularHeight = panelWithAvatarHeight + navigationHeight + bottomInset
|
||||
}
|
||||
|
||||
let backgroundFrame: CGRect
|
||||
|
|
@ -2579,6 +2600,129 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
}
|
||||
}
|
||||
|
||||
if let currentSavedMusic {
|
||||
var musicTransition = transition
|
||||
var artist = presentationData.strings.MediaPlayer_UnknownArtist
|
||||
var track: String?
|
||||
for attribute in currentSavedMusic.attributes {
|
||||
if case let .Audio(_, _, title, performer, _) = attribute {
|
||||
artist = performer ?? artist
|
||||
track = title
|
||||
break
|
||||
}
|
||||
}
|
||||
if track == nil {
|
||||
if let fileName = currentSavedMusic.fileName {
|
||||
track = fileName
|
||||
} else {
|
||||
track = presentationData.strings.MediaPlayer_UnknownTrack
|
||||
}
|
||||
}
|
||||
|
||||
if hasBackground || self.isAvatarExpanded {
|
||||
if self.musicBackground == nil {
|
||||
musicTransition = .immediate
|
||||
}
|
||||
let musicBackground = self.musicBackground ?? {
|
||||
let musicBackground = UIView()
|
||||
musicBackground.backgroundColor = .white
|
||||
self.buttonsMaskView.addSubview(musicBackground)
|
||||
self.musicBackground = musicBackground
|
||||
if transition.isAnimated {
|
||||
musicBackground.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
return musicBackground
|
||||
}()
|
||||
musicTransition.updateFrame(view: musicBackground, frame: CGRect(origin: CGPoint(x: 0.0, y: backgroundHeight - musicHeight - buttonRightOrigin.y), size: CGSize(width: backgroundFrame.width, height: musicHeight)))
|
||||
} else if let musicBackground = self.musicBackground {
|
||||
self.musicBackground = nil
|
||||
if transition.isAnimated {
|
||||
musicBackground.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||
musicBackground.removeFromSuperview()
|
||||
})
|
||||
} else {
|
||||
musicBackground.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
let music = self.music ?? {
|
||||
let componentView = ComponentView<Empty>()
|
||||
self.music = componentView
|
||||
return componentView
|
||||
}()
|
||||
|
||||
let musicString = NSMutableAttributedString()
|
||||
let isOverlay = self.isAvatarExpanded || hasBackground
|
||||
musicString.append(NSAttributedString(string: track ?? "", font: Font.semibold(12.0), textColor: isOverlay ? .white : presentationData.theme.list.itemAccentColor))
|
||||
musicString.append(NSAttributedString(string: " - \(artist)", font: Font.regular(12.0), textColor: isOverlay ? UIColor.white.withAlphaComponent(0.7) : presentationData.theme.list.itemSecondaryTextColor))
|
||||
|
||||
let musicSize = music.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
PlainButtonComponent(
|
||||
content: AnyComponent(
|
||||
HStack([
|
||||
AnyComponentWithIdentity(
|
||||
id: "icon",
|
||||
component: AnyComponent(BundleIconComponent(name: "Media Editor/SmallAudio", tintColor: isOverlay ? .white : presentationData.theme.list.itemAccentColor))
|
||||
),
|
||||
AnyComponentWithIdentity(
|
||||
id: "label",
|
||||
component: AnyComponent(MarqueeComponent(attributedText: musicString, maxWidth: backgroundFrame.width - 96.0))
|
||||
),
|
||||
AnyComponentWithIdentity(
|
||||
id: "arrow",
|
||||
component: AnyComponent(BundleIconComponent(name: "Item List/InlineTextRightArrow", tintColor: isOverlay ? .white : presentationData.theme.list.itemSecondaryTextColor))
|
||||
)
|
||||
], spacing: 4.0)
|
||||
),
|
||||
minSize: CGSize(width: backgroundFrame.width, height: musicHeight),
|
||||
action: { [weak self] in
|
||||
self?.displaySavedMusic?()
|
||||
}
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: backgroundFrame.width, height: musicHeight)
|
||||
)
|
||||
let musicFrame = CGRect(origin: CGPoint(x: 0.0, y: backgroundHeight - musicHeight), size: musicSize)
|
||||
if let musicView = music.view {
|
||||
if musicView.superview == nil {
|
||||
self.view.addSubview(musicView)
|
||||
if transition.isAnimated {
|
||||
musicView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
if additive {
|
||||
musicTransition.updateFrameAdditiveToCenter(view: musicView, frame: musicFrame)
|
||||
} else {
|
||||
musicTransition.updateFrame(view: musicView, frame: musicFrame)
|
||||
}
|
||||
musicTransition.updateAlpha(layer: musicView.layer, alpha: backgroundBannerAlpha)
|
||||
}
|
||||
} else {
|
||||
if let musicBackground = self.musicBackground {
|
||||
self.musicBackground = nil
|
||||
if transition.isAnimated {
|
||||
musicBackground.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||
musicBackground.removeFromSuperview()
|
||||
})
|
||||
} else {
|
||||
musicBackground.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
if let music = self.music {
|
||||
self.music = nil
|
||||
if transition.isAnimated {
|
||||
music.view?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||
music.view?.removeFromSuperview()
|
||||
})
|
||||
} else {
|
||||
music.view?.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isFirstTime {
|
||||
self.updateAvatarMask(transition: .immediate)
|
||||
}
|
||||
|
|
@ -2594,6 +2738,14 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
self.performButtonAction?(buttonNode.key, gesture)
|
||||
}
|
||||
|
||||
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
||||
var result = super.point(inside: point, with: event)
|
||||
if let musicView = self.music?.view, musicView.frame.contains(point) {
|
||||
result = true
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
guard let result = super.hitTest(point, with: event) else {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -4114,6 +4114,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
self?.performButtonAction(key: key, gesture: gesture)
|
||||
}
|
||||
|
||||
self.headerNode.displaySavedMusic = { [weak self] in
|
||||
self?.displaySavedMusic()
|
||||
}
|
||||
|
||||
self.headerNode.cancelUpload = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
|
|
@ -6004,6 +6008,68 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
}
|
||||
}
|
||||
|
||||
private func displaySavedMusic() {
|
||||
guard let savedMusicContext = self.data?.savedMusicContext else {
|
||||
return
|
||||
}
|
||||
let peerId = self.peerId
|
||||
let peer = self.data?.peer
|
||||
let initialMessageId: MessageId
|
||||
if let initialFileId = self.data?.savedMusicState?.files.first?.fileId {
|
||||
initialMessageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Local, id: Int32(clamping: initialFileId.id % Int64(Int32.max)))
|
||||
} else {
|
||||
initialMessageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Local, id: 0)
|
||||
}
|
||||
|
||||
let musicController = self.context.sharedContext.makeOverlayAudioPlayerController(
|
||||
context: self.context,
|
||||
chatLocation: .peer(id: peerId),
|
||||
type: .music,
|
||||
initialMessageId: initialMessageId,
|
||||
initialOrder: .regular,
|
||||
playlistLocation: PeerMessagesPlaylistLocation.custom(
|
||||
messages: savedMusicContext.state
|
||||
|> map { state in
|
||||
var messages: [Message] = []
|
||||
var peers = SimpleDictionary<PeerId, Peer>()
|
||||
peers[peerId] = peer
|
||||
for file in state.files {
|
||||
let stableId = UInt32(clamping: file.fileId.id % Int64(Int32.max))
|
||||
messages.append(Message(stableId: stableId, stableVersion: 0, id: MessageId(peerId: peerId, namespace: Namespaces.Message.Local, id: Int32(stableId)), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [file], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]))
|
||||
|
||||
}
|
||||
return (messages, Int32(messages.count), true)
|
||||
},
|
||||
canReorder: peerId == self.context.account.peerId,
|
||||
at: initialMessageId,
|
||||
loadMore: { [weak savedMusicContext] in
|
||||
guard let savedMusicContext else {
|
||||
return
|
||||
}
|
||||
savedMusicContext.loadMore()
|
||||
}
|
||||
),
|
||||
parentNavigationController: self.controller?.navigationController as? NavigationController,
|
||||
updateMusicSaved: { [weak savedMusicContext] file, isSaved in
|
||||
guard let savedMusicContext else {
|
||||
return
|
||||
}
|
||||
if isSaved {
|
||||
let _ = savedMusicContext.addMusic(file: file).start()
|
||||
} else {
|
||||
let _ = savedMusicContext.removeMusic(file: file).start()
|
||||
}
|
||||
},
|
||||
reorderSavedMusic: { [weak savedMusicContext] file, afterFile in
|
||||
guard let savedMusicContext else {
|
||||
return
|
||||
}
|
||||
let _ = savedMusicContext.addMusic(file: file, afterFile: afterFile, apply: true).start()
|
||||
}
|
||||
)
|
||||
self.controller?.present(musicController, in: .window(.root))
|
||||
}
|
||||
|
||||
private func performButtonAction(key: PeerInfoHeaderButtonKey, gesture: ContextGesture?) {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
|
|
@ -12245,7 +12311,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
}
|
||||
let headerInset = sectionInset
|
||||
|
||||
let headerHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : self.scrollNode.view.contentOffset.y, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.savedMessagesPeer ?? self.data?.peer, cachedData: self.data?.cachedData, threadData: self.data?.threadData, peerNotificationSettings: self.data?.peerNotificationSettings, threadNotificationSettings: self.data?.threadNotificationSettings, globalNotificationSettings: self.data?.globalNotificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, profileGiftsContext: self.data?.profileGiftsContext, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: self.headerNode.navigationTransition == nil ? transition : .immediate, additive: additive, animateHeader: transition.isAnimated && self.headerNode.navigationTransition == nil)
|
||||
let headerHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : self.scrollNode.view.contentOffset.y, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.savedMessagesPeer ?? self.data?.peer, cachedData: self.data?.cachedData, threadData: self.data?.threadData, peerNotificationSettings: self.data?.peerNotificationSettings, threadNotificationSettings: self.data?.threadNotificationSettings, globalNotificationSettings: self.data?.globalNotificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, profileGiftsContext: self.data?.profileGiftsContext, screenData: self.data, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: self.headerNode.navigationTransition == nil ? transition : .immediate, additive: additive, animateHeader: transition.isAnimated && self.headerNode.navigationTransition == nil)
|
||||
let headerFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: layout.size.width, height: headerHeight))
|
||||
if additive {
|
||||
transition.updateFrameAdditive(node: self.headerNode, frame: headerFrame)
|
||||
|
|
@ -12630,7 +12696,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
}
|
||||
let headerInset = sectionInset
|
||||
|
||||
let _ = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : offsetY, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.savedMessagesPeer ?? self.data?.peer, cachedData: self.data?.cachedData, threadData: self.data?.threadData, peerNotificationSettings: self.data?.peerNotificationSettings, threadNotificationSettings: self.data?.threadNotificationSettings, globalNotificationSettings: self.data?.globalNotificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, profileGiftsContext: self.data?.profileGiftsContext, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: self.headerNode.navigationTransition == nil ? transition : .immediate, additive: additive, animateHeader: animateHeader && self.headerNode.navigationTransition == nil)
|
||||
let _ = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : offsetY, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.savedMessagesPeer ?? self.data?.peer, cachedData: self.data?.cachedData, threadData: self.data?.threadData, peerNotificationSettings: self.data?.peerNotificationSettings, threadNotificationSettings: self.data?.threadNotificationSettings, globalNotificationSettings: self.data?.globalNotificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, profileGiftsContext: self.data?.profileGiftsContext, screenData: self.data, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: self.headerNode.navigationTransition == nil ? transition : .immediate, additive: additive, animateHeader: animateHeader && self.headerNode.navigationTransition == nil)
|
||||
}
|
||||
|
||||
let paneAreaExpansionDistance: CGFloat = 32.0
|
||||
|
|
@ -14320,7 +14386,7 @@ private final class PeerInfoNavigationTransitionNode: ASDisplayNode, CustomNavig
|
|||
}
|
||||
let headerInset = sectionInset
|
||||
|
||||
topHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: topNavigationBar.bounds.height, isModalOverlay: layout.isModalOverlay, isMediaOnly: false, contentOffset: 0.0, paneContainerY: 0.0, presentationData: self.presentationData, peer: self.screenNode.data?.savedMessagesPeer ?? self.screenNode.data?.peer, cachedData: self.screenNode.data?.cachedData, threadData: self.screenNode.data?.threadData, peerNotificationSettings: self.screenNode.data?.peerNotificationSettings, threadNotificationSettings: self.screenNode.data?.threadNotificationSettings, globalNotificationSettings: self.screenNode.data?.globalNotificationSettings, statusData: self.screenNode.data?.status, panelStatusData: (nil, nil, nil), isSecretChat: self.screenNode.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.screenNode.data?.isContact ?? false, isSettings: self.screenNode.isSettings, state: self.screenNode.state, profileGiftsContext: self.screenNode.data?.profileGiftsContext, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: transition, additive: false, animateHeader: false)
|
||||
topHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: topNavigationBar.bounds.height, isModalOverlay: layout.isModalOverlay, isMediaOnly: false, contentOffset: 0.0, paneContainerY: 0.0, presentationData: self.presentationData, peer: self.screenNode.data?.savedMessagesPeer ?? self.screenNode.data?.peer, cachedData: self.screenNode.data?.cachedData, threadData: self.screenNode.data?.threadData, peerNotificationSettings: self.screenNode.data?.peerNotificationSettings, threadNotificationSettings: self.screenNode.data?.threadNotificationSettings, globalNotificationSettings: self.screenNode.data?.globalNotificationSettings, statusData: self.screenNode.data?.status, panelStatusData: (nil, nil, nil), isSecretChat: self.screenNode.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.screenNode.data?.isContact ?? false, isSettings: self.screenNode.isSettings, state: self.screenNode.state, profileGiftsContext: self.screenNode.data?.profileGiftsContext, screenData: self.screenNode.data, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: transition, additive: false, animateHeader: false)
|
||||
}
|
||||
|
||||
let titleScale = (fraction * previousTitleNode.view.bounds.height + (1.0 - fraction) * self.headerNode.titleNodeRawContainer.bounds.height) / previousTitleNode.view.bounds.height
|
||||
|
|
|
|||
|
|
@ -632,7 +632,7 @@ public final class TabSelectorComponent: Component {
|
|||
}
|
||||
|
||||
let estimatedContentWidth = 2.0 * spacing + innerContentWidth + (CGFloat(component.items.count - 1) * (spacing + innerInset))
|
||||
if component.customLayout?.fillWidth == true && estimatedContentWidth < availableSize.width {
|
||||
if component.customLayout?.fillWidth == true && estimatedContentWidth < availableSize.width && component.items.count > 1 {
|
||||
spacing = (availableSize.width - innerContentWidth) / CGFloat(component.items.count + 1) - innerInset * 2.0
|
||||
} else if estimatedContentWidth > availableSize.width && !allowScroll {
|
||||
spacing = (availableSize.width - innerContentWidth) / CGFloat(component.items.count + 1)
|
||||
|
|
|
|||
12
submodules/TelegramUI/Images.xcassets/Peer Info/SaveMusic.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Peer Info/SaveMusic.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "profilemusic_20.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Peer Info/SaveMusic.imageset/profilemusic_20.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Peer Info/SaveMusic.imageset/profilemusic_20.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Peer Info/SavedMusic.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Peer Info/SavedMusic.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "profilemusic_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Peer Info/SavedMusic.imageset/profilemusic_30.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Peer Info/SavedMusic.imageset/profilemusic_30.pdf
vendored
Normal file
Binary file not shown.
BIN
submodules/TelegramUI/Resources/Animations/anim_gift.tgs
Normal file
BIN
submodules/TelegramUI/Resources/Animations/anim_gift.tgs
Normal file
Binary file not shown.
|
|
@ -4430,29 +4430,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
return
|
||||
}
|
||||
var file: TelegramMediaFile?
|
||||
var title: String?
|
||||
var performer: String?
|
||||
for media in message.media {
|
||||
if let mediaFile = media as? TelegramMediaFile, mediaFile.isMusic {
|
||||
file = mediaFile
|
||||
for attribute in mediaFile.attributes {
|
||||
if case let .Audio(_, _, titleValue, performerValue, _) = attribute {
|
||||
if let titleValue, !titleValue.isEmpty {
|
||||
title = titleValue
|
||||
}
|
||||
if let performerValue, !performerValue.isEmpty {
|
||||
performer = performerValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
guard let file else {
|
||||
return
|
||||
}
|
||||
|
||||
var signal = fetchMediaData(context: context, postbox: context.account.postbox, userLocation: .other, mediaReference: .message(message: MessageReference(message._asMessage()), media: file))
|
||||
|
||||
let disposable: MetaDisposable
|
||||
if let current = self.saveMediaDisposable {
|
||||
disposable = current
|
||||
|
|
@ -4460,108 +4446,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
disposable = MetaDisposable()
|
||||
self.saveMediaDisposable = disposable
|
||||
}
|
||||
|
||||
var cancelImpl: (() -> Void)?
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let progressSignal = Signal<Never, NoError> { [weak self] subscriber in
|
||||
guard let self else {
|
||||
return EmptyDisposable
|
||||
}
|
||||
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
|
||||
cancelImpl?()
|
||||
}))
|
||||
self.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
return ActionDisposable { [weak controller] in
|
||||
Queue.mainQueue().async() {
|
||||
controller?.dismiss()
|
||||
disposable.set(
|
||||
saveMediaToFiles(
|
||||
context: self.context,
|
||||
fileReference: .message(message: MessageReference(message._asMessage()), media: file),
|
||||
present: { [weak self] c, a in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.present(c, in: .window(.root), with: a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|> runOn(Queue.mainQueue())
|
||||
|> delay(0.15, queue: Queue.mainQueue())
|
||||
let progressDisposable = progressSignal.startStrict()
|
||||
|
||||
signal = signal
|
||||
|> afterDisposed {
|
||||
Queue.mainQueue().async {
|
||||
progressDisposable.dispose()
|
||||
}
|
||||
}
|
||||
cancelImpl = { [weak disposable] in
|
||||
disposable?.set(nil)
|
||||
}
|
||||
disposable.set((signal
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] state, _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
switch state {
|
||||
case .progress:
|
||||
break
|
||||
case let .data(data):
|
||||
if data.complete {
|
||||
var symlinkPath = data.path + ".mp3"
|
||||
if fileSize(symlinkPath) != nil {
|
||||
try? FileManager.default.removeItem(atPath: symlinkPath)
|
||||
}
|
||||
let _ = try? FileManager.default.linkItem(atPath: data.path, toPath: symlinkPath)
|
||||
|
||||
let audioUrl = URL(fileURLWithPath: symlinkPath)
|
||||
let audioAsset = AVURLAsset(url: audioUrl)
|
||||
|
||||
var fileExtension = "mp3"
|
||||
if let filename = file.fileName {
|
||||
if let dotIndex = filename.lastIndex(of: ".") {
|
||||
fileExtension = String(filename[filename.index(after: dotIndex)...])
|
||||
}
|
||||
}
|
||||
|
||||
var nameComponents: [String] = []
|
||||
if let title {
|
||||
if let performer {
|
||||
nameComponents.append(performer)
|
||||
}
|
||||
nameComponents.append(title)
|
||||
} else {
|
||||
var artist: String?
|
||||
var title: String?
|
||||
for data in audioAsset.commonMetadata {
|
||||
if data.commonKey == .commonKeyArtist {
|
||||
artist = data.stringValue
|
||||
}
|
||||
if data.commonKey == .commonKeyTitle {
|
||||
title = data.stringValue
|
||||
}
|
||||
}
|
||||
if let artist, !artist.isEmpty {
|
||||
nameComponents.append(artist)
|
||||
}
|
||||
if let title, !title.isEmpty {
|
||||
nameComponents.append(title)
|
||||
}
|
||||
if nameComponents.isEmpty, var filename = file.fileName {
|
||||
if let dotIndex = filename.lastIndex(of: ".") {
|
||||
filename = String(filename[..<dotIndex])
|
||||
}
|
||||
nameComponents.append(filename)
|
||||
}
|
||||
}
|
||||
if !nameComponents.isEmpty {
|
||||
try? FileManager.default.removeItem(atPath: symlinkPath)
|
||||
|
||||
let fileName = "\(nameComponents.joined(separator: " – ")).\(fileExtension)"
|
||||
symlinkPath = symlinkPath.replacingOccurrences(of: audioUrl.lastPathComponent, with: fileName)
|
||||
let _ = try? FileManager.default.linkItem(atPath: data.path, toPath: symlinkPath)
|
||||
}
|
||||
|
||||
let url = URL(fileURLWithPath: symlinkPath)
|
||||
let controller = legacyICloudFilePicker(theme: self.presentationData.theme, mode: .export, url: url, documentTypes: [], forceDarkTheme: false, dismissed: {}, completion: { _ in
|
||||
|
||||
})
|
||||
self.present(controller, in: .window(.root))
|
||||
}
|
||||
}
|
||||
}))
|
||||
)
|
||||
)
|
||||
})
|
||||
}, openNoAdsDemo: { [weak self] in
|
||||
guard let self else {
|
||||
|
|
|
|||
|
|
@ -553,7 +553,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||
|
||||
return (messages, Int32(messages.count), false)
|
||||
}
|
||||
source = .custom(messages: messages, messageId: MessageId(peerId: PeerId(0), namespace: 0, id: 0), quote: nil, loadMore: nil)
|
||||
source = .custom(messages: messages, messageId: MessageId(peerId: PeerId(0), namespace: 0, id: 0), quote: nil, updateAll: true, canReorder: false, loadMore: nil)
|
||||
case let .reply(reply):
|
||||
let messages = combineLatest(context.account.postbox.messagesAtIds(messageIds), context.account.postbox.loadedPeerWithId(context.account.peerId))
|
||||
|> map { messages, accountPeer -> ([Message], Int32, Bool) in
|
||||
|
|
@ -567,7 +567,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||
|
||||
return (messages, Int32(messages.count), false)
|
||||
}
|
||||
source = .custom(messages: messages, messageId: messageIds.first ?? MessageId(peerId: PeerId(0), namespace: 0, id: 0), quote: reply.quote.flatMap { quote in ChatHistoryListSource.Quote(text: quote.text, offset: quote.offset) }, loadMore: nil)
|
||||
source = .custom(messages: messages, messageId: messageIds.first ?? MessageId(peerId: PeerId(0), namespace: 0, id: 0), quote: reply.quote.flatMap { quote in ChatHistoryListSource.Quote(text: quote.text, offset: quote.offset) }, updateAll: true, canReorder: false, loadMore: nil)
|
||||
case let .link(link):
|
||||
let messages = link.options
|
||||
|> mapToSignal { options -> Signal<(ChatControllerSubject.LinkOptions, Peer, Message?, [StoryId: CodableEntry]), NoError> in
|
||||
|
|
@ -668,13 +668,13 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||
|
||||
return ([message], 1, false)
|
||||
}
|
||||
source = .custom(messages: messages, messageId: MessageId(peerId: PeerId(0), namespace: 0, id: 0), quote: nil, loadMore: nil)
|
||||
source = .custom(messages: messages, messageId: MessageId(peerId: PeerId(0), namespace: 0, id: 0), quote: nil, updateAll: true, canReorder: false, loadMore: nil)
|
||||
}
|
||||
} else if case .customChatContents = chatLocation {
|
||||
if case let .customChatContents(customChatContents) = subject {
|
||||
source = .customView(historyView: customChatContents.historyView)
|
||||
} else {
|
||||
source = .custom(messages: .single(([], 0, false)), messageId: nil, quote: nil, loadMore: nil)
|
||||
source = .custom(messages: .single(([], 0, false)), messageId: nil, quote: nil, updateAll: true, canReorder: false, loadMore: nil)
|
||||
}
|
||||
} else {
|
||||
source = .default
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@ extension ListMessageItemInteraction {
|
|||
}
|
||||
}
|
||||
|
||||
private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLocation, associatedData: ChatMessageItemAssociatedData, controllerInteraction: ChatControllerInteraction, mode: ChatHistoryListMode, lastHeaderId: Int64, entries: [ChatHistoryViewTransitionInsertEntry]) -> [ListViewInsertItem] {
|
||||
private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLocation, associatedData: ChatMessageItemAssociatedData, controllerInteraction: ChatControllerInteraction, mode: ChatHistoryListMode, lastHeaderId: Int64, canReorder: Bool, entries: [ChatHistoryViewTransitionInsertEntry]) -> [ListViewInsertItem] {
|
||||
var disableFloatingDateHeaders = false
|
||||
if case .customChatContents = chatLocation {
|
||||
disableFloatingDateHeaders = true
|
||||
|
|
@ -239,7 +239,7 @@ private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLoca
|
|||
case .allButLast:
|
||||
displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != lastHeaderId
|
||||
}
|
||||
item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch)
|
||||
item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch, canReorder: canReorder)
|
||||
}
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint)
|
||||
case let .MessageGroupEntry(_, messages, presentationData):
|
||||
|
|
@ -275,7 +275,7 @@ private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLoca
|
|||
}
|
||||
}
|
||||
|
||||
private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLocation, associatedData: ChatMessageItemAssociatedData, controllerInteraction: ChatControllerInteraction, mode: ChatHistoryListMode, lastHeaderId: Int64, entries: [ChatHistoryViewTransitionUpdateEntry]) -> [ListViewUpdateItem] {
|
||||
private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLocation, associatedData: ChatMessageItemAssociatedData, controllerInteraction: ChatControllerInteraction, mode: ChatHistoryListMode, lastHeaderId: Int64, canReorder: Bool, entries: [ChatHistoryViewTransitionUpdateEntry]) -> [ListViewUpdateItem] {
|
||||
var disableFloatingDateHeaders = false
|
||||
if case .customChatContents = chatLocation {
|
||||
disableFloatingDateHeaders = true
|
||||
|
|
@ -298,7 +298,7 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca
|
|||
case .allButLast:
|
||||
displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != lastHeaderId
|
||||
}
|
||||
item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch)
|
||||
item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch, canReorder: canReorder)
|
||||
}
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint)
|
||||
case let .MessageGroupEntry(_, messages, presentationData):
|
||||
|
|
@ -334,11 +334,11 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca
|
|||
}
|
||||
}
|
||||
|
||||
private func mappedChatHistoryViewListTransition(context: AccountContext, chatLocation: ChatLocation, associatedData: ChatMessageItemAssociatedData, controllerInteraction: ChatControllerInteraction, mode: ChatHistoryListMode, lastHeaderId: Int64, animateFromPreviousFilter: Bool, transition: ChatHistoryViewTransition) -> ChatHistoryListViewTransition {
|
||||
return ChatHistoryListViewTransition(historyView: transition.historyView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, entries: transition.insertEntries), updateItems: mappedUpdateEntries(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, initialData: transition.initialData, keyboardButtonsMessage: transition.keyboardButtonsMessage, cachedData: transition.cachedData, cachedDataMessages: transition.cachedDataMessages, readStateData: transition.readStateData, scrolledToIndex: transition.scrolledToIndex, scrolledToSomeIndex: transition.scrolledToSomeIndex, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, animateIn: transition.animateIn, reason: transition.reason, flashIndicators: transition.flashIndicators, animateFromPreviousFilter: animateFromPreviousFilter)
|
||||
private func mappedChatHistoryViewListTransition(context: AccountContext, chatLocation: ChatLocation, associatedData: ChatMessageItemAssociatedData, controllerInteraction: ChatControllerInteraction, mode: ChatHistoryListMode, lastHeaderId: Int64, canReorder: Bool, animateFromPreviousFilter: Bool, transition: ChatHistoryViewTransition) -> ChatHistoryListViewTransition {
|
||||
return ChatHistoryListViewTransition(historyView: transition.historyView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, canReorder: canReorder, entries: transition.insertEntries), updateItems: mappedUpdateEntries(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, canReorder: canReorder, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, initialData: transition.initialData, keyboardButtonsMessage: transition.keyboardButtonsMessage, cachedData: transition.cachedData, cachedDataMessages: transition.cachedDataMessages, readStateData: transition.readStateData, scrolledToIndex: transition.scrolledToIndex, scrolledToSomeIndex: transition.scrolledToSomeIndex, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, animateIn: transition.animateIn, reason: transition.reason, flashIndicators: transition.flashIndicators, animateFromPreviousFilter: animateFromPreviousFilter)
|
||||
}
|
||||
|
||||
private final class ChatHistoryTransactionOpaqueState {
|
||||
final class ChatHistoryTransactionOpaqueState {
|
||||
let historyView: ChatHistoryView
|
||||
|
||||
init(historyView: ChatHistoryView) {
|
||||
|
|
@ -1398,8 +1398,10 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
|||
var historyViewUpdate: Signal<(ChatHistoryViewUpdate, Int, ChatHistoryLocationInput?, ClosedRange<Int32>?, Set<MessageId>), NoError>
|
||||
var isFirstTime = true
|
||||
var updateAllOnEachVersion = false
|
||||
if case let .custom(messages, at, quote, _) = self.source {
|
||||
updateAllOnEachVersion = true
|
||||
var canReorder = false
|
||||
if case let .custom(messages, at, quote, updateAll, canReorderValue, _) = self.source {
|
||||
updateAllOnEachVersion = updateAll
|
||||
canReorder = canReorderValue
|
||||
historyViewUpdate = messages
|
||||
|> map { messages, _, hasMore in
|
||||
let version = currentViewVersion.modify({ value in
|
||||
|
|
@ -1916,7 +1918,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
|||
let forceSynchronous = true
|
||||
|
||||
let rawTransition = preparedChatHistoryViewTransition(from: previous, to: processedView, reason: reason, reverse: false, chatLocation: chatLocation, controllerInteraction: controllerInteraction, scrollPosition: nil, scrollAnimationCurve: nil, initialData: initialData?.initialData, keyboardButtonsMessage: nil, cachedData: initialData?.cachedData, cachedDataMessages: initialData?.cachedDataMessages, readStateData: initialData?.readStateData, flashIndicators: false, updatedMessageSelection: previousSelectedMessages != selectedMessages, messageTransitionNode: messageTransitionNode(), allUpdated: false)
|
||||
var mappedTransition = mappedChatHistoryViewListTransition(context: context, chatLocation: chatLocation, associatedData: previousViewValue.associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: 0, animateFromPreviousFilter: resetScrolling, transition: rawTransition)
|
||||
var mappedTransition = mappedChatHistoryViewListTransition(context: context, chatLocation: chatLocation, associatedData: previousViewValue.associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: 0, canReorder: canReorder, animateFromPreviousFilter: resetScrolling, transition: rawTransition)
|
||||
|
||||
if disableAnimations {
|
||||
mappedTransition.options.remove(.AnimateInsertion)
|
||||
|
|
@ -2307,7 +2309,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
|||
}
|
||||
|
||||
let rawTransition = preparedChatHistoryViewTransition(from: previous, to: processedView, reason: reason, reverse: reverse, chatLocation: chatLocation, controllerInteraction: controllerInteraction, scrollPosition: updatedScrollPosition, scrollAnimationCurve: scrollAnimationCurve, initialData: initialData?.initialData, keyboardButtonsMessage: keyboardButtonsMessage, cachedData: initialData?.cachedData, cachedDataMessages: initialData?.cachedDataMessages, readStateData: initialData?.readStateData, flashIndicators: flashIndicators, updatedMessageSelection: previousSelectedMessages != selectedMessages, messageTransitionNode: messageTransitionNode(), allUpdated: updateAllOnEachVersion || forceUpdateAll)
|
||||
var mappedTransition = mappedChatHistoryViewListTransition(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, animateFromPreviousFilter: resetScrolling, transition: rawTransition)
|
||||
var mappedTransition = mappedChatHistoryViewListTransition(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, canReorder: canReorder, animateFromPreviousFilter: resetScrolling, transition: rawTransition)
|
||||
|
||||
if disableAnimations {
|
||||
mappedTransition.options.remove(.AnimateInsertion)
|
||||
|
|
|
|||
|
|
@ -381,6 +381,7 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode {
|
|||
|
||||
self.textNode = ImmediateTextNode()
|
||||
self.textNode.maximumNumberOfLines = 3
|
||||
self.textNode.truncationType = .middle
|
||||
self.textNode.textAlignment = .center
|
||||
|
||||
super.init()
|
||||
|
|
@ -556,7 +557,7 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode {
|
|||
|
||||
transition.updateAlpha(node: self.textNode, alpha: 1.0)
|
||||
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: width - leftInset - rightInset - 80.0, height: 40.0))
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: width - leftInset - rightInset - 80.0, height: 80.0))
|
||||
self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((width - textSize.width) / 2.0), y: 10.0), size: textSize)
|
||||
|
||||
for (_, view) in self.buttons {
|
||||
|
|
@ -565,7 +566,7 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode {
|
|||
|
||||
self.tapGestureRecognizer?.isEnabled = true
|
||||
|
||||
panelHeight += 15.0
|
||||
panelHeight += max(15.0, textSize.height - 19.0)
|
||||
} else {
|
||||
transition.updateAlpha(node: self.textNode, alpha: 0.0)
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@ final class OverlayAudioPlayerControllerImpl: ViewController, OverlayAudioPlayer
|
|||
let initialOrder: MusicPlaybackSettingsOrder
|
||||
let playlistLocation: SharedMediaPlaylistLocation?
|
||||
|
||||
private weak var parentNavigationController: NavigationController?
|
||||
private(set) weak var parentNavigationController: NavigationController?
|
||||
private let updateMusicSaved: ((FileMediaReference, Bool) -> Void)?
|
||||
let reorderSavedMusic: ((FileMediaReference, FileMediaReference?) -> Void)?
|
||||
|
||||
private var animatedIn = false
|
||||
|
||||
|
|
@ -27,7 +29,17 @@ final class OverlayAudioPlayerControllerImpl: ViewController, OverlayAudioPlayer
|
|||
|
||||
private var accountInUseDisposable: Disposable?
|
||||
|
||||
init(context: AccountContext, chatLocation: ChatLocation, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, playlistLocation: SharedMediaPlaylistLocation? = nil, parentNavigationController: NavigationController?) {
|
||||
init(
|
||||
context: AccountContext,
|
||||
chatLocation: ChatLocation,
|
||||
type: MediaManagerPlayerType,
|
||||
initialMessageId: MessageId,
|
||||
initialOrder: MusicPlaybackSettingsOrder,
|
||||
playlistLocation: SharedMediaPlaylistLocation? = nil,
|
||||
parentNavigationController: NavigationController?,
|
||||
updateMusicSaved: ((FileMediaReference, Bool) -> Void)? = nil,
|
||||
reorderSavedMusic: ((FileMediaReference, FileMediaReference?) -> Void)? = nil
|
||||
) {
|
||||
self.context = context
|
||||
self.chatLocation = chatLocation
|
||||
self.type = type
|
||||
|
|
@ -35,6 +47,8 @@ final class OverlayAudioPlayerControllerImpl: ViewController, OverlayAudioPlayer
|
|||
self.initialOrder = initialOrder
|
||||
self.playlistLocation = playlistLocation
|
||||
self.parentNavigationController = parentNavigationController
|
||||
self.updateMusicSaved = updateMusicSaved
|
||||
self.reorderSavedMusic = reorderSavedMusic
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
|
||||
|
|
@ -111,7 +125,7 @@ final class OverlayAudioPlayerControllerImpl: ViewController, OverlayAudioPlayer
|
|||
guard let self, let peer else {
|
||||
return
|
||||
}
|
||||
guard let navigationController = self.navigationController as? NavigationController else {
|
||||
guard let navigationController = self.parentNavigationController else {
|
||||
return
|
||||
}
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), forceOpenChat: true))
|
||||
|
|
@ -133,10 +147,22 @@ final class OverlayAudioPlayerControllerImpl: ViewController, OverlayAudioPlayer
|
|||
strongSelf.context.sharedContext.openSearch(filter: .music, query: artist)
|
||||
strongSelf.dismiss()
|
||||
}
|
||||
})
|
||||
self.controllerNode.getParentController = { [weak self] in
|
||||
}, updateMusicSaved: { [weak self] file, isSaved in
|
||||
if let self {
|
||||
if let updateMusicSaved = self.updateMusicSaved {
|
||||
updateMusicSaved(file, isSaved)
|
||||
} else {
|
||||
if isSaved {
|
||||
let _ = self.context.engine.peers.addSavedMusic(file: file).start()
|
||||
} else {
|
||||
let _ = self.context.engine.peers.removeSavedMusic(file: file).start()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
getParentController: { [weak self] in
|
||||
return self
|
||||
}
|
||||
})
|
||||
|
||||
self.ready.set(self.controllerNode.ready.get())
|
||||
|
||||
|
|
|
|||
|
|
@ -11,18 +11,23 @@ import AccountContext
|
|||
import DirectionalPanGesture
|
||||
import ChatPresentationInterfaceState
|
||||
import ChatControllerInteraction
|
||||
import ContextUI
|
||||
import UndoUI
|
||||
import ChatHistoryEntry
|
||||
|
||||
final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestureRecognizerDelegate {
|
||||
let ready = Promise<Bool>()
|
||||
|
||||
private let context: AccountContext
|
||||
|
||||
private let source: ChatHistoryListSource
|
||||
private let chatLocation: ChatLocation
|
||||
private var presentationData: PresentationData
|
||||
private let type: MediaManagerPlayerType
|
||||
private let requestDismiss: () -> Void
|
||||
private let requestShare: (MessageId) -> Void
|
||||
private let requestSearchByArtist: (String) -> Void
|
||||
private let updateMusicSaved: (FileMediaReference, Bool) -> Void
|
||||
private let playlistLocation: SharedMediaPlaylistLocation?
|
||||
private let isGlobalSearch: Bool
|
||||
|
||||
|
|
@ -40,18 +45,32 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
private var replacementHistoryNode: ChatHistoryListNodeImpl?
|
||||
private var replacementHistoryNodeFloatingOffset: CGFloat?
|
||||
|
||||
private var saveMediaDisposable: MetaDisposable?
|
||||
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
private var presentationDataDisposable: Disposable?
|
||||
private let replacementHistoryNodeReadyDisposable = MetaDisposable()
|
||||
|
||||
var getParentController: () -> ViewController? = { return nil } {
|
||||
didSet {
|
||||
self.controlsNode.getParentController = self.getParentController
|
||||
}
|
||||
}
|
||||
private let getParentController: () -> ViewController?
|
||||
|
||||
private var savedIdsDisposable: Disposable?
|
||||
private var savedIdsPromise = Promise<Set<Int64>?>()
|
||||
private var savedIds: Set<Int64>?
|
||||
|
||||
init(context: AccountContext, chatLocation: ChatLocation, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, playlistLocation: SharedMediaPlaylistLocation?, requestDismiss: @escaping () -> Void, requestShare: @escaping (MessageId) -> Void, requestSearchByArtist: @escaping (String) -> Void) {
|
||||
init(
|
||||
context: AccountContext,
|
||||
chatLocation: ChatLocation,
|
||||
type: MediaManagerPlayerType,
|
||||
initialMessageId: MessageId,
|
||||
initialOrder: MusicPlaybackSettingsOrder,
|
||||
playlistLocation: SharedMediaPlaylistLocation?,
|
||||
requestDismiss: @escaping () -> Void,
|
||||
requestShare: @escaping (MessageId) -> Void,
|
||||
requestSearchByArtist: @escaping (String) -> Void,
|
||||
updateMusicSaved: @escaping (FileMediaReference, Bool) -> Void,
|
||||
getParentController: @escaping () -> ViewController?
|
||||
) {
|
||||
self.context = context
|
||||
self.chatLocation = chatLocation
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
|
@ -60,6 +79,16 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
self.requestShare = requestShare
|
||||
self.requestSearchByArtist = requestSearchByArtist
|
||||
self.playlistLocation = playlistLocation
|
||||
self.updateMusicSaved = updateMusicSaved
|
||||
self.getParentController = getParentController
|
||||
|
||||
if let playlistLocation = playlistLocation as? PeerMessagesPlaylistLocation, case let .custom(messages, canReorder, at, loadMore) = playlistLocation {
|
||||
self.source = .custom(messages: messages, messageId: at, quote: nil, updateAll: false, canReorder: canReorder, loadMore: loadMore)
|
||||
self.isGlobalSearch = false
|
||||
} else {
|
||||
self.source = .default
|
||||
self.isGlobalSearch = false
|
||||
}
|
||||
|
||||
if case .regular = initialOrder {
|
||||
self.currentIsReversed = false
|
||||
|
|
@ -68,6 +97,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
}
|
||||
|
||||
var openMessageImpl: ((MessageId) -> Bool)?
|
||||
var openMessageContextMenuImpl: ((Message, ASDisplayNode, CGRect, Any?) -> Void)?
|
||||
self.controllerInteraction = ChatControllerInteraction(openMessage: { message, _ in
|
||||
if let openMessageImpl = openMessageImpl {
|
||||
return openMessageImpl(message.id)
|
||||
|
|
@ -76,7 +106,8 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
}
|
||||
}, openPeer: { _, _, _, _ in
|
||||
}, openPeerMention: { _, _ in
|
||||
}, openMessageContextMenu: { _, _, _, _, _, _ in
|
||||
}, openMessageContextMenu: { message, _, node, rect, gesture, _ in
|
||||
openMessageContextMenuImpl?(message, node, rect, gesture)
|
||||
}, openMessageReactionContextMenu: { _, _, _, _ in
|
||||
}, updateMessageReaction: { _, _, _, _ in
|
||||
}, activateMessagePinch: { _ in
|
||||
|
|
@ -205,7 +236,8 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
|
||||
self.contentNode = ASDisplayNode()
|
||||
|
||||
self.controlsNode = OverlayPlayerControlsNode(account: context.account, engine: context.engine, accountManager: context.sharedContext.accountManager, presentationData: self.presentationData, status: context.sharedContext.mediaManager.musicMediaPlayerState)
|
||||
self.controlsNode = OverlayPlayerControlsNode(account: context.account, engine: context.engine, accountManager: context.sharedContext.accountManager, presentationData: self.presentationData, status: context.sharedContext.mediaManager.musicMediaPlayerState, chatLocation: self.chatLocation, source: self.source)
|
||||
self.controlsNode.getParentController = getParentController
|
||||
|
||||
self.historyBackgroundNode = ASDisplayNode()
|
||||
self.historyBackgroundNode.isLayerBacked = true
|
||||
|
|
@ -228,17 +260,9 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
|
||||
let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil)
|
||||
|
||||
let source: ChatHistoryListSource
|
||||
if let playlistLocation = playlistLocation as? PeerMessagesPlaylistLocation, case let .custom(messages, at, loadMore) = playlistLocation {
|
||||
source = .custom(messages: messages, messageId: at, quote: nil, loadMore: loadMore)
|
||||
self.isGlobalSearch = true
|
||||
} else {
|
||||
source = .default
|
||||
self.isGlobalSearch = false
|
||||
}
|
||||
|
||||
self.historyNode = ChatHistoryListNodeImpl(context: context, updatedPresentationData: (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData), chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, adMessagesContext: nil, tag: .tag(tagMask), source: source, subject: .message(id: .id(initialMessageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, reverseGroups: !self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch), isChatPreview: false, messageTransitionNode: { return nil })
|
||||
self.historyNode = ChatHistoryListNodeImpl(context: context, updatedPresentationData: (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData), chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, adMessagesContext: nil, tag: .tag(tagMask), source: self.source, subject: .message(id: .id(initialMessageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, reverseGroups: !self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch), isChatPreview: false, messageTransitionNode: { return nil })
|
||||
self.historyNode.clipsToBounds = true
|
||||
//self.historyNode.areContentAnimationsEnabled = true
|
||||
|
||||
super.init()
|
||||
|
||||
|
|
@ -269,7 +293,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
|
||||
self.controlsNode.updateIsExpanded = { [weak self] in
|
||||
if let strongSelf = self, let validLayout = strongSelf.validLayout {
|
||||
strongSelf.containerLayoutUpdated(validLayout, transition: .animated(duration: 0.3, curve: .spring))
|
||||
strongSelf.containerLayoutUpdated(validLayout, transition: .animated(duration: 0.5, curve: .spring))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -285,6 +309,12 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
self?.requestSearchByArtist(artist)
|
||||
}
|
||||
|
||||
self.controlsNode.requestLayout = { [weak self] transition in
|
||||
if let self, let validLayout = self.validLayout {
|
||||
self.containerLayoutUpdated(validLayout, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
self.controlsNode.updateOrder = { [weak self] order in
|
||||
if let strongSelf = self {
|
||||
let reversed: Bool
|
||||
|
|
@ -308,6 +338,18 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
}
|
||||
}
|
||||
|
||||
self.controlsNode.requestSaveToProfile = { [weak self] file in
|
||||
if let self {
|
||||
self.addToSavedMusic(file: file)
|
||||
}
|
||||
}
|
||||
|
||||
self.controlsNode.requestRemoveFromProfile = { [weak self] file in
|
||||
if let self {
|
||||
self.removeFromSavedMusic(file: file)
|
||||
}
|
||||
}
|
||||
|
||||
self.addSubnode(self.dimNode)
|
||||
self.addSubnode(self.contentNode)
|
||||
self.contentNode.addSubnode(self.historyBackgroundNode)
|
||||
|
|
@ -321,8 +363,8 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
openMessageImpl = { [weak self] id in
|
||||
if let strongSelf = self, strongSelf.isNodeLoaded, let message = strongSelf.historyNode.messageInCurrentHistoryView(id) {
|
||||
var playlistLocation: PeerMessagesPlaylistLocation?
|
||||
if let location = strongSelf.playlistLocation as? PeerMessagesPlaylistLocation, case let .custom(messages, _, loadMore) = location {
|
||||
playlistLocation = .custom(messages: messages, at: id, loadMore: loadMore)
|
||||
if let location = strongSelf.playlistLocation as? PeerMessagesPlaylistLocation, case let .custom(messages, canReorder, _, loadMore) = location {
|
||||
playlistLocation = .custom(messages: messages, canReorder: canReorder, at: id, loadMore: loadMore)
|
||||
}
|
||||
return strongSelf.context.sharedContext.openChatMessage(OpenChatMessageParams(context: strongSelf.context, chatLocation: nil, chatFilterTag: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: nil, dismissInput: { }, present: { _, _, _ in }, transitionNode: { _, _, _ in return nil }, addToTransitionSurface: { _ in }, openUrl: { _ in }, openPeer: { _, _ in }, callPeer: { _, _ in }, openConferenceCall: { _ in
|
||||
}, enqueueMessage: { _ in }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: playlistLocation))
|
||||
|
|
@ -330,6 +372,13 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
return false
|
||||
}
|
||||
|
||||
openMessageContextMenuImpl = { [weak self] message, node, rect, gesture in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.openMessageContextMenu(message: message, node: node, frame: rect, gesture: gesture as? ContextGesture)
|
||||
}
|
||||
|
||||
self.presentationDataDisposable = context.sharedContext.presentationData.startStrict(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
if strongSelf.presentationData.theme !== presentationData.theme || strongSelf.presentationData.strings !== presentationData.strings {
|
||||
|
|
@ -338,14 +387,45 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
}
|
||||
})
|
||||
|
||||
self.ready.set(self.historyNode.historyState.get() |> map { _ -> Bool in
|
||||
return true
|
||||
} |> take(1))
|
||||
self.savedIdsDisposable = (context.engine.peers.savedMusicIds()
|
||||
|> deliverOnMainQueue).start(next: { [weak self] savedIds in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let isFirstTime = self.savedIds == nil
|
||||
self.savedIds = savedIds
|
||||
self.savedIdsPromise.set(.single(savedIds))
|
||||
|
||||
let transition: ContainedViewLayoutTransition = isFirstTime ? .immediate : .animated(duration: 0.5, curve: .spring)
|
||||
self.updateFloatingHeaderOffset(offset: self.floatingHeaderOffset ?? 0.0, transition: transition)
|
||||
if let validLayout = self.validLayout {
|
||||
self.containerLayoutUpdated(validLayout, transition: transition)
|
||||
}
|
||||
})
|
||||
|
||||
self.ready.set(
|
||||
combineLatest(
|
||||
self.historyNode.historyState.get()
|
||||
|> take(1),
|
||||
self.savedIdsPromise.get()
|
||||
|> filter {
|
||||
$0 != nil
|
||||
}
|
||||
|> take(1)
|
||||
)
|
||||
|> map { _, _ -> Bool in
|
||||
return true
|
||||
}
|
||||
)
|
||||
|
||||
self.setupReordering()
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.presentationDataDisposable?.dispose()
|
||||
self.replacementHistoryNodeReadyDisposable.dispose()
|
||||
self.savedIdsDisposable?.dispose()
|
||||
self.saveMediaDisposable?.dispose()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
|
|
@ -371,6 +451,49 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
self.view.addGestureRecognizer(panRecognizer)
|
||||
}
|
||||
|
||||
private func setupReordering() {
|
||||
guard let controller = self.getParentController() as? OverlayAudioPlayerControllerImpl, let reorderSavedMusic = controller.reorderSavedMusic, case let .peer(peerId) = self.chatLocation else {
|
||||
return
|
||||
}
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let peer = peer.flatMap({ PeerReference($0._asPeer()) }) else {
|
||||
return
|
||||
}
|
||||
self.historyNode.reorderItem = { fromIndex, toIndex, transactionOpaqueState -> Signal<Bool, NoError> in
|
||||
guard let filteredEntries = (transactionOpaqueState as? ChatHistoryTransactionOpaqueState)?.historyView.filteredEntries else {
|
||||
return .single(false)
|
||||
}
|
||||
let fromEntry = filteredEntries[filteredEntries.count - 1 - fromIndex]
|
||||
let toEntry: ChatHistoryEntry?
|
||||
if toIndex == 0 {
|
||||
toEntry = nil
|
||||
} else {
|
||||
toEntry = filteredEntries[filteredEntries.count - 1 - toIndex]
|
||||
}
|
||||
guard case let .MessageEntry(fromMessage, _, _, _, _, _) = fromEntry else {
|
||||
return .single(false)
|
||||
}
|
||||
|
||||
guard let fromFile = fromMessage.media.first(where: { $0 is TelegramMediaFile }) as? TelegramMediaFile else {
|
||||
return .single(false)
|
||||
}
|
||||
|
||||
var toFile: TelegramMediaFile?
|
||||
if let toEntry, case let .MessageEntry(toMessage, _, _, _, _, _) = toEntry, let file = toMessage.media.first(where: { $0 is TelegramMediaFile }) as? TelegramMediaFile {
|
||||
toFile = file
|
||||
}
|
||||
if fromFile.id == toFile?.id {
|
||||
return .single(false)
|
||||
}
|
||||
|
||||
reorderSavedMusic(.savedMusic(peer: peer, media: fromFile), toFile.flatMap { .savedMusic(peer: peer, media: $0) })
|
||||
|
||||
return .single(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func updatePresentationData(_ presentationData: PresentationData) {
|
||||
self.presentationData = presentationData
|
||||
|
||||
|
|
@ -378,6 +501,128 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
self.controlsNode.updatePresentationData(self.presentationData)
|
||||
}
|
||||
|
||||
private func dismissAllTooltips() {
|
||||
guard let controller = self.getParentController() else {
|
||||
return
|
||||
}
|
||||
controller.window?.forEachController({ controller in
|
||||
if let controller = controller as? UndoOverlayController {
|
||||
controller.dismissWithCommitAction()
|
||||
}
|
||||
})
|
||||
controller.forEachController({ controller in
|
||||
if let controller = controller as? UndoOverlayController {
|
||||
controller.dismissWithCommitAction()
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func forwardToSavedMessages(file: FileMediaReference) {
|
||||
self.dismissAllTooltips()
|
||||
|
||||
let _ = self.context.engine.messages.enqueueOutgoingMessage(to: self.context.account.peerId, replyTo: nil, content: .file(file)).start()
|
||||
|
||||
let controller = UndoOverlayController(
|
||||
presentationData: self.presentationData,
|
||||
content: .forward(savedMessages: true, text: "Audio forwarded to Saved Messages."),
|
||||
action: { _ in
|
||||
return true
|
||||
}
|
||||
)
|
||||
self.getParentController()?.present(controller, in: .window(.root))
|
||||
}
|
||||
|
||||
func addToSavedMusic(file: FileMediaReference) {
|
||||
self.dismissAllTooltips()
|
||||
|
||||
var actionText: String? = "View"
|
||||
if let itemId = self.controlsNode.currentItemId as? PeerMessagesMediaPlaylistItemId, itemId.messageId.namespace == Namespaces.Message.Local && itemId.messageId.peerId == self.context.account.peerId {
|
||||
actionText = nil
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
let controller = UndoOverlayController(
|
||||
presentationData: self.presentationData,
|
||||
content: .universalImage(
|
||||
image: generateTintedImage(image: UIImage(bundleImageName: "Peer Info/SavedMusic"), color: .white)!,
|
||||
size: nil,
|
||||
title: nil,
|
||||
text: "Audio added to your profile.",
|
||||
customUndoText: actionText,
|
||||
timeout: 3.0
|
||||
),
|
||||
action: { [weak self] action in
|
||||
if let self, case .undo = action {
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let peer else {
|
||||
return
|
||||
}
|
||||
if let controller = self.context.sharedContext.makePeerInfoController(
|
||||
context: self.context,
|
||||
updatedPresentationData: nil,
|
||||
peer: peer._asPeer(),
|
||||
mode: .myProfile,
|
||||
avatarInitiallyExpanded: false,
|
||||
fromChat: false,
|
||||
requestsContext: nil
|
||||
) {
|
||||
if let navigationController = (self.getParentController() as? OverlayAudioPlayerControllerImpl)?.parentNavigationController {
|
||||
self.requestDismiss()
|
||||
navigationController.pushViewController(controller)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return true
|
||||
}
|
||||
)
|
||||
self.getParentController()?.present(controller, in: .window(.root))
|
||||
|
||||
self.updateMusicSaved(file, true)
|
||||
}
|
||||
|
||||
func removeFromSavedMusic(file: FileMediaReference) {
|
||||
self.dismissAllTooltips()
|
||||
|
||||
//TODO:localize
|
||||
let controller = UndoOverlayController(
|
||||
presentationData: self.presentationData,
|
||||
content: .universalImage(
|
||||
image: generateTintedImage(image: UIImage(bundleImageName: "Peer Info/SavedMusic"), color: .white)!,
|
||||
size: nil,
|
||||
title: nil,
|
||||
text: "Audio removed from your profile.",
|
||||
customUndoText: nil,
|
||||
timeout: 3.0
|
||||
),
|
||||
action: { _ in
|
||||
return true
|
||||
}
|
||||
)
|
||||
|
||||
if self.historyNode.originalHistoryView?.entries.count == 1 {
|
||||
if let navigationController = (self.getParentController() as? OverlayAudioPlayerControllerImpl)?.parentNavigationController {
|
||||
self.requestDismiss()
|
||||
navigationController.presentOverlay(controller: controller)
|
||||
|
||||
self.context.sharedContext.mediaManager.setPlaylist(nil, type: self.type, control: .playback(.pause))
|
||||
}
|
||||
} else {
|
||||
self.getParentController()?.present(controller, in: .window(.root))
|
||||
}
|
||||
|
||||
self.updateMusicSaved(file, false)
|
||||
}
|
||||
|
||||
private var isSaved: Bool? {
|
||||
guard let fileReference = self.controlsNode.currentFileReference else {
|
||||
return nil
|
||||
}
|
||||
return self.savedIds?.contains(fileReference.media.fileId.id)
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
self.validLayout = layout
|
||||
|
||||
|
|
@ -397,7 +642,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
|
||||
let maxHeight = layout.size.height - layoutTopInset - floor(56.0 * 0.5)
|
||||
|
||||
let controlsHeight = OverlayPlayerControlsNode.heightForLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, maxHeight: maxHeight, isExpanded: self.controlsNode.isExpanded)
|
||||
let controlsHeight = OverlayPlayerControlsNode.heightForLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, maxHeight: maxHeight, isExpanded: self.controlsNode.isExpanded, hasSectionHeader: true, savedMusic: self.isSaved)
|
||||
|
||||
let listTopInset = layoutTopInset + controlsHeight
|
||||
|
||||
|
|
@ -423,6 +668,8 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
}
|
||||
|
||||
func animateOut(completion: (() -> Void)?) {
|
||||
self.dismissAllTooltips()
|
||||
|
||||
self.layer.animateBoundsOriginYAdditive(from: self.bounds.origin.y, to: -self.bounds.size.height, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
|
||||
completion?()
|
||||
})
|
||||
|
|
@ -479,7 +726,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
@objc func panGesture(_ recognizer: UIPanGestureRecognizer) {
|
||||
switch recognizer.state {
|
||||
case .began:
|
||||
break
|
||||
self.dismissAllTooltips()
|
||||
case .changed:
|
||||
let translation = recognizer.translation(in: self.contentNode.view)
|
||||
var bounds = self.contentNode.bounds
|
||||
|
|
@ -533,7 +780,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
|
||||
let maxHeight = validLayout.size.height - layoutTopInset - floor(56.0 * 0.5)
|
||||
|
||||
let controlsHeight = self.controlsNode.updateLayout(width: validLayout.size.width, leftInset: validLayout.safeInsets.left, rightInset: validLayout.safeInsets.right, maxHeight: maxHeight, transition: transition)
|
||||
let controlsHeight = self.controlsNode.updateLayout(width: validLayout.size.width, leftInset: validLayout.safeInsets.left, rightInset: validLayout.safeInsets.right, maxHeight: maxHeight, hasSectionHeader: true, savedMusic: self.isSaved, transition: transition)
|
||||
|
||||
let listTopInset = layoutTopInset + controlsHeight
|
||||
|
||||
|
|
@ -580,7 +827,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
}
|
||||
|
||||
let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil)
|
||||
let historyNode = ChatHistoryListNodeImpl(context: self.context, updatedPresentationData: (self.context.sharedContext.currentPresentationData.with({ $0 }), self.context.sharedContext.presentationData), chatLocation: self.chatLocation, chatLocationContextHolder: chatLocationContextHolder, adMessagesContext: nil, tag: .tag(tagMask), source: .default, subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, reverseGroups: !self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch), isChatPreview: false, messageTransitionNode: { return nil })
|
||||
let historyNode = ChatHistoryListNodeImpl(context: self.context, updatedPresentationData: (self.context.sharedContext.currentPresentationData.with({ $0 }), self.context.sharedContext.presentationData), chatLocation: self.chatLocation, chatLocationContextHolder: chatLocationContextHolder, adMessagesContext: nil, tag: .tag(tagMask), source: self.source, subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, reverseGroups: !self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch), isChatPreview: false, messageTransitionNode: { return nil })
|
||||
historyNode.clipsToBounds = true
|
||||
historyNode.preloadPages = true
|
||||
historyNode.stackFromBottom = true
|
||||
|
|
@ -598,7 +845,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
|
||||
let maxHeight = layout.size.height - layoutTopInset - floor(56.0 * 0.5)
|
||||
|
||||
let controlsHeight = OverlayPlayerControlsNode.heightForLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, maxHeight: maxHeight, isExpanded: self.controlsNode.isExpanded)
|
||||
let controlsHeight = OverlayPlayerControlsNode.heightForLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, maxHeight: maxHeight, isExpanded: self.controlsNode.isExpanded, hasSectionHeader: true, savedMusic: self.isSaved)
|
||||
|
||||
let listTopInset = layoutTopInset + controlsHeight
|
||||
|
||||
|
|
@ -626,6 +873,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
previousHistoryNode.disconnect()
|
||||
self.contentNode.insertSubnode(replacementHistoryNode, belowSubnode: self.historyNode)
|
||||
self.historyNode = replacementHistoryNode
|
||||
self.setupReordering()
|
||||
|
||||
if let validLayout = self.validLayout, let offset = self.replacementHistoryNodeFloatingOffset, let previousOffset = self.floatingHeaderOffset {
|
||||
let offsetDelta = offset - previousOffset
|
||||
|
|
@ -634,7 +882,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
|
||||
let maxHeight = validLayout.size.height - layoutTopInset - floor(56.0 * 0.5)
|
||||
|
||||
let controlsHeight = OverlayPlayerControlsNode.heightForLayout(width: validLayout.size.width, leftInset: validLayout.safeInsets.left, rightInset: validLayout.safeInsets.right, maxHeight: maxHeight, isExpanded: self.controlsNode.isExpanded)
|
||||
let controlsHeight = OverlayPlayerControlsNode.heightForLayout(width: validLayout.size.width, leftInset: validLayout.safeInsets.left, rightInset: validLayout.safeInsets.right, maxHeight: maxHeight, isExpanded: self.controlsNode.isExpanded, hasSectionHeader: true, savedMusic: self.isSaved)
|
||||
|
||||
let listTopInset = layoutTopInset + controlsHeight
|
||||
|
||||
|
|
@ -695,7 +943,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
|
||||
let maxHeight = layout.size.height - layoutTopInset - floor(56.0 * 0.5)
|
||||
|
||||
let controlsHeight = OverlayPlayerControlsNode.heightForLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, maxHeight: maxHeight, isExpanded: self.controlsNode.isExpanded)
|
||||
let controlsHeight = OverlayPlayerControlsNode.heightForLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, maxHeight: maxHeight, isExpanded: self.controlsNode.isExpanded, hasSectionHeader: true, savedMusic: self.isSaved)
|
||||
|
||||
let listTopInset = layoutTopInset + controlsHeight
|
||||
|
||||
|
|
@ -712,4 +960,261 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func openMessageContextMenu(message: Message, node: ASDisplayNode, frame: CGRect, recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil, gesture: ContextGesture? = nil, location: CGPoint? = nil) {
|
||||
guard let node = node as? ContextExtractedContentContainingNode, let peer = message.peers[message.id.peerId].flatMap({ PeerReference($0) }), let file = message.media.first(where: { $0 is TelegramMediaFile}) as? TelegramMediaFile else {
|
||||
return
|
||||
}
|
||||
let context = self.context
|
||||
let presentationData = self.presentationData
|
||||
let source: ContextContentSource = .extracted(OverlayAudioPlayerContextExtractedContentSource(contentNode: node))
|
||||
let fileReference: FileMediaReference = message.id.namespace == Namespaces.Message.Local ? .savedMusic(peer: peer, media: file) : .message(message: MessageReference(message), media: file)
|
||||
|
||||
let canSaveToProfile = !(self.savedIds?.contains(file.fileId.id) == true)
|
||||
let canSaveToSavedMessages = message.id.peerId != self.context.account.peerId
|
||||
|
||||
let _ = (context.sharedContext.chatAvailableMessageActions(engine: context.engine, accountPeerId: context.account.peerId, messageIds: [message.id], keepUpdated: false)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] actions in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
//TODO:localize
|
||||
if canSaveToProfile || canSaveToSavedMessages {
|
||||
items.append(
|
||||
.action(ContextMenuActionItem(text: "Save to...", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/DownloadTone"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
|
||||
if let self {
|
||||
var subActions: [ContextMenuItem] = []
|
||||
subActions.append(
|
||||
.action(ContextMenuActionItem(text: presentationData.strings.Common_Back, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) }, iconPosition: .left, action: { c, _ in
|
||||
c?.popItems()
|
||||
}))
|
||||
)
|
||||
subActions.append(.separator)
|
||||
|
||||
if canSaveToProfile {
|
||||
subActions.append(
|
||||
.action(ContextMenuActionItem(text: "…Profile", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
if let self {
|
||||
self.addToSavedMusic(file: fileReference)
|
||||
}
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
||||
if canSaveToSavedMessages {
|
||||
subActions.append(
|
||||
.action(ContextMenuActionItem(text: "…Saved Messages", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
if let self {
|
||||
self.forwardToSavedMessages(file: fileReference)
|
||||
}
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
||||
subActions.append(
|
||||
.action(ContextMenuActionItem(text: "…Files", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
if let self {
|
||||
let disposable: MetaDisposable
|
||||
if let current = self.saveMediaDisposable {
|
||||
disposable = current
|
||||
} else {
|
||||
disposable = MetaDisposable()
|
||||
self.saveMediaDisposable = disposable
|
||||
}
|
||||
disposable.set(
|
||||
saveMediaToFiles(context: context, fileReference: fileReference, present: { [weak self] c, a in
|
||||
if let self, let controller = (self.getParentController() as? OverlayAudioPlayerControllerImpl) {
|
||||
controller.present(c, in: .window(.root), with: a)
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}))
|
||||
)
|
||||
|
||||
let noAction: ((ContextMenuActionItem.Action) -> Void)? = nil
|
||||
subActions.append(
|
||||
.action(ContextMenuActionItem(text: "Choose where you want this audio to be saved.", textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: noAction))
|
||||
)
|
||||
|
||||
c?.pushItems(items: .single(ContextController.Items(content: .list(subActions))))
|
||||
}
|
||||
}))
|
||||
)
|
||||
} else {
|
||||
items.append(.action(ContextMenuActionItem(text: "Save to Files", icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
if let self {
|
||||
let disposable: MetaDisposable
|
||||
if let current = self.saveMediaDisposable {
|
||||
disposable = current
|
||||
} else {
|
||||
disposable = MetaDisposable()
|
||||
self.saveMediaDisposable = disposable
|
||||
}
|
||||
disposable.set(
|
||||
saveMediaToFiles(context: context, fileReference: fileReference, present: { [weak self] c, a in
|
||||
if let self, let controller = (self.getParentController() as? OverlayAudioPlayerControllerImpl) {
|
||||
controller.present(c, in: .window(.root), with: a)
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
||||
items.append(.separator)
|
||||
|
||||
if message.id.namespace == Namespaces.Message.Cloud {
|
||||
items.append(
|
||||
.action(ContextMenuActionItem(text: "Show in Chat", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
context.sharedContext.navigateToChat(accountId: context.account.id, peerId: message.id.peerId, messageId: message.id)
|
||||
self.requestDismiss()
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
||||
// items.append(
|
||||
// .action(ContextMenuActionItem(text: "Forward", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
// f(.default)
|
||||
//
|
||||
// if let _ = self {
|
||||
//
|
||||
// }
|
||||
// }))
|
||||
// )
|
||||
|
||||
var canDelete = false
|
||||
if message.id.namespace == Namespaces.Message.Local {
|
||||
canDelete = true
|
||||
} else if let peer = message.peers[message.id.peerId] {
|
||||
if peer is TelegramUser || peer is TelegramSecretChat {
|
||||
canDelete = true
|
||||
} else if let _ = peer as? TelegramGroup {
|
||||
canDelete = true
|
||||
} else if let channel = peer as? TelegramChannel {
|
||||
if message.flags.contains(.Incoming) {
|
||||
canDelete = channel.hasPermission(.deleteAllMessages)
|
||||
} else {
|
||||
canDelete = true
|
||||
}
|
||||
} else {
|
||||
canDelete = false
|
||||
}
|
||||
} else {
|
||||
canDelete = false
|
||||
}
|
||||
|
||||
if canDelete {
|
||||
var actionTitle = "Delete"
|
||||
if case .custom = self.source {
|
||||
actionTitle = "Remove"
|
||||
}
|
||||
items.append(
|
||||
.action(ContextMenuActionItem(text: actionTitle, textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] c, f in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if message.id.namespace == Namespaces.Message.Local {
|
||||
f(.default)
|
||||
self.removeFromSavedMusic(file: fileReference)
|
||||
} else {
|
||||
c?.setItems(context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: message.id.peerId))
|
||||
|> map { peer -> ContextController.Items in
|
||||
var items: [ContextMenuItem] = []
|
||||
let messageIds = [message.id]
|
||||
|
||||
if let peer {
|
||||
var personalPeerName: String?
|
||||
var isChannel = false
|
||||
if case let .user(user) = peer {
|
||||
personalPeerName = EnginePeer(user).compactDisplayTitle
|
||||
} else if case let .channel(channel) = peer, case .broadcast = channel.info {
|
||||
isChannel = true
|
||||
}
|
||||
|
||||
if actions.options.contains(.deleteGlobally) {
|
||||
let globalTitle: String
|
||||
if isChannel {
|
||||
globalTitle = presentationData.strings.Conversation_DeleteMessagesForEveryone
|
||||
} else if let personalPeerName = personalPeerName {
|
||||
globalTitle = presentationData.strings.Conversation_DeleteMessagesFor(personalPeerName).string
|
||||
} else {
|
||||
globalTitle = presentationData.strings.Conversation_DeleteMessagesForEveryone
|
||||
}
|
||||
items.append(.action(ContextMenuActionItem(text: globalTitle, textColor: .destructive, icon: { _ in nil }, action: { c, f in
|
||||
c?.dismiss(completion: {
|
||||
let _ = context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone).startStandalone()
|
||||
})
|
||||
})))
|
||||
}
|
||||
|
||||
if actions.options.contains(.deleteLocally) {
|
||||
var localOptionText = presentationData.strings.Conversation_DeleteMessagesForMe
|
||||
if context.account.peerId == message.id.peerId {
|
||||
if messageIds.count == 1 {
|
||||
localOptionText = presentationData.strings.Conversation_Moderate_Delete
|
||||
} else {
|
||||
localOptionText = presentationData.strings.Conversation_DeleteManyMessages
|
||||
}
|
||||
}
|
||||
items.append(.action(ContextMenuActionItem(text: localOptionText, textColor: .destructive, icon: { _ in nil }, action: { c, f in
|
||||
c?.dismiss(completion: {
|
||||
let _ = context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forLocalPeer).startStandalone()
|
||||
})
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
return ContextController.Items(content: .list(items))
|
||||
}, minHeight: nil, animated: true)
|
||||
}
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
||||
guard !items.isEmpty else {
|
||||
return
|
||||
}
|
||||
|
||||
let contextController = ContextController(presentationData: presentationData, source: source, items: .single(ContextController.Items(content: .list(items))), recognizer: recognizer, gesture: gesture)
|
||||
self.getParentController()?.presentInGlobalOverlay(contextController)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private final class OverlayAudioPlayerContextExtractedContentSource: ContextExtractedContentSource {
|
||||
let keepInPlace: Bool = false
|
||||
let ignoreContentTouches: Bool = false
|
||||
let blurBackground: Bool = true
|
||||
|
||||
private let contentNode: ContextExtractedContentContainingNode
|
||||
|
||||
init(contentNode: ContextExtractedContentContainingNode) {
|
||||
self.contentNode = contentNode
|
||||
}
|
||||
|
||||
func takeView() -> ContextControllerTakeViewInfo? {
|
||||
return ContextControllerTakeViewInfo(containingItem: .node(self.contentNode), contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||
}
|
||||
|
||||
func putBack() -> ContextControllerPutBackViewInfo? {
|
||||
return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,11 @@ import ContextUI
|
|||
import SliderContextItem
|
||||
import UndoUI
|
||||
import MarqueeComponent
|
||||
import MultilineTextComponent
|
||||
import BundleIconComponent
|
||||
import ButtonComponent
|
||||
import Markdown
|
||||
import TextFormat
|
||||
|
||||
private func normalizeValue(_ value: CGFloat) -> CGFloat {
|
||||
return round(value * 10.0) / 10.0
|
||||
|
|
@ -106,8 +111,8 @@ private func timestampLabelWidthForDuration(_ timestamp: Double) -> CGFloat {
|
|||
return size.width
|
||||
}
|
||||
|
||||
private let titleFont = Font.semibold(18.0)
|
||||
private let descriptionFont = Font.regular(18.0)
|
||||
private let titleFont = Font.semibold(19.0)
|
||||
private let descriptionFont = Font.regular(17.0)
|
||||
|
||||
private func stringsForDisplayData(_ data: SharedMediaPlaybackDisplayData?, presentationData: PresentationData) -> (NSAttributedString?, NSAttributedString?, Bool, NSAttributedString?) {
|
||||
var titleString: NSAttributedString?
|
||||
|
|
@ -141,6 +146,8 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||
private let account: Account
|
||||
private let engine: TelegramEngine
|
||||
private var presentationData: PresentationData
|
||||
private let chatLocation: ChatLocation
|
||||
private let source: ChatHistoryListSource
|
||||
|
||||
private let backgroundNode: ASImageNode
|
||||
|
||||
|
|
@ -154,6 +161,9 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||
private let shareNode: HighlightableButtonNode
|
||||
private let artistButton: HighlightTrackingButtonNode
|
||||
|
||||
private var profileAudio: ComponentView<Empty>?
|
||||
private var cachedChevronImage: (UIImage, PresentationTheme)?
|
||||
|
||||
private let scrubberNode: MediaPlayerScrubbingNode
|
||||
private let leftDurationLabel: MediaPlayerTimeTextNode
|
||||
private let rightDurationLabel: MediaPlayerTimeTextNode
|
||||
|
|
@ -181,12 +191,18 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||
|
||||
let separatorNode: ASDisplayNode
|
||||
|
||||
private let sectionBackground: ASDisplayNode
|
||||
private let sectionTitle: ComponentView<Empty>
|
||||
|
||||
var isExpanded = false
|
||||
var updateIsExpanded: (() -> Void)?
|
||||
|
||||
var requestCollapse: (() -> Void)?
|
||||
var requestShare: ((MessageId) -> Void)?
|
||||
var requestSearchByArtist: ((String) -> Void)?
|
||||
var requestSaveToProfile: ((FileMediaReference) -> Void)?
|
||||
var requestRemoveFromProfile: ((FileMediaReference) -> Void)?
|
||||
var requestLayout: ((ContainedViewLayoutTransition) -> Void)?
|
||||
|
||||
var updateOrder: ((MusicPlaybackSettingsOrder) -> Void)?
|
||||
var control: ((SharedMediaPlayerControlAction) -> Void)?
|
||||
|
|
@ -197,10 +213,13 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||
private var displayData: SharedMediaPlaybackDisplayData?
|
||||
private var currentAlbumArtInitialized = false
|
||||
private var currentAlbumArt: SharedMediaPlaybackAlbumArt?
|
||||
private var currentFileReference: FileMediaReference?
|
||||
private(set) var currentFileReference: FileMediaReference?
|
||||
private var statusDisposable: Disposable?
|
||||
private var chapterDisposable: Disposable?
|
||||
|
||||
private var peerName: String?
|
||||
private var peerDisposable: Disposable?
|
||||
|
||||
private var previousCaption: NSAttributedString?
|
||||
private var chaptersPromise = ValuePromise<[MediaPlayerScrubbingChapter]>([])
|
||||
private var currentChapter: MediaPlayerScrubbingChapter?
|
||||
|
|
@ -215,13 +234,15 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||
private var currentDuration: Double = 0.0
|
||||
private var currentPosition: Double = 0.0
|
||||
|
||||
private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, maxHeight: CGFloat)?
|
||||
private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, maxHeight: CGFloat, hasSectionHeader: Bool, savedMusic: Bool?)?
|
||||
|
||||
init(account: Account, engine: TelegramEngine, accountManager: AccountManager<TelegramAccountManagerTypes>, presentationData: PresentationData, status: Signal<(Account, SharedMediaPlayerItemPlaybackStateOrLoading, MediaManagerPlayerType)?, NoError>) {
|
||||
init(account: Account, engine: TelegramEngine, accountManager: AccountManager<TelegramAccountManagerTypes>, presentationData: PresentationData, status: Signal<(Account, SharedMediaPlayerItemPlaybackStateOrLoading, MediaManagerPlayerType)?, NoError>, chatLocation: ChatLocation, source: ChatHistoryListSource) {
|
||||
self.accountManager = accountManager
|
||||
self.account = account
|
||||
self.engine = engine
|
||||
self.presentationData = presentationData
|
||||
self.chatLocation = chatLocation
|
||||
self.source = source
|
||||
|
||||
self.backgroundNode = ASImageNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
|
|
@ -240,7 +261,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||
self.titleNode.displaysAsynchronously = false
|
||||
|
||||
self.title = ComponentView<Empty>()
|
||||
|
||||
|
||||
self.descriptionNode = TextNode()
|
||||
self.descriptionNode.isUserInteractionEnabled = false
|
||||
self.descriptionNode.displaysAsynchronously = false
|
||||
|
|
@ -293,6 +314,11 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||
self.separatorNode.isLayerBacked = true
|
||||
self.separatorNode.backgroundColor = presentationData.theme.list.itemPlainSeparatorColor
|
||||
|
||||
self.sectionBackground = ASDisplayNode()
|
||||
self.sectionBackground.backgroundColor = presentationData.theme.chatList.sectionHeaderFillColor
|
||||
|
||||
self.sectionTitle = ComponentView<Empty>()
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
|
|
@ -318,6 +344,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||
self.addSubnode(self.playPauseButton)
|
||||
self.playPauseButton.addSubnode(self.playPauseIconNode)
|
||||
|
||||
self.addSubnode(self.sectionBackground)
|
||||
self.addSubnode(self.separatorNode)
|
||||
|
||||
let accountId = account.id
|
||||
|
|
@ -369,7 +396,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||
strongSelf.infoNodePushed = infoNodePushed
|
||||
|
||||
if let layout = strongSelf.validLayout {
|
||||
let _ = strongSelf.updateLayout(width: layout.0, leftInset: layout.1, rightInset: layout.2, maxHeight: layout.3, transition: .animated(duration: 0.35, curve: .spring))
|
||||
let _ = strongSelf.updateLayout(width: layout.0, leftInset: layout.1, rightInset: layout.2, maxHeight: layout.3, hasSectionHeader: layout.4, savedMusic: layout.5, transition: .animated(duration: 0.35, curve: .spring))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -379,6 +406,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
var itemUpdated = false
|
||||
var valueItemId: SharedMediaPlaylistItemId?
|
||||
if let (_, value, _) = value, case let .state(state) = value {
|
||||
valueItemId = state.item.id
|
||||
|
|
@ -386,6 +414,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||
if !areSharedMediaPlaylistItemIdsEqual(valueItemId, strongSelf.currentItemId) {
|
||||
strongSelf.currentItemId = valueItemId
|
||||
strongSelf.scrubberNode.ignoreSeekId = nil
|
||||
itemUpdated = true
|
||||
}
|
||||
|
||||
var rateButtonIsHidden = true
|
||||
|
|
@ -445,7 +474,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||
if duration != strongSelf.currentDuration && !duration.isZero {
|
||||
strongSelf.currentDuration = duration
|
||||
if let layout = strongSelf.validLayout {
|
||||
let _ = strongSelf.updateLayout(width: layout.0, leftInset: layout.1, rightInset: layout.2, maxHeight: layout.3, transition: .immediate)
|
||||
let _ = strongSelf.updateLayout(width: layout.0, leftInset: layout.1, rightInset: layout.2, maxHeight: layout.3, hasSectionHeader: layout.4, savedMusic: layout.5, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -485,8 +514,25 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||
|
||||
strongSelf.shareNode.isHidden = !canShare
|
||||
}
|
||||
|
||||
if itemUpdated {
|
||||
strongSelf.requestLayout?(.animated(duration: 0.2, curve: .easeInOut))
|
||||
}
|
||||
})
|
||||
|
||||
if case .custom = self.source, case let .peer(peerId) = self.chatLocation, peerId != account.peerId {
|
||||
self.peerDisposable = (engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let peer else {
|
||||
return
|
||||
}
|
||||
self.peerName = peer.compactDisplayTitle
|
||||
if let layout = self.validLayout {
|
||||
let _ = self.updateLayout(width: layout.0, leftInset: layout.1, rightInset: layout.2, maxHeight: layout.3, hasSectionHeader: layout.4, savedMusic: layout.5, transition: .immediate)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
self.chapterDisposable = combineLatest(queue: Queue.mainQueue(), mappedStatus, self.chaptersPromise.get())
|
||||
.startStrict(next: { [weak self] status, chapters in
|
||||
if let strongSelf = self, status.duration > 1.0, chapters.count > 0 {
|
||||
|
|
@ -533,7 +579,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||
strongSelf.infoNode.attributedText = NSAttributedString(string: chapter.title, font: Font.regular(13.0), textColor: strongSelf.presentationData.theme.list.itemSecondaryTextColor)
|
||||
|
||||
if let layout = strongSelf.validLayout {
|
||||
let _ = strongSelf.updateLayout(width: layout.0, leftInset: layout.1, rightInset: layout.2, maxHeight: layout.3, transition: .immediate)
|
||||
let _ = strongSelf.updateLayout(width: layout.0, leftInset: layout.1, rightInset: layout.2, maxHeight: layout.3, hasSectionHeader: layout.4, savedMusic: layout.5, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -578,6 +624,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||
self.statusDisposable?.dispose()
|
||||
self.chapterDisposable?.dispose()
|
||||
self.scrubbingDisposable?.dispose()
|
||||
self.peerDisposable?.dispose()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
|
|
@ -704,14 +751,15 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||
self.updateRateButton(rate)
|
||||
}
|
||||
self.separatorNode.backgroundColor = presentationData.theme.list.itemPlainSeparatorColor
|
||||
self.sectionBackground.backgroundColor = presentationData.theme.chatList.sectionHeaderFillColor
|
||||
}
|
||||
|
||||
private func updateLabels(transition: ContainedViewLayoutTransition) {
|
||||
guard let (width, leftInset, rightInset, maxHeight) = self.validLayout else {
|
||||
guard let (width, leftInset, rightInset, maxHeight, _, _) = self.validLayout else {
|
||||
return
|
||||
}
|
||||
|
||||
let panelHeight = OverlayPlayerControlsNode.heightForLayout(width: width, leftInset: leftInset, rightInset: rightInset, maxHeight: maxHeight, isExpanded: self.isExpanded)
|
||||
let panelHeight = OverlayPlayerControlsNode.heightForLayout(width: width, leftInset: leftInset, rightInset: rightInset, maxHeight: maxHeight, isExpanded: self.isExpanded, hasSectionHeader: false, savedMusic: nil)
|
||||
|
||||
let sideInset: CGFloat = 20.0
|
||||
|
||||
|
|
@ -740,13 +788,13 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||
MarqueeComponent(attributedText: titleString ?? NSAttributedString())
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: width - sideInset * 2.0 - leftInset - rightInset - infoLabelsLeftInset - infoLabelsRightInset + MarqueeComponent.innerPadding, height: CGFloat.greatestFiniteMagnitude)
|
||||
containerSize: CGSize(width: width - sideInset * 2.0 - leftInset - rightInset - infoLabelsLeftInset - infoLabelsRightInset, height: CGFloat.greatestFiniteMagnitude)
|
||||
)
|
||||
if let titleView = self.title.view {
|
||||
if titleView.superview == nil {
|
||||
self.view.addSubview(titleView)
|
||||
}
|
||||
transition.updateFrame(view: titleView, frame: CGRect(origin: CGPoint(x: self.isExpanded ? floor((width - titleSize.width) / 2.0) : (leftInset + sideInset + infoLabelsLeftInset) - MarqueeComponent.innerPadding, y: infoVerticalOrigin + 1.0), size: titleSize))
|
||||
transition.updateFrame(view: titleView, frame: CGRect(origin: CGPoint(x: self.isExpanded ? floor((width - titleSize.width) / 2.0) : (leftInset + sideInset + infoLabelsLeftInset), y: infoVerticalOrigin), size: titleSize))
|
||||
}
|
||||
|
||||
let makeDescriptionLayout = TextNode.asyncLayout(self.descriptionNode)
|
||||
|
|
@ -755,7 +803,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: self.isExpanded ? floor((width - titleLayout.size.width) / 2.0) : (leftInset + sideInset + infoLabelsLeftInset), y: infoVerticalOrigin + 1.0), size: titleLayout.size))
|
||||
let _ = titleApply()
|
||||
|
||||
let descriptionFrame = CGRect(origin: CGPoint(x: self.isExpanded ? floor((width - descriptionLayout.size.width) / 2.0) : (leftInset + sideInset + infoLabelsLeftInset), y: infoVerticalOrigin + 24.0), size: descriptionLayout.size)
|
||||
let descriptionFrame = CGRect(origin: CGPoint(x: self.isExpanded ? floor((width - descriptionLayout.size.width) / 2.0) : (leftInset + sideInset + infoLabelsLeftInset), y: infoVerticalOrigin + 25.0), size: descriptionLayout.size)
|
||||
transition.updateFrame(node: self.descriptionNode, frame: descriptionFrame)
|
||||
let _ = descriptionApply()
|
||||
|
||||
|
|
@ -819,20 +867,30 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
static let basePanelHeight: CGFloat = 220.0
|
||||
static let sectionHeaderHeight: CGFloat = 28.0
|
||||
|
||||
static func heightForLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, maxHeight: CGFloat, isExpanded: Bool) -> CGFloat {
|
||||
static func heightForLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, maxHeight: CGFloat, isExpanded: Bool, hasSectionHeader: Bool, savedMusic: Bool?) -> CGFloat {
|
||||
var panelHeight: CGFloat = OverlayPlayerControlsNode.basePanelHeight
|
||||
if isExpanded {
|
||||
let sideInset: CGFloat = 20.0
|
||||
panelHeight += width - leftInset - rightInset - sideInset * 2.0 + 24.0
|
||||
}
|
||||
return min(panelHeight, maxHeight)
|
||||
var height = min(panelHeight, maxHeight)
|
||||
if hasSectionHeader {
|
||||
height += sectionHeaderHeight
|
||||
}
|
||||
if let savedMusic {
|
||||
height += savedMusic ? 38.0 : 70.0
|
||||
}
|
||||
return height
|
||||
}
|
||||
|
||||
func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, maxHeight: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
self.validLayout = (width, leftInset, rightInset, maxHeight)
|
||||
func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, maxHeight: CGFloat, hasSectionHeader: Bool, savedMusic: Bool?, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
let previousSavedMusic = self.validLayout?.savedMusic
|
||||
self.validLayout = (width, leftInset, rightInset, maxHeight, hasSectionHeader, savedMusic)
|
||||
|
||||
let panelHeight = OverlayPlayerControlsNode.heightForLayout(width: width, leftInset: leftInset, rightInset: rightInset, maxHeight: maxHeight, isExpanded: self.isExpanded)
|
||||
let finalPanelHeight = OverlayPlayerControlsNode.heightForLayout(width: width, leftInset: leftInset, rightInset: rightInset, maxHeight: maxHeight, isExpanded: self.isExpanded, hasSectionHeader: hasSectionHeader, savedMusic: savedMusic)
|
||||
let panelHeight = OverlayPlayerControlsNode.heightForLayout(width: width, leftInset: leftInset, rightInset: rightInset, maxHeight: maxHeight, isExpanded: self.isExpanded, hasSectionHeader: false, savedMusic: nil)
|
||||
|
||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelHeight), size: CGSize(width: width, height: UIScreenPixel)))
|
||||
|
||||
|
|
@ -841,6 +899,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||
let sideInset: CGFloat = 20.0
|
||||
let sideButtonsInset: CGFloat = sideInset + 36.0
|
||||
|
||||
|
||||
let infoVerticalOrigin: CGFloat = panelHeight - OverlayPlayerControlsNode.basePanelHeight + 36.0
|
||||
|
||||
self.updateLabels(transition: transition)
|
||||
|
|
@ -941,7 +1000,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||
let rateRightOffset = timestampLabelWidthForDuration(self.currentDuration)
|
||||
transition.updateFrame(node: self.rateButton, frame: CGRect(origin: CGPoint(x: width - sideInset - rightInset - rateRightOffset - 28.0, y: scrubberVerticalOrigin + 10.0 + rightLabelVerticalOffset - 10.0), size: CGSize(width: 24.0, height: 44.0)))
|
||||
|
||||
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -8.0), size: CGSize(width: width, height: panelHeight + 8.0)))
|
||||
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -8.0), size: CGSize(width: width, height: finalPanelHeight + 8.0)))
|
||||
|
||||
let buttonSize = CGSize(width: 64.0, height: 64.0)
|
||||
let buttonsWidth = min(width - leftInset - rightInset - sideButtonsInset * 2.0, 320.0)
|
||||
|
|
@ -956,8 +1015,160 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||
let playPauseFrame = CGRect(origin: CGPoint(x: buttonsRect.minX + floor((buttonsRect.width - buttonSize.width) / 2.0), y: buttonsRect.minY), size: buttonSize)
|
||||
transition.updateFrame(node: self.playPauseButton, frame: playPauseFrame)
|
||||
transition.updateFrame(node: self.playPauseIconNode, frame: CGRect(origin: CGPoint(x: -6.0, y: -6.0), size: CGSize(width: 76.0, height: 76.0)))
|
||||
|
||||
var sectionHeaderTransition = transition
|
||||
if self.sectionTitle.view?.superview == nil {
|
||||
sectionHeaderTransition = .immediate
|
||||
}
|
||||
|
||||
return panelHeight
|
||||
sectionHeaderTransition.updateFrame(node: self.sectionBackground, frame: CGRect(origin: CGPoint(x: 0.0, y: finalPanelHeight - OverlayPlayerControlsNode.sectionHeaderHeight), size: CGSize(width: width, height: OverlayPlayerControlsNode.sectionHeaderHeight)))
|
||||
|
||||
self.separatorNode.isHidden = hasSectionHeader
|
||||
|
||||
if hasSectionHeader {
|
||||
//TODO:localize
|
||||
var sectionTitle = "AUDIO IN THIS CHAT"
|
||||
if let peerName = self.peerName {
|
||||
sectionTitle = "\(peerName)'S PLAYLIST"
|
||||
} else if case .custom = self.source {
|
||||
sectionTitle = "YOUR PLAYLIST"
|
||||
}
|
||||
let sectionTitleSize = self.sectionTitle.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: sectionTitle, font: Font.regular(13.0), textColor: self.presentationData.theme.chatList.sectionHeaderTextColor)))
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: width, height: OverlayPlayerControlsNode.sectionHeaderHeight)
|
||||
)
|
||||
if let sectionTitleView = self.sectionTitle.view {
|
||||
if sectionTitleView.superview == nil {
|
||||
self.view.addSubview(sectionTitleView)
|
||||
}
|
||||
sectionTitleView.bounds = CGRect(origin: .zero, size: sectionTitleSize)
|
||||
sectionHeaderTransition.updateFrame(view: sectionTitleView, frame: CGRect(origin: CGPoint(x: leftInset + 16.0, y: finalPanelHeight - OverlayPlayerControlsNode.sectionHeaderHeight + 6.0 + UIScreenPixel), size: sectionTitleSize))
|
||||
}
|
||||
} else if let sectionTitleView = self.sectionTitle.view, sectionTitleView.superview != nil {
|
||||
sectionTitleView.removeFromSuperview()
|
||||
}
|
||||
|
||||
if let savedMusic {
|
||||
var profileAudioTransition = transition
|
||||
var animateIn = false
|
||||
if previousSavedMusic != savedMusic, let profileAudio {
|
||||
self.profileAudio = nil
|
||||
if let profileAudioView = profileAudio.view {
|
||||
if transition.isAnimated {
|
||||
profileAudioView.layer.animateScale(from: 1.0, to: 0.0, duration: 0.25)
|
||||
profileAudioView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||
profileAudioView.removeFromSuperview()
|
||||
})
|
||||
animateIn = true
|
||||
} else {
|
||||
profileAudioView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.profileAudio == nil {
|
||||
profileAudioTransition = .immediate
|
||||
}
|
||||
let profileAudio: ComponentView<Empty> = self.profileAudio ?? {
|
||||
let componentView = ComponentView<Empty>()
|
||||
self.profileAudio = componentView
|
||||
return componentView
|
||||
}()
|
||||
|
||||
let profileAudioComponent: AnyComponent<Empty>
|
||||
var profileAudioOffset: CGFloat = 0.0
|
||||
if savedMusic {
|
||||
if self.cachedChevronImage == nil || self.cachedChevronImage?.1 !== self.presentationData.theme {
|
||||
self.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: self.presentationData.theme.list.itemAccentColor)!, self.presentationData.theme)
|
||||
}
|
||||
let textFont = Font.regular(13.0)
|
||||
let textColor = self.presentationData.theme.list.itemSecondaryTextColor
|
||||
let linkColor = self.presentationData.theme.list.itemAccentColor
|
||||
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: textFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in
|
||||
return (TelegramTextAttributes.URL, contents)
|
||||
})
|
||||
|
||||
let attributedString = parseMarkdownIntoAttributedString("This audio is visible on your profile. [Remove >]()", attributes: markdownAttributes, textAlignment: .center).mutableCopy() as! NSMutableAttributedString
|
||||
if let range = attributedString.string.range(of: ">"), let chevronImage = self.cachedChevronImage?.0 {
|
||||
attributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: attributedString.string))
|
||||
attributedString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: attributedString.string))
|
||||
}
|
||||
profileAudioComponent = AnyComponent(MultilineTextComponent(
|
||||
text: .plain(attributedString),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 5,
|
||||
lineSpacing: 0.2,
|
||||
highlightColor: linkColor.withAlphaComponent(0.1),
|
||||
highlightInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: -8.0),
|
||||
highlightAction: { attributes in
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
|
||||
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
},
|
||||
tapAction: { [weak self] attributes, _ in
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||
if let file = self?.currentFileReference {
|
||||
self?.requestRemoveFromProfile?(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
))
|
||||
profileAudioOffset = 18.0
|
||||
} else {
|
||||
//TODO:localize
|
||||
profileAudioComponent = AnyComponent(ButtonComponent(
|
||||
background: ButtonComponent.Background(
|
||||
color: self.presentationData.theme.list.itemCheckColors.fillColor,
|
||||
foreground: self.presentationData.theme.list.itemCheckColors.foregroundColor,
|
||||
pressedColor: self.presentationData.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9),
|
||||
cornerRadius: 10.0
|
||||
),
|
||||
content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(
|
||||
HStack([
|
||||
AnyComponentWithIdentity(id: "icon", component: AnyComponent(
|
||||
BundleIconComponent(name: "Peer Info/SaveMusic", tintColor: self.presentationData.theme.list.itemCheckColors.foregroundColor)
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "label", component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: "Add to Profile", font: Font.semibold(17.0), textColor: self.presentationData.theme.list.itemCheckColors.foregroundColor)))
|
||||
))
|
||||
], spacing: 8.0)
|
||||
)),
|
||||
action: { [weak self] in
|
||||
if let file = self?.currentFileReference {
|
||||
self?.requestSaveToProfile?(file)
|
||||
}
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
let profileAudioSize = profileAudio.update(
|
||||
transition: .immediate,
|
||||
component: profileAudioComponent,
|
||||
environment: {},
|
||||
containerSize: CGSize(width: width - leftInset - rightInset - 32.0, height: 50.0)
|
||||
)
|
||||
let profileAudioOrigin: CGFloat = finalPanelHeight + profileAudioOffset - (hasSectionHeader ? OverlayPlayerControlsNode.sectionHeaderHeight : 0.0) - 42.0 - floorToScreenPixels(profileAudioSize.height / 2.0)
|
||||
let profileAudioFrame = CGRect(origin: CGPoint(x: floor((width - profileAudioSize.width) / 2.0), y: profileAudioOrigin), size: profileAudioSize)
|
||||
if let profileAudioView = profileAudio.view {
|
||||
if profileAudioView.superview == nil {
|
||||
self.view.addSubview(profileAudioView)
|
||||
|
||||
if animateIn {
|
||||
profileAudioView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||
profileAudioView.layer.animateScale(from: 0.0, to: 1.0, duration: 0.3)
|
||||
}
|
||||
}
|
||||
profileAudioTransition.updateFrame(view: profileAudioView, frame: profileAudioFrame)
|
||||
}
|
||||
}
|
||||
|
||||
return finalPanelHeight
|
||||
}
|
||||
|
||||
func collapse() {
|
||||
|
|
|
|||
|
|
@ -427,7 +427,7 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
|||
self.messagesLocation = location
|
||||
|
||||
switch self.messagesLocation {
|
||||
case let .messages(_, _, messageId), let .singleMessage(messageId), let .custom(_, messageId, _):
|
||||
case let .messages(_, _, messageId), let .singleMessage(messageId), let .custom(_, _, messageId, _):
|
||||
self.loadItem(anchor: .messageId(messageId), navigation: .later, reversed: self.order == .reversed)
|
||||
case let .recentActions(message):
|
||||
self.loadingItem = false
|
||||
|
|
@ -521,7 +521,8 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
|||
}
|
||||
}
|
||||
self.stateValue.set(.single(SharedMediaPlaylistState(loading: self.loadingItem, playedToEnd: self.playedToEnd, item: item, nextItem: nextItem, previousItem: previousItem, order: self.order, looping: self.looping)))
|
||||
if item?.message.id != self.currentlyObservedMessageId {
|
||||
if case .custom = self.messagesLocation {
|
||||
} else if item?.message.id != self.currentlyObservedMessageId {
|
||||
self.currentlyObservedMessageId = item?.message.id
|
||||
if let id = item?.message.id {
|
||||
self.currentlyObservedMessageDisposable.set((self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Messages.Message(id: id))
|
||||
|
|
@ -593,7 +594,7 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
|||
strongSelf.updateState()
|
||||
}
|
||||
}))
|
||||
case let .custom(messages, at, _):
|
||||
case let .custom(messages, _, at, _):
|
||||
self.navigationDisposable.set((messages
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] messages in
|
||||
|
|
@ -769,7 +770,7 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
|||
self.loadingItem = false
|
||||
self.currentItem = (message, [])
|
||||
self.updateState()
|
||||
case let .custom(messages, _, loadMore):
|
||||
case let .custom(messages, _, _, loadMore):
|
||||
let inputIndex: Signal<MessageIndex, NoError>
|
||||
let looping = self.looping
|
||||
switch self.order {
|
||||
|
|
|
|||
128
submodules/TelegramUI/Sources/SaveMediaToFiles.swift
Normal file
128
submodules/TelegramUI/Sources/SaveMediaToFiles.swift
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
import Foundation
|
||||
import AVFoundation
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import AccountContext
|
||||
import OverlayStatusController
|
||||
import LegacyMediaPickerUI
|
||||
import SaveToCameraRoll
|
||||
import PresentationDataUtils
|
||||
|
||||
func saveMediaToFiles(context: AccountContext, fileReference: FileMediaReference, present: @escaping (ViewController, Any?) -> Void) -> Disposable {
|
||||
var title: String?
|
||||
var performer: String?
|
||||
for attribute in fileReference.media.attributes {
|
||||
if case let .Audio(_, _, titleValue, performerValue, _) = attribute {
|
||||
if let titleValue, !titleValue.isEmpty {
|
||||
title = titleValue
|
||||
}
|
||||
if let performerValue, !performerValue.isEmpty {
|
||||
performer = performerValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var signal = fetchMediaData(context: context, postbox: context.account.postbox, userLocation: .other, mediaReference: fileReference.abstract)
|
||||
|
||||
var cancelImpl: (() -> Void)?
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let progressSignal = Signal<Never, NoError> { subscriber in
|
||||
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
|
||||
cancelImpl?()
|
||||
}))
|
||||
present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
return ActionDisposable { [weak controller] in
|
||||
Queue.mainQueue().async() {
|
||||
controller?.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|> runOn(Queue.mainQueue())
|
||||
|> delay(0.15, queue: Queue.mainQueue())
|
||||
|
||||
let progressDisposable = progressSignal.startStrict()
|
||||
|
||||
let disposable = MetaDisposable()
|
||||
signal = signal
|
||||
|> afterDisposed {
|
||||
Queue.mainQueue().async {
|
||||
progressDisposable.dispose()
|
||||
}
|
||||
}
|
||||
cancelImpl = { [weak disposable] in
|
||||
disposable?.set(nil)
|
||||
}
|
||||
disposable.set((signal
|
||||
|> deliverOnMainQueue).startStrict(next: { state, _ in
|
||||
switch state {
|
||||
case .progress:
|
||||
break
|
||||
case let .data(data):
|
||||
if data.complete {
|
||||
var symlinkPath = data.path + ".mp3"
|
||||
if fileSize(symlinkPath) != nil {
|
||||
try? FileManager.default.removeItem(atPath: symlinkPath)
|
||||
}
|
||||
let _ = try? FileManager.default.linkItem(atPath: data.path, toPath: symlinkPath)
|
||||
|
||||
let audioUrl = URL(fileURLWithPath: symlinkPath)
|
||||
let audioAsset = AVURLAsset(url: audioUrl)
|
||||
|
||||
var fileExtension = "mp3"
|
||||
if let filename = fileReference.media.fileName {
|
||||
if let dotIndex = filename.lastIndex(of: ".") {
|
||||
fileExtension = String(filename[filename.index(after: dotIndex)...])
|
||||
}
|
||||
}
|
||||
|
||||
var nameComponents: [String] = []
|
||||
if let title {
|
||||
if let performer {
|
||||
nameComponents.append(performer)
|
||||
}
|
||||
nameComponents.append(title)
|
||||
} else {
|
||||
var artist: String?
|
||||
var title: String?
|
||||
for data in audioAsset.commonMetadata {
|
||||
if data.commonKey == .commonKeyArtist {
|
||||
artist = data.stringValue
|
||||
}
|
||||
if data.commonKey == .commonKeyTitle {
|
||||
title = data.stringValue
|
||||
}
|
||||
}
|
||||
if let artist, !artist.isEmpty {
|
||||
nameComponents.append(artist)
|
||||
}
|
||||
if let title, !title.isEmpty {
|
||||
nameComponents.append(title)
|
||||
}
|
||||
if nameComponents.isEmpty, var filename = fileReference.media.fileName {
|
||||
if let dotIndex = filename.lastIndex(of: ".") {
|
||||
filename = String(filename[..<dotIndex])
|
||||
}
|
||||
nameComponents.append(filename)
|
||||
}
|
||||
}
|
||||
if !nameComponents.isEmpty {
|
||||
try? FileManager.default.removeItem(atPath: symlinkPath)
|
||||
|
||||
let fileName = "\(nameComponents.joined(separator: " – ")).\(fileExtension)"
|
||||
symlinkPath = symlinkPath.replacingOccurrences(of: audioUrl.lastPathComponent, with: fileName)
|
||||
let _ = try? FileManager.default.linkItem(atPath: data.path, toPath: symlinkPath)
|
||||
}
|
||||
|
||||
let url = URL(fileURLWithPath: symlinkPath)
|
||||
let controller = legacyICloudFilePicker(theme: presentationData.theme, mode: .export, url: url, documentTypes: [], forceDarkTheme: false, dismissed: {}, completion: { _ in
|
||||
|
||||
})
|
||||
present(controller, nil)
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
return disposable
|
||||
}
|
||||
|
|
@ -1786,8 +1786,11 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
})
|
||||
}
|
||||
|
||||
public func makeOverlayAudioPlayerController(context: AccountContext, chatLocation: ChatLocation, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, playlistLocation: SharedMediaPlaylistLocation?, parentNavigationController: NavigationController?) -> ViewController & OverlayAudioPlayerController {
|
||||
return OverlayAudioPlayerControllerImpl(context: context, chatLocation: chatLocation, type: type, initialMessageId: initialMessageId, initialOrder: initialOrder, playlistLocation: playlistLocation, parentNavigationController: parentNavigationController)
|
||||
public func makeOverlayAudioPlayerController(context: AccountContext, chatLocation: ChatLocation, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, playlistLocation: SharedMediaPlaylistLocation?, parentNavigationController: NavigationController?, updateMusicSaved: ((FileMediaReference, Bool) -> Void)?, reorderSavedMusic: ((FileMediaReference, FileMediaReference?) -> Void)?) -> ViewController & OverlayAudioPlayerController {
|
||||
if let playlistLocation = playlistLocation as? PeerMessagesPlaylistLocation, case .custom = playlistLocation {
|
||||
context.sharedContext.mediaManager.setPlaylist((context, PeerMessagesMediaPlaylist(context: context, location: playlistLocation, chatLocationContextHolder: nil)), type: .music, control: .playback(.play))
|
||||
}
|
||||
return OverlayAudioPlayerControllerImpl(context: context, chatLocation: chatLocation, type: type, initialMessageId: initialMessageId, initialOrder: initialOrder, playlistLocation: playlistLocation, parentNavigationController: parentNavigationController, updateMusicSaved: updateMusicSaved, reorderSavedMusic: reorderSavedMusic)
|
||||
}
|
||||
|
||||
public func makeTempAccountContext(account: Account) -> AccountContext {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue