mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge commit '87409ead6c'
This commit is contained in:
commit
19da3fa55a
75 changed files with 1293 additions and 466 deletions
|
|
@ -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$@";
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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?()
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/SearchInputPanelComponent",
|
||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||
"//submodules/CounterControllerTitleView",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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] {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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") {
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/ComposePollScreen",
|
||||
"//submodules/Markdown",
|
||||
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
|
||||
"//submodules/TelegramUI/Components/EdgeEffect",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ swift_library(
|
|||
"//submodules/UIKitRuntimeUtils",
|
||||
"//submodules/UndoUI",
|
||||
"//submodules/TelegramUI/Components/LensTransition",
|
||||
"//submodules/Components/BalancedTextComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
12
submodules/TelegramUI/Images.xcassets/Chat/Message/PollReplyIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Message/PollReplyIcon.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "replypoll.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Chat/Message/PollReplyIcon.imageset/replypoll.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Message/PollReplyIcon.imageset/replypoll.pdf
vendored
Normal file
Binary file not shown.
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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? {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue