mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-05 19:28:46 +02:00
Various improvements
This commit is contained in:
parent
9110dda4b4
commit
66d08c0e1b
35 changed files with 711 additions and 230 deletions
|
|
@ -1384,7 +1384,6 @@ 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 {
|
||||
|
|
@ -1405,6 +1404,7 @@ 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)
|
||||
|
||||
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() {
|
||||
|
|
|
|||
|
|
@ -1018,6 +1018,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
|||
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 +1026,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 +1489,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 = (
|
||||
|
|
@ -1702,40 +1723,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 +1772,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)
|
||||
|
|
@ -1869,8 +1857,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 +1941,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 +1965,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 +1980,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 +1998,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 +2015,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 +2035,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 +2095,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 +2129,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,
|
||||
|
|
@ -2433,7 +2434,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 +2475,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 && topAccessoryHeight > 0.0 {
|
||||
bounds.size.height -= self.buttonSize.height
|
||||
}
|
||||
var mediaAccessoryPanelFrame: CGRect?
|
||||
if case .glass = self.panelStyle {
|
||||
let backgroundView: GlassBackgroundView
|
||||
|
|
@ -2517,9 +2521,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 && 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 +2719,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 {
|
||||
|
|
|
|||
|
|
@ -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)?
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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?()
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
||||
|
|
@ -434,6 +434,8 @@ public class AttachmentFileControllerImpl: ItemListController, AttachmentFileCon
|
|||
fileprivate var caption: NSAttributedString?
|
||||
fileprivate var selectionCount = ValuePromise<Int>(0)
|
||||
|
||||
fileprivate var bottomEdgeColor: UIColor = .clear
|
||||
|
||||
fileprivate var mulitpleCompletion: ((AttachmentMediaPickerSendMode, AttachmentMediaPickerAttachmentMode, ChatSendMessageActionSheetController.SendParameters?) -> Void)?
|
||||
|
||||
var delayDisappear = false
|
||||
|
|
@ -441,6 +443,7 @@ public class AttachmentFileControllerImpl: ItemListController, AttachmentFileCon
|
|||
var hasBottomEdgeEffect = true
|
||||
|
||||
var resetForReuseImpl: () -> Void = {}
|
||||
var onDismissImpl: () -> Void = {}
|
||||
public func resetForReuse() {
|
||||
self.resetForReuseImpl()
|
||||
self.scrollToTop?()
|
||||
|
|
@ -451,12 +454,23 @@ 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 {
|
||||
if self.hasBottomEdgeEffect {
|
||||
self.onDismissImpl()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public var mediaPickerContext: AttachmentMediaPickerContext? {
|
||||
return AttachmentFileContext(controller: self)
|
||||
}
|
||||
|
||||
private var topEdgeEffectView: EdgeEffectView?
|
||||
private var bottomEdgeEffectView: EdgeEffectView?
|
||||
|
||||
var isSearching: Bool = false {
|
||||
|
|
@ -468,21 +482,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 +497,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()
|
||||
}
|
||||
|
|
@ -521,13 +521,32 @@ private func messageSelectionState(state: AttachmentFileControllerState, message
|
|||
|
||||
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,
|
||||
|
|
@ -549,10 +568,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()
|
||||
},
|
||||
|
|
@ -603,6 +632,8 @@ public func makeAttachmentFileControllerImpl(
|
|||
}
|
||||
},
|
||||
toggleMediaPlayback: { message in
|
||||
didPreviewAudio = true
|
||||
|
||||
let playlistLocation: PeerMessagesPlaylistLocation = .custom(messages: .single(([message], 0, false)), canReorder: false, at: message.id, loadMore: nil)
|
||||
context.sharedContext.mediaManager.setPlaylist((context, PeerMessagesMediaPlaylist(context: context, location: playlistLocation, chatLocationContextHolder: nil)), type: .music, control: .playback(.togglePlayPause))
|
||||
},
|
||||
|
|
@ -727,7 +758,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 +854,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 +923,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,7 +932,6 @@ public func makeAttachmentFileControllerImpl(
|
|||
}
|
||||
|
||||
let controller = AttachmentFileControllerImpl(context: context, state: signal, hideNavigationBarBackground: true)
|
||||
|
||||
controller.mulitpleCompletion = { _, _, _ in
|
||||
let _ = stateValue.with({ state in
|
||||
if let selectedMessageIds = state.selectedMessageIds {
|
||||
|
|
@ -928,12 +971,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 +1006,9 @@ public func makeAttachmentFileControllerImpl(
|
|||
presentInGlobalOverlayImpl = { [weak controller] c in
|
||||
controller?.presentInGlobalOverlay(c)
|
||||
}
|
||||
updateBottomColorImpl = { [weak controller] color in
|
||||
controller?.bottomEdgeColor = color
|
||||
}
|
||||
return controller
|
||||
}
|
||||
|
||||
|
|
@ -962,15 +1021,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
|
||||
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):
|
||||
|
|
@ -661,6 +660,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))
|
||||
|
|
@ -1009,6 +1009,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 +1036,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
|
||||
|
|
|
|||
|
|
@ -1368,6 +1368,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 +1450,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 +2159,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,6 +2191,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
presentPollAttachmentScreen(
|
||||
context: item.context,
|
||||
updatedPresentationData: item.controllerInteraction.updatedPresentationData,
|
||||
subject: .option,
|
||||
availableButtons: [.gallery, .sticker, .emoji, .location],
|
||||
present: { [weak item] controller in
|
||||
item?.controllerInteraction.navigationController()?.pushViewController(controller)
|
||||
|
|
@ -2291,6 +2298,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
|
||||
|
|
@ -2932,6 +2941,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
|
||||
|
|
|
|||
|
|
@ -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,8 @@ swift_library(
|
|||
"//submodules/Components/PagerComponent",
|
||||
"//submodules/FeaturedStickersScreen",
|
||||
"//submodules/TelegramNotices",
|
||||
"//submodules/StickerPeekUI:StickerPeekUI",
|
||||
"//submodules/StickerPeekUI",
|
||||
"//submodules/CounterControllerTitleView",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -851,9 +851,26 @@ final class ComposePollScreenComponent: Component {
|
|||
availableButtons = [.gallery, .sticker, .emoji, .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 +1746,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] = []
|
||||
|
|
@ -2813,7 +2836,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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
|
@ -3565,6 +3565,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
|
||||
|
||||
|
|
|
|||
|
|
@ -413,7 +413,7 @@ 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()
|
||||
self?.presentICloudFileGallery(documentTypes: ["public.mp3", "public.mpeg-4-audio", "public.aac-audio", "org.xiph.flac"])
|
||||
}, presentDocumentScanner: nil, send: { [weak self] mediaReferences in
|
||||
guard let self else {
|
||||
return
|
||||
|
|
@ -1154,7 +1154,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 +1167,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 {
|
||||
|
|
|
|||
|
|
@ -2751,7 +2751,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
}
|
||||
|
||||
public func makeAttachmentFileController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, audio: Bool, bannedSendMedia: (Int32, Bool)?, presentGallery: @escaping () -> Void, presentFiles: @escaping () -> Void, presentDocumentScanner: (() -> Void)?, send: @escaping ([AnyMediaReference]) -> Void) -> AttachmentFileController {
|
||||
return makeAttachmentFileControllerImpl(context: context, updatedPresentationData: updatedPresentationData, mode: audio ? .audio : .recent, bannedSendMedia: bannedSendMedia, presentGallery: presentGallery, presentFiles: presentFiles, presentDocumentScanner: presentDocumentScanner, send: send)
|
||||
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