mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-05 19:28:46 +02:00
Various improvements
This commit is contained in:
parent
8ac0f3d9e3
commit
ea4bcd9a46
23 changed files with 733 additions and 165 deletions
|
|
@ -1389,7 +1389,7 @@ public protocol SharedAccountContext: AnyObject {
|
|||
func makeBirthdayPrivacyController(context: AccountContext, settings: Promise<AccountPrivacySettings?>, openedFromBirthdayScreen: Bool, present: @escaping (ViewController) -> Void)
|
||||
func makeSetupTwoFactorAuthController(context: AccountContext) -> ViewController
|
||||
func makeStorageManagementController(context: AccountContext) -> ViewController
|
||||
func makeAttachmentFileController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, audio: Bool, bannedSendMedia: (Int32, Bool)?, presentGallery: @escaping () -> Void, presentFiles: @escaping () -> Void, presentDocumentScanner: (() -> Void)?, send: @escaping (AnyMediaReference) -> Void) -> AttachmentFileController
|
||||
func makeAttachmentFileController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, audio: Bool, bannedSendMedia: (Int32, Bool)?, presentGallery: @escaping () -> Void, presentFiles: @escaping () -> Void, presentDocumentScanner: (() -> Void)?, send: @escaping ([AnyMediaReference]) -> Void) -> AttachmentFileController
|
||||
func makeGalleryCaptionPanelView(context: AccountContext, chatLocation: ChatLocation, isScheduledMessages: Bool, isFile: Bool, hasTimer: Bool, customEmojiAvailable: Bool, present: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void) -> NSObject?
|
||||
func makeHashtagSearchController(context: AccountContext, peer: EnginePeer?, query: String, stories: Bool, forceDark: Bool) -> ViewController
|
||||
func makeStorySearchController(context: AccountContext, scope: StorySearchControllerScope, listContext: SearchStoryListContext?) -> ViewController
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/EdgeEffect",
|
||||
"//submodules/TelegramUI/Components/LiquidLens",
|
||||
"//submodules/TelegramUI/Components/TabSelectionRecognizer",
|
||||
"//submodules/TelegramBaseController",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -1387,7 +1387,7 @@ public class AttachmentController: ViewController, MinimizableController {
|
|||
// let previousHasButton = self.hasButton
|
||||
let hasButton = self.panel.isButtonVisible && !self.isDismissing
|
||||
self.hasButton = hasButton
|
||||
if let controller = self.controller, controller.buttons.count > 1 || controller.hasTextInput {
|
||||
if let controller = self.controller, controller.buttons.count > 1 || controller.hasTextInput || self.panel.hasMediaAccessoryPanel {
|
||||
hasPanel = true
|
||||
}
|
||||
if !self.isPanelVisible {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import SwiftSignalKit
|
|||
import Postbox
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import AccountContext
|
||||
import AttachmentTextInputPanelNode
|
||||
import ChatPresentationInterfaceState
|
||||
|
|
@ -25,6 +26,10 @@ import ReactionSelectionNode
|
|||
import TopMessageReactions
|
||||
import GlassBackgroundComponent
|
||||
import LiquidLens
|
||||
import UniversalMediaPlayer
|
||||
import TelegramBaseController
|
||||
import OverlayStatusController
|
||||
import UndoUI
|
||||
import TabSelectionRecognizer
|
||||
import EmojiTextAttachmentView
|
||||
|
||||
|
|
@ -233,10 +238,10 @@ private final class AttachButtonComponent: CombinedComponent {
|
|||
case .sticker:
|
||||
//TODO:localize
|
||||
name = "Sticker"
|
||||
imageName = "Chat/Attach Menu/Gift"
|
||||
imageName = "Chat/Attach Menu/Sticker"
|
||||
case .emoji:
|
||||
name = "Emoji"
|
||||
imageName = "Chat/Attach Menu/Reply"
|
||||
imageName = "Chat/Attach Menu/Emoji"
|
||||
case .audio:
|
||||
name = "Audio"
|
||||
imageName = "Chat/Attach Menu/Audio"
|
||||
|
|
@ -967,8 +972,14 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
|||
private var updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
|
||||
private var presentationDataDisposable: Disposable?
|
||||
private var peerDisposable: Disposable?
|
||||
private var mediaStatusDisposable: Disposable?
|
||||
private var playlistPreloadDisposable: Disposable?
|
||||
|
||||
private var iconDisposables: [MediaId: Disposable] = [:]
|
||||
private var playlistStateAndType: (SharedMediaPlaylistItem, SharedMediaPlaylistItem?, SharedMediaPlaylistItem?, MusicPlaybackSettingsOrder, MediaManagerPlayerType, Account)?
|
||||
private var playlistLocation: SharedMediaPlaylistLocation?
|
||||
private var mediaAccessoryPanel: (MediaNavigationAccessoryPanel, MediaManagerPlayerType)?
|
||||
private var dismissingMediaAccessoryPanel: ASDisplayNode?
|
||||
|
||||
private var presentationInterfaceState: ChatPresentationInterfaceState
|
||||
private var interfaceInteraction: ChatPanelInterfaceInteraction?
|
||||
|
|
@ -1014,6 +1025,13 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
|||
var isButtonVisible: Bool {
|
||||
return self.mainButtonState.isVisible || self.secondaryButtonState.isVisible
|
||||
}
|
||||
var hasMediaAccessoryPanel: Bool {
|
||||
if case .glass = self.panelStyle {
|
||||
return self.playlistStateAndType != nil
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private var validLayout: ContainerViewLayout?
|
||||
private var scrollLayout: (width: CGFloat, contentSize: CGSize)?
|
||||
|
|
@ -1433,6 +1451,61 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
|||
return nil
|
||||
}, statuses: nil)
|
||||
|
||||
if case .glass = style {
|
||||
self.mediaStatusDisposable = (context.sharedContext.mediaManager.globalMediaPlayerState
|
||||
|> mapToSignal { playlistStateAndType -> Signal<(Account, SharedMediaPlayerItemPlaybackState, MediaManagerPlayerType)?, NoError> in
|
||||
guard let (account, stateOrLoading, type) = playlistStateAndType, type == .music else {
|
||||
return .single(nil) |> delay(0.2, queue: .mainQueue())
|
||||
}
|
||||
switch stateOrLoading {
|
||||
case let .state(state):
|
||||
return .single((account, state, type))
|
||||
case .loading:
|
||||
return .single(nil) |> delay(0.2, queue: .mainQueue())
|
||||
}
|
||||
}
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] playlistStateAndType in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let hadMediaAccessoryPanel = strongSelf.hasMediaAccessoryPanel
|
||||
if !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.0, playlistStateAndType?.1.item) ||
|
||||
!arePlaylistItemsEqual(strongSelf.playlistStateAndType?.1, playlistStateAndType?.1.previousItem) ||
|
||||
!arePlaylistItemsEqual(strongSelf.playlistStateAndType?.2, playlistStateAndType?.1.nextItem) ||
|
||||
strongSelf.playlistStateAndType?.3 != playlistStateAndType?.1.order ||
|
||||
strongSelf.playlistStateAndType?.4 != playlistStateAndType?.2 {
|
||||
|
||||
if let playlistStateAndType {
|
||||
strongSelf.playlistStateAndType = (
|
||||
playlistStateAndType.1.item,
|
||||
playlistStateAndType.1.previousItem,
|
||||
playlistStateAndType.1.nextItem,
|
||||
playlistStateAndType.1.order,
|
||||
playlistStateAndType.2,
|
||||
playlistStateAndType.0
|
||||
)
|
||||
strongSelf.playlistLocation = playlistStateAndType.1.playlistLocation
|
||||
} else {
|
||||
strongSelf.playlistStateAndType = nil
|
||||
strongSelf.playlistLocation = nil
|
||||
}
|
||||
|
||||
if hadMediaAccessoryPanel != strongSelf.hasMediaAccessoryPanel {
|
||||
strongSelf.requestLayout()
|
||||
} else if let layout = strongSelf.validLayout {
|
||||
let _ = strongSelf.update(
|
||||
layout: layout,
|
||||
buttons: strongSelf.buttons,
|
||||
isSelecting: strongSelf.isSelecting,
|
||||
selectionCount: strongSelf.selectionCount,
|
||||
elevateProgress: strongSelf.elevateProgress,
|
||||
transition: .animated(duration: 0.4, curve: .spring)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
self.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData)
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
|
|
@ -1441,6 +1514,14 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
|||
strongSelf.backgroundNode.updateColor(color: strongSelf.customBottomPanelBackgroundColor ?? presentationData.theme.rootController.tabBar.backgroundColor, transition: .immediate)
|
||||
strongSelf.separatorNode.backgroundColor = presentationData.theme.rootController.tabBar.separatorColor
|
||||
strongSelf.selectionNode.backgroundColor = presentationData.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.05)
|
||||
if let (mediaAccessoryPanel, _) = strongSelf.mediaAccessoryPanel {
|
||||
strongSelf.mediaAccessoryPanel = nil
|
||||
mediaAccessoryPanel.removeFromSupernode()
|
||||
}
|
||||
if let dismissingMediaAccessoryPanel = strongSelf.dismissingMediaAccessoryPanel {
|
||||
strongSelf.dismissingMediaAccessoryPanel = nil
|
||||
dismissingMediaAccessoryPanel.removeFromSupernode()
|
||||
}
|
||||
|
||||
strongSelf.updateChatPresentationInterfaceState({ $0.updatedTheme(presentationData.theme) })
|
||||
|
||||
|
|
@ -1480,6 +1561,8 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
|||
deinit {
|
||||
self.presentationDataDisposable?.dispose()
|
||||
self.peerDisposable?.dispose()
|
||||
self.mediaStatusDisposable?.dispose()
|
||||
self.playlistPreloadDisposable?.dispose()
|
||||
for (_, disposable) in self.iconDisposables {
|
||||
disposable.dispose()
|
||||
}
|
||||
|
|
@ -1512,6 +1595,246 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
|||
self.onSecondaryButtonPressed()
|
||||
}
|
||||
|
||||
private func mediaPlaybackStatusSignal() -> Signal<MediaPlayerStatus, NoError> {
|
||||
let delayedStatus = self.context.sharedContext.mediaManager.globalMediaPlayerState
|
||||
|> mapToSignal { value -> Signal<(Account, SharedMediaPlayerItemPlaybackStateOrLoading, MediaManagerPlayerType)?, NoError> in
|
||||
guard let value, value.2 == .music else {
|
||||
return .single(nil)
|
||||
}
|
||||
switch value.1 {
|
||||
case .state:
|
||||
return .single(value)
|
||||
case .loading:
|
||||
return .single(value) |> delay(0.1, queue: .mainQueue())
|
||||
}
|
||||
}
|
||||
|
||||
return delayedStatus
|
||||
|> map { state -> MediaPlayerStatus in
|
||||
if let stateOrLoading = state?.1, case let .state(state) = stateOrLoading {
|
||||
return state.status
|
||||
} else {
|
||||
return MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused, soundEnabled: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func dismissMediaAccessoryPanel(transition: ContainedViewLayoutTransition) {
|
||||
guard let (mediaAccessoryPanel, _) = self.mediaAccessoryPanel else {
|
||||
return
|
||||
}
|
||||
self.mediaAccessoryPanel = nil
|
||||
self.dismissingMediaAccessoryPanel = mediaAccessoryPanel
|
||||
mediaAccessoryPanel.animateOut(transition: transition, completion: { [weak self, weak mediaAccessoryPanel] in
|
||||
mediaAccessoryPanel?.removeFromSupernode()
|
||||
if let self, self.dismissingMediaAccessoryPanel === mediaAccessoryPanel {
|
||||
self.dismissingMediaAccessoryPanel = nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func presentAudioRateTooltip(baseRate: AudioPlaybackRate, changeType: MediaNavigationAccessoryPanel.ChangeType) {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
|
||||
var hasTooltip = false
|
||||
controller.forEachController({ controller in
|
||||
if let controller = controller as? UndoOverlayController {
|
||||
hasTooltip = true
|
||||
controller.dismissWithCommitAction()
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let text: String?
|
||||
let rate: CGFloat?
|
||||
if case let .sliderCommit(previousValue, newValue) = changeType {
|
||||
let value = String(format: "%0.1f", baseRate.doubleValue)
|
||||
if baseRate == .x1 {
|
||||
text = presentationData.strings.Conversation_AudioRateTooltipNormal
|
||||
} else {
|
||||
text = presentationData.strings.Conversation_AudioRateTooltipCustom(value).string
|
||||
}
|
||||
if newValue > previousValue {
|
||||
rate = .infinity
|
||||
} else if newValue < previousValue {
|
||||
rate = -.infinity
|
||||
} else {
|
||||
rate = nil
|
||||
}
|
||||
} else if baseRate == .x1 {
|
||||
text = presentationData.strings.Conversation_AudioRateTooltipNormal
|
||||
rate = 1.0
|
||||
} else if baseRate == .x1_5 {
|
||||
text = presentationData.strings.Conversation_AudioRateTooltip15X
|
||||
rate = 1.5
|
||||
} else if baseRate == .x2 {
|
||||
text = presentationData.strings.Conversation_AudioRateTooltipSpeedUp
|
||||
rate = 2.0
|
||||
} else {
|
||||
text = nil
|
||||
rate = nil
|
||||
}
|
||||
|
||||
var showTooltip = true
|
||||
if case .sliderChange = changeType {
|
||||
showTooltip = false
|
||||
}
|
||||
|
||||
if let rate, let text, showTooltip {
|
||||
controller.present(
|
||||
UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .audioRate(
|
||||
rate: rate,
|
||||
text: text
|
||||
),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: hasTooltip,
|
||||
action: { _ in
|
||||
return true
|
||||
}
|
||||
),
|
||||
in: .current
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func openCurrentMusicPlayer() {
|
||||
guard let (state, _, _, order, type, account) = self.playlistStateAndType,
|
||||
let id = state.id as? PeerMessagesMediaPlaylistItemId,
|
||||
let playlistLocation = self.playlistLocation as? PeerMessagesPlaylistLocation else {
|
||||
return
|
||||
}
|
||||
|
||||
guard type == .music else {
|
||||
self.context.sharedContext.navigateToChat(accountId: self.context.account.id, peerId: id.messageId.peerId, messageId: id.messageId)
|
||||
return
|
||||
}
|
||||
|
||||
if case .custom = playlistLocation {
|
||||
let controllerContext: AccountContext
|
||||
if account.id == self.context.account.id {
|
||||
controllerContext = self.context
|
||||
} else {
|
||||
controllerContext = self.context.sharedContext.makeTempAccountContext(account: account)
|
||||
}
|
||||
let controller = self.context.sharedContext.makeOverlayAudioPlayerController(
|
||||
context: controllerContext,
|
||||
chatLocation: .peer(id: id.messageId.peerId),
|
||||
type: type,
|
||||
initialMessageId: id.messageId,
|
||||
initialOrder: order,
|
||||
playlistLocation: playlistLocation,
|
||||
parentNavigationController: self.controller?.navigationController as? NavigationController
|
||||
)
|
||||
self.view.window?.endEditing(true)
|
||||
self.present(controller)
|
||||
}
|
||||
}
|
||||
|
||||
private func makeMediaAccessoryPanel() -> MediaNavigationAccessoryPanel {
|
||||
let mediaAccessoryPanel = MediaNavigationAccessoryPanel(context: self.context, presentationData: self.presentationData, displayBackground: false, customTintColor: self.presentationData.theme.rootController.tabBar.textColor)
|
||||
mediaAccessoryPanel.getController = { [weak self] in
|
||||
return self?.controller
|
||||
}
|
||||
mediaAccessoryPanel.presentInGlobalOverlay = { [weak self] controller in
|
||||
self?.presentInGlobalOverlay(controller)
|
||||
}
|
||||
mediaAccessoryPanel.close = { [weak self] in
|
||||
if let self, let (_, _, _, _, type, _) = self.playlistStateAndType {
|
||||
self.context.sharedContext.mediaManager.setPlaylist(nil, type: type, control: SharedMediaPlayerControlAction.playback(.pause))
|
||||
}
|
||||
}
|
||||
mediaAccessoryPanel.setRate = { [weak self] rate, changeType in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let _ = (self.context.sharedContext.accountManager.transaction { transaction -> AudioPlaybackRate in
|
||||
let settings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings)?.get(MusicPlaybackSettings.self) ?? MusicPlaybackSettings.defaultSettings
|
||||
|
||||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in
|
||||
return PreferencesEntry(settings.withUpdatedVoicePlaybackRate(rate))
|
||||
})
|
||||
return rate
|
||||
}
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] baseRate in
|
||||
guard let self, let (_, _, _, _, type, _) = self.playlistStateAndType else {
|
||||
return
|
||||
}
|
||||
self.context.sharedContext.mediaManager.playlistControl(.setBaseRate(baseRate), type: type)
|
||||
self.presentAudioRateTooltip(baseRate: baseRate, changeType: changeType)
|
||||
})
|
||||
}
|
||||
mediaAccessoryPanel.togglePlayPause = { [weak self] in
|
||||
if let self, let (_, _, _, _, type, _) = self.playlistStateAndType {
|
||||
self.context.sharedContext.mediaManager.playlistControl(.playback(.togglePlayPause), type: type)
|
||||
}
|
||||
}
|
||||
mediaAccessoryPanel.playPrevious = { [weak self] in
|
||||
if let self, let (_, _, _, _, type, _) = self.playlistStateAndType {
|
||||
self.context.sharedContext.mediaManager.playlistControl(.next, type: type)
|
||||
}
|
||||
}
|
||||
mediaAccessoryPanel.playNext = { [weak self] in
|
||||
if let self, let (_, _, _, _, type, _) = self.playlistStateAndType {
|
||||
self.context.sharedContext.mediaManager.playlistControl(.previous, type: type)
|
||||
}
|
||||
}
|
||||
mediaAccessoryPanel.tapAction = { [weak self] in
|
||||
self?.openCurrentMusicPlayer()
|
||||
}
|
||||
return mediaAccessoryPanel
|
||||
}
|
||||
|
||||
private func updateMediaAccessoryPanel(frame: CGRect?, transition: ContainedViewLayoutTransition) {
|
||||
guard case .glass = self.panelStyle,
|
||||
let frame,
|
||||
let (item, previousItem, nextItem, order, type, _) = self.playlistStateAndType else {
|
||||
self.dismissMediaAccessoryPanel(transition: transition)
|
||||
return
|
||||
}
|
||||
|
||||
let mediaAccessoryPanel: MediaNavigationAccessoryPanel
|
||||
let isNewPanel: Bool
|
||||
if let (currentPanel, currentType) = self.mediaAccessoryPanel, currentType == type {
|
||||
mediaAccessoryPanel = currentPanel
|
||||
isNewPanel = false
|
||||
} else {
|
||||
self.dismissMediaAccessoryPanel(transition: transition)
|
||||
mediaAccessoryPanel = self.makeMediaAccessoryPanel()
|
||||
mediaAccessoryPanel.frame = frame
|
||||
if let dismissingMediaAccessoryPanel = self.dismissingMediaAccessoryPanel {
|
||||
self.containerNode.insertSubnode(mediaAccessoryPanel, aboveSubnode: dismissingMediaAccessoryPanel)
|
||||
} else {
|
||||
self.containerNode.addSubnode(mediaAccessoryPanel)
|
||||
}
|
||||
self.mediaAccessoryPanel = (mediaAccessoryPanel, type)
|
||||
isNewPanel = true
|
||||
}
|
||||
|
||||
mediaAccessoryPanel.containerNode.headerNode.displayScrubber = item.playbackData?.type != .instantVideo
|
||||
mediaAccessoryPanel.containerNode.headerNode.playbackStatus = self.mediaPlaybackStatusSignal()
|
||||
switch order {
|
||||
case .regular:
|
||||
mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, previousItem, nextItem)
|
||||
case .reversed:
|
||||
mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, nextItem, previousItem)
|
||||
case .random:
|
||||
mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, nil, nil)
|
||||
}
|
||||
|
||||
if isNewPanel {
|
||||
mediaAccessoryPanel.updateLayout(size: frame.size, leftInset: 0.0, rightInset: 0.0, isHidden: false, transition: .immediate)
|
||||
mediaAccessoryPanel.animateIn(transition: transition)
|
||||
} else {
|
||||
transition.updateFrame(node: mediaAccessoryPanel, frame: frame)
|
||||
mediaAccessoryPanel.updateLayout(size: frame.size, leftInset: 0.0, rightInset: 0.0, isHidden: false, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
func updateBackgroundAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
transition.updateAlpha(node: self.separatorNode, alpha: alpha)
|
||||
transition.updateAlpha(node: self.backgroundNode, alpha: alpha)
|
||||
|
|
@ -2108,6 +2431,13 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
|||
insets.bottom = layout.intrinsicInsets.bottom
|
||||
}
|
||||
|
||||
let topAccessoryHeight: CGFloat
|
||||
if case .glass = self.panelStyle, self.playlistStateAndType != nil {
|
||||
topAccessoryHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight
|
||||
} else {
|
||||
topAccessoryHeight = 0.0
|
||||
}
|
||||
|
||||
if isSelecting {
|
||||
self.loadTextNodeIfNeeded()
|
||||
} else {
|
||||
|
|
@ -2127,7 +2457,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
|||
panelTransition = .immediate
|
||||
}
|
||||
let panelHeight = textInputPanelNode.updateLayout(width: layout.size.width, leftInset: insets.left + layout.safeInsets.left, rightInset: insets.right + layout.safeInsets.right, bottomInset: 0.0, additionalSideInsets: UIEdgeInsets(), maxHeight: layout.size.height / 2.0, isSecondary: false, transition: panelTransition, interfaceState: self.presentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: false)
|
||||
let panelFrame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: panelHeight)
|
||||
let panelFrame = CGRect(x: 0.0, y: topAccessoryHeight, width: layout.size.width, height: panelHeight)
|
||||
if textInputPanelNode.frame.width.isZero {
|
||||
textInputPanelNode.frame = panelFrame
|
||||
}
|
||||
|
|
@ -2143,7 +2473,8 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
|||
self.updateViews(transition: .immediate)
|
||||
|
||||
let glassPanelHeight: CGFloat = 62.0
|
||||
let bounds = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: self.buttonSize.height + insets.bottom))
|
||||
let bounds = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: self.buttonSize.height + insets.bottom + topAccessoryHeight))
|
||||
var mediaAccessoryPanelFrame: CGRect?
|
||||
if case .glass = self.panelStyle {
|
||||
let backgroundView: GlassBackgroundView
|
||||
let liquidLensView: LiquidLensView
|
||||
|
|
@ -2184,9 +2515,10 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
|||
buttonsPanelWidth = layout.size.width - layout.safeInsets.left - layout.safeInsets.right - panelSideInset * 2.0
|
||||
}
|
||||
|
||||
let panelSize = CGSize(width: isSelecting ? textPanelWidth : buttonsPanelWidth, height: isSelecting ? textPanelHeight - 11.0 : glassPanelHeight)
|
||||
let basePanelHeight = isSelecting ? max(0.0, textPanelHeight - 11.0) : glassPanelHeight
|
||||
let panelSize = CGSize(width: isSelecting ? textPanelWidth : buttonsPanelWidth, height: basePanelHeight + topAccessoryHeight)
|
||||
|
||||
let cornerRadius: CGFloat = isSelecting ? min(20.0, panelSize.height * 0.5) : panelSize.height * 0.5
|
||||
let cornerRadius: CGFloat = isSelecting ? min(20.0, basePanelHeight * 0.5) : glassPanelHeight * 0.5
|
||||
let backgroundOriginX: CGFloat = isSelecting ? panelSideInset : floorToScreenPixels((layout.size.width - panelSize.width) / 2.0)
|
||||
|
||||
backgroundView.update(size: panelSize, cornerRadius: cornerRadius, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: true, transition: ComponentTransition(transition))
|
||||
|
|
@ -2196,7 +2528,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
|||
self.lensParams = (lensPanelSize, cornerRadius)
|
||||
self.updateLiquidLens(transition: ComponentTransition(transition))
|
||||
|
||||
transition.updatePosition(layer: liquidLensView.layer, position: CGPoint(x: backgroundOriginX + panelSize.width * 0.5, y: panelSize.height * 0.5))
|
||||
transition.updatePosition(layer: liquidLensView.layer, position: CGPoint(x: backgroundOriginX + panelSize.width * 0.5, y: topAccessoryHeight + lensPanelSize.height * 0.5))
|
||||
transition.updateBounds(layer: liquidLensView.layer, bounds: CGRect(origin: .zero, size: CGSize(width: lensPanelSize.width - 3.0 * 2.0, height: lensPanelSize.height - 3.0 * 2.0)))
|
||||
|
||||
transition.updatePosition(layer: backgroundView.layer, position: CGPoint(x: backgroundOriginX + panelSize.width * 0.5, y: panelSize.height * 0.5))
|
||||
|
|
@ -2207,7 +2539,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
|||
transition.updateBounds(layer: self.selectedItemsContainer.layer, bounds: CGRect(origin: .zero, size: itemsContainerFrame.size))
|
||||
transition.updatePosition(layer: self.itemsContainer.layer, position: itemsContainerFrame.center)
|
||||
transition.updatePosition(layer: self.selectedItemsContainer.layer, position: itemsContainerFrame.center)
|
||||
|
||||
if topAccessoryHeight > 0.0 {
|
||||
mediaAccessoryPanelFrame = CGRect(origin: CGPoint(x: backgroundOriginX, y: 0.0), size: CGSize(width: panelSize.width, height: topAccessoryHeight))
|
||||
}
|
||||
}
|
||||
self.updateMediaAccessoryPanel(frame: mediaAccessoryPanelFrame, transition: transition)
|
||||
|
||||
var containerTransition: ContainedViewLayoutTransition
|
||||
var containerFrame: CGRect
|
||||
|
|
@ -2255,7 +2592,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
|||
}
|
||||
containerFrame = CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: height))
|
||||
} else if isSelecting {
|
||||
containerFrame = CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: textPanelHeight + insets.bottom))
|
||||
containerFrame = CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: textPanelHeight + insets.bottom + topAccessoryHeight))
|
||||
} else {
|
||||
containerFrame = bounds
|
||||
}
|
||||
|
|
@ -2367,7 +2704,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
|||
containerTransition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: UIScreenPixel)))
|
||||
|
||||
if case .glass = self.panelStyle {
|
||||
transition.updateFrameAsPositionAndBounds(node: self.scrollNode, frame: CGRect(origin: CGPoint(x: self.isSelecting ? panelSideInset - defaultPanelSideInset : panelSideInset, y: self.isSelecting ? -11.0 : 0.0), size: CGSize(width: layout.size.width - panelSideInset * 2.0, height: self.buttonSize.height)))
|
||||
transition.updateFrameAsPositionAndBounds(node: self.scrollNode, frame: CGRect(origin: CGPoint(x: self.isSelecting ? panelSideInset - defaultPanelSideInset : panelSideInset, y: topAccessoryHeight + (self.isSelecting ? -11.0 : 0.0)), size: CGSize(width: layout.size.width - panelSideInset * 2.0, height: self.buttonSize.height)))
|
||||
}
|
||||
|
||||
if let progress = self.loadingProgress {
|
||||
|
|
@ -2406,7 +2743,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
|||
|
||||
if !self.animatingTransition {
|
||||
let buttonOriginX = layout.safeInsets.left + buttonSideInset
|
||||
let buttonOriginY = isAnyButtonVisible || self.fromMenu ? buttonTopInset : containerFrame.height
|
||||
let buttonOriginY = isAnyButtonVisible || self.fromMenu ? topAccessoryHeight + buttonTopInset : containerFrame.height
|
||||
var mainButtonFrame: CGRect?
|
||||
var secondaryButtonFrame: CGRect?
|
||||
if self.secondaryButtonState.isVisible && self.mainButtonState.isVisible, let position = self.secondaryButtonState.position {
|
||||
|
|
|
|||
|
|
@ -1089,10 +1089,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
|||
if dateHeaderAtBottom, let header = item.header {
|
||||
insets.top += header.height
|
||||
}
|
||||
if !mergedBottom, case .blocks = item.style {
|
||||
insets.bottom += 35.0
|
||||
}
|
||||
|
||||
|
||||
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
|
||||
|
||||
var verticalInset: CGFloat = 0.0
|
||||
|
|
@ -1680,7 +1677,9 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
|||
|
||||
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if let item = self.item, case .selectable = item.selection {
|
||||
if self.bounds.contains(point) {
|
||||
if !self.iconButtonNode.isHidden && self.iconButtonNode.frame.contains(point) {
|
||||
|
||||
} else if self.bounds.contains(point) {
|
||||
return self.view
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,14 +17,14 @@ public final class MediaNavigationAccessoryContainerNode: ASDisplayNode, ASGestu
|
|||
|
||||
private var presentationData: PresentationData
|
||||
|
||||
init(context: AccountContext, presentationData: PresentationData, displayBackground: Bool) {
|
||||
init(context: AccountContext, presentationData: PresentationData, displayBackground: Bool, customTintColor: UIColor? = nil) {
|
||||
self.displayBackground = displayBackground
|
||||
|
||||
self.presentationData = presentationData
|
||||
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.separatorNode = ASDisplayNode()
|
||||
self.headerNode = MediaNavigationAccessoryHeaderNode(context: context, presentationData: presentationData)
|
||||
self.headerNode = MediaNavigationAccessoryHeaderNode(context: context, presentationData: presentationData, customTintColor: customTintColor)
|
||||
|
||||
super.init()
|
||||
|
||||
|
|
|
|||
|
|
@ -148,6 +148,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, ASScrollVi
|
|||
private var strings: PresentationStrings
|
||||
private var dateTimeFormat: PresentationDateTimeFormat
|
||||
private var nameDisplayOrder: PresentationPersonNameOrder
|
||||
private var customTintColor: UIColor?
|
||||
|
||||
private let scrollNode: ASScrollNode
|
||||
private var initialContentOffset: CGFloat?
|
||||
|
|
@ -219,13 +220,14 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, ASScrollVi
|
|||
private weak var tooltipController: TooltipScreen?
|
||||
private let dismissedPromise = ValuePromise<Bool>(false)
|
||||
|
||||
public init(context: AccountContext, presentationData: PresentationData) {
|
||||
public init(context: AccountContext, presentationData: PresentationData, customTintColor: UIColor? = nil) {
|
||||
self.context = context
|
||||
|
||||
self.theme = presentationData.theme
|
||||
self.strings = presentationData.strings
|
||||
self.dateTimeFormat = presentationData.dateTimeFormat
|
||||
self.nameDisplayOrder = presentationData.nameDisplayOrder
|
||||
self.customTintColor = customTintColor
|
||||
|
||||
self.scrollNode = ASScrollNode()
|
||||
|
||||
|
|
@ -260,13 +262,13 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, ASScrollVi
|
|||
self.actionButton.displaysAsynchronously = false
|
||||
|
||||
self.playPauseIconNode = PlayPauseIconNode()
|
||||
self.playPauseIconNode.customColor = self.theme.rootController.navigationBar.accentTextColor
|
||||
self.playPauseIconNode.customColor = customTintColor ?? self.theme.rootController.navigationBar.accentTextColor
|
||||
|
||||
self.scrubbingNode = MediaPlayerScrubbingNode(content: .standard(lineHeight: 2.0, lineCap: .square, scrubberHandle: .none, backgroundColor: .clear, foregroundColor: self.theme.rootController.navigationBar.accentTextColor, bufferingColor: self.theme.rootController.navigationBar.accentTextColor.withAlphaComponent(0.5), chapters: []))
|
||||
self.scrubbingNode = MediaPlayerScrubbingNode(content: .standard(lineHeight: 2.0, lineCap: .square, scrubberHandle: .none, backgroundColor: .clear, foregroundColor: customTintColor ?? self.theme.rootController.navigationBar.accentTextColor, bufferingColor: (customTintColor ?? self.theme.rootController.navigationBar.accentTextColor).withAlphaComponent(0.5), chapters: []))
|
||||
|
||||
self.separatorNode = ASDisplayNode()
|
||||
self.separatorNode.isLayerBacked = true
|
||||
self.separatorNode.backgroundColor = theme.rootController.navigationBar.separatorColor
|
||||
self.separatorNode.backgroundColor = self.theme.rootController.navigationBar.separatorColor
|
||||
|
||||
super.init()
|
||||
|
||||
|
|
@ -366,9 +368,9 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, ASScrollVi
|
|||
self.rightMaskNode.image = maskImage
|
||||
|
||||
self.closeButton.setImage(PresentationResourcesRootController.navigationPlayerCloseButton(self.theme), for: [])
|
||||
self.playPauseIconNode.customColor = self.theme.rootController.navigationBar.accentTextColor
|
||||
self.playPauseIconNode.customColor = self.customTintColor ?? self.theme.rootController.navigationBar.accentTextColor
|
||||
self.separatorNode.backgroundColor = self.theme.rootController.navigationBar.separatorColor
|
||||
self.scrubbingNode.updateContent(.standard(lineHeight: 2.0, lineCap: .square, scrubberHandle: .none, backgroundColor: .clear, foregroundColor: self.theme.rootController.navigationBar.accentTextColor, bufferingColor: self.theme.rootController.navigationBar.accentTextColor.withAlphaComponent(0.5), chapters: []))
|
||||
self.scrubbingNode.updateContent(.standard(lineHeight: 2.0, lineCap: .square, scrubberHandle: .none, backgroundColor: .clear, foregroundColor: self.customTintColor ?? self.theme.rootController.navigationBar.accentTextColor, bufferingColor: (self.customTintColor ?? self.theme.rootController.navigationBar.accentTextColor).withAlphaComponent(0.5), chapters: []))
|
||||
|
||||
if let playbackBaseRate = self.playbackBaseRate {
|
||||
self.rateButton.setContent(.image(optionsRateImage(rate: playbackBaseRate.stringValue.uppercased(), color: self.theme.rootController.navigationBar.controlColor)))
|
||||
|
|
@ -481,11 +483,12 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, ASScrollVi
|
|||
let rateButtonSize = CGSize(width: 30.0, height: minHeight)
|
||||
transition.updateFrame(node: self.rateButton, frame: CGRect(origin: CGPoint(x: bounds.size.width - 27.0 - closeButtonSize.width - rateButtonSize.width - rightInset, y: -4.0), size: rateButtonSize))
|
||||
|
||||
transition.updateFrame(node: self.playPauseIconNode, frame: CGRect(origin: CGPoint(x: 6.0, y: 4.0 + UIScreenPixel), size: CGSize(width: 28.0, height: 28.0)))
|
||||
transition.updateFrame(node: self.playPauseIconNode, frame: CGRect(origin: CGPoint(x: 6.0 + 4.0, y: 4.0 + UIScreenPixel), size: CGSize(width: 28.0, height: 28.0)))
|
||||
transition.updateFrame(node: self.actionButton, frame: CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: 40.0, height: 37.0)))
|
||||
transition.updateFrame(node: self.scrubbingNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 37.0 - 2.0), size: CGSize(width: size.width, height: 2.0)))
|
||||
|
||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: UIScreenPixel)))
|
||||
let originY: CGFloat = self.customTintColor != nil ? minHeight - UIScreenPixel : 0.0
|
||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: originY), size: CGSize(width: size.width, height: UIScreenPixel)))
|
||||
|
||||
self.accessibilityAreaNode.frame = CGRect(origin: CGPoint(x: self.actionButton.frame.maxX, y: 0.0), size: CGSize(width: self.rateButton.frame.minX - self.actionButton.frame.maxX, height: minHeight))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ public final class MediaNavigationAccessoryPanel: ASDisplayNode {
|
|||
public var getController: (() -> ViewController?)?
|
||||
public var presentInGlobalOverlay: ((ViewController) -> Void)?
|
||||
|
||||
public init(context: AccountContext, presentationData: PresentationData, displayBackground: Bool = false) {
|
||||
self.containerNode = MediaNavigationAccessoryContainerNode(context: context, presentationData: presentationData, displayBackground: displayBackground)
|
||||
public init(context: AccountContext, presentationData: PresentationData, displayBackground: Bool = false, customTintColor: UIColor? = nil) {
|
||||
self.containerNode = MediaNavigationAccessoryContainerNode(context: context, presentationData: presentationData, displayBackground: displayBackground, customTintColor: customTintColor)
|
||||
|
||||
super.init()
|
||||
|
||||
|
|
|
|||
|
|
@ -89,7 +89,6 @@ swift_library(
|
|||
"//submodules/ManagedAnimationNode:ManagedAnimationNode",
|
||||
"//submodules/TemporaryCachedPeerDataManager:TemporaryCachedPeerDataManager",
|
||||
"//submodules/PeerInfoAvatarListNode:PeerInfoAvatarListNode",
|
||||
"//submodules/WebSearchUI:WebSearchUI",
|
||||
"//submodules/MapResourceToAvatarSizes:MapResourceToAvatarSizes",
|
||||
"//submodules/TextFormat:TextFormat",
|
||||
"//submodules/Markdown:Markdown",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import DeleteChatPeerActionSheetItem
|
|||
import PeerListItemComponent
|
||||
import LegacyComponents
|
||||
import LegacyUI
|
||||
import WebSearchUI
|
||||
import MapResourceToAvatarSizes
|
||||
import LegacyMediaPickerUI
|
||||
import AvatarNode
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import ContextUI
|
|||
import DeleteChatPeerActionSheetItem
|
||||
import UndoUI
|
||||
import LegacyComponents
|
||||
import WebSearchUI
|
||||
import MapResourceToAvatarSizes
|
||||
import LegacyUI
|
||||
import LegacyMediaPickerUI
|
||||
|
|
@ -475,25 +474,6 @@ extension VideoChatScreenComponent.View {
|
|||
mixin.forceDark = true
|
||||
mixin.stickersContext = LegacyPaintStickersContext(context: currentCall.accountContext)
|
||||
let _ = self.currentAvatarMixin.swap(mixin)
|
||||
mixin.requestSearchController = { [weak self] assetsController in
|
||||
guard let self, let currentCall = self.currentCall, let environment = self.environment else {
|
||||
return
|
||||
}
|
||||
let controller = WebSearchController(context: currentCall.accountContext, peer: peer, chatLocation: nil, configuration: searchBotsConfiguration, mode: .avatar(initialQuery: peer.id.namespace == Namespaces.Peer.CloudUser ? nil : peer.displayTitle(strings: environment.strings, displayOrder: presentationData.nameDisplayOrder), completion: { [weak self] result in
|
||||
assetsController?.dismiss()
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.updateProfilePhoto(result)
|
||||
}))
|
||||
controller.navigationPresentation = .modal
|
||||
environment.controller()?.push(controller)
|
||||
|
||||
if fromGallery {
|
||||
completion()
|
||||
}
|
||||
}
|
||||
mixin.didFinishWithImage = { [weak self] image in
|
||||
if let image = image {
|
||||
completion()
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ import TooltipUI
|
|||
import LegacyUI
|
||||
import LegacyComponents
|
||||
import LegacyMediaPickerUI
|
||||
import WebSearchUI
|
||||
import MapResourceToAvatarSizes
|
||||
import SolidRoundedButtonNode
|
||||
import AudioBlob
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
|
|
@ -20,6 +21,7 @@ import BundleIconComponent
|
|||
import EdgeEffect
|
||||
import SaveToCameraRoll
|
||||
import PeerMessagesMediaPlaylist
|
||||
import ContextUI
|
||||
|
||||
private final class AttachmentFileControllerArguments {
|
||||
let context: AccountContext
|
||||
|
|
@ -31,8 +33,12 @@ private final class AttachmentFileControllerArguments {
|
|||
let expandRecentMusic: () -> Void
|
||||
let send: (Message) -> Void
|
||||
let toggleMediaPlayback: (Message) -> Void
|
||||
|
||||
init(context: AccountContext, isAudio: Bool, openGallery: @escaping () -> Void, openFiles: @escaping () -> Void, scanDocument: @escaping () -> Void, expandSavedMusic: @escaping () -> Void, expandRecentMusic: @escaping () -> Void, send: @escaping (Message) -> Void, toggleMediaPlayback: @escaping (Message) -> Void) {
|
||||
let isSelectionActive: () -> Bool
|
||||
let toggleMessageSelection: (Message) -> Void
|
||||
let setMessageSelection: ([MessageId], Message?, Bool) -> Void
|
||||
let openMessageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)
|
||||
|
||||
init(context: AccountContext, isAudio: Bool, openGallery: @escaping () -> Void, openFiles: @escaping () -> Void, scanDocument: @escaping () -> Void, expandSavedMusic: @escaping () -> Void, expandRecentMusic: @escaping () -> Void, send: @escaping (Message) -> Void, toggleMediaPlayback: @escaping (Message) -> Void, isSelectionActive: @escaping () -> Bool, toggleMessageSelection: @escaping (Message) -> Void, setMessageSelection: @escaping ([MessageId], Message?, Bool) -> Void, openMessageContextAction: @escaping ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)) {
|
||||
self.context = context
|
||||
self.isAudio = isAudio
|
||||
self.openGallery = openGallery
|
||||
|
|
@ -42,6 +48,10 @@ private final class AttachmentFileControllerArguments {
|
|||
self.expandRecentMusic = expandRecentMusic
|
||||
self.send = send
|
||||
self.toggleMediaPlayback = toggleMediaPlayback
|
||||
self.isSelectionActive = isSelectionActive
|
||||
self.toggleMessageSelection = toggleMessageSelection
|
||||
self.setMessageSelection = setMessageSelection
|
||||
self.openMessageContextAction = openMessageContextAction
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -69,19 +79,19 @@ private enum AttachmentFileEntry: ItemListNodeEntry {
|
|||
case selectFromGallery(PresentationTheme, String)
|
||||
case selectFromFiles(PresentationTheme, String)
|
||||
case scanDocument(PresentationTheme, String)
|
||||
|
||||
|
||||
case savedHeader(PresentationTheme, String)
|
||||
case savedFile(Int32, PresentationTheme, Message?)
|
||||
case savedFile(Int32, PresentationTheme, Message?, ChatHistoryMessageSelection)
|
||||
case savedShowMore(PresentationTheme, String)
|
||||
|
||||
|
||||
case recentHeader(PresentationTheme, String)
|
||||
case recentFile(Int32, PresentationTheme, Message?)
|
||||
case recentFile(Int32, PresentationTheme, Message?, ChatHistoryMessageSelection)
|
||||
case recentShowMore(PresentationTheme, String)
|
||||
|
||||
|
||||
case globalHeader(PresentationTheme, String)
|
||||
case globalFile(Int32, PresentationTheme, Message?)
|
||||
case globalFile(Int32, PresentationTheme, Message?, ChatHistoryMessageSelection)
|
||||
case globalShowMore(PresentationTheme, String)
|
||||
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .selectFromGallery, .selectFromFiles, .scanDocument:
|
||||
|
|
@ -94,7 +104,7 @@ private enum AttachmentFileEntry: ItemListNodeEntry {
|
|||
return AttachmentFileSection.global.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var stableId: Int32 {
|
||||
switch self {
|
||||
case .selectFromGallery:
|
||||
|
|
@ -105,25 +115,25 @@ private enum AttachmentFileEntry: ItemListNodeEntry {
|
|||
return 2
|
||||
case .savedHeader:
|
||||
return 3
|
||||
case let .savedFile(index, _, _):
|
||||
case let .savedFile(index, _, _, _):
|
||||
return 4 + index
|
||||
case .savedShowMore:
|
||||
return 9999
|
||||
case .recentHeader:
|
||||
return 10000
|
||||
case let .recentFile(index, _, _):
|
||||
case let .recentFile(index, _, _, _):
|
||||
return 10001 + index
|
||||
case .recentShowMore:
|
||||
return 100000
|
||||
case .globalHeader:
|
||||
return 100001
|
||||
case let .globalFile(index, _, _):
|
||||
case let .globalFile(index, _, _, _):
|
||||
return 100002 + index
|
||||
case .globalShowMore:
|
||||
return 200000
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static func ==(lhs: AttachmentFileEntry, rhs: AttachmentFileEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .selectFromGallery(lhsTheme, lhsText):
|
||||
|
|
@ -150,8 +160,8 @@ private enum AttachmentFileEntry: ItemListNodeEntry {
|
|||
} else {
|
||||
return false
|
||||
}
|
||||
case let .savedFile(lhsIndex, lhsTheme, lhsMessage):
|
||||
if case let .savedFile(rhsIndex, rhsTheme, rhsMessage) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, areMessagesEqual(lhsMessage, rhsMessage) {
|
||||
case let .savedFile(lhsIndex, lhsTheme, lhsMessage, lhsSelection):
|
||||
if case let .savedFile(rhsIndex, rhsTheme, rhsMessage, rhsSelection) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, areMessagesEqual(lhsMessage, rhsMessage), lhsSelection == rhsSelection {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
|
|
@ -168,8 +178,8 @@ private enum AttachmentFileEntry: ItemListNodeEntry {
|
|||
} else {
|
||||
return false
|
||||
}
|
||||
case let .recentFile(lhsIndex, lhsTheme, lhsMessage):
|
||||
if case let .recentFile(rhsIndex, rhsTheme, rhsMessage) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, areMessagesEqual(lhsMessage, rhsMessage) {
|
||||
case let .recentFile(lhsIndex, lhsTheme, lhsMessage, lhsSelection):
|
||||
if case let .recentFile(rhsIndex, rhsTheme, rhsMessage, rhsSelection) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, areMessagesEqual(lhsMessage, rhsMessage), lhsSelection == rhsSelection {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
|
|
@ -186,8 +196,8 @@ private enum AttachmentFileEntry: ItemListNodeEntry {
|
|||
} else {
|
||||
return false
|
||||
}
|
||||
case let .globalFile(lhsIndex, lhsTheme, lhsMessage):
|
||||
if case let .globalFile(rhsIndex, rhsTheme, rhsMessage) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, areMessagesEqual(lhsMessage, rhsMessage) {
|
||||
case let .globalFile(lhsIndex, lhsTheme, lhsMessage, lhsSelection):
|
||||
if case let .globalFile(rhsIndex, rhsTheme, rhsMessage, rhsSelection) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, areMessagesEqual(lhsMessage, rhsMessage), lhsSelection == rhsSelection {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
|
|
@ -200,11 +210,11 @@ private enum AttachmentFileEntry: ItemListNodeEntry {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static func <(lhs: AttachmentFileEntry, rhs: AttachmentFileEntry) -> Bool {
|
||||
return lhs.stableId < rhs.stableId
|
||||
}
|
||||
|
||||
|
||||
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
||||
let arguments = arguments as! AttachmentFileControllerArguments
|
||||
switch self {
|
||||
|
|
@ -222,56 +232,80 @@ private enum AttachmentFileEntry: ItemListNodeEntry {
|
|||
})
|
||||
case let .savedHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .savedFile(_, _, message):
|
||||
case let .savedFile(_, _, message, selection):
|
||||
let interaction = ListMessageItemInteraction(openMessage: { message, _ in
|
||||
arguments.send(message)
|
||||
if arguments.isSelectionActive() {
|
||||
arguments.toggleMessageSelection(message)
|
||||
} else {
|
||||
arguments.send(message)
|
||||
}
|
||||
return false
|
||||
}, openMessageContextMenu: { _, _, _, _, _ in }, toggleMediaPlayback: { message in
|
||||
}, openMessageContextMenu: { message, _, node, rect, gesture in
|
||||
arguments.openMessageContextAction(EngineMessage(message), node, rect, gesture)
|
||||
}, toggleMediaPlayback: { message in
|
||||
arguments.toggleMediaPlayback(message)
|
||||
}, toggleMessagesSelection: { _, _ in }, openUrl: { _, _, _, _ in }, openInstantPage: { _, _ in }, longTap: { _, _ in }, getHiddenMedia: { return [:] })
|
||||
|
||||
}, toggleMessagesSelection: { ids, value in
|
||||
arguments.setMessageSelection(ids, message, value)
|
||||
}, openUrl: { _, _, _, _ in }, openInstantPage: { _, _ in }, longTap: { _, _ in }, getHiddenMedia: { return [:] })
|
||||
|
||||
let dateTimeFormat = arguments.context.sharedContext.currentPresentationData.with({$0}).dateTimeFormat
|
||||
let chatPresentationData = ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: .color(0)), fontSize: presentationData.fontSize, strings: presentationData.strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, disableAnimations: false, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0, auxiliaryRadius: 0, mergeBubbleCorners: false))
|
||||
return ListMessageItem(presentationData: chatPresentationData, systemStyle: .glass, context: arguments.context, chatLocation: .peer(id: arguments.context.account.peerId), interaction: interaction, message: message, selection: .none, displayHeader: false, isDownloadList: arguments.isAudio, isStoryMusic: true, isAttachMusic: true, displayFileInfo: true, displayBackground: true, style: .blocks, sectionId: self.section)
|
||||
return ListMessageItem(presentationData: chatPresentationData, systemStyle: .glass, context: arguments.context, chatLocation: .peer(id: arguments.context.account.peerId), interaction: interaction, message: message, selection: selection, displayHeader: false, isDownloadList: arguments.isAudio, isStoryMusic: true, isAttachMusic: true, displayFileInfo: true, displayBackground: true, style: .blocks, sectionId: self.section)
|
||||
case let .savedShowMore(theme, text):
|
||||
return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.downArrowImage(theme), title: text, sectionId: self.section, editing: false, action: {
|
||||
arguments.expandSavedMusic()
|
||||
})
|
||||
case let .recentHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .recentFile(_, _, message):
|
||||
case let .recentFile(_, _, message, selection):
|
||||
let interaction = ListMessageItemInteraction(openMessage: { message, _ in
|
||||
arguments.send(message)
|
||||
if arguments.isSelectionActive() {
|
||||
arguments.toggleMessageSelection(message)
|
||||
} else {
|
||||
arguments.send(message)
|
||||
}
|
||||
return false
|
||||
}, openMessageContextMenu: { _, _, _, _, _ in }, toggleMediaPlayback: { message in
|
||||
}, openMessageContextMenu: { message, _, node, rect, gesture in
|
||||
arguments.openMessageContextAction(EngineMessage(message), node, rect, gesture)
|
||||
}, toggleMediaPlayback: { message in
|
||||
arguments.toggleMediaPlayback(message)
|
||||
}, toggleMessagesSelection: { _, _ in }, openUrl: { _, _, _, _ in }, openInstantPage: { _, _ in }, longTap: { _, _ in }, getHiddenMedia: { return [:] })
|
||||
|
||||
}, toggleMessagesSelection: { ids, value in
|
||||
arguments.setMessageSelection(ids, message, value)
|
||||
}, openUrl: { _, _, _, _ in }, openInstantPage: { _, _ in }, longTap: { _, _ in }, getHiddenMedia: { return [:] })
|
||||
|
||||
let dateTimeFormat = arguments.context.sharedContext.currentPresentationData.with({$0}).dateTimeFormat
|
||||
let chatPresentationData = ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: .color(0)), fontSize: presentationData.fontSize, strings: presentationData.strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, disableAnimations: false, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0, auxiliaryRadius: 0, mergeBubbleCorners: false))
|
||||
|
||||
return ListMessageItem(presentationData: chatPresentationData, systemStyle: .glass, context: arguments.context, chatLocation: .peer(id: PeerId(0)), interaction: interaction, message: message, selection: .none, displayHeader: false, isDownloadList: arguments.isAudio, isStoryMusic: true, isAttachMusic: true, displayFileInfo: true, displayBackground: true, style: .blocks, sectionId: self.section)
|
||||
|
||||
return ListMessageItem(presentationData: chatPresentationData, systemStyle: .glass, context: arguments.context, chatLocation: .peer(id: PeerId(0)), interaction: interaction, message: message, selection: selection, displayHeader: false, isDownloadList: arguments.isAudio, isStoryMusic: true, isAttachMusic: true, displayFileInfo: true, displayBackground: true, style: .blocks, sectionId: self.section)
|
||||
case let .recentShowMore(theme, text):
|
||||
return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.downArrowImage(theme), title: text, sectionId: self.section, editing: false, action: {
|
||||
arguments.expandRecentMusic()
|
||||
})
|
||||
case let .globalHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .globalFile(_, _, message):
|
||||
case let .globalFile(_, _, message, selection):
|
||||
let interaction = ListMessageItemInteraction(openMessage: { message, _ in
|
||||
arguments.send(message)
|
||||
if arguments.isSelectionActive() {
|
||||
arguments.toggleMessageSelection(message)
|
||||
} else {
|
||||
arguments.send(message)
|
||||
}
|
||||
return false
|
||||
}, openMessageContextMenu: { _, _, _, _, _ in }, toggleMediaPlayback: { message in
|
||||
}, openMessageContextMenu: { message, _, node, rect, gesture in
|
||||
arguments.openMessageContextAction(EngineMessage(message), node, rect, gesture)
|
||||
}, toggleMediaPlayback: { message in
|
||||
arguments.toggleMediaPlayback(message)
|
||||
}, toggleMessagesSelection: { _, _ in }, openUrl: { _, _, _, _ in }, openInstantPage: { _, _ in }, longTap: { _, _ in }, getHiddenMedia: { return [:] })
|
||||
|
||||
}, toggleMessagesSelection: { ids, value in
|
||||
arguments.setMessageSelection(ids, message, value)
|
||||
}, openUrl: { _, _, _, _ in }, openInstantPage: { _, _ in }, longTap: { _, _ in }, getHiddenMedia: { return [:] })
|
||||
|
||||
let dateTimeFormat = arguments.context.sharedContext.currentPresentationData.with({$0}).dateTimeFormat
|
||||
let chatPresentationData = ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: .color(0)), fontSize: presentationData.fontSize, strings: presentationData.strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, disableAnimations: false, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0, auxiliaryRadius: 0, mergeBubbleCorners: false))
|
||||
|
||||
return ListMessageItem(presentationData: chatPresentationData, systemStyle: .glass, context: arguments.context, chatLocation: .peer(id: PeerId(0)), interaction: interaction, message: message, selection: .none, displayHeader: false, isDownloadList: arguments.isAudio, isStoryMusic: true, isAttachMusic: true, displayFileInfo: true, displayBackground: true, style: .blocks, sectionId: self.section)
|
||||
|
||||
return ListMessageItem(presentationData: chatPresentationData, systemStyle: .glass, context: arguments.context, chatLocation: .peer(id: PeerId(0)), interaction: interaction, message: message, selection: selection, displayHeader: false, isDownloadList: arguments.isAudio, isStoryMusic: true, isAttachMusic: true, displayFileInfo: true, displayBackground: true, style: .blocks, sectionId: self.section)
|
||||
case let .globalShowMore(theme, text):
|
||||
return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.downArrowImage(theme), title: text, sectionId: self.section, editing: false, action: {
|
||||
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -289,7 +323,7 @@ private func attachmentFileControllerEntries(presentationData: PresentationData,
|
|||
if hasScan {
|
||||
entries.append(.scanDocument(presentationData.theme, presentationData.strings.Attachment_ScanDocument))
|
||||
}
|
||||
|
||||
|
||||
let listTitle: String
|
||||
switch mode {
|
||||
case .recent:
|
||||
|
|
@ -297,21 +331,21 @@ private func attachmentFileControllerEntries(presentationData: PresentationData,
|
|||
case .audio:
|
||||
listTitle = presentationData.strings.Attachment_SharedAudio
|
||||
}
|
||||
|
||||
|
||||
if case .audio = mode {
|
||||
if let savedMusic, savedMusic.count > 0 {
|
||||
entries.append(.savedHeader(presentationData.theme, presentationData.strings.MediaEditor_Audio_SavedMusic.uppercased()))
|
||||
|
||||
|
||||
var savedMusic = savedMusic
|
||||
var hasShowMore = false
|
||||
if savedMusic.count > 4 && !state.savedMusicExpanded {
|
||||
savedMusic = Array(savedMusic.prefix(3))
|
||||
hasShowMore = true
|
||||
}
|
||||
|
||||
|
||||
var i: Int32 = 0
|
||||
for file in savedMusic {
|
||||
entries.append(.savedFile(i, presentationData.theme, file))
|
||||
entries.append(.savedFile(i, presentationData.theme, file, messageSelectionState(state: state, message: file)))
|
||||
i += 1
|
||||
}
|
||||
if hasShowMore {
|
||||
|
|
@ -319,7 +353,7 @@ private func attachmentFileControllerEntries(presentationData: PresentationData,
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if let recentDocuments {
|
||||
if recentDocuments.count > 0 {
|
||||
entries.append(.recentHeader(presentationData.theme, listTitle.uppercased()))
|
||||
|
|
@ -330,10 +364,10 @@ private func attachmentFileControllerEntries(presentationData: PresentationData,
|
|||
recentDocuments = Array(recentDocuments.prefix(3))
|
||||
hasShowMore = true
|
||||
}
|
||||
|
||||
|
||||
var i: Int32 = 0
|
||||
for file in recentDocuments {
|
||||
entries.append(.recentFile(i, presentationData.theme, file))
|
||||
entries.append(.recentFile(i, presentationData.theme, file, messageSelectionState(state: state, message: file)))
|
||||
i += 1
|
||||
}
|
||||
if hasShowMore {
|
||||
|
|
@ -343,16 +377,47 @@ private func attachmentFileControllerEntries(presentationData: PresentationData,
|
|||
} else {
|
||||
entries.append(.recentHeader(presentationData.theme, listTitle.uppercased()))
|
||||
for i in 0 ..< 11 {
|
||||
entries.append(.recentFile(Int32(i), presentationData.theme, nil))
|
||||
entries.append(.recentFile(Int32(i), presentationData.theme, nil, .none))
|
||||
}
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
private final class AttachmentFileContext: AttachmentMediaPickerContext {
|
||||
final class AttachmentFileContext: AttachmentMediaPickerContext {
|
||||
private weak var controller: AttachmentFileControllerImpl?
|
||||
|
||||
var selectionCount: Signal<Int, NoError> {
|
||||
if let controller = self.controller {
|
||||
return controller.selectionCount.get()
|
||||
} else {
|
||||
return .single(0)
|
||||
}
|
||||
}
|
||||
|
||||
init(controller: AttachmentFileControllerImpl) {
|
||||
self.controller = controller
|
||||
}
|
||||
|
||||
func setCaption(_ caption: NSAttributedString) {
|
||||
self.controller?.caption = caption
|
||||
}
|
||||
|
||||
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) {
|
||||
self.controller?.mulitpleCompletion?(mode, .files, parameters)
|
||||
}
|
||||
|
||||
func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) {
|
||||
// self.controller?.presentScheduleTimePicker ({ time, repeatPeriod in
|
||||
// self.controller?.contactsNode.requestMultipleAction?(false, time, parameters)
|
||||
// })
|
||||
}
|
||||
|
||||
func mainButtonAction() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class AttachmentFileControllerImpl: ItemListController, AttachmentFileController, AttachmentContainable {
|
||||
public var requestAttachmentMenuExpansion: () -> Void = {}
|
||||
public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in }
|
||||
|
|
@ -365,39 +430,44 @@ public class AttachmentFileControllerImpl: ItemListController, AttachmentFileCon
|
|||
public var isContainerPanning: () -> Bool = { return false }
|
||||
public var isContainerExpanded: () -> Bool = { return false }
|
||||
public var isMinimized: Bool = false
|
||||
|
||||
fileprivate var caption: NSAttributedString?
|
||||
fileprivate var selectionCount = ValuePromise<Int>(0)
|
||||
|
||||
fileprivate var mulitpleCompletion: ((AttachmentMediaPickerSendMode, AttachmentMediaPickerAttachmentMode, ChatSendMessageActionSheetController.SendParameters?) -> Void)?
|
||||
|
||||
var delayDisappear = false
|
||||
|
||||
|
||||
var hasBottomEdgeEffect = true
|
||||
|
||||
|
||||
var resetForReuseImpl: () -> Void = {}
|
||||
public func resetForReuse() {
|
||||
self.resetForReuseImpl()
|
||||
self.scrollToTop?()
|
||||
}
|
||||
|
||||
|
||||
public func prepareForReuse() {
|
||||
self.delayDisappear = true
|
||||
self.visibleBottomContentOffsetChanged?(self.visibleBottomContentOffset)
|
||||
self.delayDisappear = false
|
||||
}
|
||||
|
||||
|
||||
public var mediaPickerContext: AttachmentMediaPickerContext? {
|
||||
return AttachmentFileContext()
|
||||
return AttachmentFileContext(controller: self)
|
||||
}
|
||||
|
||||
|
||||
private var topEdgeEffectView: EdgeEffectView?
|
||||
private var bottomEdgeEffectView: EdgeEffectView?
|
||||
|
||||
|
||||
var isSearching: Bool = false {
|
||||
didSet {
|
||||
self.requestLayout(transition: .animated(duration: 0.2, curve: .easeInOut))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
|
||||
let topEdgeEffectView: EdgeEffectView
|
||||
if let current = self.topEdgeEffectView {
|
||||
topEdgeEffectView = current
|
||||
|
|
@ -408,12 +478,12 @@ public class AttachmentFileControllerImpl: ItemListController, AttachmentFileCon
|
|||
}
|
||||
self.topEdgeEffectView = topEdgeEffectView
|
||||
}
|
||||
|
||||
|
||||
let edgeEffectHeight: CGFloat = 88.0
|
||||
let topEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: edgeEffectHeight))
|
||||
transition.updateFrame(view: topEdgeEffectView, frame: topEdgeEffectFrame)
|
||||
topEdgeEffectView.update(content: .clear, blur: true, alpha: 1.0, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: ComponentTransition(transition))
|
||||
|
||||
|
||||
if self.hasBottomEdgeEffect {
|
||||
let bottomEdgeEffectView: EdgeEffectView
|
||||
if let current = self.bottomEdgeEffectView {
|
||||
|
|
@ -423,7 +493,7 @@ public class AttachmentFileControllerImpl: ItemListController, AttachmentFileCon
|
|||
self.view.addSubview(bottomEdgeEffectView)
|
||||
self.bottomEdgeEffectView = bottomEdgeEffectView
|
||||
}
|
||||
|
||||
|
||||
let bottomEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - edgeEffectHeight - layout.additionalInsets.bottom), size: CGSize(width: layout.size.width, height: edgeEffectHeight))
|
||||
transition.updateFrame(view: bottomEdgeEffectView, frame: bottomEdgeEffectFrame)
|
||||
transition.updateAlpha(layer: bottomEdgeEffectView.layer, alpha: self.isSearching ? 0.0 : 1.0)
|
||||
|
|
@ -438,6 +508,15 @@ private struct AttachmentFileControllerState: Equatable {
|
|||
var searching: Bool
|
||||
var savedMusicExpanded: Bool
|
||||
var recentMusicExpanded: Bool
|
||||
var selectedMessageIds: Set<MessageId>?
|
||||
var messageMap: [MessageId: EngineMessage]
|
||||
}
|
||||
|
||||
private func messageSelectionState(state: AttachmentFileControllerState, message: Message?) -> ChatHistoryMessageSelection {
|
||||
guard let message = message, let selectedMessageIds = state.selectedMessageIds else {
|
||||
return .none
|
||||
}
|
||||
return .selectable(selected: selectedMessageIds.contains(message.id))
|
||||
}
|
||||
|
||||
public enum AttachmentFileControllerMode {
|
||||
|
|
@ -453,21 +532,24 @@ public func makeAttachmentFileControllerImpl(
|
|||
presentGallery: @escaping () -> Void,
|
||||
presentFiles: @escaping () -> Void,
|
||||
presentDocumentScanner: (() -> Void)?,
|
||||
send: @escaping (AnyMediaReference) -> Void
|
||||
send: @escaping ([AnyMediaReference]) -> Void
|
||||
) -> AttachmentFileController {
|
||||
let actionsDisposable = DisposableSet()
|
||||
|
||||
let statePromise = ValuePromise(AttachmentFileControllerState(searching: false, savedMusicExpanded: false, recentMusicExpanded: false), ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: AttachmentFileControllerState(searching: false, savedMusicExpanded: false, recentMusicExpanded: false))
|
||||
|
||||
let statePromise = ValuePromise(AttachmentFileControllerState(searching: false, savedMusicExpanded: false, recentMusicExpanded: false, selectedMessageIds: nil, messageMap: [:]), ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: AttachmentFileControllerState(searching: false, savedMusicExpanded: false, recentMusicExpanded: false, selectedMessageIds: nil, messageMap: [:]))
|
||||
let updateState: ((AttachmentFileControllerState) -> AttachmentFileControllerState) -> Void = { f in
|
||||
statePromise.set(stateValue.modify { f($0) })
|
||||
}
|
||||
|
||||
|
||||
var updateTabBarVisibilityImpl: ((Bool) -> Void)?
|
||||
var expandImpl: (() -> Void)?
|
||||
var dismissImpl: (() -> Void)?
|
||||
var dismissInputImpl: (() -> Void)?
|
||||
var updateIsSearchingImpl: ((Bool) -> Void)?
|
||||
var updateSelectionCountImpl: ((Int) -> Void)?
|
||||
var presentInGlobalOverlayImpl: ((ViewController) -> Void)?
|
||||
|
||||
let arguments = AttachmentFileControllerArguments(
|
||||
context: context,
|
||||
isAudio: mode == .audio,
|
||||
|
|
@ -498,7 +580,7 @@ public func makeAttachmentFileControllerImpl(
|
|||
send: { message in
|
||||
if message.id.namespace == Namespaces.Message.Local {
|
||||
if let file = message.media.first(where: { $0 is TelegramMediaFile }) as? TelegramMediaFile {
|
||||
send(.standalone(media: file))
|
||||
send([.standalone(media: file)])
|
||||
dismissImpl?()
|
||||
}
|
||||
} else {
|
||||
|
|
@ -514,7 +596,7 @@ public func makeAttachmentFileControllerImpl(
|
|||
}
|
||||
|> deliverOnMainQueue).startStandalone(next: { messages in
|
||||
if let message = messages.first, let file = message.media.first(where: { $0 is TelegramMediaFile }) as? TelegramMediaFile {
|
||||
send(.message(message: MessageReference(message), media: file))
|
||||
send([.message(message: MessageReference(message), media: file)])
|
||||
}
|
||||
dismissImpl?()
|
||||
})
|
||||
|
|
@ -523,9 +605,83 @@ public func makeAttachmentFileControllerImpl(
|
|||
toggleMediaPlayback: { message in
|
||||
let playlistLocation: PeerMessagesPlaylistLocation = .custom(messages: .single(([message], 0, false)), canReorder: false, at: message.id, loadMore: nil)
|
||||
context.sharedContext.mediaManager.setPlaylist((context, PeerMessagesMediaPlaylist(context: context, location: playlistLocation, chatLocationContextHolder: nil)), type: .music, control: .playback(.togglePlayPause))
|
||||
},
|
||||
isSelectionActive: {
|
||||
return stateValue.with { $0.selectedMessageIds != nil }
|
||||
},
|
||||
toggleMessageSelection: { message in
|
||||
updateState { state in
|
||||
guard var selectedMessageIds = state.selectedMessageIds else {
|
||||
return state
|
||||
}
|
||||
let messageId = message.id
|
||||
if selectedMessageIds.contains(messageId) {
|
||||
selectedMessageIds.remove(messageId)
|
||||
} else {
|
||||
selectedMessageIds.insert(messageId)
|
||||
}
|
||||
var updatedState = state
|
||||
updatedState.selectedMessageIds = selectedMessageIds
|
||||
updatedState.messageMap[messageId] = EngineMessage(message)
|
||||
updateSelectionCountImpl?(selectedMessageIds.count)
|
||||
return updatedState
|
||||
}
|
||||
},
|
||||
setMessageSelection: { messageIds, message, value in
|
||||
updateState { state in
|
||||
guard var selectedMessageIds = state.selectedMessageIds else {
|
||||
return state
|
||||
}
|
||||
for messageId in messageIds {
|
||||
if value {
|
||||
selectedMessageIds.insert(messageId)
|
||||
} else {
|
||||
selectedMessageIds.remove(messageId)
|
||||
}
|
||||
}
|
||||
var updatedState = state
|
||||
updatedState.selectedMessageIds = selectedMessageIds
|
||||
if let message {
|
||||
updatedState.messageMap[message.id] = EngineMessage(message)
|
||||
}
|
||||
updateSelectionCountImpl?(selectedMessageIds.count)
|
||||
return updatedState
|
||||
}
|
||||
},
|
||||
openMessageContextAction: { message, node, rect, anyRecognizer in
|
||||
guard let node = node as? ContextExtractedContentContainingNode else {
|
||||
return
|
||||
}
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let gesture: ContextGesture? = anyRecognizer as? ContextGesture
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.KeyCommand_Play, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Play"), color: theme.contextMenu.primaryColor) }, action: { c, _ in
|
||||
c?.dismiss(completion: {})
|
||||
|
||||
let playlistLocation: PeerMessagesPlaylistLocation = .custom(messages: .single(([message._asMessage()], 0, false)), canReorder: false, at: message.id, loadMore: nil)
|
||||
context.sharedContext.mediaManager.setPlaylist((context, PeerMessagesMediaPlaylist(context: context, location: playlistLocation, chatLocationContextHolder: nil)), type: .music, control: .playback(.togglePlayPause))
|
||||
})))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ContextMenuSelect, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) }, action: { c, _ in
|
||||
c?.dismiss(completion: {})
|
||||
updateState { state in
|
||||
var updatedState = state
|
||||
var selectedMessageIds = updatedState.selectedMessageIds ?? Set()
|
||||
selectedMessageIds.insert(message.id)
|
||||
updatedState.selectedMessageIds = selectedMessageIds
|
||||
updatedState.messageMap[message.id] = message
|
||||
updateSelectionCountImpl?(selectedMessageIds.count)
|
||||
return updatedState
|
||||
}
|
||||
})))
|
||||
|
||||
let controller = makeContextController(presentationData: presentationData, source: .extracted(MessageContextExtractedContentSource(sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture)
|
||||
presentInGlobalOverlayImpl?(controller)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
let recentDocuments: Signal<[Message]?, NoError>
|
||||
let savedMusicContext: ProfileSavedMusicContext?
|
||||
let savedMusic: Signal<[Message]?, NoError>
|
||||
|
|
@ -564,12 +720,12 @@ public func makeAttachmentFileControllerImpl(
|
|||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
|
||||
|
||||
|
||||
let existingCloseButton = Atomic<BarComponentHostNode?>(value: nil)
|
||||
let existingSearchButton = Atomic<BarComponentHostNode?>(value: nil)
|
||||
|
||||
|
||||
let previousRecentDocuments = Atomic<[Message]?>(value: nil)
|
||||
let signal = combineLatest(queue: Queue.mainQueue(),
|
||||
presentationData,
|
||||
|
|
@ -579,10 +735,10 @@ public func makeAttachmentFileControllerImpl(
|
|||
)
|
||||
|> map { presentationData, recentDocuments, savedMusic, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
var presentationData = presentationData
|
||||
|
||||
|
||||
let updatedTheme = presentationData.theme.withModalBlocksBackground()
|
||||
presentationData = presentationData.withUpdated(theme: updatedTheme)
|
||||
|
||||
|
||||
let barButtonSize = CGSize(width: 44.0, height: 44.0)
|
||||
let closeButton = GlassBarButtonComponent(
|
||||
size: barButtonSize,
|
||||
|
|
@ -611,7 +767,7 @@ public func makeAttachmentFileControllerImpl(
|
|||
}
|
||||
return buttonNode
|
||||
}
|
||||
|
||||
|
||||
let searchButton = GlassBarButtonComponent(
|
||||
size: barButtonSize,
|
||||
backgroundColor: nil,
|
||||
|
|
@ -634,8 +790,9 @@ public func makeAttachmentFileControllerImpl(
|
|||
updateIsSearchingImpl?(true)
|
||||
}
|
||||
)
|
||||
let searchButtonComponent = state.searching ? nil : AnyComponentWithIdentity(id: "search", component: AnyComponent(searchButton))
|
||||
let searchButtonNode: BarComponentHostNode? = !state.searching ? existingSearchButton.modify { current in
|
||||
let isSelectionActive = state.selectedMessageIds != nil
|
||||
let searchButtonComponent = (state.searching || isSelectionActive) ? nil : AnyComponentWithIdentity(id: "search", component: AnyComponent(searchButton))
|
||||
let searchButtonNode: BarComponentHostNode? = (!state.searching && !isSelectionActive) ? existingSearchButton.modify { current in
|
||||
let buttonNode: BarComponentHostNode
|
||||
if let current {
|
||||
buttonNode = current
|
||||
|
|
@ -645,7 +802,7 @@ public func makeAttachmentFileControllerImpl(
|
|||
}
|
||||
return buttonNode
|
||||
} : nil
|
||||
|
||||
|
||||
let previousRecentDocuments = previousRecentDocuments.swap(recentDocuments)
|
||||
let crossfade = previousRecentDocuments == nil && recentDocuments != nil
|
||||
var animateChanges = false
|
||||
|
|
@ -655,19 +812,19 @@ public func makeAttachmentFileControllerImpl(
|
|||
!crossfade {
|
||||
animateChanges = true
|
||||
}
|
||||
|
||||
|
||||
let leftNavigationButton = closeButtonNode.flatMap { ItemListNavigationButton(content: .node($0), style: .regular, enabled: true, action: {}) }
|
||||
|
||||
|
||||
var hasAudioSearch = false
|
||||
if let data = context.currentAppConfiguration.with({ $0 }).data, let _ = data["music_search_username"] as? String {
|
||||
hasAudioSearch = true
|
||||
}
|
||||
|
||||
|
||||
var rightNavigationButton: ItemListNavigationButton?
|
||||
if bannedSendMedia == nil && (recentDocuments == nil || (recentDocuments?.count ?? 0) > 10 || (mode == .audio && hasAudioSearch)) {
|
||||
rightNavigationButton = searchButtonNode.flatMap { ItemListNavigationButton(content: .node($0), style: .regular, enabled: true, action: {}) }
|
||||
}
|
||||
|
||||
|
||||
let title: String
|
||||
switch mode {
|
||||
case .recent:
|
||||
|
|
@ -675,7 +832,7 @@ public func makeAttachmentFileControllerImpl(
|
|||
case .audio:
|
||||
title = presentationData.strings.MediaEditor_Audio_Title
|
||||
}
|
||||
|
||||
|
||||
let controllerState = ItemListControllerState(
|
||||
presentationData: ItemListPresentationData(presentationData),
|
||||
title: .text(title),
|
||||
|
|
@ -684,7 +841,7 @@ public func makeAttachmentFileControllerImpl(
|
|||
backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back),
|
||||
animateChanges: true
|
||||
)
|
||||
|
||||
|
||||
var emptyItem: AttachmentFileEmptyStateItem?
|
||||
if let (untilDate, personal) = bannedSendMedia {
|
||||
let banDescription: String
|
||||
|
|
@ -700,7 +857,7 @@ public func makeAttachmentFileControllerImpl(
|
|||
recentDocuments.isEmpty {
|
||||
emptyItem = AttachmentFileEmptyStateItem(context: context, theme: presentationData.theme, strings: presentationData.strings, content: .intro)
|
||||
}
|
||||
|
||||
|
||||
var searchItem: ItemListControllerSearch?
|
||||
if state.searching {
|
||||
searchItem = AttachmentFileSearchItem(context: context, mode: mode, presentationData: presentationData, focus: {
|
||||
|
|
@ -719,17 +876,32 @@ public func makeAttachmentFileControllerImpl(
|
|||
dismissInputImpl?()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: attachmentFileControllerEntries(presentationData: presentationData, mode: mode, state: state, savedMusic: savedMusic, recentDocuments: recentDocuments, hasScan: presentDocumentScanner != nil, empty: bannedSendMedia != nil), style: .blocks, emptyStateItem: emptyItem, searchItem: searchItem, crossfadeState: crossfade, animateChanges: animateChanges)
|
||||
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
} |> afterDisposed {
|
||||
actionsDisposable.dispose()
|
||||
let _ = savedMusicContext?.state
|
||||
}
|
||||
|
||||
|
||||
let controller = AttachmentFileControllerImpl(context: context, state: signal, hideNavigationBarBackground: true)
|
||||
|
||||
controller.mulitpleCompletion = { _, _, _ in
|
||||
let _ = stateValue.with({ state in
|
||||
if let selectedMessageIds = state.selectedMessageIds {
|
||||
var mediaReferences: [AnyMediaReference] = []
|
||||
for id in selectedMessageIds {
|
||||
if let message = state.messageMap[id]?._asMessage(), let file = message.media.first(where: { $0 is TelegramMediaFile}) as? TelegramMediaFile {
|
||||
mediaReferences.append(.standalone(media: file))
|
||||
}
|
||||
}
|
||||
send(mediaReferences)
|
||||
dismissImpl?()
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
controller.delayDisappear = true
|
||||
controller.visibleBottomContentOffsetChanged = { [weak controller] offset in
|
||||
switch offset {
|
||||
|
|
@ -751,6 +923,7 @@ public func makeAttachmentFileControllerImpl(
|
|||
updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.searching = false
|
||||
updatedState.selectedMessageIds = nil
|
||||
return updatedState
|
||||
}
|
||||
}
|
||||
|
|
@ -769,6 +942,12 @@ public func makeAttachmentFileControllerImpl(
|
|||
updateTabBarVisibilityImpl = { [weak controller] isVisible in
|
||||
controller?.updateTabBarVisibility(isVisible, .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
updateSelectionCountImpl = { [weak controller] count in
|
||||
controller?.selectionCount.set(count)
|
||||
}
|
||||
presentInGlobalOverlayImpl = { [weak controller] c in
|
||||
controller?.presentInGlobalOverlay(c)
|
||||
}
|
||||
return controller
|
||||
}
|
||||
|
||||
|
|
@ -786,8 +965,8 @@ public func storyAudioPickerController(
|
|||
let filePickerController = makeAttachmentFileControllerImpl(context: context, updatedPresentationData: updatedPresentationData, mode: .audio, bannedSendMedia: nil, presentGallery: {}, presentFiles: {
|
||||
selectFromFiles()
|
||||
dismissImpl?()
|
||||
}, presentDocumentScanner: nil, send: { file in
|
||||
completion(file)
|
||||
}, presentDocumentScanner: nil, send: { files in
|
||||
completion(files.first!)
|
||||
dismissImpl?()
|
||||
}) as! AttachmentFileControllerImpl
|
||||
present(filePickerController, filePickerController.mediaPickerContext)
|
||||
|
|
@ -803,3 +982,26 @@ public func storyAudioPickerController(
|
|||
}
|
||||
return controller
|
||||
}
|
||||
|
||||
private final class MessageContextExtractedContentSource: ContextExtractedContentSource {
|
||||
let keepInPlace: Bool = false
|
||||
let ignoreContentTouches: Bool = true
|
||||
let blurBackground: Bool = true
|
||||
|
||||
let shouldBeDismissed: Signal<Bool, NoError>
|
||||
|
||||
private let sourceNode: ContextExtractedContentContainingNode
|
||||
|
||||
init(sourceNode: ContextExtractedContentContainingNode, shouldBeDismissed: Signal<Bool, NoError>? = nil) {
|
||||
self.sourceNode = sourceNode
|
||||
self.shouldBeDismissed = shouldBeDismissed ?? .single(false)
|
||||
}
|
||||
|
||||
func takeView() -> ContextControllerTakeViewInfo? {
|
||||
return ContextControllerTakeViewInfo(containingItem: .node(self.sourceNode), contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||
}
|
||||
|
||||
func putBack() -> ContextControllerPutBackViewInfo? {
|
||||
return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -216,11 +216,13 @@ private final class AttachmentFileSearchItemNode: ItemListControllerSearchNode {
|
|||
private final class AttachmentFileSearchContainerInteraction {
|
||||
let context: AccountContext
|
||||
let send: (Message) -> Void
|
||||
let toggleMediaPlayback: (Message) -> Void
|
||||
let expandSection: (Int32) -> Void
|
||||
|
||||
init(context: AccountContext, send: @escaping (Message) -> Void, expandSection: @escaping (Int32) -> Void) {
|
||||
init(context: AccountContext, send: @escaping (Message) -> Void, toggleMediaPlayback: @escaping (Message) -> Void, expandSection: @escaping (Int32) -> Void) {
|
||||
self.context = context
|
||||
self.send = send
|
||||
self.toggleMediaPlayback = toggleMediaPlayback
|
||||
self.expandSection = expandSection
|
||||
}
|
||||
}
|
||||
|
|
@ -322,13 +324,15 @@ private enum AttachmentFileSearchEntry: Comparable, Identifiable {
|
|||
let itemInteraction = ListMessageItemInteraction(openMessage: { message, _ in
|
||||
interaction.send(message)
|
||||
return false
|
||||
}, openMessageContextMenu: { _, _, _, _, _ in }, toggleMediaPlayback: { _ in }, toggleMessagesSelection: { _, _ in }, openUrl: { _, _, _, _ in }, openInstantPage: { _, _ in }, longTap: { _, _ in }, getHiddenMedia: { return [:] })
|
||||
}, openMessageContextMenu: { _, _, _, _, _ in }, toggleMediaPlayback: { message in
|
||||
interaction.toggleMediaPlayback(message)
|
||||
}, toggleMessagesSelection: { _, _ in }, openUrl: { _, _, _, _ in }, openInstantPage: { _, _ in }, longTap: { _, _ in }, getHiddenMedia: { return [:] })
|
||||
|
||||
let displayFileInfo = mode == .audio
|
||||
let isStoryMusic = mode == .audio
|
||||
let isDownloadList = mode == .audio
|
||||
|
||||
return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), systemStyle: .glass, context: interaction.context, chatLocation: .peer(id: PeerId(0)), interaction: itemInteraction, message: message, selection: .none, displayHeader: false, isDownloadList: isDownloadList, isStoryMusic: isStoryMusic, displayFileInfo: displayFileInfo, displayBackground: true, style: .blocks, sectionId: section)
|
||||
return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), systemStyle: .glass, context: interaction.context, chatLocation: .peer(id: PeerId(0)), interaction: itemInteraction, message: message, selection: .none, displayHeader: false, isDownloadList: isDownloadList, isStoryMusic: isStoryMusic, isAttachMusic: true, displayFileInfo: displayFileInfo, displayBackground: true, style: .blocks, sectionId: section)
|
||||
case let .showMore(text, section):
|
||||
return ItemListPeerActionItem(presentationData: ItemListPresentationData(presentationData), style: .blocks, systemStyle: .glass, icon: PresentationResourcesItemList.downArrowImage(presentationData.theme), title: text, sectionId: section, editing: false, action: {
|
||||
interaction.expandSection(section)
|
||||
|
|
@ -459,10 +463,17 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon
|
|||
self.addSubnode(self.emptyResultsTextNode)
|
||||
|
||||
|
||||
let interaction = AttachmentFileSearchContainerInteraction(context: context, send: { [weak self] message in
|
||||
send(message)
|
||||
self?.listNode.clearHighlightAnimated(true)
|
||||
}, expandSection: { [weak self] section in
|
||||
let interaction = AttachmentFileSearchContainerInteraction(
|
||||
context: context,
|
||||
send: { [weak self] message in
|
||||
send(message)
|
||||
self?.listNode.clearHighlightAnimated(true)
|
||||
},
|
||||
toggleMediaPlayback: { message in
|
||||
let playlistLocation: PeerMessagesPlaylistLocation = .custom(messages: .single(([message], 0, false)), canReorder: false, at: message.id, loadMore: nil)
|
||||
context.sharedContext.mediaManager.setPlaylist((context, PeerMessagesMediaPlaylist(context: context, location: playlistLocation, chatLocationContextHolder: nil)), type: .music, control: .playback(.togglePlayPause))
|
||||
},
|
||||
expandSection: { [weak self] section in
|
||||
self?.expandedSections.insert(section)
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -845,7 +845,7 @@ final class ComposePollScreenComponent: Component {
|
|||
|
||||
var availableButtons: [AttachmentButtonType]
|
||||
switch subject {
|
||||
case .description:
|
||||
case .description, .quizAnswer:
|
||||
availableButtons = [.gallery, .file, .location]
|
||||
default:
|
||||
availableButtons = [.gallery, .sticker, .emoji, .location]
|
||||
|
|
|
|||
|
|
@ -79,8 +79,8 @@ public func presentPollAttachmentScreen(
|
|||
//self?.presentICloudFileGallery()
|
||||
}, presentDocumentScanner: {
|
||||
//self?.presentDocumentScanner()
|
||||
}, send: { mediaReference in
|
||||
completion(mediaReference)
|
||||
}, send: { mediaReferences in
|
||||
completion(mediaReferences.first!)
|
||||
})
|
||||
guard let controller = controller as? AttachmentFileControllerImpl else {
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -1948,7 +1948,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) {
|
|||
}
|
||||
attachmentController?.dismiss(animated: true)
|
||||
self.presentICloudFileGallery(view: view, peer: peer, replyMessageId: nil, replyToStoryId: focusedStoryId)
|
||||
}, presentDocumentScanner: nil, send: { [weak view] mediaReference in
|
||||
}, presentDocumentScanner: nil, send: { [weak view] mediaReferences in
|
||||
guard let view, let component = view.component else {
|
||||
return
|
||||
}
|
||||
|
|
@ -1956,7 +1956,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) {
|
|||
if let sendPaidMessageStars = component.slice.additionalPeerData.sendPaidMessageStars {
|
||||
messageAttributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false))
|
||||
}
|
||||
let message: EnqueueMessage = .message(text: "", attributes: messageAttributes, inlineStickers: [:], mediaReference: mediaReference, threadId: nil, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])
|
||||
let message: EnqueueMessage = .message(text: "", attributes: messageAttributes, inlineStickers: [:], mediaReference: mediaReferences.first!, threadId: nil, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])
|
||||
let _ = (enqueueMessages(account: component.context.account, peerId: peer.id, messages: [message.withUpdatedReplyToMessageId(nil)])
|
||||
|> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in
|
||||
if let self, let view {
|
||||
|
|
|
|||
12
submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Emoji.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Emoji.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "emojitab.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Emoji.imageset/emojitab.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Emoji.imageset/emojitab.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Sticker.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Sticker.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "stickertab.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Sticker.imageset/stickertab.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Sticker.imageset/stickertab.pdf
vendored
Normal file
Binary file not shown.
|
|
@ -381,13 +381,20 @@ extension ChatControllerImpl {
|
|||
self?.presentICloudFileGallery()
|
||||
}, presentDocumentScanner: { [weak self] in
|
||||
self?.presentDocumentScanner()
|
||||
}, send: { [weak self] mediaReference in
|
||||
}, send: { [weak self] mediaReferences in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: mediaReference, threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])
|
||||
var messages: [EnqueueMessage] = []
|
||||
var groupingKey: Int64?
|
||||
if mediaReferences.count > 1 {
|
||||
groupingKey = Int64.random(in: .min ..< .max)
|
||||
}
|
||||
for mediaReference in mediaReferences {
|
||||
messages.append(.message(text: "", attributes: [], inlineStickers: [:], mediaReference: mediaReference, threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: groupingKey, correlationId: nil, bubbleUpEmojiOrStickersets: []))
|
||||
}
|
||||
self.presentPaidMessageAlertIfNeeded(completion: { [weak self] postpone in
|
||||
self?.sendMessages([message], media: true, postpone: postpone)
|
||||
self?.sendMessages(messages, media: true, postpone: postpone)
|
||||
})
|
||||
})
|
||||
if let controller = controller as? AttachmentFileControllerImpl {
|
||||
|
|
@ -407,13 +414,20 @@ extension ChatControllerImpl {
|
|||
}, presentFiles: { [weak self, weak attachmentController] in
|
||||
attachmentController?.dismiss(animated: true)
|
||||
self?.presentICloudFileGallery()
|
||||
}, presentDocumentScanner: nil, send: { [weak self] mediaReference in
|
||||
}, presentDocumentScanner: nil, send: { [weak self] mediaReferences in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: mediaReference, threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])
|
||||
var messages: [EnqueueMessage] = []
|
||||
var groupingKey: Int64?
|
||||
if mediaReferences.count > 1 {
|
||||
groupingKey = Int64.random(in: .min ..< .max)
|
||||
}
|
||||
for mediaReference in mediaReferences {
|
||||
messages.append(.message(text: "", attributes: [], inlineStickers: [:], mediaReference: mediaReference, threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: groupingKey, correlationId: nil, bubbleUpEmojiOrStickersets: []))
|
||||
}
|
||||
self.presentPaidMessageAlertIfNeeded(completion: { [weak self] postpone in
|
||||
self?.sendMessages([message], media: true, postpone: postpone)
|
||||
self?.sendMessages(messages, media: true, postpone: postpone)
|
||||
})
|
||||
})
|
||||
if let controller = controller as? AttachmentFileControllerImpl {
|
||||
|
|
|
|||
|
|
@ -2733,7 +2733,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
})
|
||||
}
|
||||
|
||||
public func makeAttachmentFileController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, audio: Bool, bannedSendMedia: (Int32, Bool)?, presentGallery: @escaping () -> Void, presentFiles: @escaping () -> Void, presentDocumentScanner: (() -> Void)?, send: @escaping (AnyMediaReference) -> Void) -> AttachmentFileController {
|
||||
public func makeAttachmentFileController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, audio: Bool, bannedSendMedia: (Int32, Bool)?, presentGallery: @escaping () -> Void, presentFiles: @escaping () -> Void, presentDocumentScanner: (() -> Void)?, send: @escaping ([AnyMediaReference]) -> Void) -> AttachmentFileController {
|
||||
return makeAttachmentFileControllerImpl(context: context, updatedPresentationData: updatedPresentationData, mode: audio ? .audio : .recent, bannedSendMedia: bannedSendMedia, presentGallery: presentGallery, presentFiles: presentFiles, presentDocumentScanner: presentDocumentScanner, send: send)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue