mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Attachment menu improvements
This commit is contained in:
parent
3ddf3518c5
commit
2c3a1c802b
@ -7304,3 +7304,24 @@ Sorry for the inconvenience.";
|
||||
|
||||
"Attachment.OpenSettings" = "Go to Settings";
|
||||
"Attachment.OpenCamera" = "Open Camera";
|
||||
|
||||
"Attachment.DeselectedPhotos_1" = "%@ photo deselected";
|
||||
"Attachment.DeselectedPhotos_2" = "%@ photos deselected";
|
||||
"Attachment.DeselectedPhotos_3_10" = "%@ photos deselected";
|
||||
"Attachment.DeselectedPhotos_any" = "%@ photos deselected";
|
||||
"Attachment.DeselectedPhotos_many" = "%@ photos deselected";
|
||||
"Attachment.DeselectedPhotos_0" = "%@ photos deselected";
|
||||
|
||||
"Attachment.DeselectedVideos_1" = "%@ video deselected";
|
||||
"Attachment.DeselectedVideos_2" = "%@ videos deselected";
|
||||
"Attachment.DeselectedVideos_3_10" = "%@ videos deselected";
|
||||
"Attachment.DeselectedVideos_any" = "%@ videos deselected";
|
||||
"Attachment.DeselectedVideos_many" = "%@ videos deselected";
|
||||
"Attachment.DeselectedVideos_0" = "%@ videos deselected";
|
||||
|
||||
"Attachment.DeselectedItems_1" = "%@ item deselected";
|
||||
"Attachment.DeselectedItems_2" = "%@ items deselected";
|
||||
"Attachment.DeselectedItems_3_10" = "%@ items deselected";
|
||||
"Attachment.DeselectedItems_any" = "%@ items deselected";
|
||||
"Attachment.DeselectedItems_many" = "%@ items deselected";
|
||||
"Attachment.DeselectedItems_0" = "%@ items deselected";
|
||||
|
@ -23,10 +23,15 @@ public enum AttachmentButtonType: Equatable {
|
||||
public protocol AttachmentContainable: ViewController {
|
||||
var requestAttachmentMenuExpansion: () -> Void { get set }
|
||||
|
||||
func resetForReuse()
|
||||
func prepareForReuse()
|
||||
}
|
||||
|
||||
public extension AttachmentContainable {
|
||||
func resetForReuse() {
|
||||
|
||||
}
|
||||
|
||||
func prepareForReuse() {
|
||||
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ private final class AttachButtonComponent: CombinedComponent {
|
||||
availableSize: CGSize(width: 30.0, height: 30.0),
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
|
||||
let title = title.update(
|
||||
component: Text(
|
||||
text: name,
|
||||
|
@ -1106,6 +1106,11 @@ open class NavigationBar: ASDisplayNode {
|
||||
let leftButtonSize = self.leftButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight), isLandscape: isLandscape)
|
||||
leftTitleInset = leftButtonSize.width + leftButtonInset + 1.0
|
||||
|
||||
var transition = transition
|
||||
if self.leftButtonNode.frame.width.isZero {
|
||||
transition = .immediate
|
||||
}
|
||||
|
||||
self.leftButtonNode.alpha = 1.0
|
||||
transition.updateFrame(node: self.leftButtonNode, frame: CGRect(origin: CGPoint(x: leftButtonInset, y: contentVerticalOrigin + floor((nominalHeight - leftButtonSize.height) / 2.0)), size: leftButtonSize))
|
||||
}
|
||||
@ -1118,6 +1123,11 @@ open class NavigationBar: ASDisplayNode {
|
||||
let rightButtonSize = self.rightButtonNode.updateLayout(constrainedSize: (CGSize(width: size.width, height: nominalHeight)), isLandscape: isLandscape)
|
||||
rightTitleInset = rightButtonSize.width + leftButtonInset + 1.0
|
||||
self.rightButtonNode.alpha = 1.0
|
||||
|
||||
var transition = transition
|
||||
if self.rightButtonNode.frame.width.isZero {
|
||||
transition = .immediate
|
||||
}
|
||||
transition.updateFrame(node: self.rightButtonNode, frame: CGRect(origin: CGPoint(x: size.width - leftButtonInset - rightButtonSize.width, y: contentVerticalOrigin + floor((nominalHeight - rightButtonSize.height) / 2.0)), size: rightButtonSize))
|
||||
}
|
||||
|
||||
@ -1182,6 +1192,10 @@ open class NavigationBar: ASDisplayNode {
|
||||
self.titleNode.alpha = progress * progress
|
||||
}
|
||||
} else {
|
||||
var transition = transition
|
||||
if self.titleNode.frame.width.isZero {
|
||||
transition = .immediate
|
||||
}
|
||||
self.titleNode.alpha = 1.0
|
||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize))
|
||||
}
|
||||
|
@ -531,6 +531,9 @@ NSString *const PGCameraAdjustingFocusKey = @"adjustingFocus";
|
||||
{
|
||||
TGDispatchOnMainThread(^
|
||||
{
|
||||
if (!_subscribedForCameraChanges) {
|
||||
return;
|
||||
}
|
||||
if ([keyPath isEqualToString:PGCameraAdjustingFocusKey])
|
||||
{
|
||||
bool adjustingFocus = [[change objectForKey:NSKeyValueChangeNewKey] isEqualToNumber:@YES];
|
||||
|
@ -337,7 +337,7 @@ public final class LocationPickerController: ViewController, AttachmentContainab
|
||||
self.interaction?.openSearch()
|
||||
}
|
||||
|
||||
public func prepareForReuse() {
|
||||
public func resetForReuse() {
|
||||
self.interaction?.updateMapMode(.map)
|
||||
self.interaction?.dismissSearch()
|
||||
self.scrollToTop?()
|
||||
|
@ -127,7 +127,7 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
self.checkNode = checkNode
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
|
||||
|
||||
if let asset = self.asset, let interaction = self.interaction, let selectionState = interaction.selectionState {
|
||||
let selected = selectionState.isIdentifierSelected(asset.localIdentifier)
|
||||
if let legacyAsset = TGMediaAsset(phAsset: asset) {
|
||||
@ -177,7 +177,7 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
}
|
||||
}, error: { _ in
|
||||
}, completed: nil)!
|
||||
|
||||
|
||||
return ActionDisposable {
|
||||
disposable.dispose()
|
||||
}
|
||||
@ -185,9 +185,9 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
return EmptyDisposable
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let scale = min(2.0, UIScreenScale)
|
||||
let targetSize = CGSize(width: 140.0 * scale, height: 140.0 * scale)
|
||||
let targetSize = CGSize(width: 128.0 * scale, height: 128.0 * scale)
|
||||
let originalSignal = assetImage(fetchResult: fetchResult, index: index, targetSize: targetSize, exact: false)
|
||||
let imageSignal: Signal<UIImage?, NoError> = editedSignal
|
||||
|> mapToSignal { result in
|
||||
|
@ -58,7 +58,7 @@ final class MediaPickerManageNode: ASDisplayNode {
|
||||
|
||||
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(15.0), textColor: theme.list.freeTextColor, paragraphAlignment: .left)
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - 16.0 - buttonWidth - 26.0, height: layout.size.height))
|
||||
let panelHeight = max(64.0, textSize.height + 10.0)
|
||||
let panelHeight = max(64.0, textSize.height + 24.0)
|
||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + 16.0, y: floorToScreenPixels((panelHeight - textSize.height) / 2.0) - 5.0), size: textSize))
|
||||
|
||||
if themeUpdated {
|
||||
|
@ -8,6 +8,7 @@ import SwiftSignalKit
|
||||
import AccountContext
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import TelegramStringFormatting
|
||||
import MergeLists
|
||||
import Photos
|
||||
import PhotosUI
|
||||
@ -75,8 +76,11 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
private var presentationData: PresentationData
|
||||
private var presentationDataDisposable: Disposable?
|
||||
|
||||
fileprivate var interaction: MediaPickerInteraction?
|
||||
|
||||
private let peer: EnginePeer?
|
||||
private let chatLocation: ChatLocation?
|
||||
private let bannedSendMedia: (Int32, Bool)?
|
||||
|
||||
private let titleView: MediaPickerTitleView
|
||||
private let moreButtonNode: MediaPickerMoreButtonNode
|
||||
@ -102,19 +106,19 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
|
||||
enum State {
|
||||
case noAccess(cameraAccess: AVAuthorizationStatus?)
|
||||
case assets(fetchResult: PHFetchResult<PHAsset>?, mediaAccess: PHAuthorizationStatus, cameraAccess: AVAuthorizationStatus?)
|
||||
case assets(fetchResult: PHFetchResult<PHAsset>?, preload: Bool, mediaAccess: PHAuthorizationStatus, cameraAccess: AVAuthorizationStatus?)
|
||||
}
|
||||
|
||||
private weak var controller: MediaPickerScreen?
|
||||
fileprivate var interaction: MediaPickerInteraction?
|
||||
private var presentationData: PresentationData
|
||||
private let mediaAssetsContext: MediaAssetsContext
|
||||
|
||||
private let backgroundNode: NavigationBackgroundNode
|
||||
private let gridNode: GridNode
|
||||
private var cameraView: TGAttachmentCameraView?
|
||||
fileprivate var cameraView: TGAttachmentCameraView?
|
||||
private var placeholderNode: MediaPickerPlaceholderNode?
|
||||
private var manageNode: MediaPickerManageNode?
|
||||
private var bannedTextNode: ImmediateTextNode?
|
||||
|
||||
private var selectionNode: MediaPickerSelectedListNode?
|
||||
|
||||
@ -123,6 +127,8 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
private var enqueuedTransactions: [MediaPickerGridTransaction] = []
|
||||
private var state: State?
|
||||
|
||||
private var preloadPromise = ValuePromise<Bool>(true)
|
||||
|
||||
private var itemsDisposable: Disposable?
|
||||
private var selectionChangedDisposable: Disposable?
|
||||
private var itemsDimensionsUpdatedDisposable: Disposable?
|
||||
@ -131,7 +137,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
private let hiddenMediaId = Promise<String?>(nil)
|
||||
|
||||
private var didSetReady = false
|
||||
private let _ready = Promise<Bool>()
|
||||
private let _ready = Promise<Bool>(true)
|
||||
var ready: Promise<Bool> {
|
||||
return self._ready
|
||||
}
|
||||
@ -154,45 +160,18 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.gridNode)
|
||||
|
||||
self.interaction = MediaPickerInteraction(openMedia: { [weak self] fetchResult, index, immediateThumbnail in
|
||||
self?.openMedia(fetchResult: fetchResult, index: index, immediateThumbnail: immediateThumbnail)
|
||||
}, openSelectedMedia: { [weak self] item, immediateThumbnail in
|
||||
self?.openSelectedMedia(item: item, immediateThumbnail: immediateThumbnail)
|
||||
}, toggleSelection: { [weak self] item, value in
|
||||
if let strongSelf = self {
|
||||
strongSelf.interaction?.selectionState?.setItem(item, selected: value)
|
||||
}
|
||||
}, sendSelected: { [weak self] currentItem, silently, scheduleTime, animated in
|
||||
if let strongSelf = self, let selectionState = strongSelf.interaction?.selectionState {
|
||||
if let currentItem = currentItem {
|
||||
selectionState.setItem(currentItem, selected: true)
|
||||
}
|
||||
strongSelf.send(silently: silently, scheduleTime: scheduleTime, animated: animated)
|
||||
}
|
||||
}, schedule: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.controller?.presentSchedulePicker(false, { [weak self] time in
|
||||
self?.interaction?.sendSelected(nil, false, time, true)
|
||||
})
|
||||
}
|
||||
}, dismissInput: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.dismissInput()
|
||||
}
|
||||
}, selectionState: TGMediaSelectionContext(), editingState: TGMediaEditingContext())
|
||||
self.interaction?.selectionState?.grouping = true
|
||||
|
||||
|
||||
let preloadPromise = self.preloadPromise
|
||||
let updatedState = combineLatest(mediaAssetsContext.mediaAccess(), mediaAssetsContext.cameraAccess())
|
||||
|> mapToSignal { mediaAccess, cameraAccess -> Signal<State, NoError> in
|
||||
if case .notDetermined = mediaAccess {
|
||||
return .single(.assets(fetchResult: nil, mediaAccess: mediaAccess, cameraAccess: cameraAccess))
|
||||
return .single(.assets(fetchResult: nil, preload: false, mediaAccess: mediaAccess, cameraAccess: cameraAccess))
|
||||
} else if [.restricted, .denied].contains(mediaAccess) {
|
||||
return .single(.noAccess(cameraAccess: cameraAccess))
|
||||
} else {
|
||||
return mediaAssetsContext.recentAssets()
|
||||
|> map { fetchResult in
|
||||
return .assets(fetchResult: fetchResult, mediaAccess: mediaAccess, cameraAccess: cameraAccess)
|
||||
return combineLatest(mediaAssetsContext.recentAssets(), preloadPromise.get())
|
||||
|> map { fetchResult, preload in
|
||||
return .assets(fetchResult: fetchResult, preload: preload, mediaAccess: mediaAccess, cameraAccess: cameraAccess)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -212,7 +191,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
self.hiddenMediaDisposable = (self.hiddenMediaId.get()
|
||||
|> deliverOnMainQueue).start(next: { [weak self] id in
|
||||
if let strongSelf = self {
|
||||
strongSelf.interaction?.hiddenMediaId = id
|
||||
strongSelf.controller?.interaction?.hiddenMediaId = id
|
||||
|
||||
strongSelf.gridNode.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? MediaPickerGridItemNode {
|
||||
@ -224,7 +203,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
}
|
||||
})
|
||||
|
||||
if let selectionState = self.interaction?.selectionState {
|
||||
if let selectionState = self.controller?.interaction?.selectionState {
|
||||
func selectionChangedSignal(selectionState: TGMediaSelectionContext) -> Signal<Void, NoError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = selectionState.selectionChangedSignal()?.start(next: { next in
|
||||
@ -244,7 +223,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
})
|
||||
}
|
||||
|
||||
if let editingState = self.interaction?.editingState {
|
||||
if let editingState = self.controller?.interaction?.editingState {
|
||||
func itemsDimensionsUpdatedSignal(editingState: TGMediaEditingContext) -> Signal<Void, NoError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = editingState.cropAdjustmentsUpdatedSignal()?.start(next: { next in
|
||||
@ -290,11 +269,11 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
cameraView.startPreview()
|
||||
|
||||
self.gridNode.scrollView.addSubview(cameraView)
|
||||
|
||||
|
||||
// self.controller?.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate)
|
||||
}
|
||||
|
||||
private func dismissInput() {
|
||||
fileprivate func dismissInput() {
|
||||
self.view.window?.endEditing(true)
|
||||
}
|
||||
|
||||
@ -302,7 +281,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
private var requestedCameraAccess = false
|
||||
|
||||
private func updateState(_ state: State) {
|
||||
guard let interaction = self.interaction, let controller = self.controller else {
|
||||
guard let controller = self.controller, let interaction = controller.interaction else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -325,14 +304,17 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
self.requestedCameraAccess = true
|
||||
self.mediaAssetsContext.requestCameraAccess()
|
||||
}
|
||||
case let .assets(fetchResult, mediaAccess, cameraAccess):
|
||||
case let .assets(fetchResult, preload, mediaAccess, cameraAccess):
|
||||
if let fetchResult = fetchResult {
|
||||
for i in 0 ..< fetchResult.count {
|
||||
entries.append(MediaPickerGridEntry(stableId: stableId, content: .asset(fetchResult, fetchResult.count - i - 1)))
|
||||
let totalCount = fetchResult.count
|
||||
let count = preload ? min(10, totalCount) : totalCount
|
||||
|
||||
for i in 0 ..< count {
|
||||
entries.append(MediaPickerGridEntry(stableId: stableId, content: .asset(fetchResult, totalCount - i - 1)))
|
||||
stableId += 1
|
||||
}
|
||||
|
||||
if case let .assets(previousFetchResult, _, previousCameraAccess) = previousState, previousFetchResult == nil || previousCameraAccess != cameraAccess {
|
||||
if case let .assets(previousFetchResult, _, _, previousCameraAccess) = previousState, previousFetchResult == nil || previousCameraAccess != cameraAccess {
|
||||
updateLayout = true
|
||||
}
|
||||
|
||||
@ -365,7 +347,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
}
|
||||
self.selectionNode?.updateSelectionState()
|
||||
|
||||
let count = Int32(self.interaction?.selectionState?.count() ?? 0)
|
||||
let count = Int32(self.controller?.interaction?.selectionState?.count() ?? 0)
|
||||
self.controller?.updateSelectionState(count: count)
|
||||
|
||||
if let (layout, navigationBarHeight) = self.validLayout {
|
||||
@ -387,7 +369,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
let selectionNode = MediaPickerSelectedListNode(context: controller.context)
|
||||
selectionNode.alpha = 0.0
|
||||
selectionNode.isUserInteractionEnabled = false
|
||||
selectionNode.interaction = self.interaction
|
||||
selectionNode.interaction = self.controller?.interaction
|
||||
self.insertSubnode(selectionNode, aboveSubnode: self.gridNode)
|
||||
self.selectionNode = selectionNode
|
||||
|
||||
@ -405,8 +387,8 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
}
|
||||
}
|
||||
|
||||
private func openMedia(fetchResult: PHFetchResult<PHAsset>, index: Int, immediateThumbnail: UIImage?) {
|
||||
guard let controller = self.controller, let interaction = self.interaction, let (layout, _) = self.validLayout else {
|
||||
fileprivate func openMedia(fetchResult: PHFetchResult<PHAsset>, index: Int, immediateThumbnail: UIImage?) {
|
||||
guard let controller = self.controller, let interaction = controller.interaction, let (layout, _) = self.validLayout else {
|
||||
return
|
||||
}
|
||||
Queue.mainQueue().justDispatch {
|
||||
@ -422,15 +404,15 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
return self?.transitionView(for: identifier)
|
||||
}, completed: { [weak self] result, silently, scheduleTime in
|
||||
if let strongSelf = self {
|
||||
strongSelf.interaction?.sendSelected(result, silently, scheduleTime, false)
|
||||
strongSelf.controller?.interaction?.sendSelected(result, silently, scheduleTime, false)
|
||||
}
|
||||
}, presentStickers: controller.presentStickers, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in
|
||||
self?.controller?.present(c, in: .window(.root), with: a)
|
||||
})
|
||||
}
|
||||
|
||||
private func openSelectedMedia(item: TGMediaSelectableItem, immediateThumbnail: UIImage?) {
|
||||
guard let controller = self.controller, let interaction = self.interaction, let (layout, _) = self.validLayout else {
|
||||
fileprivate func openSelectedMedia(item: TGMediaSelectableItem, immediateThumbnail: UIImage?) {
|
||||
guard let controller = self.controller, let interaction = controller.interaction, let (layout, _) = self.validLayout else {
|
||||
return
|
||||
}
|
||||
Queue.mainQueue().justDispatch {
|
||||
@ -445,7 +427,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
return self?.transitionView(for: identifier)
|
||||
}, completed: { [weak self] result, silently, scheduleTime in
|
||||
if let strongSelf = self {
|
||||
strongSelf.interaction?.sendSelected(result, silently, scheduleTime, false)
|
||||
strongSelf.controller?.interaction?.sendSelected(result, silently, scheduleTime, false)
|
||||
}
|
||||
}, presentStickers: controller.presentStickers, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in
|
||||
self?.controller?.present(c, in: .window(.root), with: a)
|
||||
@ -453,7 +435,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
}
|
||||
|
||||
fileprivate func send(asFile: Bool = false, silently: Bool, scheduleTime: Int32?, animated: Bool) {
|
||||
guard let signals = TGMediaAssetsController.resultSignals(for: self.interaction?.selectionState, editingContext: self.interaction?.editingState, intent: asFile ? TGMediaAssetsControllerSendFileIntent : TGMediaAssetsControllerSendMediaIntent, currentItem: nil, storeAssets: true, convertToJpeg: false, descriptionGenerator: legacyAssetPickerItemGenerator(), saveEditedPhotos: true) else {
|
||||
guard let signals = TGMediaAssetsController.resultSignals(for: self.controller?.interaction?.selectionState, editingContext: self.controller?.interaction?.editingState, intent: asFile ? TGMediaAssetsControllerSendFileIntent : TGMediaAssetsControllerSendMediaIntent, currentItem: nil, storeAssets: true, convertToJpeg: false, descriptionGenerator: legacyAssetPickerItemGenerator(), saveEditedPhotos: true) else {
|
||||
return
|
||||
}
|
||||
self.controller?.legacyCompletion(signals, silently, scheduleTime)
|
||||
@ -549,11 +531,42 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
var cameraRect: CGRect? = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: 0.0), size: CGSize(width: itemWidth, height: itemWidth * 2.0 + 1.0))
|
||||
|
||||
var manageHeight: CGFloat = 0.0
|
||||
if case let .assets(_, mediaAccess, cameraAccess) = self.state {
|
||||
if case let .assets(_, _, mediaAccess, cameraAccess) = self.state {
|
||||
if cameraAccess == nil {
|
||||
cameraRect = nil
|
||||
}
|
||||
if case .notDetermined = mediaAccess {
|
||||
if let (untilDate, personal) = self.controller?.bannedSendMedia {
|
||||
self.gridNode.alpha = 0.3
|
||||
self.gridNode.isUserInteractionEnabled = false
|
||||
|
||||
let banDescription: String
|
||||
if untilDate != 0 && untilDate != Int32.max {
|
||||
banDescription = self.presentationData.strings.Conversation_RestrictedMediaTimed(stringForFullDate(timestamp: untilDate, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat)).string
|
||||
} else if personal {
|
||||
banDescription = self.presentationData.strings.Conversation_RestrictedMedia
|
||||
} else {
|
||||
banDescription = self.presentationData.strings.Conversation_DefaultRestrictedMedia
|
||||
}
|
||||
|
||||
let bannedTextNode: ImmediateTextNode
|
||||
if let current = self.bannedTextNode {
|
||||
bannedTextNode = current
|
||||
} else {
|
||||
bannedTextNode = ImmediateTextNode()
|
||||
bannedTextNode.maximumNumberOfLines = 0
|
||||
bannedTextNode.textAlignment = .center
|
||||
self.bannedTextNode = bannedTextNode
|
||||
self.addSubnode(bannedTextNode)
|
||||
}
|
||||
|
||||
bannedTextNode.attributedText = NSAttributedString(string: banDescription, font: Font.regular(15.0), textColor: self.presentationData.theme.list.freeTextColor, paragraphAlignment: .center)
|
||||
|
||||
let bannedTextSize = bannedTextNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - 16.0, height: layout.size.height))
|
||||
|
||||
manageHeight = max(44.0, bannedTextSize.height + 20.0)
|
||||
|
||||
transition.updateFrame(node: bannedTextNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - bannedTextSize.width) / 2.0), y: insets.top + floorToScreenPixels((manageHeight - bannedTextSize.height) / 2.0) - 4.0), size: bannedTextSize))
|
||||
} else if case .notDetermined = mediaAccess {
|
||||
|
||||
} else {
|
||||
if case .limited = mediaAccess {
|
||||
@ -604,7 +617,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
transition.updateFrame(node: self.backgroundNode, frame: bounds)
|
||||
self.backgroundNode.update(size: bounds.size, transition: transition)
|
||||
|
||||
self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: bounds.size, insets: gridInsets, scrollIndicatorInsets: nil, preloadSize: 200.0, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemWidth), fillWidth: true, lineSpacing: itemSpacing, itemSpacing: itemSpacing), cutout: cameraRect), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil, updateOpaqueState: nil, synchronousLoads: false), completion: { [weak self] _ in
|
||||
self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: bounds.size, insets: gridInsets, scrollIndicatorInsets: nil, preloadSize: itemWidth, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemWidth), fillWidth: true, lineSpacing: itemSpacing, itemSpacing: itemSpacing), cutout: cameraRect), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil, updateOpaqueState: nil, synchronousLoads: false), completion: { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -612,12 +625,16 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
strongSelf.didSetReady = true
|
||||
Queue.mainQueue().justDispatch {
|
||||
strongSelf._ready.set(.single(true))
|
||||
|
||||
Queue.mainQueue().after(0.5, {
|
||||
strongSelf.preloadPromise.set(false)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if let selectionNode = self.selectionNode {
|
||||
let selectedItems = self.interaction?.selectionState?.selectedItems() as? [TGMediaSelectableItem] ?? []
|
||||
let selectedItems = self.controller?.interaction?.selectionState?.selectedItems() as? [TGMediaSelectableItem] ?? []
|
||||
let updateSelectionNode = {
|
||||
selectionNode.updateLayout(size: bounds.size, insets: cleanGridInsets, items: selectedItems, grouped: self.controller?.groupedValue ?? true, theme: self.presentationData.theme, wallpaper: self.presentationData.chatWallpaper, bubbleCorners: self.presentationData.chatBubbleCorners, transition: transition)
|
||||
}
|
||||
@ -690,7 +707,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
private var groupedValue: Bool = true {
|
||||
didSet {
|
||||
self.groupedPromise.set(self.groupedValue)
|
||||
self.controllerNode.interaction?.selectionState?.grouping = self.groupedValue
|
||||
self.interaction?.selectionState?.grouping = self.groupedValue
|
||||
|
||||
if let layout = self.validLayout {
|
||||
self.containerLayoutUpdated(layout, transition: .immediate)
|
||||
@ -699,11 +716,12 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
}
|
||||
private let groupedPromise = ValuePromise<Bool>(true)
|
||||
|
||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peer: EnginePeer?, chatLocation: ChatLocation?) {
|
||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peer: EnginePeer?, chatLocation: ChatLocation?, bannedSendMedia: (Int32, Bool)?) {
|
||||
self.context = context
|
||||
self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.peer = peer
|
||||
self.chatLocation = chatLocation
|
||||
self.bannedSendMedia = bannedSendMedia
|
||||
|
||||
self.titleView = MediaPickerTitleView(theme: self.presentationData.theme, segments: [self.presentationData.strings.Attachment_AllMedia, self.presentationData.strings.Attachment_SelectedMedia(1)], selectedIndex: 0)
|
||||
self.titleView.title = self.presentationData.strings.Attachment_Gallery
|
||||
@ -763,6 +781,34 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.interaction = MediaPickerInteraction(openMedia: { [weak self] fetchResult, index, immediateThumbnail in
|
||||
self?.controllerNode.openMedia(fetchResult: fetchResult, index: index, immediateThumbnail: immediateThumbnail)
|
||||
}, openSelectedMedia: { [weak self] item, immediateThumbnail in
|
||||
self?.controllerNode.openSelectedMedia(item: item, immediateThumbnail: immediateThumbnail)
|
||||
}, toggleSelection: { [weak self] item, value in
|
||||
if let strongSelf = self {
|
||||
strongSelf.interaction?.selectionState?.setItem(item, selected: value)
|
||||
}
|
||||
}, sendSelected: { [weak self] currentItem, silently, scheduleTime, animated in
|
||||
if let strongSelf = self, let selectionState = strongSelf.interaction?.selectionState {
|
||||
if let currentItem = currentItem {
|
||||
selectionState.setItem(currentItem, selected: true)
|
||||
}
|
||||
strongSelf.controllerNode.send(silently: silently, scheduleTime: scheduleTime, animated: animated)
|
||||
}
|
||||
}, schedule: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.presentSchedulePicker(false, { [weak self] time in
|
||||
self?.interaction?.sendSelected(nil, false, time, true)
|
||||
})
|
||||
}
|
||||
}, dismissInput: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.controllerNode.dismissInput()
|
||||
}
|
||||
}, selectionState: TGMediaSelectionContext(), editingState: TGMediaEditingContext())
|
||||
self.interaction?.selectionState?.grouping = true
|
||||
}
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
@ -811,12 +857,18 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
self.dismiss()
|
||||
}
|
||||
|
||||
public func prepareForReuse() {
|
||||
public func resetForReuse() {
|
||||
if let webSearchController = self.webSearchController {
|
||||
self.webSearchController = nil
|
||||
webSearchController.dismiss()
|
||||
}
|
||||
self.scrollToTop?()
|
||||
|
||||
self.controllerNode.cameraView?.pausePreview()
|
||||
}
|
||||
|
||||
public func prepareForReuse() {
|
||||
self.controllerNode.cameraView?.resumePreview()
|
||||
}
|
||||
|
||||
@objc private func searchOrMorePressed(node: ContextReferenceContentNode, gesture: ContextGesture?) {
|
||||
@ -881,7 +933,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
}
|
||||
|
||||
public var mediaPickerContext: AttachmentMediaPickerContext? {
|
||||
if let interaction = self.controllerNode.interaction {
|
||||
if let interaction = self.interaction {
|
||||
return MediaPickerContext(interaction: interaction)
|
||||
} else {
|
||||
return nil
|
||||
|
@ -189,7 +189,7 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDelegate {
|
||||
private let context: AccountContext
|
||||
|
||||
fileprivate let wallpaperBackgroundNode: WallpaperBackgroundNode
|
||||
@ -226,9 +226,11 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
|
||||
self.scrollNode.view.delegate = self
|
||||
self.scrollNode.view.panGestureRecognizer.cancelsTouchesInView = true
|
||||
|
||||
self.view.addGestureRecognizer(ReorderingGestureRecognizer(shouldBegin: { [weak self] point in
|
||||
if let strongSelf = self, !strongSelf.scrollNode.view.isTracking {
|
||||
if let strongSelf = self, !strongSelf.scrollNode.view.isDragging {
|
||||
let point = strongSelf.view.convert(point, to: strongSelf.scrollNode.view)
|
||||
for (_, itemNode) in strongSelf.itemNodes {
|
||||
if itemNode.frame.contains(point) {
|
||||
return (true, true, itemNode)
|
||||
@ -242,20 +244,54 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}, began: { [weak self] itemNode in
|
||||
self?.beginReordering(itemNode: itemNode)
|
||||
}, ended: { [weak self] point in
|
||||
self?.endReordering(point: point)
|
||||
if let strongSelf = self {
|
||||
if var point = point {
|
||||
point = strongSelf.view.convert(point, to: strongSelf.scrollNode.view)
|
||||
strongSelf.endReordering(point: point)
|
||||
} else {
|
||||
strongSelf.endReordering(point: nil)
|
||||
}
|
||||
}
|
||||
}, moved: { [weak self] offset in
|
||||
self?.updateReordering(offset: offset)
|
||||
}))
|
||||
|
||||
Queue.mainQueue().after(0.1, {
|
||||
self.updateAbsoluteRects()
|
||||
})
|
||||
}
|
||||
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||
self.interaction?.dismissInput()
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
self.updateAbsoluteRects()
|
||||
}
|
||||
|
||||
func scrollToTop(animated: Bool) {
|
||||
self.scrollNode.view.setContentOffset(CGPoint(), animated: animated)
|
||||
}
|
||||
|
||||
func updateAbsoluteRects() {
|
||||
guard let messageNodes = self.messageNodes, let (size, _, _, _, _, _, _) = self.validLayout else {
|
||||
return
|
||||
}
|
||||
|
||||
for itemNode in messageNodes {
|
||||
var absoluteRect = itemNode.frame
|
||||
if let supernode = self.supernode {
|
||||
absoluteRect = supernode.convert(itemNode.bounds, from: itemNode)
|
||||
}
|
||||
absoluteRect.origin.y = size.height - absoluteRect.origin.y - absoluteRect.size.height
|
||||
itemNode.updateAbsoluteRect(absoluteRect, within: self.bounds.size)
|
||||
}
|
||||
}
|
||||
|
||||
private func beginReordering(itemNode: MediaPickerSelectedItemNode) {
|
||||
self.isReordering = true
|
||||
|
||||
@ -265,7 +301,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
let reorderNode = ReorderingItemNode(itemNode: itemNode, initialLocation: itemNode.frame.origin)
|
||||
self.reorderNode = reorderNode
|
||||
self.addSubnode(reorderNode)
|
||||
self.scrollNode.addSubnode(reorderNode)
|
||||
|
||||
itemNode.isHidden = true
|
||||
|
||||
@ -549,6 +585,8 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
self.updateAbsoluteRects()
|
||||
|
||||
self.scrollNode.view.contentSize = CGSize(width: size.width, height: contentHeight)
|
||||
}
|
||||
|
||||
|
@ -164,7 +164,7 @@ private class AttachmentFileControllerImpl: ItemListController, AttachmentContai
|
||||
public var requestAttachmentMenuExpansion: () -> Void = {}
|
||||
|
||||
var prepareForReuseImpl: () -> Void = {}
|
||||
public func prepareForReuse() {
|
||||
public func resetForReuse() {
|
||||
self.prepareForReuseImpl()
|
||||
self.scrollToTop?()
|
||||
}
|
||||
|
@ -9121,6 +9121,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
|
||||
var layout = layout
|
||||
if let _ = self.attachmentController {
|
||||
layout = layout.withUpdatedInputHeight(nil)
|
||||
}
|
||||
|
||||
var navigationBarTransition = transition
|
||||
self.chatDisplayNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition, listViewTransaction: { updateSizeAndInsets, additionalScrollDistance, scrollToTop, completion in
|
||||
self.chatDisplayNode.historyNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, additionalScrollDistance: additionalScrollDistance, scrollToTop: scrollToTop, completion: completion)
|
||||
@ -10320,18 +10325,22 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return
|
||||
}
|
||||
self.chatDisplayNode.dismissInput()
|
||||
|
||||
let currentFilesController = Atomic<AttachmentContainable?>(value: nil)
|
||||
let currentLocationController = Atomic<AttachmentContainable?>(value: nil)
|
||||
|
||||
|
||||
var bannedSendMedia: (Int32, Bool)?
|
||||
var canSendPolls = true
|
||||
if peer is TelegramUser || peer is TelegramSecretChat {
|
||||
canSendPolls = false
|
||||
} else if let channel = peer as? TelegramChannel {
|
||||
if let value = channel.hasBannedPermission(.banSendMedia) {
|
||||
bannedSendMedia = value
|
||||
}
|
||||
if channel.hasBannedPermission(.banSendPolls) != nil {
|
||||
canSendPolls = false
|
||||
}
|
||||
} else if let group = peer as? TelegramGroup {
|
||||
if group.hasBannedPermission(.banSendMedia) {
|
||||
bannedSendMedia = (Int32.max, false)
|
||||
}
|
||||
if group.hasBannedPermission(.banSendPolls) {
|
||||
canSendPolls = false
|
||||
}
|
||||
@ -10344,6 +10353,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
let inputText = self.presentationInterfaceState.interfaceState.effectiveInputState.inputText
|
||||
|
||||
let currentMediaController = Atomic<AttachmentContainable?>(value: nil)
|
||||
let currentFilesController = Atomic<AttachmentContainable?>(value: nil)
|
||||
let currentLocationController = Atomic<AttachmentContainable?>(value: nil)
|
||||
|
||||
let attachmentController = AttachmentController(context: self.context, updatedPresentationData: self.updatedPresentationData, buttons: availableTabs)
|
||||
attachmentController.requestController = { [weak self, weak attachmentController] type, completion in
|
||||
guard let strongSelf = self else {
|
||||
@ -10351,7 +10364,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
switch type {
|
||||
case .gallery:
|
||||
strongSelf.presentMediaPicker(present: { controller, mediaPickerContext in
|
||||
strongSelf.controllerNavigationDisposable.set(nil)
|
||||
let existingController = currentMediaController.with { $0 }
|
||||
if let controller = existingController {
|
||||
controller.prepareForReuse()
|
||||
completion(controller, nil)
|
||||
return
|
||||
}
|
||||
strongSelf.presentMediaPicker(bannedSendMedia: bannedSendMedia, present: { controller, mediaPickerContext in
|
||||
let _ = currentMediaController.swap(controller)
|
||||
completion(controller, mediaPickerContext)
|
||||
}, updateMediaPickerContext: { [weak attachmentController] mediaPickerContext in
|
||||
attachmentController?.mediaPickerContext = mediaPickerContext
|
||||
@ -10361,7 +10382,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime)
|
||||
})
|
||||
strongSelf.controllerNavigationDisposable.set(nil)
|
||||
case .file:
|
||||
strongSelf.controllerNavigationDisposable.set(nil)
|
||||
let existingController = currentFilesController.with { $0 }
|
||||
@ -10978,11 +10998,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self.present(actionSheet, in: .window(.root))
|
||||
}
|
||||
|
||||
private func presentMediaPicker(present: @escaping (AttachmentContainable, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?) -> Void) {
|
||||
private func presentMediaPicker(bannedSendMedia: (Int32, Bool)?, present: @escaping (AttachmentContainable, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?) -> Void) {
|
||||
guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
|
||||
return
|
||||
}
|
||||
let controller = MediaPickerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: EnginePeer(peer), chatLocation: self.chatLocation)
|
||||
let controller = MediaPickerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: EnginePeer(peer), chatLocation: self.chatLocation, bannedSendMedia: bannedSendMedia)
|
||||
let mediaPickerContext = controller.mediaPickerContext
|
||||
controller.openCamera = { [weak self] cameraView in
|
||||
self?.openCamera(cameraView: cameraView)
|
||||
|
@ -38,6 +38,7 @@ public enum UndoOverlayContent {
|
||||
case mediaSaved(text: String)
|
||||
case paymentSent(currencyValue: String, itemTitle: String)
|
||||
case inviteRequestSent(title: String, text: String)
|
||||
case image(image: UIImage, text: String)
|
||||
}
|
||||
|
||||
public enum UndoOverlayAction {
|
||||
|
@ -749,6 +749,16 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
self.textNode.maximumNumberOfLines = 2
|
||||
displayUndo = false
|
||||
self.originalRemainingSeconds = 5
|
||||
case let .image(image, text):
|
||||
self.avatarNode = nil
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode?.image = image
|
||||
self.iconCheckNode = nil
|
||||
self.animationNode = nil
|
||||
self.animatedStickerNode = nil
|
||||
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
|
||||
displayUndo = true
|
||||
self.originalRemainingSeconds = 5
|
||||
}
|
||||
|
||||
self.remainingSeconds = self.originalRemainingSeconds
|
||||
@ -777,7 +787,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
switch content {
|
||||
case .removedChat:
|
||||
self.panelWrapperNode.addSubnode(self.timerTextNode)
|
||||
case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .voiceChatRecording, .voiceChatFlag, .voiceChatCanSpeak, .sticker, .copy, .mediaSaved, .paymentSent, .inviteRequestSent:
|
||||
case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .voiceChatRecording, .voiceChatFlag, .voiceChatCanSpeak, .sticker, .copy, .mediaSaved, .paymentSent, .image, .inviteRequestSent:
|
||||
break
|
||||
case .dice:
|
||||
self.panelWrapperNode.clipsToBounds = true
|
||||
|
Loading…
x
Reference in New Issue
Block a user