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 containerInsets = containerLayout.intrinsicInsets
|
||||||
var hasPanel = false
|
var hasPanel = false
|
||||||
// let previousHasButton = self.hasButton
|
|
||||||
let hasButton = self.panel.isButtonVisible && !self.isDismissing
|
let hasButton = self.panel.isButtonVisible && !self.isDismissing
|
||||||
self.hasButton = hasButton
|
self.hasButton = hasButton
|
||||||
if let controller = self.controller, controller.buttons.count > 1 || controller.hasTextInput || self.panel.hasMediaAccessoryPanel {
|
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 isEffecitvelyCollapsedUpdated = (self.selectionCount > 0) != (self.panel.isSelecting)
|
||||||
let panelHeight = self.panel.update(layout: containerLayout, buttons: self.controller?.buttons ?? [], isSelecting: self.selectionCount > 0, selectionCount: self.selectionCount, elevateProgress: !hasPanel && !hasButton, transition: transition)
|
let panelHeight = self.panel.update(layout: containerLayout, buttons: self.controller?.buttons ?? [], isSelecting: self.selectionCount > 0, selectionCount: self.selectionCount, elevateProgress: !hasPanel && !hasButton, transition: transition)
|
||||||
|
|
||||||
if hasPanel || hasButton {
|
if hasPanel || hasButton {
|
||||||
containerInsets.bottom = panelHeight + panelOffset
|
containerInsets.bottom = panelHeight + panelOffset
|
||||||
}
|
}
|
||||||
|
|
@ -1526,7 +1526,7 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||||
public var forceSourceRect = false
|
public var forceSourceRect = false
|
||||||
|
|
||||||
fileprivate var isStandalone: Bool {
|
fileprivate var isStandalone: Bool {
|
||||||
return self.buttons.contains(.standalone)
|
return self.buttons.contains(.standalone) || self.buttons.count == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
public func convertToStandalone() {
|
public func convertToStandalone() {
|
||||||
|
|
|
||||||
|
|
@ -1018,6 +1018,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
||||||
private var elevateProgress: Bool = false
|
private var elevateProgress: Bool = false
|
||||||
private var buttons: [AttachmentButtonType] = []
|
private var buttons: [AttachmentButtonType] = []
|
||||||
private var selectedIndex: Int = 0
|
private var selectedIndex: Int = 0
|
||||||
|
private var selectionOverrideIndex: Int?
|
||||||
private(set) var isSelecting: Bool = false
|
private(set) var isSelecting: Bool = false
|
||||||
private var selectionCount: Int = 0
|
private var selectionCount: Int = 0
|
||||||
|
|
||||||
|
|
@ -1025,12 +1026,30 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
||||||
var isButtonVisible: Bool {
|
var isButtonVisible: Bool {
|
||||||
return self.mainButtonState.isVisible || self.secondaryButtonState.isVisible
|
return self.mainButtonState.isVisible || self.secondaryButtonState.isVisible
|
||||||
}
|
}
|
||||||
var hasMediaAccessoryPanel: Bool {
|
private var peerMessagesPlaylistLocation: PeerMessagesPlaylistLocation? {
|
||||||
if case .glass = self.panelStyle {
|
return self.playlistLocation as? PeerMessagesPlaylistLocation
|
||||||
return self.playlistStateAndType != nil
|
}
|
||||||
} else {
|
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
|
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?
|
private var validLayout: ContainerViewLayout?
|
||||||
|
|
@ -1470,11 +1489,13 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let hadMediaAccessoryPanel = strongSelf.hasMediaAccessoryPanel
|
let hadMediaAccessoryPanel = strongSelf.hasMediaAccessoryPanel
|
||||||
|
let updatedPeerMessagesPlaylistLocation = playlistStateAndType?.1.playlistLocation as? PeerMessagesPlaylistLocation
|
||||||
if !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.0, playlistStateAndType?.1.item) ||
|
if !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.0, playlistStateAndType?.1.item) ||
|
||||||
!arePlaylistItemsEqual(strongSelf.playlistStateAndType?.1, playlistStateAndType?.1.previousItem) ||
|
!arePlaylistItemsEqual(strongSelf.playlistStateAndType?.1, playlistStateAndType?.1.previousItem) ||
|
||||||
!arePlaylistItemsEqual(strongSelf.playlistStateAndType?.2, playlistStateAndType?.1.nextItem) ||
|
!arePlaylistItemsEqual(strongSelf.playlistStateAndType?.2, playlistStateAndType?.1.nextItem) ||
|
||||||
strongSelf.playlistStateAndType?.3 != playlistStateAndType?.1.order ||
|
strongSelf.playlistStateAndType?.3 != playlistStateAndType?.1.order ||
|
||||||
strongSelf.playlistStateAndType?.4 != playlistStateAndType?.2 {
|
strongSelf.playlistStateAndType?.4 != playlistStateAndType?.2 ||
|
||||||
|
strongSelf.peerMessagesPlaylistLocation != updatedPeerMessagesPlaylistLocation {
|
||||||
|
|
||||||
if let playlistStateAndType {
|
if let playlistStateAndType {
|
||||||
strongSelf.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 {
|
private func makeMediaAccessoryPanel() -> MediaNavigationAccessoryPanel {
|
||||||
let mediaAccessoryPanel = MediaNavigationAccessoryPanel(context: self.context, presentationData: self.presentationData, displayBackground: false, customTintColor: self.presentationData.theme.rootController.tabBar.textColor)
|
let mediaAccessoryPanel = MediaNavigationAccessoryPanel(context: self.context, presentationData: self.presentationData, displayBackground: false, customTintColor: self.presentationData.theme.rootController.tabBar.textColor)
|
||||||
mediaAccessoryPanel.getController = { [weak self] in
|
mediaAccessoryPanel.getController = { [weak self] in
|
||||||
|
|
@ -1784,14 +1772,14 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
||||||
self.context.sharedContext.mediaManager.playlistControl(.previous, type: type)
|
self.context.sharedContext.mediaManager.playlistControl(.previous, type: type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mediaAccessoryPanel.tapAction = { [weak self] in
|
mediaAccessoryPanel.tapAction = {
|
||||||
self?.openCurrentMusicPlayer()
|
|
||||||
}
|
}
|
||||||
return mediaAccessoryPanel
|
return mediaAccessoryPanel
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateMediaAccessoryPanel(frame: CGRect?, transition: ContainedViewLayoutTransition) {
|
private func updateMediaAccessoryPanel(frame: CGRect?, transition: ContainedViewLayoutTransition) {
|
||||||
guard case .glass = self.panelStyle,
|
guard case .glass = self.panelStyle,
|
||||||
|
self.hasMediaAccessoryPanel,
|
||||||
let frame,
|
let frame,
|
||||||
let (item, previousItem, nextItem, order, type, _) = self.playlistStateAndType else {
|
let (item, previousItem, nextItem, order, type, _) = self.playlistStateAndType else {
|
||||||
self.dismissMediaAccessoryPanel(transition: transition)
|
self.dismissMediaAccessoryPanel(transition: transition)
|
||||||
|
|
@ -1869,8 +1857,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateSelectedIndex(_ index: Int) {
|
func updateSelectedIndex(_ index: Int) {
|
||||||
|
let hadMediaAccessoryPanel = self.hasMediaAccessoryPanel
|
||||||
self.selectedIndex = index
|
self.selectedIndex = index
|
||||||
self.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring)))
|
self.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring)))
|
||||||
|
if hadMediaAccessoryPanel != self.hasMediaAccessoryPanel {
|
||||||
|
self.requestLayout(transition: .immediate)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var buttonSize: CGSize {
|
var buttonSize: CGSize {
|
||||||
|
|
@ -1949,6 +1941,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
||||||
}
|
}
|
||||||
let button = self.buttons[index]
|
let button = self.buttons[index]
|
||||||
self.skipLensUpdate = true
|
self.skipLensUpdate = true
|
||||||
|
self.selectionOverrideIndex = index
|
||||||
if self.selectionChanged(button) {
|
if self.selectionChanged(button) {
|
||||||
self.selectedIndex = index
|
self.selectedIndex = index
|
||||||
if self.buttons.count > 5, let button = self.itemViews[button.key] {
|
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.updateItemContainers(contentOffset: newBounds.minX, transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.selectionOverrideIndex = nil
|
||||||
self.skipLensUpdate = false
|
self.skipLensUpdate = false
|
||||||
}
|
}
|
||||||
self.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
|
self.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
|
||||||
|
|
@ -1986,8 +1980,13 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var buttons = self.buttons
|
||||||
|
if buttons.count == 1 {
|
||||||
|
buttons.removeAll()
|
||||||
|
}
|
||||||
|
|
||||||
var width = layout.size.width
|
var width = layout.size.width
|
||||||
if self.buttons.count == 3 {
|
if buttons.count == 3 {
|
||||||
width = smallPanelWidth
|
width = smallPanelWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1999,11 +1998,11 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
||||||
panelSideInset = 3.0
|
panelSideInset = 3.0
|
||||||
}
|
}
|
||||||
|
|
||||||
var distanceBetweenNodes = floorToScreenPixels((width - panelSideInset * 2.0 - self.buttonSize.width) / CGFloat(max(1, self.buttons.count - 1)))
|
var distanceBetweenNodes = floorToScreenPixels((width - panelSideInset * 2.0 - self.buttonSize.width) / CGFloat(max(1, buttons.count - 1)))
|
||||||
if self.buttons.count == 3 {
|
if buttons.count == 3 {
|
||||||
distanceBetweenNodes = floorToScreenPixels((width - panelSideInset * 2.0 - 32.0) / CGFloat(max(1, self.buttons.count - 1)))
|
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 buttonWidth = self.buttonSize.width
|
||||||
var leftNodeOriginX: CGFloat
|
var leftNodeOriginX: CGFloat
|
||||||
var maxButtonsToFit = 5
|
var maxButtonsToFit = 5
|
||||||
|
|
@ -2016,11 +2015,11 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
||||||
case .legacy:
|
case .legacy:
|
||||||
leftNodeOriginX = (width - internalWidth) / 2.0
|
leftNodeOriginX = (width - internalWidth) / 2.0
|
||||||
}
|
}
|
||||||
if self.buttons.count == 3 {
|
if buttons.count == 3 {
|
||||||
leftNodeOriginX = floor((layout.size.width - width) / 2.0) + 16.0
|
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 {
|
switch self.panelStyle {
|
||||||
case .glass:
|
case .glass:
|
||||||
buttonWidth = smallGlassButtonSize.width
|
buttonWidth = smallGlassButtonSize.width
|
||||||
|
|
@ -2036,12 +2035,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
||||||
|
|
||||||
var selectionFrame = CGRect()
|
var selectionFrame = CGRect()
|
||||||
var mostRightX = 0.0
|
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 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))
|
let buttonFrame = CGRect(origin: CGPoint(x: originX, y: -3.0), size: CGSize(width: buttonWidth, height: self.buttonSize.height))
|
||||||
mostRightX = buttonFrame.maxX
|
mostRightX = buttonFrame.maxX
|
||||||
|
|
||||||
let type = self.buttons[i]
|
let type = buttons[i]
|
||||||
let _ = validIds.insert(type.key)
|
let _ = validIds.insert(type.key)
|
||||||
|
|
||||||
var buttonTransition = transition
|
var buttonTransition = transition
|
||||||
|
|
@ -2096,20 +2095,22 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
||||||
context: self.context,
|
context: self.context,
|
||||||
style: self.panelStyle == .glass ? .glass : .legacy,
|
style: self.panelStyle == .glass ? .glass : .legacy,
|
||||||
type: type,
|
type: type,
|
||||||
isFirstOrLast: i == 0 || i == self.buttons.count - 1,
|
isFirstOrLast: i == 0 || i == buttons.count - 1,
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
strings: self.presentationData.strings,
|
strings: self.presentationData.strings,
|
||||||
theme: self.presentationData.theme,
|
theme: self.presentationData.theme,
|
||||||
action: { [weak self] in
|
action: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
strongSelf.selectionOverrideIndex = i
|
||||||
if strongSelf.selectionChanged(type) {
|
if strongSelf.selectionChanged(type) {
|
||||||
strongSelf.selectedIndex = i
|
strongSelf.selectedIndex = i
|
||||||
strongSelf.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring)))
|
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.scrollNode.view.scrollRectToVisible(button.frame.insetBy(dx: -35.0, dy: 0.0), animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
strongSelf.selectionOverrideIndex = nil
|
||||||
}
|
}
|
||||||
}, longPressAction: { [weak self] in
|
}, longPressAction: { [weak self] in
|
||||||
if let strongSelf = self, i == strongSelf.selectedIndex {
|
if let strongSelf = self, i == strongSelf.selectedIndex {
|
||||||
|
|
@ -2128,7 +2129,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
||||||
context: self.context,
|
context: self.context,
|
||||||
style: self.panelStyle == .glass ? .glass : .legacy,
|
style: self.panelStyle == .glass ? .glass : .legacy,
|
||||||
type: type,
|
type: type,
|
||||||
isFirstOrLast: i == 0 || i == self.buttons.count - 1,
|
isFirstOrLast: i == 0 || i == buttons.count - 1,
|
||||||
isSelected: true,
|
isSelected: true,
|
||||||
strings: self.presentationData.strings,
|
strings: self.presentationData.strings,
|
||||||
theme: self.presentationData.theme,
|
theme: self.presentationData.theme,
|
||||||
|
|
@ -2433,7 +2434,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
||||||
}
|
}
|
||||||
|
|
||||||
let topAccessoryHeight: CGFloat
|
let topAccessoryHeight: CGFloat
|
||||||
if case .glass = self.panelStyle, self.playlistStateAndType != nil {
|
if self.hasMediaAccessoryPanel {
|
||||||
topAccessoryHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight
|
topAccessoryHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight
|
||||||
} else {
|
} else {
|
||||||
topAccessoryHeight = 0.0
|
topAccessoryHeight = 0.0
|
||||||
|
|
@ -2474,7 +2475,10 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
||||||
self.updateViews(transition: .immediate)
|
self.updateViews(transition: .immediate)
|
||||||
|
|
||||||
let glassPanelHeight: CGFloat = 62.0
|
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?
|
var mediaAccessoryPanelFrame: CGRect?
|
||||||
if case .glass = self.panelStyle {
|
if case .glass = self.panelStyle {
|
||||||
let backgroundView: GlassBackgroundView
|
let backgroundView: GlassBackgroundView
|
||||||
|
|
@ -2517,9 +2521,19 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
||||||
}
|
}
|
||||||
|
|
||||||
let basePanelHeight = isSelecting ? max(0.0, textPanelHeight - 11.0) : glassPanelHeight
|
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)
|
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))
|
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)))
|
containerTransition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: UIScreenPixel)))
|
||||||
|
|
||||||
if case .glass = self.panelStyle {
|
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 {
|
if let progress = self.loadingProgress {
|
||||||
|
|
|
||||||
|
|
@ -2380,6 +2380,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||||
var currentStoryIcon: UIImage?
|
var currentStoryIcon: UIImage?
|
||||||
var currentGiftIcon: UIImage?
|
var currentGiftIcon: UIImage?
|
||||||
var currentLocationIcon: UIImage?
|
var currentLocationIcon: UIImage?
|
||||||
|
var currentPollIcon: UIImage?
|
||||||
|
|
||||||
var selectableControlSizeAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)?
|
var selectableControlSizeAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)?
|
||||||
var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool, ContainedViewLayoutTransition) -> ItemListEditableReorderControlNode)?
|
var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool, ContainedViewLayoutTransition) -> ItemListEditableReorderControlNode)?
|
||||||
|
|
@ -2555,6 +2556,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||||
var displayStoryReplyIcon = false
|
var displayStoryReplyIcon = false
|
||||||
var displayGiftIcon = false
|
var displayGiftIcon = false
|
||||||
var displayLocationIcon = false
|
var displayLocationIcon = false
|
||||||
|
var displayPollIcon = false
|
||||||
var ignoreForwardedIcon = false
|
var ignoreForwardedIcon = false
|
||||||
|
|
||||||
switch contentData {
|
switch contentData {
|
||||||
|
|
@ -2867,7 +2869,9 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||||
displayStoryReplyIcon = true
|
displayStoryReplyIcon = true
|
||||||
} else {
|
} else {
|
||||||
for media in message.media {
|
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
|
displayLocationIcon = true
|
||||||
} else if let action = media as? TelegramMediaAction {
|
} else if let action = media as? TelegramMediaAction {
|
||||||
switch action.action {
|
switch action.action {
|
||||||
|
|
@ -3048,6 +3052,10 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||||
currentLocationIcon = PresentationResourcesChatList.locationIcon(item.presentationData.theme)
|
currentLocationIcon = PresentationResourcesChatList.locationIcon(item.presentationData.theme)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if displayPollIcon {
|
||||||
|
currentPollIcon = PresentationResourcesChatList.pollIcon(item.presentationData.theme)
|
||||||
|
}
|
||||||
|
|
||||||
if let currentForwardedIcon {
|
if let currentForwardedIcon {
|
||||||
textLeftCutout += currentForwardedIcon.size.width
|
textLeftCutout += currentForwardedIcon.size.width
|
||||||
if !contentImageSpecs.isEmpty {
|
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 {
|
for i in 0 ..< contentImageSpecs.count {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
textLeftCutout += contentImageSpacing
|
textLeftCutout += contentImageSpacing
|
||||||
|
|
@ -4745,6 +4762,9 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||||
} else if let currentLocationIcon {
|
} else if let currentLocationIcon {
|
||||||
messageTypeIcon = currentLocationIcon
|
messageTypeIcon = currentLocationIcon
|
||||||
messageTypeIconOffset.y -= 2.0 - UIScreenPixel
|
messageTypeIconOffset.y -= 2.0 - UIScreenPixel
|
||||||
|
} else if let currentPollIcon {
|
||||||
|
messageTypeIcon = currentPollIcon
|
||||||
|
messageTypeIconOffset.y -= 2.0 - UIScreenPixel
|
||||||
}
|
}
|
||||||
|
|
||||||
if let messageTypeIcon {
|
if let messageTypeIcon {
|
||||||
|
|
|
||||||
|
|
@ -395,15 +395,13 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
|
||||||
messageText = text
|
messageText = text
|
||||||
}
|
}
|
||||||
case let poll as TelegramMediaPoll:
|
case let poll as TelegramMediaPoll:
|
||||||
let pollPrefix = "📊 "
|
messageText = poll.text
|
||||||
let entityOffset = (pollPrefix as NSString).length
|
|
||||||
messageText = "\(pollPrefix)\(poll.text)"
|
|
||||||
for entity in poll.textEntities {
|
for entity in poll.textEntities {
|
||||||
if case let .CustomEmoji(_, fileId) = entity.type {
|
if case let .CustomEmoji(_, fileId) = entity.type {
|
||||||
if customEmojiRanges == nil {
|
if customEmojiRanges == nil {
|
||||||
customEmojiRanges = []
|
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)
|
let attribute = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: message.associatedMedia[EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile)
|
||||||
customEmojiRanges?.append((range, attribute))
|
customEmojiRanges?.append((range, attribute))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,18 @@ public final class Text: Component {
|
||||||
self.color = color
|
self.color = color
|
||||||
self.tintColor = tintColor
|
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 {
|
public static func ==(lhs: Text, rhs: Text) -> Bool {
|
||||||
if lhs.text != rhs.text {
|
if lhs.text != rhs.text {
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ public final class CounterControllerTitleView: UIView {
|
||||||
|
|
||||||
private var primaryTextColor: UIColor?
|
private var primaryTextColor: UIColor?
|
||||||
private var secondaryTextColor: UIColor?
|
private var secondaryTextColor: UIColor?
|
||||||
|
private let verticalOffset: CGFloat
|
||||||
|
|
||||||
private var nextLayoutTransition: ContainedViewLayoutTransition?
|
private var nextLayoutTransition: ContainedViewLayoutTransition?
|
||||||
|
|
||||||
|
|
@ -65,7 +66,7 @@ public final class CounterControllerTitleView: UIView {
|
||||||
let secondaryTextColor = self.secondaryTextColor ?? self.theme.rootController.navigationBar.secondaryTextColor
|
let secondaryTextColor = self.secondaryTextColor ?? self.theme.rootController.navigationBar.secondaryTextColor
|
||||||
self.titleNode.attributedText = NSAttributedString(string: self.title.title, font: Font.semibold(17.0), textColor: primaryTextColor)
|
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 previousSubtitleText = self.subtitleNode.attributedText, previousSubtitleText.string.isEmpty != subtitleText.string.isEmpty && subtitleText.string.isEmpty {
|
||||||
if let disappearingSubtitleNode = self.disappearingSubtitleNode {
|
if let disappearingSubtitleNode = self.disappearingSubtitleNode {
|
||||||
self.disappearingSubtitleNode = nil
|
self.disappearingSubtitleNode = nil
|
||||||
|
|
@ -94,8 +95,9 @@ public final class CounterControllerTitleView: UIView {
|
||||||
self.setNeedsLayout()
|
self.setNeedsLayout()
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(theme: PresentationTheme) {
|
public init(theme: PresentationTheme, verticalOffset: CGFloat = 0.0) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
self.verticalOffset = verticalOffset
|
||||||
|
|
||||||
self.titleNode = ImmediateTextNode()
|
self.titleNode = ImmediateTextNode()
|
||||||
self.titleNode.displaysAsynchronously = false
|
self.titleNode.displaysAsynchronously = false
|
||||||
|
|
@ -126,7 +128,7 @@ public final class CounterControllerTitleView: UIView {
|
||||||
super.layoutSubviews()
|
super.layoutSubviews()
|
||||||
|
|
||||||
let size = self.bounds.size
|
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 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))
|
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
|
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)
|
self.titleNode.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
|
||||||
transition.updatePosition(node: self.titleNode, position: titleFrame.center)
|
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)
|
self.subtitleNode.bounds = CGRect(origin: CGPoint(), size: subtitleFrame.size)
|
||||||
transition.updatePosition(node: self.subtitleNode, position: subtitleFrame.center)
|
transition.updatePosition(node: self.subtitleNode, position: subtitleFrame.center)
|
||||||
transition.updateTransformScale(node: self.subtitleNode, scale: self.title.counter != nil ? 1.0 : 0.001)
|
transition.updateTransformScale(node: self.subtitleNode, scale: self.title.counter != nil ? 1.0 : 0.001)
|
||||||
|
|
|
||||||
|
|
@ -2149,7 +2149,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||||
if isActive {
|
if isActive {
|
||||||
self.setLivePhotoVideoVisible(true, animated: animated)
|
self.setLivePhotoVideoVisible(true, animated: animated)
|
||||||
videoNode.seek(0.0)
|
videoNode.seek(0.0)
|
||||||
videoNode.play()
|
videoNode.playOnceWithSound(playAndRecord: false)
|
||||||
} else {
|
} else {
|
||||||
videoNode.pause()
|
videoNode.pause()
|
||||||
videoNode.seek(0.0)
|
videoNode.seek(0.0)
|
||||||
|
|
|
||||||
|
|
@ -699,14 +699,14 @@ private final class ItemListTextWithSubtitleTitleView: UIView, NavigationBarTitl
|
||||||
self.titleNode.maximumNumberOfLines = 1
|
self.titleNode.maximumNumberOfLines = 1
|
||||||
self.titleNode.isOpaque = false
|
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 = ImmediateTextNode()
|
||||||
self.subtitleNode.displaysAsynchronously = false
|
self.subtitleNode.displaysAsynchronously = false
|
||||||
self.subtitleNode.maximumNumberOfLines = 1
|
self.subtitleNode.maximumNumberOfLines = 1
|
||||||
self.subtitleNode.isOpaque = false
|
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())
|
super.init(frame: CGRect())
|
||||||
|
|
||||||
|
|
@ -719,8 +719,8 @@ private final class ItemListTextWithSubtitleTitleView: UIView, NavigationBarTitl
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateTheme(theme: PresentationTheme) {
|
func updateTheme(theme: PresentationTheme) {
|
||||||
self.titleNode.attributedText = NSAttributedString(string: self.titleNode.attributedText?.string ?? "", font: Font.medium(17.0), textColor: theme.rootController.navigationBar.primaryTextColor)
|
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(13.0), textColor: theme.rootController.navigationBar.secondaryTextColor)
|
self.subtitleNode.attributedText = NSAttributedString(string: self.subtitleNode.attributedText?.string ?? "", font: Font.regular(12.0), textColor: theme.rootController.navigationBar.secondaryTextColor)
|
||||||
if let size = self.validLayout {
|
if let size = self.validLayout {
|
||||||
let _ = self.updateLayout(availableSize: size, transition: .immediate)
|
let _ = self.updateLayout(availableSize: size, transition: .immediate)
|
||||||
}
|
}
|
||||||
|
|
@ -741,7 +741,7 @@ private final class ItemListTextWithSubtitleTitleView: UIView, NavigationBarTitl
|
||||||
|
|
||||||
let titleSize = self.titleNode.updateLayout(size)
|
let titleSize = self.titleNode.updateLayout(size)
|
||||||
let subtitleSize = self.subtitleNode.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 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 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)
|
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)?
|
var dismissImpl: (() -> Void)?
|
||||||
let legacyController = LegacyICloudFileController(presentation: .modal(animateIn: true), theme: theme, completion: { urls in
|
let legacyController = LegacyICloudFileController(presentation: .modal(animateIn: true), theme: theme, completion: { urls in
|
||||||
dismissImpl?()
|
dismissImpl?()
|
||||||
|
|
|
||||||
|
|
@ -933,7 +933,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
||||||
|
|
||||||
if statusUpdated && item.displayFileInfo {
|
if statusUpdated && item.displayFileInfo {
|
||||||
if let file = selectedMedia as? TelegramMediaFile {
|
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
|
|> mapToSignal { value -> Signal<FileMediaResourceStatus, NoError> in
|
||||||
if case .Fetching = value.fetchStatus, !item.isDownloadList {
|
if case .Fetching = value.fetchStatus, !item.isDownloadList {
|
||||||
return .single(value) |> delay(0.1, queue: Queue.concurrentDefaultQueue())
|
return .single(value) |> delay(0.1, queue: Queue.concurrentDefaultQueue())
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ swift_library(
|
||||||
"//submodules/TelegramUI/Components/SearchInputPanelComponent",
|
"//submodules/TelegramUI/Components/SearchInputPanelComponent",
|
||||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||||
|
"//submodules/CounterControllerTitleView",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import CoreLocation
|
||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
import DeviceAccess
|
import DeviceAccess
|
||||||
import AttachmentUI
|
import AttachmentUI
|
||||||
|
import CounterControllerTitleView
|
||||||
|
|
||||||
public enum LocationPickerMode {
|
public enum LocationPickerMode {
|
||||||
case share(peer: EnginePeer?, selfPeer: EnginePeer?, hasLiveLocation: Bool)
|
case share(peer: EnginePeer?, selfPeer: EnginePeer?, hasLiveLocation: Bool)
|
||||||
|
|
@ -52,9 +53,16 @@ class LocationPickerInteraction {
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class LocationPickerController: ViewController, AttachmentContainable {
|
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 generic
|
||||||
case story
|
case story
|
||||||
|
case poll(PollMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var controllerNode: LocationPickerControllerNode {
|
private var controllerNode: LocationPickerControllerNode {
|
||||||
|
|
@ -63,7 +71,7 @@ public final class LocationPickerController: ViewController, AttachmentContainab
|
||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let mode: LocationPickerMode
|
private let mode: LocationPickerMode
|
||||||
private let source: Source
|
let source: Source
|
||||||
let initialLocation: CLLocationCoordinate2D?
|
let initialLocation: CLLocationCoordinate2D?
|
||||||
private let completion: (TelegramMediaMap, Int64?, String?, String?, String?) -> Void
|
private let completion: (TelegramMediaMap, Int64?, String?, String?, String?) -> Void
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
|
|
@ -115,7 +123,6 @@ public final class LocationPickerController: ViewController, AttachmentContainab
|
||||||
if case .glass = style {
|
if case .glass = style {
|
||||||
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView())
|
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView())
|
||||||
} else {
|
} 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.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
|
||||||
}
|
}
|
||||||
self.updateBarButtons()
|
self.updateBarButtons()
|
||||||
|
|
|
||||||
|
|
@ -354,6 +354,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
||||||
private let cancelButton = ComponentView<Empty>()
|
private let cancelButton = ComponentView<Empty>()
|
||||||
private let searchButton = ComponentView<Empty>()
|
private let searchButton = ComponentView<Empty>()
|
||||||
private let title = ComponentView<Empty>()
|
private let title = ComponentView<Empty>()
|
||||||
|
private let subtitle = ComponentView<Empty>()
|
||||||
|
|
||||||
fileprivate let topEdgeEffectView: EdgeEffectView
|
fileprivate let topEdgeEffectView: EdgeEffectView
|
||||||
fileprivate let bottomEdgeEffectView: EdgeEffectView
|
fileprivate let bottomEdgeEffectView: EdgeEffectView
|
||||||
|
|
@ -670,15 +671,18 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
||||||
var coordinate = userLocation?.coordinate
|
var coordinate = userLocation?.coordinate
|
||||||
switch strongSelf.mode {
|
switch strongSelf.mode {
|
||||||
case .share:
|
case .share:
|
||||||
if source == .story {
|
switch source {
|
||||||
|
case .generic:
|
||||||
|
title = presentationData.strings.Map_SendMyCurrentLocation
|
||||||
|
case .story:
|
||||||
if let initialLocation = strongSelf.controller?.initialLocation {
|
if let initialLocation = strongSelf.controller?.initialLocation {
|
||||||
title = presentationData.strings.Location_AddThisLocation
|
title = presentationData.strings.Location_AddThisLocation
|
||||||
coordinate = initialLocation
|
coordinate = initialLocation
|
||||||
} else {
|
} else {
|
||||||
title = presentationData.strings.Location_AddMyLocation
|
title = presentationData.strings.Location_AddMyLocation
|
||||||
}
|
}
|
||||||
} else {
|
case .poll:
|
||||||
title = presentationData.strings.Map_SendMyCurrentLocation
|
title = presentationData.strings.Location_AddThisLocation
|
||||||
}
|
}
|
||||||
case .pick:
|
case .pick:
|
||||||
title = presentationData.strings.Map_SetThisLocation
|
title = presentationData.strings.Map_SetThisLocation
|
||||||
|
|
@ -1358,6 +1362,27 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
||||||
self.view.addSubview(self.topEdgeEffectView)
|
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(
|
let titleSize = self.title.update(
|
||||||
transition: ComponentTransition(transition),
|
transition: ComponentTransition(transition),
|
||||||
component: AnyComponent(
|
component: AnyComponent(
|
||||||
|
|
@ -1374,7 +1399,37 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: 200.0, height: 40.0)
|
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 let titleView = self.title.view {
|
||||||
if titleView.superview == nil {
|
if titleView.superview == nil {
|
||||||
self.view.addSubview(titleView)
|
self.view.addSubview(titleView)
|
||||||
|
|
|
||||||
|
|
@ -412,8 +412,13 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
||||||
if case .glass = controller.style {
|
if case .glass = controller.style {
|
||||||
self.containerNode.view.addSubview(self.topEdgeEffectView)
|
self.containerNode.view.addSubview(self.topEdgeEffectView)
|
||||||
|
|
||||||
if case let .assets(_, mode) = controller.subject, [.default, .story].contains(mode) {
|
if case let .assets(_, mode) = controller.subject {
|
||||||
self.containerNode.view.addSubview(self.bottomEdgeEffectView)
|
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
|
self.titleView.title = presentationData.strings.MediaPicker_ChooseCover
|
||||||
case let .poll(pollMode):
|
case let .poll(pollMode):
|
||||||
self.titleView.title = presentationData.strings.MediaPicker_Recents
|
self.titleView.title = presentationData.strings.MediaPicker_Recents
|
||||||
|
//TODO:localize
|
||||||
switch pollMode {
|
switch pollMode {
|
||||||
case .description:
|
case .description:
|
||||||
self.titleView.subtitle = "Add media to the poll description"
|
self.titleView.subtitle = "Add media to the poll description"
|
||||||
|
|
@ -2560,7 +2566,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
||||||
titleIsEnabled = false
|
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)
|
self.cancelButtonNode.setState(isEnabled ? .cancel : .back, animated: true)
|
||||||
isBack = !isEnabled
|
isBack = !isEnabled
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,7 @@ public enum PresentationResourceKey: Int32 {
|
||||||
case chatListStoryReplyIcon
|
case chatListStoryReplyIcon
|
||||||
case chatListGiftIcon
|
case chatListGiftIcon
|
||||||
case chatListLocationIcon
|
case chatListLocationIcon
|
||||||
|
case chatListPollIcon
|
||||||
|
|
||||||
case chatListGeneralTopicIcon
|
case chatListGeneralTopicIcon
|
||||||
case chatListGeneralTopicTemplateIcon
|
case chatListGeneralTopicTemplateIcon
|
||||||
|
|
@ -420,4 +421,5 @@ public enum PresentationResourceParameterKey: Hashable {
|
||||||
|
|
||||||
case chatExpiredStoryIndicatorIcon(type: ChatExpiredStoryIndicatorType)
|
case chatExpiredStoryIndicatorIcon(type: ChatExpiredStoryIndicatorType)
|
||||||
case chatReplyStoryIndicatorIcon(type: ChatExpiredStoryIndicatorType)
|
case chatReplyStoryIndicatorIcon(type: ChatExpiredStoryIndicatorType)
|
||||||
|
case chatReplyPollIndicatorIcon(type: ChatExpiredStoryIndicatorType)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1277,9 +1277,9 @@ public struct PresentationResourcesChat {
|
||||||
let foregroundColor: UIColor
|
let foregroundColor: UIColor
|
||||||
switch type {
|
switch type {
|
||||||
case .incoming:
|
case .incoming:
|
||||||
foregroundColor = theme.chat.message.incoming.mediaActiveControlColor
|
foregroundColor = theme.chat.message.incoming.accentTextColor
|
||||||
case .outgoing:
|
case .outgoing:
|
||||||
foregroundColor = theme.chat.message.outgoing.mediaActiveControlColor
|
foregroundColor = theme.chat.message.outgoing.accentTextColor
|
||||||
case .free:
|
case .free:
|
||||||
foregroundColor = theme.chat.serviceMessage.components.withDefaultWallpaper.primaryText
|
foregroundColor = theme.chat.serviceMessage.components.withDefaultWallpaper.primaryText
|
||||||
}
|
}
|
||||||
|
|
@ -1303,9 +1303,9 @@ public struct PresentationResourcesChat {
|
||||||
let foregroundColor: UIColor
|
let foregroundColor: UIColor
|
||||||
switch type {
|
switch type {
|
||||||
case .incoming:
|
case .incoming:
|
||||||
foregroundColor = theme.chat.message.incoming.mediaActiveControlColor
|
foregroundColor = theme.chat.message.incoming.accentTextColor
|
||||||
case .outgoing:
|
case .outgoing:
|
||||||
foregroundColor = theme.chat.message.outgoing.mediaActiveControlColor
|
foregroundColor = theme.chat.message.outgoing.accentTextColor
|
||||||
case .free:
|
case .free:
|
||||||
foregroundColor = theme.chat.serviceMessage.components.withDefaultWallpaper.primaryText
|
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? {
|
public static func storyViewListLikeIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||||
return theme.image(PresentationResourceKey.storyViewListLikeIcon.rawValue, { theme in
|
return theme.image(PresentationResourceKey.storyViewListLikeIcon.rawValue, { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Stories/InputLikeOn"), color: UIColor(rgb: 0xFF3B30))
|
return generateTintedImage(image: UIImage(bundleImageName: "Stories/InputLikeOn"), color: UIColor(rgb: 0xFF3B30))
|
||||||
|
|
@ -1417,8 +1443,8 @@ public struct PresentationResourcesChat {
|
||||||
context.setFillColor(UIColor.white.cgColor)
|
context.setFillColor(UIColor.white.cgColor)
|
||||||
|
|
||||||
let lineHeight = 2.0 - UIScreenPixel
|
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: 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, y: 0.0, width: lineHeight, height: 15.0), 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()
|
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? {
|
public static func verifiedIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||||
return theme.image(PresentationResourceKey.chatListVerifiedIcon.rawValue, { theme in
|
return theme.image(PresentationResourceKey.chatListVerifiedIcon.rawValue, { theme in
|
||||||
if let backgroundImage = UIImage(bundleImageName: "Chat List/PeerVerifiedIconBackground"), let foregroundImage = UIImage(bundleImageName: "Chat List/PeerVerifiedIconForeground") {
|
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:
|
case .expiredVideoMessage:
|
||||||
return (NSAttributedString(string: strings.Message_VideoMessageExpired), true)
|
return (NSAttributedString(string: strings.Message_VideoMessageExpired), true)
|
||||||
case let .poll(text):
|
case let .poll(text):
|
||||||
return (NSAttributedString(string: "📊 \(text)"), false)
|
return (NSAttributedString(string: text), false)
|
||||||
case let .todo(text):
|
case let .todo(text):
|
||||||
return (NSAttributedString(string: "☑️ \(text)"), false)
|
return (NSAttributedString(string: "☑️ \(text)"), false)
|
||||||
case let .restricted(text):
|
case let .restricted(text):
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import ContextUI
|
||||||
private final class AttachmentFileControllerArguments {
|
private final class AttachmentFileControllerArguments {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let isAudio: Bool
|
let isAudio: Bool
|
||||||
|
let isAttach: Bool
|
||||||
let openGallery: () -> Void
|
let openGallery: () -> Void
|
||||||
let openFiles: () -> Void
|
let openFiles: () -> Void
|
||||||
let scanDocument: () -> Void
|
let scanDocument: () -> Void
|
||||||
|
|
@ -38,9 +39,10 @@ private final class AttachmentFileControllerArguments {
|
||||||
let setMessageSelection: ([MessageId], Message?, Bool) -> Void
|
let setMessageSelection: ([MessageId], Message?, Bool) -> Void
|
||||||
let openMessageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> 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.context = context
|
||||||
self.isAudio = isAudio
|
self.isAudio = isAudio
|
||||||
|
self.isAttach = isAttach
|
||||||
self.openGallery = openGallery
|
self.openGallery = openGallery
|
||||||
self.openFiles = openFiles
|
self.openFiles = openFiles
|
||||||
self.scanDocument = scanDocument
|
self.scanDocument = scanDocument
|
||||||
|
|
@ -250,7 +252,7 @@ private enum AttachmentFileEntry: ItemListNodeEntry {
|
||||||
|
|
||||||
let dateTimeFormat = arguments.context.sharedContext.currentPresentationData.with({$0}).dateTimeFormat
|
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))
|
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):
|
case let .savedShowMore(theme, text):
|
||||||
return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.downArrowImage(theme), title: text, sectionId: self.section, editing: false, action: {
|
return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.downArrowImage(theme), title: text, sectionId: self.section, editing: false, action: {
|
||||||
arguments.expandSavedMusic()
|
arguments.expandSavedMusic()
|
||||||
|
|
@ -275,8 +277,7 @@ private enum AttachmentFileEntry: ItemListNodeEntry {
|
||||||
|
|
||||||
let dateTimeFormat = arguments.context.sharedContext.currentPresentationData.with({$0}).dateTimeFormat
|
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))
|
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: arguments.isAttach, displayFileInfo: true, displayBackground: true, style: .blocks, sectionId: self.section)
|
||||||
return ListMessageItem(presentationData: chatPresentationData, systemStyle: .glass, context: arguments.context, chatLocation: .peer(id: PeerId(0)), interaction: interaction, message: message, selection: selection, displayHeader: false, isDownloadList: arguments.isAudio, isStoryMusic: true, isAttachMusic: true, displayFileInfo: true, displayBackground: true, style: .blocks, sectionId: self.section)
|
|
||||||
case let .recentShowMore(theme, text):
|
case let .recentShowMore(theme, text):
|
||||||
return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.downArrowImage(theme), title: text, sectionId: self.section, editing: false, action: {
|
return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.downArrowImage(theme), title: text, sectionId: self.section, editing: false, action: {
|
||||||
arguments.expandRecentMusic()
|
arguments.expandRecentMusic()
|
||||||
|
|
@ -301,8 +302,7 @@ private enum AttachmentFileEntry: ItemListNodeEntry {
|
||||||
|
|
||||||
let dateTimeFormat = arguments.context.sharedContext.currentPresentationData.with({$0}).dateTimeFormat
|
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))
|
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: arguments.isAttach, displayFileInfo: true, displayBackground: true, style: .blocks, sectionId: self.section)
|
||||||
return ListMessageItem(presentationData: chatPresentationData, systemStyle: .glass, context: arguments.context, chatLocation: .peer(id: PeerId(0)), interaction: interaction, message: message, selection: selection, displayHeader: false, isDownloadList: arguments.isAudio, isStoryMusic: true, isAttachMusic: true, displayFileInfo: true, displayBackground: true, style: .blocks, sectionId: self.section)
|
|
||||||
case let .globalShowMore(theme, text):
|
case let .globalShowMore(theme, text):
|
||||||
return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.downArrowImage(theme), title: text, sectionId: self.section, editing: false, action: {
|
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 caption: NSAttributedString?
|
||||||
fileprivate var selectionCount = ValuePromise<Int>(0)
|
fileprivate var selectionCount = ValuePromise<Int>(0)
|
||||||
|
|
||||||
|
fileprivate var bottomEdgeColor: UIColor = .clear
|
||||||
|
|
||||||
fileprivate var mulitpleCompletion: ((AttachmentMediaPickerSendMode, AttachmentMediaPickerAttachmentMode, ChatSendMessageActionSheetController.SendParameters?) -> Void)?
|
fileprivate var mulitpleCompletion: ((AttachmentMediaPickerSendMode, AttachmentMediaPickerAttachmentMode, ChatSendMessageActionSheetController.SendParameters?) -> Void)?
|
||||||
|
|
||||||
var delayDisappear = false
|
var delayDisappear = false
|
||||||
|
|
@ -441,6 +443,7 @@ public class AttachmentFileControllerImpl: ItemListController, AttachmentFileCon
|
||||||
var hasBottomEdgeEffect = true
|
var hasBottomEdgeEffect = true
|
||||||
|
|
||||||
var resetForReuseImpl: () -> Void = {}
|
var resetForReuseImpl: () -> Void = {}
|
||||||
|
var onDismissImpl: () -> Void = {}
|
||||||
public func resetForReuse() {
|
public func resetForReuse() {
|
||||||
self.resetForReuseImpl()
|
self.resetForReuseImpl()
|
||||||
self.scrollToTop?()
|
self.scrollToTop?()
|
||||||
|
|
@ -451,12 +454,23 @@ public class AttachmentFileControllerImpl: ItemListController, AttachmentFileCon
|
||||||
self.visibleBottomContentOffsetChanged?(self.visibleBottomContentOffset)
|
self.visibleBottomContentOffsetChanged?(self.visibleBottomContentOffset)
|
||||||
self.delayDisappear = false
|
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? {
|
public var mediaPickerContext: AttachmentMediaPickerContext? {
|
||||||
return AttachmentFileContext(controller: self)
|
return AttachmentFileContext(controller: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var topEdgeEffectView: EdgeEffectView?
|
|
||||||
private var bottomEdgeEffectView: EdgeEffectView?
|
private var bottomEdgeEffectView: EdgeEffectView?
|
||||||
|
|
||||||
var isSearching: Bool = false {
|
var isSearching: Bool = false {
|
||||||
|
|
@ -468,21 +482,7 @@ public class AttachmentFileControllerImpl: ItemListController, AttachmentFileCon
|
||||||
public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
super.containerLayoutUpdated(layout, transition: transition)
|
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 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 {
|
if self.hasBottomEdgeEffect {
|
||||||
let bottomEdgeEffectView: EdgeEffectView
|
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))
|
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.updateFrame(view: bottomEdgeEffectView, frame: bottomEdgeEffectFrame)
|
||||||
transition.updateAlpha(layer: bottomEdgeEffectView.layer, alpha: self.isSearching ? 0.0 : 1.0)
|
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 {
|
} else if let bottomEdgeEffectView = self.bottomEdgeEffectView {
|
||||||
bottomEdgeEffectView.removeFromSuperview()
|
bottomEdgeEffectView.removeFromSuperview()
|
||||||
}
|
}
|
||||||
|
|
@ -521,13 +521,32 @@ private func messageSelectionState(state: AttachmentFileControllerState, message
|
||||||
|
|
||||||
public enum AttachmentFileControllerMode {
|
public enum AttachmentFileControllerMode {
|
||||||
case recent
|
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(
|
public func makeAttachmentFileControllerImpl(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
|
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
|
||||||
mode: AttachmentFileControllerMode = .recent,
|
mode: AttachmentFileControllerMode = .recent,
|
||||||
|
source: AttachmentFileControllerSource = .generic,
|
||||||
bannedSendMedia: (Int32, Bool)?,
|
bannedSendMedia: (Int32, Bool)?,
|
||||||
presentGallery: @escaping () -> Void,
|
presentGallery: @escaping () -> Void,
|
||||||
presentFiles: @escaping () -> Void,
|
presentFiles: @escaping () -> Void,
|
||||||
|
|
@ -549,10 +568,20 @@ public func makeAttachmentFileControllerImpl(
|
||||||
var updateIsSearchingImpl: ((Bool) -> Void)?
|
var updateIsSearchingImpl: ((Bool) -> Void)?
|
||||||
var updateSelectionCountImpl: ((Int) -> Void)?
|
var updateSelectionCountImpl: ((Int) -> Void)?
|
||||||
var presentInGlobalOverlayImpl: ((ViewController) -> 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(
|
let arguments = AttachmentFileControllerArguments(
|
||||||
context: context,
|
context: context,
|
||||||
isAudio: mode == .audio,
|
isAudio: isAudio,
|
||||||
|
isAttach: isAttach,
|
||||||
openGallery: {
|
openGallery: {
|
||||||
presentGallery()
|
presentGallery()
|
||||||
},
|
},
|
||||||
|
|
@ -603,6 +632,8 @@ public func makeAttachmentFileControllerImpl(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
toggleMediaPlayback: { message in
|
toggleMediaPlayback: { message in
|
||||||
|
didPreviewAudio = true
|
||||||
|
|
||||||
let playlistLocation: PeerMessagesPlaylistLocation = .custom(messages: .single(([message], 0, false)), canReorder: false, at: message.id, loadMore: nil)
|
let playlistLocation: PeerMessagesPlaylistLocation = .custom(messages: .single(([message], 0, false)), canReorder: false, at: message.id, loadMore: nil)
|
||||||
context.sharedContext.mediaManager.setPlaylist((context, PeerMessagesMediaPlaylist(context: context, location: playlistLocation, chatLocationContextHolder: nil)), type: .music, control: .playback(.togglePlayPause))
|
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 existingCloseButton = Atomic<BarComponentHostNode?>(value: nil)
|
||||||
let existingSearchButton = Atomic<BarComponentHostNode?>(value: nil)
|
let existingSearchButton = Atomic<BarComponentHostNode?>(value: nil)
|
||||||
|
|
||||||
let previousRecentDocuments = Atomic<[Message]?>(value: nil)
|
let previousRecentDocuments = Atomic<[Message]?>(value: nil)
|
||||||
let signal = combineLatest(queue: Queue.mainQueue(),
|
let signal = combineLatest(queue: Queue.mainQueue(),
|
||||||
presentationData,
|
presentationData,
|
||||||
|
|
@ -823,21 +854,32 @@ public func makeAttachmentFileControllerImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
var rightNavigationButton: ItemListNavigationButton?
|
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: {}) }
|
rightNavigationButton = searchButtonNode.flatMap { ItemListNavigationButton(content: .node($0), style: .regular, enabled: true, action: {}) }
|
||||||
}
|
}
|
||||||
|
|
||||||
let title: String
|
let title: String
|
||||||
|
var subtitle: String?
|
||||||
switch mode {
|
switch mode {
|
||||||
case .recent:
|
case .recent:
|
||||||
title = presentationData.strings.Attachment_File
|
title = presentationData.strings.Attachment_File
|
||||||
case .audio:
|
case .audio:
|
||||||
title = presentationData.strings.MediaEditor_Audio_Title
|
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(
|
let controllerState = ItemListControllerState(
|
||||||
presentationData: ItemListPresentationData(presentationData),
|
presentationData: ItemListPresentationData(presentationData),
|
||||||
title: .text(title),
|
title: subtitle.flatMap { .textWithSubtitle(title, $0) } ?? .text(title),
|
||||||
leftNavigationButton: leftNavigationButton,
|
leftNavigationButton: leftNavigationButton,
|
||||||
rightNavigationButton: rightNavigationButton,
|
rightNavigationButton: rightNavigationButton,
|
||||||
backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back),
|
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)
|
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))
|
return (controllerState, (listState, arguments))
|
||||||
} |> afterDisposed {
|
} |> afterDisposed {
|
||||||
actionsDisposable.dispose()
|
actionsDisposable.dispose()
|
||||||
|
|
@ -888,7 +932,6 @@ public func makeAttachmentFileControllerImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
let controller = AttachmentFileControllerImpl(context: context, state: signal, hideNavigationBarBackground: true)
|
let controller = AttachmentFileControllerImpl(context: context, state: signal, hideNavigationBarBackground: true)
|
||||||
|
|
||||||
controller.mulitpleCompletion = { _, _, _ in
|
controller.mulitpleCompletion = { _, _, _ in
|
||||||
let _ = stateValue.with({ state in
|
let _ = stateValue.with({ state in
|
||||||
if let selectedMessageIds = state.selectedMessageIds {
|
if let selectedMessageIds = state.selectedMessageIds {
|
||||||
|
|
@ -928,12 +971,25 @@ public func makeAttachmentFileControllerImpl(
|
||||||
updatedState.selectedMessageIds = nil
|
updatedState.selectedMessageIds = nil
|
||||||
return updatedState
|
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
|
updateIsSearchingImpl = { [weak controller] isSearching in
|
||||||
controller?.isSearching = isSearching
|
controller?.isSearching = isSearching
|
||||||
}
|
}
|
||||||
dismissImpl = { [weak controller] in
|
dismissImpl = { [weak controller] in
|
||||||
controller?.dismiss(animated: true)
|
controller?.dismiss(animated: true)
|
||||||
|
|
||||||
|
if didPreviewAudio {
|
||||||
|
context.sharedContext.mediaManager.setPlaylist(nil, type: .music, control: .playback(.pause))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dismissInputImpl = { [weak controller] in
|
dismissInputImpl = { [weak controller] in
|
||||||
controller?.view.endEditing(true)
|
controller?.view.endEditing(true)
|
||||||
|
|
@ -950,6 +1006,9 @@ public func makeAttachmentFileControllerImpl(
|
||||||
presentInGlobalOverlayImpl = { [weak controller] c in
|
presentInGlobalOverlayImpl = { [weak controller] c in
|
||||||
controller?.presentInGlobalOverlay(c)
|
controller?.presentInGlobalOverlay(c)
|
||||||
}
|
}
|
||||||
|
updateBottomColorImpl = { [weak controller] color in
|
||||||
|
controller?.bottomEdgeColor = color
|
||||||
|
}
|
||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -962,15 +1021,16 @@ public func storyAudioPickerController(
|
||||||
var dismissImpl: (() -> Void)?
|
var dismissImpl: (() -> Void)?
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkColorPresentationTheme)
|
let presentationData = context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkColorPresentationTheme)
|
||||||
let updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>) = (presentationData, .single(presentationData))
|
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
|
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()
|
selectFromFiles()
|
||||||
dismissImpl?()
|
dismissImpl?()
|
||||||
}, presentDocumentScanner: nil, send: { files in
|
}, presentDocumentScanner: nil, send: { files in
|
||||||
completion(files.first!)
|
completion(files.first!)
|
||||||
dismissImpl?()
|
dismissImpl?()
|
||||||
}) as! AttachmentFileControllerImpl
|
}) as! AttachmentFileControllerImpl
|
||||||
|
filePickerController.hasBottomEdgeEffect = false
|
||||||
present(filePickerController, filePickerController.mediaPickerContext)
|
present(filePickerController, filePickerController.mediaPickerContext)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,12 +74,11 @@ final class AttachmentFileSearchItem: ItemListControllerSearch {
|
||||||
func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)? {
|
func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)? {
|
||||||
switch self.mode {
|
switch self.mode {
|
||||||
case .audio:
|
case .audio:
|
||||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
|
||||||
if let current = current as? AttachmentFileSearchNavigationContentNode {
|
if let current = current as? AttachmentFileSearchNavigationContentNode {
|
||||||
current.updateTheme(presentationData.theme)
|
current.updateTheme(self.presentationData.theme)
|
||||||
return current
|
return current
|
||||||
} else {
|
} 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:
|
default:
|
||||||
|
|
@ -164,7 +163,7 @@ private final class AttachmentFileSearchItemNode: ItemListControllerSearchNode {
|
||||||
strings: self.presentationData.strings,
|
strings: self.presentationData.strings,
|
||||||
metrics: layout.metrics,
|
metrics: layout.metrics,
|
||||||
safeInsets: layout.safeInsets,
|
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
|
updated: { [weak self] query in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
|
|
@ -329,9 +328,9 @@ private enum AttachmentFileSearchEntry: Comparable, Identifiable {
|
||||||
interaction.toggleMediaPlayback(message)
|
interaction.toggleMediaPlayback(message)
|
||||||
}, toggleMessagesSelection: { _, _ in }, openUrl: { _, _, _, _ in }, openInstantPage: { _, _ in }, longTap: { _, _ in }, getHiddenMedia: { return [:] })
|
}, toggleMessagesSelection: { _, _ in }, openUrl: { _, _, _, _ in }, openInstantPage: { _, _ in }, longTap: { _, _ in }, getHiddenMedia: { return [:] })
|
||||||
|
|
||||||
let displayFileInfo = mode == .audio
|
let displayFileInfo = mode.isAudio
|
||||||
let isStoryMusic = mode == .audio
|
let isStoryMusic = mode.isAudio
|
||||||
let isDownloadList = mode == .audio
|
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)
|
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):
|
case let .showMore(text, section):
|
||||||
|
|
@ -661,6 +660,7 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
entries.append(.header(title: " ", section: 0))
|
||||||
var index: Int32 = 0
|
var index: Int32 = 0
|
||||||
for _ in 0 ..< 16 {
|
for _ in 0 ..< 16 {
|
||||||
entries.append(.file(index: index, message: nil, section: 0))
|
entries.append(.file(index: index, message: nil, section: 0))
|
||||||
|
|
@ -1009,6 +1009,20 @@ private final class AttachmentFileSearchNavigationContentNode: NavigationBarCont
|
||||||
|
|
||||||
func updateTheme(_ theme: PresentationTheme) {
|
func updateTheme(_ theme: PresentationTheme) {
|
||||||
self.theme = theme
|
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 {
|
if let params = self.params {
|
||||||
let _ = self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate)
|
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 {
|
override var nominalHeight: CGFloat {
|
||||||
return 60.0
|
return 68.0
|
||||||
}
|
}
|
||||||
|
|
||||||
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize {
|
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 {
|
if strongSelf.selectionNode != nil {
|
||||||
return .none
|
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 let action = strongSelf.gestureRecognized(gesture: .tap, location: location, recognizer: nil) {
|
||||||
if case let .action(action) = action, !action.contextMenuOnLongPress {
|
if case let .action(action) = action, !action.contextMenuOnLongPress {
|
||||||
return .none
|
return .none
|
||||||
|
|
|
||||||
|
|
@ -1368,6 +1368,7 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode {
|
||||||
var attachPressed: (() -> Void)?
|
var attachPressed: (() -> Void)?
|
||||||
var mediaPressed: (() -> Void)?
|
var mediaPressed: (() -> Void)?
|
||||||
var modeSelectorPressed: (() -> Void)?
|
var modeSelectorPressed: (() -> Void)?
|
||||||
|
var requestSave: (() -> Void)?
|
||||||
|
|
||||||
static let characterLimit = 100
|
static let characterLimit = 100
|
||||||
private static let leftInset: CGFloat = 50.0
|
private static let leftInset: CGFloat = 50.0
|
||||||
|
|
@ -1449,17 +1450,25 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode {
|
||||||
hideKeyboard: self.currentFocusedTextInputIsMedia,
|
hideKeyboard: self.currentFocusedTextInputIsMedia,
|
||||||
customInputView: nil,
|
customInputView: nil,
|
||||||
placeholder: NSAttributedString(string: strings.CreatePoll_AddOption, font: font, textColor: currentPlaceholderColor),
|
placeholder: NSAttributedString(string: strings.CreatePoll_AddOption, font: font, textColor: currentPlaceholderColor),
|
||||||
|
placeholderVerticalOffset: 1.0 + UIScreenPixel,
|
||||||
resetText: nil,
|
resetText: nil,
|
||||||
isOneLineWhenUnfocused: false,
|
isOneLineWhenUnfocused: false,
|
||||||
characterLimit: ChatMessagePollAddOptionNode.characterLimit,
|
characterLimit: ChatMessagePollAddOptionNode.characterLimit,
|
||||||
enableInlineAnimations: true,
|
enableInlineAnimations: true,
|
||||||
emptyLineHandling: .allowed,
|
emptyLineHandling: .notAllowed,
|
||||||
formatMenuAvailability: .none,
|
formatMenuAvailability: .none,
|
||||||
|
returnKeyType: .done,
|
||||||
lockedFormatAction: {
|
lockedFormatAction: {
|
||||||
},
|
},
|
||||||
present: { _ in
|
present: { _ in
|
||||||
},
|
},
|
||||||
paste: { _ in
|
paste: { _ in
|
||||||
|
},
|
||||||
|
returnKeyAction: { [weak self] in
|
||||||
|
self?.requestSave?()
|
||||||
|
},
|
||||||
|
backspaceKeyAction: {
|
||||||
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -2150,17 +2159,14 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
||||||
guard let item = self.item else {
|
guard let item = self.item else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
item.controllerInteraction.updatePresentationState { [weak item] state in
|
item.controllerInteraction.updatePresentationState { state in
|
||||||
var focusedTextInputIsMedia = false
|
|
||||||
let updatedState = state.updatedInputMode({ inputMode in
|
let updatedState = state.updatedInputMode({ inputMode in
|
||||||
if case .media = inputMode {
|
if case .media = inputMode {
|
||||||
return .text
|
return .text
|
||||||
} else {
|
} else {
|
||||||
focusedTextInputIsMedia = true
|
|
||||||
return .media(mode: .other, expanded: .none, focused: true)
|
return .media(mode: .other, expanded: .none, focused: true)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
item?.controllerInteraction.focusedTextInputIsMedia = focusedTextInputIsMedia
|
|
||||||
|
|
||||||
return updatedState
|
return updatedState
|
||||||
}
|
}
|
||||||
|
|
@ -2185,6 +2191,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
||||||
presentPollAttachmentScreen(
|
presentPollAttachmentScreen(
|
||||||
context: item.context,
|
context: item.context,
|
||||||
updatedPresentationData: item.controllerInteraction.updatedPresentationData,
|
updatedPresentationData: item.controllerInteraction.updatedPresentationData,
|
||||||
|
subject: .option,
|
||||||
availableButtons: [.gallery, .sticker, .emoji, .location],
|
availableButtons: [.gallery, .sticker, .emoji, .location],
|
||||||
present: { [weak item] controller in
|
present: { [weak item] controller in
|
||||||
item?.controllerInteraction.navigationController()?.pushViewController(controller)
|
item?.controllerInteraction.navigationController()?.pushViewController(controller)
|
||||||
|
|
@ -2291,6 +2298,8 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
||||||
let optionData = "\(poll.options.count)".data(using: .utf8)!
|
let optionData = "\(poll.options.count)".data(using: .utf8)!
|
||||||
item.controllerInteraction.requestAddMessagePollOption(item.message.id, trimmedNewOptionText, entities, optionData, self.currentNewOptionMedia?.media)
|
item.controllerInteraction.requestAddMessagePollOption(item.message.id, trimmedNewOptionText, entities, optionData, self.currentNewOptionMedia?.media)
|
||||||
return
|
return
|
||||||
|
} else if self.newOptionIsFocused {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasSelection = false
|
var hasSelection = false
|
||||||
|
|
@ -2932,6 +2941,9 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
||||||
addOptionNode.modeSelectorPressed = { [weak self] in
|
addOptionNode.modeSelectorPressed = { [weak self] in
|
||||||
self?.toggleNewOptionInputMode()
|
self?.toggleNewOptionInputMode()
|
||||||
}
|
}
|
||||||
|
addOptionNode.requestSave = { [weak self] in
|
||||||
|
self?.buttonPressed()
|
||||||
|
}
|
||||||
addOptionNode.focusUpdated = { [weak self] focused in
|
addOptionNode.focusUpdated = { [weak self] focused in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -203,6 +203,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||||
let isText: Bool
|
let isText: Bool
|
||||||
var isExpiredStory: Bool = false
|
var isExpiredStory: Bool = false
|
||||||
var isStory: Bool = false
|
var isStory: Bool = false
|
||||||
|
var isPoll: Bool = false
|
||||||
|
|
||||||
let titleColor: UIColor
|
let titleColor: UIColor
|
||||||
let mainColor: UIColor
|
let mainColor: UIColor
|
||||||
|
|
@ -429,6 +430,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
let textColor: UIColor
|
let textColor: UIColor
|
||||||
|
let iconColor: UIColor
|
||||||
|
|
||||||
switch arguments.type {
|
switch arguments.type {
|
||||||
case let .bubble(incoming):
|
case let .bubble(incoming):
|
||||||
|
|
@ -439,8 +441,10 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||||
} else {
|
} else {
|
||||||
textColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.primaryTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.primaryTextColor
|
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:
|
case .standalone:
|
||||||
textColor = titleColor
|
textColor = titleColor
|
||||||
|
iconColor = titleColor
|
||||||
}
|
}
|
||||||
|
|
||||||
var textLeftInset: CGFloat = 0.0
|
var textLeftInset: CGFloat = 0.0
|
||||||
|
|
@ -516,6 +520,10 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
messageText = NSAttributedString(string: textString.string, font: textFont, textColor: textColor)
|
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
|
var leftInset: CGFloat = 11.0
|
||||||
|
|
@ -562,6 +570,26 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||||
hasRoundImage = true
|
hasRoundImage = true
|
||||||
}
|
}
|
||||||
break
|
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] {
|
} 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))
|
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
|
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.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 = titleLayout.size.height + textLayout.size.height - 2 * (textInsets.top + textInsets.bottom) + 2 * spacing
|
||||||
size.height += 2.0
|
size.height += 2.0
|
||||||
if isExpiredStory || isStory {
|
if isExpiredStory || isStory || isPoll {
|
||||||
size.width += 16.0
|
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)
|
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 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 {
|
if textNode.textNode.bounds.isEmpty || !animation.isAnimated || textNode.textNode.bounds.height == effectiveTextFrame.height {
|
||||||
textNode.textNode.frame = effectiveTextFrame
|
textNode.textNode.frame = effectiveTextFrame
|
||||||
|
|
@ -809,7 +837,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if isExpiredStory || isStory {
|
if isExpiredStory || isStory || isPoll {
|
||||||
let expiredStoryIconView: UIImageView
|
let expiredStoryIconView: UIImageView
|
||||||
if let current = node.expiredStoryIconView {
|
if let current = node.expiredStoryIconView {
|
||||||
expiredStoryIconView = current
|
expiredStoryIconView = current
|
||||||
|
|
@ -827,7 +855,9 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||||
imageType = incoming ? .incoming : .outgoing
|
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)
|
expiredStoryIconView.image = PresentationResourcesChat.chatExpiredStoryIndicatorIcon(arguments.presentationData.theme.theme, type: imageType)
|
||||||
} else {
|
} else {
|
||||||
expiredStoryIconView.image = PresentationResourcesChat.chatReplyStoryIndicatorIcon(arguments.presentationData.theme.theme, type: imageType)
|
expiredStoryIconView.image = PresentationResourcesChat.chatReplyStoryIndicatorIcon(arguments.presentationData.theme.theme, type: imageType)
|
||||||
|
|
@ -921,7 +951,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||||
|
|
||||||
if let todoItemCompleted {
|
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 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
|
let checkLayer: CheckLayer
|
||||||
if let current = node.checkLayer {
|
if let current = node.checkLayer {
|
||||||
|
|
|
||||||
|
|
@ -327,6 +327,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
|
||||||
public var canReadHistory: Bool = false
|
public var canReadHistory: Bool = false
|
||||||
public var summarizedMessageIds: Set<MessageId> = Set()
|
public var summarizedMessageIds: Set<MessageId> = Set()
|
||||||
public var focusedTextInputIsMedia: Bool = false
|
public var focusedTextInputIsMedia: Bool = false
|
||||||
|
public var focusedPollAddOptionMessageId: MessageId?
|
||||||
|
|
||||||
private var isOpeningMediaValue: Bool = false
|
private var isOpeningMediaValue: Bool = false
|
||||||
public var isOpeningMedia: Bool {
|
public var isOpeningMedia: Bool {
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,8 @@ swift_library(
|
||||||
"//submodules/Components/PagerComponent",
|
"//submodules/Components/PagerComponent",
|
||||||
"//submodules/FeaturedStickersScreen",
|
"//submodules/FeaturedStickersScreen",
|
||||||
"//submodules/TelegramNotices",
|
"//submodules/TelegramNotices",
|
||||||
"//submodules/StickerPeekUI:StickerPeekUI",
|
"//submodules/StickerPeekUI",
|
||||||
|
"//submodules/CounterControllerTitleView",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|
|
||||||
|
|
@ -851,9 +851,26 @@ final class ComposePollScreenComponent: Component {
|
||||||
availableButtons = [.gallery, .sticker, .emoji, .location]
|
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
|
let pollAttachmentSubject: PollAttachmentSubject
|
||||||
(self?.environment?.controller() as? ComposePollScreen)?.parentController()?.push(c)
|
switch subject {
|
||||||
}, completion: { [weak self] media in
|
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 {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -1729,7 +1746,13 @@ final class ComposePollScreenComponent: Component {
|
||||||
maximumNumberOfLines: 0
|
maximumNumberOfLines: 0
|
||||||
))
|
))
|
||||||
} else {
|
} 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))
|
let rawString = environment.strings.CreatePoll_OptionCountFooterFormat(Int32(remainingCount))
|
||||||
|
|
||||||
var pollOptionsFooterItems: [AnimatedTextComponent.Item] = []
|
var pollOptionsFooterItems: [AnimatedTextComponent.Item] = []
|
||||||
|
|
@ -2813,7 +2836,7 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont
|
||||||
public func prepareForReuse() {
|
public func prepareForReuse() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func requestDismiss(completion: @escaping () -> Void) {
|
public func requestDismiss(completion: @escaping () -> Void) {
|
||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,16 @@ import LocationUI
|
||||||
import AttachmentFileController
|
import AttachmentFileController
|
||||||
import ChatEntityKeyboardInputNode
|
import ChatEntityKeyboardInputNode
|
||||||
|
|
||||||
|
public enum PollAttachmentSubject {
|
||||||
|
case description
|
||||||
|
case quizAnswer
|
||||||
|
case option
|
||||||
|
}
|
||||||
|
|
||||||
public func presentPollAttachmentScreen(
|
public func presentPollAttachmentScreen(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?,
|
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?,
|
||||||
|
subject: PollAttachmentSubject,
|
||||||
availableButtons: [AttachmentButtonType],
|
availableButtons: [AttachmentButtonType],
|
||||||
inputMediaNodeData: Signal<ChatEntityKeyboardInputNode.InputData?, NoError> = .single(nil),
|
inputMediaNodeData: Signal<ChatEntityKeyboardInputNode.InputData?, NoError> = .single(nil),
|
||||||
present: @escaping (ViewController) -> Void,
|
present: @escaping (ViewController) -> Void,
|
||||||
|
|
@ -36,16 +43,18 @@ public func presentPollAttachmentScreen(
|
||||||
return nil
|
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
|
attachmentController.requestController = { [weak attachmentController] type, controllerCompletion in
|
||||||
switch type {
|
switch type {
|
||||||
case .gallery:
|
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(
|
let controller = MediaPickerScreenImpl(
|
||||||
context: context,
|
context: context,
|
||||||
updatedPresentationData: updatedPresentationData,
|
updatedPresentationData: updatedPresentationData,
|
||||||
|
|
@ -54,7 +63,7 @@ public func presentPollAttachmentScreen(
|
||||||
threadTitle: nil,
|
threadTitle: nil,
|
||||||
chatLocation: nil,
|
chatLocation: nil,
|
||||||
enableMultiselection: false,
|
enableMultiselection: false,
|
||||||
subject: .assets(nil, .poll(.option))
|
subject: .assets(nil, .poll(mediaPickerPollSubject))
|
||||||
)
|
)
|
||||||
controller.getCaptionPanelView = {
|
controller.getCaptionPanelView = {
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -71,24 +80,49 @@ public func presentPollAttachmentScreen(
|
||||||
controllerCompletion(controller, controller.mediaPickerContext)
|
controllerCompletion(controller, controller.mediaPickerContext)
|
||||||
return true
|
return true
|
||||||
case .file:
|
case .file:
|
||||||
let controller = context.sharedContext.makeAttachmentFileController(context: context, updatedPresentationData: updatedPresentationData, audio: false, bannedSendMedia: nil, presentGallery: { [weak attachmentController] in
|
let filePickerPollSubject: AttachmentFileControllerSource.PollMode
|
||||||
attachmentController?.dismiss(animated: true)
|
switch subject {
|
||||||
//self?.presentFileGallery()
|
case .description:
|
||||||
}, presentFiles: { [weak attachmentController] in
|
filePickerPollSubject = .description
|
||||||
attachmentController?.dismiss(animated: true)
|
case .quizAnswer:
|
||||||
//self?.presentICloudFileGallery()
|
filePickerPollSubject = .quizAnswer
|
||||||
}, presentDocumentScanner: {
|
default:
|
||||||
//self?.presentDocumentScanner()
|
filePickerPollSubject = .description
|
||||||
}, send: { mediaReferences in
|
|
||||||
completion(mediaReferences.first!)
|
|
||||||
})
|
|
||||||
guard let controller = controller as? AttachmentFileControllerImpl else {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
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)
|
controllerCompletion(controller, controller.mediaPickerContext)
|
||||||
return true
|
return true
|
||||||
case .location:
|
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))
|
completion(.standalone(media: location))
|
||||||
})
|
})
|
||||||
controllerCompletion(controller, controller.mediaPickerContext)
|
controllerCompletion(controller, controller.mediaPickerContext)
|
||||||
|
|
@ -100,9 +134,23 @@ public func presentPollAttachmentScreen(
|
||||||
guard let content = content?.stickers else {
|
guard let content = content?.stickers else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let controller = StickerAttachmentScreen(context: context, mode: .stickers(content), completion: { sticker in
|
let stickerPickerPollSubject: StickerAttachmentScreen.Source.PollMode
|
||||||
completion(sticker)
|
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)
|
controllerCompletion(controller, controller.mediaPickerContext)
|
||||||
})
|
})
|
||||||
return true
|
return true
|
||||||
|
|
@ -113,9 +161,23 @@ public func presentPollAttachmentScreen(
|
||||||
guard let content = content?.emoji else {
|
guard let content = content?.emoji else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let controller = StickerAttachmentScreen(context: context, mode: .emoji(content), completion: { sticker in
|
let stickerPickerPollSubject: StickerAttachmentScreen.Source.PollMode
|
||||||
completion(sticker)
|
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)
|
controllerCompletion(controller, controller.mediaPickerContext)
|
||||||
})
|
})
|
||||||
return true
|
return true
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,10 @@ import ChatPresentationInterfaceState
|
||||||
import PagerComponent
|
import PagerComponent
|
||||||
import FeaturedStickersScreen
|
import FeaturedStickersScreen
|
||||||
import TelegramNotices
|
import TelegramNotices
|
||||||
|
import CounterControllerTitleView
|
||||||
|
import GlassBackgroundComponent
|
||||||
|
import GlassBarButtonComponent
|
||||||
|
import BundleIconComponent
|
||||||
|
|
||||||
final class StickerAttachmentScreenComponent: Component {
|
final class StickerAttachmentScreenComponent: Component {
|
||||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||||
|
|
@ -49,8 +53,10 @@ final class StickerAttachmentScreenComponent: Component {
|
||||||
final class View: UIView, UIScrollViewDelegate {
|
final class View: UIView, UIScrollViewDelegate {
|
||||||
fileprivate let keyboardView: ComponentView<Empty>
|
fileprivate let keyboardView: ComponentView<Empty>
|
||||||
private let keyboardClippingView: KeyboardClippingView
|
private let keyboardClippingView: KeyboardClippingView
|
||||||
|
private let panelBackgroundView: GlassBackgroundView
|
||||||
|
private let panelClippingView: UIView
|
||||||
private let panelHostView: PagerExternalTopPanelContainer
|
private let panelHostView: PagerExternalTopPanelContainer
|
||||||
private let panelSeparatorView: UIView
|
private let cancelButton: ComponentView<Empty>
|
||||||
|
|
||||||
private var component: StickerAttachmentScreenComponent?
|
private var component: StickerAttachmentScreenComponent?
|
||||||
private(set) weak var state: EmptyComponentState?
|
private(set) weak var state: EmptyComponentState?
|
||||||
|
|
@ -109,14 +115,17 @@ final class StickerAttachmentScreenComponent: Component {
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
self.keyboardView = ComponentView<Empty>()
|
self.keyboardView = ComponentView<Empty>()
|
||||||
self.keyboardClippingView = KeyboardClippingView()
|
self.keyboardClippingView = KeyboardClippingView()
|
||||||
|
self.panelBackgroundView = GlassBackgroundView()
|
||||||
|
self.panelClippingView = UIView()
|
||||||
self.panelHostView = PagerExternalTopPanelContainer()
|
self.panelHostView = PagerExternalTopPanelContainer()
|
||||||
self.panelSeparatorView = UIView()
|
self.cancelButton = ComponentView<Empty>()
|
||||||
|
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
||||||
self.addSubview(self.keyboardClippingView)
|
self.addSubview(self.keyboardClippingView)
|
||||||
self.addSubview(self.panelSeparatorView)
|
self.addSubview(self.panelBackgroundView)
|
||||||
self.addSubview(self.panelHostView)
|
self.panelBackgroundView.contentView.addSubview(self.panelClippingView)
|
||||||
|
self.panelClippingView.addSubview(self.panelHostView)
|
||||||
|
|
||||||
self.interaction = ChatEntityKeyboardInputNode.Interaction(
|
self.interaction = ChatEntityKeyboardInputNode.Interaction(
|
||||||
sendSticker: { [weak self] file, _, _, _, _, _, _, _, _ in
|
sendSticker: { [weak self] file, _, _, _, _, _, _, _, _ in
|
||||||
|
|
@ -200,10 +209,7 @@ final class StickerAttachmentScreenComponent: Component {
|
||||||
(self.environment?.controller() as? StickerAttachmentScreen)?.dismiss(animated: true)
|
(self.environment?.controller() as? StickerAttachmentScreen)?.dismiss(animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateContent() {
|
func updateContent(component: StickerAttachmentScreenComponent) {
|
||||||
guard let component = self.component else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.emojiContent?.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction(
|
self.emojiContent?.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction(
|
||||||
performItemAction: { [weak self] groupId, item, _, _, _, _ in
|
performItemAction: { [weak self] groupId, item, _, _, _, _ in
|
||||||
guard let self, let component = self.component else {
|
guard let self, let component = self.component else {
|
||||||
|
|
@ -599,7 +605,7 @@ final class StickerAttachmentScreenComponent: Component {
|
||||||
externalBackground: nil,
|
externalBackground: nil,
|
||||||
externalExpansionView: nil,
|
externalExpansionView: nil,
|
||||||
customContentView: nil,
|
customContentView: nil,
|
||||||
useOpaqueTheme: false,
|
useOpaqueTheme: true,
|
||||||
hideBackground: true,
|
hideBackground: true,
|
||||||
stateContext: nil,
|
stateContext: nil,
|
||||||
addImage: nil
|
addImage: nil
|
||||||
|
|
@ -872,7 +878,7 @@ final class StickerAttachmentScreenComponent: Component {
|
||||||
externalBackground: nil,
|
externalBackground: nil,
|
||||||
externalExpansionView: nil,
|
externalExpansionView: nil,
|
||||||
customContentView: nil,
|
customContentView: nil,
|
||||||
useOpaqueTheme: false,
|
useOpaqueTheme: true,
|
||||||
hideBackground: true,
|
hideBackground: true,
|
||||||
stateContext: nil,
|
stateContext: nil,
|
||||||
addImage: nil
|
addImage: nil
|
||||||
|
|
@ -884,7 +890,6 @@ final class StickerAttachmentScreenComponent: Component {
|
||||||
self.environment = environment
|
self.environment = environment
|
||||||
|
|
||||||
self.backgroundColor = environment.theme.list.plainBackgroundColor
|
self.backgroundColor = environment.theme.list.plainBackgroundColor
|
||||||
self.panelSeparatorView.backgroundColor = .clear
|
|
||||||
|
|
||||||
if self.component == nil {
|
if self.component == nil {
|
||||||
let data = combineLatest(
|
let data = combineLatest(
|
||||||
|
|
@ -933,10 +938,7 @@ final class StickerAttachmentScreenComponent: Component {
|
||||||
}
|
}
|
||||||
self.stickerContent = stickerContent
|
self.stickerContent = stickerContent
|
||||||
}
|
}
|
||||||
Queue.mainQueue().justDispatch {
|
self.updateContent(component: component)
|
||||||
self.updateContent()
|
|
||||||
self.state?.updated()
|
|
||||||
}
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -962,15 +964,15 @@ final class StickerAttachmentScreenComponent: Component {
|
||||||
theme: environment.theme,
|
theme: environment.theme,
|
||||||
strings: environment.strings,
|
strings: environment.strings,
|
||||||
isContentInFocus: true,
|
isContentInFocus: true,
|
||||||
containerInsets: UIEdgeInsets(top: topPanelHeight - 34.0 + topInset, left: 0.0, bottom: 0.0, right: 0.0),
|
containerInsets: UIEdgeInsets(top: topPanelHeight + topInset - 11.0, left: 0.0, bottom: 0.0, right: 0.0),
|
||||||
topPanelInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0),
|
topPanelInsets: UIEdgeInsets(top: 0.0, left: 12.0, bottom: 0.0, right: 12.0),
|
||||||
emojiContent: self.emojiContent,
|
emojiContent: self.emojiContent,
|
||||||
stickerContent: self.stickerContent,
|
stickerContent: self.stickerContent,
|
||||||
maskContent: nil,
|
maskContent: nil,
|
||||||
gifContent: nil,
|
gifContent: nil,
|
||||||
hasRecentGifs: false,
|
hasRecentGifs: false,
|
||||||
availableGifSearchEmojies: [],
|
availableGifSearchEmojies: [],
|
||||||
defaultToEmojiTab: emojiContent != nil,
|
defaultToEmojiTab: self.emojiContent != nil,
|
||||||
externalTopPanelContainer: self.panelHostView,
|
externalTopPanelContainer: self.panelHostView,
|
||||||
externalBottomPanelContainer: nil,
|
externalBottomPanelContainer: nil,
|
||||||
externalTintMaskContainer: nil,
|
externalTintMaskContainer: nil,
|
||||||
|
|
@ -993,6 +995,16 @@ final class StickerAttachmentScreenComponent: Component {
|
||||||
self.forceUpdate = true
|
self.forceUpdate = true
|
||||||
self.searchVisible = searchVisible
|
self.searchVisible = searchVisible
|
||||||
self.state?.updated(transition: transition)
|
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
|
hideTopPanelUpdated: { _, _ in
|
||||||
},
|
},
|
||||||
|
|
@ -1053,28 +1065,57 @@ final class StickerAttachmentScreenComponent: Component {
|
||||||
self.keyboardClippingView.addSubview(keyboardComponentView)
|
self.keyboardClippingView.addSubview(keyboardComponentView)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if panelBackgroundColor.alpha < 0.01 {
|
self.keyboardClippingView.clipsToBounds = false
|
||||||
// self.keyboardClippingView.clipsToBounds = true
|
|
||||||
// } else {
|
|
||||||
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)))
|
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)
|
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: 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)))
|
transition.setFrame(view: self.panelHostView, frame: CGRect(origin: CGPoint(x: -12.0, y: 8.0 - UIScreenPixel), 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.setAlpha(view: self.panelSeparatorView, alpha: topPanelAlpha)
|
let barButtonSize = CGSize(width: 44.0, height: 44.0)
|
||||||
|
let cancelButtonSize = self.cancelButton.update(
|
||||||
transition.setFrame(view: self.panelSeparatorView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight + topInset - UIScreenPixel), size: CGSize(width: keyboardSize.width, height: UIScreenPixel)))
|
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
|
return availableSize
|
||||||
|
|
@ -1096,11 +1137,21 @@ final class StickerAttachmentScreen: ViewControllerComponentContainer, Attachmen
|
||||||
case emoji(EmojiPagerContentComponent)
|
case emoji(EmojiPagerContentComponent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Source: Equatable {
|
||||||
|
enum PollMode: Equatable {
|
||||||
|
case description
|
||||||
|
case quizAnswer
|
||||||
|
case option
|
||||||
|
}
|
||||||
|
|
||||||
|
case poll(PollMode)
|
||||||
|
}
|
||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let mode: Mode
|
private let mode: Mode
|
||||||
private let completion: (AnyMediaReference) -> Void
|
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.context = context
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.completion = completion
|
self.completion = completion
|
||||||
|
|
@ -1109,9 +1160,43 @@ final class StickerAttachmentScreen: ViewControllerComponentContainer, Attachmen
|
||||||
context: context,
|
context: context,
|
||||||
mode: mode,
|
mode: mode,
|
||||||
completion: completion
|
completion: completion
|
||||||
), navigationBarAppearance: .default, theme: .default)
|
), navigationBarAppearance: .transparent, theme: .default)
|
||||||
|
|
||||||
self._hasGlassStyle = true
|
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) {
|
required init(coder: NSCoder) {
|
||||||
|
|
|
||||||
|
|
@ -115,8 +115,8 @@ private class MediaHeaderItemNode: ASDisplayNode {
|
||||||
|
|
||||||
let minimizedTitleOffset: CGFloat = subtitleString == nil ? 6.0 : 0.0
|
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 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: 20.0), size: subtitleLayout.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.titleNode, frame: minimizedTitleFrame)
|
||||||
transition.updateFrame(node: self.subtitleNode, frame: minimizedSubtitleFrame)
|
transition.updateFrame(node: self.subtitleNode, frame: minimizedSubtitleFrame)
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,7 @@ public final class TextFieldComponent: Component {
|
||||||
public let hideKeyboard: Bool
|
public let hideKeyboard: Bool
|
||||||
public let customInputView: UIView?
|
public let customInputView: UIView?
|
||||||
public let placeholder: NSAttributedString?
|
public let placeholder: NSAttributedString?
|
||||||
|
public let placeholderVerticalOffset: CGFloat
|
||||||
public let prefix: NSAttributedString?
|
public let prefix: NSAttributedString?
|
||||||
public let suffix: NSAttributedString?
|
public let suffix: NSAttributedString?
|
||||||
public let resetText: NSAttributedString?
|
public let resetText: NSAttributedString?
|
||||||
|
|
@ -185,6 +186,7 @@ public final class TextFieldComponent: Component {
|
||||||
hideKeyboard: Bool,
|
hideKeyboard: Bool,
|
||||||
customInputView: UIView?,
|
customInputView: UIView?,
|
||||||
placeholder: NSAttributedString? = nil,
|
placeholder: NSAttributedString? = nil,
|
||||||
|
placeholderVerticalOffset: CGFloat = 0.0,
|
||||||
prefix: NSAttributedString? = nil,
|
prefix: NSAttributedString? = nil,
|
||||||
suffix: NSAttributedString? = nil,
|
suffix: NSAttributedString? = nil,
|
||||||
resetText: NSAttributedString?,
|
resetText: NSAttributedString?,
|
||||||
|
|
@ -216,6 +218,7 @@ public final class TextFieldComponent: Component {
|
||||||
self.hideKeyboard = hideKeyboard
|
self.hideKeyboard = hideKeyboard
|
||||||
self.customInputView = customInputView
|
self.customInputView = customInputView
|
||||||
self.placeholder = placeholder
|
self.placeholder = placeholder
|
||||||
|
self.placeholderVerticalOffset = placeholderVerticalOffset
|
||||||
self.prefix = prefix
|
self.prefix = prefix
|
||||||
self.suffix = suffix
|
self.suffix = suffix
|
||||||
self.resetText = resetText
|
self.resetText = resetText
|
||||||
|
|
@ -271,6 +274,9 @@ public final class TextFieldComponent: Component {
|
||||||
if lhs.placeholder != rhs.placeholder {
|
if lhs.placeholder != rhs.placeholder {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.placeholderVerticalOffset != rhs.placeholderVerticalOffset {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.prefix != rhs.prefix {
|
if lhs.prefix != rhs.prefix {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -365,6 +371,7 @@ public final class TextFieldComponent: Component {
|
||||||
self.textView.layer.isOpaque = false
|
self.textView.layer.isOpaque = false
|
||||||
self.textView.indicatorStyle = .white
|
self.textView.indicatorStyle = .white
|
||||||
self.textView.scrollIndicatorInsets = UIEdgeInsets(top: 9.0, left: 0.0, bottom: 9.0, right: 0.0)
|
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)
|
self.inputMenu = TextInputMenu(hasSpoilers: true, hasQuotes: true)
|
||||||
|
|
||||||
|
|
@ -1587,27 +1594,28 @@ public final class TextFieldComponent: Component {
|
||||||
}
|
}
|
||||||
let placeholderSize = placeholder.update(
|
let placeholderSize = placeholder.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(MultilineTextComponent(
|
component: AnyComponent(
|
||||||
text: .plain(placeholderValue)
|
Text(attributedString: placeholderValue)
|
||||||
)),
|
),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: textFrame.size
|
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 let placeholderView = placeholder.view {
|
||||||
if placeholderView.superview == nil {
|
if placeholderView.superview == nil {
|
||||||
placeholderView.isUserInteractionEnabled = false
|
placeholderView.isUserInteractionEnabled = false
|
||||||
placeholderView.layer.anchorPoint = CGPoint()
|
|
||||||
self.textView.insertSubview(placeholderView, at: 0)
|
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.bounds = CGRect(origin: CGPoint(), size: placeholderFrame.size)
|
||||||
|
|
||||||
placeholderView.isHidden = self.textView.textStorage.length != 0
|
placeholderView.isHidden = self.textView.textStorage.length != 0
|
||||||
}
|
}
|
||||||
} else if let placeholder = self.placeholder {
|
} else {
|
||||||
self.placeholder = nil
|
if let placeholder = self.placeholder {
|
||||||
placeholder.view?.removeFromSuperview()
|
self.placeholder = nil
|
||||||
|
placeholder.view?.removeFromSuperview()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.updateEmojiSuggestion(transition: .immediate)
|
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))
|
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
|
let updateInputTextState = self.chatPresentationInterfaceState.interfaceState.effectiveInputState != chatPresentationInterfaceState.interfaceState.effectiveInputState
|
||||||
self.chatPresentationInterfaceState = chatPresentationInterfaceState
|
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: {
|
let controller = strongSelf.context.sharedContext.makeAttachmentFileController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, audio: true, bannedSendMedia: bannedSendFiles, presentGallery: {
|
||||||
}, presentFiles: { [weak self, weak attachmentController] in
|
}, presentFiles: { [weak self, weak attachmentController] in
|
||||||
attachmentController?.dismiss(animated: true)
|
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
|
}, presentDocumentScanner: nil, send: { [weak self] mediaReferences in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
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(
|
let _ = (self.context.engine.data.get(
|
||||||
TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId),
|
TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId),
|
||||||
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false),
|
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false),
|
||||||
|
|
@ -1167,7 +1167,7 @@ extension ChatControllerImpl {
|
||||||
let (accountPeer, limits, premiumLimits) = result
|
let (accountPeer, limits, premiumLimits) = result
|
||||||
let isPremium = accountPeer?.isPremium ?? false
|
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 {
|
if let strongSelf = self, !urls.isEmpty {
|
||||||
var signals: [Signal<ICloudFileDescription?, NoError>] = []
|
var signals: [Signal<ICloudFileDescription?, NoError>] = []
|
||||||
for url in urls {
|
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 {
|
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? {
|
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