Various improvements

This commit is contained in:
Ilya Laktyushin 2026-03-21 09:47:17 +01:00
parent 8ac0f3d9e3
commit ea4bcd9a46
23 changed files with 733 additions and 165 deletions

View file

@ -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

View file

@ -46,6 +46,7 @@ swift_library(
"//submodules/TelegramUI/Components/EdgeEffect",
"//submodules/TelegramUI/Components/LiquidLens",
"//submodules/TelegramUI/Components/TabSelectionRecognizer",
"//submodules/TelegramBaseController",
],
visibility = [
"//visibility:public",

View file

@ -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 {

View file

@ -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 {

View file

@ -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
}
}

View file

@ -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()

View file

@ -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))
}

View file

@ -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()

View file

@ -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",

View file

@ -8,7 +8,6 @@ import DeleteChatPeerActionSheetItem
import PeerListItemComponent
import LegacyComponents
import LegacyUI
import WebSearchUI
import MapResourceToAvatarSizes
import LegacyMediaPickerUI
import AvatarNode

View file

@ -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()

View file

@ -26,7 +26,6 @@ import TooltipUI
import LegacyUI
import LegacyComponents
import LegacyMediaPickerUI
import WebSearchUI
import MapResourceToAvatarSizes
import SolidRoundedButtonNode
import AudioBlob

View file

@ -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)
}
}

View file

@ -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)
})

View file

@ -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]

View file

@ -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

View file

@ -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 {

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "emojitab.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "stickertab.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -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 {

View file

@ -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)
}