diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index d4c335f4c6..09174f58ca 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -13715,6 +13715,7 @@ Sorry for the inconvenience."; "PeerInfo.Gifts.SendGift" = "Send Gift"; "PeerInfo.Gifts.ChannelNotify" = "Notify About New Gifts"; "PeerInfo.Gifts.ChannelNotifyTooltip" = "You will receive a message from Telegram when your channel receives a gift."; +"PeerInfo.Gifts.ChannelNotifyDisabledTooltip" = "You will not receive a message from Telegram when your channel receives a gift."; "Notification.StarsGift.Channel.Sent" = "%1$@ sent a gift to %2$@ for %3$@"; diff --git a/submodules/AvatarNode/Sources/AvatarNode.swift b/submodules/AvatarNode/Sources/AvatarNode.swift index 24523c4537..ef4f04dd57 100644 --- a/submodules/AvatarNode/Sources/AvatarNode.swift +++ b/submodules/AvatarNode/Sources/AvatarNode.swift @@ -87,7 +87,7 @@ public func calculateAvatarColors(context: AccountContext?, explicitColorIndex: } let colors: [UIColor] - if icon != .none && icon != .cameraIcon { + if icon != .none { if case .deletedIcon = icon { colors = AvatarNode.grayscaleColors } else if case .phoneIcon = icon { @@ -120,6 +120,8 @@ public func calculateAvatarColors(context: AccountContext?, explicitColorIndex: backgroundColors = theme.chatList.pinnedArchiveAvatarColor.backgroundColors.colors } colors = [backgroundColors.1, backgroundColors.0] + } else if case .cameraIcon = icon { + colors = AvatarNode.repostColors } else { colors = AvatarNode.grayscaleColors } @@ -288,7 +290,7 @@ public final class AvatarNode: ASDisplayNode { ] static let repostColors: [UIColor] = [ - UIColor(rgb: 0x34C76F), UIColor(rgb: 0x3DA1FD) + UIColor(rgb: 0x3DA1FD), UIColor(rgb: 0x34C76F) ] public final class ContentNode: ASDisplayNode { diff --git a/submodules/Display/Source/GridNode.swift b/submodules/Display/Source/GridNode.swift index 568bfb5257..622317ad8d 100644 --- a/submodules/Display/Source/GridNode.swift +++ b/submodules/Display/Source/GridNode.swift @@ -68,15 +68,15 @@ public struct GridNodeLayout: Equatable { public let scrollIndicatorInsets: UIEdgeInsets? public let preloadSize: CGFloat public let type: GridNodeLayoutType - public let cutout: CGRect? + public let cutouts: [CGRect] - public init(size: CGSize, insets: UIEdgeInsets, scrollIndicatorInsets: UIEdgeInsets? = nil, preloadSize: CGFloat, type: GridNodeLayoutType, cutout: CGRect? = nil) { + public init(size: CGSize, insets: UIEdgeInsets, scrollIndicatorInsets: UIEdgeInsets? = nil, preloadSize: CGFloat, type: GridNodeLayoutType, cutouts: [CGRect] = []) { self.size = size self.insets = insets self.scrollIndicatorInsets = scrollIndicatorInsets self.preloadSize = preloadSize self.type = type - self.cutout = cutout + self.cutouts = cutouts } } @@ -568,8 +568,12 @@ open class GridNode: GridNodeScroller, ASScrollViewDelegate { } } - if let cutout = self.gridLayout.cutout, cutout.intersects(CGRect(origin: nextItemOrigin, size: itemSize)) { - nextItemOrigin.x += cutout.width + itemSpacing + if !self.gridLayout.cutouts.isEmpty, nextItemOrigin.y < itemSize.height * 3.0 { + for cutout in self.gridLayout.cutouts { + if cutout.intersects(CGRect(origin: nextItemOrigin, size: itemSize)) { + nextItemOrigin.x += cutout.width + itemSpacing + } + } } if !incrementedCurrentRow { diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAvatarMenuMixin.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAvatarMenuMixin.h index a44a5ea677..c862703c9e 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAvatarMenuMixin.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAvatarMenuMixin.h @@ -26,7 +26,7 @@ typedef void (^TGMediaAvatarPresentImpl)(id, void (^)(U @property (nonatomic, copy) void (^requestSearchController)(TGMediaAssetsController *); @property (nonatomic, copy) CGRect (^sourceRect)(void); -@property (nonatomic, copy) void (^requestAvatarEditor)(void (^)(UIImage *image, void(^commit)(void)), void (^)(UIImage *image, NSURL *asset, TGVideoEditAdjustments *adjustments, void(^commit)(void))); +@property (nonatomic, copy) void (^requestAvatarEditor)(void (^)(UIImage *image, void(^commit)(void)), void (^)(UIImage *image, NSURL *asset, id adjustments, id markup, void(^commit)(void))); @property (nonatomic, strong) id stickersContext; diff --git a/submodules/LegacyComponents/Sources/TGMediaAvatarMenuMixin.m b/submodules/LegacyComponents/Sources/TGMediaAvatarMenuMixin.m index b415e1b0cb..113259fed5 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAvatarMenuMixin.m +++ b/submodules/LegacyComponents/Sources/TGMediaAvatarMenuMixin.m @@ -193,60 +193,60 @@ }]; [itemViews addObject:galleryItem]; - if (!_signup) { - TGMenuSheetButtonItemView *viewItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"ProfilePhoto.SetEmoji") type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^ - { - __strong TGMediaAvatarMenuMixin *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - __strong TGMenuSheetController *strongController = weakController; - if (strongController == nil) - return; - - [strongController dismissAnimated:true]; - if (strongSelf != nil && strongSelf.requestAvatarEditor) { - strongSelf.requestAvatarEditor(^(UIImage *image, void (^commit)(void)) { - __strong TGMediaAvatarMenuMixin *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - if (strongSelf.willFinishWithImage != nil) { - strongSelf.willFinishWithImage(image, ^{ - if (strongSelf.didFinishWithImage != nil) - strongSelf.didFinishWithImage(image); - - commit(); - }); - } else { - if (strongSelf.didFinishWithImage != nil) - strongSelf.didFinishWithImage(image); - - commit(); - } - }, ^(UIImage *image, NSURL *asset, TGVideoEditAdjustments *adjustments, void (^commit)(void)) { - __strong TGMediaAvatarMenuMixin *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - if (strongSelf.willFinishWithVideo != nil) { - strongSelf.willFinishWithVideo(image, ^{ - if (strongSelf.didFinishWithVideo != nil) - strongSelf.didFinishWithVideo(image, asset, adjustments); - - commit(); - }); - } else { - if (strongSelf.didFinishWithVideo != nil) - strongSelf.didFinishWithVideo(image, asset, adjustments); - - commit(); - } - }); - } - }]; - [itemViews addObject:viewItem]; - } +// if (!_signup) { +// TGMenuSheetButtonItemView *viewItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"ProfilePhoto.SetEmoji") type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^ +// { +// __strong TGMediaAvatarMenuMixin *strongSelf = weakSelf; +// if (strongSelf == nil) +// return; +// +// __strong TGMenuSheetController *strongController = weakController; +// if (strongController == nil) +// return; +// +// [strongController dismissAnimated:true]; +// if (strongSelf != nil && strongSelf.requestAvatarEditor) { +// strongSelf.requestAvatarEditor(^(UIImage *image, void (^commit)(void)) { +// __strong TGMediaAvatarMenuMixin *strongSelf = weakSelf; +// if (strongSelf == nil) +// return; +// +// if (strongSelf.willFinishWithImage != nil) { +// strongSelf.willFinishWithImage(image, ^{ +// if (strongSelf.didFinishWithImage != nil) +// strongSelf.didFinishWithImage(image); +// +// commit(); +// }); +// } else { +// if (strongSelf.didFinishWithImage != nil) +// strongSelf.didFinishWithImage(image); +// +// commit(); +// } +// }, ^(UIImage *image, NSURL *asset, id adjustments, id markup, void (^commit)(void)) { +// __strong TGMediaAvatarMenuMixin *strongSelf = weakSelf; +// if (strongSelf == nil) +// return; +// +// if (strongSelf.willFinishWithVideo != nil) { +// strongSelf.willFinishWithVideo(image, ^{ +// if (strongSelf.didFinishWithVideo != nil) +// strongSelf.didFinishWithVideo(image, asset, adjustments); +// +// commit(); +// }); +// } else { +// if (strongSelf.didFinishWithVideo != nil) +// strongSelf.didFinishWithVideo(image, asset, adjustments); +// +// commit(); +// } +// }); +// } +// }]; +// [itemViews addObject:viewItem]; +// } // if (_hasSearchButton) // { diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index e9d86327dd..e4becd9189 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -1546,7 +1546,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att let itemSpacing: CGFloat = 1.0 let itemWidth = floorToScreenPixels((width - itemSpacing * CGFloat(itemsPerRow - 1)) / CGFloat(itemsPerRow)) - var cutoutRect: CGRect? + var cutoutRects: [CGRect] = [] var cameraRect: CGRect? = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: 0.0), size: CGSize(width: itemWidth, height: itemWidth * 2.0 + 1.0)) if self.cameraView == nil && self.modernCameraView == nil { cameraRect = nil @@ -1647,9 +1647,11 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: bounds.height))) - cutoutRect = cameraRect + if let cameraRect { + cutoutRects.append(cameraRect) + } if let _ = self.avatarEditorPreviewView { - cutoutRect = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: 0.0), size: CGSize(width: cameraRect != nil ? itemWidth * 2.0 : itemWidth, height: itemWidth)) + cutoutRects.append(CGRect(x: cameraRect != nil ? cameraRect!.maxX + itemSpacing : layout.safeInsets.left, y: 0.0, width: itemWidth, height: itemWidth)) } var itemHeight = itemWidth @@ -1657,7 +1659,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att itemHeight = floor(itemWidth * 1.227) } let preloadSize: CGFloat = itemHeight// * 3.0 - self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: bounds.size, insets: gridInsets, scrollIndicatorInsets: nil, preloadSize: preloadSize, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemHeight), fillWidth: true, lineSpacing: itemSpacing, itemSpacing: itemSpacing), cutout: cutoutRect), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil, updateOpaqueState: nil, synchronousLoads: false), completion: { [weak self] _ in + self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: bounds.size, insets: gridInsets, scrollIndicatorInsets: nil, preloadSize: preloadSize, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemHeight), fillWidth: true, lineSpacing: itemSpacing, itemSpacing: itemSpacing), cutouts: cutoutRects), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil, updateOpaqueState: nil, synchronousLoads: false), completion: { [weak self] _ in guard let strongSelf = self else { return } @@ -1680,9 +1682,6 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att if let avatarEditorPreviewView = self.avatarEditorPreviewView { avatarEditorPreviewView.frame = CGRect(origin: CGPoint(x: cameraRect != nil ? cameraRect!.maxX + itemSpacing : layout.safeInsets.left, y: 0.0), size: CGSize(width: itemWidth, height: itemWidth)) avatarEditorPreviewView.updateLayout(size: CGSize(width: itemWidth, height: itemWidth)) - if self.gridNode.view.subviews.last !== avatarEditorPreviewView { - self.gridNode.view.bringSubviewToFront(avatarEditorPreviewView) - } } if let selectionNode = self.selectionNode, let controller = self.controller { diff --git a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift index 83225a8c54..9cf2bfb8b4 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift @@ -419,8 +419,6 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr private let centralItemAttributesDisposable = DisposableSet(); public var openAvatarSetup: ((@escaping () -> Void) -> Void)? - public var avatarPhotoEditCompletion: ((UIImage) -> Void)? - public var avatarVideoEditCompletion: ((UIImage, URL, TGVideoEditAdjustments?) -> Void)? public var removedEntry: ((AvatarGalleryEntry) -> Void)? diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift index 2a44c6a012..8fb4497a39 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift @@ -815,8 +815,13 @@ func _internal_upgradeStarGift(account: Account, formId: Int64?, reference: Star case let .updateNewMessage(message, _, _): if let message = StoreMessage(apiMessage: message, accountPeerId: account.peerId, peerIsForum: false) { for media in message.media { - if let action = media as? TelegramMediaAction, case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, _, _, _) = action.action, case let .Id(messageId) = message.id { - let _ = messageId + if let action = media as? TelegramMediaAction, case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, peerId, _, savedId) = action.action, case let .Id(messageId) = message.id { + let reference: StarGiftReference + if let peerId, let savedId { + reference = .peer(peerId: peerId, id: savedId) + } else { + reference = .message(messageId: messageId) + } return .single(ProfileGiftsContext.State.StarGift( gift: gift, reference: reference, @@ -1098,6 +1103,14 @@ private final class ProfileGiftsContextImpl { } } + func toggleStarGiftsNotifications(enabled: Bool) { + self.actionDisposable.set( + _internal_toggleStarGiftsNotifications(account: self.account, peerId: self.peerId, enabled: enabled).startStrict() + ) + self.notificationsEnabled = enabled + self.pushState() + } + private func pushState() { self._state = ProfileGiftsContext.State(gifts: self.gifts, count: self.count, dataState: self.dataState, notificationsEnabled: self.notificationsEnabled) self.stateValue.set(.single(ProfileGiftsContext.State(gifts: self.gifts, count: self.count, dataState: self.dataState, notificationsEnabled: self.notificationsEnabled))) @@ -1313,7 +1326,7 @@ public final class ProfileGiftsContext { impl.transferStarGift(prepaid: prepaid, reference: reference, peerId: peerId) } } - + public func upgradeStarGift(formId: Int64?, reference: StarGiftReference, keepOriginalInfo: Bool) -> Signal { return Signal { subscriber in let disposable = MetaDisposable() @@ -1330,6 +1343,12 @@ public final class ProfileGiftsContext { } } + public func toggleStarGiftsNotifications(enabled: Bool) { + self.impl.with { impl in + impl.toggleStarGiftsNotifications(enabled: enabled) + } + } + public var currentState: ProfileGiftsContext.State? { var state: ProfileGiftsContext.State? self.impl.syncWith { impl in diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift index 8f8d2755f2..d061f03e05 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift @@ -1464,10 +1464,16 @@ func _internal_sendStarsPaymentForm(account: Account, formId: Int64, source: Bot case .giftCode, .stars, .starsGift, .starsChatSubscription, .starGift, .starGiftUpgrade, .starGiftTransfer: receiptMessageId = nil } - } else if case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, _, _, _) = action.action, case let .Id(messageId) = message.id { + } else if case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, peerId, _, savedId) = action.action, case let .Id(messageId) = message.id { + let reference: StarGiftReference + if let peerId, let savedId { + reference = .peer(peerId: peerId, id: savedId) + } else { + reference = .message(messageId: messageId) + } resultGift = ProfileGiftsContext.State.StarGift( gift: gift, - reference: .message(messageId: messageId), + reference: reference, fromPeer: nil, date: message.timestamp, text: nil, diff --git a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift index 6035d1ca59..6790c0b448 100644 --- a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift +++ b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift @@ -1374,31 +1374,50 @@ final class AvatarEditorScreenComponent: Component { } } - let colors: [NSNumber] = state.selectedBackground.colors.map { Int32(bitPattern: $0) as NSNumber } + let backgroundColors = state.selectedBackground.colors.map { Int32(bitPattern: $0) } + guard let codableEntity = CodableDrawingEntity(entity: entity) else { + return + } - let entitiesData = DrawingEntitiesView.encodeEntitiesData([entity]) - - let paintingData = TGPaintingData( - drawing: nil, - entitiesData: entitiesData, - image: nil, - stillImage: nil, - hasAnimation: entity.isAnimated, - stickers: [] - ) - - let adjustments = PGPhotoEditorValues( - originalSize: size, + let values = MediaEditorValues( + peerId: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(0)), + originalDimensions: PixelDimensions(size), + cropOffset: .zero, cropRect: CGRect(origin: .zero, size: size), + cropScale: 1.0, cropRotation: 0.0, - cropOrientation: .up, - cropLockedAspectRatio: 1.0, - cropMirrored: false, + cropMirroring: false, + cropOrientation: nil, + gradientColors: nil, + videoTrimRange: nil, + videoIsMuted: false, + videoIsFullHd: false, + videoIsMirrored: false, + videoVolume: nil, + additionalVideoPath: nil, + additionalVideoIsDual: false, + additionalVideoPosition: nil, + additionalVideoScale: nil, + additionalVideoRotation: nil, + additionalVideoPositionChanges: [], + additionalVideoTrimRange: nil, + additionalVideoOffset: nil, + additionalVideoVolume: nil, + collage: [], + nightTheme: false, + drawing: nil, + maskDrawing: nil, + entities: [codableEntity], toolValues: [:], - paintingData: paintingData, - sendAsGif: true + audioTrack: nil, + audioTrackTrimRange: nil, + audioTrackOffset: nil, + audioTrackVolume: nil, + audioTrackSamples: nil, + collageTrackSamples: nil, + coverImageTimestamp: nil, + qualityPreset: .profileHigh ) - let preset: TGMediaVideoConversionPreset = TGMediaVideoConversionPresetProfileHigh let combinedImage = generateImage(size, contextGenerator: { size, context in let bounds = CGRect(origin: .zero, size: size) @@ -1415,12 +1434,19 @@ final class AvatarEditorScreenComponent: Component { }, opaque: false)! if entity.isAnimated { + let markup: UploadPeerPhotoMarkup if stickerPackId != 0 { - controller.videoCompletion(combinedImage, tempUrl, TGVideoEditAdjustments(photoEditorValues: adjustments, preset: preset, stickerPackId: stickerPackId, stickerPackAccessHash: stickerPackAccessHash, documentId: fileId, colors: colors), { [weak controller] in + markup = .sticker(packReference: .id(id: stickerPackId, accessHash: stickerPackAccessHash), fileId: fileId, backgroundColors: backgroundColors) + } else { + markup = .emoji(fileId: fileId, backgroundColors: backgroundColors) + } + + if stickerPackId != 0 { + controller.videoCompletion(combinedImage, tempUrl, values, markup, { [weak controller] in controller?.dismiss() }) } else { - controller.videoCompletion(combinedImage, tempUrl, TGVideoEditAdjustments(photoEditorValues: adjustments, preset: preset, documentId: fileId, colors: colors), { [weak controller] in + controller.videoCompletion(combinedImage, tempUrl, values, markup, { [weak controller] in controller?.dismiss() }) } @@ -1461,7 +1487,7 @@ public final class AvatarEditorScreen: ViewControllerComponentContainer { } public var imageCompletion: (UIImage, @escaping () -> Void) -> Void = { _, _ in } - public var videoCompletion: (UIImage, URL, TGVideoEditAdjustments, @escaping () -> Void) -> Void = { _, _, _, _ in } + public var videoCompletion: (UIImage, URL, MediaEditorValues, UploadPeerPhotoMarkup, @escaping () -> Void) -> Void = { _, _, _, _, _ in } public static func inputData(context: AccountContext, isGroup: Bool) -> Signal { let emojiItems = EmojiPagerContentComponent.emojiInputData( diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 16a1956972..b89ca0b540 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -3959,6 +3959,9 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID return } if gestureRecognizer.numberOfTouches == 2, let subject = self.subject, !self.entitiesView.hasSelection { + if case .avatarEditor = self.controller?.mode { + return + } switch subject { case .message, .gift: return @@ -3979,6 +3982,9 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID return } if gestureRecognizer.numberOfTouches == 2, let subject = self.subject, !self.entitiesView.hasSelection { + if case .avatarEditor = self.controller?.mode { + return + } switch subject { case .message, .gift: return @@ -3999,6 +4005,9 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID return } if gestureRecognizer.numberOfTouches == 2, let subject = self.subject, !self.entitiesView.hasSelection { + if case .avatarEditor = self.controller?.mode { + return + } switch subject { case .message, .gift: return @@ -4168,19 +4177,20 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID let sourceAspectRatio = sourceLocalFrame.height / sourceLocalFrame.width let duration: Double = 0.4 + let timingFunction = kCAMediaTimingFunctionSpring - self.previewContainerView.layer.animatePosition(from: sourceLocalFrame.center, to: self.previewContainerView.center, duration: duration, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in + self.previewContainerView.layer.animatePosition(from: sourceLocalFrame.center, to: self.previewContainerView.center, duration: duration, timingFunction: timingFunction, completion: { _ in completion() }) self.previewContainerView.layer.animateScale(from: sourceScale, to: 1.0, duration: duration, timingFunction: kCAMediaTimingFunctionSpring) - self.previewContainerView.layer.animateBounds(from: CGRect(origin: CGPoint(x: 0.0, y: (self.previewContainerView.bounds.height - self.previewContainerView.bounds.width * sourceAspectRatio) / 2.0), size: CGSize(width: self.previewContainerView.bounds.width, height: self.previewContainerView.bounds.width * sourceAspectRatio)), to: self.previewContainerView.bounds, duration: duration, timingFunction: kCAMediaTimingFunctionSpring) + self.previewContainerView.layer.animateBounds(from: CGRect(origin: CGPoint(x: 0.0, y: (self.previewContainerView.bounds.height - self.previewContainerView.bounds.width * sourceAspectRatio) / 2.0), size: CGSize(width: self.previewContainerView.bounds.width, height: self.previewContainerView.bounds.width * sourceAspectRatio)), to: self.previewContainerView.bounds, duration: duration, timingFunction: timingFunction) self.backgroundDimView.isHidden = false - self.backgroundDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + self.backgroundDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.35) if let componentView = self.componentHost.view { componentView.layer.animatePosition(from: sourceLocalFrame.center, to: componentView.center, duration: duration, timingFunction: kCAMediaTimingFunctionSpring) - componentView.layer.animateScale(from: sourceScale, to: 1.0, duration: duration, timingFunction: kCAMediaTimingFunctionSpring) + componentView.layer.animateScale(from: sourceScale, to: 1.0, duration: duration, timingFunction: timingFunction) componentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) } } @@ -5231,7 +5241,9 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID let result = super.hitTest(point, with: event) if result == self.componentHost.view { let point = self.view.convert(point, to: self.previewContainerView) - return self.previewContainerView.hitTest(point, with: event) + if let previewResult = self.previewContainerView.hitTest(point, with: event) { + return previewResult + } } return result } @@ -6189,7 +6201,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID fileprivate let transitionOut: (Bool, Bool?) -> TransitionOut? public var cancelled: (Bool) -> Void = { _ in } - public var completion: (MediaEditorScreenImpl.Result, @escaping (@escaping () -> Void) -> Void) -> Void = { _, _ in } + public var willComplete: (UIImage?, Bool, @escaping () -> Void) -> Void + public var completion: (MediaEditorScreenImpl.Result, @escaping (@escaping () -> Void) -> Void) -> Void public var dismissed: () -> Void = { } public var willDismiss: () -> Void = { } public var sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? @@ -6222,6 +6235,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID initialLink: (url: String, name: String?)? = nil, transitionIn: TransitionIn?, transitionOut: @escaping (Bool, Bool?) -> TransitionOut?, + willComplete: @escaping (UIImage?, Bool, @escaping () -> Void) -> Void = { _, _, commit in commit() }, completion: @escaping (MediaEditorScreenImpl.Result, @escaping (@escaping () -> Void) -> Void) -> Void ) { self.context = context @@ -6238,6 +6252,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID self.initialLink = initialLink self.transitionIn = transitionIn self.transitionOut = transitionOut + self.willComplete = willComplete self.completion = completion self.storiesBlockedPeers = BlockedPeersContext(account: context.account, subject: .stories) @@ -7401,13 +7416,18 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID makeEditorImageComposition(context: self.node.ciContext, postbox: self.context.account.postbox, inputImage: inputImage, dimensions: storyDimensions, values: mediaEditor.values, time: firstFrameTime, textScale: 2.0, completion: { [weak self] coverImage in if let self { - Logger.shared.log("MediaEditor", "Completed with video \(videoResult)") - self.completion(MediaEditorScreenImpl.Result(media: .video(video: videoResult, coverImage: coverImage, values: mediaEditor.values, duration: duration, dimensions: mediaEditor.values.resultDimensions), mediaAreas: mediaAreas, caption: caption, coverTimestamp: mediaEditor.values.coverImageTimestamp, options: self.state.privacy, stickers: stickers, randomId: randomId), { [weak self] finished in - self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in - self?.dismiss() - Queue.mainQueue().justDispatch { - finished() - } + self.willComplete(coverImage, true, { [weak self] in + guard let self else { + return + } + Logger.shared.log("MediaEditor", "Completed with video \(videoResult)") + self.completion(MediaEditorScreenImpl.Result(media: .video(video: videoResult, coverImage: coverImage, values: mediaEditor.values, duration: duration, dimensions: mediaEditor.values.resultDimensions), mediaAreas: mediaAreas, caption: caption, coverTimestamp: mediaEditor.values.coverImageTimestamp, options: self.state.privacy, stickers: stickers, randomId: randomId), { [weak self] finished in + self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in + self?.dismiss() + Queue.mainQueue().justDispatch { + finished() + } + }) }) }) } @@ -7430,18 +7450,23 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID } makeEditorImageComposition(context: self.node.ciContext, postbox: self.context.account.postbox, inputImage: image, dimensions: storyDimensions, outputDimensions: outputDimensions, values: values, time: .zero, textScale: 2.0, completion: { [weak self] resultImage in if let self, let resultImage { - Logger.shared.log("MediaEditor", "Completed with image \(resultImage)") - self.completion(MediaEditorScreenImpl.Result(media: .image(image: resultImage, dimensions: PixelDimensions(resultImage.size)), mediaAreas: mediaAreas, caption: caption, coverTimestamp: nil, options: self.state.privacy, stickers: stickers, randomId: randomId), { [weak self] finished in - self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in - self?.dismiss() - Queue.mainQueue().justDispatch { - finished() - } + self.willComplete(resultImage, false, { [weak self] in + guard let self else { + return + } + Logger.shared.log("MediaEditor", "Completed with image \(resultImage)") + self.completion(MediaEditorScreenImpl.Result(media: .image(image: resultImage, dimensions: PixelDimensions(resultImage.size)), mediaAreas: mediaAreas, caption: caption, coverTimestamp: nil, options: self.state.privacy, stickers: stickers, randomId: randomId), { [weak self] finished in + self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in + self?.dismiss() + Queue.mainQueue().justDispatch { + finished() + } + }) }) + if case let .draft(draft, id) = actualSubject, id == nil { + removeStoryDraft(engine: self.context.engine, path: draft.path, delete: true) + } }) - if case let .draft(draft, id) = actualSubject, id == nil { - removeStoryDraft(engine: self.context.engine, path: draft.path, delete: true) - } } }) } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift index 1e6afc1246..c16d3c4379 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift @@ -1579,9 +1579,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen hasSavedMessageTags, isPremiumRequiredForStoryPosting, starsRevenueContextAndState, - revenueContextAndState + revenueContextAndState, + profileGiftsContext.state ) - |> map { peerView, availablePanes, globalNotificationSettings, status, currentInvitationsContext, invitations, currentRequestsContext, requests, hasStories, accountIsPremium, recommendedChannels, hasSavedMessages, hasSavedMessagesChats, hasSavedMessageTags, isPremiumRequiredForStoryPosting, starsRevenueContextAndState, revenueContextAndState -> PeerInfoScreenData in + |> map { peerView, availablePanes, globalNotificationSettings, status, currentInvitationsContext, invitations, currentRequestsContext, requests, hasStories, accountIsPremium, recommendedChannels, hasSavedMessages, hasSavedMessagesChats, hasSavedMessageTags, isPremiumRequiredForStoryPosting, starsRevenueContextAndState, revenueContextAndState, profileGiftsState -> PeerInfoScreenData in var availablePanes = availablePanes if let hasStories { if hasStories { @@ -1605,7 +1606,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen } if availablePanes != nil, let cachedData = peerView.cachedData as? CachedChannelData { - if let starGiftsCount = cachedData.starGiftsCount, starGiftsCount > 0 { + if (cachedData.starGiftsCount ?? 0) > 0 || (profileGiftsState.count ?? 0) > 0 { availablePanes?.insert(.gifts, at: hasStories ? 1 : 0) } } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index d3fdca07f7..f66995b36d 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -9610,6 +9610,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro currentIsVideo = !videoRepresentations.isEmpty emojiMarkup = emojiMarkupValue } + let _ = emojiMarkup let peerId = self.peerId let _ = (self.context.engine.data.get( @@ -9685,31 +9686,31 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasDeleteButton, hasViewButton: false, personalPhoto: strongSelf.isSettings || strongSelf.isMyProfile, isVideo: currentIsVideo, saveEditedPhotos: false, saveCapturedMedia: false, signup: false, forum: isForum, title: title, isSuggesting: [.custom, .suggest].contains(mode))! mixin.stickersContext = LegacyPaintStickersContext(context: strongSelf.context) let _ = strongSelf.currentAvatarMixin.swap(mixin) - var isFromEditor = false - mixin.requestAvatarEditor = { [weak self, weak parentController] imageCompletion, videoCompletion in - guard let strongSelf = self, let imageCompletion, let videoCompletion else { - return - } - let peerType: AvatarEditorScreen.PeerType - if mode == .suggest { - peerType = .suggest - } else if case .legacyGroup = peer { - peerType = .group - } else if case let .channel(channel) = peer { - if case .group = channel.info { - peerType = channel.flags.contains(.isForum) ? .forum : .group - } else { - peerType = .channel - } - } else { - peerType = .user - } - let controller = AvatarEditorScreen(context: strongSelf.context, inputData: keyboardInputData.get(), peerType: peerType, markup: emojiMarkup) - controller.imageCompletion = imageCompletion - controller.videoCompletion = videoCompletion - parentController?.push(controller) - isFromEditor = true - } + let isFromEditor = !"".isEmpty +// mixin.requestAvatarEditor = { [weak self, weak parentController] imageCompletion, videoCompletion in +// guard let strongSelf = self, let imageCompletion, let videoCompletion else { +// return +// } +// let peerType: AvatarEditorScreen.PeerType +// if mode == .suggest { +// peerType = .suggest +// } else if case .legacyGroup = peer { +// peerType = .group +// } else if case let .channel(channel) = peer { +// if case .group = channel.info { +// peerType = channel.flags.contains(.isForum) ? .forum : .group +// } else { +// peerType = .channel +// } +// } else { +// peerType = .user +// } +// let controller = AvatarEditorScreen(context: strongSelf.context, inputData: keyboardInputData.get(), peerType: peerType, markup: emojiMarkup) +// controller.imageCompletion = imageCompletion +// controller.videoCompletion = videoCompletion +// parentController?.push(controller) +// isFromEditor = true +// } if let confirmationTextPhoto, let confirmationAction { mixin.willFinishWithImage = { [weak self, weak parentController] image, commit in @@ -9737,12 +9738,12 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro self?.controller?.updateProfilePhoto(image, mode: mode, uploadStatus: nil) } } - mixin.didFinishWithVideo = { [weak self] image, asset, adjustments in - if let image = image, let asset = asset { - completion(image) - self?.controller?.updateProfileVideo(image, asset: asset, adjustments: adjustments, mode: mode) - } - } +// mixin.didFinishWithVideo = { [weak self] image, asset, adjustments, _ in +// if let image = image, let asset = asset { +// completion(image) +// self?.controller?.updateProfileVideo(image, asset: asset, adjustments: adjustments, mode: mode) +// } +// } mixin.didFinishWithDelete = { guard let strongSelf = self else { return @@ -12238,12 +12239,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro completion() }) } - galleryController.avatarPhotoEditCompletion = { [weak self] image in - self?.controller?.updateProfilePhoto(image, mode: .generic, uploadStatus: nil) - } - galleryController.avatarVideoEditCompletion = { [weak self] image, asset, adjustments in - self?.controller?.updateProfileVideo(image, asset: asset, adjustments: adjustments, mode: .generic) - } galleryController.removedEntry = { [weak self] entry in if let item = PeerInfoAvatarListItem(entry: entry) { let _ = self?.headerNode.avatarListNode.listContainerNode.deleteItem(item) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift index b4fe717e92..6de7f0a6d8 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift @@ -69,7 +69,32 @@ extension PeerInfoScreenImpl { } } - let _ = hasDeleteButton + struct ConfirmationAlert { + let title: String + let photoText: String + let videoText: String + let action: String + } + + let confirmationAlert: ConfirmationAlert? + switch mode { + case .suggest: + confirmationAlert = ConfirmationAlert( + title: self.presentationData.strings.UserInfo_SuggestPhotoTitle(peer.compactDisplayTitle).string, + photoText: self.presentationData.strings.UserInfo_SuggestPhoto_AlertPhotoText(peer.compactDisplayTitle).string, + videoText: self.presentationData.strings.UserInfo_SuggestPhoto_AlertVideoText(peer.compactDisplayTitle).string, + action: self.presentationData.strings.UserInfo_SuggestPhoto_AlertSuggest + ) + case .custom: + confirmationAlert = ConfirmationAlert( + title: self.presentationData.strings.UserInfo_SetCustomPhotoTitle(peer.compactDisplayTitle).string, + photoText: self.presentationData.strings.UserInfo_SetCustomPhoto_AlertPhotoText(peer.compactDisplayTitle, peer.compactDisplayTitle).string, + videoText: self.presentationData.strings.UserInfo_SetCustomPhoto_AlertVideoText(peer.compactDisplayTitle, peer.compactDisplayTitle).string, + action: self.presentationData.strings.UserInfo_SetCustomPhoto_AlertSet + ) + default: + confirmationAlert = nil + } let parentController = (self.context.sharedContext.mainWindow?.viewController as? NavigationController)?.topViewController as? ViewController @@ -77,6 +102,9 @@ extension PeerInfoScreenImpl { let mainController = self.context.sharedContext.makeAvatarMediaPickerScreen(context: self.context, getSourceRect: { return nil }, canDelete: hasDeleteButton, performDelete: { [weak self] in self?.openAvatarRemoval(mode: mode, peer: peer, item: item) }, completion: { result, transitionView, transitionRect, transitionImage, fromCamera, transitionOut, cancelled in + var resultImage: UIImage? + let uploadStatusPromise = Promise(.progress(0.0)) + let subject: Signal if let asset = result as? PHAsset { subject = .single(.asset(asset)) @@ -112,15 +140,21 @@ extension PeerInfoScreenImpl { peerType = .user } let controller = AvatarEditorScreen(context: self.context, inputData: keyboardInputData.get(), peerType: peerType, markup: emojiMarkup) - //controller.imageCompletion = imageCompletion - //controller.videoCompletion = videoCompletion + controller.imageCompletion = { [weak self] image, commit in + resultImage = image + self?.updateProfilePhoto(image, mode: mode, uploadStatus: uploadStatusPromise) + commit() + } + controller.videoCompletion = { [weak self] image, url, values, markup, commit in + resultImage = image + self?.updateProfileVideo(image, asset: url, values: values, markup: markup, mode: mode, uploadStatus: uploadStatusPromise) + commit() + } parentController?.push(controller) //isFromEditor = true return } - var resultImage: UIImage? - let uploadStatusPromise = Promise(.progress(0.0)) let editorController = MediaEditorScreenImpl( context: self.context, mode: .avatarEditor, @@ -153,7 +187,18 @@ extension PeerInfoScreenImpl { ) } return nil - }, completion: { [weak self] result, commit in + }, + willComplete: { [weak self, weak parentController] image, isVideo, commit in + if let self, let confirmationAlert, let image { + let controller = photoUpdateConfirmationController(context: self.context, peer: peer, image: image, text: isVideo ? confirmationAlert.videoText : confirmationAlert.photoText, doneTitle: confirmationAlert.action, commit: { + commit() + }) + parentController?.presentInGlobalOverlay(controller) + } else { + commit() + } + }, + completion: { [weak self] result, commit in switch result.media { case let .image(image, _): resultImage = image @@ -161,10 +206,8 @@ extension PeerInfoScreenImpl { commit({}) case let .video(video, coverImage, values, _, _): if let coverImage { - let _ = values - //TODO:release resultImage = coverImage - self?.updateProfileVideo(coverImage, asset: video, adjustments: nil, mode: mode) + self?.updateProfileVideo(coverImage, asset: video, values: values, markup: nil, mode: mode, uploadStatus: uploadStatusPromise) } commit({}) default: @@ -415,228 +458,136 @@ extension PeerInfoScreenImpl { } })) } + + public func updateProfileVideo(_ image: UIImage, asset: Any?, values: MediaEditorValues?, markup: UploadPeerPhotoMarkup?, mode: PeerInfoAvatarEditingMode, uploadStatus: Promise?) { + var uploadVideo = true + if let _ = markup { + if let data = self.context.currentAppConfiguration.with({ $0 }).data, let uploadVideoValue = data["upload_markup_video"] as? Bool, uploadVideoValue { + uploadVideo = true + } else { + uploadVideo = false + } + } + guard let photoResource = self.setupProfilePhotoUpload(image: image, mode: mode, indefiniteProgress: !uploadVideo) else { + uploadStatus?.set(.single(.done)) + return + } + + var videoStartTimestamp: Double? = nil + if let values, let coverImageTimestamp = values.coverImageTimestamp, coverImageTimestamp > 0.0 { + videoStartTimestamp = coverImageTimestamp - (values.videoTrimRange?.lowerBound ?? 0.0) + } -// public func updateProfileVideo(_ image: UIImage, video: MediaEditorScreenImpl.MediaResult.VideoResult, values: MediaEditorValues, mode: PeerInfoAvatarEditingMode) { -// var markup: UploadPeerPhotoMarkup? = nil -// if let fileId = adjustments?.documentId, let backgroundColors = adjustments?.colors as? [Int32], fileId != 0 { -// if let packId = adjustments?.stickerPackId, let accessHash = adjustments?.stickerPackAccessHash, packId != 0 { -// markup = .sticker(packReference: .id(id: packId, accessHash: accessHash), fileId: fileId, backgroundColors: backgroundColors) -// } else { -// markup = .emoji(fileId: fileId, backgroundColors: backgroundColors) -// } -// } -// -// var videoStartTimestamp: Double? = nil -// if let adjustments = adjustments, adjustments.videoStartValue > 0.0 { -// videoStartTimestamp = adjustments.videoStartValue - adjustments.trimStartValue -// } -// -// var uploadVideo = true -// if let _ = markup { -// if let data = self.context.currentAppConfiguration.with({ $0 }).data, let uploadVideoValue = data["upload_markup_video"] as? Bool, uploadVideoValue { -// uploadVideo = true -// } else { -// uploadVideo = false -// } -// } -// -// guard let photoResource = self.setupProfilePhotoUpload(image: image, mode: mode, indefiniteProgress: !uploadVideo) else { -// return -// } -// -// let context = self.context -// -// let videoResource: Signal -// if uploadVideo { -// let path = NSTemporaryDirectory() + "\(Int64.random(in: Int64.min ... Int64.max)).mp4" -// let videoExport = MediaEditorVideoExport( -// postbox: context.account.postbox, -// subject: .image(image: image), -// configuration: configuration, -// outputPath: path -// ) -// -// videoResource = Signal { [weak self] subscriber in -// let entityRenderer: LegacyPaintEntityRenderer? = adjustments.flatMap { adjustments in -// if let paintingData = adjustments.paintingData, paintingData.hasAnimation { -// return LegacyPaintEntityRenderer(postbox: account.postbox, adjustments: adjustments) -// } else { -// return nil -// } -// } -// -// let tempFile = EngineTempBox.shared.tempFile(fileName: "video.mp4") -// let uploadInterface = LegacyLiveUploadInterface(context: context) -// let signal: SSignal -// if let url = asset as? URL, url.absoluteString.hasSuffix(".jpg"), let data = try? Data(contentsOf: url, options: [.mappedRead]), let image = UIImage(data: data), let entityRenderer = entityRenderer { -// let durationSignal: SSignal = SSignal(generator: { subscriber in -// let disposable = (entityRenderer.duration()).start(next: { duration in -// subscriber.putNext(duration) -// subscriber.putCompletion() -// }) -// -// return SBlockDisposable(block: { -// disposable.dispose() -// }) -// }) -// signal = durationSignal.map(toSignal: { duration -> SSignal in -// if let duration = duration as? Double { -// return TGMediaVideoConverter.renderUIImage(image, duration: duration, adjustments: adjustments, path: tempFile.path, watcher: nil, entityRenderer: entityRenderer)! -// } else { -// return SSignal.single(nil) -// } -// }) -// } else if let asset = asset as? AVAsset { -// signal = TGMediaVideoConverter.convert(asset, adjustments: adjustments, path: tempFile.path, watcher: uploadInterface, entityRenderer: entityRenderer)! -// } else { -// signal = SSignal.complete() -// } -// -// let signalDisposable = signal.start(next: { next in -// if let result = next as? TGMediaVideoConversionResult { -// if let image = result.coverImage, let data = image.jpegData(compressionQuality: 0.7) { -// account.postbox.mediaBox.storeResourceData(photoResource.id, data: data) -// } -// -// if let timestamp = videoStartTimestamp { -// videoStartTimestamp = max(0.0, min(timestamp, result.duration - 0.05)) -// } -// -// var value = stat() -// if stat(result.fileURL.path, &value) == 0 { -// if let data = try? Data(contentsOf: result.fileURL) { -// let resource: TelegramMediaResource -// if let liveUploadData = result.liveUploadData as? LegacyLiveUploadInterfaceResult { -// resource = LocalFileMediaResource(fileId: liveUploadData.id) -// } else { -// resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) -// } -// account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) -// subscriber.putNext(resource) -// -// EngineTempBox.shared.dispose(tempFile) -// } -// } -// subscriber.putCompletion() -// } else if let strongSelf = self, let progress = next as? NSNumber { -// Queue.mainQueue().async { -// strongSelf.controllerNode.state = strongSelf.controllerNode.state.withAvatarUploadProgress(.value(CGFloat(progress.floatValue * 0.45))) -// if let (layout, navigationHeight) = strongSelf.controllerNode.validLayout { -// strongSelf.controllerNode.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) -// } -// } -// } -// }, error: { _ in -// }, completed: nil) -// -// let disposable = ActionDisposable { -// signalDisposable?.dispose() -// } -// -// return ActionDisposable { -// disposable.dispose() -// } -// } -// } else { -// videoResource = .single(nil) -// } -// -// var dismissStatus: (() -> Void)? -// if [.suggest, .fallback, .accept].contains(mode) { -// let statusController = OverlayStatusController(theme: self.presentationData.theme, type: .loading(cancelled: { [weak self] in -// self?.controllerNode.updateAvatarDisposable.set(nil) -// dismissStatus?() -// })) -// dismissStatus = { [weak statusController] in -// statusController?.dismiss() -// } -// if let topController = self.navigationController?.topViewController as? ViewController { -// topController.presentInGlobalOverlay(statusController) -// } else if let topController = self.parentController?.topViewController as? ViewController { -// topController.presentInGlobalOverlay(statusController) -// } else { -// self.presentInGlobalOverlay(statusController) -// } -// } -// -// let peerId = self.peerId -// let isSettings = self.isSettings -// let isMyProfile = self.isMyProfile -// self.controllerNode.updateAvatarDisposable.set((videoResource -// |> mapToSignal { videoResource -> Signal in -// if isSettings || isMyProfile { -// if case .fallback = mode { -// return context.engine.accountData.updateFallbackPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in -// return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) -// }) -// } else { -// return context.engine.accountData.updateAccountPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in -// return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) -// }) -// } -// } else if case .custom = mode { -// return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mode: .custom, mapResourceToAvatarSizes: { resource, representations in -// return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) -// }) -// } else if case .suggest = mode { -// return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mode: .suggest, mapResourceToAvatarSizes: { resource, representations in -// return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) -// }) -// } else { -// return context.engine.peers.updatePeerPhoto(peerId: peerId, photo: context.engine.peers.uploadedPeerPhoto(resource: photoResource), video: videoResource.flatMap { context.engine.peers.uploadedPeerVideo(resource: $0) |> map(Optional.init) }, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in -// return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) -// }) -// } -// } -// |> deliverOnMainQueue).startStrict(next: { [weak self] result in -// guard let strongSelf = self else { -// return -// } -// switch result { -// case .complete: -// strongSelf.controllerNode.state = strongSelf.controllerNode.state.withUpdatingAvatar(nil).withAvatarUploadProgress(nil) -// case let .progress(value): -// strongSelf.controllerNode.state = strongSelf.controllerNode.state.withAvatarUploadProgress(.value(CGFloat(0.45 + value * 0.55))) -// } -// if let (layout, navigationHeight) = strongSelf.controllerNode.validLayout { -// strongSelf.controllerNode.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) -// } -// -// if case .complete = result { -// dismissStatus?() -// -// let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.peerId)) -// |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in -// if let strongSelf = self, let peer { -// switch mode { -// case .fallback: -// (strongSelf.parentController?.topViewController as? ViewController)?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .image(image: image, title: nil, text: strongSelf.presentationData.strings.Privacy_ProfilePhoto_PublicVideoSuccess, round: true, undoText: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) -// case .custom: -// strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, title: nil, text: strongSelf.presentationData.strings.UserInfo_SetCustomPhoto_SuccessVideoText(peer.compactDisplayTitle).string, action: nil, duration: 5), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) -// -// let _ = (strongSelf.context.peerChannelMemberCategoriesContextsManager.profilePhotos(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, peerId: strongSelf.peerId, fetch: peerInfoProfilePhotos(context: strongSelf.context, peerId: strongSelf.peerId)) |> ignoreValues).startStandalone() -// case .suggest: -// if let navigationController = (strongSelf.navigationController as? NavigationController) { -// strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), keepStack: .default, completion: { _ in -// })) -// } -// case .accept: -// (strongSelf.parentController?.topViewController as? ViewController)?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .image(image: image, title: strongSelf.presentationData.strings.Conversation_SuggestedVideoSuccess, text: strongSelf.presentationData.strings.Conversation_SuggestedVideoSuccessText, round: true, undoText: nil), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] action in -// if case .info = action { -// self?.parentController?.openSettings() -// } -// return false -// }), in: .current) -// default: -// break -// } -// } -// }) -// } -// })) -// } + let account = self.context.account + let context = self.context + + let videoResource: Signal + if uploadVideo { + videoResource = Signal { subscriber in + return EmptyDisposable + } + } else { + videoResource = .single(nil) + } + + var dismissStatus: (() -> Void)? + if [.suggest, .fallback, .accept].contains(mode) { + let statusController = OverlayStatusController(theme: self.presentationData.theme, type: .loading(cancelled: { [weak self] in + self?.controllerNode.updateAvatarDisposable.set(nil) + dismissStatus?() + })) + dismissStatus = { [weak statusController] in + statusController?.dismiss() + } + if let topController = self.navigationController?.topViewController as? ViewController { + topController.presentInGlobalOverlay(statusController) + } else if let topController = self.parentController?.topViewController as? ViewController { + topController.presentInGlobalOverlay(statusController) + } else { + self.presentInGlobalOverlay(statusController) + } + } + + let peerId = self.peerId + let isSettings = self.isSettings + let isMyProfile = self.isMyProfile + self.controllerNode.updateAvatarDisposable.set((videoResource + |> mapToSignal { videoResource -> Signal in + if isSettings || isMyProfile { + if case .fallback = mode { + return context.engine.accountData.updateFallbackPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) + }) + } else { + return context.engine.accountData.updateAccountPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) + }) + } + } else if case .custom = mode { + return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mode: .custom, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) + }) + } else if case .suggest = mode { + return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mode: .suggest, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) + }) + } else { + return context.engine.peers.updatePeerPhoto(peerId: peerId, photo: context.engine.peers.uploadedPeerPhoto(resource: photoResource), video: videoResource.flatMap { context.engine.peers.uploadedPeerVideo(resource: $0) |> map(Optional.init) }, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) + }) + } + } + |> deliverOnMainQueue).startStrict(next: { [weak self] result in + guard let strongSelf = self else { + return + } + switch result { + case .complete: + uploadStatus?.set(.single(.done)) + strongSelf.controllerNode.state = strongSelf.controllerNode.state.withUpdatingAvatar(nil).withAvatarUploadProgress(nil) + case let .progress(value): + uploadStatus?.set(.single(.progress(value))) + strongSelf.controllerNode.state = strongSelf.controllerNode.state.withAvatarUploadProgress(.value(CGFloat(0.45 + value * 0.55))) + } + if let (layout, navigationHeight) = strongSelf.controllerNode.validLayout { + strongSelf.controllerNode.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) + } + + if case .complete = result { + dismissStatus?() + + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.peerId)) + |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in + if let strongSelf = self, let peer { + switch mode { + case .fallback: + (strongSelf.parentController?.topViewController as? ViewController)?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .image(image: image, title: nil, text: strongSelf.presentationData.strings.Privacy_ProfilePhoto_PublicVideoSuccess, round: true, undoText: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + case .custom: + strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, title: nil, text: strongSelf.presentationData.strings.UserInfo_SetCustomPhoto_SuccessVideoText(peer.compactDisplayTitle).string, action: nil, duration: 5), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + + let _ = (strongSelf.context.peerChannelMemberCategoriesContextsManager.profilePhotos(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, peerId: strongSelf.peerId, fetch: peerInfoProfilePhotos(context: strongSelf.context, peerId: strongSelf.peerId)) |> ignoreValues).startStandalone() + case .suggest: + if let navigationController = (strongSelf.navigationController as? NavigationController) { + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), keepStack: .default, completion: { _ in + })) + } + case .accept: + (strongSelf.parentController?.topViewController as? ViewController)?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .image(image: image, title: strongSelf.presentationData.strings.Conversation_SuggestedVideoSuccess, text: strongSelf.presentationData.strings.Conversation_SuggestedVideoSuccessText, round: true, undoText: nil), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] action in + if case .info = action { + self?.parentController?.openSettings() + } + return false + }), in: .current) + default: + break + } + } + }) + } + })) + } - public func updateProfileVideo(_ image: UIImage, asset: Any?, adjustments: TGVideoEditAdjustments?, mode: PeerInfoAvatarEditingMode) { + public func oldUpdateProfileVideo(_ image: UIImage, asset: Any?, adjustments: TGVideoEditAdjustments?, mode: PeerInfoAvatarEditingMode) { var markup: UploadPeerPhotoMarkup? = nil if let fileId = adjustments?.documentId, let backgroundColors = adjustments?.colors as? [Int32], fileId != 0 { if let packId = adjustments?.stickerPackId, let accessHash = adjustments?.stickerPackAccessHash, packId != 0 { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift index 7112eca177..7875288158 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift @@ -411,7 +411,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(CheckComponent( theme: checkTheme, size: CGSize(width: 22.0, height: 22.0), - selected: self.notify + selected: self.profileGifts.currentState?.notificationsEnabled ?? false ))), AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString(string: presentationData.strings.PeerInfo_Gifts_ChannelNotify, font: Font.regular(17.0), textColor: presentationData.theme.list.itemPrimaryTextColor)) @@ -421,20 +421,23 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr )), effectAlignment: .center, action: { [weak self] in - guard let self else { + guard let self, let currentState = self.profileGifts.currentState else { return } - self.notify = !self.notify + let enabled = !(currentState.notificationsEnabled ?? false) + self.profileGifts.toggleStarGiftsNotifications(enabled: enabled) - if self.notify { - let controller = UndoOverlayController( - presentationData: presentationData, - content: .universal(animation: "anim_profileunmute", scale: 0.075, colors: ["__allcolors__": UIColor.white], title: nil, text: presentationData.strings.PeerInfo_Gifts_ChannelNotifyTooltip, customUndoText: nil, timeout: nil), - appearance: UndoOverlayController.Appearance(bottomInset: 53.0), - action: { _ in return true } - ) - self.chatControllerInteraction.presentController(controller, nil) - } + let animation = enabled ? "anim_profileunmute" : "anim_profilemute" + let text = enabled ? presentationData.strings.PeerInfo_Gifts_ChannelNotifyTooltip : presentationData.strings.PeerInfo_Gifts_ChannelNotifyDisabledTooltip + + let controller = UndoOverlayController( + presentationData: presentationData, + content: .universal(animation: animation, scale: 0.075, colors: ["__allcolors__": UIColor.white], title: nil, text: text, customUndoText: nil, timeout: nil), + appearance: UndoOverlayController.Appearance(bottomInset: 53.0), + action: { _ in return true } + ) + self.chatControllerInteraction.presentController(controller, nil) + self.updateScrolling(transition: .immediate) }, animateAlpha: false, diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 50b400e2fa..dcdcd5f011 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -1233,10 +1233,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } } - controller.videoCompletion = { [weak self] image, url, adjustments, commit in + controller.videoCompletion = { [weak self] image, url, values, markup, commit in if let strongSelf = self { if let rootController = strongSelf.effectiveNavigationController as? TelegramRootController, let settingsController = rootController.accountSettingsController as? PeerInfoScreenImpl { - settingsController.updateProfileVideo(image, asset: AVURLAsset(url: url), adjustments: adjustments, mode: .accept) + settingsController.updateProfileVideo(image, asset: AVURLAsset(url: url), values: values, markup: markup, mode: .accept, uploadStatus: nil) commit() } } @@ -1271,7 +1271,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }, videoCompletion: { [weak self] image, url, adjustments in if let strongSelf = self { if let rootController = strongSelf.effectiveNavigationController as? TelegramRootController, let settingsController = rootController.accountSettingsController as? PeerInfoScreenImpl { - settingsController.updateProfileVideo(image, asset: AVURLAsset(url: url), adjustments: adjustments, mode: .accept) + settingsController.oldUpdateProfileVideo(image, asset: AVURLAsset(url: url), adjustments: adjustments, mode: .accept) } } }) diff --git a/submodules/TelegramUI/Sources/CreateChannelController.swift b/submodules/TelegramUI/Sources/CreateChannelController.swift index d7cdbd02da..1d743308c0 100644 --- a/submodules/TelegramUI/Sources/CreateChannelController.swift +++ b/submodules/TelegramUI/Sources/CreateChannelController.swift @@ -642,15 +642,15 @@ public func createChannelController(context: AccountContext, mode: CreateChannel })) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } - mixin.requestAvatarEditor = { imageCompletion, videoCompletion in - guard let imageCompletion, let videoCompletion else { - return - } - let controller = AvatarEditorScreen(context: context, inputData: keyboardInputData.get(), peerType: .channel, markup: nil) - controller.imageCompletion = imageCompletion - controller.videoCompletion = videoCompletion - pushControllerImpl?(controller) - } +// mixin.requestAvatarEditor = { imageCompletion, videoCompletion in +// guard let imageCompletion, let videoCompletion else { +// return +// } +// let controller = AvatarEditorScreen(context: context, inputData: keyboardInputData.get(), peerType: .channel, markup: nil) +// controller.imageCompletion = imageCompletion +// controller.videoCompletion = videoCompletion +// pushControllerImpl?(controller) +// } mixin.didFinishWithImage = { image in if let image = image { completedChannelPhotoImpl(image) diff --git a/submodules/TelegramUI/Sources/CreateGroupController.swift b/submodules/TelegramUI/Sources/CreateGroupController.swift index 3532b0b827..463453523c 100644 --- a/submodules/TelegramUI/Sources/CreateGroupController.swift +++ b/submodules/TelegramUI/Sources/CreateGroupController.swift @@ -1061,15 +1061,15 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId] })) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } - mixin.requestAvatarEditor = { imageCompletion, videoCompletion in - guard let imageCompletion, let videoCompletion else { - return - } - let controller = AvatarEditorScreen(context: context, inputData: keyboardInputData.get(), peerType: .group, markup: nil) - controller.imageCompletion = imageCompletion - controller.videoCompletion = videoCompletion - pushImpl?(controller) - } +// mixin.requestAvatarEditor = { imageCompletion, videoCompletion in +// guard let imageCompletion, let videoCompletion else { +// return +// } +// let controller = AvatarEditorScreen(context: context, inputData: keyboardInputData.get(), peerType: .group, markup: nil) +// controller.imageCompletion = imageCompletion +// controller.videoCompletion = videoCompletion +// pushImpl?(controller) +// } mixin.didFinishWithImage = { image in if let image = image { completedGroupPhotoImpl(image)