diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index b31b9d97d7..1845ca9656 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -718,6 +718,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi } func animateOut(result initialResult: ContextMenuActionResult, completion: @escaping () -> Void) { + self.isUserInteractionEnabled = false + self.beganAnimatingOut() var transitionDuration: Double = 0.2 diff --git a/submodules/Display/Source/CAAnimationUtils.swift b/submodules/Display/Source/CAAnimationUtils.swift index 060f9fb134..129c3581b8 100644 --- a/submodules/Display/Source/CAAnimationUtils.swift +++ b/submodules/Display/Source/CAAnimationUtils.swift @@ -229,6 +229,10 @@ public extension CALayer { func animateScale(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = CAMediaTimingFunctionName.easeInEaseOut.rawValue, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.scale", timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) } + + func animateScaleX(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = CAMediaTimingFunctionName.easeInEaseOut.rawValue, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.scale.x", timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, completion: completion) + } func animateScaleY(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = CAMediaTimingFunctionName.easeInEaseOut.rawValue, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.scale.y", timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, completion: completion) diff --git a/submodules/Display/Source/ContainedViewLayoutTransition.swift b/submodules/Display/Source/ContainedViewLayoutTransition.swift index 2642564f85..50de64416a 100644 --- a/submodules/Display/Source/ContainedViewLayoutTransition.swift +++ b/submodules/Display/Source/ContainedViewLayoutTransition.swift @@ -675,6 +675,52 @@ public extension ContainedViewLayoutTransition { }) } } + + func animateTransformScale(node: ASDisplayNode, from fromScale: CGPoint, completion: ((Bool) -> Void)? = nil) { + let t = node.layer.transform + + switch self { + case .immediate: + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + let calculatedFrom: CGPoint + let calculatedTo: CGPoint + + calculatedFrom = fromScale + calculatedTo = CGPoint(x: 1.0, y: 1.0) + + node.layer.animateScaleX(from: calculatedFrom.x, to: calculatedTo.x, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + node.layer.animateScaleY(from: calculatedFrom.y, to: calculatedTo.y, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction) + } + } + + func animateTransformScale(layer: CALayer, from fromScale: CGPoint, completion: ((Bool) -> Void)? = nil) { + switch self { + case .immediate: + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + let calculatedFrom: CGPoint + let calculatedTo: CGPoint + + calculatedFrom = fromScale + calculatedTo = CGPoint(x: 1.0, y: 1.0) + + layer.animateScaleX(from: calculatedFrom.x, to: calculatedTo.x, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + layer.animateScaleY(from: calculatedFrom.y, to: calculatedTo.y, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction) + } + } func animateTransformScale(view: UIView, from fromScale: CGFloat, completion: ((Bool) -> Void)? = nil) { let t = view.layer.transform @@ -920,7 +966,12 @@ public extension ContainedViewLayoutTransition { completion?(true) return } - let t = node.layer.transform + + self.updateTransformScale(layer: node.layer, scale: scale, completion: completion) + } + + func updateTransformScale(layer: CALayer, scale: CGPoint, completion: ((Bool) -> Void)? = nil) { + let t = layer.transform let currentScaleX = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13)) var currentScaleY = sqrt((t.m21 * t.m21) + (t.m22 * t.m22) + (t.m23 * t.m23)) if t.m22 < 0.0 { @@ -932,16 +983,16 @@ public extension ContainedViewLayoutTransition { } return } - + switch self { case .immediate: - node.layer.transform = CATransform3DMakeScale(scale.x, scale.y, 1.0) + layer.transform = CATransform3DMakeScale(scale.x, scale.y, 1.0) if let completion = completion { completion(true) } case let .animated(duration, curve): - node.layer.transform = CATransform3DMakeScale(scale.x, scale.y, 1.0) - node.layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: node.layer.transform), keyPath: "transform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: true, additive: false, completion: { + layer.transform = CATransform3DMakeScale(scale.x, scale.y, 1.0) + layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: layer.transform), keyPath: "transform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: true, additive: false, completion: { result in if let completion = completion { completion(result) diff --git a/submodules/GradientBackground/Sources/GradientBackground.swift b/submodules/GradientBackground/Sources/GradientBackground.swift index 45deab1eb0..7e55be5a08 100644 --- a/submodules/GradientBackground/Sources/GradientBackground.swift +++ b/submodules/GradientBackground/Sources/GradientBackground.swift @@ -86,14 +86,15 @@ private func generateGradientComponent(size: CGSize, color: UIColor) -> UIImage? c?.setBlendMode(.normal) - var gradLocs: [CGFloat] = [0, 0.1, 0.35, 1] + //var gradLocs: [CGFloat] = [0, 0.1, 0.35, 1] + var gradLocs: [CGFloat] = [0.0, 1.0] let colorSpace = CGColorSpaceCreateDeviceRGB() let radius = min(size.width / 2.0, size.height / 2.0) let colors = [ color.cgColor, - color.withAlphaComponent(0.8).cgColor, - color.withAlphaComponent(0.3).cgColor, + //color.withAlphaComponent(0.8).cgColor, + //color.withAlphaComponent(0.3).cgColor, color.withAlphaComponent(0).cgColor ] @@ -110,49 +111,11 @@ private func generateGradientComponent(size: CGSize, color: UIColor) -> UIImage? return image } -private func generateGradient(with size: CGSize, gradPointArray gradPoints: [GradientPoint]) -> UIImage? { - UIGraphicsBeginImageContextWithOptions(size, true, 1.0) - - let c = UIGraphicsGetCurrentContext() - - c?.setFillColor(UIColor.white.cgColor) - c?.fill(CGRect(origin: CGPoint.zero, size: size)) - - c?.setBlendMode(.multiply) - - var gradLocs: [CGFloat] = [0, 0.0, 0.35, 1] - let colorSpace = CGColorSpaceCreateDeviceRGB() - let radius = max(size.width, size.height) - - for point in gradPoints { - let colors = [ - point.color.cgColor, - point.color.withAlphaComponent(0.8).cgColor, - point.color.withAlphaComponent(0.3).cgColor, - point.color.withAlphaComponent(0).cgColor - ] - - let grad = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &gradLocs) - if let grad = grad { - let newPoint = point.position.applying( - .init(scaleX: size.width, y: size.height) - ) - - c?.drawRadialGradient(grad, startCenter: newPoint, startRadius: 0, endCenter: newPoint, endRadius: radius, options: []) - } - } - - let i = UIGraphicsGetImageFromCurrentImageContext() - - UIGraphicsEndImageContext() - - return i -} - public final class GradientBackgroundNode: ASDisplayNode { //private let imageView: UIImageView private var pointImages: [UIImageView] = [] + private let dimView: UIView private let firstStepPoints: [CGPoint] = [ CGPoint(x: 0.823, y: 0.086), @@ -169,6 +132,7 @@ public final class GradientBackgroundNode: ASDisplayNode { ] private var phase: Int = 0 + private var subphase: Int = 0 private var timer: Timer? @@ -177,9 +141,12 @@ public final class GradientBackgroundNode: ASDisplayNode { override public init() { //self.imageView = UIImageView() + self.dimView = UIView() + self.dimView.backgroundColor = UIColor(white: 1.0, alpha: 0.0) + super.init() - self.phase = 3 + self.phase = 0 self.backgroundColor = .white self.clipsToBounds = true @@ -202,22 +169,22 @@ public final class GradientBackgroundNode: ASDisplayNode { //self.imageView.layer.compositingFilter = "multiplyBlendMode" let colors: [UIColor] = [ - UIColor(rgb: 0xfff6bf), - UIColor(rgb: 0x76a076), - UIColor(rgb: 0xf6e477), - UIColor(rgb: 0x316b4d) + UIColor(rgb: 0x7FA381), + UIColor(rgb: 0xFFF5C5), + UIColor(rgb: 0x336F55), + UIColor(rgb: 0xFBE37D) ] - for color in colors { - let pointImage = UIImageView(image: generateGradientComponent(size: CGSize(width: 800.0, height: 800.0), color: color.withMultiplied(hue: 1.0, saturation: 1.2, brightness: 1.0))) - pointImage.layer.compositingFilter = "multiplyBlendMode" - //pointImage.layer.compositingFilter = "additionBlendMode" - pointImage.alpha = 0.7 + for i in 0 ..< colors.count { + let pointImage = UIImageView(image: generateGradientComponent(size: CGSize(width: 300.0, height: 300.0), color: colors[i].withMultiplied(hue: 1.0, saturation: 1.1, brightness: 1.1))) + //pointImage.layer.compositingFilter = "multiplyBlendMode" self.view.addSubview(pointImage) self.pointImages.append(pointImage) } - if #available(iOS 10.0, *) { + self.view.addSubview(self.dimView) + + /*if #available(iOS 10.0, *) { let timer = Timer(timeInterval: 2.0, repeats: true, block: { [weak self] _ in guard let strongSelf = self else { return @@ -229,7 +196,7 @@ public final class GradientBackgroundNode: ASDisplayNode { }) self.timer = timer RunLoop.main.add(timer, forMode: .common) - } + }*/ } deinit { @@ -239,12 +206,28 @@ public final class GradientBackgroundNode: ASDisplayNode { public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { self.validLayout = size + self.dimView.frame = CGRect(origin: CGPoint(), size: size) + let positions: [CGPoint] - if self.phase % 2 == 0 { - positions = shiftArray(array: firstStepPoints, offset: self.phase / 2) - } else { - positions = shiftArray(array: nextStepPoints, offset: self.phase / 2 + 1) + let basePositions: [CGPoint] = [ + CGPoint(x: 0.1, y: 0.1), + CGPoint(x: 0.1, y: 0.9), + CGPoint(x: 0.9, y: 0.9), + CGPoint(x: 0.9, y: 0.1), + ] + + switch self.phase % 4 { + case 0: + positions = basePositions + case 1: + positions = shiftArray(array: basePositions, offset: 1) + case 2: + positions = shiftArray(array: basePositions, offset: 2) + case 3: + positions = shiftArray(array: basePositions, offset: 3) + default: + preconditionFailure() } for i in 0 ..< firstStepPoints.count { @@ -252,14 +235,15 @@ public final class GradientBackgroundNode: ASDisplayNode { break } let pointCenter = CGPoint(x: size.width * positions[i].x, y: size.height * positions[i].y) - let pointSide = max(size.width, size.height) * 2.0 - let pointSize = CGSize(width: pointSide, height: pointSide) + let pointSize = CGSize(width: size.width * 2.0, height: size.height * 2.0) transition.updateFrame(view: self.pointImages[i], frame: CGRect(origin: CGPoint(x: pointCenter.x - pointSize.width / 2.0, y: pointCenter.y - pointSize.height / 2.0), size: pointSize)) } + } - /*self.imageView.frame = CGRect(origin: CGPoint(), size: size) - self.imageView.layer.magnificationFilter = .linear - - self.imageView.image = generateGradient(with: size.fitted(CGSize(width: 64.0, height: 64.0)), gradPointArray: applyTransformerToPoints(step: 0, substep: 0))*/ + public func animateEvent(transition: ContainedViewLayoutTransition) { + self.phase = (self.phase + 1) % 4 + if let size = self.validLayout { + self.updateLayout(size: size, transition: transition) + } } } diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGAttachmentCarouselItemView.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGAttachmentCarouselItemView.h index 9346362f3d..407b78688f 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGAttachmentCarouselItemView.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGAttachmentCarouselItemView.h @@ -62,4 +62,6 @@ - (instancetype)initWithContext:(id)context camera:(bool)hasCamera selfPortrait:(bool)selfPortrait forProfilePhoto:(bool)forProfilePhoto assetType:(TGMediaAssetType)assetType saveEditedPhotos:(bool)saveEditedPhotos allowGrouping:(bool)allowGrouping allowSelection:(bool)allowSelection allowEditing:(bool)allowEditing document:(bool)document selectionLimit:(int)selectionLimit; +- (UIView *)getItemSnapshot:(NSString *)uniqueId; + @end diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h index 9f16354018..5d94419416 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h @@ -74,7 +74,7 @@ typedef enum @property (nonatomic, strong) NSString *recipientName; -@property (nonatomic, copy) NSDictionary *(^descriptionGenerator)(id, NSString *, NSArray *, NSString *); +@property (nonatomic, copy) NSDictionary *(^descriptionGenerator)(id, NSString *, NSArray *, NSString *, NSString *); @property (nonatomic, copy) void (^avatarCompletionBlock)(UIImage *image); @property (nonatomic, copy) void (^completionBlock)(NSArray *signals, bool silentPosting, int32_t scheduleTime); @property (nonatomic, copy) void (^avatarVideoCompletionBlock)(UIImage *image, AVAsset *asset, TGVideoEditAdjustments *adjustments); @@ -92,7 +92,7 @@ typedef enum - (UIBarButtonItem *)rightBarButtonItem; -- (NSArray *)resultSignalsWithCurrentItem:(TGMediaAsset *)currentItem descriptionGenerator:(id (^)(id, NSString *, NSArray *, NSString *))descriptionGenerator; +- (NSArray *)resultSignalsWithCurrentItem:(TGMediaAsset *)currentItem descriptionGenerator:(id (^)(id, NSString *, NSArray *, NSString *, NSString *))descriptionGenerator; - (void)completeWithAvatarImage:(UIImage *)image; - (void)completeWithAvatarVideo:(AVAsset *)asset adjustments:(TGVideoEditAdjustments *)adjustments image:(UIImage *)image; @@ -105,6 +105,6 @@ typedef enum + (TGMediaAssetType)assetTypeForIntent:(TGMediaAssetsControllerIntent)intent; -+ (NSArray *)resultSignalsForSelectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext intent:(TGMediaAssetsControllerIntent)intent currentItem:(TGMediaAsset *)currentItem storeAssets:(bool)storeAssets useMediaCache:(bool)useMediaCache descriptionGenerator:(id (^)(id, NSString *, NSArray *, NSString *))descriptionGenerator saveEditedPhotos:(bool)saveEditedPhotos; ++ (NSArray *)resultSignalsForSelectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext intent:(TGMediaAssetsControllerIntent)intent currentItem:(TGMediaAsset *)currentItem storeAssets:(bool)storeAssets useMediaCache:(bool)useMediaCache descriptionGenerator:(id (^)(id, NSString *, NSArray *, NSString *, NSString *))descriptionGenerator saveEditedPhotos:(bool)saveEditedPhotos; @end diff --git a/submodules/LegacyComponents/Sources/TGAttachmentCarouselItemView.m b/submodules/LegacyComponents/Sources/TGAttachmentCarouselItemView.m index 3aa9bcda19..5cb451457b 100644 --- a/submodules/LegacyComponents/Sources/TGAttachmentCarouselItemView.m +++ b/submodules/LegacyComponents/Sources/TGAttachmentCarouselItemView.m @@ -346,6 +346,21 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500; [_itemsSizeChangedDisposable dispose]; } +- (UIView *)getItemSnapshot:(NSString *)uniqueId { + for (UIView *cell in _collectionView.visibleCells) { + if ([cell isKindOfClass:[TGAttachmentAssetCell class]]) { + TGAttachmentAssetCell *assetCell = (TGAttachmentAssetCell *)cell; + if (assetCell.asset.identifier != nil && [assetCell.asset.identifier isEqualToString:uniqueId]) { + UIView *snapshotView = [assetCell snapshotViewAfterScreenUpdates:false]; + snapshotView.frame = [assetCell convertRect:assetCell.bounds toView:nil]; + assetCell.alpha = 0.01f; + return snapshotView; + } + } + } + return nil; +} + - (void)setPallete:(TGMenuSheetPallete *)pallete { _pallete = pallete; diff --git a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m index 4e3c7e6644..23674be13c 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m @@ -578,8 +578,11 @@ [super loadView]; bool hasOnScreenNavigation = false; - if (iosMajorVersion() >= 11) - hasOnScreenNavigation = (self.viewLoaded && self.view.safeAreaInsets.bottom > FLT_EPSILON) || _context.safeAreaInset.bottom > FLT_EPSILON; + if (iosMajorVersion() >= 11) { + if (@available(iOS 11.0, *)) { + hasOnScreenNavigation = (self.viewLoaded && self.view.safeAreaInsets.bottom > FLT_EPSILON) || _context.safeAreaInset.bottom > FLT_EPSILON; + } + } CGFloat inset = [TGViewController safeAreaInsetForOrientation:self.interfaceOrientation hasOnScreenNavigation:hasOnScreenNavigation].bottom; _toolbarView = [[TGMediaPickerToolbarView alloc] initWithFrame:CGRectMake(0, self.view.frame.size.height - TGMediaPickerToolbarHeight - inset, self.view.frame.size.width, TGMediaPickerToolbarHeight + inset)]; @@ -648,7 +651,9 @@ return; [strongController dismissAnimated:true manual:false completion:nil]; + if (@available(iOS 14, *)) { [[PHPhotoLibrary sharedPhotoLibrary] presentLimitedLibraryPickerFromViewController:strongSelf]; + } }], [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Media.LimitedAccessChangeSettings") type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^ { @@ -725,8 +730,11 @@ orientation = UIInterfaceOrientationLandscapeLeft; bool hasOnScreenNavigation = false; - if (iosMajorVersion() >= 11) - hasOnScreenNavigation = (self.viewLoaded && self.view.safeAreaInsets.bottom > FLT_EPSILON) || _context.safeAreaInset.bottom > FLT_EPSILON; + if (iosMajorVersion() >= 11) { + if (@available(iOS 11.0, *)) { + hasOnScreenNavigation = (self.viewLoaded && self.view.safeAreaInsets.bottom > FLT_EPSILON) || _context.safeAreaInset.bottom > FLT_EPSILON; + } + } _toolbarView.safeAreaInset = [TGViewController safeAreaInsetForOrientation:orientation hasOnScreenNavigation:hasOnScreenNavigation]; _accessView.safeAreaInset = [TGViewController safeAreaInsetForOrientation:orientation hasOnScreenNavigation:hasOnScreenNavigation]; @@ -810,7 +818,7 @@ } } -- (NSArray *)resultSignalsWithCurrentItem:(TGMediaAsset *)currentItem descriptionGenerator:(id (^)(id, NSString *, NSArray *, NSString *))descriptionGenerator +- (NSArray *)resultSignalsWithCurrentItem:(TGMediaAsset *)currentItem descriptionGenerator:(id (^)(id, NSString *, NSArray *, NSString *, NSString *))descriptionGenerator { bool storeAssets = (_editingContext != nil) && self.shouldStoreAssets; @@ -827,7 +835,7 @@ return value; } -+ (NSArray *)resultSignalsForSelectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext intent:(TGMediaAssetsControllerIntent)intent currentItem:(TGMediaAsset *)currentItem storeAssets:(bool)storeAssets useMediaCache:(bool)__unused useMediaCache descriptionGenerator:(id (^)(id, NSString *, NSArray *, NSString *))descriptionGenerator saveEditedPhotos:(bool)saveEditedPhotos ++ (NSArray *)resultSignalsForSelectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext intent:(TGMediaAssetsControllerIntent)intent currentItem:(TGMediaAsset *)currentItem storeAssets:(bool)storeAssets useMediaCache:(bool)__unused useMediaCache descriptionGenerator:(id (^)(id, NSString *, NSArray *, NSString *, NSString *))descriptionGenerator saveEditedPhotos:(bool)saveEditedPhotos { NSMutableArray *signals = [[NSMutableArray alloc] init]; NSMutableArray *selectedItems = selectionContext.selectedItems ? [selectionContext.selectedItems mutableCopy] : [[NSMutableArray alloc] init]; @@ -945,7 +953,7 @@ if (groupedId != nil) dict[@"groupedId"] = groupedId; - id generatedItem = descriptionGenerator(dict, caption, entities, nil); + id generatedItem = descriptionGenerator(dict, caption, entities, nil, asset.identifier); return generatedItem; }] catch:^SSignal *(id error) { @@ -971,7 +979,7 @@ if (groupedId != nil) dict[@"groupedId"] = groupedId; - id generatedItem = descriptionGenerator(dict, caption, entities, nil); + id generatedItem = descriptionGenerator(dict, caption, entities, nil, asset.identifier); return generatedItem; }]; }]]; @@ -998,7 +1006,7 @@ else if (groupedId != nil && !hasAnyTimers) dict[@"groupedId"] = groupedId; - id generatedItem = descriptionGenerator(dict, caption, entities, nil); + id generatedItem = descriptionGenerator(dict, caption, entities, nil, asset.identifier); return generatedItem; }]; @@ -1074,7 +1082,7 @@ else if (groupedId != nil && !hasAnyTimers) dict[@"groupedId"] = groupedId; - id generatedItem = descriptionGenerator(dict, caption, entities, nil); + id generatedItem = descriptionGenerator(dict, caption, entities, nil, asset.identifier); return generatedItem; }]; }]]; @@ -1156,7 +1164,7 @@ else if (groupedId != nil && !hasAnyTimers) dict[@"groupedId"] = groupedId; - id generatedItem = descriptionGenerator(dict, caption, entities, nil); + id generatedItem = descriptionGenerator(dict, caption, entities, nil, asset.identifier); return generatedItem; }] catch:^SSignal *(__unused id error) { @@ -1197,7 +1205,7 @@ if (groupedId != nil) dict[@"groupedId"] = groupedId; - id generatedItem = descriptionGenerator(dict, caption, entities, nil); + id generatedItem = descriptionGenerator(dict, caption, entities, nil, asset.identifier); return generatedItem; }]]; @@ -1267,7 +1275,7 @@ else if (groupedId != nil && !hasAnyTimers) dict[@"groupedId"] = groupedId; - id generatedItem = descriptionGenerator(dict, caption, entities, nil); + id generatedItem = descriptionGenerator(dict, caption, entities, nil, asset.identifier); return generatedItem; }]]; @@ -1345,7 +1353,7 @@ if (timer != nil) dict[@"timer"] = timer; - id generatedItem = descriptionGenerator(dict, caption, entities, nil); + id generatedItem = descriptionGenerator(dict, caption, entities, nil, asset.identifier); return generatedItem; }]]; diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift index bce688145a..7e27064fc5 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift @@ -106,7 +106,10 @@ public func legacyMediaEditor(context: AccountContext, peer: Peer, media: AnyMed TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, caption: initialCaption, entities: [], withItem: item, paint: true, recipientName: recipientName, stickersContext: paintStickersContext, snapshots: snapshots as? [Any], immediate: transitionCompletion != nil, appeared: { transitionCompletion?() }, completion: { result, editingContext in - let signals = TGCameraController.resultSignals(for: nil, editingContext: editingContext, currentItem: result as! TGMediaSelectableItem, storeAssets: false, saveEditedPhotos: false, descriptionGenerator: legacyAssetPickerItemGenerator()) + let nativeGenerator = legacyAssetPickerItemGenerator() + let signals = TGCameraController.resultSignals(for: nil, editingContext: editingContext, currentItem: result as! TGMediaSelectableItem, storeAssets: false, saveEditedPhotos: false, descriptionGenerator: { _1, _2, _3, _4 in + nativeGenerator(_1, _2, _3, _4, nil) + }) sendMessagesWithSignals(signals, false, 0) }, dismissed: { [weak legacyController] in legacyController?.dismiss() @@ -114,7 +117,7 @@ public func legacyMediaEditor(context: AccountContext, peer: Peer, media: AnyMed }) } -public func legacyAttachmentMenu(context: AccountContext, peer: Peer, chatLocation: ChatLocation, editMediaOptions: LegacyAttachmentMenuMediaEditing?, saveEditedPhotos: Bool, allowGrouping: Bool, hasSchedule: Bool, canSendPolls: Bool, presentationData: PresentationData, parentController: LegacyController, recentlyUsedInlineBots: [Peer], initialCaption: String, openGallery: @escaping () -> Void, openCamera: @escaping (TGAttachmentCameraView?, TGMenuSheetController?) -> Void, openFileGallery: @escaping () -> Void, openWebSearch: @escaping () -> Void, openMap: @escaping () -> Void, openContacts: @escaping () -> Void, openPoll: @escaping () -> Void, presentSelectionLimitExceeded: @escaping () -> Void, presentCantSendMultipleFiles: @escaping () -> Void, presentSchedulePicker: @escaping (@escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, selectRecentlyUsedInlineBot: @escaping (Peer) -> Void, presentStickers: @escaping (@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> TGPhotoPaintStickersScreen?, present: @escaping (ViewController, Any?) -> Void) -> TGMenuSheetController { +public func legacyAttachmentMenu(context: AccountContext, peer: Peer, chatLocation: ChatLocation, editMediaOptions: LegacyAttachmentMenuMediaEditing?, saveEditedPhotos: Bool, allowGrouping: Bool, hasSchedule: Bool, canSendPolls: Bool, presentationData: PresentationData, parentController: LegacyController, recentlyUsedInlineBots: [Peer], initialCaption: String, openGallery: @escaping () -> Void, openCamera: @escaping (TGAttachmentCameraView?, TGMenuSheetController?) -> Void, openFileGallery: @escaping () -> Void, openWebSearch: @escaping () -> Void, openMap: @escaping () -> Void, openContacts: @escaping () -> Void, openPoll: @escaping () -> Void, presentSelectionLimitExceeded: @escaping () -> Void, presentCantSendMultipleFiles: @escaping () -> Void, presentSchedulePicker: @escaping (@escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32, @escaping (String) -> UIView?, @escaping () -> Void) -> Void, selectRecentlyUsedInlineBot: @escaping (Peer) -> Void, presentStickers: @escaping (@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> TGPhotoPaintStickersScreen?, present: @escaping (ViewController, Any?) -> Void) -> TGMenuSheetController { let defaultVideoPreset = defaultVideoPresetForContext(context) UserDefaults.standard.set(defaultVideoPreset.rawValue as NSNumber, forKey: "TG_preferredVideoPreset_v0") @@ -217,8 +220,14 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, chatLocati if slowModeEnabled, let signals = signals, signals.count > 1 { presentCantSendMultipleFiles() } else { - controller.dismiss(animated: true) - sendMessagesWithSignals(signals, silentPosting, scheduleTime) + sendMessagesWithSignals(signals, silentPosting, scheduleTime, { [weak carouselItem] uniqueId in + if let carouselItem = carouselItem { + return carouselItem.getItemSnapshot(uniqueId) + } + return nil + }, { [weak controller] in + controller?.dismiss(animated: true) + }) } } }; @@ -310,8 +319,11 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, chatLocati TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, caption: "", entities: [], withItem: item, paint: false, recipientName: recipientName, stickersContext: paintStickersContext, snapshots: [], immediate: false, appeared: { }, completion: { result, editingContext in - let signals = TGCameraController.resultSignals(for: nil, editingContext: editingContext, currentItem: result as! TGMediaSelectableItem, storeAssets: false, saveEditedPhotos: false, descriptionGenerator: legacyAssetPickerItemGenerator()) - sendMessagesWithSignals(signals, false, 0) + let nativeGenerator = legacyAssetPickerItemGenerator() + let signals = TGCameraController.resultSignals(for: nil, editingContext: editingContext, currentItem: result as! TGMediaSelectableItem, storeAssets: false, saveEditedPhotos: false, descriptionGenerator: { _1, _2, _3, _4 in + nativeGenerator(_1, _2, _3, _4, nil) + }) + sendMessagesWithSignals(signals, false, 0, { _ in nil}, {}) }, dismissed: { [weak legacyController] in legacyController?.dismiss() }) @@ -435,7 +447,10 @@ public func presentLegacyPasteMenu(context: AccountContext, peer: Peer, chatLoca done?(time) } }, completed: { selectionContext, editingContext, currentItem, silentPosting, scheduleTime in - let signals = TGClipboardMenu.resultSignals(for: selectionContext, editingContext: editingContext, currentItem: currentItem, descriptionGenerator: legacyAssetPickerItemGenerator()) + let nativeGenerator = legacyAssetPickerItemGenerator() + let signals = TGClipboardMenu.resultSignals(for: selectionContext, editingContext: editingContext, currentItem: currentItem, descriptionGenerator: { _1, _2, _3, _4 in + nativeGenerator(_1, _2, _3, _4, nil) + }) sendMessagesWithSignals(signals, silentPosting, scheduleTime) }, dismissed: { [weak legacyController] in legacyController?.dismiss() diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift index 56dec373e7..b1a92b8663 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift @@ -130,18 +130,20 @@ private final class LegacyAssetItemWrapper: NSObject { let item: LegacyAssetItem let timer: Int? let groupedId: Int64? + let uniqueId: String? - init(item: LegacyAssetItem, timer: Int?, groupedId: Int64?) { + init(item: LegacyAssetItem, timer: Int?, groupedId: Int64?, uniqueId: String?) { self.item = item self.timer = timer self.groupedId = groupedId + self.uniqueId = uniqueId super.init() } } -public func legacyAssetPickerItemGenerator() -> ((Any?, String?, [Any]?, String?) -> [AnyHashable : Any]?) { - return { anyDict, caption, entities, hash in +public func legacyAssetPickerItemGenerator() -> ((Any?, String?, [Any]?, String?, String?) -> [AnyHashable : Any]?) { + return { anyDict, caption, entities, hash, uniqueId in let dict = anyDict as! NSDictionary let stickers = (dict["stickers"] as? [Data])?.compactMap { data -> FileMediaReference? in let decoder = PostboxDecoder(buffer: MemoryBuffer(data: data)) @@ -160,10 +162,10 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, String?, [Any]?, String? let url: String? = (dict["url"] as? String) ?? (dict["url"] as? URL)?.path if let url = url { let dimensions = image.size - result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: 4.0), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: false, asAnimation: true, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) + result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: 4.0), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: false, asAnimation: true, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) } } else { - result["item" as NSString] = LegacyAssetItemWrapper(item: .image(data: .image(image), thumbnail: thumbnail, caption: caption, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) + result["item" as NSString] = LegacyAssetItemWrapper(item: .image(data: .image(image), thumbnail: thumbnail, caption: caption, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) } return result } else if (dict["type"] as! NSString) == "cloudPhoto" { @@ -184,9 +186,9 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, String?, [Any]?, String? name = customName } - result["item" as NSString] = LegacyAssetItemWrapper(item: .file(data: .asset(asset.backingAsset), thumbnail: thumbnail, mimeType: mimeType, name: name, caption: caption), timer: nil, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) + result["item" as NSString] = LegacyAssetItemWrapper(item: .file(data: .asset(asset.backingAsset), thumbnail: thumbnail, mimeType: mimeType, name: name, caption: caption), timer: nil, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) } else { - result["item" as NSString] = LegacyAssetItemWrapper(item: .image(data: .asset(asset.backingAsset), thumbnail: thumbnail, caption: caption, stickers: []), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) + result["item" as NSString] = LegacyAssetItemWrapper(item: .image(data: .asset(asset.backingAsset), thumbnail: thumbnail, caption: caption, stickers: []), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) } return result } else if (dict["type"] as! NSString) == "file" { @@ -207,12 +209,12 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, String?, [Any]?, String? let dimensions = (dict["dimensions"]! as AnyObject).cgSizeValue! let duration = (dict["duration"]! as AnyObject).doubleValue! - result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: tempFileUrl.path, dimensions: dimensions, duration: duration), thumbnail: thumbnail, adjustments: nil, caption: caption, asFile: false, asAnimation: true, stickers: []), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) + result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: tempFileUrl.path, dimensions: dimensions, duration: duration), thumbnail: thumbnail, adjustments: nil, caption: caption, asFile: false, asAnimation: true, stickers: []), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) return result } var result: [AnyHashable: Any] = [:] - result["item" as NSString] = LegacyAssetItemWrapper(item: .file(data: .tempFile(tempFileUrl.path), thumbnail: thumbnail, mimeType: mimeType, name: name, caption: caption), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) + result["item" as NSString] = LegacyAssetItemWrapper(item: .file(data: .tempFile(tempFileUrl.path), thumbnail: thumbnail, mimeType: mimeType, name: name, caption: caption), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) return result } } else if (dict["type"] as! NSString) == "video" { @@ -224,13 +226,13 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, String?, [Any]?, String? if let asset = dict["asset"] as? TGMediaAsset { var result: [AnyHashable: Any] = [:] - result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .asset(asset), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) + result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .asset(asset), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) return result } else if let url = (dict["url"] as? String) ?? (dict["url"] as? URL)?.absoluteString { let dimensions = (dict["dimensions"]! as AnyObject).cgSizeValue! let duration = (dict["duration"]! as AnyObject).doubleValue! var result: [AnyHashable: Any] = [:] - result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: duration), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) + result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: duration), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) return result } } else if (dict["type"] as! NSString) == "cameraVideo" { @@ -246,7 +248,7 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, String?, [Any]?, String? let dimensions = previewImage.pixelSize() let duration = (dict["duration"]! as AnyObject).doubleValue! var result: [AnyHashable: Any] = [:] - result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: duration), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) + result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: duration), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId) return result } } @@ -338,10 +340,15 @@ public func legacyEnqueueVideoMessage(account: Account, data: Data, correlationI } |> runOn(Queue.concurrentDefaultQueue()) } -public func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -> Signal<[EnqueueMessage], Void> { +public struct LegacyAssetPickerEnqueueMessage { + public var message: EnqueueMessage + public var uniqueId: String? +} + +public func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -> Signal<[LegacyAssetPickerEnqueueMessage], Void> { return Signal { subscriber in let disposable = SSignal.combineSignals(signals).start(next: { anyValues in - var messages: [EnqueueMessage] = [] + var messages: [LegacyAssetPickerEnqueueMessage] = [] outer: for item in (anyValues as! NSArray) { if let item = (item as? NSDictionary)?.object(forKey: "item") as? LegacyAssetItemWrapper { @@ -391,7 +398,7 @@ public func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) - } var text = caption ?? "" - messages.append(.message(text: text, attributes: attributes, mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: item.groupedId, correlationId: nil)) + messages.append(LegacyAssetPickerEnqueueMessage(message: .message(text: text, attributes: attributes, mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: item.groupedId, correlationId: nil), uniqueId: item.uniqueId)) } } case let .asset(asset): @@ -407,7 +414,7 @@ public func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) - if let timer = item.timer, timer > 0 && timer <= 60 { attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil)) } - messages.append(.message(text: caption ?? "", attributes: attributes, mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: item.groupedId, correlationId: nil)) + messages.append(LegacyAssetPickerEnqueueMessage(message: .message(text: caption ?? "", attributes: attributes, mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: item.groupedId, correlationId: nil), uniqueId: item.uniqueId)) case .tempFile: break } @@ -418,13 +425,13 @@ public func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) - arc4random_buf(&randomId, 8) let resource = LocalFileReferenceMediaResource(localFilePath: path, randomId: randomId) let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: mimeType, size: nil, attributes: [.FileName(fileName: name)]) - messages.append(.message(text: caption ?? "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: item.groupedId, correlationId: nil)) + messages.append(LegacyAssetPickerEnqueueMessage(message: .message(text: caption ?? "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: item.groupedId, correlationId: nil), uniqueId: item.uniqueId)) case let .asset(asset): var randomId: Int64 = 0 arc4random_buf(&randomId, 8) let resource = PhotoLibraryMediaResource(localIdentifier: asset.localIdentifier, uniqueId: Int64.random(in: Int64.min ... Int64.max)) let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: mimeType, size: nil, attributes: [.FileName(fileName: name)]) - messages.append(.message(text: caption ?? "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: item.groupedId, correlationId: nil)) + messages.append(LegacyAssetPickerEnqueueMessage(message: .message(text: caption ?? "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: item.groupedId, correlationId: nil), uniqueId: item.uniqueId)) default: break } @@ -552,7 +559,7 @@ public func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) - if let timer = item.timer, timer > 0 && timer <= 60 { attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil)) } - messages.append(.message(text: caption ?? "", attributes: attributes, mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: item.groupedId, correlationId: nil)) + messages.append(LegacyAssetPickerEnqueueMessage(message: .message(text: caption ?? "", attributes: attributes, mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: item.groupedId, correlationId: nil), uniqueId: item.uniqueId)) } } } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 67a766b535..b3a1805c55 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -4523,7 +4523,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } else { isScheduledMessages = false } - strongSelf.chatDisplayNode.containerLayoutUpdated(validLayout, navigationBarHeight: strongSelf.navigationHeight, transition: .animated(duration: strongSelf.chatDisplayNode.messageTransitionNode.hasScheduledTransitions ? 0.5 : 0.4, curve: strongSelf.chatDisplayNode.messageTransitionNode.hasScheduledTransitions ? .custom(0.33, 0.0, 0.0, 1.0) : .spring), listViewTransaction: { updateSizeAndInsets, _, _, _ in + strongSelf.chatDisplayNode.containerLayoutUpdated(validLayout, navigationBarHeight: strongSelf.navigationHeight, transition: .animated(duration: strongSelf.chatDisplayNode.messageTransitionNode.hasScheduledTransitions ? 0.5 : 0.3, curve: strongSelf.chatDisplayNode.messageTransitionNode.hasScheduledTransitions ? .custom(0.33, 0.0, 0.0, 1.0) : .spring), listViewTransaction: { updateSizeAndInsets, _, _, _ in var options = transition.options let _ = options.insert(.Synchronous) let _ = options.insert(.LowLatency) @@ -8374,7 +8374,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G private func editMessageMediaWithLegacySignals(_ signals: [Any]) { let _ = (legacyAssetPickerEnqueueMessages(account: self.context.account, signals: signals) |> deliverOnMainQueue).start(next: { [weak self] messages in - self?.editMessageMediaWithMessages(messages) + self?.editMessageMediaWithMessages(messages.map { $0.message }) }) } @@ -8583,14 +8583,21 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G done(time) }) } - }, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in + }, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime, getAnimatedTransitionSource, completion in + guard let strongSelf = self else { + completion() + return + } if !inputText.string.isEmpty { //strongSelf.clearInputText() } if editMediaOptions != nil { - self?.editMessageMediaWithLegacySignals(signals!) + strongSelf.editMessageMediaWithLegacySignals(signals!) + completion() } else { - self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil) + strongSelf.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: { + completion() + }) } }, selectRecentlyUsedInlineBot: { [weak self] peer in if let strongSelf = self, let addressName = peer.addressName { @@ -9531,11 +9538,32 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - private func enqueueMediaMessages(signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil) { + private func enqueueMediaMessages(signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil, getAnimatedTransitionSource: @escaping (String) -> UIView? = { _ in nil }, completion: @escaping () -> Void = {}) { self.enqueueMediaMessageDisposable.set((legacyAssetPickerEnqueueMessages(account: self.context.account, signals: signals!) - |> deliverOnMainQueue).start(next: { [weak self] messages in + |> deliverOnMainQueue).start(next: { [weak self] items in if let strongSelf = self { - let messages = strongSelf.transformEnqueueMessages(messages, silentPosting: silentPosting, scheduleTime: scheduleTime) + var completionImpl: (() -> Void)? = completion + + var mappedMessages: [EnqueueMessage] = [] + for item in items { + var message = item.message + if let uniqueId = item.uniqueId { + let correlationId = Int64.random(in: 0 ..< Int64.max) + message = message.withUpdatedCorrelationId(correlationId) + + if items.count == 1 { + completionImpl = nil + strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .mediaInput(ChatMessageTransitionNode.Source.MediaInput(extractSnapshot: { + return getAnimatedTransitionSource(uniqueId) + })), initiated: { + completion() + }) + } + } + mappedMessages.append(message) + } + + let messages = strongSelf.transformEnqueueMessages(mappedMessages, silentPosting: silentPosting, scheduleTime: scheduleTime) let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { @@ -9543,7 +9571,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } }) } + completionImpl?() }) + strongSelf.sendMessages(messages.map { $0.withUpdatedReplyToMessageId(replyMessageId) }) } })) diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index e0e7c85b75..27ea7edb62 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -346,6 +346,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { let navigateButtons: ChatHistoryNavigationButtons private var ignoreUpdateHeight = false + private var overrideUpdateTextInputHeightTransition: ContainedViewLayoutTransition? private var animateInAsOverlayCompletion: (() -> Void)? private var dismissAsOverlayCompletion: (() -> Void)? @@ -469,7 +470,11 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.backgroundNode = WallpaperBackgroundNode() self.backgroundNode.displaysAsynchronously = false - self.gradientBackgroundNode = GradientBackgroundNode() + if chatPresentationInterfaceState.chatWallpaper.isBuiltin { + self.gradientBackgroundNode = GradientBackgroundNode() + } else { + self.gradientBackgroundNode = nil + } self.titleAccessoryPanelContainer = ChatControllerTitlePanelNodeContainer() self.titleAccessoryPanelContainer.clipsToBounds = true @@ -510,8 +515,11 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.navigationBarSeparatorNode.backgroundColor = chatPresentationInterfaceState.theme.rootController.navigationBar.separatorColor var getContentAreaInScreenSpaceImpl: (() -> CGRect)? + var onTransitionEventImpl: ((ContainedViewLayoutTransition) -> Void)? self.messageTransitionNode = ChatMessageTransitionNode(listNode: self.historyNode, getContentAreaInScreenSpace: { return getContentAreaInScreenSpaceImpl?() ?? CGRect() + }, onTransitionEvent: { transition in + onTransitionEventImpl?(transition) }) super.init() @@ -523,6 +531,13 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { return strongSelf.view.convert(strongSelf.frameForVisibleArea(), to: nil) } + + onTransitionEventImpl = { [weak self] transition in + guard let strongSelf = self, let gradientBackgroundNode = strongSelf.gradientBackgroundNode else { + return + } + gradientBackgroundNode.animateEvent(transition: transition) + } self.controller?.presentationContext.topLevelSubview = { [weak self] in guard let strongSelf = self else { @@ -633,7 +648,15 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.textInputPanelNode?.updateHeight = { [weak self] animated in if let strongSelf = self, let _ = strongSelf.inputPanelNode as? ChatTextInputPanelNode, !strongSelf.ignoreUpdateHeight { if strongSelf.scheduledLayoutTransitionRequest == nil { - strongSelf.scheduleLayoutTransitionRequest(animated ? .animated(duration: 0.1, curve: .easeInOut) : .immediate) + let transition: ContainedViewLayoutTransition + if !animated { + transition = .immediate + } else if let overrideUpdateTextInputHeightTransition = strongSelf.overrideUpdateTextInputHeightTransition { + transition = overrideUpdateTextInputHeightTransition + } else { + transition = .animated(duration: 0.1, curve: .easeInOut) + } + strongSelf.scheduleLayoutTransitionRequest(transition) } } } @@ -1908,6 +1931,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { let listBottomInset = self.historyNode.insets.top if let previousListBottomInset = previousListBottomInset, listBottomInset != previousListBottomInset { + if abs(listBottomInset - previousListBottomInset) > 80.0 { + self.gradientBackgroundNode?.animateEvent(transition: transition) + } //self.historyNode.didScrollWithOffset?(listBottomInset - previousListBottomInset, transition, nil) } @@ -2000,7 +2026,10 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } if let textInputPanelNode = self.textInputPanelNode, updateInputTextState { + let previous = self.overrideUpdateTextInputHeightTransition + self.overrideUpdateTextInputHeightTransition = transition textInputPanelNode.updateInputTextState(chatPresentationInterfaceState.interfaceState.effectiveInputState, keepSendButtonEnabled: keepSendButtonEnabled, extendedSearchLayout: extendedSearchLayout, accessoryItems: chatPresentationInterfaceState.inputTextPanelState.accessoryItems, animated: transition.isAnimated) + self.overrideUpdateTextInputHeightTransition = previous } else { self.textInputPanelNode?.updateKeepSendButtonEnabled(keepSendButtonEnabled: keepSendButtonEnabled, extendedSearchLayout: extendedSearchLayout, animated: transition.isAnimated) } diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index 0abb7cf070..59468a690d 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -717,6 +717,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } return nil } + + func animateContentFromMediaInput(snapshotView: UIView, horizontalTransition: ContainedViewLayoutTransition, verticalTransition: ContainedViewLayoutTransition) { + self.mainContextSourceNode.contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + } override func didLoad() { super.didLoad() diff --git a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift index 4c5d935b5e..e3f6312f21 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift @@ -147,10 +147,19 @@ final class ChatMessageTransitionNode: ASDisplayNode { } } + final class MediaInput { + let extractSnapshot: () -> UIView? + + init(extractSnapshot: @escaping () -> UIView?) { + self.extractSnapshot = extractSnapshot + } + } + case textInput(textInput: TextInput, replyPanel: ReplyAccessoryPanelNode?) case stickerMediaInput(input: StickerInput, replyPanel: ReplyAccessoryPanelNode?) case audioMicInput(AudioMicInput) case videoMessage(VideoMessage) + case mediaInput(MediaInput) } private final class AnimatingItemNode: ASDisplayNode { @@ -203,7 +212,7 @@ final class ChatMessageTransitionNode: ASDisplayNode { updatedContentAreaInScreenSpace.origin.x = 0.0 updatedContentAreaInScreenSpace.size.width = self.clippingNode.bounds.width - let timingFunction = CAMediaTimingFunction(controlPoints: 0.33, 0.0, 0.0, 1.0) + //let timingFunction = CAMediaTimingFunction(controlPoints: 0.33, 0.0, 0.0, 1.0) let clippingOffset = updatedContentAreaInScreenSpace.minY - self.clippingNode.frame.minY self.clippingNode.frame = CGRect(origin: CGPoint(x: 0.0, y: updatedContentAreaInScreenSpace.minY), size: self.clippingNode.bounds.size) @@ -303,8 +312,6 @@ final class ChatMessageTransitionNode: ASDisplayNode { sourceReplyPanel = ReplyPanel(titleNode: replyPanel.titleNode, textNode: replyPanel.textNode, lineNode: replyPanel.lineNode, imageNode: replyPanel.imageNode, relativeSourceRect: replySourceAbsoluteFrame) } - self.itemNode.cancelInsertionAnimations() - let transition: ContainedViewLayoutTransition = .animated(duration: horizontalDuration, curve: .custom(0.33, 0.0, 0.0, 1.0)) if let itemNode = self.itemNode as? ChatMessageAnimatedStickerItemNode { @@ -410,6 +417,54 @@ final class ChatMessageTransitionNode: ASDisplayNode { itemNode.animateFromSnapshot(snapshotView: videoMessage.view, transition: transition) } + case let .mediaInput(mediaInput): + if let snapshotView = mediaInput.extractSnapshot() { + if let itemNode = self.itemNode as? ChatMessageBubbleItemNode { + itemNode.cancelInsertionAnimations() + + self.contextSourceNode.isExtractedToContextPreview = true + self.contextSourceNode.isExtractedToContextPreviewUpdated?(true) + + self.containerNode.addSubnode(self.contextSourceNode.contentNode) + + let targetAbsoluteRect = self.contextSourceNode.view.convert(self.contextSourceNode.contentRect, to: nil) + let sourceBackgroundAbsoluteRect = snapshotView.frame + let sourceAbsoluteRect = CGRect(origin: CGPoint(x: sourceBackgroundAbsoluteRect.midX - self.contextSourceNode.contentRect.size.width / 2.0, y: sourceBackgroundAbsoluteRect.midY - self.contextSourceNode.contentRect.size.height / 2.0), size: self.contextSourceNode.contentRect.size) + + let transition: ContainedViewLayoutTransition = .animated(duration: verticalDuration, curve: .custom(0.33, 0.0, 0.0, 1.0)) + let verticalTransition: ContainedViewLayoutTransition = .animated(duration: horizontalDuration, curve: .custom(0.33, 0.0, 0.0, 1.0)) + + if let itemNode = self.itemNode as? ChatMessageBubbleItemNode { + itemNode.animateContentFromMediaInput(snapshotView: snapshotView, horizontalTransition: verticalTransition, verticalTransition: transition) + } + + self.containerNode.frame = targetAbsoluteRect.offsetBy(dx: -self.contextSourceNode.contentRect.minX, dy: -self.contextSourceNode.contentRect.minY) + + snapshotView.center = targetAbsoluteRect.center.offsetBy(dx: -self.containerNode.frame.minX, dy: -self.containerNode.frame.minY) + self.containerNode.view.addSubview(snapshotView) + + self.contextSourceNode.updateAbsoluteRect?(self.containerNode.frame, UIScreen.main.bounds.size) + + self.containerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: sourceAbsoluteRect.midY - targetAbsoluteRect.midY), to: CGPoint(), duration: horizontalDuration, delay: delay, mediaTimingFunction: CAMediaTimingFunction(controlPoints: 0.33, 0.0, 0.0, 1.0), additive: true, force: true) + self.containerNode.layer.animatePosition(from: CGPoint(x: sourceAbsoluteRect.midX - targetAbsoluteRect.midX, y: 0.0), to: CGPoint(), duration: verticalDuration, delay: delay, mediaTimingFunction: CAMediaTimingFunction(controlPoints: 0.33, 0.0, 0.0, 1.0), additive: true, force: true, completion: { [weak self] _ in + guard let strongSelf = self else { + return + } + strongSelf.endAnimation() + }) + + verticalTransition.animateTransformScale(node: self.contextSourceNode.contentNode, from: CGPoint(x: sourceBackgroundAbsoluteRect.width / targetAbsoluteRect.width, y: sourceBackgroundAbsoluteRect.height / targetAbsoluteRect.height)) + + verticalTransition.updateTransformScale(layer: snapshotView.layer, scale: CGPoint(x: 1.0 / (sourceBackgroundAbsoluteRect.width / targetAbsoluteRect.width), y: 1.0 / (sourceBackgroundAbsoluteRect.height / targetAbsoluteRect.height))) + + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + + self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: sourceAbsoluteRect.minX - targetAbsoluteRect.minX, y: 0.0), .custom(0.33, 0.0, 0.0, 1.0), horizontalDuration) + self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: 0.0, y: sourceAbsoluteRect.maxY - targetAbsoluteRect.maxY), .custom(0.33, 0.0, 0.0, 1.0), verticalDuration) + } + } } } @@ -455,6 +510,7 @@ final class ChatMessageTransitionNode: ASDisplayNode { private let listNode: ChatHistoryListNode private let getContentAreaInScreenSpace: () -> CGRect + private let onTransitionEvent: (ContainedViewLayoutTransition) -> Void private var currentPendingItem: (Int64, Source, () -> Void)? @@ -464,9 +520,10 @@ final class ChatMessageTransitionNode: ASDisplayNode { return self.currentPendingItem != nil } - init(listNode: ChatHistoryListNode, getContentAreaInScreenSpace: @escaping () -> CGRect) { + init(listNode: ChatHistoryListNode, getContentAreaInScreenSpace: @escaping () -> CGRect, onTransitionEvent: @escaping (ContainedViewLayoutTransition) -> Void) { self.listNode = listNode self.getContentAreaInScreenSpace = getContentAreaInScreenSpace + self.onTransitionEvent = onTransitionEvent super.init() @@ -505,7 +562,7 @@ final class ChatMessageTransitionNode: ASDisplayNode { self.animatingItemNodes.append(animatingItemNode) switch source { - case .audioMicInput, .videoMessage: + case .audioMicInput, .videoMessage, .mediaInput: let overlayController = OverlayTransitionContainerController() overlayController.displayNode.addSubnode(animatingItemNode) animatingItemNode.overlayController = overlayController @@ -527,6 +584,8 @@ final class ChatMessageTransitionNode: ASDisplayNode { animatingItemNode.frame = self.bounds animatingItemNode.beginAnimation() + + self.onTransitionEvent(.animated(duration: 0.5, curve: .custom(0.33, 0.0, 0.0, 1.0))) } } diff --git a/submodules/TelegramUI/Sources/LegacyCamera.swift b/submodules/TelegramUI/Sources/LegacyCamera.swift index c253078988..6a92bb8726 100644 --- a/submodules/TelegramUI/Sources/LegacyCamera.swift +++ b/submodules/TelegramUI/Sources/LegacyCamera.swift @@ -123,7 +123,10 @@ func presentedLegacyCamera(context: AccountContext, peer: Peer, chatLocation: Ch controller.finishedWithResults = { [weak menuController, weak legacyController] overlayController, selectionContext, editingContext, currentItem, silentPosting, scheduleTime in if let selectionContext = selectionContext, let editingContext = editingContext { - let signals = TGCameraController.resultSignals(for: selectionContext, editingContext: editingContext, currentItem: currentItem, storeAssets: saveCapturedPhotos && !isSecretChat, saveEditedPhotos: saveCapturedPhotos && !isSecretChat, descriptionGenerator: legacyAssetPickerItemGenerator()) + let nativeGenerator = legacyAssetPickerItemGenerator() + let signals = TGCameraController.resultSignals(for: selectionContext, editingContext: editingContext, currentItem: currentItem, storeAssets: saveCapturedPhotos && !isSecretChat, saveEditedPhotos: saveCapturedPhotos && !isSecretChat, descriptionGenerator: { _1, _2, _3, _4 in + nativeGenerator(_1, _2, _3, _4, nil) + }) sendMessagesWithSignals(signals, silentPosting, scheduleTime) } @@ -139,7 +142,7 @@ func presentedLegacyCamera(context: AccountContext, peer: Peer, chatLocation: Ch if let timer = timer { description["timer"] = timer } - if let item = legacyAssetPickerItemGenerator()(description, caption, entities, nil) { + if let item = legacyAssetPickerItemGenerator()(description, caption, entities, nil, nil) { sendMessagesWithSignals([SSignal.single(item)], false, 0) } } @@ -164,7 +167,7 @@ func presentedLegacyCamera(context: AccountContext, peer: Peer, chatLocation: Ch if let timer = timer { description["timer"] = timer } - if let item = legacyAssetPickerItemGenerator()(description, caption, entities, nil) { + if let item = legacyAssetPickerItemGenerator()(description, caption, entities, nil, nil) { sendMessagesWithSignals([SSignal.single(item)], false, 0) } } @@ -217,16 +220,19 @@ func presentedLegacyShortcutCamera(context: AccountContext, saveCapturedMedia: B controller.finishedWithResults = { [weak controller, weak parentController, weak legacyController] overlayController, selectionContext, editingContext, currentItem, _, _ in if let selectionContext = selectionContext, let editingContext = editingContext { - let signals = TGCameraController.resultSignals(for: selectionContext, editingContext: editingContext, currentItem: currentItem, storeAssets: saveCapturedMedia, saveEditedPhotos: saveEditedPhotos, descriptionGenerator: legacyAssetPickerItemGenerator()) + let nativeGenerator = legacyAssetPickerItemGenerator() + let signals = TGCameraController.resultSignals(for: selectionContext, editingContext: editingContext, currentItem: currentItem, storeAssets: saveCapturedMedia, saveEditedPhotos: saveEditedPhotos, descriptionGenerator: { _1, _2, _3, _4 in + nativeGenerator(_1, _2, _3, _4, nil) + }) if let parentController = parentController { parentController.present(ShareController(context: context, subject: .fromExternal({ peerIds, text, account in return legacyAssetPickerEnqueueMessages(account: account, signals: signals!) - |> `catch` { _ -> Signal<[EnqueueMessage], NoError> in + |> `catch` { _ -> Signal<[LegacyAssetPickerEnqueueMessage], NoError> in return .single([]) } |> mapToSignal { messages -> Signal in let resultSignals = peerIds.map({ peerId in - return enqueueMessages(account: account, peerId: peerId, messages: messages) + return enqueueMessages(account: account, peerId: peerId, messages: messages.map { $0.message }) |> mapToSignal { _ -> Signal in return .complete() } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 6aabd0001d..0755dbe75a 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -3113,7 +3113,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD strongSelf.enqueueMediaMessageDisposable.set((legacyAssetPickerEnqueueMessages(account: strongSelf.context.account, signals: signals!) |> deliverOnMainQueue).start(next: { [weak self] messages in if let strongSelf = self { - let _ = enqueueMessages(account: strongSelf.context.account, peerId: strongSelf.peerId, messages: messages).start() + let _ = enqueueMessages(account: strongSelf.context.account, peerId: strongSelf.peerId, messages: messages.map { $0.message }).start() } })) } diff --git a/submodules/WebSearchUI/Sources/LegacyWebSearchGallery.swift b/submodules/WebSearchUI/Sources/LegacyWebSearchGallery.swift index ab3e1d4267..d45a671e63 100644 --- a/submodules/WebSearchUI/Sources/LegacyWebSearchGallery.swift +++ b/submodules/WebSearchUI/Sources/LegacyWebSearchGallery.swift @@ -466,7 +466,7 @@ public func legacyEnqueueWebSearchMessages(_ selectionState: TGMediaSelectionCon } } - return legacyAssetPickerItemGenerator()(dict, nil, nil, nil) as Any + return legacyAssetPickerItemGenerator()(dict, nil, nil, nil, nil) as Any } else { return SSignal.complete() }