mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit '6c93d3bd5d1b8d41a638a74e057e0bdaa71b45d7'
This commit is contained in:
commit
3cb6168c8f
@ -8,7 +8,7 @@ import DirectionalPanGesture
|
||||
import TelegramPresentationData
|
||||
import MapKit
|
||||
|
||||
private let overflowInset: CGFloat = 70.0
|
||||
private let overflowInset: CGFloat = 0.0
|
||||
|
||||
final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
let wrappingNode: ASDisplayNode
|
||||
@ -406,7 +406,7 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
intrinsicInsets.left += overflowInset
|
||||
intrinsicInsets.right += overflowInset
|
||||
|
||||
containerLayout = ContainerViewLayout(size: CGSize(width: layout.size.width + overflowInset * 2.0, height: layout.size.height - containerTopInset), metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: intrinsicInsets.left, bottom: layout.intrinsicInsets.bottom, right: intrinsicInsets.right), safeInsets: UIEdgeInsets(top: 0.0, left: safeInsets.left, bottom: safeInsets.bottom, right: safeInsets.right), additionalInsets: layout.additionalInsets, statusBarHeight: effectiveStatusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver)
|
||||
containerLayout = ContainerViewLayout(size: CGSize(width: layout.size.width + overflowInset * 2.0, height: layout.size.height - containerTopInset), metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: intrinsicInsets.left, bottom: intrinsicInsets.bottom, right: intrinsicInsets.right), safeInsets: UIEdgeInsets(top: 0.0, left: safeInsets.left, bottom: safeInsets.bottom, right: safeInsets.right), additionalInsets: layout.additionalInsets, statusBarHeight: effectiveStatusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver)
|
||||
let unscaledFrame = CGRect(origin: CGPoint(x: 0.0, y: containerTopInset - coveredByModalTransition * 10.0), size: containerLayout.size)
|
||||
let maxScale: CGFloat = (containerLayout.size.width - 16.0 * 2.0) / containerLayout.size.width
|
||||
containerScale = 1.0 * (1.0 - coveredByModalTransition) + maxScale * coveredByModalTransition
|
||||
@ -417,7 +417,7 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
clipFrame = CGRect(x: containerFrame.minX + overflowInset, y: containerFrame.minY, width: containerFrame.width - overflowInset * 2.0, height: containerFrame.height)
|
||||
}
|
||||
} else {
|
||||
containerLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: layout.inVoiceOver)
|
||||
containerLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0), safeInsets: UIEdgeInsets(), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: layout.inVoiceOver)
|
||||
|
||||
let unscaledFrame = CGRect(origin: CGPoint(), size: containerLayout.size)
|
||||
containerScale = 1.0
|
||||
|
@ -74,17 +74,17 @@ private func generateShadowImage() -> UIImage? {
|
||||
}
|
||||
|
||||
private func generateMaskImage() -> UIImage? {
|
||||
return generateImage(CGSize(width: 390.0, height: 620.0), rotatedContext: { size, context in
|
||||
return generateImage(CGSize(width: 390.0, height: 220.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
|
||||
let path = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: 390.0, height: 609.0), cornerRadius: 10.0).cgPath
|
||||
let path = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: 390.0, height: 209.0), cornerRadius: 10.0).cgPath
|
||||
context.addPath(path)
|
||||
context.fillPath()
|
||||
|
||||
try? drawSvgPath(context, path: "M183.219,608.889 H206.781 C205.648,608.889 204.567,609.37 203.808,610.213 L197.23,617.522 C196.038,618.847 193.962,618.847 192.77,617.522 L186.192,610.213 C185.433,609.37 184.352,608.889 183.219,608.889 Z ")
|
||||
})
|
||||
try? drawSvgPath(context, path: "M183.219,208.89 H206.781 C205.648,208.89 204.567,209.371 203.808,210.214 L197.23,217.523 C196.038,218.848 193.962,218.848 192.77,217.523 L186.192,210.214 C185.433,209.371 184.352,208.89 183.219,208.89 Z ")
|
||||
})?.stretchableImage(withLeftCapWidth: 195, topCapHeight: 110)
|
||||
}
|
||||
|
||||
public class AttachmentController: ViewController {
|
||||
@ -157,6 +157,7 @@ public class AttachmentController: ViewController {
|
||||
self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.25)
|
||||
|
||||
self.shadowNode = ASImageNode()
|
||||
self.shadowNode.isUserInteractionEnabled = false
|
||||
|
||||
self.wrapperNode = ASDisplayNode()
|
||||
self.wrapperNode.clipsToBounds = true
|
||||
@ -200,7 +201,9 @@ public class AttachmentController: ViewController {
|
||||
|
||||
self.panel.selectionChanged = { [weak self] type, ascending in
|
||||
if let strongSelf = self {
|
||||
strongSelf.switchToController(type, ascending)
|
||||
return strongSelf.switchToController(type, ascending)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@ -258,7 +261,7 @@ public class AttachmentController: ViewController {
|
||||
|
||||
self.dim.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
|
||||
|
||||
self.switchToController(.gallery, false)
|
||||
let _ = self.switchToController(.gallery, false)
|
||||
}
|
||||
|
||||
private func updateSelectionCount(_ count: Int) {
|
||||
@ -274,16 +277,16 @@ public class AttachmentController: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
func switchToController(_ type: AttachmentButtonType, _ ascending: Bool) {
|
||||
func switchToController(_ type: AttachmentButtonType, _ ascending: Bool) -> Bool {
|
||||
guard !self.animating else {
|
||||
return false
|
||||
}
|
||||
guard self.currentType != type else {
|
||||
if self.animating {
|
||||
return
|
||||
}
|
||||
if let controller = self.currentControllers.last {
|
||||
controller.scrollToTopWithTabBar?()
|
||||
controller.requestAttachmentMenuExpansion()
|
||||
}
|
||||
return
|
||||
return true
|
||||
}
|
||||
let previousType = self.currentType
|
||||
self.currentType = type
|
||||
@ -317,28 +320,10 @@ public class AttachmentController: ViewController {
|
||||
}
|
||||
}
|
||||
let previousController = strongSelf.currentControllers.last
|
||||
let animateTransition = previousType != nil
|
||||
strongSelf.currentControllers = [controller]
|
||||
|
||||
if animateTransition, let snapshotView = strongSelf.container.container.view.snapshotView(afterScreenUpdates: false) {
|
||||
snapshotView.frame = strongSelf.container.container.frame
|
||||
strongSelf.container.clipNode.view.addSubview(snapshotView)
|
||||
|
||||
let _ = (controller.ready.get()
|
||||
|> filter {
|
||||
$0
|
||||
}
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self, weak snapshotView] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.container.container.view.layer.animatePosition(from: CGPoint(x: ascending ? 70.0 : -70.0, y: 0.0), to: CGPoint(), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
snapshotView?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
snapshotView?.removeFromSuperview()
|
||||
previousController?.resetForReuse()
|
||||
})
|
||||
})
|
||||
if previousType != nil {
|
||||
strongSelf.animateSwitchTransition(controller, previousController: previousController)
|
||||
}
|
||||
|
||||
if let layout = strongSelf.validLayout {
|
||||
@ -350,6 +335,48 @@ public class AttachmentController: ViewController {
|
||||
strongSelf.mediaPickerContext = mediaPickerContext
|
||||
}
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
private func animateSwitchTransition(_ controller: AttachmentContainable, previousController: AttachmentContainable?) {
|
||||
guard let snapshotView = self.container.container.view.snapshotView(afterScreenUpdates: false) else {
|
||||
return
|
||||
}
|
||||
|
||||
snapshotView.frame = self.container.container.frame
|
||||
self.container.clipNode.view.addSubview(snapshotView)
|
||||
|
||||
self.animating = true
|
||||
|
||||
let _ = (controller.ready.get()
|
||||
|> filter {
|
||||
$0
|
||||
}
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self, weak snapshotView] _ in
|
||||
guard let strongSelf = self, let layout = strongSelf.validLayout else {
|
||||
return
|
||||
}
|
||||
|
||||
if case .compact = layout.metrics.widthClass {
|
||||
let offset = strongSelf.container.isExpanded ? 10.0 : 24.0
|
||||
strongSelf.container.clipNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -offset), duration: 0.18, removeOnCompletion: false, additive: true, completion: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.container.clipNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: 0.0, y: 0.0)), to: NSValue(cgPoint: CGPoint(x: 0.0, y: offset)), keyPath: "position", duration: 0.55, delay: 0.0, initialVelocity: 0.0, damping: 70.0, removeOnCompletion: false, additive: true, completion: { [weak self] _ in
|
||||
self?.container.clipNode.layer.removeAllAnimations()
|
||||
self?.animating = false
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
strongSelf.animating = false
|
||||
}
|
||||
|
||||
snapshotView?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
snapshotView?.removeFromSuperview()
|
||||
previousController?.resetForReuse()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
private var animating = false
|
||||
@ -432,7 +459,9 @@ public class AttachmentController: ViewController {
|
||||
var containerLayout = layout
|
||||
let containerRect: CGRect
|
||||
if case .regular = layout.metrics.widthClass {
|
||||
let size = CGSize(width: 390.0, height: 620.0)
|
||||
let availableHeight = layout.size.height - (layout.inputHeight ?? 0.0) - 60.0
|
||||
|
||||
let size = CGSize(width: 390.0, height: min(620.0, availableHeight))
|
||||
|
||||
let insets = layout.insets(options: [.input])
|
||||
let masterWidth = min(max(320.0, floor(layout.size.width / 3.0)), floor(layout.size.width / 2.0))
|
||||
@ -446,9 +475,12 @@ public class AttachmentController: ViewController {
|
||||
if self.wrapperNode.view.mask == nil {
|
||||
let maskView = UIImageView()
|
||||
maskView.image = generateMaskImage()
|
||||
maskView.frame = CGRect(origin: CGPoint(), size: maskView.image?.size ?? CGSize())
|
||||
maskView.contentMode = .scaleToFill
|
||||
self.wrapperNode.view.mask = maskView
|
||||
}
|
||||
if let maskView = self.wrapperNode.view.mask {
|
||||
transition.updateFrame(view: maskView, frame: CGRect(origin: CGPoint(), size: size))
|
||||
}
|
||||
|
||||
self.shadowNode.alpha = 1.0
|
||||
if self.shadowNode.image == nil {
|
||||
|
@ -174,7 +174,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
|
||||
private var validLayout: ContainerViewLayout?
|
||||
private var scrollLayout: (width: CGFloat, contentSize: CGSize)?
|
||||
|
||||
var selectionChanged: (AttachmentButtonType, Bool) -> Void = { _, _ in }
|
||||
var selectionChanged: (AttachmentButtonType, Bool) -> Bool = { _, _ in return false }
|
||||
var beganTextEditing: () -> Void = {}
|
||||
var textUpdated: (NSAttributedString) -> Void = { _ in }
|
||||
var sendMessagePressed: (AttachmentTextInputPanelSendMode) -> Void = { _ in }
|
||||
@ -481,9 +481,10 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
|
||||
action: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
let ascending = i > strongSelf.selectedIndex
|
||||
strongSelf.selectedIndex = i
|
||||
strongSelf.selectionChanged(type, ascending)
|
||||
strongSelf.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring)))
|
||||
if strongSelf.selectionChanged(type, ascending) {
|
||||
strongSelf.selectedIndex = i
|
||||
strongSelf.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring)))
|
||||
}
|
||||
}
|
||||
})
|
||||
),
|
||||
|
@ -208,7 +208,7 @@ private struct WrappedGridItemNode: Hashable {
|
||||
}
|
||||
|
||||
open class GridNode: GridNodeScroller, UIScrollViewDelegate {
|
||||
private var gridLayout = GridNodeLayout(size: CGSize(), insets: UIEdgeInsets(), preloadSize: 0.0, type: .fixed(itemSize: CGSize(), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil))
|
||||
public private(set) var gridLayout = GridNodeLayout(size: CGSize(), insets: UIEdgeInsets(), preloadSize: 0.0, type: .fixed(itemSize: CGSize(), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil))
|
||||
private var firstIndexInSectionOffset: Int = 0
|
||||
public private(set) var items: [GridItem] = []
|
||||
private var itemNodes: [Int: GridItemNode] = [:]
|
||||
@ -244,7 +244,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
|
||||
self.scrollView.indicatorStyle = self.indicatorStyle
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public private(set) var opaqueState: Any?
|
||||
|
||||
public override init() {
|
||||
|
@ -112,7 +112,7 @@ final class NavigationModalFrame: ASDisplayNode {
|
||||
let cornerRadius: CGFloat = 9.0
|
||||
let initialCornerRadius: CGFloat
|
||||
if !layout.safeInsets.top.isZero {
|
||||
initialCornerRadius = 40.0
|
||||
initialCornerRadius = layout.deviceMetrics.screenCornerRadius
|
||||
} else {
|
||||
initialCornerRadius = 0.0
|
||||
}
|
||||
|
@ -45,6 +45,12 @@
|
||||
- (NSOrderedSet *)selectedItemsIdentifiers;
|
||||
- (NSArray *)selectedItems;
|
||||
|
||||
- (void)saveState;
|
||||
- (void)restoreState;
|
||||
- (void)clearSavedState;
|
||||
|
||||
- (NSUInteger)savedStateDifference;
|
||||
|
||||
- (NSUInteger)count;
|
||||
|
||||
+ (SSignal *)combinedSelectionChangedSignalForContexts:(NSArray *)contexts;
|
||||
|
@ -9,6 +9,8 @@
|
||||
|
||||
@interface TGMediaSelectionContext ()
|
||||
{
|
||||
NSMutableArray *_savedSelectedIdentifiers;
|
||||
|
||||
NSMutableArray *_selectedIdentifiers;
|
||||
NSMutableDictionary *_selectionMap;
|
||||
|
||||
@ -74,9 +76,6 @@
|
||||
NSString *identifier = item.uniqueIdentifier;
|
||||
if (selected)
|
||||
{
|
||||
if (_selectionMap[identifier] != nil)
|
||||
return false;
|
||||
|
||||
if (_selectedIdentifiers.count >= _selectionLimit) {
|
||||
if (_selectionLimitExceeded) {
|
||||
_selectionLimitExceeded();
|
||||
@ -92,7 +91,6 @@
|
||||
if (_selectionMap[identifier] == nil)
|
||||
return false;
|
||||
|
||||
[_selectionMap removeObjectForKey:identifier];
|
||||
[_selectedIdentifiers removeObject:identifier];
|
||||
}
|
||||
|
||||
@ -186,9 +184,13 @@
|
||||
if (enumerationBlock == nil)
|
||||
return;
|
||||
|
||||
NSArray *items = [_selectionMap allValues];
|
||||
for (id<TGMediaSelectableItem> item in items)
|
||||
enumerationBlock(item);
|
||||
for (NSArray *identifier in _selectedIdentifiers)
|
||||
{
|
||||
NSObject<TGMediaSelectableItem> *item = _selectionMap[identifier];
|
||||
if (item != nil) {
|
||||
enumerationBlock(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSOrderedSet *)selectedItemsIdentifiers
|
||||
@ -213,6 +215,31 @@
|
||||
return _selectedIdentifiers.count;
|
||||
}
|
||||
|
||||
- (void)saveState {
|
||||
if (_savedSelectedIdentifiers == nil) {
|
||||
_savedSelectedIdentifiers = [_selectedIdentifiers mutableCopy];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)restoreState {
|
||||
_selectedIdentifiers = _savedSelectedIdentifiers;
|
||||
_savedSelectedIdentifiers = nil;
|
||||
|
||||
_pipe.sink([TGMediaSelectionChange changeWithItem:nil selected:false animated:false sender:nil]);
|
||||
}
|
||||
|
||||
- (void)clearSavedState {
|
||||
_savedSelectedIdentifiers = nil;
|
||||
}
|
||||
|
||||
- (NSUInteger)savedStateDifference {
|
||||
if (_savedSelectedIdentifiers != nil) {
|
||||
return _savedSelectedIdentifiers.count - _selectedIdentifiers.count;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)setItemSourceUpdatedSignal:(SSignal *)signal
|
||||
|
@ -38,6 +38,8 @@ swift_library(
|
||||
"//submodules/WallpaperBackgroundNode:WallpaperBackgroundNode",
|
||||
"//submodules/WebSearchUI:WebSearchUI",
|
||||
"//submodules/ChatMessageBackground:ChatMessageBackground",
|
||||
"//submodules/SparseItemGrid:SparseItemGrid",
|
||||
"//submodules/UndoUI:UndoUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -114,13 +114,26 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
}
|
||||
}
|
||||
|
||||
var _cachedTag: Int32?
|
||||
var tag: Int32? {
|
||||
if let tag = self._cachedTag {
|
||||
return tag
|
||||
} else if let asset = self.asset, let localTimestamp = asset.creationDate?.timeIntervalSince1970 {
|
||||
let tag = Month(localTimestamp: Int32(localTimestamp)).packedValue
|
||||
self._cachedTag = tag
|
||||
return tag
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func updateSelectionState(animated: Bool = false) {
|
||||
if self.checkNode == nil, let _ = self.interaction?.selectionState, let theme = self.theme {
|
||||
let checkNode = InteractiveCheckNode(theme: CheckNodeTheme(theme: theme, style: .overlay))
|
||||
checkNode.valueChanged = { [weak self] value in
|
||||
if let strongSelf = self, let asset = strongSelf.asset, let interaction = strongSelf.interaction {
|
||||
if let legacyAsset = TGMediaAsset(phAsset: asset) {
|
||||
interaction.toggleSelection(legacyAsset, value)
|
||||
interaction.toggleSelection(legacyAsset, value, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -164,6 +177,8 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
self.interaction = interaction
|
||||
self.theme = theme
|
||||
|
||||
self.backgroundColor = theme.list.mediaPlaceholderColor
|
||||
|
||||
if self.currentState == nil || self.currentState!.0 !== fetchResult || self.currentState!.1 != index {
|
||||
let editingContext = interaction.editingState
|
||||
let asset = fetchResult.object(at: index)
|
||||
|
@ -119,7 +119,7 @@ final class MediaPickerMoreButtonNode: ASDisplayNode {
|
||||
override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
||||
let animationSize = CGSize(width: 30.0, height: 30.0)
|
||||
let inset: CGFloat = 0.0
|
||||
self.iconNode.frame = CGRect(origin: CGPoint(x: inset + 4.0, y: floor((constrainedSize.height - animationSize.height) / 2.0)), size: animationSize)
|
||||
self.iconNode.frame = CGRect(origin: CGPoint(x: inset + 6.0, y: floor((constrainedSize.height - animationSize.height) / 2.0) + 1.0), size: animationSize)
|
||||
|
||||
let size = CGSize(width: animationSize.width + inset * 2.0, height: constrainedSize.height)
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
|
@ -17,13 +17,15 @@ import LegacyMediaPickerUI
|
||||
import AttachmentUI
|
||||
import ContextUI
|
||||
import WebSearchUI
|
||||
import SparseItemGrid
|
||||
import UndoUI
|
||||
|
||||
let overflowInset: CGFloat = 70.0
|
||||
let overflowInset: CGFloat = 0.0
|
||||
|
||||
final class MediaPickerInteraction {
|
||||
let openMedia: (PHFetchResult<PHAsset>, Int, UIImage?) -> Void
|
||||
let openSelectedMedia: (TGMediaSelectableItem, UIImage?) -> Void
|
||||
let toggleSelection: (TGMediaSelectableItem, Bool) -> Void
|
||||
let toggleSelection: (TGMediaSelectableItem, Bool, Bool) -> Void
|
||||
let sendSelected: (TGMediaSelectableItem?, Bool, Int32?, Bool) -> Void
|
||||
let schedule: () -> Void
|
||||
let dismissInput: () -> Void
|
||||
@ -31,7 +33,7 @@ final class MediaPickerInteraction {
|
||||
let editingState: TGMediaEditingContext
|
||||
var hiddenMediaId: String?
|
||||
|
||||
init(openMedia: @escaping (PHFetchResult<PHAsset>, Int, UIImage?) -> Void, openSelectedMedia: @escaping (TGMediaSelectableItem, UIImage?) -> Void, toggleSelection: @escaping (TGMediaSelectableItem, Bool) -> Void, sendSelected: @escaping (TGMediaSelectableItem?, Bool, Int32?, Bool) -> Void, schedule: @escaping () -> Void, dismissInput: @escaping () -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) {
|
||||
init(openMedia: @escaping (PHFetchResult<PHAsset>, Int, UIImage?) -> Void, openSelectedMedia: @escaping (TGMediaSelectableItem, UIImage?) -> Void, toggleSelection: @escaping (TGMediaSelectableItem, Bool, Bool) -> Void, sendSelected: @escaping (TGMediaSelectableItem?, Bool, Int32?, Bool) -> Void, schedule: @escaping () -> Void, dismissInput: @escaping () -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) {
|
||||
self.openMedia = openMedia
|
||||
self.openSelectedMedia = openSelectedMedia
|
||||
self.toggleSelection = toggleSelection
|
||||
@ -73,6 +75,33 @@ private struct MediaPickerGridTransaction {
|
||||
}
|
||||
}
|
||||
|
||||
struct Month: Equatable {
|
||||
var packedValue: Int32
|
||||
|
||||
init(packedValue: Int32) {
|
||||
self.packedValue = packedValue
|
||||
}
|
||||
|
||||
init(localTimestamp: Int32) {
|
||||
var time: time_t = time_t(localTimestamp)
|
||||
var timeinfo: tm = tm()
|
||||
gmtime_r(&time, &timeinfo)
|
||||
|
||||
let year = UInt32(timeinfo.tm_year)
|
||||
let month = UInt32(timeinfo.tm_mon)
|
||||
|
||||
self.packedValue = Int32(bitPattern: year | (month << 16))
|
||||
}
|
||||
|
||||
var year: Int32 {
|
||||
return Int32(bitPattern: (UInt32(bitPattern: self.packedValue) >> 0) & 0xffff)
|
||||
}
|
||||
|
||||
var month: Int32 {
|
||||
return Int32(bitPattern: (UInt32(bitPattern: self.packedValue) >> 16) & 0xffff)
|
||||
}
|
||||
}
|
||||
|
||||
public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
private let context: AccountContext
|
||||
private var presentationData: PresentationData
|
||||
@ -131,6 +160,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
fileprivate var cameraView: TGAttachmentCameraView?
|
||||
private var placeholderNode: MediaPickerPlaceholderNode?
|
||||
private var manageNode: MediaPickerManageNode?
|
||||
private var scrollingArea: SparseItemGridScrollingArea
|
||||
|
||||
private var selectionNode: MediaPickerSelectedListNode?
|
||||
|
||||
@ -167,6 +197,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
self.backgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.tabBar.backgroundColor)
|
||||
self.backgroundNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
||||
self.gridNode = GridNode()
|
||||
self.scrollingArea = SparseItemGridScrollingArea()
|
||||
|
||||
super.init()
|
||||
|
||||
@ -177,7 +208,8 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
self.addSubnode(self.containerNode)
|
||||
self.containerNode.addSubnode(self.backgroundNode)
|
||||
self.containerNode.addSubnode(self.gridNode)
|
||||
|
||||
self.containerNode.addSubnode(self.scrollingArea)
|
||||
|
||||
let collection = controller.collection
|
||||
let preloadPromise = self.preloadPromise
|
||||
let updatedState = combineLatest(mediaAssetsContext.mediaAccess(), mediaAssetsContext.cameraAccess())
|
||||
@ -215,6 +247,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
|
||||
self.gridNode.visibleContentOffsetChanged = { [weak self] _ in
|
||||
self?.updateNavigation(transition: .immediate)
|
||||
self?.updateScrollingArea()
|
||||
}
|
||||
|
||||
self.hiddenMediaDisposable = (self.hiddenMediaId.get()
|
||||
@ -295,6 +328,21 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
}
|
||||
}
|
||||
|
||||
self.scrollingArea.beginScrolling = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return nil
|
||||
}
|
||||
return strongSelf.gridNode.scrollView
|
||||
}
|
||||
self.scrollingArea.setContentOffset = { [weak self] offset in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
// strongSelf.isFastScrolling = true
|
||||
strongSelf.gridNode.scrollView.setContentOffset(offset, animated: false)
|
||||
// strongSelf.isFastScrolling = false
|
||||
}
|
||||
|
||||
if self.controller?.collection == nil {
|
||||
let cameraView = TGAttachmentCameraView(forSelfPortrait: false)!
|
||||
cameraView.clipsToBounds = true
|
||||
@ -347,6 +395,44 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
self.view.window?.endEditing(true)
|
||||
}
|
||||
|
||||
private func scrollerTextForTag(tag: Int32) -> String {
|
||||
let month = Month(packedValue: tag)
|
||||
return stringForMonth(strings: self.presentationData.strings, month: month.month, ofYear: month.year)
|
||||
}
|
||||
|
||||
private var currentScrollingTag: Int32?
|
||||
private func updateScrollingArea() {
|
||||
guard let (layout, _) = self.validLayout else {
|
||||
return
|
||||
}
|
||||
|
||||
var tag: Int32?
|
||||
self.gridNode.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? MediaPickerGridItemNode {
|
||||
tag = itemNode.tag
|
||||
}
|
||||
}
|
||||
|
||||
let dateString = tag.flatMap { self.scrollerTextForTag(tag: $0) }
|
||||
if self.currentScrollingTag != tag {
|
||||
self.currentScrollingTag = tag
|
||||
if self.scrollingArea.isDragging {
|
||||
self.scrollingArea.feedbackTap()
|
||||
}
|
||||
}
|
||||
|
||||
self.scrollingArea.update(
|
||||
containerSize: layout.size,
|
||||
containerInsets: self.gridNode.gridLayout.insets,
|
||||
contentHeight: self.gridNode.scrollView.contentSize.height,
|
||||
contentOffset: self.gridNode.scrollView.bounds.minY,
|
||||
isScrolling: self.gridNode.scrollView.isDragging || self.gridNode.scrollView.isDecelerating,
|
||||
date: (dateString ?? "", tag ?? 0),
|
||||
theme: self.presentationData.theme,
|
||||
transition: .immediate
|
||||
)
|
||||
}
|
||||
|
||||
private func updateState(_ state: State) {
|
||||
guard let controller = self.controller, let interaction = controller.interaction else {
|
||||
return
|
||||
@ -442,7 +528,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
}
|
||||
|
||||
private var currentDisplayMode: DisplayMode = .all
|
||||
func updateMode(_ displayMode: DisplayMode) {
|
||||
func updateDisplayMode(_ displayMode: DisplayMode) {
|
||||
let updated = self.currentDisplayMode != displayMode
|
||||
self.currentDisplayMode = displayMode
|
||||
|
||||
@ -452,6 +538,19 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
selectionNode.alpha = 0.0
|
||||
selectionNode.isUserInteractionEnabled = false
|
||||
selectionNode.interaction = self.controller?.interaction
|
||||
selectionNode.getTransitionView = { [weak self] identifier in
|
||||
if let strongSelf = self {
|
||||
var view: UIView?
|
||||
strongSelf.gridNode.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? MediaPickerGridItemNode, itemNode.asset?.localIdentifier == identifier {
|
||||
view = itemNode.view
|
||||
}
|
||||
}
|
||||
return view
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
self.containerNode.insertSubnode(selectionNode, aboveSubnode: self.gridNode)
|
||||
self.selectionNode = selectionNode
|
||||
|
||||
@ -460,25 +559,26 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
}
|
||||
}
|
||||
|
||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
|
||||
self.gridNode.isUserInteractionEnabled = displayMode == .all
|
||||
self.selectionNode?.isUserInteractionEnabled = displayMode == .selected
|
||||
|
||||
var completion: () -> Void = {}
|
||||
if updated && displayMode == .all {
|
||||
completion = {
|
||||
self.updateNavigation(transition: .animated(duration: 0.1, curve: .easeInOut))
|
||||
self.selectionNode?.alpha = 0.0
|
||||
}
|
||||
}
|
||||
|
||||
if let selectionNode = self.selectionNode {
|
||||
transition.updateAlpha(node: selectionNode, alpha: displayMode == .selected ? 1.0 : 0.0, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
selectionNode.isUserInteractionEnabled = displayMode == .selected
|
||||
}
|
||||
|
||||
if updated && displayMode == .selected {
|
||||
self.updateNavigation(transition: .immediate)
|
||||
if updated {
|
||||
switch displayMode {
|
||||
case .selected:
|
||||
self.selectionNode?.alpha = 1.0
|
||||
self.selectionNode?.animateIn(completion: completion)
|
||||
self.updateNavigation(transition: .immediate)
|
||||
case .all:
|
||||
self.selectionNode?.animateOut(completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -570,7 +670,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
}
|
||||
|
||||
let proceed: (Bool) -> Void = { convertToJpeg in
|
||||
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 {
|
||||
guard let signals = TGMediaAssetsController.resultSignals(for: self.controller?.interaction?.selectionState, editingContext: self.controller?.interaction?.editingState, intent: asFile ? TGMediaAssetsControllerSendFileIntent : TGMediaAssetsControllerSendMediaIntent, currentItem: nil, storeAssets: true, convertToJpeg: convertToJpeg, descriptionGenerator: legacyAssetPickerItemGenerator(), saveEditedPhotos: true) else {
|
||||
return
|
||||
}
|
||||
self.controller?.legacyCompletion(signals, silently, scheduleTime)
|
||||
@ -804,6 +904,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
let cleanGridInsets = UIEdgeInsets(top: insets.top, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.safeInsets.right)
|
||||
let gridInsets = UIEdgeInsets(top: insets.top + manageHeight, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.safeInsets.right)
|
||||
transition.updateFrame(node: self.gridNode, frame: innerBounds)
|
||||
self.scrollingArea.frame = innerBounds
|
||||
|
||||
transition.updateFrame(node: self.backgroundNode, frame: innerBounds)
|
||||
self.backgroundNode.update(size: bounds.size, transition: transition)
|
||||
@ -834,7 +935,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
}
|
||||
|
||||
if selectedItems.count < 1 && self.currentDisplayMode == .selected {
|
||||
self.updateMode(.all)
|
||||
self.updateDisplayMode(.all)
|
||||
Queue.mainQueue().after(0.3, updateSelectionNode)
|
||||
} else {
|
||||
updateSelectionNode()
|
||||
@ -949,7 +1050,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
|
||||
self.titleView.indexUpdated = { [weak self] index in
|
||||
if let strongSelf = self {
|
||||
strongSelf.controllerNode.updateMode(index == 0 ? .all : .selected)
|
||||
strongSelf.controllerNode.updateDisplayMode(index == 0 ? .all : .selected)
|
||||
}
|
||||
}
|
||||
|
||||
@ -994,9 +1095,23 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
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)
|
||||
}, toggleSelection: { [weak self] item, value, suggestUndo in
|
||||
if let strongSelf = self, let selectionState = strongSelf.interaction?.selectionState {
|
||||
var showUndo = false
|
||||
if suggestUndo {
|
||||
if !value {
|
||||
selectionState.saveState()
|
||||
showUndo = true
|
||||
} else {
|
||||
selectionState.clearSavedState()
|
||||
}
|
||||
}
|
||||
|
||||
selectionState.setItem(item, selected: value)
|
||||
|
||||
if showUndo {
|
||||
strongSelf.showSelectionUndo(item: item, count: Int32(selectionState.savedStateDifference()))
|
||||
}
|
||||
}
|
||||
}, sendSelected: { [weak self] currentItem, silently, scheduleTime, animated in
|
||||
if let strongSelf = self, let selectionState = strongSelf.interaction?.selectionState {
|
||||
@ -1037,6 +1152,51 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
super.displayNodeDidLoad()
|
||||
}
|
||||
|
||||
private weak var undoController: UndoOverlayController?
|
||||
private func showSelectionUndo(item: TGMediaSelectableItem, count: Int32) {
|
||||
var asset: PHAsset?
|
||||
if let item = item as? TGMediaAsset {
|
||||
asset = item.backingAsset
|
||||
} else if let item = item as? TGCameraCapturedVideo {
|
||||
asset = item.originalAsset.backingAsset
|
||||
}
|
||||
|
||||
guard let asset = asset else {
|
||||
return
|
||||
}
|
||||
|
||||
let scale = min(2.0, UIScreenScale)
|
||||
let targetSize = CGSize(width: 64.0 * scale, height: 64.0 * scale)
|
||||
let _ = (assetImage(asset: asset, targetSize: targetSize, exact: false)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] image in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let presentationData = strongSelf.presentationData
|
||||
if let undoController = strongSelf.undoController {
|
||||
let text = presentationData.strings.Attachment_DeselectedItems(count)
|
||||
undoController.content = .image(image: image ?? UIImage(), text: text)
|
||||
} else {
|
||||
let text = presentationData.strings.Attachment_DeselectedItems(count)
|
||||
let undoController = UndoOverlayController(presentationData: presentationData, content: .image(image: image ?? UIImage(), text: text), elevatedLayout: true, action: { [weak self] action in
|
||||
guard let strongSelf = self else {
|
||||
return true
|
||||
}
|
||||
switch action {
|
||||
case .undo:
|
||||
strongSelf.interaction?.selectionState?.restoreState()
|
||||
default:
|
||||
strongSelf.interaction?.selectionState?.clearSavedState()
|
||||
}
|
||||
return true
|
||||
})
|
||||
strongSelf.present(undoController, in: .window(.root))
|
||||
strongSelf.undoController = undoController
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private var selectionCount: Int32 = 0
|
||||
fileprivate func updateSelectionState(count: Int32) {
|
||||
self.selectionCount = count
|
||||
|
@ -118,7 +118,7 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
|
||||
let checkNode = InteractiveCheckNode(theme: CheckNodeTheme(theme: theme, style: .overlay))
|
||||
checkNode.valueChanged = { [weak self] value in
|
||||
if let strongSelf = self, let interaction = strongSelf.interaction {
|
||||
interaction.toggleSelection(strongSelf.asset, value)
|
||||
interaction.toggleSelection(strongSelf.asset, value, true)
|
||||
}
|
||||
}
|
||||
self.addSubnode(checkNode)
|
||||
@ -189,6 +189,41 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
|
||||
view.frame = self.convert(self.bounds, to: nil)
|
||||
return view
|
||||
}
|
||||
|
||||
func animateFrom(_ view: UIView) {
|
||||
view.alpha = 0.0
|
||||
|
||||
let frame = view.convert(view.bounds, to: self.supernode?.view)
|
||||
let targetFrame = self.frame
|
||||
|
||||
self.updateLayout(size: frame.size, transition: .immediate)
|
||||
self.updateLayout(size: targetFrame.size, transition: .animated(duration: 0.25, curve: .spring))
|
||||
self.layer.animateFrame(from: frame, to: targetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak view] _ in
|
||||
view?.alpha = 1.0
|
||||
})
|
||||
}
|
||||
|
||||
func animateTo(_ view: UIView) {
|
||||
view.alpha = 0.0
|
||||
|
||||
let frame = self.frame
|
||||
let targetFrame = view.convert(view.bounds, to: self.supernode?.view)
|
||||
|
||||
let corners = self.corners
|
||||
|
||||
self.corners = []
|
||||
self.updateLayout(size: targetFrame.size, transition: .animated(duration: 0.25, curve: .spring))
|
||||
self.layer.animateFrame(from: frame, to: targetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak view, weak self] _ in
|
||||
view?.alpha = 1.0
|
||||
|
||||
self?.corners = corners
|
||||
self?.updateLayout(size: frame.size, transition: .immediate)
|
||||
|
||||
Queue.mainQueue().after(0.01) {
|
||||
self?.layer.removeAllAnimations()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private class MessageBackgroundNode: ASDisplayNode {
|
||||
@ -288,7 +323,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI
|
||||
}
|
||||
return (false, false, nil)
|
||||
}, willBegin: { _ in
|
||||
// self?.willBeginReorder(point)
|
||||
|
||||
}, began: { [weak self] itemNode in
|
||||
self?.beginReordering(itemNode: itemNode)
|
||||
}, ended: { [weak self] point in
|
||||
@ -309,6 +344,74 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI
|
||||
})
|
||||
}
|
||||
|
||||
var getTransitionView: (String) -> UIView? = { _ in return nil }
|
||||
|
||||
func animateIn(completion: @escaping () -> Void = {}) {
|
||||
self.wallpaperBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
self.wallpaperBackgroundNode.layer.animateScale(from: 1.2, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
|
||||
for (_, backgroundNode) in self.backgroundNodes {
|
||||
backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.1)
|
||||
}
|
||||
|
||||
for (identifier, itemNode) in self.itemNodes {
|
||||
if let transitionView = self.getTransitionView(identifier) {
|
||||
itemNode.animateFrom(transitionView)
|
||||
} else {
|
||||
itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
|
||||
}
|
||||
}
|
||||
|
||||
if let topNode = self.messageNodes?.first, !topNode.alpha.isZero {
|
||||
topNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.1)
|
||||
topNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -30.0), to: CGPoint(), duration: 0.4, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
}
|
||||
|
||||
if let bottomNode = self.messageNodes?.last, !bottomNode.alpha.isZero {
|
||||
bottomNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.1)
|
||||
bottomNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 30.0), to: CGPoint(), duration: 0.4, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
}
|
||||
}
|
||||
|
||||
func animateOut(completion: @escaping () -> Void = {}) {
|
||||
self.wallpaperBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
completion()
|
||||
|
||||
if let strongSelf = self {
|
||||
Queue.mainQueue().after(0.01) {
|
||||
for (_, backgroundNode) in strongSelf.backgroundNodes {
|
||||
backgroundNode.layer.removeAllAnimations()
|
||||
}
|
||||
|
||||
strongSelf.messageNodes?.first?.layer.removeAllAnimations()
|
||||
strongSelf.messageNodes?.last?.layer.removeAllAnimations()
|
||||
|
||||
strongSelf.wallpaperBackgroundNode.layer.removeAllAnimations()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
self.wallpaperBackgroundNode.layer.animateScale(from: 1.0, to: 1.2, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
|
||||
for (_, backgroundNode) in self.backgroundNodes {
|
||||
backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
for (identifier, itemNode) in self.itemNodes {
|
||||
if let transitionView = self.getTransitionView(identifier) {
|
||||
itemNode.animateTo(transitionView)
|
||||
}
|
||||
}
|
||||
|
||||
self.messageNodes?.first?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
self.messageNodes?.first?.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -30.0), duration: 0.4, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
||||
|
||||
self.messageNodes?.last?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
self.messageNodes?.last?.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: 30.0), duration: 0.4, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
||||
}
|
||||
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return true
|
||||
}
|
||||
@ -556,9 +659,14 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI
|
||||
self.backgroundNodes[groupIndex] = groupBackgroundNode
|
||||
self.scrollNode.insertSubnode(groupBackgroundNode, at: 0)
|
||||
}
|
||||
|
||||
var itemTransition = transition
|
||||
if groupBackgroundNode.frame.width.isZero {
|
||||
itemTransition = .immediate
|
||||
}
|
||||
|
||||
transition.updateFrame(node: groupBackgroundNode, frame: groupRect.insetBy(dx: -5.0, dy: -2.0).offsetBy(dx: 3.0, dy: 0.0))
|
||||
groupBackgroundNode.update(size: groupBackgroundNode.frame.size, theme: theme, wallpaper: wallpaper, graphics: graphics, wallpaperBackgroundNode: self.wallpaperBackgroundNode, transition: transition)
|
||||
itemTransition.updateFrame(node: groupBackgroundNode, frame: groupRect.insetBy(dx: -5.0, dy: -2.0).offsetBy(dx: 3.0, dy: 0.0))
|
||||
groupBackgroundNode.update(size: groupBackgroundNode.frame.size, theme: theme, wallpaper: wallpaper, graphics: graphics, wallpaperBackgroundNode: self.wallpaperBackgroundNode, transition: itemTransition)
|
||||
|
||||
for (item, itemRect, itemPosition) in items {
|
||||
if let identifier = item.uniqueIdentifier, let itemNode = self.itemNodes[identifier] {
|
||||
@ -578,8 +686,18 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI
|
||||
itemNode.corners = corners
|
||||
itemNode.radius = bubbleCorners.mainRadius
|
||||
|
||||
itemNode.updateLayout(size: itemRect.size, transition: transition)
|
||||
transition.updateFrame(node: itemNode, frame: itemRect.offsetBy(dx: groupRect.minX, dy: groupRect.minY))
|
||||
var itemTransition = itemTransition
|
||||
if itemNode.frame.width.isZero {
|
||||
itemTransition = .immediate
|
||||
}
|
||||
|
||||
itemNode.updateLayout(size: itemRect.size, transition: itemTransition)
|
||||
itemTransition.updateFrame(node: itemNode, frame: itemRect.offsetBy(dx: groupRect.minX, dy: groupRect.minY))
|
||||
|
||||
if case .immediate = itemTransition, case .animated = transition {
|
||||
transition.animateTransformScale(node: itemNode, from: 0.01)
|
||||
itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -753,6 +753,8 @@ final class SparseItemGridScrollingIndicatorComponent: CombinedComponent {
|
||||
let textYear = Child(RollingText.self)
|
||||
|
||||
return { context in
|
||||
context.view.clipsToBounds = true
|
||||
|
||||
let date = context.component.date
|
||||
|
||||
let components = date.0.components(separatedBy: " ")
|
||||
@ -828,7 +830,7 @@ final class SparseItemGridScrollingIndicatorComponent: CombinedComponent {
|
||||
context.add(textYear
|
||||
.position(CGPoint(x: yearTextFrame.midX, y: yearTextFrame.midY))
|
||||
)
|
||||
|
||||
|
||||
return rect.size
|
||||
}
|
||||
}
|
||||
@ -1099,7 +1101,7 @@ public final class SparseItemGridScrollingArea: ASDisplayNode {
|
||||
self.updateLineIndicator(transition: transition)
|
||||
}
|
||||
|
||||
func feedbackTap() {
|
||||
public func feedbackTap() {
|
||||
self.hapticFeedback.tap()
|
||||
}
|
||||
|
||||
|
@ -10353,7 +10353,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
let inputText = self.presentationInterfaceState.interfaceState.effectiveInputState.inputText
|
||||
|
||||
let currentMediaController = Atomic<AttachmentContainable?>(value: nil)
|
||||
let currentMediaController = Atomic<MediaPickerScreen?>(value: nil)
|
||||
let currentFilesController = Atomic<AttachmentContainable?>(value: nil)
|
||||
let currentLocationController = Atomic<AttachmentContainable?>(value: nil)
|
||||
|
||||
@ -10367,7 +10367,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
strongSelf.controllerNavigationDisposable.set(nil)
|
||||
let existingController = currentMediaController.with { $0 }
|
||||
if let controller = existingController {
|
||||
completion(controller, nil)
|
||||
completion(controller, controller.mediaPickerContext)
|
||||
controller.prepareForReuse()
|
||||
return
|
||||
}
|
||||
@ -11023,7 +11023,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self.present(actionSheet, in: .window(.root))
|
||||
}
|
||||
|
||||
private func presentMediaPicker(bannedSendMedia: (Int32, Bool)?, present: @escaping (AttachmentContainable, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?) -> Void) {
|
||||
private func presentMediaPicker(bannedSendMedia: (Int32, Bool)?, present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?) -> Void) {
|
||||
guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
|
||||
return
|
||||
}
|
||||
|
@ -49,7 +49,11 @@ public enum UndoOverlayAction {
|
||||
|
||||
public final class UndoOverlayController: ViewController {
|
||||
private let presentationData: PresentationData
|
||||
public let content: UndoOverlayContent
|
||||
public var content: UndoOverlayContent {
|
||||
didSet {
|
||||
(self.displayNode as! UndoOverlayControllerNode).updateContent(self.content)
|
||||
}
|
||||
}
|
||||
private let elevatedLayout: Bool
|
||||
private let animateInAsReplacement: Bool
|
||||
private var action: (UndoOverlayAction) -> Bool
|
||||
|
@ -24,6 +24,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
private let timerTextNode: ImmediateTextNode
|
||||
private let avatarNode: AvatarNode?
|
||||
private let iconNode: ASImageNode?
|
||||
private var iconImageSize: CGSize?
|
||||
private let iconCheckNode: RadialStatusNode?
|
||||
private let animationNode: AnimationNode?
|
||||
private var animatedStickerNode: AnimatedStickerNode?
|
||||
@ -41,7 +42,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
private let action: (UndoOverlayAction) -> Bool
|
||||
private let dismiss: () -> Void
|
||||
|
||||
private let content: UndoOverlayContent
|
||||
private var content: UndoOverlayContent
|
||||
|
||||
private let effectView: UIView
|
||||
|
||||
@ -752,7 +753,11 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
case let .image(image, text):
|
||||
self.avatarNode = nil
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode?.clipsToBounds = true
|
||||
self.iconNode?.contentMode = .scaleAspectFill
|
||||
self.iconNode?.image = image
|
||||
self.iconNode?.cornerRadius = 4.0
|
||||
self.iconImageSize = CGSize(width: 32.0, height: 32.0)
|
||||
self.iconCheckNode = nil
|
||||
self.animationNode = nil
|
||||
self.animatedStickerNode = nil
|
||||
@ -892,6 +897,24 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
self.checkTimer()
|
||||
}
|
||||
|
||||
func updateContent(_ content: UndoOverlayContent) {
|
||||
self.content = content
|
||||
|
||||
switch content {
|
||||
case let .image(image, text):
|
||||
self.iconNode?.image = image
|
||||
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
self.renewWithCurrentContent()
|
||||
|
||||
if let validLayout = self.validLayout {
|
||||
self.containerLayoutUpdated(layout: validLayout, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
let firstLayout = self.validLayout == nil
|
||||
self.validLayout = layout
|
||||
@ -979,7 +1002,16 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: textContentOrigin), size: titleSize))
|
||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: leftInset, y: textContentOrigin + textOffset), size: textSize))
|
||||
|
||||
if let iconNode = self.iconNode, let iconSize = iconNode.image?.size {
|
||||
if let iconNode = self.iconNode {
|
||||
let iconSize: CGSize
|
||||
if let size = self.iconImageSize {
|
||||
iconSize = size
|
||||
} else if let size = iconNode.image?.size {
|
||||
iconSize = size
|
||||
} else {
|
||||
iconSize = CGSize()
|
||||
}
|
||||
|
||||
let iconFrame = CGRect(origin: CGPoint(x: floor((leftInset - iconSize.width) / 2.0), y: floor((contentHeight - iconSize.height) / 2.0) + verticalOffset), size: iconSize)
|
||||
transition.updateFrame(node: iconNode, frame: iconFrame)
|
||||
|
||||
|
@ -1446,7 +1446,7 @@ public func themeIconImage(account: Account, accountManager: AccountManager<Tele
|
||||
|
||||
return patternWallpaperImage(account: account, accountManager: accountManager, representations: convertedPreviewRepresentations, mode: .thumbnail, autoFetchFullSize: true)
|
||||
|> mapToSignal { generator -> Signal<((UIColor, UIColor?, [UInt32]), [UIColor], [UIColor], UIImage?, Bool, Bool, CGFloat, Int32?), NoError> in
|
||||
let imageSize = CGSize(width: 148, height: 320)
|
||||
let imageSize = CGSize(width: 148.0, height: 320.0)
|
||||
let imageArguments = TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: nil, custom: arguments)
|
||||
let context = generator?(imageArguments)
|
||||
let image = context?.generateImage()
|
||||
|
Loading…
x
Reference in New Issue
Block a user