This commit is contained in:
Isaac 2026-03-23 21:33:37 +08:00
commit 19da3fa55a
75 changed files with 1293 additions and 466 deletions

View file

@ -15983,3 +15983,12 @@ Error: %8$@";
"Notification.PollAddedOptionYou" = "You added the option \"%1$@\" to the poll";
"Notification.ManagedBotCreated" = "%@ bot created";
"Chat.PolOptionAddedTimestamp.Date" = "Added by %1$@ %2$@";
"Chat.PolOptionAddedTimestamp.TodayAt" = "Added by %1$@ today at %2$@";
"Chat.PolOptionAddedTimestamp.YesterdayAt" = "Added by %1$@ yesterday at %2$@";
"Chat.PolOptionAddedTimestampYou.Date" = "Added by you %1$@";
"Chat.PolOptionAddedTimestampYou.TodayAt" = "Added by you today at %1$@";
"Chat.PolOptionAddedTimestampYou.YesterdayAt" = "Added by you yesterday at %1$@";

View file

@ -1390,7 +1390,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], Bool, Int32?, NSAttributedString?) -> 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

@ -263,7 +263,7 @@ public enum ChatControllerInteractionLongTapAction {
public enum ChatHistoryMessageSelection: Equatable {
case none
case selectable(selected: Bool)
case selectable(selected: Bool, num: Int?)
public static func ==(lhs: ChatHistoryMessageSelection, rhs: ChatHistoryMessageSelection) -> Bool {
switch lhs {
@ -273,8 +273,8 @@ public enum ChatHistoryMessageSelection: Equatable {
} else {
return false
}
case let .selectable(selected):
if case .selectable(selected) = rhs {
case let .selectable(selected, num):
if case .selectable(selected, num) = rhs {
return true
} else {
return false

View file

@ -28,7 +28,7 @@ public enum PeerMessagesPlaylistLocation: Equatable, SharedMediaPlaylistLocation
case singleMessage(MessageId)
case recentActions(Message)
case savedMusic(context: ProfileSavedMusicContext, at: Int32, canReorder: Bool)
case custom(messages: Signal<([Message], Int32, Bool), NoError>, canReorder: Bool, at: MessageId, loadMore: (() -> Void)?)
case custom(messages: Signal<([Message], Int32, Bool), NoError>, canReorder: Bool, at: MessageId, loadMore: (() -> Void)?, hidePanel: Bool)
public var playlistId: PeerMessagesMediaPlaylistId {
switch self {
@ -84,7 +84,8 @@ public enum PeerMessagesPlaylistLocation: Equatable, SharedMediaPlaylistLocation
return
}
savedMusicContext.loadMore()
}
},
hidePanel: false
)
default:
return self
@ -93,7 +94,7 @@ public enum PeerMessagesPlaylistLocation: Equatable, SharedMediaPlaylistLocation
public var messageId: MessageId? {
switch self {
case let .messages(_, _, messageId), let .singleMessage(messageId), let .custom(_, _, messageId, _):
case let .messages(_, _, messageId), let .singleMessage(messageId), let .custom(_, _, messageId, _, _):
return messageId
default:
return nil
@ -134,8 +135,8 @@ public enum PeerMessagesPlaylistLocation: Equatable, SharedMediaPlaylistLocation
} else {
return false
}
case let .custom(_, _, lhsAt, _):
if case let .custom(_, _, rhsAt, _) = rhs, lhsAt == rhsAt {
case let .custom(_, _, lhsAt, _, _):
if case let .custom(_, _, rhsAt, _, _) = rhs, lhsAt == rhsAt {
return true
} else {
return false

View file

@ -1384,13 +1384,12 @@ public class AttachmentController: ViewController, MinimizableController {
var containerInsets = containerLayout.intrinsicInsets
var hasPanel = false
// 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 || self.panel.hasMediaAccessoryPanel {
hasPanel = true
}
if !self.isPanelVisible {
if !self.isPanelVisible && !self.panel.hasMediaAccessoryPanel {
hasPanel = false
}
@ -1404,7 +1403,8 @@ public class AttachmentController: ViewController, MinimizableController {
}
let isEffecitvelyCollapsedUpdated = (self.selectionCount > 0) != (self.panel.isSelecting)
let panelHeight = self.panel.update(layout: containerLayout, buttons: self.controller?.buttons ?? [], isSelecting: self.selectionCount > 0, selectionCount: self.selectionCount, elevateProgress: !hasPanel && !hasButton, transition: transition)
let panelHeight = self.panel.update(layout: containerLayout, buttons: self.controller?.buttons ?? [], isSelecting: self.selectionCount > 0, selectionCount: self.selectionCount, elevateProgress: !hasPanel && !hasButton, hideButtons: !self.isPanelVisible && self.panel.hasMediaAccessoryPanel, transition: transition)
if hasPanel || hasButton {
containerInsets.bottom = panelHeight + panelOffset
}
@ -1526,7 +1526,7 @@ public class AttachmentController: ViewController, MinimizableController {
public var forceSourceRect = false
fileprivate var isStandalone: Bool {
return self.buttons.contains(.standalone)
return self.buttons.contains(.standalone) || self.buttons.count == 1
}
public func convertToStandalone() {

View file

@ -1015,9 +1015,11 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
private var secondaryButtonState: AttachmentMainButtonState = .initial
private var customBottomPanelBackgroundColor: UIColor?
private var hideButtons: Bool = false
private var elevateProgress: Bool = false
private var buttons: [AttachmentButtonType] = []
private var selectedIndex: Int = 0
private var selectionOverrideIndex: Int?
private(set) var isSelecting: Bool = false
private var selectionCount: Int = 0
@ -1025,12 +1027,30 @@ 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 {
private var peerMessagesPlaylistLocation: PeerMessagesPlaylistLocation? {
return self.playlistLocation as? PeerMessagesPlaylistLocation
}
private var currentButtonType: AttachmentButtonType? {
let selectedIndex = self.selectionOverrideIndex ?? self.selectedIndex
guard selectedIndex >= 0 && selectedIndex < self.buttons.count else {
return nil
}
return self.buttons[selectedIndex]
}
private var shouldDisplayMediaAccessoryPanel: Bool {
guard case .glass = self.panelStyle, self.playlistStateAndType != nil else {
return false
}
guard let playlistLocation = self.peerMessagesPlaylistLocation, case .custom = playlistLocation else {
return false
}
guard let currentButtonType = self.currentButtonType, case .audio = currentButtonType else {
return false
}
return true
}
var hasMediaAccessoryPanel: Bool {
return self.shouldDisplayMediaAccessoryPanel
}
private var validLayout: ContainerViewLayout?
@ -1470,11 +1490,13 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
return
}
let hadMediaAccessoryPanel = strongSelf.hasMediaAccessoryPanel
let updatedPeerMessagesPlaylistLocation = playlistStateAndType?.1.playlistLocation as? PeerMessagesPlaylistLocation
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 {
strongSelf.playlistStateAndType?.4 != playlistStateAndType?.2 ||
strongSelf.peerMessagesPlaylistLocation != updatedPeerMessagesPlaylistLocation {
if let playlistStateAndType {
strongSelf.playlistStateAndType = (
@ -1500,6 +1522,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
isSelecting: strongSelf.isSelecting,
selectionCount: strongSelf.selectionCount,
elevateProgress: strongSelf.elevateProgress,
hideButtons: strongSelf.hideButtons,
transition: .animated(duration: 0.4, curve: .spring)
)
}
@ -1527,7 +1550,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
strongSelf.updateChatPresentationInterfaceState({ $0.updatedTheme(presentationData.theme) })
if let layout = strongSelf.validLayout {
let _ = strongSelf.update(layout: layout, buttons: strongSelf.buttons, isSelecting: strongSelf.isSelecting, selectionCount: strongSelf.selectionCount, elevateProgress: strongSelf.elevateProgress, transition: .immediate)
let _ = strongSelf.update(layout: layout, buttons: strongSelf.buttons, isSelecting: strongSelf.isSelecting, selectionCount: strongSelf.selectionCount, elevateProgress: strongSelf.elevateProgress, hideButtons: strongSelf.hideButtons, transition: .immediate)
}
}
}).strict()
@ -1584,7 +1607,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
func requestLayout(transition: ContainedViewLayoutTransition) {
if let layout = self.validLayout {
let _ = self.update(layout: layout, buttons: self.buttons, isSelecting: self.isSelecting, selectionCount: self.selectionCount, elevateProgress: self.elevateProgress, transition: transition)
let _ = self.update(layout: layout, buttons: self.buttons, isSelecting: self.isSelecting, selectionCount: self.selectionCount, elevateProgress: self.elevateProgress, hideButtons: self.hideButtons, transition: transition)
}
}
@ -1702,40 +1725,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
)
}
}
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
@ -1784,14 +1774,14 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
self.context.sharedContext.mediaManager.playlistControl(.previous, type: type)
}
}
mediaAccessoryPanel.tapAction = { [weak self] in
self?.openCurrentMusicPlayer()
mediaAccessoryPanel.tapAction = {
}
return mediaAccessoryPanel
}
private func updateMediaAccessoryPanel(frame: CGRect?, transition: ContainedViewLayoutTransition) {
guard case .glass = self.panelStyle,
self.hasMediaAccessoryPanel,
let frame,
let (item, previousItem, nextItem, order, type, _) = self.playlistStateAndType else {
self.dismissMediaAccessoryPanel(transition: transition)
@ -1827,12 +1817,13 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, nil, nil)
}
let inset: CGFloat = self.hideButtons ? 19.0 : 0.0
if isNewPanel {
mediaAccessoryPanel.updateLayout(size: frame.size, leftInset: 0.0, rightInset: 0.0, isHidden: false, transition: .immediate)
mediaAccessoryPanel.updateLayout(size: frame.size, leftInset: inset, rightInset: inset, 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)
mediaAccessoryPanel.updateLayout(size: frame.size, leftInset: inset, rightInset: inset, isHidden: false, transition: transition)
}
}
@ -1869,8 +1860,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
}
func updateSelectedIndex(_ index: Int) {
let hadMediaAccessoryPanel = self.hasMediaAccessoryPanel
self.selectedIndex = index
self.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring)))
if hadMediaAccessoryPanel != self.hasMediaAccessoryPanel {
self.requestLayout(transition: .immediate)
}
}
var buttonSize: CGSize {
@ -1949,6 +1944,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
}
let button = self.buttons[index]
self.skipLensUpdate = true
self.selectionOverrideIndex = index
if self.selectionChanged(button) {
self.selectedIndex = index
if self.buttons.count > 5, let button = self.itemViews[button.key] {
@ -1972,6 +1968,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
self.updateItemContainers(contentOffset: newBounds.minX, transition: transition)
}
}
self.selectionOverrideIndex = nil
self.skipLensUpdate = false
}
self.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
@ -1986,8 +1983,13 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
return
}
var buttons = self.buttons
if buttons.count == 1 {
buttons.removeAll()
}
var width = layout.size.width
if self.buttons.count == 3 {
if buttons.count == 3 {
width = smallPanelWidth
}
@ -1999,11 +2001,11 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
panelSideInset = 3.0
}
var distanceBetweenNodes = floorToScreenPixels((width - panelSideInset * 2.0 - self.buttonSize.width) / CGFloat(max(1, self.buttons.count - 1)))
if self.buttons.count == 3 {
distanceBetweenNodes = floorToScreenPixels((width - panelSideInset * 2.0 - 32.0) / CGFloat(max(1, self.buttons.count - 1)))
var distanceBetweenNodes = floorToScreenPixels((width - panelSideInset * 2.0 - self.buttonSize.width) / CGFloat(max(1, buttons.count - 1)))
if buttons.count == 3 {
distanceBetweenNodes = floorToScreenPixels((width - panelSideInset * 2.0 - 32.0) / CGFloat(max(1, buttons.count - 1)))
}
let internalWidth = distanceBetweenNodes * CGFloat(self.buttons.count - 1)
let internalWidth = distanceBetweenNodes * CGFloat(max(0, buttons.count - 1))
var buttonWidth = self.buttonSize.width
var leftNodeOriginX: CGFloat
var maxButtonsToFit = 5
@ -2016,11 +2018,11 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
case .legacy:
leftNodeOriginX = (width - internalWidth) / 2.0
}
if self.buttons.count == 3 {
if buttons.count == 3 {
leftNodeOriginX = floor((layout.size.width - width) / 2.0) + 16.0
}
if self.buttons.count > maxButtonsToFit && layout.size.width < layout.size.height {
if buttons.count > maxButtonsToFit && layout.size.width < layout.size.height {
switch self.panelStyle {
case .glass:
buttonWidth = smallGlassButtonSize.width
@ -2036,12 +2038,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
var selectionFrame = CGRect()
var mostRightX = 0.0
for i in 0 ..< self.buttons.count {
for i in 0 ..< buttons.count {
let originX = floorToScreenPixels(leftNodeOriginX + CGFloat(i) * distanceBetweenNodes - buttonWidth / 2.0)
let buttonFrame = CGRect(origin: CGPoint(x: originX, y: -3.0), size: CGSize(width: buttonWidth, height: self.buttonSize.height))
mostRightX = buttonFrame.maxX
let type = self.buttons[i]
let type = buttons[i]
let _ = validIds.insert(type.key)
var buttonTransition = transition
@ -2096,20 +2098,22 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
context: self.context,
style: self.panelStyle == .glass ? .glass : .legacy,
type: type,
isFirstOrLast: i == 0 || i == self.buttons.count - 1,
isFirstOrLast: i == 0 || i == buttons.count - 1,
isSelected: false,
strings: self.presentationData.strings,
theme: self.presentationData.theme,
action: { [weak self] in
if let strongSelf = self {
strongSelf.selectionOverrideIndex = i
if strongSelf.selectionChanged(type) {
strongSelf.selectedIndex = i
strongSelf.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring)))
if strongSelf.buttons.count > 5, let button = strongSelf.itemViews[type.key] {
if buttons.count > 5, let button = strongSelf.itemViews[type.key] {
strongSelf.scrollNode.view.scrollRectToVisible(button.frame.insetBy(dx: -35.0, dy: 0.0), animated: true)
}
}
strongSelf.selectionOverrideIndex = nil
}
}, longPressAction: { [weak self] in
if let strongSelf = self, i == strongSelf.selectedIndex {
@ -2128,7 +2132,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
context: self.context,
style: self.panelStyle == .glass ? .glass : .legacy,
type: type,
isFirstOrLast: i == 0 || i == self.buttons.count - 1,
isFirstOrLast: i == 0 || i == buttons.count - 1,
isSelected: true,
strings: self.presentationData.strings,
theme: self.presentationData.theme,
@ -2203,6 +2207,16 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
self.scrollNode.view.isScrollEnabled = contentSize.width - self.scrollNode.view.bounds.width > 1.0
self.liquidLensView?.clipsToBounds = self.scrollNode.view.isScrollEnabled
}
if !self.hideButtons && self.itemsContainer.isHidden {
Queue.mainQueue().after(0.3, {
self.itemsContainer.isHidden = false
self.selectedItemsContainer.isHidden = false
})
} else {
self.itemsContainer.isHidden = self.hideButtons
self.selectedItemsContainer.isHidden = self.hideButtons
}
}
private func loadTextNodeIfNeeded() {
@ -2393,10 +2407,11 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
}
}
func update(layout: ContainerViewLayout, buttons: [AttachmentButtonType], isSelecting: Bool, selectionCount: Int, elevateProgress: Bool, transition: ContainedViewLayoutTransition) -> CGFloat {
func update(layout: ContainerViewLayout, buttons: [AttachmentButtonType], isSelecting: Bool, selectionCount: Int, elevateProgress: Bool, hideButtons: Bool, transition: ContainedViewLayoutTransition) -> CGFloat {
self.validLayout = layout
self.buttons = buttons
self.elevateProgress = elevateProgress
self.hideButtons = hideButtons
if selectionCount != self.selectionCount {
self.selectionCount = selectionCount
@ -2433,7 +2448,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
}
let topAccessoryHeight: CGFloat
if case .glass = self.panelStyle, self.playlistStateAndType != nil {
if self.hasMediaAccessoryPanel {
topAccessoryHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight
} else {
topAccessoryHeight = 0.0
@ -2474,7 +2489,10 @@ 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 + topAccessoryHeight))
var bounds = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: topAccessoryHeight + self.buttonSize.height + insets.bottom))
if (buttons.count == 1 || hideButtons) && topAccessoryHeight > 0.0 {
bounds.size.height -= self.buttonSize.height
}
var mediaAccessoryPanelFrame: CGRect?
if case .glass = self.panelStyle {
let backgroundView: GlassBackgroundView
@ -2517,9 +2535,19 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
}
let basePanelHeight = isSelecting ? max(0.0, textPanelHeight - 11.0) : glassPanelHeight
let panelSize = CGSize(width: isSelecting ? textPanelWidth : buttonsPanelWidth, height: basePanelHeight + topAccessoryHeight)
var panelSize = CGSize(width: isSelecting ? textPanelWidth : buttonsPanelWidth, height: basePanelHeight + topAccessoryHeight)
if !isSelecting && (buttons.count == 1 || hideButtons) && topAccessoryHeight > 0.0 {
panelSize.height = topAccessoryHeight
}
let cornerRadius: CGFloat = isSelecting ? min(20.0, basePanelHeight * 0.5) : glassPanelHeight * 0.5
let cornerRadius: CGFloat
if isSelecting {
cornerRadius = min(20.0, basePanelHeight * 0.5)
} else if self.hasMediaAccessoryPanel {
cornerRadius = 18.5
} else {
cornerRadius = 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))
@ -2705,7 +2733,9 @@ 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: topAccessoryHeight + (self.isSelecting ? -11.0 : 0.0)), size: CGSize(width: layout.size.width - panelSideInset * 2.0, height: self.buttonSize.height)))
let scrollFrame = 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))
transition.updatePosition(node: self.scrollNode, position: scrollFrame.center)
transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: self.scrollNode.view.contentOffset.x, y: 0.0), size: scrollFrame.size))
}
if let progress = self.loadingProgress {
@ -2741,7 +2771,6 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
buttonTopInset = isNarrowButton ? 2.0 : 8.0
}
if !self.animatingTransition {
let buttonOriginX = layout.safeInsets.left + buttonSideInset
let buttonOriginY = isAnyButtonVisible || self.fromMenu ? topAccessoryHeight + buttonTopInset : containerFrame.height
@ -2852,7 +2881,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
}
let inset: CGFloat = 3.0
liquidLensView.update(size: CGSize(width: panelSize.width - inset * 2.0, height: panelSize.height - inset * 2.0), cornerRadius: 28.0, selectionOrigin: CGPoint(x: lensSelection.x, y: 0.0), selectionSize: CGSize(width: lensSelection.width, height: panelSize.height - inset * 2.0), inset: 0.0, isDark: self.presentationData.theme.overallDarkAppearance, isLifted: isLifted, isCollapsed: self.isSelecting || self.buttons.count < 2, transition: transition)
liquidLensView.update(size: CGSize(width: panelSize.width - inset * 2.0, height: panelSize.height - inset * 2.0), cornerRadius: 28.0, selectionOrigin: CGPoint(x: lensSelection.x, y: 0.0), selectionSize: CGSize(width: lensSelection.width, height: panelSize.height - inset * 2.0), inset: 0.0, isDark: self.presentationData.theme.overallDarkAppearance, isLifted: isLifted, isCollapsed: self.isSelecting || self.buttons.count < 2 || self.hideButtons, transition: transition)
}
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {

View file

@ -1466,19 +1466,23 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
)
}
if let mediaPlayback = self.controller?.globalControlPanelsContextState?.mediaPlayback {
panels.append(HeaderPanelContainerComponent.Panel(
key: "media",
orderIndex: 1,
component: AnyComponent(MediaPlaybackHeaderPanelComponent(
context: self.context,
theme: self.presentationData.theme,
strings: self.presentationData.strings,
data: mediaPlayback,
controller: { [weak self] in
return self?.controller
}
)))
)
if let playlistLocation = mediaPlayback.playlistLocation as? PeerMessagesPlaylistLocation, case let .custom(_, _, _, _, hidePanel) = playlistLocation, hidePanel {
} else {
panels.append(HeaderPanelContainerComponent.Panel(
key: "media",
orderIndex: 1,
component: AnyComponent(MediaPlaybackHeaderPanelComponent(
context: self.context,
theme: self.presentationData.theme,
strings: self.presentationData.strings,
data: mediaPlayback,
controller: { [weak self] in
return self?.controller
}
)))
)
}
}
if let liveLocation = self.controller?.globalControlPanelsContextState?.liveLocation {
panels.append(HeaderPanelContainerComponent.Panel(

View file

@ -1148,7 +1148,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
})
}
}
let selection: ChatHistoryMessageSelection = selected.flatMap { .selectable(selected: $0) } ?? .none
let selection: ChatHistoryMessageSelection = selected.flatMap { .selectable(selected: $0, num: nil) } ?? .none
var isMedia = false
if let tagMask, tagMask != .photoOrVideo {
isMedia = true
@ -3534,7 +3534,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
return (message.map { $0._asMessage() }, a, b)
}, canReorder: false, at: message.id, loadMore: {
loadMore()
})
}, hidePanel: false)
}
return context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: .peer(id: message.id.peerId), chatFilterTag: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: true, mode: mode, navigationController: navigationController, dismissInput: {
@ -5886,7 +5886,7 @@ public final class ChatListSearchShimmerNode: ASDisplayNode {
associatedStories: [:]
)
return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), context: context, chatLocation: .peer(id: peer1.id), interaction: ListMessageItemInteraction.default, message: message._asMessage(), selection: hasSelection ? .selectable(selected: false) : .none, displayHeader: false, customHeader: nil, hintIsLink: true, isGlobalSearchResult: true)
return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), context: context, chatLocation: .peer(id: peer1.id), interaction: ListMessageItemInteraction.default, message: message._asMessage(), selection: hasSelection ? .selectable(selected: false, num: nil) : .none, displayHeader: false, customHeader: nil, hintIsLink: true, isGlobalSearchResult: true)
case .files:
var media: [EngineMedia] = []
media.append(.file(TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: 0, attributes: [.FileName(fileName: "Text.txt")], alternativeRepresentations: [])))
@ -5917,7 +5917,7 @@ public final class ChatListSearchShimmerNode: ASDisplayNode {
associatedStories: [:]
)
return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), context: context, chatLocation: .peer(id: peer1.id), interaction: ListMessageItemInteraction.default, message: message._asMessage(), selection: hasSelection ? .selectable(selected: false) : .none, displayHeader: false, customHeader: nil, hintIsLink: false, isGlobalSearchResult: true)
return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), context: context, chatLocation: .peer(id: peer1.id), interaction: ListMessageItemInteraction.default, message: message._asMessage(), selection: hasSelection ? .selectable(selected: false, num: nil) : .none, displayHeader: false, customHeader: nil, hintIsLink: false, isGlobalSearchResult: true)
case .music:
var media: [EngineMedia] = []
media.append(.file(TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: [.Audio(isVoice: false, duration: 0, title: nil, performer: nil, waveform: Data())], alternativeRepresentations: [])))
@ -5948,7 +5948,7 @@ public final class ChatListSearchShimmerNode: ASDisplayNode {
associatedStories: [:]
)
return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), context: context, chatLocation: .peer(id: peer1.id), interaction: ListMessageItemInteraction.default, message: message._asMessage(), selection: hasSelection ? .selectable(selected: false) : .none, displayHeader: false, customHeader: nil, hintIsLink: false, isGlobalSearchResult: true)
return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), context: context, chatLocation: .peer(id: peer1.id), interaction: ListMessageItemInteraction.default, message: message._asMessage(), selection: hasSelection ? .selectable(selected: false, num: nil) : .none, displayHeader: false, customHeader: nil, hintIsLink: false, isGlobalSearchResult: true)
case .voice, .instantVideo:
var media: [EngineMedia] = []
media.append(.file(TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: [.Audio(isVoice: true, duration: 0, title: nil, performer: nil, waveform: Data())], alternativeRepresentations: [])))
@ -5979,7 +5979,7 @@ public final class ChatListSearchShimmerNode: ASDisplayNode {
associatedStories: [:]
)
return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), context: context, chatLocation: .peer(id: peer1.id), interaction: ListMessageItemInteraction.default, message: message._asMessage(), selection: hasSelection ? .selectable(selected: false) : .none, displayHeader: false, customHeader: nil, hintIsLink: false, isGlobalSearchResult: true)
return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), context: context, chatLocation: .peer(id: peer1.id), interaction: ListMessageItemInteraction.default, message: message._asMessage(), selection: hasSelection ? .selectable(selected: false, num: nil) : .none, displayHeader: false, customHeader: nil, hintIsLink: false, isGlobalSearchResult: true)
}
}

View file

@ -2380,6 +2380,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
var currentStoryIcon: UIImage?
var currentGiftIcon: UIImage?
var currentLocationIcon: UIImage?
var currentPollIcon: UIImage?
var selectableControlSizeAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)?
var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool, ContainedViewLayoutTransition) -> ItemListEditableReorderControlNode)?
@ -2394,7 +2395,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
selectionControlStyle = .compact
}
let sizeAndApply = selectableControlLayout(item.presentationData.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.list.itemCheckColors.fillColor, item.presentationData.theme.list.itemCheckColors.foregroundColor, item.selected, selectionControlStyle)
let sizeAndApply = selectableControlLayout(item.presentationData.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.list.itemCheckColors.fillColor, item.presentationData.theme.list.itemCheckColors.foregroundColor, item.selected, selectionControlStyle, nil)
if promoInfo == nil && !isPeerGroup {
selectableControlSizeAndApply = sizeAndApply
}
@ -2555,6 +2556,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
var displayStoryReplyIcon = false
var displayGiftIcon = false
var displayLocationIcon = false
var displayPollIcon = false
var ignoreForwardedIcon = false
switch contentData {
@ -2867,7 +2869,9 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
displayStoryReplyIcon = true
} else {
for media in message.media {
if let _ = media as? TelegramMediaMap {
if let _ = media as? TelegramMediaPoll {
displayPollIcon = true
} else if let _ = media as? TelegramMediaMap {
displayLocationIcon = true
} else if let action = media as? TelegramMediaAction {
switch action.action {
@ -3048,6 +3052,10 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
currentLocationIcon = PresentationResourcesChatList.locationIcon(item.presentationData.theme)
}
if displayPollIcon {
currentPollIcon = PresentationResourcesChatList.pollIcon(item.presentationData.theme)
}
if let currentForwardedIcon {
textLeftCutout += currentForwardedIcon.size.width
if !contentImageSpecs.isEmpty {
@ -3084,6 +3092,15 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
}
}
if let currentPollIcon {
textLeftCutout += currentPollIcon.size.width
if !contentImageSpecs.isEmpty {
textLeftCutout += forwardedIconSpacing
} else {
textLeftCutout += contentImageTrailingSpace
}
}
for i in 0 ..< contentImageSpecs.count {
if i != 0 {
textLeftCutout += contentImageSpacing
@ -4745,6 +4762,9 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
} else if let currentLocationIcon {
messageTypeIcon = currentLocationIcon
messageTypeIconOffset.y -= 2.0 - UIScreenPixel
} else if let currentPollIcon {
messageTypeIcon = currentPollIcon
messageTypeIconOffset.y -= 2.0 - UIScreenPixel
}
if let messageTypeIcon {

View file

@ -395,15 +395,13 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
messageText = text
}
case let poll as TelegramMediaPoll:
let pollPrefix = "📊 "
let entityOffset = (pollPrefix as NSString).length
messageText = "\(pollPrefix)\(poll.text)"
messageText = poll.text
for entity in poll.textEntities {
if case let .CustomEmoji(_, fileId) = entity.type {
if customEmojiRanges == nil {
customEmojiRanges = []
}
let range = NSRange(location: entityOffset + entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound)
let range = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound)
let attribute = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: message.associatedMedia[EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile)
customEmojiRanges?.append((range, attribute))
}

View file

@ -83,6 +83,18 @@ public final class Text: Component {
self.color = color
self.tintColor = tintColor
}
public convenience init(attributedString: NSAttributedString, tintColor: UIColor? = nil) {
let attributes = attributedString.attributes(at: 0, effectiveRange: nil)
let font = attributes[.font] as? UIFont ?? UIFont.systemFont(ofSize: UIFont.systemFontSize)
let color = attributes[.foregroundColor] as? UIColor ?? .black
self.init(
text: attributedString.string,
font: font,
color: color,
tintColor: tintColor
)
}
public static func ==(lhs: Text, rhs: Text) -> Bool {
if lhs.text != rhs.text {

View file

@ -184,7 +184,7 @@ public final class BalancedTextComponent: Component {
var bestSize: (availableWidth: CGFloat, info: TextNodeLayout)
let info = self.textView.updateLayoutFullInfo(availableSize)
let info = self.textView.updateLayoutFullInfo(availableSize)
bestSize = (availableSize.width, info)
if component.balanced && info.numberOfLines > 1 {

View file

@ -37,6 +37,7 @@ public final class CounterControllerTitleView: UIView {
private var primaryTextColor: UIColor?
private var secondaryTextColor: UIColor?
private let verticalOffset: CGFloat
private var nextLayoutTransition: ContainedViewLayoutTransition?
@ -65,7 +66,7 @@ public final class CounterControllerTitleView: UIView {
let secondaryTextColor = self.secondaryTextColor ?? self.theme.rootController.navigationBar.secondaryTextColor
self.titleNode.attributedText = NSAttributedString(string: self.title.title, font: Font.semibold(17.0), textColor: primaryTextColor)
let subtitleText = NSAttributedString(string: self.title.counter ?? "", font: Font.with(size: 13.0, traits: .monospacedNumbers), textColor: secondaryTextColor)
let subtitleText = NSAttributedString(string: self.title.counter ?? "", font: Font.with(size: 12.0, traits: .monospacedNumbers), textColor: secondaryTextColor)
if let previousSubtitleText = self.subtitleNode.attributedText, previousSubtitleText.string.isEmpty != subtitleText.string.isEmpty && subtitleText.string.isEmpty {
if let disappearingSubtitleNode = self.disappearingSubtitleNode {
self.disappearingSubtitleNode = nil
@ -94,8 +95,9 @@ public final class CounterControllerTitleView: UIView {
self.setNeedsLayout()
}
public init(theme: PresentationTheme) {
public init(theme: PresentationTheme, verticalOffset: CGFloat = 0.0) {
self.theme = theme
self.verticalOffset = verticalOffset
self.titleNode = ImmediateTextNode()
self.titleNode.displaysAsynchronously = false
@ -126,7 +128,7 @@ public final class CounterControllerTitleView: UIView {
super.layoutSubviews()
let size = self.bounds.size
let spacing: CGFloat = 0.0
let spacing: CGFloat = 1.0
let titleSize = self.titleNode.updateLayout(CGSize(width: max(1.0, size.width), height: size.height))
let subtitleSize = self.subtitleNode.updateLayout(CGSize(width: max(1.0, size.width), height: size.height))
@ -146,11 +148,11 @@ public final class CounterControllerTitleView: UIView {
self.nextLayoutTransition = nil
}
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0)), size: titleSize)
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0) + self.verticalOffset), size: titleSize)
self.titleNode.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
transition.updatePosition(node: self.titleNode, position: titleFrame.center)
let subtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0) + titleSize.height + spacing), size: subtitleSize)
let subtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0) + titleSize.height + spacing + self.verticalOffset), size: subtitleSize)
self.subtitleNode.bounds = CGRect(origin: CGPoint(), size: subtitleFrame.size)
transition.updatePosition(node: self.subtitleNode, position: subtitleFrame.center)
transition.updateTransformScale(node: self.subtitleNode, scale: self.title.counter != nil ? 1.0 : 0.001)

View file

@ -1213,7 +1213,6 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
guard let message = self.message else {
return false
}
var canDelete = false
if let peer = message.peers[message.id.peerId] {
if peer is TelegramUser || peer is TelegramSecretChat {
@ -1232,6 +1231,9 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
} else {
canDelete = false
}
if let _ = message.media.first(where: { $0 is TelegramMediaPoll }) {
canDelete = false
}
return canDelete
}

View file

@ -2149,7 +2149,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
if isActive {
self.setLivePhotoVideoVisible(true, animated: animated)
videoNode.seek(0.0)
videoNode.play()
videoNode.playOnceWithSound(playAndRecord: false)
} else {
videoNode.pause()
videoNode.seek(0.0)

View file

@ -191,7 +191,7 @@ public class ItemListAddressItemNode: ListViewItemNode {
var leftOffset: CGFloat = 0.0
var selectionNodeWidthAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)?
if let selected = item.selected {
let (selectionWidth, selectionApply) = selectionNodeLayout(item.theme.list.itemCheckColors.strokeColor, item.theme.list.itemCheckColors.fillColor, item.theme.list.itemCheckColors.foregroundColor, selected, .regular)
let (selectionWidth, selectionApply) = selectionNodeLayout(item.theme.list.itemCheckColors.strokeColor, item.theme.list.itemCheckColors.fillColor, item.theme.list.itemCheckColors.foregroundColor, selected, .regular, nil)
selectionNodeWidthAndApply = (selectionWidth, selectionApply)
leftOffset += selectionWidth - 8.0
}

View file

@ -451,7 +451,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
if case let .check(checked) = item.control {
selected = checked
}
let sizeAndApply = selectableControlLayout(item.presentationData.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.list.itemCheckColors.fillColor, item.presentationData.theme.list.itemCheckColors.foregroundColor, selected, .compact)
let sizeAndApply = selectableControlLayout(item.presentationData.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.list.itemCheckColors.fillColor, item.presentationData.theme.list.itemCheckColors.foregroundColor, selected, .compact, nil)
selectableControlSizeAndApply = sizeAndApply
editingOffset = sizeAndApply.0
} else {

View file

@ -699,14 +699,14 @@ private final class ItemListTextWithSubtitleTitleView: UIView, NavigationBarTitl
self.titleNode.maximumNumberOfLines = 1
self.titleNode.isOpaque = false
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(17.0), textColor: theme.rootController.navigationBar.primaryTextColor)
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: theme.rootController.navigationBar.primaryTextColor)
self.subtitleNode = ImmediateTextNode()
self.subtitleNode.displaysAsynchronously = false
self.subtitleNode.maximumNumberOfLines = 1
self.subtitleNode.isOpaque = false
self.subtitleNode.attributedText = NSAttributedString(string: subtitle, font: Font.regular(13.0), textColor: theme.rootController.navigationBar.secondaryTextColor)
self.subtitleNode.attributedText = NSAttributedString(string: subtitle, font: Font.regular(12.0), textColor: theme.rootController.navigationBar.secondaryTextColor)
super.init(frame: CGRect())
@ -719,8 +719,8 @@ private final class ItemListTextWithSubtitleTitleView: UIView, NavigationBarTitl
}
func updateTheme(theme: PresentationTheme) {
self.titleNode.attributedText = NSAttributedString(string: self.titleNode.attributedText?.string ?? "", font: Font.medium(17.0), textColor: theme.rootController.navigationBar.primaryTextColor)
self.subtitleNode.attributedText = NSAttributedString(string: self.subtitleNode.attributedText?.string ?? "", font: Font.regular(13.0), textColor: theme.rootController.navigationBar.secondaryTextColor)
self.titleNode.attributedText = NSAttributedString(string: self.titleNode.attributedText?.string ?? "", font: Font.semibold(17.0), textColor: theme.rootController.navigationBar.primaryTextColor)
self.subtitleNode.attributedText = NSAttributedString(string: self.subtitleNode.attributedText?.string ?? "", font: Font.regular(12.0), textColor: theme.rootController.navigationBar.secondaryTextColor)
if let size = self.validLayout {
let _ = self.updateLayout(availableSize: size, transition: .immediate)
}
@ -741,7 +741,7 @@ private final class ItemListTextWithSubtitleTitleView: UIView, NavigationBarTitl
let titleSize = self.titleNode.updateLayout(size)
let subtitleSize = self.subtitleNode.updateLayout(size)
let spacing: CGFloat = 0.0
let spacing: CGFloat = 1.0
let contentHeight = titleSize.height + spacing + subtitleSize.height
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor((size.height - contentHeight) / 2.0)), size: titleSize)
let subtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: titleFrame.maxY + spacing), size: subtitleSize)

View file

@ -22,8 +22,8 @@ public final class ItemListSelectableControlNode: ASDisplayNode {
self.addSubnode(self.checkNode)
}
public static func asyncLayout(_ node: ItemListSelectableControlNode?) -> (_ strokeColor: UIColor, _ fillColor: UIColor, _ foregroundColor: UIColor, _ selected: Bool, _ style: Style) -> (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode) {
return { strokeColor, fillColor, foregroundColor, selected, style in
public static func asyncLayout(_ node: ItemListSelectableControlNode?) -> (_ strokeColor: UIColor, _ fillColor: UIColor, _ foregroundColor: UIColor, _ selected: Bool, _ style: Style, _ num: Int?) -> (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode) {
return { strokeColor, fillColor, foregroundColor, selected, style, num in
let resultNode: ItemListSelectableControlNode
if let node = node {
resultNode = node
@ -53,6 +53,9 @@ public final class ItemListSelectableControlNode: ASDisplayNode {
checkOffset = 16.0
}
resultNode.checkNode.frame = CGRect(origin: CGPoint(x: checkOffset, y: floorToScreenPixels((size.height - checkSize.height) / 2.0)), size: checkSize)
if let num {
resultNode.checkNode.content = .counter(num)
}
resultNode.checkNode.setSelected(selected, animated: animated)
return resultNode
})

View file

@ -189,7 +189,7 @@ public class ItemListTextWithLabelItemNode: ListViewItemNode {
var leftOffset: CGFloat = 0.0
var selectionNodeWidthAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)?
if let selected = item.selected {
let (selectionWidth, selectionApply) = selectionNodeLayout(item.presentationData.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.list.itemCheckColors.fillColor, item.presentationData.theme.list.itemCheckColors.foregroundColor, selected, .regular)
let (selectionWidth, selectionApply) = selectionNodeLayout(item.presentationData.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.list.itemCheckColors.fillColor, item.presentationData.theme.list.itemCheckColors.foregroundColor, selected, .regular, nil)
selectionNodeWidthAndApply = (selectionWidth, selectionApply)
leftOffset += selectionWidth - 8.0
}

View file

@ -66,7 +66,7 @@ public enum LegacyICloudFilePickerMode {
}
}
public func legacyICloudFilePicker(theme: PresentationTheme, mode: LegacyICloudFilePickerMode = .default, url: URL? = nil, documentTypes: [String] = ["public.item"], forceDarkTheme: Bool = false, dismissed: @escaping () -> Void = {}, completion: @escaping ([URL]) -> Void) -> ViewController {
public func legacyICloudFilePicker(theme: PresentationTheme, mode: LegacyICloudFilePickerMode = .default, url: URL? = nil, documentTypes: [String] = ["public.item"], forceDarkTheme: Bool = false, dismissed: @escaping () -> Void = {}, completion: @escaping ([URL]) -> Void) -> ViewController {
var dismissImpl: (() -> Void)?
let legacyController = LegacyICloudFileController(presentation: .modal(animateIn: true), theme: theme, completion: { urls in
dismissImpl?()

View file

@ -638,8 +638,8 @@ public final class ListMessageFileItemNode: ListMessageNode {
var leftOffset: CGFloat = 0.0
var selectionNodeWidthAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)?
if case let .selectable(selected) = item.selection {
let (selectionWidth, selectionApply) = selectionNodeLayout(item.presentationData.theme.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.theme.list.itemCheckColors.fillColor, item.presentationData.theme.theme.list.itemCheckColors.foregroundColor, selected, .regular)
if case let .selectable(selected, num) = item.selection {
let (selectionWidth, selectionApply) = selectionNodeLayout(item.presentationData.theme.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.theme.list.itemCheckColors.fillColor, item.presentationData.theme.theme.list.itemCheckColors.foregroundColor, selected, .regular, num.flatMap { $0 + 1 })
selectionNodeWidthAndApply = (selectionWidth, selectionApply)
leftOffset += selectionWidth
}
@ -933,7 +933,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
if statusUpdated && item.displayFileInfo {
if let file = selectedMedia as? TelegramMediaFile {
updatedStatusSignal = messageFileMediaResourceStatus(context: item.context, file: file, message: EngineMessage(message), isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult, isDownloadList: item.isDownloadList, isSavedMusic: item.isSavedMusic, isAttachMusic: item.isAttachMusic)
updatedStatusSignal = messageFileMediaResourceStatus(context: item.context, file: file, message: EngineMessage(message), isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult, isDownloadList: item.isDownloadList, isSavedMusic: item.isSavedMusic, isAttachMusic: item.isAttachMusic || item.isStoryMusic)
|> mapToSignal { value -> Signal<FileMediaResourceStatus, NoError> in
if case .Fetching = value.fetchStatus, !item.isDownloadList {
return .single(value) |> delay(0.1, queue: Queue.concurrentDefaultQueue())

View file

@ -245,7 +245,7 @@ public final class ListMessageItem: ListViewItem, ItemListItem {
return
}
if case let .selectable(selected) = self.selection {
if case let .selectable(selected, _) = self.selection {
self.interaction.toggleMessagesSelection([message.id], !selected)
} else {
if !self.displayFileInfo || self.isAttachMusic {

View file

@ -267,8 +267,8 @@ public final class ListMessageSnippetItemNode: ListMessageNode {
var leftOffset: CGFloat = 0.0
var selectionNodeWidthAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)?
if case let .selectable(selected) = item.selection {
let (selectionWidth, selectionApply) = selectionNodeLayout(item.presentationData.theme.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.theme.list.itemCheckColors.fillColor, item.presentationData.theme.theme.list.itemCheckColors.foregroundColor, selected, .regular)
if case let .selectable(selected, num) = item.selection {
let (selectionWidth, selectionApply) = selectionNodeLayout(item.presentationData.theme.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.theme.list.itemCheckColors.fillColor, item.presentationData.theme.theme.list.itemCheckColors.foregroundColor, selected, .regular, num.flatMap { $0 + 1 })
selectionNodeWidthAndApply = (selectionWidth, selectionApply)
leftOffset += selectionWidth
}

View file

@ -53,6 +53,7 @@ swift_library(
"//submodules/TelegramUI/Components/SearchInputPanelComponent",
"//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/TelegramUI/Components/PlainButtonComponent",
"//submodules/CounterControllerTitleView",
],
visibility = [
"//visibility:public",

View file

@ -11,6 +11,7 @@ import CoreLocation
import PresentationDataUtils
import DeviceAccess
import AttachmentUI
import CounterControllerTitleView
public enum LocationPickerMode {
case share(peer: EnginePeer?, selfPeer: EnginePeer?, hasLiveLocation: Bool)
@ -52,9 +53,16 @@ class LocationPickerInteraction {
}
public final class LocationPickerController: ViewController, AttachmentContainable {
public enum Source {
public enum Source: Equatable {
public enum PollMode: Equatable {
case description
case quizAnswer
case option
}
case generic
case story
case poll(PollMode)
}
private var controllerNode: LocationPickerControllerNode {
@ -63,7 +71,7 @@ public final class LocationPickerController: ViewController, AttachmentContainab
private let context: AccountContext
private let mode: LocationPickerMode
private let source: Source
let source: Source
let initialLocation: CLLocationCoordinate2D?
private let completion: (TelegramMediaMap, Int64?, String?, String?, String?) -> Void
private var presentationData: PresentationData
@ -115,7 +123,6 @@ public final class LocationPickerController: ViewController, AttachmentContainab
if case .glass = style {
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView())
} else {
self.title = self.presentationData.strings.Map_ChooseLocationTitle
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
}
self.updateBarButtons()

View file

@ -354,6 +354,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
private let cancelButton = ComponentView<Empty>()
private let searchButton = ComponentView<Empty>()
private let title = ComponentView<Empty>()
private let subtitle = ComponentView<Empty>()
fileprivate let topEdgeEffectView: EdgeEffectView
fileprivate let bottomEdgeEffectView: EdgeEffectView
@ -670,15 +671,18 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
var coordinate = userLocation?.coordinate
switch strongSelf.mode {
case .share:
if source == .story {
switch source {
case .generic:
title = presentationData.strings.Map_SendMyCurrentLocation
case .story:
if let initialLocation = strongSelf.controller?.initialLocation {
title = presentationData.strings.Location_AddThisLocation
coordinate = initialLocation
} else {
title = presentationData.strings.Location_AddMyLocation
}
} else {
title = presentationData.strings.Map_SendMyCurrentLocation
case .poll:
title = presentationData.strings.Location_AddThisLocation
}
case .pick:
title = presentationData.strings.Map_SetThisLocation
@ -1358,6 +1362,27 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
self.view.addSubview(self.topEdgeEffectView)
}
let titleSpacing: CGFloat = 1.0
var combinedTitleHeight: CGFloat = 0.0
var subtitle: String = ""
if let source = self.controller?.source {
switch source {
case let .poll(pollMode):
//TODO:localize
switch pollMode {
case .description:
subtitle = "Add location to the poll description"
case .quizAnswer:
subtitle = "Add location to the quiz explanation"
case .option:
subtitle = "Add location to this option"
}
default:
break
}
}
let titleSize = self.title.update(
transition: ComponentTransition(transition),
component: AnyComponent(
@ -1374,7 +1399,37 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
environment: {},
containerSize: CGSize(width: 200.0, height: 40.0)
)
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - titleSize.width) / 2.0), y: floorToScreenPixels((navigationHeight - titleSize.height) / 2.0) + 3.0), size: titleSize)
combinedTitleHeight += titleSize.height
if !subtitle.isEmpty {
let subtitleSize = self.subtitle.update(
transition: ComponentTransition(transition),
component: AnyComponent(
MultilineTextComponent(
text: .plain(
NSAttributedString(
string: subtitle,
font: Font.regular(12.0),
textColor: self.headerNode.mapNode.mapMode == .map ? self.presentationData.theme.rootController.navigationBar.primaryTextColor.withMultipliedAlpha(0.5) : .white.withAlphaComponent(0.5)
)
)
)
),
environment: {},
containerSize: CGSize(width: 200.0, height: 40.0)
)
combinedTitleHeight += subtitleSize.height + titleSpacing
let subtitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - subtitleSize.width) / 2.0), y: floorToScreenPixels((navigationHeight + combinedTitleHeight) / 2.0) + 3.0 - subtitleSize.height), size: subtitleSize)
if let subtitleView = self.subtitle.view {
if subtitleView.superview == nil {
self.view.addSubview(subtitleView)
}
transition.updateFrame(view: subtitleView, frame: subtitleFrame)
}
}
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - titleSize.width) / 2.0), y: floorToScreenPixels((navigationHeight - combinedTitleHeight) / 2.0) + 3.0), size: titleSize)
if let titleView = self.title.view {
if titleView.superview == nil {
self.view.addSubview(titleView)

View file

@ -412,8 +412,13 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
if case .glass = controller.style {
self.containerNode.view.addSubview(self.topEdgeEffectView)
if case let .assets(_, mode) = controller.subject, [.default, .story].contains(mode) {
self.containerNode.view.addSubview(self.bottomEdgeEffectView)
if case let .assets(_, mode) = controller.subject {
switch mode {
case .default, .story, .poll:
self.containerNode.view.addSubview(self.bottomEdgeEffectView)
default:
break
}
}
}
@ -2046,6 +2051,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self.titleView.title = presentationData.strings.MediaPicker_ChooseCover
case let .poll(pollMode):
self.titleView.title = presentationData.strings.MediaPicker_Recents
//TODO:localize
switch pollMode {
case .description:
self.titleView.subtitle = "Add media to the poll description"
@ -2560,7 +2566,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
titleIsEnabled = false
}
self.titleView.updateTitle(title: title, isEnabled: titleIsEnabled, animated: true)
self.titleView.updateTitle(title: title, subtitle: self.titleView.subtitle, isEnabled: titleIsEnabled, animated: true)
self.cancelButtonNode.setState(isEnabled ? .cancel : .back, animated: true)
isBack = !isEnabled

View file

@ -303,7 +303,7 @@ class GiftOptionItemNode: ItemListRevealOptionsItemNode {
var editingOffset: CGFloat = 0.0
if let isSelected = item.isSelected {
let sizeAndApply = selectableControlLayout(item.presentationData.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.list.itemCheckColors.fillColor, item.presentationData.theme.list.itemCheckColors.foregroundColor, isSelected, .regular)
let sizeAndApply = selectableControlLayout(item.presentationData.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.list.itemCheckColors.fillColor, item.presentationData.theme.list.itemCheckColors.foregroundColor, isSelected, .regular, nil)
selectableControlSizeAndApply = sizeAndApply
editingOffset = sizeAndApply.0
}

View file

@ -236,7 +236,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[2104790276] = { return Api.DataJSON.parse_dataJSON($0) }
dict[414687501] = { return Api.DcOption.parse_dcOption($0) }
dict[1135897376] = { return Api.DefaultHistoryTTL.parse_defaultHistoryTTL($0) }
dict[-712374074] = { return Api.Dialog.parse_dialog($0) }
dict[-58066957] = { return Api.Dialog.parse_dialog($0) }
dict[1908216652] = { return Api.Dialog.parse_dialogFolder($0) }
dict[-1438177711] = { return Api.DialogFilter.parse_dialogFilter($0) }
dict[-1772913705] = { return Api.DialogFilter.parse_dialogFilterChatlist($0) }
@ -878,7 +878,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1546531968] = { return Api.ReactionCount.parse_reactionCount($0) }
dict[1268654752] = { return Api.ReactionNotificationsFrom.parse_reactionNotificationsFromAll($0) }
dict[-1161583078] = { return Api.ReactionNotificationsFrom.parse_reactionNotificationsFromContacts($0) }
dict[1457736048] = { return Api.ReactionsNotifySettings.parse_reactionsNotifySettings($0) }
dict[1910827608] = { return Api.ReactionsNotifySettings.parse_reactionsNotifySettings($0) }
dict[1246753138] = { return Api.ReadParticipantDate.parse_readParticipantDate($0) }
dict[-1551583367] = { return Api.ReceivedNotifyMessage.parse_receivedNotifyMessage($0) }
dict[-1294306862] = { return Api.RecentMeUrl.parse_recentMeUrlChat($0) }
@ -1173,7 +1173,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1216408986] = { return Api.Update.parse_updateManagedBot($0) }
dict[-710666460] = { return Api.Update.parse_updateMessageExtendedMedia($0) }
dict[1318109142] = { return Api.Update.parse_updateMessageID($0) }
dict[-1398708869] = { return Api.Update.parse_updateMessagePoll($0) }
dict[-699641301] = { return Api.Update.parse_updateMessagePoll($0) }
dict[619974263] = { return Api.Update.parse_updateMessagePollVote($0) }
dict[506035194] = { return Api.Update.parse_updateMessageReactions($0) }
dict[-1618924792] = { return Api.Update.parse_updateMonoForumNoPaidException($0) }

View file

@ -351,17 +351,19 @@ public extension Api {
public var flags: Int32
public var messagesNotifyFrom: Api.ReactionNotificationsFrom?
public var storiesNotifyFrom: Api.ReactionNotificationsFrom?
public var pollVotesNotifyFrom: Api.ReactionNotificationsFrom?
public var sound: Api.NotificationSound
public var showPreviews: Api.Bool
public init(flags: Int32, messagesNotifyFrom: Api.ReactionNotificationsFrom?, storiesNotifyFrom: Api.ReactionNotificationsFrom?, sound: Api.NotificationSound, showPreviews: Api.Bool) {
public init(flags: Int32, messagesNotifyFrom: Api.ReactionNotificationsFrom?, storiesNotifyFrom: Api.ReactionNotificationsFrom?, pollVotesNotifyFrom: Api.ReactionNotificationsFrom?, sound: Api.NotificationSound, showPreviews: Api.Bool) {
self.flags = flags
self.messagesNotifyFrom = messagesNotifyFrom
self.storiesNotifyFrom = storiesNotifyFrom
self.pollVotesNotifyFrom = pollVotesNotifyFrom
self.sound = sound
self.showPreviews = showPreviews
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("reactionsNotifySettings", [("flags", self.flags as Any), ("messagesNotifyFrom", self.messagesNotifyFrom as Any), ("storiesNotifyFrom", self.storiesNotifyFrom as Any), ("sound", self.sound as Any), ("showPreviews", self.showPreviews as Any)])
return ("reactionsNotifySettings", [("flags", self.flags as Any), ("messagesNotifyFrom", self.messagesNotifyFrom as Any), ("storiesNotifyFrom", self.storiesNotifyFrom as Any), ("pollVotesNotifyFrom", self.pollVotesNotifyFrom as Any), ("sound", self.sound as Any), ("showPreviews", self.showPreviews as Any)])
}
}
case reactionsNotifySettings(Cons_reactionsNotifySettings)
@ -370,7 +372,7 @@ public extension Api {
switch self {
case .reactionsNotifySettings(let _data):
if boxed {
buffer.appendInt32(1457736048)
buffer.appendInt32(1910827608)
}
serializeInt32(_data.flags, buffer: buffer, boxed: false)
if Int(_data.flags) & Int(1 << 0) != 0 {
@ -379,6 +381,9 @@ public extension Api {
if Int(_data.flags) & Int(1 << 1) != 0 {
_data.storiesNotifyFrom!.serialize(buffer, true)
}
if Int(_data.flags) & Int(1 << 2) != 0 {
_data.pollVotesNotifyFrom!.serialize(buffer, true)
}
_data.sound.serialize(buffer, true)
_data.showPreviews.serialize(buffer, true)
break
@ -388,7 +393,7 @@ public extension Api {
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .reactionsNotifySettings(let _data):
return ("reactionsNotifySettings", [("flags", _data.flags as Any), ("messagesNotifyFrom", _data.messagesNotifyFrom as Any), ("storiesNotifyFrom", _data.storiesNotifyFrom as Any), ("sound", _data.sound as Any), ("showPreviews", _data.showPreviews as Any)])
return ("reactionsNotifySettings", [("flags", _data.flags as Any), ("messagesNotifyFrom", _data.messagesNotifyFrom as Any), ("storiesNotifyFrom", _data.storiesNotifyFrom as Any), ("pollVotesNotifyFrom", _data.pollVotesNotifyFrom as Any), ("sound", _data.sound as Any), ("showPreviews", _data.showPreviews as Any)])
}
}
@ -407,21 +412,28 @@ public extension Api {
_3 = Api.parse(reader, signature: signature) as? Api.ReactionNotificationsFrom
}
}
var _4: Api.NotificationSound?
if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.NotificationSound
var _4: Api.ReactionNotificationsFrom?
if Int(_1!) & Int(1 << 2) != 0 {
if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.ReactionNotificationsFrom
}
}
var _5: Api.Bool?
var _5: Api.NotificationSound?
if let signature = reader.readInt32() {
_5 = Api.parse(reader, signature: signature) as? Api.Bool
_5 = Api.parse(reader, signature: signature) as? Api.NotificationSound
}
var _6: Api.Bool?
if let signature = reader.readInt32() {
_6 = Api.parse(reader, signature: signature) as? Api.Bool
}
let _c1 = _1 != nil
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
let _c4 = _4 != nil
let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil
let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.ReactionsNotifySettings.reactionsNotifySettings(Cons_reactionsNotifySettings(flags: _1!, messagesNotifyFrom: _2, storiesNotifyFrom: _3, sound: _4!, showPreviews: _5!))
let _c6 = _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.ReactionsNotifySettings.reactionsNotifySettings(Cons_reactionsNotifySettings(flags: _1!, messagesNotifyFrom: _2, storiesNotifyFrom: _3, pollVotesNotifyFrom: _4, sound: _5!, showPreviews: _6!))
}
else {
return nil

View file

@ -1667,17 +1667,23 @@ public extension Api {
}
public class Cons_updateMessagePoll: TypeConstructorDescription {
public var flags: Int32
public var peer: Api.Peer?
public var msgId: Int32?
public var topMsgId: Int32?
public var pollId: Int64
public var poll: Api.Poll?
public var results: Api.PollResults
public init(flags: Int32, pollId: Int64, poll: Api.Poll?, results: Api.PollResults) {
public init(flags: Int32, peer: Api.Peer?, msgId: Int32?, topMsgId: Int32?, pollId: Int64, poll: Api.Poll?, results: Api.PollResults) {
self.flags = flags
self.peer = peer
self.msgId = msgId
self.topMsgId = topMsgId
self.pollId = pollId
self.poll = poll
self.results = results
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("updateMessagePoll", [("flags", self.flags as Any), ("pollId", self.pollId as Any), ("poll", self.poll as Any), ("results", self.results as Any)])
return ("updateMessagePoll", [("flags", self.flags as Any), ("peer", self.peer as Any), ("msgId", self.msgId as Any), ("topMsgId", self.topMsgId as Any), ("pollId", self.pollId as Any), ("poll", self.poll as Any), ("results", self.results as Any)])
}
}
public class Cons_updateMessagePollVote: TypeConstructorDescription {
@ -3414,9 +3420,18 @@ public extension Api {
break
case .updateMessagePoll(let _data):
if boxed {
buffer.appendInt32(-1398708869)
buffer.appendInt32(-699641301)
}
serializeInt32(_data.flags, buffer: buffer, boxed: false)
if Int(_data.flags) & Int(1 << 1) != 0 {
_data.peer!.serialize(buffer, true)
}
if Int(_data.flags) & Int(1 << 1) != 0 {
serializeInt32(_data.msgId!, buffer: buffer, boxed: false)
}
if Int(_data.flags) & Int(1 << 2) != 0 {
serializeInt32(_data.topMsgId!, buffer: buffer, boxed: false)
}
serializeInt64(_data.pollId, buffer: buffer, boxed: false)
if Int(_data.flags) & Int(1 << 0) != 0 {
_data.poll!.serialize(buffer, true)
@ -4228,7 +4243,7 @@ public extension Api {
case .updateMessageID(let _data):
return ("updateMessageID", [("id", _data.id as Any), ("randomId", _data.randomId as Any)])
case .updateMessagePoll(let _data):
return ("updateMessagePoll", [("flags", _data.flags as Any), ("pollId", _data.pollId as Any), ("poll", _data.poll as Any), ("results", _data.results as Any)])
return ("updateMessagePoll", [("flags", _data.flags as Any), ("peer", _data.peer as Any), ("msgId", _data.msgId as Any), ("topMsgId", _data.topMsgId as Any), ("pollId", _data.pollId as Any), ("poll", _data.poll as Any), ("results", _data.results as Any)])
case .updateMessagePollVote(let _data):
return ("updateMessagePollVote", [("pollId", _data.pollId as Any), ("peer", _data.peer as Any), ("options", _data.options as Any), ("qts", _data.qts as Any)])
case .updateMessageReactions(let _data):
@ -5956,24 +5971,41 @@ public extension Api {
public static func parse_updateMessagePoll(_ reader: BufferReader) -> Update? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int64?
_2 = reader.readInt64()
var _3: Api.Poll?
if Int(_1!) & Int(1 << 0) != 0 {
var _2: Api.Peer?
if Int(_1!) & Int(1 << 1) != 0 {
if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.Poll
_2 = Api.parse(reader, signature: signature) as? Api.Peer
}
}
var _4: Api.PollResults?
var _3: Int32?
if Int(_1!) & Int(1 << 1) != 0 {
_3 = reader.readInt32()
}
var _4: Int32?
if Int(_1!) & Int(1 << 2) != 0 {
_4 = reader.readInt32()
}
var _5: Int64?
_5 = reader.readInt64()
var _6: Api.Poll?
if Int(_1!) & Int(1 << 0) != 0 {
if let signature = reader.readInt32() {
_6 = Api.parse(reader, signature: signature) as? Api.Poll
}
}
var _7: Api.PollResults?
if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.PollResults
_7 = Api.parse(reader, signature: signature) as? Api.PollResults
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.Update.updateMessagePoll(Cons_updateMessagePoll(flags: _1!, pollId: _2!, poll: _3, results: _4!))
let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil
let _c5 = _5 != nil
let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil
let _c7 = _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.Update.updateMessagePoll(Cons_updateMessagePoll(flags: _1!, peer: _2, msgId: _3, topMsgId: _4, pollId: _5!, poll: _6, results: _7!))
}
else {
return nil

View file

@ -7872,6 +7872,30 @@ public extension Api.functions.messages {
})
}
}
public extension Api.functions.messages {
static func getUnreadPollVotes(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.Messages>) {
let buffer = Buffer()
buffer.appendInt32(1126722802)
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {
serializeInt32(topMsgId!, buffer: buffer, boxed: false)
}
serializeInt32(offsetId, buffer: buffer, boxed: false)
serializeInt32(addOffset, buffer: buffer, boxed: false)
serializeInt32(limit, buffer: buffer, boxed: false)
serializeInt32(maxId, buffer: buffer, boxed: false)
serializeInt32(minId, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.getUnreadPollVotes", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("topMsgId", String(describing: topMsgId)), ("offsetId", String(describing: offsetId)), ("addOffset", String(describing: addOffset)), ("limit", String(describing: limit)), ("maxId", String(describing: maxId)), ("minId", String(describing: minId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in
let reader = BufferReader(buffer)
var result: Api.messages.Messages?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.messages.Messages
}
return result
})
}
}
public extension Api.functions.messages {
static func getUnreadReactions(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?, savedPeerId: Api.InputPeer?, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.Messages>) {
let buffer = Buffer()
@ -8219,6 +8243,25 @@ public extension Api.functions.messages {
})
}
}
public extension Api.functions.messages {
static func readPollVotes(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.AffectedHistory>) {
let buffer = Buffer()
buffer.appendInt32(388019416)
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {
serializeInt32(topMsgId!, buffer: buffer, boxed: false)
}
return (FunctionDescription(name: "messages.readPollVotes", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("topMsgId", String(describing: topMsgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in
let reader = BufferReader(buffer)
var result: Api.messages.AffectedHistory?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory
}
return result
})
}
}
public extension Api.functions.messages {
static func readReactions(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?, savedPeerId: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.AffectedHistory>) {
let buffer = Buffer()

View file

@ -1570,12 +1570,13 @@ public extension Api {
public var unreadCount: Int32
public var unreadMentionsCount: Int32
public var unreadReactionsCount: Int32
public var unreadPollVotesCount: Int32
public var notifySettings: Api.PeerNotifySettings
public var pts: Int32?
public var draft: Api.DraftMessage?
public var folderId: Int32?
public var ttlPeriod: Int32?
public init(flags: Int32, peer: Api.Peer, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, unreadMentionsCount: Int32, unreadReactionsCount: Int32, notifySettings: Api.PeerNotifySettings, pts: Int32?, draft: Api.DraftMessage?, folderId: Int32?, ttlPeriod: Int32?) {
public init(flags: Int32, peer: Api.Peer, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, unreadMentionsCount: Int32, unreadReactionsCount: Int32, unreadPollVotesCount: Int32, notifySettings: Api.PeerNotifySettings, pts: Int32?, draft: Api.DraftMessage?, folderId: Int32?, ttlPeriod: Int32?) {
self.flags = flags
self.peer = peer
self.topMessage = topMessage
@ -1584,6 +1585,7 @@ public extension Api {
self.unreadCount = unreadCount
self.unreadMentionsCount = unreadMentionsCount
self.unreadReactionsCount = unreadReactionsCount
self.unreadPollVotesCount = unreadPollVotesCount
self.notifySettings = notifySettings
self.pts = pts
self.draft = draft
@ -1591,7 +1593,7 @@ public extension Api {
self.ttlPeriod = ttlPeriod
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("dialog", [("flags", self.flags as Any), ("peer", self.peer as Any), ("topMessage", self.topMessage as Any), ("readInboxMaxId", self.readInboxMaxId as Any), ("readOutboxMaxId", self.readOutboxMaxId as Any), ("unreadCount", self.unreadCount as Any), ("unreadMentionsCount", self.unreadMentionsCount as Any), ("unreadReactionsCount", self.unreadReactionsCount as Any), ("notifySettings", self.notifySettings as Any), ("pts", self.pts as Any), ("draft", self.draft as Any), ("folderId", self.folderId as Any), ("ttlPeriod", self.ttlPeriod as Any)])
return ("dialog", [("flags", self.flags as Any), ("peer", self.peer as Any), ("topMessage", self.topMessage as Any), ("readInboxMaxId", self.readInboxMaxId as Any), ("readOutboxMaxId", self.readOutboxMaxId as Any), ("unreadCount", self.unreadCount as Any), ("unreadMentionsCount", self.unreadMentionsCount as Any), ("unreadReactionsCount", self.unreadReactionsCount as Any), ("unreadPollVotesCount", self.unreadPollVotesCount as Any), ("notifySettings", self.notifySettings as Any), ("pts", self.pts as Any), ("draft", self.draft as Any), ("folderId", self.folderId as Any), ("ttlPeriod", self.ttlPeriod as Any)])
}
}
public class Cons_dialogFolder: TypeConstructorDescription {
@ -1624,7 +1626,7 @@ public extension Api {
switch self {
case .dialog(let _data):
if boxed {
buffer.appendInt32(-712374074)
buffer.appendInt32(-58066957)
}
serializeInt32(_data.flags, buffer: buffer, boxed: false)
_data.peer.serialize(buffer, true)
@ -1634,6 +1636,7 @@ public extension Api {
serializeInt32(_data.unreadCount, buffer: buffer, boxed: false)
serializeInt32(_data.unreadMentionsCount, buffer: buffer, boxed: false)
serializeInt32(_data.unreadReactionsCount, buffer: buffer, boxed: false)
serializeInt32(_data.unreadPollVotesCount, buffer: buffer, boxed: false)
_data.notifySettings.serialize(buffer, true)
if Int(_data.flags) & Int(1 << 0) != 0 {
serializeInt32(_data.pts!, buffer: buffer, boxed: false)
@ -1667,7 +1670,7 @@ public extension Api {
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .dialog(let _data):
return ("dialog", [("flags", _data.flags as Any), ("peer", _data.peer as Any), ("topMessage", _data.topMessage as Any), ("readInboxMaxId", _data.readInboxMaxId as Any), ("readOutboxMaxId", _data.readOutboxMaxId as Any), ("unreadCount", _data.unreadCount as Any), ("unreadMentionsCount", _data.unreadMentionsCount as Any), ("unreadReactionsCount", _data.unreadReactionsCount as Any), ("notifySettings", _data.notifySettings as Any), ("pts", _data.pts as Any), ("draft", _data.draft as Any), ("folderId", _data.folderId as Any), ("ttlPeriod", _data.ttlPeriod as Any)])
return ("dialog", [("flags", _data.flags as Any), ("peer", _data.peer as Any), ("topMessage", _data.topMessage as Any), ("readInboxMaxId", _data.readInboxMaxId as Any), ("readOutboxMaxId", _data.readOutboxMaxId as Any), ("unreadCount", _data.unreadCount as Any), ("unreadMentionsCount", _data.unreadMentionsCount as Any), ("unreadReactionsCount", _data.unreadReactionsCount as Any), ("unreadPollVotesCount", _data.unreadPollVotesCount as Any), ("notifySettings", _data.notifySettings as Any), ("pts", _data.pts as Any), ("draft", _data.draft as Any), ("folderId", _data.folderId as Any), ("ttlPeriod", _data.ttlPeriod as Any)])
case .dialogFolder(let _data):
return ("dialogFolder", [("flags", _data.flags as Any), ("folder", _data.folder as Any), ("peer", _data.peer as Any), ("topMessage", _data.topMessage as Any), ("unreadMutedPeersCount", _data.unreadMutedPeersCount as Any), ("unreadUnmutedPeersCount", _data.unreadUnmutedPeersCount as Any), ("unreadMutedMessagesCount", _data.unreadMutedMessagesCount as Any), ("unreadUnmutedMessagesCount", _data.unreadUnmutedMessagesCount as Any)])
}
@ -1692,28 +1695,30 @@ public extension Api {
_7 = reader.readInt32()
var _8: Int32?
_8 = reader.readInt32()
var _9: Api.PeerNotifySettings?
var _9: Int32?
_9 = reader.readInt32()
var _10: Api.PeerNotifySettings?
if let signature = reader.readInt32() {
_9 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings
_10 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings
}
var _10: Int32?
var _11: Int32?
if Int(_1!) & Int(1 << 0) != 0 {
_10 = reader.readInt32()
_11 = reader.readInt32()
}
var _11: Api.DraftMessage?
var _12: Api.DraftMessage?
if Int(_1!) & Int(1 << 1) != 0 {
if let signature = reader.readInt32() {
_11 = Api.parse(reader, signature: signature) as? Api.DraftMessage
_12 = Api.parse(reader, signature: signature) as? Api.DraftMessage
}
}
var _12: Int32?
if Int(_1!) & Int(1 << 4) != 0 {
_12 = reader.readInt32()
}
var _13: Int32?
if Int(_1!) & Int(1 << 5) != 0 {
if Int(_1!) & Int(1 << 4) != 0 {
_13 = reader.readInt32()
}
var _14: Int32?
if Int(_1!) & Int(1 << 5) != 0 {
_14 = reader.readInt32()
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
@ -1723,12 +1728,13 @@ public extension Api {
let _c7 = _7 != nil
let _c8 = _8 != nil
let _c9 = _9 != nil
let _c10 = (Int(_1!) & Int(1 << 0) == 0) || _10 != nil
let _c11 = (Int(_1!) & Int(1 << 1) == 0) || _11 != nil
let _c12 = (Int(_1!) & Int(1 << 4) == 0) || _12 != nil
let _c13 = (Int(_1!) & Int(1 << 5) == 0) || _13 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 {
return Api.Dialog.dialog(Cons_dialog(flags: _1!, peer: _2!, topMessage: _3!, readInboxMaxId: _4!, readOutboxMaxId: _5!, unreadCount: _6!, unreadMentionsCount: _7!, unreadReactionsCount: _8!, notifySettings: _9!, pts: _10, draft: _11, folderId: _12, ttlPeriod: _13))
let _c10 = _10 != nil
let _c11 = (Int(_1!) & Int(1 << 0) == 0) || _11 != nil
let _c12 = (Int(_1!) & Int(1 << 1) == 0) || _12 != nil
let _c13 = (Int(_1!) & Int(1 << 4) == 0) || _13 != nil
let _c14 = (Int(_1!) & Int(1 << 5) == 0) || _14 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 {
return Api.Dialog.dialog(Cons_dialog(flags: _1!, peer: _2!, topMessage: _3!, readInboxMaxId: _4!, readOutboxMaxId: _5!, unreadCount: _6!, unreadMentionsCount: _7!, unreadReactionsCount: _8!, unreadPollVotesCount: _9!, notifySettings: _10!, pts: _11, draft: _12, folderId: _13, ttlPeriod: _14))
}
else {
return nil

View file

@ -443,7 +443,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, ASScrollVi
let inset: CGFloat = 45.0 + leftInset
let constrainedSize = CGSize(width: size.width - inset * 2.0, height: size.height)
let (titleString, subtitleString, rateButtonHidden) = self.currentItemNode.updateLayout(size: constrainedSize, leftInset: leftInset, rightInset: rightInset, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, playbackItem: self.playbackItems?.0, transition: transition)
let (titleString, subtitleString, rateButtonHidden) = self.currentItemNode.updateLayout(size: constrainedSize, leftInset: 0.0, rightInset: 0.0, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, playbackItem: self.playbackItems?.0, transition: transition)
self.accessibilityAreaNode.accessibilityLabel = "\(titleString?.string ?? ""). \(subtitleString?.string ?? "")"
self.rateButton.isHidden = rateButtonHidden
@ -479,13 +479,13 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, ASScrollVi
let bounds = CGRect(origin: CGPoint(), size: size)
let closeButtonSize = self.closeButton.measure(CGSize(width: 100.0, height: 100.0))
transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: bounds.size.width - 44.0 - rightInset, y: 0.0), size: CGSize(width: 44.0, height: minHeight)))
transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: bounds.size.width - 44.0, y: 0.0), size: CGSize(width: 44.0, height: minHeight)))
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 + 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.actionButton, frame: CGRect(origin: CGPoint(x: 4.0, y: 0.0), size: CGSize(width: 40.0, height: 37.0)))
transition.updateFrame(node: self.scrubbingNode, frame: CGRect(origin: CGPoint(x: leftInset, y: 37.0 - 2.0), size: CGSize(width: size.width - leftInset - rightInset, height: 2.0)))
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)))

View file

@ -454,7 +454,7 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI
let (poll, results) = (messageMediaPollData.poll, messageMediaPollData.results)
switch poll {
case let .poll(pollData):
let (id, flags, question, answers, closePeriod, closeDate) = (pollData.id, pollData.flags, pollData.question, pollData.answers, pollData.closePeriod, pollData.closeDate)
let (id, flags, question, answers, closePeriod, closeDate, pollHash) = (pollData.id, pollData.flags, pollData.question, pollData.answers, pollData.closePeriod, pollData.closeDate, pollData.hash)
let publicity: TelegramMediaPollPublicity
if (flags & (1 << 1)) != 0 {
publicity = .public
@ -472,6 +472,7 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI
let revotingDisabled = (flags & (1 << 7)) != 0
let shuffleAnswers = (flags & (1 << 8)) != 0
let hideResultsUntilClose = (flags & (1 << 9)) != 0
let isCreator = (flags & (1 << 10)) != 0
let questionText: String
let questionEntities: [MessageTextEntity]
@ -486,7 +487,7 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI
if let apiAttachedMedia = messageMediaPollData.attachedMedia {
parsedAttachedMedia = textMediaAndExpirationTimerFromApiMedia(apiAttachedMedia, peerId).media
}
return (TelegramMediaPoll(pollId: MediaId(namespace: Namespaces.Media.CloudPoll, id: id), publicity: publicity, kind: kind, text: questionText, textEntities: questionEntities, options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: TelegramMediaPollResults(apiResults: results), isClosed: (flags & (1 << 0)) != 0, deadlineTimeout: closePeriod, deadlineDate: closeDate, openAnswers: openAnswers, revotingDisabled: revotingDisabled, shuffleAnswers: shuffleAnswers, hideResultsUntilClose: hideResultsUntilClose, attachedMedia: parsedAttachedMedia), nil, nil, nil, nil, nil)
return (TelegramMediaPoll(pollId: MediaId(namespace: Namespaces.Media.CloudPoll, id: id), publicity: publicity, kind: kind, text: questionText, textEntities: questionEntities, options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: TelegramMediaPollResults(apiResults: results), isClosed: (flags & (1 << 0)) != 0, deadlineTimeout: closePeriod, deadlineDate: closeDate, pollHash: pollHash, openAnswers: openAnswers, revotingDisabled: revotingDisabled, shuffleAnswers: shuffleAnswers, hideResultsUntilClose: hideResultsUntilClose, isCreator: isCreator, attachedMedia: parsedAttachedMedia), nil, nil, nil, nil, nil)
}
case let .messageMediaToDo(messageMediaToDoData):
let (todo, completions) = (messageMediaToDoData.todo, messageMediaToDoData.completions)

View file

@ -4500,7 +4500,7 @@ func replayFinalState(
if let apiPoll = apiPoll {
switch apiPoll {
case let .poll(pollData):
let (id, flags, question, answers, closePeriod, closeDate) = (pollData.id, pollData.flags, pollData.question, pollData.answers, pollData.closePeriod, pollData.closeDate)
let (id, flags, question, answers, closePeriod, closeDate, pollHash) = (pollData.id, pollData.flags, pollData.question, pollData.answers, pollData.closePeriod, pollData.closeDate, pollData.hash)
let publicity: TelegramMediaPollPublicity
if (flags & (1 << 1)) != 0 {
publicity = .public
@ -4517,6 +4517,7 @@ func replayFinalState(
let revotingDisabled = (flags & (1 << 7)) != 0
let shuffleAnswers = (flags & (1 << 8)) != 0
let hideResultsUntilClose = (flags & (1 << 9)) != 0
let isCreator = (flags & (1 << 10)) != 0
let questionText: String
let questionEntities: [MessageTextEntity]
@ -4527,7 +4528,7 @@ func replayFinalState(
questionEntities = messageTextEntitiesFromApiEntities(entities)
}
updatedPoll = TelegramMediaPoll(pollId: MediaId(namespace: Namespaces.Media.CloudPoll, id: id), publicity: publicity, kind: kind, text: questionText, textEntities: questionEntities, options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: poll.results, isClosed: (flags & (1 << 0)) != 0, deadlineTimeout: closePeriod, deadlineDate: closeDate, openAnswers: openAnswers, revotingDisabled: revotingDisabled, shuffleAnswers: shuffleAnswers, hideResultsUntilClose: hideResultsUntilClose, attachedMedia: poll.attachedMedia)
updatedPoll = TelegramMediaPoll(pollId: MediaId(namespace: Namespaces.Media.CloudPoll, id: id), publicity: publicity, kind: kind, text: questionText, textEntities: questionEntities, options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: poll.results, isClosed: (flags & (1 << 0)) != 0, deadlineTimeout: closePeriod, deadlineDate: closeDate, pollHash: pollHash, openAnswers: openAnswers, revotingDisabled: revotingDisabled, shuffleAnswers: shuffleAnswers, hideResultsUntilClose: hideResultsUntilClose, isCreator: isCreator, attachedMedia: poll.attachedMedia)
}
}
updatedPoll = updatedPoll.withUpdatedResults(TelegramMediaPollResults(apiResults: results), min: resultsMin)

View file

@ -164,13 +164,15 @@ private func fetchWebpage(account: Account, messageId: MessageId, threadId: Int6
}
private func fetchPoll(account: Account, messageId: MessageId) -> Signal<Void, NoError> {
return account.postbox.loadedPeerWithId(messageId.peerId)
|> take(1)
|> mapToSignal { peer -> Signal<Void, NoError> in
guard let inputPeer = apiInputPeer(peer) else {
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
guard let peer = transaction.getPeer(messageId.peerId), let inputPeer = apiInputPeer(peer) else {
return .complete()
}
return account.network.request(Api.functions.messages.getPollResults(peer: inputPeer, msgId: messageId.id, pollHash: 0))
var pollHash: Int64 = 0
if let message = transaction.getMessage(messageId), let poll = message.media.first(where: { $0 is TelegramMediaPoll }) as? TelegramMediaPoll {
pollHash = poll.pollHash
}
return account.network.request(Api.functions.messages.getPollResults(peer: inputPeer, msgId: messageId.id, pollHash: pollHash))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil)
@ -182,6 +184,7 @@ private func fetchPoll(account: Account, messageId: MessageId) -> Signal<Void, N
return .complete()
}
}
|> switchToLatest
}
private func wrappedHistoryViewAdditionalData(chatLocation: ChatLocationInput, additionalData: [AdditionalMessageHistoryViewData]) -> [AdditionalMessageHistoryViewData] {

View file

@ -421,6 +421,7 @@ private func pushedNotificationSettings(network: Network, settings: GlobalNotifi
flags: reactionFlags,
messagesNotifyFrom: reactionsMessages,
storiesNotifyFrom: reactionsStories,
pollVotesNotifyFrom: nil,
sound: settings.reactionSettings.sound.apiSound,
showPreviews: settings.reactionSettings.hideSender == .hide ? .boolFalse : .boolTrue
))

View file

@ -252,14 +252,16 @@ public final class TelegramMediaPoll: Media, Equatable {
public let isClosed: Bool
public let deadlineTimeout: Int32?
public let deadlineDate: Int32?
public let pollHash: Int64
public let openAnswers: Bool
public let revotingDisabled: Bool
public let shuffleAnswers: Bool
public let hideResultsUntilClose: Bool
public let isCreator: Bool
public let attachedMedia: Media?
public init(pollId: MediaId, publicity: TelegramMediaPollPublicity, kind: TelegramMediaPollKind, text: String, textEntities: [MessageTextEntity], options: [TelegramMediaPollOption], correctAnswers: [Data]?, results: TelegramMediaPollResults, isClosed: Bool, deadlineTimeout: Int32?, deadlineDate: Int32?, openAnswers: Bool = false, revotingDisabled: Bool = false, shuffleAnswers: Bool = false, hideResultsUntilClose: Bool = false, attachedMedia: Media? = nil) {
public init(pollId: MediaId, publicity: TelegramMediaPollPublicity, kind: TelegramMediaPollKind, text: String, textEntities: [MessageTextEntity], options: [TelegramMediaPollOption], correctAnswers: [Data]?, results: TelegramMediaPollResults, isClosed: Bool, deadlineTimeout: Int32?, deadlineDate: Int32?, pollHash: Int64, openAnswers: Bool = false, revotingDisabled: Bool = false, shuffleAnswers: Bool = false, hideResultsUntilClose: Bool = false, isCreator: Bool = false, attachedMedia: Media? = nil) {
self.pollId = pollId
self.publicity = publicity
self.kind = kind
@ -271,10 +273,12 @@ public final class TelegramMediaPoll: Media, Equatable {
self.isClosed = isClosed
self.deadlineTimeout = deadlineTimeout
self.deadlineDate = deadlineDate
self.pollHash = pollHash
self.openAnswers = openAnswers
self.revotingDisabled = revotingDisabled
self.shuffleAnswers = shuffleAnswers
self.hideResultsUntilClose = hideResultsUntilClose
self.isCreator = isCreator
self.attachedMedia = attachedMedia
}
@ -294,10 +298,12 @@ public final class TelegramMediaPoll: Media, Equatable {
self.isClosed = decoder.decodeInt32ForKey("ic", orElse: 0) != 0
self.deadlineTimeout = decoder.decodeOptionalInt32ForKey("dt")
self.deadlineDate = decoder.decodeOptionalInt32ForKey("dd")
self.pollHash = decoder.decodeInt64ForKey("ph", orElse: 0)
self.openAnswers = decoder.decodeInt32ForKey("oa", orElse: 0) != 0
self.revotingDisabled = decoder.decodeInt32ForKey("rd", orElse: 0) != 0
self.shuffleAnswers = decoder.decodeInt32ForKey("sa", orElse: 0) != 0
self.hideResultsUntilClose = decoder.decodeInt32ForKey("hr", orElse: 0) != 0
self.isCreator = decoder.decodeInt32ForKey("cr", orElse: 0) != 0
self.attachedMedia = decoder.decodeObjectForKey("am") as? Media
}
@ -315,7 +321,7 @@ public final class TelegramMediaPoll: Media, Equatable {
} else {
encoder.encodeNil(forKey: "ca")
}
encoder.encodeObject(results, forKey: "rs")
encoder.encodeObject(self.results, forKey: "rs")
encoder.encodeInt32(self.isClosed ? 1 : 0, forKey: "ic")
if let deadlineTimeout = self.deadlineTimeout {
encoder.encodeInt32(deadlineTimeout, forKey: "dt")
@ -327,10 +333,12 @@ public final class TelegramMediaPoll: Media, Equatable {
} else {
encoder.encodeNil(forKey: "dd")
}
encoder.encodeInt64(self.pollHash, forKey: "ph")
encoder.encodeInt32(self.openAnswers ? 1 : 0, forKey: "oa")
encoder.encodeInt32(self.revotingDisabled ? 1 : 0, forKey: "rd")
encoder.encodeInt32(self.shuffleAnswers ? 1 : 0, forKey: "sa")
encoder.encodeInt32(self.hideResultsUntilClose ? 1 : 0, forKey: "hr")
encoder.encodeInt32(self.isCreator ? 1 : 0, forKey: "cr")
if let attachedMedia = self.attachedMedia {
encoder.encodeObject(attachedMedia, forKey: "am")
} else {
@ -383,6 +391,9 @@ public final class TelegramMediaPoll: Media, Equatable {
if lhs.deadlineDate != rhs.deadlineDate {
return false
}
if lhs.pollHash != rhs.pollHash {
return false
}
if lhs.openAnswers != rhs.openAnswers {
return false
}
@ -395,6 +406,9 @@ public final class TelegramMediaPoll: Media, Equatable {
if lhs.hideResultsUntilClose != rhs.hideResultsUntilClose {
return false
}
if lhs.isCreator != rhs.isCreator {
return false
}
if let lhsMedia = lhs.attachedMedia, let rhsMedia = rhs.attachedMedia {
if !lhsMedia.isEqual(to: rhsMedia) { return false }
} else if (lhs.attachedMedia == nil) != (rhs.attachedMedia == nil) {
@ -428,6 +442,6 @@ public final class TelegramMediaPoll: Media, Equatable {
} else {
updatedResults = results
}
return TelegramMediaPoll(pollId: self.pollId, publicity: self.publicity, kind: self.kind, text: self.text, textEntities: self.textEntities, options: self.options, correctAnswers: self.correctAnswers, results: updatedResults, isClosed: self.isClosed, deadlineTimeout: self.deadlineTimeout, deadlineDate: self.deadlineDate, openAnswers: self.openAnswers, revotingDisabled: self.revotingDisabled, shuffleAnswers: self.shuffleAnswers, hideResultsUntilClose: self.hideResultsUntilClose, attachedMedia: self.attachedMedia)
return TelegramMediaPoll(pollId: self.pollId, publicity: self.publicity, kind: self.kind, text: self.text, textEntities: self.textEntities, options: self.options, correctAnswers: self.correctAnswers, results: updatedResults, isClosed: self.isClosed, deadlineTimeout: self.deadlineTimeout, deadlineDate: self.deadlineDate, pollHash: self.pollHash, openAnswers: self.openAnswers, revotingDisabled: self.revotingDisabled, shuffleAnswers: self.shuffleAnswers, hideResultsUntilClose: self.hideResultsUntilClose, isCreator: self.isCreator, attachedMedia: self.attachedMedia)
}
}

View file

@ -58,7 +58,7 @@ func _internal_requestMessageSelectPollOption(account: Account, messageId: Messa
if let poll = poll {
switch poll {
case let .poll(pollData):
let (flags, question, answers, closePeriod, closeDate) = (pollData.flags, pollData.question, pollData.answers, pollData.closePeriod, pollData.closeDate)
let (flags, question, answers, closePeriod, closeDate, pollHash) = (pollData.flags, pollData.question, pollData.answers, pollData.closePeriod, pollData.closeDate, pollData.hash)
let publicity: TelegramMediaPollPublicity
if (flags & (1 << 1)) != 0 {
publicity = .public
@ -83,7 +83,7 @@ func _internal_requestMessageSelectPollOption(account: Account, messageId: Messa
questionText = text
questionEntities = messageTextEntitiesFromApiEntities(entities)
}
resultPoll = TelegramMediaPoll(pollId: pollId, publicity: publicity, kind: kind, text: questionText, textEntities: questionEntities, options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: TelegramMediaPollResults(apiResults: results), isClosed: (flags & (1 << 0)) != 0, deadlineTimeout: closePeriod, deadlineDate: closeDate, openAnswers: openAnswers, revotingDisabled: revotingDisabled, shuffleAnswers: shuffleAnswers, hideResultsUntilClose: hideResultsUntilClose, attachedMedia: resultPoll?.attachedMedia)
resultPoll = TelegramMediaPoll(pollId: pollId, publicity: publicity, kind: kind, text: questionText, textEntities: questionEntities, options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: TelegramMediaPollResults(apiResults: results), isClosed: (flags & (1 << 0)) != 0, deadlineTimeout: closePeriod, deadlineDate: closeDate, pollHash: pollHash, openAnswers: openAnswers, revotingDisabled: revotingDisabled, shuffleAnswers: shuffleAnswers, hideResultsUntilClose: hideResultsUntilClose, attachedMedia: resultPoll?.attachedMedia)
}
}

View file

@ -1660,7 +1660,11 @@ public final class PresentationTheme: Equatable {
public func withModalBlocksBackground() -> PresentationTheme {
if self.list.blocksBackgroundColor.rgb == self.list.plainBackgroundColor.rgb {
let list = self.list.withUpdated(blocksBackgroundColor: self.list.modalBlocksBackgroundColor, itemBlocksBackgroundColor: self.list.itemModalBlocksBackgroundColor)
let list = self.list.withUpdated(
blocksBackgroundColor: self.list.modalBlocksBackgroundColor,
itemPlaceholderTextColor: self.overallDarkAppearance ? self.list.itemPlaceholderTextColor.withMultipliedBrightnessBy(1.25) : nil,
itemBlocksBackgroundColor: self.list.itemModalBlocksBackgroundColor
)
return PresentationTheme(name: self.name, index: self.index, referenceTheme: self.referenceTheme, overallDarkAppearance: self.overallDarkAppearance, intro: self.intro, passcode: self.passcode, rootController: self.rootController, list: list, chatList: self.chatList, chat: self.chat, actionSheet: self.actionSheet, contextMenu: self.contextMenu, inAppNotification: self.inAppNotification, chart: self.chart, preview: self.preview)
} else {
return self

View file

@ -130,6 +130,7 @@ public enum PresentationResourceKey: Int32 {
case chatListStoryReplyIcon
case chatListGiftIcon
case chatListLocationIcon
case chatListPollIcon
case chatListGeneralTopicIcon
case chatListGeneralTopicTemplateIcon
@ -420,4 +421,5 @@ public enum PresentationResourceParameterKey: Hashable {
case chatExpiredStoryIndicatorIcon(type: ChatExpiredStoryIndicatorType)
case chatReplyStoryIndicatorIcon(type: ChatExpiredStoryIndicatorType)
case chatReplyPollIndicatorIcon(type: ChatExpiredStoryIndicatorType)
}

View file

@ -1277,9 +1277,9 @@ public struct PresentationResourcesChat {
let foregroundColor: UIColor
switch type {
case .incoming:
foregroundColor = theme.chat.message.incoming.mediaActiveControlColor
foregroundColor = theme.chat.message.incoming.accentTextColor
case .outgoing:
foregroundColor = theme.chat.message.outgoing.mediaActiveControlColor
foregroundColor = theme.chat.message.outgoing.accentTextColor
case .free:
foregroundColor = theme.chat.serviceMessage.components.withDefaultWallpaper.primaryText
}
@ -1303,9 +1303,9 @@ public struct PresentationResourcesChat {
let foregroundColor: UIColor
switch type {
case .incoming:
foregroundColor = theme.chat.message.incoming.mediaActiveControlColor
foregroundColor = theme.chat.message.incoming.accentTextColor
case .outgoing:
foregroundColor = theme.chat.message.outgoing.mediaActiveControlColor
foregroundColor = theme.chat.message.outgoing.accentTextColor
case .free:
foregroundColor = theme.chat.serviceMessage.components.withDefaultWallpaper.primaryText
}
@ -1322,6 +1322,32 @@ public struct PresentationResourcesChat {
})
}
public static func chatReplyPollIndicatorIcon(_ theme: PresentationTheme, type: ChatExpiredStoryIndicatorType) -> UIImage? {
return theme.image(PresentationResourceParameterKey.chatReplyStoryIndicatorIcon(type: type), { theme in
return generateImage(CGSize(width: 16.0, height: 16.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
let foregroundColor: UIColor
switch type {
case .incoming:
foregroundColor = theme.chat.message.incoming.accentTextColor
case .outgoing:
foregroundColor = theme.chat.message.outgoing.accentTextColor
case .free:
foregroundColor = theme.chat.serviceMessage.components.withDefaultWallpaper.primaryText
}
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/PollReplyIcon"), color: foregroundColor) {
UIGraphicsPushContext(context)
let fittedSize = image.size
image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - fittedSize.width) * 0.5), y: floor((size.height - fittedSize.height) * 0.5)), size: fittedSize), blendMode: .normal, alpha: 1.0)
UIGraphicsPopContext()
}
})
})
}
public static func storyViewListLikeIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.storyViewListLikeIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Stories/InputLikeOn"), color: UIColor(rgb: 0xFF3B30))
@ -1417,8 +1443,8 @@ public struct PresentationResourcesChat {
context.setFillColor(UIColor.white.cgColor)
let lineHeight = 2.0 - UIScreenPixel
context.addPath(CGPath(roundedRect: CGRect(x: 1.0, y: 7.0, width: 15.0, height: lineHeight), cornerWidth: lineHeight / 2.0, cornerHeight: lineHeight / 2.0, transform: nil))
context.addPath(CGPath(roundedRect: CGRect(x: 8.0, y: 0.0, width: lineHeight, height: 15.0), cornerWidth: lineHeight / 2.0, cornerHeight: lineHeight / 2.0, transform: nil))
context.addPath(CGPath(roundedRect: CGRect(x: 1.0, y: 7.0 - UIScreenPixel, width: 15.0, height: lineHeight), cornerWidth: lineHeight / 2.0, cornerHeight: lineHeight / 2.0, transform: nil))
context.addPath(CGPath(roundedRect: CGRect(x: 8.0 - UIScreenPixel, y: 0.0, width: lineHeight, height: 15.0), cornerWidth: lineHeight / 2.0, cornerHeight: lineHeight / 2.0, transform: nil))
context.fillPath()
})
})

View file

@ -284,6 +284,24 @@ public struct PresentationResourcesChatList {
})
}
public static func pollIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatListPollIcon.rawValue, { theme in
if let image = UIImage(bundleImageName: "Chat List/PollBadgeIcon") {
return generateImage(CGSize(width: 20.0, height: 20.0), contextGenerator: { size, context in
if let cgImage = image.cgImage {
context.clear(CGRect(origin: CGPoint(), size: size))
context.clip(to: CGRect(origin: .zero, size: size).insetBy(dx: 2.0, dy: 2.0), mask: cgImage)
context.setFillColor(theme.chatList.muteIconColor.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
}
})
} else {
return nil
}
})
}
public static func verifiedIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatListVerifiedIcon.rawValue, { theme in
if let backgroundImage = UIImage(bundleImageName: "Chat List/PeerVerifiedIconBackground"), let foregroundImage = UIImage(bundleImageName: "Chat List/PeerVerifiedIconForeground") {

View file

@ -466,7 +466,7 @@ public func stringForMediaKind(_ kind: MessageContentKind, strings: Presentation
case .expiredVideoMessage:
return (NSAttributedString(string: strings.Message_VideoMessageExpired), true)
case let .poll(text):
return (NSAttributedString(string: "📊 \(text)"), false)
return (NSAttributedString(string: text), false)
case let .todo(text):
return (NSAttributedString(string: "☑️ \(text)"), false)
case let .restricted(text):

View file

@ -26,6 +26,7 @@ import ContextUI
private final class AttachmentFileControllerArguments {
let context: AccountContext
let isAudio: Bool
let isAttach: Bool
let openGallery: () -> Void
let openFiles: () -> Void
let scanDocument: () -> Void
@ -38,9 +39,10 @@ private final class AttachmentFileControllerArguments {
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)) {
init(context: AccountContext, isAudio: Bool, isAttach: 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.isAttach = isAttach
self.openGallery = openGallery
self.openFiles = openFiles
self.scanDocument = scanDocument
@ -250,7 +252,7 @@ private enum AttachmentFileEntry: ItemListNodeEntry {
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: selection, 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: arguments.isAttach, 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()
@ -275,8 +277,7 @@ private enum AttachmentFileEntry: ItemListNodeEntry {
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: selection, 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: arguments.isAttach, 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()
@ -301,8 +302,7 @@ private enum AttachmentFileEntry: ItemListNodeEntry {
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: selection, 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: arguments.isAttach, 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: {
@ -404,7 +404,7 @@ final class AttachmentFileContext: AttachmentMediaPickerContext {
}
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) {
self.controller?.mulitpleCompletion?(mode, .files, parameters)
self.controller?.mulitpleCompletion?(mode, .files, parameters, self.controller?.caption)
}
func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) {
@ -434,13 +434,16 @@ public class AttachmentFileControllerImpl: ItemListController, AttachmentFileCon
fileprivate var caption: NSAttributedString?
fileprivate var selectionCount = ValuePromise<Int>(0)
fileprivate var mulitpleCompletion: ((AttachmentMediaPickerSendMode, AttachmentMediaPickerAttachmentMode, ChatSendMessageActionSheetController.SendParameters?) -> Void)?
fileprivate var bottomEdgeColor: UIColor = .clear
fileprivate var mulitpleCompletion: ((AttachmentMediaPickerSendMode, AttachmentMediaPickerAttachmentMode, ChatSendMessageActionSheetController.SendParameters?, NSAttributedString?) -> Void)?
var delayDisappear = false
var hasBottomEdgeEffect = true
var resetForReuseImpl: () -> Void = {}
var onDismissImpl: () -> Void = {}
public func resetForReuse() {
self.resetForReuseImpl()
self.scrollToTop?()
@ -451,12 +454,20 @@ public class AttachmentFileControllerImpl: ItemListController, AttachmentFileCon
self.visibleBottomContentOffsetChanged?(self.visibleBottomContentOffset)
self.delayDisappear = false
}
public func requestDismiss(completion: @escaping () -> Void) {
self.onDismissImpl()
completion()
}
public func shouldDismissImmediately() -> Bool {
return true
}
public var mediaPickerContext: AttachmentMediaPickerContext? {
return AttachmentFileContext(controller: self)
}
private var topEdgeEffectView: EdgeEffectView?
private var bottomEdgeEffectView: EdgeEffectView?
var isSearching: Bool = false {
@ -468,21 +479,7 @@ public class AttachmentFileControllerImpl: ItemListController, AttachmentFileCon
public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
let topEdgeEffectView: EdgeEffectView
if let current = self.topEdgeEffectView {
topEdgeEffectView = current
} else {
topEdgeEffectView = EdgeEffectView()
if let navigationBar = self.navigationBar {
self.view.insertSubview(topEdgeEffectView, belowSubview: navigationBar.view)
}
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
@ -497,7 +494,7 @@ public class AttachmentFileControllerImpl: ItemListController, AttachmentFileCon
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)
bottomEdgeEffectView.update(content: .clear, blur: true, alpha: 1.0, rect: bottomEdgeEffectFrame, edge: .bottom, edgeSize: bottomEdgeEffectFrame.height, transition: ComponentTransition(transition))
bottomEdgeEffectView.update(content: self.bottomEdgeColor, blur: true, alpha: 1.0, rect: bottomEdgeEffectFrame, edge: .bottom, edgeSize: bottomEdgeEffectFrame.height, transition: ComponentTransition(transition))
} else if let bottomEdgeEffectView = self.bottomEdgeEffectView {
bottomEdgeEffectView.removeFromSuperview()
}
@ -508,31 +505,54 @@ private struct AttachmentFileControllerState: Equatable {
var searching: Bool
var savedMusicExpanded: Bool
var recentMusicExpanded: Bool
var selectedMessageIds: Set<MessageId>?
var selectedMessageIds: [MessageId]?
var messageMap: [MessageId: EngineMessage]
}
private func messageSelectionState(state: AttachmentFileControllerState, message: Message?) -> ChatHistoryMessageSelection {
guard let message = message, let selectedMessageIds = state.selectedMessageIds else {
guard let message, let selectedMessageIds = state.selectedMessageIds else {
return .none
}
return .selectable(selected: selectedMessageIds.contains(message.id))
if let index = selectedMessageIds.firstIndex(where: { $0 == message.id }) {
return .selectable(selected: true, num: index)
} else {
return .selectable(selected: false, num: nil)
}
}
public enum AttachmentFileControllerMode {
case recent
case audio
case audio(story: Bool)
var isAudio: Bool {
if case .audio = self {
return true
} else {
return false
}
}
}
public enum AttachmentFileControllerSource: Equatable {
public enum PollMode: Equatable {
case description
case quizAnswer
}
case generic
case poll(PollMode)
}
public func makeAttachmentFileControllerImpl(
context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
mode: AttachmentFileControllerMode = .recent,
source: AttachmentFileControllerSource = .generic,
bannedSendMedia: (Int32, Bool)?,
presentGallery: @escaping () -> Void,
presentFiles: @escaping () -> Void,
presentDocumentScanner: (() -> Void)?,
send: @escaping ([AnyMediaReference]) -> Void
send: @escaping ([AnyMediaReference], Bool, Int32?, NSAttributedString?) -> Void
) -> AttachmentFileController {
let actionsDisposable = DisposableSet()
@ -549,10 +569,20 @@ public func makeAttachmentFileControllerImpl(
var updateIsSearchingImpl: ((Bool) -> Void)?
var updateSelectionCountImpl: ((Int) -> Void)?
var presentInGlobalOverlayImpl: ((ViewController) -> Void)?
var updateBottomColorImpl: ((UIColor) -> Void)?
var isAudio = false
var isAttach = true
if case let .audio(isStory) = mode {
isAudio = true
isAttach = !isStory
}
var didPreviewAudio = false
let arguments = AttachmentFileControllerArguments(
context: context,
isAudio: mode == .audio,
isAudio: isAudio,
isAttach: isAttach,
openGallery: {
presentGallery()
},
@ -580,7 +610,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)], false, nil, nil)
dismissImpl?()
}
} else {
@ -596,14 +626,16 @@ 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)], false, nil, nil)
}
dismissImpl?()
})
}
},
toggleMediaPlayback: { message in
let playlistLocation: PeerMessagesPlaylistLocation = .custom(messages: .single(([message], 0, false)), canReorder: false, at: message.id, loadMore: nil)
didPreviewAudio = true
let playlistLocation: PeerMessagesPlaylistLocation = .custom(messages: .single(([message], 0, false)), canReorder: false, at: message.id, loadMore: nil, hidePanel: true)
context.sharedContext.mediaManager.setPlaylist((context, PeerMessagesMediaPlaylist(context: context, location: playlistLocation, chatLocationContextHolder: nil)), type: .music, control: .playback(.togglePlayPause))
},
isSelectionActive: {
@ -616,9 +648,9 @@ public func makeAttachmentFileControllerImpl(
}
let messageId = message.id
if selectedMessageIds.contains(messageId) {
selectedMessageIds.remove(messageId)
selectedMessageIds.removeAll(where: { $0 == messageId })
} else {
selectedMessageIds.insert(messageId)
selectedMessageIds.append(messageId)
}
var updatedState = state
updatedState.selectedMessageIds = selectedMessageIds
@ -634,9 +666,9 @@ public func makeAttachmentFileControllerImpl(
}
for messageId in messageIds {
if value {
selectedMessageIds.insert(messageId)
selectedMessageIds.append(messageId)
} else {
selectedMessageIds.remove(messageId)
selectedMessageIds.removeAll(where: { $0 == messageId })
}
}
var updatedState = state
@ -661,7 +693,7 @@ public func makeAttachmentFileControllerImpl(
items.append(.action(ContextMenuActionItem(text: "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)
let playlistLocation: PeerMessagesPlaylistLocation = .custom(messages: .single(([message._asMessage()], 0, false)), canReorder: false, at: message.id, loadMore: nil, hidePanel: true)
context.sharedContext.mediaManager.setPlaylist((context, PeerMessagesMediaPlaylist(context: context, location: playlistLocation, chatLocationContextHolder: nil)), type: .music, control: .playback(.togglePlayPause))
})))
}
@ -670,8 +702,8 @@ public func makeAttachmentFileControllerImpl(
c?.dismiss(completion: {})
updateState { state in
var updatedState = state
var selectedMessageIds = updatedState.selectedMessageIds ?? Set()
selectedMessageIds.insert(message.id)
var selectedMessageIds = updatedState.selectedMessageIds ?? []
selectedMessageIds.append(message.id)
updatedState.selectedMessageIds = selectedMessageIds
updatedState.messageMap[message.id] = message
updateSelectionCountImpl?(selectedMessageIds.count)
@ -727,7 +759,7 @@ public func makeAttachmentFileControllerImpl(
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,
@ -823,21 +855,32 @@ public func makeAttachmentFileControllerImpl(
}
var rightNavigationButton: ItemListNavigationButton?
if bannedSendMedia == nil && (recentDocuments == nil || (recentDocuments?.count ?? 0) > 10 || (mode == .audio && hasAudioSearch)) {
if bannedSendMedia == nil && (recentDocuments == nil || (recentDocuments?.count ?? 0) > 10 || (mode.isAudio && hasAudioSearch)) {
rightNavigationButton = searchButtonNode.flatMap { ItemListNavigationButton(content: .node($0), style: .regular, enabled: true, action: {}) }
}
let title: String
var subtitle: String?
switch mode {
case .recent:
title = presentationData.strings.Attachment_File
case .audio:
title = presentationData.strings.MediaEditor_Audio_Title
}
if case let .poll(pollMode) = source {
//TODO:localize
switch pollMode {
case .description:
subtitle = "Add file to the poll description"
case .quizAnswer:
subtitle = "Add file to the quiz explanation"
}
}
let controllerState = ItemListControllerState(
presentationData: ItemListPresentationData(presentationData),
title: .text(title),
title: subtitle.flatMap { .textWithSubtitle(title, $0) } ?? .text(title),
leftNavigationButton: leftNavigationButton,
rightNavigationButton: rightNavigationButton,
backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back),
@ -881,6 +924,8 @@ public func makeAttachmentFileControllerImpl(
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)
updateBottomColorImpl?(presentationData.theme.list.blocksBackgroundColor)
return (controllerState, (listState, arguments))
} |> afterDisposed {
actionsDisposable.dispose()
@ -888,8 +933,7 @@ public func makeAttachmentFileControllerImpl(
}
let controller = AttachmentFileControllerImpl(context: context, state: signal, hideNavigationBarBackground: true)
controller.mulitpleCompletion = { _, _, _ in
controller.mulitpleCompletion = { sendMode, _, _, caption in
let _ = stateValue.with({ state in
if let selectedMessageIds = state.selectedMessageIds {
var mediaReferences: [AnyMediaReference] = []
@ -898,7 +942,7 @@ public func makeAttachmentFileControllerImpl(
mediaReferences.append(.standalone(media: file))
}
}
send(mediaReferences)
send(mediaReferences, sendMode == .silently, nil, caption)
dismissImpl?()
}
})
@ -928,12 +972,25 @@ public func makeAttachmentFileControllerImpl(
updatedState.selectedMessageIds = nil
return updatedState
}
if didPreviewAudio {
context.sharedContext.mediaManager.setPlaylist(nil, type: .music, control: .playback(.pause))
}
}
controller.onDismissImpl = {
if didPreviewAudio {
context.sharedContext.mediaManager.setPlaylist(nil, type: .music, control: .playback(.pause))
}
}
updateIsSearchingImpl = { [weak controller] isSearching in
controller?.isSearching = isSearching
}
dismissImpl = { [weak controller] in
controller?.dismiss(animated: true)
if didPreviewAudio {
context.sharedContext.mediaManager.setPlaylist(nil, type: .music, control: .playback(.pause))
}
}
dismissInputImpl = { [weak controller] in
controller?.view.endEditing(true)
@ -950,6 +1007,9 @@ public func makeAttachmentFileControllerImpl(
presentInGlobalOverlayImpl = { [weak controller] c in
controller?.presentInGlobalOverlay(c)
}
updateBottomColorImpl = { [weak controller] color in
controller?.bottomEdgeColor = color
}
return controller
}
@ -962,15 +1022,16 @@ public func storyAudioPickerController(
var dismissImpl: (() -> Void)?
let presentationData = context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkColorPresentationTheme)
let updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>) = (presentationData, .single(presentationData))
let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, style: .glass, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false)
let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, style: .glass, chatLocation: nil, buttons: [.audio], initialButton: .audio, fromMenu: false, hasTextInput: false)
controller.requestController = { _, present in
let filePickerController = makeAttachmentFileControllerImpl(context: context, updatedPresentationData: updatedPresentationData, mode: .audio, bannedSendMedia: nil, presentGallery: {}, presentFiles: {
let filePickerController = makeAttachmentFileControllerImpl(context: context, updatedPresentationData: updatedPresentationData, mode: .audio(story: true), bannedSendMedia: nil, presentGallery: {}, presentFiles: {
selectFromFiles()
dismissImpl?()
}, presentDocumentScanner: nil, send: { files in
}, presentDocumentScanner: nil, send: { files, _, _, _ in
completion(files.first!)
dismissImpl?()
}) as! AttachmentFileControllerImpl
filePickerController.hasBottomEdgeEffect = false
present(filePickerController, filePickerController.mediaPickerContext)
return true
}

View file

@ -74,12 +74,11 @@ final class AttachmentFileSearchItem: ItemListControllerSearch {
func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)? {
switch self.mode {
case .audio:
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
if let current = current as? AttachmentFileSearchNavigationContentNode {
current.updateTheme(presentationData.theme)
current.updateTheme(self.presentationData.theme)
return current
} else {
return AttachmentFileSearchNavigationContentNode(theme: presentationData.theme, strings: presentationData.strings, cancel: self.cancel, focus: self.focus, updateActivity: { _ in
return AttachmentFileSearchNavigationContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, cancel: self.cancel, focus: self.focus, updateActivity: { _ in
})
}
default:
@ -164,7 +163,7 @@ private final class AttachmentFileSearchItemNode: ItemListControllerSearchNode {
strings: self.presentationData.strings,
metrics: layout.metrics,
safeInsets: layout.safeInsets,
placeholder: self.mode == .audio ? self.presentationData.strings.Attachment_FilesSearchPlaceholder : self.presentationData.strings.Attachment_FilesSearchPlaceholder,
placeholder: self.presentationData.strings.Attachment_FilesSearchPlaceholder,
updated: { [weak self] query in
guard let self else {
return
@ -329,9 +328,9 @@ private enum AttachmentFileSearchEntry: Comparable, Identifiable {
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
let displayFileInfo = mode.isAudio
let isStoryMusic = mode.isAudio
let isDownloadList = mode.isAudio
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):
@ -349,16 +348,30 @@ struct AttachmentFileSearchContainerTransition {
let isSearching: Bool
let isEmpty: Bool
let query: String
let crossfade: Bool
}
private func attachmentFileSearchContainerPreparedRecentTransition(from fromEntries: [AttachmentFileSearchEntry], to toEntries: [AttachmentFileSearchEntry], isSearching: Bool, isEmpty: Bool, query: String, context: AccountContext, presentationData: PresentationData, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, interaction: AttachmentFileSearchContainerInteraction, mode: AttachmentFileControllerMode) -> AttachmentFileSearchContainerTransition {
private func attachmentFileSearchContainerPreparedRecentTransition(
from fromEntries: [AttachmentFileSearchEntry],
to toEntries: [AttachmentFileSearchEntry],
isSearching: Bool,
isEmpty: Bool,
query: String,
context: AccountContext,
presentationData: PresentationData,
nameSortOrder: PresentationPersonNameOrder,
nameDisplayOrder: PresentationPersonNameOrder,
interaction: AttachmentFileSearchContainerInteraction,
mode: AttachmentFileControllerMode,
crossfade: Bool
) -> AttachmentFileSearchContainerTransition {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, interaction: interaction, mode: mode), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, interaction: interaction, mode: mode), directionHint: nil) }
return AttachmentFileSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, isSearching: isSearching, isEmpty: isEmpty, query: query)
return AttachmentFileSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, isSearching: isSearching, isEmpty: isEmpty, query: query, crossfade: crossfade)
}
@ -471,7 +484,7 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon
self?.listNode.clearHighlightAnimated(true)
},
toggleMediaPlayback: { message in
let playlistLocation: PeerMessagesPlaylistLocation = .custom(messages: .single(([message], 0, false)), canReorder: false, at: message.id, loadMore: nil)
let playlistLocation: PeerMessagesPlaylistLocation = .custom(messages: .single(([message], 0, false)), canReorder: false, at: message.id, loadMore: nil, hidePanel: true)
context.sharedContext.mediaManager.setPlaylist((context, PeerMessagesMediaPlaylist(context: context, location: playlistLocation, chatLocationContextHolder: nil)), type: .music, control: .playback(.togglePlayPause))
},
expandSection: { [weak self] section in
@ -554,7 +567,9 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon
}
)
if let data = context.currentAppConfiguration.with({ $0 }).data, let searchBot = data["music_search_username"] as? String, !searchBot.isEmpty {
let trimmedQuery = query.trimmingCharacters(in: .whitespacesAndNewlines)
if let data = context.currentAppConfiguration.with({ $0 }).data, let searchBot = data["music_search_username"] as? String, !searchBot.isEmpty, trimmedQuery.count >= 3 {
globalMusic = .single(nil)
|> then(
context.engine.peers.resolvePeerByName(name: searchBot, referrer: nil)
@ -568,7 +583,7 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon
guard let peer = peer else {
return .single(nil)
}
return context.engine.messages.requestChatContextResults(botId: peer.id, peerId: context.account.peerId, query: query, offset: "")
return context.engine.messages.requestChatContextResults(botId: peer.id, peerId: context.account.peerId, query: trimmedQuery, offset: "")
|> map { results -> ChatContextResultCollection? in
return results?.results
}
@ -661,6 +676,7 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon
}
}
} else {
entries.append(.header(title: " ", section: 0))
var index: Int32 = 0
for _ in 0 ..< 16 {
entries.append(.file(index: index, message: nil, section: 0))
@ -673,13 +689,27 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon
}
let previousSearchItems = Atomic<[AttachmentFileSearchEntry]?>(value: nil)
let previousHadGlobalItems = Atomic<Bool>(value: false)
self.searchDisposable.set((combineLatest(searchQuery, foundItems, self.presentationDataPromise.get())
|> deliverOnMainQueue).startStrict(next: { [weak self] query, entries, presentationData in
if let strongSelf = self {
let previousEntries = previousSearchItems.swap(entries)
updateActivity(false)
let firstTime = previousEntries == nil
let transition = attachmentFileSearchContainerPreparedRecentTransition(from: previousEntries ?? [], to: entries ?? [], isSearching: entries != nil, isEmpty: entries?.isEmpty ?? false, query: query ?? "", context: context, presentationData: presentationData, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, interaction: interaction, mode: mode)
var hasGlobalItems = false
if let entries {
for entry in entries {
if case let .header(_, section) = entry, section == 2 {
hasGlobalItems = true
}
}
}
let hadGlobalItems = previousHadGlobalItems.swap(hasGlobalItems)
let crossfade = hadGlobalItems != hasGlobalItems
let transition = attachmentFileSearchContainerPreparedRecentTransition(from: previousEntries ?? [], to: entries ?? [], isSearching: entries != nil, isEmpty: entries?.isEmpty ?? false, query: query ?? "", context: context, presentationData: presentationData, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, interaction: interaction, mode: mode, crossfade: crossfade)
strongSelf.enqueueTransition(transition, firstTime: firstTime)
}
}))
@ -739,6 +769,10 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon
//options.insert(.AnimateInsertion)
if transition.crossfade {
options.insert(.AnimateCrossfade)
}
let isSearching = transition.isSearching
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in
guard let strongSelf = self else {
@ -1009,6 +1043,20 @@ private final class AttachmentFileSearchNavigationContentNode: NavigationBarCont
func updateTheme(_ theme: PresentationTheme) {
self.theme = theme
let searchBarTheme = SearchBarNodeTheme(
background: .clear,
separator: .clear,
inputFill: .clear,
primaryText: theme.chat.inputPanel.panelControlColor,
placeholder: theme.chat.inputPanel.inputPlaceholderColor,
inputIcon: theme.chat.inputPanel.inputControlColor,
inputClear: theme.chat.inputPanel.panelControlColor,
accent: theme.chat.inputPanel.panelControlAccentColor,
keyboard: theme.rootController.keyboardColor
)
self.searchBar.updateThemeAndStrings(theme: searchBarTheme, presentationTheme: theme, strings: self.strings)
if let params = self.params {
let _ = self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate)
}
@ -1022,7 +1070,7 @@ private final class AttachmentFileSearchNavigationContentNode: NavigationBarCont
}
override var nominalHeight: CGFloat {
return 60.0
return 68.0
}
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize {

View file

@ -815,6 +815,9 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
if strongSelf.selectionNode != nil {
return .none
}
if let item = strongSelf.item, item.controllerInteraction.focusedPollAddOptionMessageId != nil {
return .none
}
if let action = strongSelf.gestureRecognized(gesture: .tap, location: location, recognizer: nil) {
if case let .action(action) = action, !action.contextMenuOnLongPress {
return .none
@ -2180,7 +2183,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
switch selection {
case .none:
break
case let .selectable(selected):
case let .selectable(selected, _):
itemSelection = selected
}
break

View file

@ -1686,13 +1686,18 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
updatedFetchControls = FetchControls(fetch: { manual in
if let strongSelf = self {
let disposableSet = DisposableSet()
if file.isAnimated {
strongSelf.fetchDisposable.set(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .peer(message.id.peerId), userContentType: MediaResourceUserContentType(file: file), reference: AnyMediaReference.message(message: MessageReference(message), media: file).resourceReference(file.resource), statsCategory: statsCategoryForFileWithAttributes(file.attributes)).startStrict())
disposableSet.add(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .peer(message.id.peerId), userContentType: MediaResourceUserContentType(file: file), reference: AnyMediaReference.message(message: MessageReference(message), media: file).resourceReference(file.resource), statsCategory: statsCategoryForFileWithAttributes(file.attributes)).startStrict())
} else if NativeVideoContent.isHLSVideo(file: file) {
strongSelf.fetchDisposable.set(nil)
} else {
strongSelf.fetchDisposable.set(messageMediaFileInteractiveFetched(context: context, message: message, file: file, userInitiated: manual, storeToDownloadsPeerId: storeToDownloadsPeerId).startStrict())
disposableSet.add(messageMediaFileInteractiveFetched(context: context, message: message, file: file, userInitiated: manual, storeToDownloadsPeerId: storeToDownloadsPeerId).startStrict())
}
if let image = media as? TelegramMediaImage, let representation = largestRepresentationForPhoto(image) {
disposableSet.add(messageMediaImageInteractiveFetched(context: context, message: message, image: image, resource: representation.resource, range: representationFetchRangeForDisplayAtSize(representation: representation, dimension: nil/*isSecretMedia ? nil : 600*/), userInitiated: manual, storeToDownloadsPeerId: storeToDownloadsPeerId).startStrict())
}
strongSelf.fetchDisposable.set(disposableSet)
}
}, cancel: {
if file.isAnimated {

View file

@ -1061,7 +1061,10 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
node.mediaFrame = mediaFrame
if !recentVoterPeers.isEmpty {
let avatarsFrame = CGRect(origin: CGPoint(x: trailingOriginX + 15.0 - ChatMessagePollOptionNode.avatarsSize.width, y: floor((contentLayoutHeight - ChatMessagePollOptionNode.avatarsSize.height) * 0.5)), size: ChatMessagePollOptionNode.avatarsSize)
var avatarsFrame = CGRect(origin: CGPoint(x: trailingOriginX + 15.0 - ChatMessagePollOptionNode.avatarsSize.width, y: floor((contentLayoutHeight - ChatMessagePollOptionNode.avatarsSize.height) * 0.5)), size: ChatMessagePollOptionNode.avatarsSize)
if recentVoterPeers.count > 1 {
avatarsFrame.origin.x -= 15.0
}
node.avatarsNode.frame = avatarsFrame
node.avatarsNode.updateLayout(size: avatarsFrame.size)
node.avatarsNode.update(context: context, peers: recentVoterPeers, synchronousLoad: attemptSynchronous, imageSize: MergedAvatarsNode.defaultMergedImageSize, imageSpacing: MergedAvatarsNode.defaultMergedImageSpacing, borderWidth: MergedAvatarsNode.defaultBorderWidth)
@ -1368,6 +1371,7 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode {
var attachPressed: (() -> Void)?
var mediaPressed: (() -> Void)?
var modeSelectorPressed: (() -> Void)?
var requestSave: (() -> Void)?
static let characterLimit = 100
private static let leftInset: CGFloat = 50.0
@ -1449,17 +1453,25 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode {
hideKeyboard: self.currentFocusedTextInputIsMedia,
customInputView: nil,
placeholder: NSAttributedString(string: strings.CreatePoll_AddOption, font: font, textColor: currentPlaceholderColor),
placeholderVerticalOffset: 1.0 + UIScreenPixel,
resetText: nil,
isOneLineWhenUnfocused: false,
characterLimit: ChatMessagePollAddOptionNode.characterLimit,
enableInlineAnimations: true,
emptyLineHandling: .allowed,
emptyLineHandling: .notAllowed,
formatMenuAvailability: .none,
returnKeyType: .done,
lockedFormatAction: {
},
present: { _ in
},
paste: { _ in
},
returnKeyAction: { [weak self] in
self?.requestSave?()
},
backspaceKeyAction: {
}
)
}
@ -2150,17 +2162,14 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
guard let item = self.item else {
return
}
item.controllerInteraction.updatePresentationState { [weak item] state in
var focusedTextInputIsMedia = false
item.controllerInteraction.updatePresentationState { state in
let updatedState = state.updatedInputMode({ inputMode in
if case .media = inputMode {
return .text
} else {
focusedTextInputIsMedia = true
return .media(mode: .other, expanded: .none, focused: true)
}
})
item?.controllerInteraction.focusedTextInputIsMedia = focusedTextInputIsMedia
return updatedState
}
@ -2185,7 +2194,8 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
presentPollAttachmentScreen(
context: item.context,
updatedPresentationData: item.controllerInteraction.updatedPresentationData,
availableButtons: [.gallery, .sticker, .emoji, .location],
subject: .option,
availableButtons: [.gallery, .sticker, .location],
present: { [weak item] controller in
item?.controllerInteraction.navigationController()?.pushViewController(controller)
},
@ -2291,6 +2301,8 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
let optionData = "\(poll.options.count)".data(using: .utf8)!
item.controllerInteraction.requestAddMessagePollOption(item.message.id, trimmedNewOptionText, entities, optionData, self.currentNewOptionMedia?.media)
return
} else if self.newOptionIsFocused {
return
}
var hasSelection = false
@ -2306,20 +2318,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
}
}
var canAlwaysViewResults = false
if let peer = item.message.peers[item.message.id.peerId] {
if let group = peer as? TelegramGroup {
switch group.role {
case .creator, .admin:
canAlwaysViewResults = true
default:
break
}
} else if let channel = peer as? TelegramChannel, let _ = channel.adminRights {
canAlwaysViewResults = true
}
}
let canAlwaysViewResults = poll.isCreator
if !hasSelection || (canAlwaysViewResults && selectedOpaqueIdentifiers.isEmpty) {
if !Namespaces.Message.allNonRegular.contains(item.message.id.namespace) {
switch poll.publicity {
@ -2739,7 +2738,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
pollOptionsFinalizeLayouts.append((optionResult != nil, result.1))
}
let displayAddOption = poll.openAnswers && !isClosed && !hasVoted && poll.pollId.namespace == Namespaces.Media.CloudPoll
let displayAddOption = poll.openAnswers && !isClosed && poll.pollId.namespace == Namespaces.Media.CloudPoll
if displayAddOption {
let addOptionResult = makeAddOptionLayout(item.context, item.presentationData, item.presentationData.strings, incoming, item.controllerInteraction.focusedTextInputIsMedia, currentNewOptionText, currentNewOptionAttachment, constrainedSize.width - layoutConstants.bubble.borderInset * 2.0)
boundingSize.width = max(boundingSize.width, addOptionResult.minimumWidth + layoutConstants.bubble.borderInset * 2.0)
@ -2932,6 +2931,9 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
addOptionNode.modeSelectorPressed = { [weak self] in
self?.toggleNewOptionInputMode()
}
addOptionNode.requestSave = { [weak self] in
self?.buttonPressed()
}
addOptionNode.focusUpdated = { [weak self] focused in
guard let self else {
return
@ -3172,11 +3174,20 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
let _ = buttonViewResultsTextApply()
strongSelf.buttonViewResultsTextNode.frame = buttonViewResultsTextFrame.offsetBy(dx: 0.0, dy: verticalOffset)
strongSelf.buttonNode.frame = CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: CGSize(width: resultSize.width, height: 44.0))
strongSelf.updateSelection()
strongSelf.updatePollTooltipMessageState(animated: false)
var buttonWidth: CGFloat = 0.0
if !strongSelf.buttonSaveTextNode.isHidden {
buttonWidth = strongSelf.buttonSaveTextNode.frame.width
} else if !strongSelf.buttonViewResultsTextNode.isHidden {
buttonWidth = strongSelf.buttonViewResultsTextNode.frame.width
} else if !strongSelf.buttonSubmitActiveTextNode.isHidden {
buttonWidth = strongSelf.buttonSubmitActiveTextNode.frame.width
}
buttonWidth = floor(buttonWidth * 1.1)
strongSelf.buttonNode.frame = CGRect(origin: CGPoint(x: floor((resultSize.width - buttonWidth) / 2.0), y: verticalOffset), size: CGSize(width: buttonWidth, height: 44.0))
strongSelf.updateIsTranslating(isTranslating)
}
})
@ -3268,20 +3279,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
let isClosed = isPollEffectivelyClosed(message: item.message, poll: poll)
var canAlwaysViewResults = false
if !poll.hideResultsUntilClose, let peer = item.message.peers[item.message.id.peerId] {
if let group = peer as? TelegramGroup {
switch group.role {
case .creator, .admin:
canAlwaysViewResults = true
default:
break
}
} else if let channel = peer as? TelegramChannel, let _ = channel.adminRights {
canAlwaysViewResults = true
}
}
let canAlwaysViewResults = !poll.hideResultsUntilClose && poll.isCreator
var hasAnyVotes = false
var hasResults = false
if isClosed {

View file

@ -203,6 +203,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
let isText: Bool
var isExpiredStory: Bool = false
var isStory: Bool = false
var isPoll: Bool = false
let titleColor: UIColor
let mainColor: UIColor
@ -429,6 +430,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
}
let textColor: UIColor
let iconColor: UIColor
switch arguments.type {
case let .bubble(incoming):
@ -439,8 +441,10 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
} else {
textColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.primaryTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.primaryTextColor
}
iconColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor
case .standalone:
textColor = titleColor
iconColor = titleColor
}
var textLeftInset: CGFloat = 0.0
@ -516,6 +520,10 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
}
} else {
messageText = NSAttributedString(string: textString.string, font: textFont, textColor: textColor)
if let _ = arguments.message?.media.first(where: { $0 is TelegramMediaPoll }) as? TelegramMediaPoll {
isPoll = true
}
}
var leftInset: CGFloat = 11.0
@ -562,6 +570,26 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
hasRoundImage = true
}
break
} else if let poll = media as? TelegramMediaPoll, let media = poll.attachedMedia {
if let image = media as? TelegramMediaImage {
updatedMediaReference = .message(message: MessageReference(message), media: image)
if let representation = largestRepresentationForPhoto(image) {
imageDimensions = representation.dimensions.cgSize
}
break
} else if let file = media as? TelegramMediaFile, !file.isVideoSticker {
updatedMediaReference = .message(message: MessageReference(message), media: file)
if let dimensions = file.dimensions {
imageDimensions = dimensions.cgSize
} else if let representation = largestImageRepresentation(file.previewRepresentations), !file.isSticker {
imageDimensions = representation.dimensions.cgSize
}
if file.isInstantVideo {
hasRoundImage = true
}
break
}
}
}
} else if let story = arguments.story, let storyPeer = arguments.parentMessage.peers[story.peerId], let storyItem = arguments.parentMessage.associatedStories[story] {
@ -646,7 +674,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
}
let (titleLayout, titleApply) = titleNodeLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: maxTitleNumberOfLines, truncationType: .end, constrainedSize: CGSize(width: contrainedTextSize.width - additionalTitleWidth, height: contrainedTextSize.height), alignment: .natural, cutout: nil, insets: textInsets))
if isExpiredStory || isStory {
if isExpiredStory || isStory || isPoll {
contrainedTextSize.width -= 26.0
}
@ -713,7 +741,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
size.width = max(titleLayout.size.width + additionalTitleWidth - textInsets.left - textInsets.right, textLeftInset + textLayout.size.width - textInsets.left - textInsets.right - textCutoutWidth) + leftInset + 6.0
size.height = titleLayout.size.height + textLayout.size.height - 2 * (textInsets.top + textInsets.bottom) + 2 * spacing
size.height += 2.0
if isExpiredStory || isStory {
if isExpiredStory || isStory || isPoll {
size.width += 16.0
}
@ -786,7 +814,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
titleNode.frame = CGRect(origin: CGPoint(x: leftInset - textInsets.left - 2.0, y: spacing - textInsets.top + 1.0), size: titleLayout.size)
let textFrame = CGRect(origin: CGPoint(x: textLeftInset + leftInset - textInsets.left - 2.0 - textCutoutWidth, y: titleNode.frame.maxY - textInsets.bottom + spacing - textInsets.top - 2.0), size: textLayout.size)
let effectiveTextFrame = textFrame.offsetBy(dx: (isExpiredStory || isStory) ? 18.0 : 0.0, dy: 0.0)
let effectiveTextFrame = textFrame.offsetBy(dx: (isExpiredStory || isStory || isPoll) ? 18.0 : 0.0, dy: 0.0)
if textNode.textNode.bounds.isEmpty || !animation.isAnimated || textNode.textNode.bounds.height == effectiveTextFrame.height {
textNode.textNode.frame = effectiveTextFrame
@ -809,7 +837,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
}
}
if isExpiredStory || isStory {
if isExpiredStory || isStory || isPoll {
let expiredStoryIconView: UIImageView
if let current = node.expiredStoryIconView {
expiredStoryIconView = current
@ -827,7 +855,9 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
imageType = incoming ? .incoming : .outgoing
}
if isExpiredStory {
if isPoll {
expiredStoryIconView.image = PresentationResourcesChat.chatReplyPollIndicatorIcon(arguments.presentationData.theme.theme, type: imageType)
} else if isExpiredStory {
expiredStoryIconView.image = PresentationResourcesChat.chatExpiredStoryIndicatorIcon(arguments.presentationData.theme.theme, type: imageType)
} else {
expiredStoryIconView.image = PresentationResourcesChat.chatReplyStoryIndicatorIcon(arguments.presentationData.theme.theme, type: imageType)
@ -921,7 +951,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
if let todoItemCompleted {
let checkLayerFrame = CGRect(origin: CGPoint(x: textFrame.minX - 16.0, y: textFrame.minY + 5.0), size: CGSize(width: 12.0, height: 12.0))
let checkTheme = CheckNodeTheme(backgroundColor: titleColor, strokeColor: .clear, borderColor: titleColor, overlayBorder: false, hasInset: true, hasShadow: false, borderWidth: 1.0)
let checkTheme = CheckNodeTheme(backgroundColor: iconColor, strokeColor: .clear, borderColor: iconColor, overlayBorder: false, hasInset: true, hasShadow: false, borderWidth: 1.0)
let checkLayer: CheckLayer
if let current = node.checkLayer {

View file

@ -327,6 +327,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
public var canReadHistory: Bool = false
public var summarizedMessageIds: Set<MessageId> = Set()
public var focusedTextInputIsMedia: Bool = false
public var focusedPollAddOptionMessageId: MessageId?
private var isOpeningMediaValue: Bool = false
public var isOpeningMedia: Bool {

View file

@ -56,7 +56,9 @@ swift_library(
"//submodules/Components/PagerComponent",
"//submodules/FeaturedStickersScreen",
"//submodules/TelegramNotices",
"//submodules/StickerPeekUI:StickerPeekUI",
"//submodules/StickerPeekUI",
"//submodules/CounterControllerTitleView",
"//submodules/TelegramUI/Components/EdgeEffect",
],
visibility = [
"//visibility:public",

View file

@ -32,6 +32,7 @@ import GlassBarButtonComponent
import ChatScheduleTimeController
import ContextUI
import StickerPeekUI
import EdgeEffect
public final class ComposedPoll {
public struct Text {
@ -177,6 +178,9 @@ final class ComposePollScreenComponent: Component {
final class View: UIView, UIScrollViewDelegate {
private let scrollView: ScrollView
private var topEdgeEffectView: EdgeEffectView
private var bottomEdgeEffectView: EdgeEffectView
private var reactionInput: ComponentView<Empty>?
private let pollTextSection = ComponentView<Empty>()
private let quizAnswerSection = ComponentView<Empty>()
@ -267,6 +271,9 @@ final class ComposePollScreenComponent: Component {
self.scrollView.contentInsetAdjustmentBehavior = .never
self.scrollView.alwaysBounceVertical = true
self.topEdgeEffectView = EdgeEffectView()
self.bottomEdgeEffectView = EdgeEffectView()
self.pollOptionsSectionContainer = ListSectionContentView(frame: CGRect())
super.init(frame: frame)
@ -274,6 +281,9 @@ final class ComposePollScreenComponent: Component {
self.scrollView.delegate = self
self.addSubview(self.scrollView)
self.addSubview(self.topEdgeEffectView)
self.addSubview(self.bottomEdgeEffectView)
let reorderRecognizer = ReorderGestureRecognizer(
shouldBegin: { [weak self] point in
guard let self, let (id, item) = self.item(at: point) else {
@ -848,12 +858,29 @@ final class ComposePollScreenComponent: Component {
case .description, .quizAnswer:
availableButtons = [.gallery, .file, .location]
default:
availableButtons = [.gallery, .sticker, .emoji, .location]
availableButtons = [.gallery, .sticker, .location]
}
presentPollAttachmentScreen(context: component.context, updatedPresentationData: nil, availableButtons: availableButtons, inputMediaNodeData: self.inputMediaNodeDataPromise.get() |> map(Optional.init), present: { [weak self] c in
(self?.environment?.controller() as? ComposePollScreen)?.parentController()?.push(c)
}, completion: { [weak self] media in
let pollAttachmentSubject: PollAttachmentSubject
switch subject {
case .description:
pollAttachmentSubject = .description
case .quizAnswer:
pollAttachmentSubject = .quizAnswer
case .pollOption:
pollAttachmentSubject = .option
}
presentPollAttachmentScreen(
context: component.context,
updatedPresentationData: nil,
subject: pollAttachmentSubject,
availableButtons: availableButtons,
inputMediaNodeData: self.inputMediaNodeDataPromise.get() |> map(Optional.init),
present: { [weak self] c in
(self?.environment?.controller() as? ComposePollScreen)?.parentController()?.push(c)
},
completion: { [weak self] media in
guard let self else {
return
}
@ -1729,7 +1756,13 @@ final class ComposePollScreenComponent: Component {
maximumNumberOfLines: 0
))
} else {
let remainingCount = component.initialData.maxPollAnswersCount - self.pollOptions.count
var filledOptionsCount = 0
for option in self.pollOptions {
if option.textInputState.hasText {
filledOptionsCount += 1
}
}
let remainingCount = component.initialData.maxPollAnswersCount - filledOptionsCount
let rawString = environment.strings.CreatePoll_OptionCountFooterFormat(Int32(remainingCount))
var pollOptionsFooterItems: [AnimatedTextComponent.Item] = []
@ -2624,6 +2657,15 @@ final class ComposePollScreenComponent: Component {
}
}
let edgeEffectHeight: CGFloat = 88.0
let topEdgeEffectFrame = CGRect(origin: .zero, size: CGSize(width: availableSize.width, height: edgeEffectHeight))
transition.setFrame(view: self.topEdgeEffectView, frame: topEdgeEffectFrame)
self.topEdgeEffectView.update(content: theme.list.blocksBackgroundColor, blur: true, alpha: 1.0, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: transition)
let bottomEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - edgeEffectHeight - environment.additionalInsets.bottom), size: CGSize(width: availableSize.width, height: edgeEffectHeight))
transition.setFrame(view: self.bottomEdgeEffectView, frame: bottomEdgeEffectFrame)
self.bottomEdgeEffectView.update(content: theme.list.blocksBackgroundColor, blur: true, alpha: 1.0, rect: bottomEdgeEffectFrame, edge: .bottom, edgeSize: bottomEdgeEffectFrame.height, transition: transition)
return availableSize
}
}
@ -2713,7 +2755,7 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont
isQuiz: isQuiz,
initialData: initialData,
completion: completion
), navigationBarAppearance: .default, theme: .default)
), navigationBarAppearance: .transparent, theme: .default)
self._hasGlassStyle = true
@ -2813,7 +2855,7 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont
public func prepareForReuse() {
}
public func requestDismiss(completion: @escaping () -> Void) {
public func requestDismiss(completion: @escaping () -> Void) {
completion()
}

View file

@ -16,9 +16,16 @@ import LocationUI
import AttachmentFileController
import ChatEntityKeyboardInputNode
public enum PollAttachmentSubject {
case description
case quizAnswer
case option
}
public func presentPollAttachmentScreen(
context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?,
subject: PollAttachmentSubject,
availableButtons: [AttachmentButtonType],
inputMediaNodeData: Signal<ChatEntityKeyboardInputNode.InputData?, NoError> = .single(nil),
present: @escaping (ViewController) -> Void,
@ -36,16 +43,18 @@ public func presentPollAttachmentScreen(
return nil
}
)
// attachmentController.getSourceRect = { [weak self] in
// if let strongSelf = self {
// return strongSelf.chatDisplayNode.frameForAttachmentButton()?.offsetBy(dx: strongSelf.chatDisplayNode.supernode?.frame.minX ?? 0.0, dy: 0.0)
// } else {
// return nil
// }
// }
attachmentController.requestController = { [weak attachmentController] type, controllerCompletion in
switch type {
case .gallery:
let mediaPickerPollSubject: MediaPickerScreenImpl.Subject.AssetsMode.PollMode
switch subject {
case .description:
mediaPickerPollSubject = .description
case .quizAnswer:
mediaPickerPollSubject = .quizAnswer
case .option:
mediaPickerPollSubject = .option
}
let controller = MediaPickerScreenImpl(
context: context,
updatedPresentationData: updatedPresentationData,
@ -54,7 +63,7 @@ public func presentPollAttachmentScreen(
threadTitle: nil,
chatLocation: nil,
enableMultiselection: false,
subject: .assets(nil, .poll(.option))
subject: .assets(nil, .poll(mediaPickerPollSubject))
)
controller.getCaptionPanelView = {
return nil
@ -71,24 +80,49 @@ public func presentPollAttachmentScreen(
controllerCompletion(controller, controller.mediaPickerContext)
return true
case .file:
let controller = context.sharedContext.makeAttachmentFileController(context: context, updatedPresentationData: updatedPresentationData, audio: false, bannedSendMedia: nil, presentGallery: { [weak attachmentController] in
attachmentController?.dismiss(animated: true)
//self?.presentFileGallery()
}, presentFiles: { [weak attachmentController] in
attachmentController?.dismiss(animated: true)
//self?.presentICloudFileGallery()
}, presentDocumentScanner: {
//self?.presentDocumentScanner()
}, send: { mediaReferences in
completion(mediaReferences.first!)
})
guard let controller = controller as? AttachmentFileControllerImpl else {
return false
let filePickerPollSubject: AttachmentFileControllerSource.PollMode
switch subject {
case .description:
filePickerPollSubject = .description
case .quizAnswer:
filePickerPollSubject = .quizAnswer
default:
filePickerPollSubject = .description
}
let controller = makeAttachmentFileControllerImpl(
context: context,
updatedPresentationData: updatedPresentationData,
source: .poll(filePickerPollSubject),
bannedSendMedia: nil,
presentGallery: {},
presentFiles: { [weak attachmentController] in
attachmentController?.dismiss(animated: true)
//TODO
},
presentDocumentScanner: nil,
send: { mediaReferences, _, _, _ in
completion(mediaReferences.first!)
}
) as! AttachmentFileControllerImpl
controllerCompletion(controller, controller.mediaPickerContext)
return true
case .location:
let controller = LocationPickerController(context: context, style: .glass, updatedPresentationData: updatedPresentationData, mode: .share(peer: nil, selfPeer: nil, hasLiveLocation: false), completion: { location, _, _, _, _ in
let locationPickerPollSubject: LocationPickerController.Source.PollMode
switch subject {
case .description:
locationPickerPollSubject = .description
case .quizAnswer:
locationPickerPollSubject = .quizAnswer
case .option:
locationPickerPollSubject = .option
}
let controller = LocationPickerController(
context: context,
style: .glass,
updatedPresentationData: updatedPresentationData,
mode: .share(peer: nil, selfPeer: nil, hasLiveLocation: false),
source: .poll(locationPickerPollSubject),
completion: { location, _, _, _, _ in
completion(.standalone(media: location))
})
controllerCompletion(controller, controller.mediaPickerContext)
@ -100,9 +134,23 @@ public func presentPollAttachmentScreen(
guard let content = content?.stickers else {
return
}
let controller = StickerAttachmentScreen(context: context, mode: .stickers(content), completion: { sticker in
completion(sticker)
})
let stickerPickerPollSubject: StickerAttachmentScreen.Source.PollMode
switch subject {
case .description:
stickerPickerPollSubject = .description
case .quizAnswer:
stickerPickerPollSubject = .quizAnswer
case .option:
stickerPickerPollSubject = .option
}
let controller = StickerAttachmentScreen(
context: context,
mode: .stickers(content),
source: .poll(stickerPickerPollSubject),
completion: { sticker in
completion(sticker)
}
)
controllerCompletion(controller, controller.mediaPickerContext)
})
return true
@ -113,9 +161,23 @@ public func presentPollAttachmentScreen(
guard let content = content?.emoji else {
return
}
let controller = StickerAttachmentScreen(context: context, mode: .emoji(content), completion: { sticker in
completion(sticker)
})
let stickerPickerPollSubject: StickerAttachmentScreen.Source.PollMode
switch subject {
case .description:
stickerPickerPollSubject = .description
case .quizAnswer:
stickerPickerPollSubject = .quizAnswer
case .option:
stickerPickerPollSubject = .option
}
let controller = StickerAttachmentScreen(
context: context,
mode: .emoji(content),
source: .poll(stickerPickerPollSubject),
completion: { sticker in
completion(sticker)
}
)
controllerCompletion(controller, controller.mediaPickerContext)
})
return true

View file

@ -15,6 +15,10 @@ import ChatPresentationInterfaceState
import PagerComponent
import FeaturedStickersScreen
import TelegramNotices
import CounterControllerTitleView
import GlassBackgroundComponent
import GlassBarButtonComponent
import BundleIconComponent
final class StickerAttachmentScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -49,8 +53,10 @@ final class StickerAttachmentScreenComponent: Component {
final class View: UIView, UIScrollViewDelegate {
fileprivate let keyboardView: ComponentView<Empty>
private let keyboardClippingView: KeyboardClippingView
private let panelBackgroundView: GlassBackgroundView
private let panelClippingView: UIView
private let panelHostView: PagerExternalTopPanelContainer
private let panelSeparatorView: UIView
private let cancelButton: ComponentView<Empty>
private var component: StickerAttachmentScreenComponent?
private(set) weak var state: EmptyComponentState?
@ -109,14 +115,17 @@ final class StickerAttachmentScreenComponent: Component {
override init(frame: CGRect) {
self.keyboardView = ComponentView<Empty>()
self.keyboardClippingView = KeyboardClippingView()
self.panelBackgroundView = GlassBackgroundView()
self.panelClippingView = UIView()
self.panelHostView = PagerExternalTopPanelContainer()
self.panelSeparatorView = UIView()
self.cancelButton = ComponentView<Empty>()
super.init(frame: frame)
self.addSubview(self.keyboardClippingView)
self.addSubview(self.panelSeparatorView)
self.addSubview(self.panelHostView)
self.addSubview(self.panelBackgroundView)
self.panelBackgroundView.contentView.addSubview(self.panelClippingView)
self.panelClippingView.addSubview(self.panelHostView)
self.interaction = ChatEntityKeyboardInputNode.Interaction(
sendSticker: { [weak self] file, _, _, _, _, _, _, _, _ in
@ -200,10 +209,7 @@ final class StickerAttachmentScreenComponent: Component {
(self.environment?.controller() as? StickerAttachmentScreen)?.dismiss(animated: true)
}
func updateContent() {
guard let component = self.component else {
return
}
func updateContent(component: StickerAttachmentScreenComponent) {
self.emojiContent?.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction(
performItemAction: { [weak self] groupId, item, _, _, _, _ in
guard let self, let component = self.component else {
@ -599,7 +605,7 @@ final class StickerAttachmentScreenComponent: Component {
externalBackground: nil,
externalExpansionView: nil,
customContentView: nil,
useOpaqueTheme: false,
useOpaqueTheme: true,
hideBackground: true,
stateContext: nil,
addImage: nil
@ -872,7 +878,7 @@ final class StickerAttachmentScreenComponent: Component {
externalBackground: nil,
externalExpansionView: nil,
customContentView: nil,
useOpaqueTheme: false,
useOpaqueTheme: true,
hideBackground: true,
stateContext: nil,
addImage: nil
@ -884,7 +890,6 @@ final class StickerAttachmentScreenComponent: Component {
self.environment = environment
self.backgroundColor = environment.theme.list.plainBackgroundColor
self.panelSeparatorView.backgroundColor = .clear
if self.component == nil {
let data = combineLatest(
@ -933,10 +938,7 @@ final class StickerAttachmentScreenComponent: Component {
}
self.stickerContent = stickerContent
}
Queue.mainQueue().justDispatch {
self.updateContent()
self.state?.updated()
}
self.updateContent(component: component)
}))
}
@ -962,15 +964,15 @@ final class StickerAttachmentScreenComponent: Component {
theme: environment.theme,
strings: environment.strings,
isContentInFocus: true,
containerInsets: UIEdgeInsets(top: topPanelHeight - 34.0 + topInset, left: 0.0, bottom: 0.0, right: 0.0),
topPanelInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0),
containerInsets: UIEdgeInsets(top: topPanelHeight + topInset - 11.0, left: 0.0, bottom: 0.0, right: 0.0),
topPanelInsets: UIEdgeInsets(top: 0.0, left: 12.0, bottom: 0.0, right: 12.0),
emojiContent: self.emojiContent,
stickerContent: self.stickerContent,
maskContent: nil,
gifContent: nil,
hasRecentGifs: false,
availableGifSearchEmojies: [],
defaultToEmojiTab: emojiContent != nil,
defaultToEmojiTab: self.emojiContent != nil,
externalTopPanelContainer: self.panelHostView,
externalBottomPanelContainer: nil,
externalTintMaskContainer: nil,
@ -993,6 +995,16 @@ final class StickerAttachmentScreenComponent: Component {
self.forceUpdate = true
self.searchVisible = searchVisible
self.state?.updated(transition: transition)
let transition: ComponentTransition = .easeInOut(duration: 0.2)
if let controller = self.environment?.controller() as? StickerAttachmentScreen {
if let titleView = controller.navigationItem.titleView {
transition.setAlpha(view: titleView, alpha: searchVisible ? 0.0 : 1.0)
}
if searchVisible {
controller.requestAttachmentMenuExpansion()
}
}
},
hideTopPanelUpdated: { _, _ in
},
@ -1053,28 +1065,57 @@ final class StickerAttachmentScreenComponent: Component {
self.keyboardClippingView.addSubview(keyboardComponentView)
}
// if panelBackgroundColor.alpha < 0.01 {
// self.keyboardClippingView.clipsToBounds = true
// } else {
self.keyboardClippingView.clipsToBounds = false
// }
self.keyboardClippingView.clipsToBounds = false
transition.setFrame(view: self.keyboardClippingView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight + topInset), size: CGSize(width: availableSize.width, height: availableSize.height - topPanelHeight - topInset)))
self.keyboardClippingView.hitEdgeInsets = UIEdgeInsets(top: -topPanelHeight - topInset, left: 0.0, bottom: 0.0, right: 0.0)
let panelBackgroundFrame = CGRect(origin: CGPoint(x: 12.0, y: topPanelHeight + topInset - 29.0), size: CGSize(width: availableSize.width - 24.0, height: 44.0))
self.panelClippingView.clipsToBounds = true
self.panelClippingView.layer.cornerRadius = panelBackgroundFrame.height * 0.5
transition.setFrame(view: self.panelClippingView, frame: CGRect(origin: .zero, size: panelBackgroundFrame.size))
self.panelBackgroundView.update(size: panelBackgroundFrame.size, cornerRadius: panelBackgroundFrame.size.height * 0.5, isDark: environment.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: true, isVisible: !self.searchVisible, transition: transition)
transition.setFrame(view: self.panelBackgroundView, frame: panelBackgroundFrame)
transition.setFrame(view: keyboardComponentView, frame: CGRect(origin: CGPoint(x: 0.0, y: -topPanelHeight - topInset), size: keyboardSize))
transition.setFrame(view: self.panelHostView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight + topInset - 34.0), size: CGSize(width: keyboardSize.width, height: 0.0)))
let topPanelAlpha: CGFloat
if self.searchVisible || self.keyboardContentId == AnyHashable("gifs") {
topPanelAlpha = 0.0
} else {
topPanelAlpha = max(0.0, min(1.0, (self.topPanelScrollingOffset / 20.0)))
}
transition.setFrame(view: self.panelHostView, frame: CGRect(origin: CGPoint(x: -12.0, y: 8.0 - UIScreenPixel), size: CGSize(width: keyboardSize.width, height: 0.0)))
}
transition.setAlpha(view: self.panelSeparatorView, alpha: topPanelAlpha)
transition.setFrame(view: self.panelSeparatorView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight + topInset - UIScreenPixel), size: CGSize(width: keyboardSize.width, height: UIScreenPixel)))
let barButtonSize = CGSize(width: 44.0, height: 44.0)
let cancelButtonSize = self.cancelButton.update(
transition: transition,
component: AnyComponent(GlassBarButtonComponent(
size: barButtonSize,
backgroundColor: nil,
isDark: environment.theme.overallDarkAppearance,
state: .glass,
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
BundleIconComponent(
name: "Navigation/Close",
tintColor: environment.theme.chat.inputPanel.panelControlColor
)
)),
action: { [weak self] _ in
guard let self else {
return
}
(self.environment?.controller() as? StickerAttachmentScreen)?.dismiss(animated: true)
}
)),
environment: {},
containerSize: barButtonSize
)
let cancelButtonFrame = CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: cancelButtonSize)
if let cancelButtonView = self.cancelButton.view {
if cancelButtonView.superview == nil {
self.addSubview(cancelButtonView)
}
transition.setBounds(view: cancelButtonView, bounds: CGRect(origin: .zero, size: cancelButtonFrame.size))
transition.setPosition(view: cancelButtonView, position: cancelButtonFrame.center)
transition.setAlpha(view: cancelButtonView, alpha: self.searchVisible ? 0.0 : 1.0)
transition.setScale(view: cancelButtonView, scale: self.searchVisible ? 0.001 : 1.0)
}
return availableSize
@ -1096,11 +1137,21 @@ final class StickerAttachmentScreen: ViewControllerComponentContainer, Attachmen
case emoji(EmojiPagerContentComponent)
}
enum Source: Equatable {
enum PollMode: Equatable {
case description
case quizAnswer
case option
}
case poll(PollMode)
}
private let context: AccountContext
private let mode: Mode
private let completion: (AnyMediaReference) -> Void
init(context: AccountContext, mode: Mode, completion: @escaping (AnyMediaReference) -> Void) {
init(context: AccountContext, mode: Mode, source: Source, completion: @escaping (AnyMediaReference) -> Void) {
self.context = context
self.mode = mode
self.completion = completion
@ -1109,9 +1160,43 @@ final class StickerAttachmentScreen: ViewControllerComponentContainer, Attachmen
context: context,
mode: mode,
completion: completion
), navigationBarAppearance: .default, theme: .default)
), navigationBarAppearance: .transparent, theme: .default)
self._hasGlassStyle = true
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
switch source {
case let .poll(pollMode):
//TODO:localize
let title: String
let subtitle: String
switch mode {
case .stickers:
title = "Sticker"
switch pollMode {
case .description:
subtitle = "Add sticker to the poll description"
case .quizAnswer:
subtitle = "Add sticker to the quiz explanation"
case .option:
subtitle = "Add sticker to this option"
}
case .emoji:
title = "Emoji"
switch pollMode {
case .description:
subtitle = "Add emoji to the poll description"
case .quizAnswer:
subtitle = "Add emoji to the quiz explanation"
case .option:
subtitle = "Add emoji to this option"
}
}
let titleView = CounterControllerTitleView(theme: presentationData.theme, verticalOffset: -2.0)
titleView.title = CounterControllerTitle(title: title, counter: subtitle)
self.navigationItem.titleView = titleView
}
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView())
}
required init(coder: NSCoder) {

View file

@ -45,6 +45,7 @@ swift_library(
"//submodules/TelegramUI/Components/ComposePollScreen",
"//submodules/Markdown",
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
"//submodules/TelegramUI/Components/EdgeEffect",
],
visibility = [
"//visibility:public",

View file

@ -30,6 +30,7 @@ import ListComposePollOptionComponent
import Markdown
import PresentationDataUtils
import GlassBarButtonComponent
import EdgeEffect
final class ComposeTodoScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -72,6 +73,9 @@ final class ComposeTodoScreenComponent: Component {
final class View: UIView, UIScrollViewDelegate {
private let scrollView: UIScrollView
private var topEdgeEffectView: EdgeEffectView
private var bottomEdgeEffectView: EdgeEffectView
private let todoTextSection = ComponentView<Empty>()
private let todoItemsSectionHeader = ComponentView<Empty>()
@ -132,6 +136,9 @@ final class ComposeTodoScreenComponent: Component {
self.scrollView.contentInsetAdjustmentBehavior = .never
self.scrollView.alwaysBounceVertical = true
self.topEdgeEffectView = EdgeEffectView()
self.bottomEdgeEffectView = EdgeEffectView()
self.todoItemsSectionContainer = ListSectionContentView(frame: CGRect())
self.todoItemsSectionContainer.automaticallyLayoutExternalContentBackgroundView = false
@ -140,6 +147,9 @@ final class ComposeTodoScreenComponent: Component {
self.scrollView.delegate = self
self.addSubview(self.scrollView)
self.addSubview(self.topEdgeEffectView)
self.addSubview(self.bottomEdgeEffectView)
let reorderRecognizer = ReorderGestureRecognizer(
shouldBegin: { [weak self] point in
guard let self, let (id, item) = self.item(at: point) else {
@ -1654,6 +1664,15 @@ final class ComposeTodoScreenComponent: Component {
self.todoItems[i].resetText = nil
}
let edgeEffectHeight: CGFloat = 88.0
let topEdgeEffectFrame = CGRect(origin: .zero, size: CGSize(width: availableSize.width, height: edgeEffectHeight))
transition.setFrame(view: self.topEdgeEffectView, frame: topEdgeEffectFrame)
self.topEdgeEffectView.update(content: theme.list.blocksBackgroundColor, blur: true, alpha: 1.0, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: transition)
let bottomEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - edgeEffectHeight - environment.additionalInsets.bottom), size: CGSize(width: availableSize.width, height: edgeEffectHeight))
transition.setFrame(view: self.bottomEdgeEffectView, frame: bottomEdgeEffectFrame)
self.bottomEdgeEffectView.update(content: theme.list.blocksBackgroundColor, blur: true, alpha: 1.0, rect: bottomEdgeEffectFrame, edge: .bottom, edgeSize: bottomEdgeEffectFrame.height, transition: transition)
return availableSize
}
}
@ -1753,7 +1772,7 @@ public class ComposeTodoScreen: ViewControllerComponentContainer, AttachmentCont
peer: peer,
initialData: initialData,
completion: completion
), navigationBarAppearance: .default, theme: .default)
), navigationBarAppearance: .transparent, theme: .default)
self._hasGlassStyle = true

View file

@ -35,6 +35,7 @@ swift_library(
"//submodules/UIKitRuntimeUtils",
"//submodules/UndoUI",
"//submodules/TelegramUI/Components/LensTransition",
"//submodules/Components/BalancedTextComponent",
],
visibility = [
"//visibility:public",

View file

@ -20,6 +20,7 @@ import LottieComponent
import TextNodeWithEntities
import ContextUI
import LensTransition
import BalancedTextComponent
public protocol ContextControllerActionsListItemNode: ASDisplayNode {
func update(presentationData: PresentationData, constrainedSize: CGSize) -> (minSize: CGSize, apply: (_ size: CGSize, _ transition: ContainedViewLayoutTransition) -> Void)
@ -39,6 +40,7 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
private var item: ContextMenuActionItem
private let titleLabelNode: ImmediateTextNodeWithEntities
private let balancedTitleLabel = ComponentView<Empty>()
private let subtitleNode: ImmediateTextNode
private let iconNode: ASImageNode
private let additionalIconNode: ASImageNode
@ -282,7 +284,13 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
titleColor = presentationData.theme.contextMenu.primaryColor.withMultipliedAlpha(0.4)
}
var balanceTitleAttributedText: NSAttributedString?
if self.item.parseMarkdown || !self.item.entities.isEmpty {
var balancedTextLayout = false
if self.item.parseMarkdown, case .twoLinesMax = self.item.textLayout {
balancedTextLayout = true
}
let attributedText: NSAttributedString
if !self.item.entities.isEmpty {
let inputStateText = ChatTextInputStateText(text: self.item.text, attributes: self.item.entities.compactMap { entity -> ChatTextInputStateTextAttribute? in
@ -324,18 +332,25 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
)
)
}
self.titleLabelNode.attributedText = attributedText
self.titleLabelNode.linkHighlightColor = presentationData.theme.list.itemAccentColor.withMultipliedAlpha(0.5)
self.titleLabelNode.highlightAttributeAction = { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] {
return NSAttributedString.Key(rawValue: "URL")
} else {
return nil
if self.item.entities.isEmpty && balancedTextLayout {
self.titleLabelNode.isHidden = true
balanceTitleAttributedText = attributedText
} else {
self.titleLabelNode.isHidden = false
self.titleLabelNode.attributedText = attributedText
self.titleLabelNode.linkHighlightColor = presentationData.theme.list.itemAccentColor.withMultipliedAlpha(0.5)
self.titleLabelNode.highlightAttributeAction = { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] {
return NSAttributedString.Key(rawValue: "URL")
} else {
return nil
}
}
}
self.titleLabelNode.tapAttributeAction = { [weak item] attributes, _ in
if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] {
item?.textLinkAction()
self.titleLabelNode.tapAttributeAction = { [weak item] attributes, _ in
if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] {
item?.textLinkAction()
}
}
}
} else {
@ -506,7 +521,22 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
maxTextWidth = max(1.0, maxTextWidth)
let titleSize = self.titleLabelNode.updateLayout(CGSize(width: maxTextWidth, height: 1000.0))
let titleSize: CGSize
if let balanceTitleAttributedText {
titleSize = self.balancedTitleLabel.update(
transition: .immediate,
component: AnyComponent(
BalancedTextComponent(
text: .plain(balanceTitleAttributedText),
maximumNumberOfLines: 2
)
),
environment: {},
containerSize: CGSize(width: maxTextWidth, height: 1000.0)
)
} else {
titleSize = self.titleLabelNode.updateLayout(CGSize(width: maxTextWidth, height: 1000.0))
}
let subtitleSize = self.subtitleNode.updateLayout(CGSize(width: maxTextWidth, height: 1000.0))
var minSize = CGSize()
@ -549,7 +579,16 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
subtitleFrame.origin.x = titleFrame.minX
}
transition.updateFrameAdditive(node: self.titleLabelNode, frame: titleFrame)
if let _ = balanceTitleAttributedText {
if let balancedTitleLabelView = self.balancedTitleLabel.view {
if balancedTitleLabelView.superview == nil {
self.view.addSubview(balancedTitleLabelView)
}
transition.updateFrameAdditive(view: balancedTitleLabelView, frame: titleFrame)
}
} else {
transition.updateFrameAdditive(node: self.titleLabelNode, frame: titleFrame)
}
transition.updateFrameAdditive(node: self.subtitleNode, frame: subtitleFrame)
if let badgeIconNode = self.badgeIconNode, let iconSize = badgeIconNode.image?.size {

View file

@ -434,7 +434,7 @@ public final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
self.messagesLocation = location
switch self.messagesLocation.effectiveLocation(context: context) {
case let .messages(_, _, messageId), let .singleMessage(messageId), let .custom(_, _, messageId, _):
case let .messages(_, _, messageId), let .singleMessage(messageId), let .custom(_, _, messageId, _, _):
self.loadItem(anchor: .messageId(messageId), navigation: .later, reversed: self.order == .reversed)
case let .recentActions(message):
self.loadingItem = false
@ -608,7 +608,7 @@ public final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
strongSelf.updateState()
}
}))
case let .custom(messages, _, at, _):
case let .custom(messages, _, at, _, _):
self.navigationDisposable.set((messages
|> take(1)
|> deliverOnMainQueue).startStrict(next: { [weak self] messages in
@ -787,7 +787,7 @@ public final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
self.updateState()
case .savedMusic:
fatalError()
case let .custom(messages, _, _, loadMore):
case let .custom(messages, _, _, loadMore, _):
let inputIndex: Signal<MessageIndex, NoError>
let looping = self.looping
switch self.order {

View file

@ -115,8 +115,8 @@ private class MediaHeaderItemNode: ASDisplayNode {
let minimizedTitleOffset: CGFloat = subtitleString == nil ? 6.0 : 0.0
let minimizedTitleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleLayout.size.width) / 2.0), y: 4.0 + minimizedTitleOffset), size: titleLayout.size)
let minimizedSubtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleLayout.size.width) / 2.0), y: 20.0), size: subtitleLayout.size)
let minimizedTitleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleLayout.size.width) / 2.0), y: 5.0 + minimizedTitleOffset), size: titleLayout.size)
let minimizedSubtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleLayout.size.width) / 2.0), y: 21.0), size: subtitleLayout.size)
transition.updateFrame(node: self.titleNode, frame: minimizedTitleFrame)
transition.updateFrame(node: self.subtitleNode, frame: minimizedSubtitleFrame)

View file

@ -532,7 +532,7 @@ private final class ItemView: UIView, SparseItemGridView {
}
if let item = self.item, let messageItem = self.messageItem, let itemNode = itemNode as? ListMessageFileItemNode {
if case let .selectable(selected) = messageItem.selection {
if case let .selectable(selected, _) = messageItem.selection {
self.interaction?.toggleMessagesSelection([item.message.id], !selected)
} else {
itemNode.activateMedia()
@ -560,7 +560,7 @@ private final class ItemView: UIView, SparseItemGridView {
interaction: interaction,
message: item.message,
selection: isSelected.flatMap { isSelected in
return .selectable(selected: isSelected)
return .selectable(selected: isSelected, num: nil)
} ?? .none,
displayHeader: 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] mediaReferences in
}, presentDocumentScanner: nil, send: { [weak view] mediaReferences, _, _, _ in
guard let view, let component = view.component else {
return
}

View file

@ -153,6 +153,7 @@ public final class TextFieldComponent: Component {
public let hideKeyboard: Bool
public let customInputView: UIView?
public let placeholder: NSAttributedString?
public let placeholderVerticalOffset: CGFloat
public let prefix: NSAttributedString?
public let suffix: NSAttributedString?
public let resetText: NSAttributedString?
@ -185,6 +186,7 @@ public final class TextFieldComponent: Component {
hideKeyboard: Bool,
customInputView: UIView?,
placeholder: NSAttributedString? = nil,
placeholderVerticalOffset: CGFloat = 0.0,
prefix: NSAttributedString? = nil,
suffix: NSAttributedString? = nil,
resetText: NSAttributedString?,
@ -216,6 +218,7 @@ public final class TextFieldComponent: Component {
self.hideKeyboard = hideKeyboard
self.customInputView = customInputView
self.placeholder = placeholder
self.placeholderVerticalOffset = placeholderVerticalOffset
self.prefix = prefix
self.suffix = suffix
self.resetText = resetText
@ -271,6 +274,9 @@ public final class TextFieldComponent: Component {
if lhs.placeholder != rhs.placeholder {
return false
}
if lhs.placeholderVerticalOffset != rhs.placeholderVerticalOffset {
return false
}
if lhs.prefix != rhs.prefix {
return false
}
@ -365,6 +371,7 @@ public final class TextFieldComponent: Component {
self.textView.layer.isOpaque = false
self.textView.indicatorStyle = .white
self.textView.scrollIndicatorInsets = UIEdgeInsets(top: 9.0, left: 0.0, bottom: 9.0, right: 0.0)
self.textView.showsHorizontalScrollIndicator = false
self.inputMenu = TextInputMenu(hasSpoilers: true, hasQuotes: true)
@ -1587,27 +1594,28 @@ public final class TextFieldComponent: Component {
}
let placeholderSize = placeholder.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(placeholderValue)
)),
component: AnyComponent(
Text(attributedString: placeholderValue)
),
environment: {},
containerSize: textFrame.size
)
let placeholderFrame = CGRect(origin: CGPoint(x: 0.0, y: floor((textFrame.height - placeholderSize.height) * 0.5) - 1.0), size: placeholderSize)
let placeholderFrame = CGRect(origin: CGPoint(x: 0.0, y: floor((textFrame.height - placeholderSize.height) * 0.5) - 1.0 + component.placeholderVerticalOffset), size: placeholderSize)
if let placeholderView = placeholder.view {
if placeholderView.superview == nil {
placeholderView.isUserInteractionEnabled = false
placeholderView.layer.anchorPoint = CGPoint()
self.textView.insertSubview(placeholderView, at: 0)
}
placeholderTransition.setPosition(view: placeholderView, position: placeholderFrame.origin)
placeholderTransition.setPosition(view: placeholderView, position: placeholderFrame.center)
placeholderView.bounds = CGRect(origin: CGPoint(), size: placeholderFrame.size)
placeholderView.isHidden = self.textView.textStorage.length != 0
}
} else if let placeholder = self.placeholder {
self.placeholder = nil
placeholder.view?.removeFromSuperview()
} else {
if let placeholder = self.placeholder {
self.placeholder = nil
placeholder.view?.removeFromSuperview()
}
}
self.updateEmojiSuggestion(transition: .immediate)

View file

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

View file

@ -14,6 +14,7 @@ import ChatControllerInteraction
import Pasteboard
import TelegramStringFormatting
import TelegramPresentationData
import AvatarNode
private enum OptionsId: Hashable {
case item
@ -37,8 +38,16 @@ extension ChatControllerImpl {
}
}
let _ = (contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: self.presentationInterfaceState, context: self.context, messages: [message], controllerInteraction: self.controllerInteraction, selectAll: false, interfaceInteraction: self.interfaceInteraction, messageNode: params.messageNode as? ChatMessageItemView)
|> deliverOnMainQueue).start(next: { [weak self] actions in
var addedByPeer: Signal<EnginePeer?, NoError> = .single(nil)
if let peerId = pollOption.addedBy {
addedByPeer = self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
}
let _ = combineLatest(
queue: Queue.mainQueue(),
contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: self.presentationInterfaceState, context: self.context, messages: [message], controllerInteraction: self.controllerInteraction, selectAll: false, interfaceInteraction: self.interfaceInteraction, messageNode: params.messageNode as? ChatMessageItemView),
addedByPeer
).start(next: { [weak self] actions, addedByPeer in
guard let self else {
return
}
@ -146,7 +155,7 @@ extension ChatControllerImpl {
})))
}
if pollOption.date != nil {
if let addedByPeer, let date = pollOption.date {
//TODO:localize
items.append(.action(ContextMenuActionItem(text: "Remove", textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] _, f in
f(.default)
@ -156,6 +165,49 @@ extension ChatControllerImpl {
}
let _ = self.context.engine.messages.deletePollOption(messageId: message.id, opaqueIdentifier: pollOption.opaqueIdentifier).start()
})))
items.append(.separator)
let peerName = "**\(addedByPeer.compactDisplayTitle)**"
let dateText = humanReadableStringForTimestamp(strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, timestamp: date, alwaysShowTime: true, allowYesterday: true, format: HumanReadableStringFormat(
dateFormatString: { value in
if addedByPeer.id == self.context.account.peerId {
return PresentationStrings.FormattedString(string: self.presentationData.strings.Chat_PolOptionAddedTimestampYou_Date(value).string, ranges: [])
} else {
return PresentationStrings.FormattedString(string: self.presentationData.strings.Chat_PolOptionAddedTimestamp_Date(peerName, value).string, ranges: [])
}
},
tomorrowFormatString: { value in
if addedByPeer.id == self.context.account.peerId {
return PresentationStrings.FormattedString(string: self.presentationData.strings.Chat_PolOptionAddedTimestampYou_TodayAt(value).string, ranges: [])
} else {
return PresentationStrings.FormattedString(string: self.presentationData.strings.Chat_PolOptionAddedTimestamp_TodayAt(peerName, value).string, ranges: [])
}
},
todayFormatString: { value in
if addedByPeer.id == self.context.account.peerId {
return PresentationStrings.FormattedString(string: self.presentationData.strings.Chat_PolOptionAddedTimestampYou_TodayAt(value).string, ranges: [])
} else {
return PresentationStrings.FormattedString(string: self.presentationData.strings.Chat_PolOptionAddedTimestamp_TodayAt(peerName, value).string, ranges: [])
}
},
yesterdayFormatString: { value in
if addedByPeer.id == self.context.account.peerId {
return PresentationStrings.FormattedString(string: self.presentationData.strings.Chat_PolOptionAddedTimestampYou_YesterdayAt(value).string, ranges: [])
} else {
return PresentationStrings.FormattedString(string: self.presentationData.strings.Chat_PolOptionAddedTimestamp_YesterdayAt(peerName, value).string, ranges: [])
}
}
)).string
let avatarSize = CGSize(width: 24.0, height: 24.0)
items.append(.action(ContextMenuActionItem(text: dateText, textFont: .small, parseMarkdown: true, icon: { _ in return nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: peerAvatarCompleteImage(account: self.context.account, peer: addedByPeer, size: avatarSize)), action: { [weak self] _, f in
f(.default)
guard let self else {
return
}
self.openPeer(peer: addedByPeer, navigation: .chat(textInputState: nil, subject: nil, peekData: nil), fromMessage: nil)
})))
}
self.canReadHistory.set(false)

View file

@ -553,7 +553,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
if let poll = media as? TelegramMediaPoll {
var updatedMedia = message.media.filter { !($0 is TelegramMediaPoll) }
updatedMedia.append(TelegramMediaPoll(pollId: poll.pollId, publicity: poll.publicity, kind: poll.kind, text: poll.text, textEntities: poll.textEntities, options: poll.options, correctAnswers: poll.correctAnswers, results: TelegramMediaPollResults(voters: nil, totalVoters: nil, recentVoters: [], solution: nil), isClosed: false, deadlineTimeout: nil, deadlineDate: nil))
updatedMedia.append(TelegramMediaPoll(pollId: poll.pollId, publicity: poll.publicity, kind: poll.kind, text: poll.text, textEntities: poll.textEntities, options: poll.options, correctAnswers: poll.correctAnswers, results: TelegramMediaPollResults(voters: nil, totalVoters: nil, recentVoters: [], solution: nil), isClosed: false, deadlineTimeout: nil, deadlineDate: nil, pollHash: poll.pollHash))
messageMedia = updatedMedia
}
if let _ = media as? TelegramMediaDice {
@ -1377,19 +1377,23 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
}
if self.chatPresentationInterfaceState.search == nil, let mediaPlayback = self.controller?.globalControlPanelsContextState?.mediaPlayback {
headerPanels.append(HeaderPanelContainerComponent.Panel(
key: "media",
orderIndex: 1,
component: AnyComponent(MediaPlaybackHeaderPanelComponent(
context: self.context,
theme: self.chatPresentationInterfaceState.theme,
strings: self.chatPresentationInterfaceState.strings,
data: mediaPlayback,
controller: { [weak self] in
return self?.controller
}
)))
)
if let playlistLocation = mediaPlayback.playlistLocation as? PeerMessagesPlaylistLocation, case let .custom(_, _, _, _, hidePanel) = playlistLocation, hidePanel {
} else {
headerPanels.append(HeaderPanelContainerComponent.Panel(
key: "media",
orderIndex: 1,
component: AnyComponent(MediaPlaybackHeaderPanelComponent(
context: self.context,
theme: self.chatPresentationInterfaceState.theme,
strings: self.chatPresentationInterfaceState.strings,
data: mediaPlayback,
controller: { [weak self] in
return self?.controller
}
)))
)
}
}
if self.chatPresentationInterfaceState.search == nil, let liveLocation = self.controller?.globalControlPanelsContextState?.liveLocation {
headerPanels.append(HeaderPanelContainerComponent.Panel(
@ -3565,6 +3569,13 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
self.controller?.navigateToMessage(from: nil, to: .id(messageId, NavigateToMessageParams()), scrollPosition: .top(-18.0))
}
var focusedTextInputIsMedia = false
if case .media = chatPresentationInterfaceState.inputMode {
focusedTextInputIsMedia = true
}
self.controllerInteraction.focusedTextInputIsMedia = focusedTextInputIsMedia
self.controllerInteraction.focusedPollAddOptionMessageId = chatPresentationInterfaceState.focusedPollAddOptionMessageId
let updateInputTextState = self.chatPresentationInterfaceState.interfaceState.effectiveInputState != chatPresentationInterfaceState.interfaceState.effectiveInputState
self.chatPresentationInterfaceState = chatPresentationInterfaceState

View file

@ -381,7 +381,7 @@ extension ChatControllerImpl {
self?.presentICloudFileGallery()
}, presentDocumentScanner: { [weak self] in
self?.presentDocumentScanner()
}, send: { [weak self] mediaReferences in
}, send: { [weak self] mediaReferences, silentPosting, scheduleTime, caption in
guard let self else {
return
}
@ -390,9 +390,26 @@ extension ChatControllerImpl {
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: []))
var attributes: [MessageAttribute] = []
var text = ""
if let caption {
text = caption.string
let entities = generateTextEntities(text, enabledTypes: .all, currentEntities: generateChatInputTextEntities(caption))
if !entities.isEmpty {
attributes.append(TextEntitiesMessageAttribute(entities: entities))
}
}
for mediaReference in mediaReferences {
if messages.count == 10 {
groupingKey = Int64.random(in: .min ..< .max)
}
let isLast = mediaReference == mediaReferences.last
messages.append(.message(text: isLast ? text : "", attributes: isLast ? attributes : [], inlineStickers: [:], mediaReference: mediaReference, threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: groupingKey, correlationId: nil, bubbleUpEmojiOrStickersets: []))
}
messages = self.transformEnqueueMessages(messages, silentPosting: silentPosting, scheduleTime: scheduleTime, repeatPeriod: nil, postpone: false)
self.presentPaidMessageAlertIfNeeded(completion: { [weak self] postpone in
self?.sendMessages(messages, media: true, postpone: postpone)
})
@ -413,8 +430,8 @@ extension ChatControllerImpl {
let controller = strongSelf.context.sharedContext.makeAttachmentFileController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, audio: true, bannedSendMedia: bannedSendFiles, presentGallery: {
}, presentFiles: { [weak self, weak attachmentController] in
attachmentController?.dismiss(animated: true)
self?.presentICloudFileGallery()
}, presentDocumentScanner: nil, send: { [weak self] mediaReferences in
self?.presentICloudFileGallery(documentTypes: ["public.mp3", "public.mpeg-4-audio", "public.aac-audio", "org.xiph.flac"])
}, presentDocumentScanner: nil, send: { [weak self] mediaReferences, silentPosting, scheduleTime, caption in
guard let self else {
return
}
@ -423,9 +440,26 @@ extension ChatControllerImpl {
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: []))
var attributes: [MessageAttribute] = []
var text = ""
if let caption {
text = caption.string
let entities = generateTextEntities(text, enabledTypes: .all, currentEntities: generateChatInputTextEntities(caption))
if !entities.isEmpty {
attributes.append(TextEntitiesMessageAttribute(entities: entities))
}
}
for mediaReference in mediaReferences {
if messages.count == 10 {
groupingKey = Int64.random(in: .min ..< .max)
}
let isLast = mediaReference == mediaReferences.last
messages.append(.message(text: isLast ? text : "", attributes: isLast ? attributes : [], inlineStickers: [:], mediaReference: mediaReference, threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: groupingKey, correlationId: nil, bubbleUpEmojiOrStickersets: []))
}
messages = self.transformEnqueueMessages(messages, silentPosting: silentPosting, scheduleTime: scheduleTime, repeatPeriod: nil, postpone: false)
self.presentPaidMessageAlertIfNeeded(completion: { [weak self] postpone in
self?.sendMessages(messages, media: true, postpone: postpone)
})
@ -1154,7 +1188,7 @@ extension ChatControllerImpl {
})
}
func presentICloudFileGallery(editingMessage: Bool = false) {
func presentICloudFileGallery(editingMessage: Bool = false, documentTypes: [String] = ["public.item"]) {
let _ = (self.context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId),
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false),
@ -1167,7 +1201,7 @@ extension ChatControllerImpl {
let (accountPeer, limits, premiumLimits) = result
let isPremium = accountPeer?.isPremium ?? false
strongSelf.present(legacyICloudFilePicker(theme: strongSelf.presentationData.theme, completion: { [weak self] urls in
strongSelf.present(legacyICloudFilePicker(theme: strongSelf.presentationData.theme, documentTypes: documentTypes, completion: { [weak self] urls in
if let strongSelf = self, !urls.isEmpty {
var signals: [Signal<ICloudFileDescription?, NoError>] = []
for url in urls {
@ -2096,6 +2130,7 @@ extension ChatControllerImpl {
isClosed: false,
deadlineTimeout: poll.deadlineTimeout,
deadlineDate: poll.deadlineDate,
pollHash: 0,
openAnswers: poll.openAnswers,
revotingDisabled: poll.revotingDisabled,
shuffleAnswers: poll.shuffleAnswers,

View file

@ -247,7 +247,7 @@ func chatHistoryEntriesForView(
if let messageGroupingKey = message.groupingKey {
let selection: ChatHistoryMessageSelection
if let selectedMessages = selectedMessages {
selection = .selectable(selected: selectedMessages.contains(message.id))
selection = .selectable(selected: selectedMessages.contains(message.id), num: nil)
} else {
selection = .none
}
@ -293,7 +293,7 @@ func chatHistoryEntriesForView(
} else {
let selection: ChatHistoryMessageSelection
if let selectedMessages = selectedMessages {
selection = .selectable(selected: selectedMessages.contains(message.id))
selection = .selectable(selected: selectedMessages.contains(message.id), num: nil)
} else {
selection = .none
}
@ -308,7 +308,7 @@ func chatHistoryEntriesForView(
} else {
let selection: ChatHistoryMessageSelection
if let selectedMessages = selectedMessages {
selection = .selectable(selected: selectedMessages.contains(message.id))
selection = .selectable(selected: selectedMessages.contains(message.id), num: nil)
} else {
selection = .none
}

View file

@ -305,7 +305,7 @@ final class ContactSelectionControllerNode: ASDisplayNode {
let topEdgeEffectHeight: CGFloat = 80.0
let topEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: topEdgeEffectHeight))
transition.updateFrame(view: self.topEdgeEffectView, frame: topEdgeEffectFrame)
self.topEdgeEffectView.update(content: self.presentationData.theme.list.blocksBackgroundColor, blur: true, alpha: 1.0, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: ComponentTransition(transition))
self.topEdgeEffectView.update(content: self.presentationData.theme.list.modalBlocksBackgroundColor, blur: true, alpha: 1.0, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: ComponentTransition(transition))
let bottomEdgeEffectHeight: CGFloat = 88.0
let bottomEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomEdgeEffectHeight - layout.additionalInsets.bottom), size: CGSize(width: layout.size.width, height: bottomEdgeEffectHeight))

View file

@ -81,7 +81,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
self.playlistLocation = playlistLocation
self.getParentController = getParentController
if let playlistLocation = playlistLocation as? PeerMessagesPlaylistLocation, case let .custom(messages, canReorder, at, loadMore) = playlistLocation.effectiveLocation(context: context) {
if let playlistLocation = playlistLocation as? PeerMessagesPlaylistLocation, case let .custom(messages, canReorder, at, loadMore, _) = playlistLocation.effectiveLocation(context: context) {
self.source = .custom(messages: messages, messageId: at, quote: nil, isSavedMusic: true, canReorder: canReorder, loadMore: loadMore)
self.isGlobalSearch = false
} else {
@ -372,8 +372,8 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
if let strongSelf = self, strongSelf.isNodeLoaded, let message = strongSelf.historyNode.messageInCurrentHistoryView(id) {
var playlistLocation: PeerMessagesPlaylistLocation?
if let location = strongSelf.playlistLocation as? PeerMessagesPlaylistLocation {
if case let .custom(messages, canReorder, _, loadMore) = location {
playlistLocation = .custom(messages: messages, canReorder: canReorder, at: id, loadMore: loadMore)
if case let .custom(messages, canReorder, _, loadMore, hidePanel) = location {
playlistLocation = .custom(messages: messages, canReorder: canReorder, at: id, loadMore: loadMore, hidePanel: hidePanel)
} else if case let .savedMusic(context, _, canReorder) = location {
playlistLocation = .savedMusic(context: context, at: id.id, canReorder: canReorder)
}

View file

@ -2751,8 +2751,8 @@ 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 {
return makeAttachmentFileControllerImpl(context: context, updatedPresentationData: updatedPresentationData, mode: audio ? .audio : .recent, bannedSendMedia: bannedSendMedia, presentGallery: presentGallery, presentFiles: presentFiles, presentDocumentScanner: presentDocumentScanner, send: send)
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], Bool, Int32?, NSAttributedString?) -> Void) -> AttachmentFileController {
return makeAttachmentFileControllerImpl(context: context, updatedPresentationData: updatedPresentationData, mode: audio ? .audio(story: false) : .recent, bannedSendMedia: bannedSendMedia, presentGallery: presentGallery, presentFiles: presentFiles, presentDocumentScanner: presentDocumentScanner, send: send)
}
public func makeGalleryCaptionPanelView(context: AccountContext, chatLocation: ChatLocation, isScheduledMessages: Bool, isFile: Bool, hasTimer: Bool, customEmojiAvailable: Bool, present: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void) -> NSObject? {