mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
[WIP] Multiple story upload
This commit is contained in:
parent
746239cf69
commit
d8dd96e39e
@ -14188,3 +14188,12 @@ Sorry for the inconvenience.";
|
|||||||
"SendInviteLink.TextCallsRestrictedMultipleUsers_1" = "{user_list}, and **%d** more person do not accept calls.";
|
"SendInviteLink.TextCallsRestrictedMultipleUsers_1" = "{user_list}, and **%d** more person do not accept calls.";
|
||||||
"SendInviteLink.TextCallsRestrictedMultipleUsers_any" = "{user_list}, and **%d** more people do not accept calls.";
|
"SendInviteLink.TextCallsRestrictedMultipleUsers_any" = "{user_list}, and **%d** more people do not accept calls.";
|
||||||
"SendInviteLink.TextCallsRestrictedSendInviteLink" = "You can try to send an invite link instead.";
|
"SendInviteLink.TextCallsRestrictedSendInviteLink" = "You can try to send an invite link instead.";
|
||||||
|
|
||||||
|
"Story.Privacy.ShareStories" = "Share Stories";
|
||||||
|
"Story.Privacy.PostStoriesAsHeader" = "POST STORIES AS";
|
||||||
|
"Story.Privacy.WhoCanViewStoriesHeader" = "WHO CAN VIEW THIS STORIES";
|
||||||
|
"Story.Privacy.PostStories_1" = "Post %@ Story";
|
||||||
|
"Story.Privacy.PostStories_any" = "Post %@ Stories";
|
||||||
|
"Story.Privacy.KeepOnMyPageManyInfo" = "Keep these stories on your profile even after they expire in %@. Privacy settings will apply.";
|
||||||
|
"Story.Privacy.KeepOnChannelPageManyInfo" = "Keep these stories on the channel profile even after they expire in %@.";
|
||||||
|
"Story.Privacy.KeepOnGroupPageManyInfo" = "Keep these stories on the group page even after they expire in %@.";
|
||||||
|
@ -809,7 +809,7 @@ public protocol MediaEditorScreenResult {
|
|||||||
public protocol TelegramRootControllerInterface: NavigationController {
|
public protocol TelegramRootControllerInterface: NavigationController {
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func openStoryCamera(customTarget: Stories.PendingTarget?, transitionIn: StoryCameraTransitionIn?, transitionedIn: @escaping () -> Void, transitionOut: @escaping (Stories.PendingTarget?, Bool) -> StoryCameraTransitionOut?) -> StoryCameraTransitionInCoordinator?
|
func openStoryCamera(customTarget: Stories.PendingTarget?, transitionIn: StoryCameraTransitionIn?, transitionedIn: @escaping () -> Void, transitionOut: @escaping (Stories.PendingTarget?, Bool) -> StoryCameraTransitionOut?) -> StoryCameraTransitionInCoordinator?
|
||||||
func proceedWithStoryUpload(target: Stories.PendingTarget, result: MediaEditorScreenResult, existingMedia: EngineMedia?, forwardInfo: Stories.PendingForwardInfo?, externalState: MediaEditorTransitionOutExternalState, commit: @escaping (@escaping () -> Void) -> Void)
|
func proceedWithStoryUpload(target: Stories.PendingTarget, results: [MediaEditorScreenResult], existingMedia: EngineMedia?, forwardInfo: Stories.PendingForwardInfo?, externalState: MediaEditorTransitionOutExternalState, commit: @escaping (@escaping () -> Void) -> Void)
|
||||||
|
|
||||||
func getContactsController() -> ViewController?
|
func getContactsController() -> ViewController?
|
||||||
func getChatsController() -> ViewController?
|
func getChatsController() -> ViewController?
|
||||||
@ -1152,7 +1152,7 @@ public protocol SharedAccountContext: AnyObject {
|
|||||||
|
|
||||||
func makeAvatarMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, canDelete: Bool, performDelete: @escaping () -> Void, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController
|
func makeAvatarMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, canDelete: Bool, performDelete: @escaping () -> Void, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController
|
||||||
|
|
||||||
func makeStoryMediaPickerScreen(context: AccountContext, isDark: Bool, forCollage: Bool, selectionLimit: Int?, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, multipleCompletion: @escaping ([Any]) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void) -> ViewController
|
func makeStoryMediaPickerScreen(context: AccountContext, isDark: Bool, forCollage: Bool, selectionLimit: Int?, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, multipleCompletion: @escaping ([Any], Bool) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void) -> ViewController
|
||||||
|
|
||||||
func makeStickerPickerScreen(context: AccountContext, inputData: Promise<StickerPickerInput>, completion: @escaping (FileMediaReference) -> Void) -> ViewController
|
func makeStickerPickerScreen(context: AccountContext, inputData: Promise<StickerPickerInput>, completion: @escaping (FileMediaReference) -> Void) -> ViewController
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@ public struct AttachmentMainButtonState {
|
|||||||
public let isEnabled: Bool
|
public let isEnabled: Bool
|
||||||
public let hasShimmer: Bool
|
public let hasShimmer: Bool
|
||||||
public let iconName: String?
|
public let iconName: String?
|
||||||
|
public let smallSpacing: Bool
|
||||||
public let position: Position?
|
public let position: Position?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
@ -55,6 +56,7 @@ public struct AttachmentMainButtonState {
|
|||||||
isEnabled: Bool,
|
isEnabled: Bool,
|
||||||
hasShimmer: Bool,
|
hasShimmer: Bool,
|
||||||
iconName: String? = nil,
|
iconName: String? = nil,
|
||||||
|
smallSpacing: Bool = false,
|
||||||
position: Position? = nil
|
position: Position? = nil
|
||||||
) {
|
) {
|
||||||
self.text = text
|
self.text = text
|
||||||
@ -67,6 +69,7 @@ public struct AttachmentMainButtonState {
|
|||||||
self.isEnabled = isEnabled
|
self.isEnabled = isEnabled
|
||||||
self.hasShimmer = hasShimmer
|
self.hasShimmer = hasShimmer
|
||||||
self.iconName = iconName
|
self.iconName = iconName
|
||||||
|
self.smallSpacing = smallSpacing
|
||||||
self.position = position
|
self.position = position
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -790,6 +790,7 @@ private final class MainButtonNode: HighlightTrackingButtonNode {
|
|||||||
iconNode = ASImageNode()
|
iconNode = ASImageNode()
|
||||||
iconNode.displaysAsynchronously = false
|
iconNode.displaysAsynchronously = false
|
||||||
iconNode.image = generateTintedImage(image: UIImage(bundleImageName: iconName), color: state.textColor)
|
iconNode.image = generateTintedImage(image: UIImage(bundleImageName: iconName), color: state.textColor)
|
||||||
|
self.iconNode = iconNode
|
||||||
self.addSubnode(iconNode)
|
self.addSubnode(iconNode)
|
||||||
}
|
}
|
||||||
if let iconSize = iconNode.image?.size {
|
if let iconSize = iconNode.image?.size {
|
||||||
@ -1806,7 +1807,9 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
} else {
|
} else {
|
||||||
height = bounds.height + 8.0
|
height = bounds.height + 8.0
|
||||||
}
|
}
|
||||||
if !isNarrowButton {
|
if isTwoVerticalButtons && self.secondaryButtonState.smallSpacing {
|
||||||
|
|
||||||
|
} else if !isNarrowButton {
|
||||||
height += 9.0
|
height += 9.0
|
||||||
}
|
}
|
||||||
if isTwoVerticalButtons {
|
if isTwoVerticalButtons {
|
||||||
@ -1896,7 +1899,8 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
mainButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY + sideInset + buttonSize.height), size: buttonSize)
|
mainButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY + sideInset + buttonSize.height), size: buttonSize)
|
||||||
case .bottom:
|
case .bottom:
|
||||||
mainButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY), size: buttonSize)
|
mainButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY), size: buttonSize)
|
||||||
secondaryButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY + sideInset + buttonSize.height), size: buttonSize)
|
let buttonSpacing = self.secondaryButtonState.smallSpacing ? 8.0 : sideInset
|
||||||
|
secondaryButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY + buttonSpacing + buttonSize.height), size: buttonSize)
|
||||||
case .left:
|
case .left:
|
||||||
secondaryButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY), size: buttonSize)
|
secondaryButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY), size: buttonSize)
|
||||||
mainButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX + buttonSize.width + sideInset, y: buttonOriginY), size: buttonSize)
|
mainButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX + buttonSize.width + sideInset, y: buttonOriginY), size: buttonSize)
|
||||||
|
@ -566,6 +566,14 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
|
|||||||
self.hasSelectionChanged(false)
|
self.hasSelectionChanged(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func clearAll() {
|
||||||
|
for case let view as DrawingEntityView in self.subviews {
|
||||||
|
view.reset()
|
||||||
|
view.selectionView?.removeFromSuperview()
|
||||||
|
view.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func clear(animated: Bool = false) {
|
private func clear(animated: Bool = false) {
|
||||||
if animated {
|
if animated {
|
||||||
for case let view as DrawingEntityView in self.subviews {
|
for case let view as DrawingEntityView in self.subviews {
|
||||||
|
@ -184,7 +184,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
|||||||
private let bannedSendVideos: (Int32, Bool)?
|
private let bannedSendVideos: (Int32, Bool)?
|
||||||
private let canBoostToUnrestrict: Bool
|
private let canBoostToUnrestrict: Bool
|
||||||
fileprivate let paidMediaAllowed: Bool
|
fileprivate let paidMediaAllowed: Bool
|
||||||
private let subject: Subject
|
fileprivate let subject: Subject
|
||||||
fileprivate let forCollage: Bool
|
fileprivate let forCollage: Bool
|
||||||
private let saveEditedPhotos: Bool
|
private let saveEditedPhotos: Bool
|
||||||
|
|
||||||
@ -1826,6 +1826,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
|||||||
fileprivate let secondaryButtonStatePromise = Promise<AttachmentMainButtonState?>(nil)
|
fileprivate let secondaryButtonStatePromise = Promise<AttachmentMainButtonState?>(nil)
|
||||||
|
|
||||||
private let mainButtonAction: (() -> Void)?
|
private let mainButtonAction: (() -> Void)?
|
||||||
|
private let secondaryButtonAction: (() -> Void)?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
@ -1845,7 +1846,8 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
|||||||
selectionContext: TGMediaSelectionContext? = nil,
|
selectionContext: TGMediaSelectionContext? = nil,
|
||||||
saveEditedPhotos: Bool = false,
|
saveEditedPhotos: Bool = false,
|
||||||
mainButtonState: AttachmentMainButtonState? = nil,
|
mainButtonState: AttachmentMainButtonState? = nil,
|
||||||
mainButtonAction: (() -> Void)? = nil
|
mainButtonAction: (() -> Void)? = nil,
|
||||||
|
secondaryButtonAction: (() -> Void)? = nil
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
|
||||||
@ -1865,6 +1867,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
|||||||
self.saveEditedPhotos = saveEditedPhotos
|
self.saveEditedPhotos = saveEditedPhotos
|
||||||
self.mainButtonStatePromise.set(.single(mainButtonState))
|
self.mainButtonStatePromise.set(.single(mainButtonState))
|
||||||
self.mainButtonAction = mainButtonAction
|
self.mainButtonAction = mainButtonAction
|
||||||
|
self.secondaryButtonAction = secondaryButtonAction
|
||||||
|
|
||||||
let selectionContext = selectionContext ?? TGMediaSelectionContext()
|
let selectionContext = selectionContext ?? TGMediaSelectionContext()
|
||||||
|
|
||||||
@ -1998,7 +2001,14 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
|||||||
} else if collection == nil {
|
} else if collection == nil {
|
||||||
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
|
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
|
||||||
|
|
||||||
|
var hasSelect = false
|
||||||
if forCollage {
|
if forCollage {
|
||||||
|
hasSelect = true
|
||||||
|
} else if case .story = mode {
|
||||||
|
hasSelect = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasSelect {
|
||||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.Common_Select, target: self, action: #selector(self.selectPressed))
|
self.navigationItem.rightBarButtonItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.Common_Select, target: self, action: #selector(self.selectPressed))
|
||||||
} else {
|
} else {
|
||||||
if [.createSticker].contains(mode) {
|
if [.createSticker].contains(mode) {
|
||||||
@ -2338,6 +2348,9 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
|||||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut)
|
let transition = ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut)
|
||||||
var moreIsVisible = false
|
var moreIsVisible = false
|
||||||
if case let .assets(_, mode) = self.subject, [.story, .createSticker].contains(mode) {
|
if case let .assets(_, mode) = self.subject, [.story, .createSticker].contains(mode) {
|
||||||
|
if count == 1 {
|
||||||
|
self.requestAttachmentMenuExpansion()
|
||||||
|
}
|
||||||
moreIsVisible = true
|
moreIsVisible = true
|
||||||
} else if case let .media(media) = self.subject {
|
} else if case let .media(media) = self.subject {
|
||||||
self.titleView.title = media.count == 1 ? self.presentationData.strings.Attachment_Pasteboard : self.presentationData.strings.Attachment_SelectedMedia(count)
|
self.titleView.title = media.count == 1 ? self.presentationData.strings.Attachment_Pasteboard : self.presentationData.strings.Attachment_SelectedMedia(count)
|
||||||
@ -2381,7 +2394,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
|||||||
transition.updateAlpha(node: self.moreButtonNode.iconNode, alpha: moreIsVisible ? 1.0 : 0.0)
|
transition.updateAlpha(node: self.moreButtonNode.iconNode, alpha: moreIsVisible ? 1.0 : 0.0)
|
||||||
transition.updateTransformScale(node: self.moreButtonNode.iconNode, scale: moreIsVisible ? 1.0 : 0.1)
|
transition.updateTransformScale(node: self.moreButtonNode.iconNode, scale: moreIsVisible ? 1.0 : 0.1)
|
||||||
|
|
||||||
if self.selectionCount > 0 {
|
if case .assets(_, .story) = self.subject, self.selectionCount > 0 {
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
var text = "Create 1 Story"
|
var text = "Create 1 Story"
|
||||||
if self.selectionCount > 1 {
|
if self.selectionCount > 1 {
|
||||||
@ -2390,7 +2403,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
|||||||
self.mainButtonStatePromise.set(.single(AttachmentMainButtonState(text: text, badge: nil, font: .bold, background: .color(self.presentationData.theme.actionSheet.controlAccentColor), textColor: self.presentationData.theme.list.itemCheckColors.foregroundColor, isVisible: true, progress: .none, isEnabled: true, hasShimmer: false, position: .top)))
|
self.mainButtonStatePromise.set(.single(AttachmentMainButtonState(text: text, badge: nil, font: .bold, background: .color(self.presentationData.theme.actionSheet.controlAccentColor), textColor: self.presentationData.theme.list.itemCheckColors.foregroundColor, isVisible: true, progress: .none, isEnabled: true, hasShimmer: false, position: .top)))
|
||||||
|
|
||||||
if self.selectionCount > 1 && self.selectionCount <= 6 {
|
if self.selectionCount > 1 && self.selectionCount <= 6 {
|
||||||
self.secondaryButtonStatePromise.set(.single(AttachmentMainButtonState(text: "Combine into Collage", badge: nil, font: .regular, background: .color(.clear), textColor: self.presentationData.theme.actionSheet.controlAccentColor, isVisible: true, progress: .none, isEnabled: true, hasShimmer: false, iconName: "Media Editor/Collage", position: .bottom)))
|
self.secondaryButtonStatePromise.set(.single(AttachmentMainButtonState(text: "Combine into Collage", badge: nil, font: .regular, background: .color(.clear), textColor: self.presentationData.theme.actionSheet.controlAccentColor, isVisible: true, progress: .none, isEnabled: true, hasShimmer: false, iconName: "Media Editor/Collage", smallSpacing: true, position: .bottom)))
|
||||||
} else {
|
} else {
|
||||||
self.secondaryButtonStatePromise.set(.single(nil))
|
self.secondaryButtonStatePromise.set(.single(nil))
|
||||||
}
|
}
|
||||||
@ -2427,6 +2440,10 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
|||||||
self.mainButtonAction?()
|
self.mainButtonAction?()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func secondaryButtonPressed() {
|
||||||
|
self.secondaryButtonAction?()
|
||||||
|
}
|
||||||
|
|
||||||
func dismissAllTooltips() {
|
func dismissAllTooltips() {
|
||||||
self.undoOverlayController?.dismissWithCommitAction()
|
self.undoOverlayController?.dismissWithCommitAction()
|
||||||
}
|
}
|
||||||
@ -2810,7 +2827,7 @@ final class MediaPickerContext: AttachmentMediaPickerContext {
|
|||||||
private weak var controller: MediaPickerScreenImpl?
|
private weak var controller: MediaPickerScreenImpl?
|
||||||
|
|
||||||
var selectionCount: Signal<Int, NoError> {
|
var selectionCount: Signal<Int, NoError> {
|
||||||
if self.controller?.forCollage == true {
|
if let controller = self.controller, case .assets(_, .story) = controller.subject {
|
||||||
return .single(0)
|
return .single(0)
|
||||||
} else {
|
} else {
|
||||||
return Signal { [weak self] subscriber in
|
return Signal { [weak self] subscriber in
|
||||||
@ -2973,7 +2990,7 @@ final class MediaPickerContext: AttachmentMediaPickerContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func secondaryButtonAction() {
|
func secondaryButtonAction() {
|
||||||
self.controller?.mainButtonPressed()
|
self.controller?.secondaryButtonPressed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3162,7 +3179,7 @@ public func storyMediaPickerController(
|
|||||||
selectionLimit: Int?,
|
selectionLimit: Int?,
|
||||||
getSourceRect: @escaping () -> CGRect,
|
getSourceRect: @escaping () -> CGRect,
|
||||||
completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void,
|
completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void,
|
||||||
multipleCompletion: @escaping ([Any]) -> Void,
|
multipleCompletion: @escaping ([Any], Bool) -> Void,
|
||||||
dismissed: @escaping () -> Void,
|
dismissed: @escaping () -> Void,
|
||||||
groupsPresented: @escaping () -> Void
|
groupsPresented: @escaping () -> Void
|
||||||
) -> ViewController {
|
) -> ViewController {
|
||||||
@ -3181,9 +3198,18 @@ public func storyMediaPickerController(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: {
|
let controller = AttachmentController(
|
||||||
return nil
|
context: context,
|
||||||
})
|
updatedPresentationData: updatedPresentationData,
|
||||||
|
chatLocation: nil,
|
||||||
|
buttons: [.standalone],
|
||||||
|
initialButton: .standalone,
|
||||||
|
fromMenu: false,
|
||||||
|
hasTextInput: false,
|
||||||
|
makeEntityInputView: {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
)
|
||||||
controller.forceSourceRect = true
|
controller.forceSourceRect = true
|
||||||
controller.getSourceRect = getSourceRect
|
controller.getSourceRect = getSourceRect
|
||||||
controller.requestController = { _, present in
|
controller.requestController = { _, present in
|
||||||
@ -3207,7 +3233,18 @@ public func storyMediaPickerController(
|
|||||||
results.append(asset)
|
results.append(asset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
multipleCompletion(results)
|
multipleCompletion(results, false)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
secondaryButtonAction: { [weak selectionContext] in
|
||||||
|
if let selectionContext, let selectedItems = selectionContext.selectedItems() {
|
||||||
|
var results: [Any] = []
|
||||||
|
for item in selectedItems {
|
||||||
|
if let item = item as? TGMediaAsset, let asset = item.backingAsset {
|
||||||
|
results.append(asset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
multipleCompletion(results, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -1651,6 +1651,7 @@ public class CameraScreenImpl: ViewController, CameraScreen {
|
|||||||
case videoCollage(VideoCollage)
|
case videoCollage(VideoCollage)
|
||||||
case asset(PHAsset)
|
case asset(PHAsset)
|
||||||
case draft(MediaEditorDraft)
|
case draft(MediaEditorDraft)
|
||||||
|
case assets([PHAsset])
|
||||||
|
|
||||||
func withPIPPosition(_ position: CameraScreenImpl.PIPPosition) -> Result {
|
func withPIPPosition(_ position: CameraScreenImpl.PIPPosition) -> Result {
|
||||||
switch self {
|
switch self {
|
||||||
@ -3637,11 +3638,10 @@ public class CameraScreenImpl: ViewController, CameraScreen {
|
|||||||
selectionLimit = 10
|
selectionLimit = 10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//TODO:unmock
|
|
||||||
controller = self.context.sharedContext.makeStoryMediaPickerScreen(
|
controller = self.context.sharedContext.makeStoryMediaPickerScreen(
|
||||||
context: self.context,
|
context: self.context,
|
||||||
isDark: true,
|
isDark: true,
|
||||||
forCollage: self.cameraState.isCollageEnabled || "".isEmpty,
|
forCollage: self.cameraState.isCollageEnabled,
|
||||||
selectionLimit: selectionLimit,
|
selectionLimit: selectionLimit,
|
||||||
getSourceRect: { [weak self] in
|
getSourceRect: { [weak self] in
|
||||||
if let self {
|
if let self {
|
||||||
@ -3707,44 +3707,52 @@ public class CameraScreenImpl: ViewController, CameraScreen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, multipleCompletion: { [weak self] results in
|
}, multipleCompletion: { [weak self] results, collage in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.cameraState.isCollageEnabled {
|
if collage {
|
||||||
var selectedGrid: Camera.CollageGrid = collageGrids.first!
|
if !self.cameraState.isCollageEnabled {
|
||||||
for grid in collageGrids {
|
var selectedGrid: Camera.CollageGrid = collageGrids.first!
|
||||||
if grid.count == results.count {
|
for grid in collageGrids {
|
||||||
selectedGrid = grid
|
if grid.count == results.count {
|
||||||
break
|
selectedGrid = grid
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
self.updateCameraState({
|
||||||
|
$0.updatedIsCollageEnabled(true).updatedCollageProgress(0.0).updatedIsDualCameraEnabled(false).updatedCollageGrid(selectedGrid)
|
||||||
|
}, transition: .spring(duration: 0.3))
|
||||||
}
|
}
|
||||||
self.updateCameraState({
|
|
||||||
$0.updatedIsCollageEnabled(true).updatedCollageProgress(0.0).updatedIsDualCameraEnabled(false).updatedCollageGrid(selectedGrid)
|
if let assets = results as? [PHAsset] {
|
||||||
}, transition: .spring(duration: 0.3))
|
var results: [Signal<CameraScreenImpl.Result, NoError>] = []
|
||||||
}
|
for asset in assets {
|
||||||
|
if asset.mediaType == .video && asset.duration > 1.0 {
|
||||||
if let assets = results as? [PHAsset] {
|
results.append(.single(.asset(asset)))
|
||||||
var results: [Signal<CameraScreenImpl.Result, NoError>] = []
|
} else {
|
||||||
for asset in assets {
|
results.append(
|
||||||
if asset.mediaType == .video && asset.duration > 1.0 {
|
assetImage(asset: asset, targetSize: CGSize(width: 1080, height: 1080), exact: false, deliveryMode: .highQualityFormat)
|
||||||
results.append(.single(.asset(asset)))
|
|> runOn(Queue.concurrentDefaultQueue())
|
||||||
} else {
|
|> mapToSignal { image -> Signal<CameraScreenImpl.Result, NoError> in
|
||||||
results.append(
|
if let image {
|
||||||
assetImage(asset: asset, targetSize: CGSize(width: 1080, height: 1080), exact: false, deliveryMode: .highQualityFormat)
|
return .single(.image(Result.Image(image: image, additionalImage: nil, additionalImagePosition: .topLeft)))
|
||||||
|> runOn(Queue.concurrentDefaultQueue())
|
} else {
|
||||||
|> mapToSignal { image -> Signal<CameraScreenImpl.Result, NoError> in
|
return .complete()
|
||||||
if let image {
|
}
|
||||||
return .single(.image(Result.Image(image: image, additionalImage: nil, additionalImagePosition: .topLeft)))
|
|
||||||
} else {
|
|
||||||
return .complete()
|
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
|
self.node.collage?.addResults(signals: results)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let assets = results as? [PHAsset] {
|
||||||
|
self.completion(.single(.assets(assets)), nil, {
|
||||||
|
|
||||||
|
})
|
||||||
}
|
}
|
||||||
self.node.collage?.addResults(signals: results)
|
|
||||||
}
|
}
|
||||||
self.galleryController = nil
|
self.galleryController = nil
|
||||||
|
|
||||||
|
@ -65,6 +65,8 @@ swift_library(
|
|||||||
"//submodules/UrlEscaping",
|
"//submodules/UrlEscaping",
|
||||||
"//submodules/DeviceLocationManager",
|
"//submodules/DeviceLocationManager",
|
||||||
"//submodules/TelegramUI/Components/SaveProgressScreen",
|
"//submodules/TelegramUI/Components/SaveProgressScreen",
|
||||||
|
"//submodules/TelegramUI/Components/MediaAssetsContext",
|
||||||
|
"//submodules/CheckNode",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -160,7 +160,7 @@ public extension MediaEditorScreenImpl {
|
|||||||
} else {
|
} else {
|
||||||
existingMedia = storyItem.media
|
existingMedia = storyItem.media
|
||||||
}
|
}
|
||||||
rootController.proceedWithStoryUpload(target: target, result: result as! MediaEditorScreenResult, existingMedia: existingMedia, forwardInfo: forwardInfo, externalState: externalState, commit: commit)
|
rootController.proceedWithStoryUpload(target: target, results: [result as! MediaEditorScreenResult], existingMedia: existingMedia, forwardInfo: forwardInfo, externalState: externalState, commit: commit)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -327,6 +327,7 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
private let switchCameraButton = ComponentView<Empty>()
|
private let switchCameraButton = ComponentView<Empty>()
|
||||||
|
|
||||||
private let selectionButton = ComponentView<Empty>()
|
private let selectionButton = ComponentView<Empty>()
|
||||||
|
private let selectionPanel = ComponentView<Empty>()
|
||||||
|
|
||||||
private let textCancelButton = ComponentView<Empty>()
|
private let textCancelButton = ComponentView<Empty>()
|
||||||
private let textDoneButton = ComponentView<Empty>()
|
private let textDoneButton = ComponentView<Empty>()
|
||||||
@ -741,6 +742,13 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
return inputText
|
return inputText
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setInputText(_ text: NSAttributedString) {
|
||||||
|
guard let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
inputPanelView.setSendMessageInput(value: .text(text), updateState: true)
|
||||||
|
}
|
||||||
|
|
||||||
private func updateCoverPosition() {
|
private func updateCoverPosition() {
|
||||||
guard let controller = self.environment?.controller() as? MediaEditorScreenImpl, let mediaEditor = controller.node.mediaEditor else {
|
guard let controller = self.environment?.controller() as? MediaEditorScreenImpl, let mediaEditor = controller.node.mediaEditor else {
|
||||||
return
|
return
|
||||||
@ -1993,39 +2001,118 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
transition.setAlpha(view: switchCameraButtonView, alpha: isRecordingAdditionalVideo ? 1.0 : 0.0)
|
transition.setAlpha(view: switchCameraButtonView, alpha: isRecordingAdditionalVideo ? 1.0 : 0.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if controller.node.items.count > 1 {
|
||||||
let selectionButtonSize = self.selectionButton.update(
|
let selectionButtonSize = self.selectionButton.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(PlainButtonComponent(
|
component: AnyComponent(PlainButtonComponent(
|
||||||
content: AnyComponent(
|
content: AnyComponent(
|
||||||
SelectionPanelButtonContentComponent(
|
SelectionPanelButtonContentComponent(
|
||||||
count: 1,
|
count: Int32(controller.node.items.count(where: { $0.isEnabled })),
|
||||||
isSelected: self.isSelectionPanelOpen,
|
isSelected: self.isSelectionPanelOpen,
|
||||||
tag: nil
|
tag: nil
|
||||||
|
)
|
||||||
|
),
|
||||||
|
effectAlignment: .center,
|
||||||
|
action: { [weak self] in
|
||||||
|
if let self {
|
||||||
|
self.isSelectionPanelOpen = !self.isSelectionPanelOpen
|
||||||
|
self.state?.updated()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
animateAlpha: false
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: 33.0, height: 33.0)
|
||||||
|
)
|
||||||
|
let selectionButtonFrame = CGRect(
|
||||||
|
origin: CGPoint(x: availableSize.width - selectionButtonSize.width - 12.0, y: inputPanelFrame.minY - selectionButtonSize.height - 3.0),
|
||||||
|
size: selectionButtonSize
|
||||||
|
)
|
||||||
|
if let selectionButtonView = self.selectionButton.view as? PlainButtonComponent.View {
|
||||||
|
if selectionButtonView.superview == nil {
|
||||||
|
self.addSubview(selectionButtonView)
|
||||||
|
}
|
||||||
|
transition.setPosition(view: selectionButtonView, position: selectionButtonFrame.center)
|
||||||
|
transition.setBounds(view: selectionButtonView, bounds: CGRect(origin: .zero, size: selectionButtonFrame.size))
|
||||||
|
transition.setScale(view: selectionButtonView, scale: displayTopButtons ? 1.0 : 0.01)
|
||||||
|
transition.setAlpha(view: selectionButtonView, alpha: displayTopButtons && !component.isDismissing && !component.isInteractingWithEntities ? 1.0 : 0.0)
|
||||||
|
|
||||||
|
if self.isSelectionPanelOpen {
|
||||||
|
let selectionPanelFrame = CGRect(
|
||||||
|
origin: CGPoint(x: 12.0, y: inputPanelFrame.minY - selectionButtonSize.height - 3.0 - 130.0),
|
||||||
|
size: CGSize(width: availableSize.width - 24.0, height: 120.0)
|
||||||
)
|
)
|
||||||
),
|
|
||||||
effectAlignment: .center,
|
var selectedItemId = ""
|
||||||
action: { [weak self] in
|
if case let .asset(asset) = controller.node.subject {
|
||||||
if let self {
|
selectedItemId = asset.localIdentifier
|
||||||
self.isSelectionPanelOpen = !self.isSelectionPanelOpen
|
|
||||||
self.state?.updated()
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
animateAlpha: false
|
let _ = self.selectionPanel.update(
|
||||||
)),
|
transition: transition,
|
||||||
environment: {},
|
component: AnyComponent(
|
||||||
containerSize: CGSize(width: 33.0, height: 33.0)
|
SelectionPanelComponent(
|
||||||
)
|
previewContainerView: controller.node.previewContentContainerView,
|
||||||
let selectionButtonFrame = CGRect(
|
frame: selectionPanelFrame,
|
||||||
origin: CGPoint(x: availableSize.width - selectionButtonSize.width - 12.0, y: max(environment.statusBarHeight + 10.0, inputPanelFrame.minY - selectionButtonSize.height - 3.0)),
|
items: controller.node.items,
|
||||||
size: selectionButtonSize
|
selectedItemId: selectedItemId,
|
||||||
)
|
itemTapped: { [weak self, weak controller] id in
|
||||||
if let selectionButtonView = self.selectionButton.view {
|
guard let self, let controller else {
|
||||||
if selectionButtonView.superview == nil {
|
return
|
||||||
self.addSubview(selectionButtonView)
|
}
|
||||||
|
self.isSelectionPanelOpen = false
|
||||||
|
self.state?.updated()
|
||||||
|
|
||||||
|
if let id {
|
||||||
|
controller.node.switchToItem(id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
itemSelectionToggled: { [weak self, weak controller] id in
|
||||||
|
guard let self, let controller else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let itemIndex = controller.node.items.firstIndex(where: { $0.asset.localIdentifier == id }) {
|
||||||
|
controller.node.items[itemIndex].isEnabled = !controller.node.items[itemIndex].isEnabled
|
||||||
|
}
|
||||||
|
self.state?.updated(transition: .spring(duration: 0.3))
|
||||||
|
},
|
||||||
|
itemReordered: { [weak self, weak controller] fromId, toId in
|
||||||
|
guard let self, let controller else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let fromIndex = controller.node.items.firstIndex(where: { $0.asset.localIdentifier == fromId }), let toIndex = controller.node.items.firstIndex(where: { $0.asset.localIdentifier == toId }), toIndex < controller.node.items.count else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let fromItem = controller.node.items[fromIndex]
|
||||||
|
let toItem = controller.node.items[toIndex]
|
||||||
|
controller.node.items[fromIndex] = toItem
|
||||||
|
controller.node.items[toIndex] = fromItem
|
||||||
|
self.state?.updated(transition: .spring(duration: 0.3))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
environment: {},
|
||||||
|
containerSize: availableSize
|
||||||
|
)
|
||||||
|
if let selectionPanelView = self.selectionPanel.view as? SelectionPanelComponent.View {
|
||||||
|
if selectionPanelView.superview == nil {
|
||||||
|
self.insertSubview(selectionPanelView, belowSubview: selectionButtonView)
|
||||||
|
if let buttonView = selectionButtonView.contentView as? SelectionPanelButtonContentComponent.View {
|
||||||
|
selectionPanelView.animateIn(from: buttonView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selectionPanelView.frame = CGRect(origin: .zero, size: availableSize)
|
||||||
|
}
|
||||||
|
} else if let selectionPanelView = self.selectionPanel.view as? SelectionPanelComponent.View {
|
||||||
|
if let buttonView = selectionButtonView.contentView as? SelectionPanelButtonContentComponent.View {
|
||||||
|
selectionPanelView.animateOut(to: buttonView, completion: { [weak selectionPanelView] in
|
||||||
|
selectionPanelView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
selectionPanelView.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
transition.setPosition(view: selectionButtonView, position: selectionButtonFrame.center)
|
|
||||||
transition.setBounds(view: selectionButtonView, bounds: CGRect(origin: .zero, size: selectionButtonFrame.size))
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
inputPanelSize = CGSize(width: 0.0, height: 12.0)
|
inputPanelSize = CGSize(width: 0.0, height: 12.0)
|
||||||
@ -2795,6 +2882,38 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct EditingItem: Equatable {
|
||||||
|
let asset: PHAsset
|
||||||
|
var values: MediaEditorValues?
|
||||||
|
var caption = NSAttributedString()
|
||||||
|
var thumbnail: UIImage?
|
||||||
|
var isEnabled = true
|
||||||
|
var version: Int = 0
|
||||||
|
|
||||||
|
init(asset: PHAsset) {
|
||||||
|
self.asset = asset
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: EditingItem, rhs: EditingItem) -> Bool {
|
||||||
|
if lhs.asset.localIdentifier != rhs.asset.localIdentifier {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.values != rhs.values {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.caption != rhs.caption {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.thumbnail != rhs.thumbnail {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.version != rhs.version {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final class Node: ViewControllerTracingNode, ASGestureRecognizerDelegate, UIScrollViewDelegate {
|
final class Node: ViewControllerTracingNode, ASGestureRecognizerDelegate, UIScrollViewDelegate {
|
||||||
private weak var controller: MediaEditorScreenImpl?
|
private weak var controller: MediaEditorScreenImpl?
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
@ -2803,6 +2922,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
|
|
||||||
var subject: MediaEditorScreenImpl.Subject?
|
var subject: MediaEditorScreenImpl.Subject?
|
||||||
var actualSubject: MediaEditorScreenImpl.Subject?
|
var actualSubject: MediaEditorScreenImpl.Subject?
|
||||||
|
var items: [EditingItem] = []
|
||||||
|
|
||||||
private var subjectDisposable: Disposable?
|
private var subjectDisposable: Disposable?
|
||||||
private var appInForegroundDisposable: Disposable?
|
private var appInForegroundDisposable: Disposable?
|
||||||
@ -2891,6 +3011,10 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
|
|
||||||
private let readyValue = Promise<Bool>()
|
private let readyValue = Promise<Bool>()
|
||||||
|
|
||||||
|
var componentHostView: MediaEditorScreenComponent.View? {
|
||||||
|
return self.componentHost.view as? MediaEditorScreenComponent.View
|
||||||
|
}
|
||||||
|
|
||||||
init(controller: MediaEditorScreenImpl) {
|
init(controller: MediaEditorScreenImpl) {
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.context = controller.context
|
self.context = controller.context
|
||||||
@ -3062,7 +3186,46 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
|> deliverOnMainQueue
|
|> deliverOnMainQueue
|
||||||
).start(next: { [weak self] subject in
|
).start(next: { [weak self] subject in
|
||||||
if let self, let subject {
|
if let self, let subject {
|
||||||
self.setup(with: subject)
|
self.actualSubject = subject
|
||||||
|
|
||||||
|
var effectiveSubject = subject
|
||||||
|
switch subject {
|
||||||
|
case let .assets(assets):
|
||||||
|
effectiveSubject = .asset(assets.first!)
|
||||||
|
self.items = assets.map { EditingItem(asset: $0) }
|
||||||
|
case let .draft(draft, _):
|
||||||
|
for entity in draft.values.entities {
|
||||||
|
if case let .sticker(sticker) = entity {
|
||||||
|
switch sticker.content {
|
||||||
|
case let .message(ids, _, _, _, _):
|
||||||
|
effectiveSubject = .message(ids)
|
||||||
|
case let .gift(gift, _):
|
||||||
|
effectiveSubject = .gift(gift)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
var privacy: MediaEditorResultPrivacy?
|
||||||
|
var values: MediaEditorValues?
|
||||||
|
var isDraft = false
|
||||||
|
if case let .draft(draft, _) = subject {
|
||||||
|
privacy = draft.privacy
|
||||||
|
values = draft.values
|
||||||
|
isDraft = true
|
||||||
|
}
|
||||||
|
|
||||||
|
self.setup(
|
||||||
|
subject: effectiveSubject,
|
||||||
|
privacy: privacy,
|
||||||
|
values: values,
|
||||||
|
caption: nil,
|
||||||
|
isDraft: isDraft
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -3183,35 +3346,24 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
self.stickerCutoutStatusDisposable?.dispose()
|
self.stickerCutoutStatusDisposable?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setup(with subject: MediaEditorScreenImpl.Subject) {
|
func setup(
|
||||||
|
subject: MediaEditorScreenImpl.Subject,
|
||||||
|
privacy: MediaEditorResultPrivacy? = nil,
|
||||||
|
values: MediaEditorValues?,
|
||||||
|
caption: NSAttributedString?,
|
||||||
|
isDraft: Bool = false
|
||||||
|
) {
|
||||||
guard let controller = self.controller else {
|
guard let controller = self.controller else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.actualSubject = subject
|
self.subject = subject
|
||||||
|
|
||||||
var effectiveSubject = subject
|
|
||||||
if case let .draft(draft, _ ) = subject {
|
|
||||||
for entity in draft.values.entities {
|
|
||||||
if case let .sticker(sticker) = entity {
|
|
||||||
switch sticker.content {
|
|
||||||
case let .message(ids, _, _, _, _):
|
|
||||||
effectiveSubject = .message(ids)
|
|
||||||
case let .gift(gift, _):
|
|
||||||
effectiveSubject = .gift(gift)
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.subject = effectiveSubject
|
|
||||||
|
|
||||||
Queue.mainQueue().justDispatch {
|
Queue.mainQueue().justDispatch {
|
||||||
controller.setupAudioSessionIfNeeded()
|
controller.setupAudioSessionIfNeeded()
|
||||||
}
|
}
|
||||||
|
|
||||||
if case let .draft(draft, _) = subject, let privacy = draft.privacy {
|
if let privacy {
|
||||||
controller.state.privacy = privacy
|
controller.state.privacy = privacy
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3229,7 +3381,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
controller.isSavingAvailable = isSavingAvailable
|
controller.isSavingAvailable = isSavingAvailable
|
||||||
controller.requestLayout(transition: .immediate)
|
controller.requestLayout(transition: .immediate)
|
||||||
|
|
||||||
let mediaDimensions = effectiveSubject.dimensions
|
let mediaDimensions = subject.dimensions
|
||||||
let maxSide: CGFloat = 1920.0 / UIScreen.main.scale
|
let maxSide: CGFloat = 1920.0 / UIScreen.main.scale
|
||||||
let fittedSize = mediaDimensions.cgSize.fitted(CGSize(width: maxSide, height: maxSide))
|
let fittedSize = mediaDimensions.cgSize.fitted(CGSize(width: maxSide, height: maxSide))
|
||||||
let mediaEntity = DrawingMediaEntity(size: fittedSize)
|
let mediaEntity = DrawingMediaEntity(size: fittedSize)
|
||||||
@ -3268,27 +3420,28 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
}
|
}
|
||||||
|
|
||||||
let initialValues: MediaEditorValues?
|
let initialValues: MediaEditorValues?
|
||||||
if case let .draft(draft, _) = subject {
|
if let values {
|
||||||
initialValues = draft.values
|
initialValues = values
|
||||||
|
|
||||||
for entity in draft.values.entities {
|
for entity in values.entities {
|
||||||
self.entitiesView.add(entity.entity.duplicate(copy: true), announce: false)
|
self.entitiesView.add(entity.entity.duplicate(copy: true), announce: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let drawingData = initialValues?.drawing?.pngData() {
|
if let drawingData = values.drawing?.pngData() {
|
||||||
self.drawingView.setup(withDrawing: drawingData)
|
self.drawingView.setup(withDrawing: drawingData)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
initialValues = nil
|
initialValues = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var mediaEditorMode: MediaEditor.Mode = .default
|
let mediaEditorMode: MediaEditor.Mode
|
||||||
if case .stickerEditor = controller.mode {
|
switch controller.mode {
|
||||||
|
case .stickerEditor:
|
||||||
mediaEditorMode = .sticker
|
mediaEditorMode = .sticker
|
||||||
} else if case .avatarEditor = controller.mode {
|
case .avatarEditor, .coverEditor:
|
||||||
mediaEditorMode = .avatar
|
|
||||||
} else if case .coverEditor = controller.mode {
|
|
||||||
mediaEditorMode = .avatar
|
mediaEditorMode = .avatar
|
||||||
|
default:
|
||||||
|
mediaEditorMode = .default
|
||||||
}
|
}
|
||||||
|
|
||||||
if let mediaEntityView = self.entitiesView.add(mediaEntity, announce: false) as? DrawingMediaEntityView {
|
if let mediaEntityView = self.entitiesView.add(mediaEntity, announce: false) as? DrawingMediaEntityView {
|
||||||
@ -3314,7 +3467,13 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mediaEditor = MediaEditor(context: self.context, mode: mediaEditorMode, subject: effectiveSubject.editorSubject, values: initialValues, hasHistogram: true)
|
let mediaEditor = MediaEditor(
|
||||||
|
context: self.context,
|
||||||
|
mode: mediaEditorMode,
|
||||||
|
subject: subject.editorSubject,
|
||||||
|
values: initialValues,
|
||||||
|
hasHistogram: true
|
||||||
|
)
|
||||||
if case .avatarEditor = controller.mode {
|
if case .avatarEditor = controller.mode {
|
||||||
mediaEditor.setVideoIsMuted(true)
|
mediaEditor.setVideoIsMuted(true)
|
||||||
} else if case let .coverEditor(dimensions) = controller.mode {
|
} else if case let .coverEditor(dimensions) = controller.mode {
|
||||||
@ -3327,7 +3486,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
mediaEditor.seek(initialVideoPosition, andPlay: true)
|
mediaEditor.seek(initialVideoPosition, andPlay: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.context.sharedContext.currentPresentationData.with({$0}).autoNightModeTriggered {
|
if !isDraft, self.context.sharedContext.currentPresentationData.with({$0}).autoNightModeTriggered {
|
||||||
switch subject {
|
switch subject {
|
||||||
case .message, .gift:
|
case .message, .gift:
|
||||||
mediaEditor.setNightTheme(true)
|
mediaEditor.setNightTheme(true)
|
||||||
@ -3347,46 +3506,48 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
controller.requestLayout(transition: .animated(duration: 0.25, curve: .easeInOut))
|
controller.requestLayout(transition: .animated(duration: 0.25, curve: .easeInOut))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.stickerCutoutStatusDisposable = (mediaEditor.cutoutStatus
|
if case .stickerEditor = controller.mode {
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] cutoutStatus in
|
self.stickerCutoutStatusDisposable = (mediaEditor.cutoutStatus
|
||||||
guard let self else {
|
|> deliverOnMainQueue).start(next: { [weak self] cutoutStatus in
|
||||||
return
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.stickerCutoutStatus = cutoutStatus
|
||||||
|
self.requestLayout(forceUpdate: true, transition: .easeInOut(duration: 0.25))
|
||||||
|
})
|
||||||
|
mediaEditor.maskUpdated = { [weak self] mask, apply in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if self.stickerMaskDrawingView == nil {
|
||||||
|
self.setupMaskDrawingView(size: mask.size)
|
||||||
|
}
|
||||||
|
if apply, let maskData = mask.pngData() {
|
||||||
|
self.stickerMaskDrawingView?.setup(withDrawing: maskData, storeAsClear: true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.stickerCutoutStatus = cutoutStatus
|
mediaEditor.classificationUpdated = { [weak self] classes in
|
||||||
self.requestLayout(forceUpdate: true, transition: .easeInOut(duration: 0.25))
|
guard let self else {
|
||||||
})
|
return
|
||||||
mediaEditor.maskUpdated = { [weak self] mask, apply in
|
}
|
||||||
guard let self else {
|
self.controller?.stickerRecommendedEmoji = emojiForClasses(classes.map { $0.0 })
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if self.stickerMaskDrawingView == nil {
|
|
||||||
self.setupMaskDrawingView(size: mask.size)
|
|
||||||
}
|
|
||||||
if apply, let maskData = mask.pngData() {
|
|
||||||
self.stickerMaskDrawingView?.setup(withDrawing: maskData, storeAsClear: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mediaEditor.classificationUpdated = { [weak self] classes in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.controller?.stickerRecommendedEmoji = emojiForClasses(classes.map { $0.0 })
|
|
||||||
}
|
}
|
||||||
mediaEditor.attachPreviewView(self.previewView, andPlay: !(self.controller?.isEditingStoryCover ?? false))
|
mediaEditor.attachPreviewView(self.previewView, andPlay: !(self.controller?.isEditingStoryCover ?? false))
|
||||||
|
|
||||||
if case .empty = effectiveSubject {
|
if case .empty = subject {
|
||||||
self.stickerMaskDrawingView?.emptyColor = .black
|
self.stickerMaskDrawingView?.emptyColor = .black
|
||||||
self.stickerMaskDrawingView?.clearWithEmptyColor()
|
self.stickerMaskDrawingView?.clearWithEmptyColor()
|
||||||
}
|
}
|
||||||
|
|
||||||
switch effectiveSubject {
|
switch subject {
|
||||||
case .message, .gift:
|
case .message, .gift:
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
self.readyValue.set(.single(true))
|
self.readyValue.set(.single(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
switch effectiveSubject {
|
switch subject {
|
||||||
case let .image(_, _, additionalImage, position):
|
case let .image(_, _, additionalImage, position):
|
||||||
if let additionalImage {
|
if let additionalImage {
|
||||||
let image = generateImage(CGSize(width: additionalImage.size.width, height: additionalImage.size.width), contextGenerator: { size, context in
|
let image = generateImage(CGSize(width: additionalImage.size.width, height: additionalImage.size.width), contextGenerator: { size, context in
|
||||||
@ -3431,7 +3592,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
case .message, .gift:
|
case .message, .gift:
|
||||||
var isGift = false
|
var isGift = false
|
||||||
let messages: Signal<[Message], NoError>
|
let messages: Signal<[Message], NoError>
|
||||||
if case let .message(messageIds) = effectiveSubject {
|
if case let .message(messageIds) = subject {
|
||||||
messages = self.context.engine.data.get(
|
messages = self.context.engine.data.get(
|
||||||
EngineDataMap(messageIds.map(TelegramEngine.EngineData.Item.Messages.Message.init(id:)))
|
EngineDataMap(messageIds.map(TelegramEngine.EngineData.Item.Messages.Message.init(id:)))
|
||||||
)
|
)
|
||||||
@ -3444,7 +3605,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
}
|
}
|
||||||
return messages
|
return messages
|
||||||
}
|
}
|
||||||
} else if case let .gift(gift) = effectiveSubject {
|
} else if case let .gift(gift) = subject {
|
||||||
isGift = true
|
isGift = true
|
||||||
let media: [Media] = [TelegramMediaAction(action: .starGiftUnique(gift: .unique(gift), isUpgrade: false, isTransferred: false, savedToProfile: false, canExportDate: nil, transferStars: nil, isRefunded: false, peerId: nil, senderId: nil, savedId: nil, resaleStars: nil))]
|
let media: [Media] = [TelegramMediaAction(action: .starGiftUnique(gift: .unique(gift), isUpgrade: false, isTransferred: false, savedToProfile: false, canExportDate: nil, transferStars: nil, isRefunded: false, peerId: nil, senderId: nil, savedId: nil, resaleStars: nil))]
|
||||||
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: self.context.account.peerId, namespace: Namespaces.Message.Cloud, id: -1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: media, peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
|
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: self.context.account.peerId, namespace: Namespaces.Message.Cloud, id: -1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: media, peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
|
||||||
@ -3468,7 +3629,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
}
|
}
|
||||||
|
|
||||||
let wallpaperColors: Signal<(UIColor?, UIColor?), NoError>
|
let wallpaperColors: Signal<(UIColor?, UIColor?), NoError>
|
||||||
if let subject = self.subject, case .gift = subject {
|
if case .gift = subject {
|
||||||
wallpaperColors = self.mediaEditorPromise.get()
|
wallpaperColors = self.mediaEditorPromise.get()
|
||||||
|> mapToSignal { mediaEditor in
|
|> mapToSignal { mediaEditor in
|
||||||
if let mediaEditor {
|
if let mediaEditor {
|
||||||
@ -3498,7 +3659,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
}
|
}
|
||||||
let renderer = DrawingMessageRenderer(context: self.context, messages: messages, parentView: self.view, isGift: isGift, wallpaperDayColor: wallpaperColors.0, wallpaperNightColor: wallpaperColors.1)
|
let renderer = DrawingMessageRenderer(context: self.context, messages: messages, parentView: self.view, isGift: isGift, wallpaperDayColor: wallpaperColors.0, wallpaperNightColor: wallpaperColors.1)
|
||||||
renderer.render(completion: { result in
|
renderer.render(completion: { result in
|
||||||
if case .draft = subject, let existingEntityView = self.entitiesView.getView(where: { entityView in
|
if isDraft, let existingEntityView = self.entitiesView.getView(where: { entityView in
|
||||||
if let stickerEntityView = entityView as? DrawingStickerEntityView {
|
if let stickerEntityView = entityView as? DrawingStickerEntityView {
|
||||||
if case .message = (stickerEntityView.entity as! DrawingStickerEntity).content {
|
if case .message = (stickerEntityView.entity as! DrawingStickerEntity).content {
|
||||||
return true
|
return true
|
||||||
@ -3508,13 +3669,6 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}) as? DrawingStickerEntityView {
|
}) as? DrawingStickerEntityView {
|
||||||
#if DEBUG
|
|
||||||
if let data = result.dayImage.pngData() {
|
|
||||||
let path = NSTemporaryDirectory() + "\(Int(Date().timeIntervalSince1970)).png"
|
|
||||||
try? data.write(to: URL(fileURLWithPath: path))
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
existingEntityView.isNightTheme = isNightTheme
|
existingEntityView.isNightTheme = isNightTheme
|
||||||
let messageEntity = existingEntityView.entity as! DrawingStickerEntity
|
let messageEntity = existingEntityView.entity as! DrawingStickerEntity
|
||||||
messageEntity.renderImage = result.dayImage
|
messageEntity.renderImage = result.dayImage
|
||||||
@ -3524,7 +3678,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
} else {
|
} else {
|
||||||
var content: DrawingStickerEntity.Content
|
var content: DrawingStickerEntity.Content
|
||||||
var position: CGPoint
|
var position: CGPoint
|
||||||
switch effectiveSubject {
|
switch subject {
|
||||||
case let .message(messageIds):
|
case let .message(messageIds):
|
||||||
content = .message(messageIds, result.size, messageFile, result.mediaFrame?.rect, result.mediaFrame?.cornerRadius)
|
content = .message(messageIds, result.size, messageFile, result.mediaFrame?.rect, result.mediaFrame?.cornerRadius)
|
||||||
position = CGPoint(x: storyDimensions.width / 2.0 - 54.0, y: storyDimensions.height / 2.0)
|
position = CGPoint(x: storyDimensions.width / 2.0 - 54.0, y: storyDimensions.height / 2.0)
|
||||||
@ -3579,7 +3733,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
self.backgroundDimView.isHidden = false
|
self.backgroundDimView.isHidden = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else if CACurrentMediaTime() - self.initializationTimestamp > 0.2, case .image = subject {
|
} else if CACurrentMediaTime() - self.initializationTimestamp > 0.2, case .image = subject {
|
||||||
self.previewContainerView.alpha = 1.0
|
self.previewContainerView.alpha = 1.0
|
||||||
self.previewContainerView.layer.allowsGroupOpacity = true
|
self.previewContainerView.layer.allowsGroupOpacity = true
|
||||||
self.previewContainerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { _ in
|
self.previewContainerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { _ in
|
||||||
@ -3608,7 +3762,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if effectiveSubject.isPhoto {
|
if subject.isPhoto {
|
||||||
self.previewContainerView.layer.allowsGroupOpacity = true
|
self.previewContainerView.layer.allowsGroupOpacity = true
|
||||||
self.previewContainerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { _ in
|
self.previewContainerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { _ in
|
||||||
self.previewContainerView.layer.allowsGroupOpacity = false
|
self.previewContainerView.layer.allowsGroupOpacity = false
|
||||||
@ -3621,6 +3775,12 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if let caption {
|
||||||
|
mediaEditor.onFirstDisplay = { [weak self] in
|
||||||
|
self?.componentHostView?.setInputText(caption)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaEditor.onPlaybackAction = { [weak self] action in
|
mediaEditor.onPlaybackAction = { [weak self] action in
|
||||||
@ -3815,7 +3975,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
let imageSize = CGSize(width: 1080, height: 1920)
|
let imageSize = storyDimensions
|
||||||
if let context = DrawingContext(size: imageSize, scale: 1.0, opaque: true, colorSpace: colorSpace) {
|
if let context = DrawingContext(size: imageSize, scale: 1.0, opaque: true, colorSpace: colorSpace) {
|
||||||
context.withFlippedContext { context in
|
context.withFlippedContext { context in
|
||||||
if let image = mediaEditor.resultImage?.cgImage {
|
if let image = mediaEditor.resultImage?.cgImage {
|
||||||
@ -4214,9 +4374,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
completion()
|
completion()
|
||||||
case .camera:
|
case .camera:
|
||||||
if let view = self.componentHost.view as? MediaEditorScreenComponent.View {
|
self.componentHostView?.animateIn(from: .camera, completion: completion)
|
||||||
view.animateIn(from: .camera, completion: completion)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let subject = self.subject, case let .video(_, mainTransitionImage, _, _, additionalTransitionImage, _, _, positionChangeTimestamps, pipPosition) = subject, let mainTransitionImage {
|
if let subject = self.subject, case let .video(_, mainTransitionImage, _, _, additionalTransitionImage, _, _, positionChangeTimestamps, pipPosition) = subject, let mainTransitionImage {
|
||||||
var transitionImage = mainTransitionImage
|
var transitionImage = mainTransitionImage
|
||||||
@ -4227,7 +4385,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
backgroundImage = additionalTransitionImage
|
backgroundImage = additionalTransitionImage
|
||||||
foregroundImage = mainTransitionImage
|
foregroundImage = mainTransitionImage
|
||||||
}
|
}
|
||||||
if let combinedTransitionImage = generateImage(CGSize(width: 1080, height: 1920), scale: 1.0, rotatedContext: { size, context in
|
if let combinedTransitionImage = generateImage(storyDimensions, scale: 1.0, rotatedContext: { size, context in
|
||||||
UIGraphicsPushContext(context)
|
UIGraphicsPushContext(context)
|
||||||
backgroundImage.draw(in: CGRect(origin: CGPoint(x: (size.width - backgroundImage.size.width) / 2.0, y: (size.height - backgroundImage.size.height) / 2.0), size: backgroundImage.size))
|
backgroundImage.draw(in: CGRect(origin: CGPoint(x: (size.width - backgroundImage.size.width) / 2.0, y: (size.height - backgroundImage.size.height) / 2.0), size: backgroundImage.size))
|
||||||
|
|
||||||
@ -4253,9 +4411,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
self.setupTransitionImage(sourceImage)
|
self.setupTransitionImage(sourceImage)
|
||||||
}
|
}
|
||||||
if let sourceView = transitionIn.sourceView {
|
if let sourceView = transitionIn.sourceView {
|
||||||
if let view = self.componentHost.view as? MediaEditorScreenComponent.View {
|
self.componentHostView?.animateIn(from: .gallery)
|
||||||
view.animateIn(from: .gallery)
|
|
||||||
}
|
|
||||||
|
|
||||||
let sourceLocalFrame = sourceView.convert(transitionIn.sourceRect, to: self.view)
|
let sourceLocalFrame = sourceView.convert(transitionIn.sourceRect, to: self.view)
|
||||||
let sourceScale = sourceLocalFrame.width / self.previewContainerView.frame.width
|
let sourceScale = sourceLocalFrame.width / self.previewContainerView.frame.width
|
||||||
@ -4273,7 +4429,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
self.backgroundDimView.isHidden = false
|
self.backgroundDimView.isHidden = false
|
||||||
self.backgroundDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.35)
|
self.backgroundDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.35)
|
||||||
|
|
||||||
if let componentView = self.componentHost.view {
|
if let componentView = self.componentHostView {
|
||||||
componentView.layer.animatePosition(from: sourceLocalFrame.center, to: componentView.center, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
|
componentView.layer.animatePosition(from: sourceLocalFrame.center, to: componentView.center, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
componentView.layer.animateScale(from: sourceScale, to: 1.0, duration: duration, timingFunction: timingFunction)
|
componentView.layer.animateScale(from: sourceScale, to: 1.0, duration: duration, timingFunction: timingFunction)
|
||||||
componentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
componentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
@ -4293,8 +4449,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
if animateIn, let layout = self.validLayout {
|
if animateIn, let layout = self.validLayout {
|
||||||
self.layer.animatePosition(from: CGPoint(x: 0.0, y: layout.size.height), to: .zero, duration: 0.35, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
self.layer.animatePosition(from: CGPoint(x: 0.0, y: layout.size.height), to: .zero, duration: 0.35, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||||
completion()
|
completion()
|
||||||
} else if let view = self.componentHost.view as? MediaEditorScreenComponent.View {
|
} else {
|
||||||
view.animateIn(from: .camera, completion: completion)
|
self.componentHostView?.animateIn(from: .camera, completion: completion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4346,9 +4502,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
destinationTransitionView = destinationTransitionOutView
|
destinationTransitionView = destinationTransitionOutView
|
||||||
destinationTransitionRect = galleryTransitionIn.sourceRect
|
destinationTransitionRect = galleryTransitionIn.sourceRect
|
||||||
}
|
}
|
||||||
if let view = self.componentHost.view as? MediaEditorScreenComponent.View {
|
self.componentHostView?.animateOut(to: .gallery)
|
||||||
view.animateOut(to: .gallery)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
let destinationLocalFrame = destinationView.convert(transitionOut.destinationRect, to: self.view)
|
let destinationLocalFrame = destinationView.convert(transitionOut.destinationRect, to: self.view)
|
||||||
let destinationScale = destinationLocalFrame.width / self.previewContainerView.frame.width
|
let destinationScale = destinationLocalFrame.width / self.previewContainerView.frame.width
|
||||||
@ -4446,7 +4600,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
removeOnCompletion: false
|
removeOnCompletion: false
|
||||||
)
|
)
|
||||||
|
|
||||||
if let componentView = self.componentHost.view {
|
if let componentView = self.componentHostView {
|
||||||
componentView.clipsToBounds = true
|
componentView.clipsToBounds = true
|
||||||
componentView.layer.animatePosition(from: componentView.center, to: destinationLocalFrame.center, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
componentView.layer.animatePosition(from: componentView.center, to: destinationLocalFrame.center, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||||
componentView.layer.animateScale(from: 1.0, to: destinationScale, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
componentView.layer.animateScale(from: 1.0, to: destinationScale, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||||
@ -4464,18 +4618,14 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let transitionIn = controller.transitionIn, case .camera = transitionIn {
|
} else if let transitionIn = controller.transitionIn, case .camera = transitionIn {
|
||||||
if let view = self.componentHost.view as? MediaEditorScreenComponent.View {
|
self.componentHostView?.animateOut(to: .camera)
|
||||||
view.animateOut(to: .camera)
|
|
||||||
}
|
|
||||||
let transition = ComponentTransition(animation: .curve(duration: 0.25, curve: .easeInOut))
|
let transition = ComponentTransition(animation: .curve(duration: 0.25, curve: .easeInOut))
|
||||||
transition.setAlpha(view: self.previewContainerView, alpha: 0.0, completion: { _ in
|
transition.setAlpha(view: self.previewContainerView, alpha: 0.0, completion: { _ in
|
||||||
completion()
|
completion()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
if controller.isEmbeddedEditor {
|
if controller.isEmbeddedEditor {
|
||||||
if let view = self.componentHost.view as? MediaEditorScreenComponent.View {
|
self.componentHostView?.animateOut(to: .gallery)
|
||||||
view.animateOut(to: .gallery)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.layer.allowsGroupOpacity = true
|
self.layer.allowsGroupOpacity = true
|
||||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.4, removeOnCompletion: false, completion: { _ in
|
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.4, removeOnCompletion: false, completion: { _ in
|
||||||
@ -4495,9 +4645,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
self.isDisplayingTool = tool
|
self.isDisplayingTool = tool
|
||||||
|
|
||||||
let transition: ComponentTransition = .easeInOut(duration: 0.2)
|
let transition: ComponentTransition = .easeInOut(duration: 0.2)
|
||||||
if let view = self.componentHost.view as? MediaEditorScreenComponent.View {
|
self.componentHostView?.animateOutToTool(inPlace: inPlace, transition: transition)
|
||||||
view.animateOutToTool(inPlace: inPlace, transition: transition)
|
|
||||||
}
|
|
||||||
self.requestUpdate(transition: transition)
|
self.requestUpdate(transition: transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4505,9 +4653,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
self.isDisplayingTool = nil
|
self.isDisplayingTool = nil
|
||||||
|
|
||||||
let transition: ComponentTransition = .easeInOut(duration: 0.2)
|
let transition: ComponentTransition = .easeInOut(duration: 0.2)
|
||||||
if let view = self.componentHost.view as? MediaEditorScreenComponent.View {
|
self.componentHostView?.animateInFromTool(inPlace: inPlace, transition: transition)
|
||||||
view.animateInFromTool(inPlace: inPlace, transition: transition)
|
|
||||||
}
|
|
||||||
self.requestUpdate(transition: transition)
|
self.requestUpdate(transition: transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4721,12 +4867,10 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
}
|
}
|
||||||
|
|
||||||
var location: CLLocationCoordinate2D?
|
var location: CLLocationCoordinate2D?
|
||||||
if let subject = self.actualSubject {
|
if case let .draft(draft, _) = self.actualSubject {
|
||||||
if case let .asset(asset) = subject {
|
location = draft.location
|
||||||
location = asset.location?.coordinate
|
} else if case let .asset(asset) = self.subject {
|
||||||
} else if case let .draft(draft, _) = subject {
|
location = asset.location?.coordinate
|
||||||
location = draft.location
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
let locationController = storyLocationPickerController(
|
let locationController = storyLocationPickerController(
|
||||||
context: self.context,
|
context: self.context,
|
||||||
@ -5195,7 +5339,6 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
|
|
||||||
func addWeather(_ weather: StickerPickerScreen.Weather.LoadedWeather?) {
|
func addWeather(_ weather: StickerPickerScreen.Weather.LoadedWeather?) {
|
||||||
guard let weather else {
|
guard let weather else {
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let maxWeatherCount = 1
|
let maxWeatherCount = 1
|
||||||
@ -5222,6 +5365,58 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getCaption() -> NSAttributedString {
|
||||||
|
return self.componentHostView?.getInputText() ?? NSAttributedString()
|
||||||
|
}
|
||||||
|
|
||||||
|
func switchToItem(_ identifier: String) {
|
||||||
|
guard let controller = self.controller, let mediaEditor = self.mediaEditor, let itemIndex = self.items.firstIndex(where: { $0.asset.localIdentifier == identifier }), case let .asset(asset) = self.subject, let currentItemIndex = self.items.firstIndex(where: { $0.asset.localIdentifier == asset.localIdentifier }) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let entities = self.entitiesView.entities.filter { !($0 is DrawingMediaEntity) }
|
||||||
|
let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.entitiesView)
|
||||||
|
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
|
||||||
|
|
||||||
|
var updatedCurrentItem = self.items[currentItemIndex]
|
||||||
|
updatedCurrentItem.caption = self.getCaption()
|
||||||
|
|
||||||
|
if mediaEditor.values.hasChanges && updatedCurrentItem.values != mediaEditor.values {
|
||||||
|
updatedCurrentItem.values = mediaEditor.values
|
||||||
|
updatedCurrentItem.version += 1
|
||||||
|
|
||||||
|
if let resultImage = mediaEditor.resultImage {
|
||||||
|
mediaEditor.seek(0.0, andPlay: false)
|
||||||
|
makeEditorImageComposition(
|
||||||
|
context: self.ciContext,
|
||||||
|
postbox: self.context.account.postbox,
|
||||||
|
inputImage: resultImage,
|
||||||
|
dimensions: storyDimensions,
|
||||||
|
values: mediaEditor.values,
|
||||||
|
time: .zero,
|
||||||
|
textScale: 2.0,
|
||||||
|
completion: { [weak self] resultImage in
|
||||||
|
updatedCurrentItem.version += 1
|
||||||
|
updatedCurrentItem.thumbnail = resultImage
|
||||||
|
self?.items[currentItemIndex] = updatedCurrentItem
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updatedCurrentItem.version += 1
|
||||||
|
self.items[currentItemIndex] = updatedCurrentItem
|
||||||
|
}
|
||||||
|
|
||||||
|
self.entitiesView.clearAll()
|
||||||
|
|
||||||
|
let targetItem = self.items[itemIndex]
|
||||||
|
controller.node.setup(
|
||||||
|
subject: .asset(targetItem.asset),
|
||||||
|
values: targetItem.values,
|
||||||
|
caption: targetItem.caption
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func requestCompletion(playHaptic: Bool = true) {
|
func requestCompletion(playHaptic: Bool = true) {
|
||||||
guard let controller = self.controller else {
|
guard let controller = self.controller else {
|
||||||
return
|
return
|
||||||
@ -5323,7 +5518,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
|
|
||||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
let result = super.hitTest(point, with: event)
|
let result = super.hitTest(point, with: event)
|
||||||
if result == self.componentHost.view {
|
if result == self.componentHostView {
|
||||||
let point = self.view.convert(point, to: self.previewContainerView)
|
let point = self.view.convert(point, to: self.previewContainerView)
|
||||||
if let previewResult = self.previewContainerView.hitTest(point, with: event) {
|
if let previewResult = self.previewContainerView.hitTest(point, with: event) {
|
||||||
return previewResult
|
return previewResult
|
||||||
@ -6181,6 +6376,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
case message([MessageId])
|
case message([MessageId])
|
||||||
case gift(StarGift.UniqueGift)
|
case gift(StarGift.UniqueGift)
|
||||||
case sticker(TelegramMediaFile, [String])
|
case sticker(TelegramMediaFile, [String])
|
||||||
|
case assets([PHAsset])
|
||||||
|
|
||||||
var dimensions: PixelDimensions {
|
var dimensions: PixelDimensions {
|
||||||
switch self {
|
switch self {
|
||||||
@ -6192,8 +6388,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
return PixelDimensions(width: Int32(asset.pixelWidth), height: Int32(asset.pixelHeight))
|
return PixelDimensions(width: Int32(asset.pixelWidth), height: Int32(asset.pixelHeight))
|
||||||
case let .draft(draft, _):
|
case let .draft(draft, _):
|
||||||
return draft.dimensions
|
return draft.dimensions
|
||||||
case .message, .gift, .sticker, .videoCollage:
|
case .message, .gift, .sticker, .videoCollage, .assets:
|
||||||
return PixelDimensions(width: 1080, height: 1920)
|
return PixelDimensions(storyDimensions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -6220,6 +6416,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
return .gift(gift)
|
return .gift(gift)
|
||||||
case let .sticker(sticker, _):
|
case let .sticker(sticker, _):
|
||||||
return .sticker(sticker)
|
return .sticker(sticker)
|
||||||
|
case let .assets(assets):
|
||||||
|
return .asset(assets.first!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -6247,6 +6445,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
return false
|
return false
|
||||||
case .sticker:
|
case .sticker:
|
||||||
return false
|
return false
|
||||||
|
case .assets:
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -6555,7 +6755,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
|
|
||||||
let privacy = privacy ?? self.state.privacy
|
let privacy = privacy ?? self.state.privacy
|
||||||
|
|
||||||
let text = self.getCaption().string
|
let text = self.node.getCaption().string
|
||||||
let mentions = generateTextEntities(text, enabledTypes: [.mention], currentEntities: []).map { (text as NSString).substring(with: NSRange(location: $0.range.lowerBound + 1, length: $0.range.upperBound - $0.range.lowerBound - 1)) }
|
let mentions = generateTextEntities(text, enabledTypes: [.mention], currentEntities: []).map { (text as NSString).substring(with: NSRange(location: $0.range.lowerBound + 1, length: $0.range.upperBound - $0.range.lowerBound - 1)) }
|
||||||
|
|
||||||
let coverImage: UIImage?
|
let coverImage: UIImage?
|
||||||
@ -6567,7 +6767,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
|
|
||||||
let stateContext = ShareWithPeersScreen.StateContext(
|
let stateContext = ShareWithPeersScreen.StateContext(
|
||||||
context: self.context,
|
context: self.context,
|
||||||
subject: .stories(editing: false),
|
subject: .stories(editing: false, count: Int32(self.node.items.count(where: { $0.isEnabled }))),
|
||||||
editing: false,
|
editing: false,
|
||||||
initialPeerIds: Set(privacy.privacy.additionallyIncludePeers),
|
initialPeerIds: Set(privacy.privacy.additionallyIncludePeers),
|
||||||
closeFriends: self.closeFriends.get(),
|
closeFriends: self.closeFriends.get(),
|
||||||
@ -7105,13 +7305,9 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
self?.dismissed()
|
self?.dismissed()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCaption() -> NSAttributedString {
|
|
||||||
return (self.node.componentHost.view as? MediaEditorScreenComponent.View)?.getInputText() ?? NSAttributedString()
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate func checkCaptionLimit() -> Bool {
|
fileprivate func checkCaptionLimit() -> Bool {
|
||||||
let caption = self.getCaption()
|
let caption = self.node.getCaption()
|
||||||
if caption.length > self.context.userLimits.maxStoryCaptionLength {
|
if caption.length > self.context.userLimits.maxStoryCaptionLength {
|
||||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||||
@ -7160,7 +7356,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView)
|
let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView)
|
||||||
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
|
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
|
||||||
|
|
||||||
var caption = self.getCaption()
|
var caption = self.node.getCaption()
|
||||||
caption = convertMarkdownToAttributes(caption)
|
caption = convertMarkdownToAttributes(caption)
|
||||||
|
|
||||||
var hasEntityChanges = false
|
var hasEntityChanges = false
|
||||||
@ -7209,7 +7405,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.isEmbeddedEditor && !(hasAnyChanges || hasEntityChanges) {
|
if self.isEmbeddedEditor && !(hasAnyChanges || hasEntityChanges) {
|
||||||
self.saveDraft(id: randomId, edit: true)
|
self.saveDraft(id: randomId, isEdit: true)
|
||||||
|
|
||||||
self.completion(MediaEditorScreenImpl.Result(media: nil, mediaAreas: [], caption: caption, coverTimestamp: mediaEditor.values.coverImageTimestamp, options: self.state.privacy, stickers: stickers, randomId: randomId), { [weak self] finished in
|
self.completion(MediaEditorScreenImpl.Result(media: nil, 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?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
||||||
@ -7498,7 +7694,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
}
|
}
|
||||||
duration = 5.0
|
duration = 5.0
|
||||||
case .sticker:
|
case .sticker:
|
||||||
let image = generateImage(CGSize(width: 1080, height: 1920), contextGenerator: { size, context in
|
let image = generateImage(storyDimensions, contextGenerator: { size, context in
|
||||||
context.clear(CGRect(origin: .zero, size: size))
|
context.clear(CGRect(origin: .zero, size: size))
|
||||||
}, opaque: false, scale: 1.0)
|
}, opaque: false, scale: 1.0)
|
||||||
let tempImagePath = NSTemporaryDirectory() + "\(Int64.random(in: Int64.min ... Int64.max)).png"
|
let tempImagePath = NSTemporaryDirectory() + "\(Int64.random(in: Int64.min ... Int64.max)).png"
|
||||||
@ -7509,6 +7705,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
duration = 3.0
|
duration = 3.0
|
||||||
|
|
||||||
firstFrame = .single((image, nil))
|
firstFrame = .single((image, nil))
|
||||||
|
case .assets:
|
||||||
|
fatalError()
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = combineLatest(queue: Queue.mainQueue(), firstFrame, videoResult)
|
let _ = combineLatest(queue: Queue.mainQueue(), firstFrame, videoResult)
|
||||||
@ -8399,6 +8597,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
}
|
}
|
||||||
case let .sticker(file, _):
|
case let .sticker(file, _):
|
||||||
exportSubject = .single(.sticker(file: file))
|
exportSubject = .single(.sticker(file: file))
|
||||||
|
case .assets:
|
||||||
|
fatalError()
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = (exportSubject
|
let _ = (exportSubject
|
||||||
|
@ -11,7 +11,16 @@ import DrawingUI
|
|||||||
|
|
||||||
extension MediaEditorScreenImpl {
|
extension MediaEditorScreenImpl {
|
||||||
func isEligibleForDraft() -> Bool {
|
func isEligibleForDraft() -> Bool {
|
||||||
if self.isEditingStory {
|
guard !self.isEditingStory else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if case .avatarEditor = self.mode {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if case .coverEditor = self.mode {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if case .assets = self.node.actualSubject {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
guard let mediaEditor = self.node.mediaEditor else {
|
guard let mediaEditor = self.node.mediaEditor else {
|
||||||
@ -21,13 +30,6 @@ extension MediaEditorScreenImpl {
|
|||||||
let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView)
|
let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView)
|
||||||
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
|
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
|
||||||
|
|
||||||
if case .avatarEditor = self.mode {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if case .coverEditor = self.mode {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
let filteredEntities = self.node.entitiesView.entities.filter { entity in
|
let filteredEntities = self.node.entitiesView.entities.filter { entity in
|
||||||
if entity is DrawingMediaEntity {
|
if entity is DrawingMediaEntity {
|
||||||
return false
|
return false
|
||||||
@ -44,34 +46,42 @@ extension MediaEditorScreenImpl {
|
|||||||
|
|
||||||
let values = mediaEditor.values
|
let values = mediaEditor.values
|
||||||
let filteredValues = values.withUpdatedEntities([])
|
let filteredValues = values.withUpdatedEntities([])
|
||||||
|
let caption = self.node.getCaption()
|
||||||
|
|
||||||
let caption = self.getCaption()
|
|
||||||
if let subject = self.node.subject {
|
if let subject = self.node.subject {
|
||||||
if case .asset = subject, !values.hasChanges && caption.string.isEmpty {
|
switch subject {
|
||||||
return false
|
case .asset:
|
||||||
} else if case .message = subject, !filteredValues.hasChanges && filteredEntities.isEmpty && caption.string.isEmpty {
|
if !values.hasChanges && caption.string.isEmpty {
|
||||||
return false
|
return false
|
||||||
} else if case .gift = subject, !filteredValues.hasChanges && filteredEntities.isEmpty && caption.string.isEmpty {
|
}
|
||||||
return false
|
case .message, .gift:
|
||||||
} else if case .empty = subject, !self.node.hasAnyChanges && !self.node.drawingView.internalState.canUndo {
|
if !filteredValues.hasChanges && filteredEntities.isEmpty && caption.string.isEmpty {
|
||||||
return false
|
return false
|
||||||
} else if case .videoCollage = subject {
|
}
|
||||||
|
case .empty:
|
||||||
|
if !self.node.hasAnyChanges && !self.node.drawingView.internalState.canUndo {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case .videoCollage:
|
||||||
return false
|
return false
|
||||||
|
default:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveDraft(id: Int64?, edit: Bool = false) {
|
func saveDraft(id: Int64?, isEdit: Bool = false, completion: ((MediaEditorDraft) -> Void)? = nil) {
|
||||||
guard case .storyEditor = self.mode, let subject = self.node.subject, let actualSubject = self.node.actualSubject, let mediaEditor = self.node.mediaEditor else {
|
guard case .storyEditor = self.mode, let subject = self.node.subject, let actualSubject = self.node.actualSubject, let mediaEditor = self.node.mediaEditor else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try? FileManager.default.createDirectory(atPath: draftPath(engine: self.context.engine), withIntermediateDirectories: true)
|
try? FileManager.default.createDirectory(atPath: draftPath(engine: self.context.engine), withIntermediateDirectories: true)
|
||||||
|
|
||||||
let values = mediaEditor.values
|
let values = mediaEditor.values
|
||||||
let privacy = self.state.privacy
|
let privacy = self.state.privacy
|
||||||
let forwardSource = self.forwardSource
|
let forwardSource = self.forwardSource
|
||||||
let caption = self.getCaption()
|
let caption = self.node.getCaption()
|
||||||
let duration = mediaEditor.duration ?? 0.0
|
let duration = mediaEditor.duration ?? 0.0
|
||||||
|
|
||||||
let currentTimestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
let currentTimestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||||
@ -99,10 +109,19 @@ extension MediaEditorScreenImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let resultImage = mediaEditor.resultImage {
|
if let resultImage = mediaEditor.resultImage {
|
||||||
if !edit {
|
if !isEdit {
|
||||||
mediaEditor.seek(0.0, andPlay: false)
|
mediaEditor.seek(0.0, andPlay: false)
|
||||||
}
|
}
|
||||||
makeEditorImageComposition(context: self.node.ciContext, postbox: self.context.account.postbox, inputImage: resultImage, dimensions: storyDimensions, values: values, time: .zero, textScale: 2.0, completion: { resultImage in
|
|
||||||
|
makeEditorImageComposition(
|
||||||
|
context: self.node.ciContext,
|
||||||
|
postbox: self.context.account.postbox,
|
||||||
|
inputImage: resultImage,
|
||||||
|
dimensions: storyDimensions,
|
||||||
|
values: values,
|
||||||
|
time: .zero,
|
||||||
|
textScale: 2.0,
|
||||||
|
completion: { resultImage in
|
||||||
guard let resultImage else {
|
guard let resultImage else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -148,54 +167,64 @@ extension MediaEditorScreenImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let context = self.context
|
let context = self.context
|
||||||
func innerSaveDraft(media: MediaInput) {
|
func innerSaveDraft(media: MediaInput, save: Bool = true) -> MediaEditorDraft? {
|
||||||
let fittedSize = resultImage.size.aspectFitted(CGSize(width: 128.0, height: 128.0))
|
let fittedSize = resultImage.size.aspectFitted(CGSize(width: 128.0, height: 128.0))
|
||||||
if let thumbnailImage = generateScaledImage(image: resultImage, size: fittedSize) {
|
guard let thumbnailImage = generateScaledImage(image: resultImage, size: fittedSize) else {
|
||||||
let path = "\(Int64.random(in: .min ... .max)).\(media.fileExtension)"
|
return nil
|
||||||
let draft = MediaEditorDraft(
|
}
|
||||||
path: path,
|
let path = "\(Int64.random(in: .min ... .max)).\(media.fileExtension)"
|
||||||
isVideo: media.isVideo,
|
let draft = MediaEditorDraft(
|
||||||
thumbnail: thumbnailImage,
|
path: path,
|
||||||
dimensions: media.dimensions,
|
isVideo: media.isVideo,
|
||||||
duration: media.duration,
|
thumbnail: thumbnailImage,
|
||||||
values: values,
|
dimensions: media.dimensions,
|
||||||
caption: caption,
|
duration: media.duration,
|
||||||
privacy: privacy,
|
values: values,
|
||||||
forwardInfo: forwardSource.flatMap { StoryId(peerId: $0.0.id, id: $0.1.id) },
|
caption: caption,
|
||||||
timestamp: timestamp,
|
privacy: privacy,
|
||||||
location: location,
|
forwardInfo: forwardSource.flatMap { StoryId(peerId: $0.0.id, id: $0.1.id) },
|
||||||
expiresOn: expiresOn
|
timestamp: timestamp,
|
||||||
)
|
location: location,
|
||||||
switch media {
|
expiresOn: expiresOn
|
||||||
case let .image(image, _):
|
)
|
||||||
if let data = image.jpegData(compressionQuality: 0.87) {
|
switch media {
|
||||||
try? data.write(to: URL(fileURLWithPath: draft.fullPath(engine: context.engine)))
|
case let .image(image, _):
|
||||||
}
|
if let data = image.jpegData(compressionQuality: 0.87) {
|
||||||
case let .video(path, _, _):
|
try? data.write(to: URL(fileURLWithPath: draft.fullPath(engine: context.engine)))
|
||||||
try? FileManager.default.copyItem(atPath: path, toPath: draft.fullPath(engine: context.engine))
|
|
||||||
}
|
}
|
||||||
|
case let .video(path, _, _):
|
||||||
|
try? FileManager.default.copyItem(atPath: path, toPath: draft.fullPath(engine: context.engine))
|
||||||
|
}
|
||||||
|
if save {
|
||||||
if let id {
|
if let id {
|
||||||
saveStorySource(engine: context.engine, item: draft, peerId: context.account.peerId, id: id)
|
saveStorySource(engine: context.engine, item: draft, peerId: context.account.peerId, id: id)
|
||||||
} else {
|
} else {
|
||||||
addStoryDraft(engine: context.engine, item: draft)
|
addStoryDraft(engine: context.engine, item: draft)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return draft
|
||||||
}
|
}
|
||||||
|
|
||||||
switch subject {
|
switch subject {
|
||||||
case .empty:
|
case .empty:
|
||||||
break
|
break
|
||||||
case let .image(image, dimensions, _, _):
|
case let .image(image, dimensions, _, _):
|
||||||
innerSaveDraft(media: .image(image: image, dimensions: dimensions))
|
if let draft = innerSaveDraft(media: .image(image: image, dimensions: dimensions)) {
|
||||||
|
completion?(draft)
|
||||||
|
}
|
||||||
case let .video(path, _, _, _, _, dimensions, _, _, _):
|
case let .video(path, _, _, _, _, dimensions, _, _, _):
|
||||||
innerSaveDraft(media: .video(path: path, dimensions: dimensions, duration: duration))
|
if let draft = innerSaveDraft(media: .video(path: path, dimensions: dimensions, duration: duration)) {
|
||||||
|
completion?(draft)
|
||||||
|
}
|
||||||
case let .videoCollage(items):
|
case let .videoCollage(items):
|
||||||
let _ = items
|
let _ = items
|
||||||
case let .asset(asset):
|
case let .asset(asset):
|
||||||
if asset.mediaType == .video {
|
if asset.mediaType == .video {
|
||||||
PHImageManager.default().requestAVAsset(forVideo: asset, options: nil) { avAsset, _, _ in
|
PHImageManager.default().requestAVAsset(forVideo: asset, options: nil) { avAsset, _, _ in
|
||||||
if let urlAsset = avAsset as? AVURLAsset {
|
if let urlAsset = avAsset as? AVURLAsset {
|
||||||
innerSaveDraft(media: .video(path: urlAsset.url.relativePath, dimensions: PixelDimensions(width: Int32(asset.pixelWidth), height: Int32(asset.pixelHeight)), duration: duration))
|
if let draft = innerSaveDraft(media: .video(path: urlAsset.url.relativePath, dimensions: PixelDimensions(width: Int32(asset.pixelWidth), height: Int32(asset.pixelHeight)), duration: duration)) {
|
||||||
|
completion?(draft)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -203,22 +232,32 @@ extension MediaEditorScreenImpl {
|
|||||||
options.deliveryMode = .highQualityFormat
|
options.deliveryMode = .highQualityFormat
|
||||||
PHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .default, options: options) { image, _ in
|
PHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .default, options: options) { image, _ in
|
||||||
if let image {
|
if let image {
|
||||||
innerSaveDraft(media: .image(image: image, dimensions: PixelDimensions(image.size)))
|
if let draft = innerSaveDraft(media: .image(image: image, dimensions: PixelDimensions(image.size))) {
|
||||||
|
completion?(draft)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case let .draft(draft, _):
|
case let .draft(draft, _):
|
||||||
if draft.isVideo {
|
if draft.isVideo {
|
||||||
innerSaveDraft(media: .video(path: draft.fullPath(engine: context.engine), dimensions: draft.dimensions, duration: draft.duration ?? 0.0))
|
if let draft = innerSaveDraft(media: .video(path: draft.fullPath(engine: context.engine), dimensions: draft.dimensions, duration: draft.duration ?? 0.0)) {
|
||||||
|
completion?(draft)
|
||||||
|
}
|
||||||
} else if let image = UIImage(contentsOfFile: draft.fullPath(engine: context.engine)) {
|
} else if let image = UIImage(contentsOfFile: draft.fullPath(engine: context.engine)) {
|
||||||
innerSaveDraft(media: .image(image: image, dimensions: draft.dimensions))
|
if let draft = innerSaveDraft(media: .image(image: image, dimensions: draft.dimensions)) {
|
||||||
|
completion?(draft)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case .message, .gift:
|
case .message, .gift:
|
||||||
if let pixel = generateSingleColorImage(size: CGSize(width: 1, height: 1), color: .black) {
|
if let pixel = generateSingleColorImage(size: CGSize(width: 1, height: 1), color: .black) {
|
||||||
innerSaveDraft(media: .image(image: pixel, dimensions: PixelDimensions(width: 1080, height: 1920)))
|
if let draft = innerSaveDraft(media: .image(image: pixel, dimensions: PixelDimensions(width: 1080, height: 1920))) {
|
||||||
|
completion?(draft)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case .sticker:
|
case .sticker:
|
||||||
break
|
break
|
||||||
|
case .assets:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if case let .draft(draft, _) = actualSubject {
|
if case let .draft(draft, _) = actualSubject {
|
@ -34,7 +34,7 @@ final class SelectionPanelButtonContentComponent: Component {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private let backgroundView: BlurredBackgroundView
|
let backgroundView: BlurredBackgroundView
|
||||||
private let outline = SimpleLayer()
|
private let outline = SimpleLayer()
|
||||||
private let icon = SimpleLayer()
|
private let icon = SimpleLayer()
|
||||||
private let label = ComponentView<Empty>()
|
private let label = ComponentView<Empty>()
|
||||||
|
@ -1,7 +1,670 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
import Display
|
import Display
|
||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
|
import SwiftSignalKit
|
||||||
|
import AccountContext
|
||||||
|
import MediaEditor
|
||||||
|
import MediaAssetsContext
|
||||||
|
import CheckNode
|
||||||
|
import TelegramPresentationData
|
||||||
|
|
||||||
|
final class SelectionPanelComponent: Component {
|
||||||
|
let previewContainerView: PortalSourceView
|
||||||
|
let frame: CGRect
|
||||||
|
let items: [MediaEditorScreenImpl.EditingItem]
|
||||||
|
let selectedItemId: String
|
||||||
|
let itemTapped: (String?) -> Void
|
||||||
|
let itemSelectionToggled: (String) -> Void
|
||||||
|
let itemReordered: (String, String) -> Void
|
||||||
|
|
||||||
|
init(
|
||||||
|
previewContainerView: PortalSourceView,
|
||||||
|
frame: CGRect,
|
||||||
|
items: [MediaEditorScreenImpl.EditingItem],
|
||||||
|
selectedItemId: String,
|
||||||
|
itemTapped: @escaping (String?) -> Void,
|
||||||
|
itemSelectionToggled: @escaping (String) -> Void,
|
||||||
|
itemReordered: @escaping (String, String) -> Void
|
||||||
|
) {
|
||||||
|
self.previewContainerView = previewContainerView
|
||||||
|
self.frame = frame
|
||||||
|
self.items = items
|
||||||
|
self.selectedItemId = selectedItemId
|
||||||
|
self.itemTapped = itemTapped
|
||||||
|
self.itemSelectionToggled = itemSelectionToggled
|
||||||
|
self.itemReordered = itemReordered
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: SelectionPanelComponent, rhs: SelectionPanelComponent) -> Bool {
|
||||||
|
return lhs.frame == rhs.frame && lhs.items == rhs.items && lhs.selectedItemId == rhs.selectedItemId
|
||||||
|
}
|
||||||
|
|
||||||
|
final class View: UIView, UIGestureRecognizerDelegate {
|
||||||
|
final class ItemView: UIView {
|
||||||
|
private let backgroundNode: ASImageNode
|
||||||
|
private let imageNode: ImageNode
|
||||||
|
private let checkNode: InteractiveCheckNode
|
||||||
|
private var selectionLayer: SimpleShapeLayer?
|
||||||
|
|
||||||
|
var toggleSelection: () -> Void = {}
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
self.backgroundNode = ASImageNode()
|
||||||
|
self.backgroundNode.displaysAsynchronously = false
|
||||||
|
|
||||||
|
self.imageNode = ImageNode()
|
||||||
|
self.imageNode.contentMode = .scaleAspectFill
|
||||||
|
|
||||||
|
self.checkNode = InteractiveCheckNode(theme: CheckNodeTheme(theme: defaultDarkColorPresentationTheme, style: .overlay))
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.clipsToBounds = true
|
||||||
|
self.layer.cornerRadius = 6.0
|
||||||
|
|
||||||
|
self.addSubview(self.backgroundNode.view)
|
||||||
|
self.addSubview(self.imageNode.view)
|
||||||
|
self.addSubview(self.checkNode.view)
|
||||||
|
|
||||||
|
self.checkNode.valueChanged = { [weak self] value in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.toggleSelection()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
preconditionFailure()
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate var item: MediaEditorScreenImpl.EditingItem?
|
||||||
|
func update(item: MediaEditorScreenImpl.EditingItem, number: Int, isSelected: Bool, isEnabled: Bool, size: CGSize, portalView: PortalView?, transition: ComponentTransition) {
|
||||||
|
let previousItem = self.item
|
||||||
|
self.item = item
|
||||||
|
|
||||||
|
if previousItem?.asset.localIdentifier != item.asset.localIdentifier || previousItem?.version != item.version {
|
||||||
|
let imageSignal: Signal<UIImage?, NoError>
|
||||||
|
if let thumbnail = item.thumbnail {
|
||||||
|
imageSignal = .single(thumbnail)
|
||||||
|
self.imageNode.contentMode = .scaleAspectFill
|
||||||
|
} else {
|
||||||
|
imageSignal = assetImage(asset: item.asset, targetSize:CGSize(width: 128.0 * UIScreenScale, height: 128.0 * UIScreenScale), exact: false, synchronous: true)
|
||||||
|
self.imageNode.contentUpdated = { [weak self] image in
|
||||||
|
if let self {
|
||||||
|
if self.backgroundNode.image == nil {
|
||||||
|
if let image, image.size.width > image.size.height {
|
||||||
|
self.imageNode.contentMode = .scaleAspectFit
|
||||||
|
Queue.concurrentDefaultQueue().async {
|
||||||
|
let colors = mediaEditorGetGradientColors(from: image)
|
||||||
|
let gradientImage = mediaEditorGenerateGradientImage(size: CGSize(width: 3.0, height: 128.0), colors: colors.array)
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
self.backgroundNode.image = gradientImage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.imageNode.contentMode = .scaleAspectFill
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.imageNode.setSignal(imageSignal)
|
||||||
|
}
|
||||||
|
|
||||||
|
let backgroundSize = CGSize(width: size.width, height: floorToScreenPixels(size.width / 9.0 * 16.0))
|
||||||
|
self.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - backgroundSize.height) / 2.0)), size: backgroundSize)
|
||||||
|
|
||||||
|
self.imageNode.frame = CGRect(origin: .zero, size: size)
|
||||||
|
|
||||||
|
//self.checkNode.content = .counter(number)
|
||||||
|
self.checkNode.setSelected(isEnabled, animated: previousItem != nil)
|
||||||
|
|
||||||
|
let checkSize = CGSize(width: 29.0, height: 29.0)
|
||||||
|
self.checkNode.frame = CGRect(origin: CGPoint(x: size.width - checkSize.width - 4.0, y: 4.0), size: checkSize)
|
||||||
|
|
||||||
|
if isSelected, let portalView {
|
||||||
|
portalView.view.frame = CGRect(origin: .zero, size: size)
|
||||||
|
self.insertSubview(portalView.view, aboveSubview: self.imageNode.view)
|
||||||
|
}
|
||||||
|
|
||||||
|
let lineWidth: CGFloat = 2.0 - UIScreenPixel
|
||||||
|
let selectionFrame = CGRect(origin: .zero, size: size)
|
||||||
|
if isSelected {
|
||||||
|
let selectionLayer: SimpleShapeLayer
|
||||||
|
if let current = self.selectionLayer {
|
||||||
|
selectionLayer = current
|
||||||
|
} else {
|
||||||
|
selectionLayer = SimpleShapeLayer()
|
||||||
|
self.selectionLayer = selectionLayer
|
||||||
|
self.layer.addSublayer(selectionLayer)
|
||||||
|
|
||||||
|
selectionLayer.fillColor = UIColor.clear.cgColor
|
||||||
|
selectionLayer.strokeColor = UIColor.white.cgColor
|
||||||
|
selectionLayer.lineWidth = lineWidth
|
||||||
|
selectionLayer.frame = selectionFrame
|
||||||
|
selectionLayer.path = CGPath(roundedRect: CGRect(origin: .zero, size: selectionFrame.size).insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0), cornerWidth: 6.0, cornerHeight: 6.0, transform: nil)
|
||||||
|
|
||||||
|
// if !transition.animation.isImmediate {
|
||||||
|
// let initialPath = CGPath(roundedRect: CGRect(origin: .zero, size: selectionFrame.size).insetBy(dx: 0.0, dy: 0.0), cornerWidth: 6.0, cornerHeight: 6.0, transform: nil)
|
||||||
|
// selectionLayer.animate(from: initialPath, to: selectionLayer.path as AnyObject, keyPath: "path", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2)
|
||||||
|
// selectionLayer.animateShapeLineWidth(from: 0.0, to: lineWidth, duration: 0.2)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if let selectionLayer = self.selectionLayer {
|
||||||
|
self.selectionLayer = nil
|
||||||
|
selectionLayer.removeFromSuperlayer()
|
||||||
|
|
||||||
|
// let targetPath = CGPath(roundedRect: CGRect(origin: .zero, size: selectionFrame.size).insetBy(dx: 0.0, dy: 0.0), cornerWidth: 6.0, cornerHeight: 6.0, transform: nil)
|
||||||
|
// selectionLayer.animate(from: selectionLayer.path, to: targetPath, keyPath: "path", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2, removeOnCompletion: false)
|
||||||
|
// selectionLayer.animateShapeLineWidth(from: selectionLayer.lineWidth, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||||
|
// selectionLayer.removeFromSuperlayer()
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let backgroundView: BlurredBackgroundView
|
||||||
|
private let backgroundMaskView: UIView
|
||||||
|
private let backgroundMaskPanelView: UIView
|
||||||
|
|
||||||
|
private let scrollView: UIScrollView
|
||||||
|
private var itemViews: [AnyHashable: ItemView] = [:]
|
||||||
|
private var portalView: PortalView?
|
||||||
|
|
||||||
|
private var reorderRecognizer: ReorderGestureRecognizer?
|
||||||
|
private var reorderingItem: (id: AnyHashable, initialPosition: CGPoint, position: CGPoint, snapshotView: UIView)?
|
||||||
|
|
||||||
|
private var tapRecognizer: UITapGestureRecognizer?
|
||||||
|
|
||||||
|
private var component: SelectionPanelComponent?
|
||||||
|
private var state: EmptyComponentState?
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
self.backgroundView = BlurredBackgroundView(color: UIColor(white: 0.2, alpha: 0.45), enableBlur: true)
|
||||||
|
self.backgroundMaskView = UIView(frame: .zero)
|
||||||
|
|
||||||
|
self.backgroundMaskPanelView = UIView(frame: .zero)
|
||||||
|
self.backgroundMaskPanelView.backgroundColor = UIColor.white
|
||||||
|
self.backgroundMaskPanelView.clipsToBounds = true
|
||||||
|
self.backgroundMaskPanelView.layer.cornerRadius = 10.0
|
||||||
|
|
||||||
|
self.scrollView = UIScrollView(frame: .zero)
|
||||||
|
self.scrollView.contentInsetAdjustmentBehavior = .never
|
||||||
|
self.scrollView.showsHorizontalScrollIndicator = false
|
||||||
|
self.scrollView.showsVerticalScrollIndicator = false
|
||||||
|
self.scrollView.layer.cornerRadius = 10.0
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.backgroundView.mask = self.backgroundMaskView
|
||||||
|
|
||||||
|
let reorderRecognizer = ReorderGestureRecognizer(
|
||||||
|
shouldBegin: { [weak self] point in
|
||||||
|
guard let self, let item = self.item(at: point) else {
|
||||||
|
return (allowed: false, requiresLongPress: false, item: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (allowed: true, requiresLongPress: true, item: item)
|
||||||
|
},
|
||||||
|
willBegin: { point in
|
||||||
|
},
|
||||||
|
began: { [weak self] item in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.setReorderingItem(item: item)
|
||||||
|
},
|
||||||
|
ended: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.setReorderingItem(item: nil)
|
||||||
|
},
|
||||||
|
moved: { [weak self] distance in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.moveReorderingItem(distance: distance)
|
||||||
|
},
|
||||||
|
isActiveUpdated: { _ in
|
||||||
|
}
|
||||||
|
)
|
||||||
|
reorderRecognizer.delegate = self
|
||||||
|
self.reorderRecognizer = reorderRecognizer
|
||||||
|
self.addGestureRecognizer(reorderRecognizer)
|
||||||
|
|
||||||
|
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleTap))
|
||||||
|
self.tapRecognizer = tapRecognizer
|
||||||
|
self.addGestureRecognizer(tapRecognizer)
|
||||||
|
|
||||||
|
self.addSubview(self.backgroundView)
|
||||||
|
self.addSubview(self.scrollView)
|
||||||
|
|
||||||
|
self.backgroundMaskView.addSubview(self.backgroundMaskPanelView)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
preconditionFailure()
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func handleTap(_ gestureRecognizer: UITapGestureRecognizer) {
|
||||||
|
guard let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.reorderRecognizer?.isEnabled = false
|
||||||
|
self.reorderRecognizer?.isEnabled = true
|
||||||
|
|
||||||
|
let location = gestureRecognizer.location(in: self)
|
||||||
|
if let itemView = self.item(at: location), let item = itemView.item, item.asset.localIdentifier != component.selectedItemId {
|
||||||
|
component.itemTapped(item.asset.localIdentifier)
|
||||||
|
} else {
|
||||||
|
component.itemTapped(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
|
if otherGestureRecognizer is UITapGestureRecognizer {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if otherGestureRecognizer is UIPanGestureRecognizer {
|
||||||
|
if gestureRecognizer === self.reorderRecognizer, ![.began, .changed].contains(gestureRecognizer.state) {
|
||||||
|
gestureRecognizer.isEnabled = false
|
||||||
|
gestureRecognizer.isEnabled = true
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func item(at point: CGPoint) -> ItemView? {
|
||||||
|
let point = self.convert(point, to: self.scrollView)
|
||||||
|
for (_, itemView) in self.itemViews {
|
||||||
|
if itemView.frame.contains(point) {
|
||||||
|
return itemView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setReorderingItem(item: ItemView?) {
|
||||||
|
self.tapRecognizer?.isEnabled = false
|
||||||
|
self.tapRecognizer?.isEnabled = true
|
||||||
|
|
||||||
|
var mappedItem: (AnyHashable, ItemView)?
|
||||||
|
if let item {
|
||||||
|
for (id, visibleItem) in self.itemViews {
|
||||||
|
if visibleItem === item {
|
||||||
|
mappedItem = (id, visibleItem)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.reorderingItem?.id != mappedItem?.0 {
|
||||||
|
let transition: ComponentTransition = .spring(duration: 0.4)
|
||||||
|
if let (id, itemView) = mappedItem, let snapshotView = itemView.snapshotView(afterScreenUpdates: false) {
|
||||||
|
itemView.isHidden = true
|
||||||
|
|
||||||
|
let position = self.scrollView.convert(itemView.center, to: self)
|
||||||
|
snapshotView.center = position
|
||||||
|
transition.setScale(view: snapshotView, scale: 0.9)
|
||||||
|
self.addSubview(snapshotView)
|
||||||
|
|
||||||
|
self.reorderingItem = (id, position, position, snapshotView)
|
||||||
|
} else {
|
||||||
|
if let (id, _, _, snapshotView) = self.reorderingItem {
|
||||||
|
if let itemView = self.itemViews[id] {
|
||||||
|
if let innerSnapshotView = snapshotView.snapshotView(afterScreenUpdates: false) {
|
||||||
|
innerSnapshotView.center = self.convert(snapshotView.center, to: self.scrollView)
|
||||||
|
innerSnapshotView.transform = CGAffineTransformMakeScale(0.9, 0.9)
|
||||||
|
self.scrollView.addSubview(innerSnapshotView)
|
||||||
|
|
||||||
|
transition.setPosition(view: innerSnapshotView, position: itemView.center, completion: { [weak innerSnapshotView] _ in
|
||||||
|
innerSnapshotView?.removeFromSuperview()
|
||||||
|
itemView.isHidden = false
|
||||||
|
})
|
||||||
|
transition.setScale(view: innerSnapshotView, scale: 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.setPosition(view: snapshotView, position: self.scrollView.convert(itemView.center, to: self), completion: { [weak snapshotView] _ in
|
||||||
|
snapshotView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
transition.setScale(view: snapshotView, scale: 1.0)
|
||||||
|
transition.setAlpha(view: snapshotView, alpha: 0.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.reorderingItem = nil
|
||||||
|
}
|
||||||
|
self.state?.updated(transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func moveReorderingItem(distance: CGPoint) {
|
||||||
|
guard let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let (id, initialPosition, _, snapshotView) = self.reorderingItem {
|
||||||
|
let targetPosition = CGPoint(x: initialPosition.x + distance.x, y: initialPosition.y + distance.y)
|
||||||
|
self.reorderingItem = (id, initialPosition, targetPosition, snapshotView)
|
||||||
|
snapshotView.center = targetPosition
|
||||||
|
|
||||||
|
let mappedPosition = self.convert(targetPosition, to: self.scrollView)
|
||||||
|
|
||||||
|
if let visibleReorderingItem = self.itemViews[id], let fromId = self.itemViews[id]?.item?.asset.localIdentifier {
|
||||||
|
for (_, visibleItem) in self.itemViews {
|
||||||
|
if visibleItem === visibleReorderingItem {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if visibleItem.frame.contains(mappedPosition), let toId = visibleItem.item?.asset.localIdentifier {
|
||||||
|
component.itemReordered(fromId, toId)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateIn(from buttonView: SelectionPanelButtonContentComponent.View) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateOut(to buttonView: SelectionPanelButtonContentComponent.View, completion: @escaping () -> Void) {
|
||||||
|
completion()
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(component: SelectionPanelComponent, availableSize: CGSize, state: EmptyComponentState, transition: ComponentTransition) -> CGSize {
|
||||||
|
self.component = component
|
||||||
|
self.state = state
|
||||||
|
|
||||||
|
if self.portalView == nil {
|
||||||
|
if let portalView = PortalView(matchPosition: false) {
|
||||||
|
portalView.view.layer.rasterizationScale = UIScreenScale
|
||||||
|
|
||||||
|
let scale = 95.0 / component.previewContainerView.frame.width
|
||||||
|
portalView.view.transform = CGAffineTransformMakeScale(scale, scale)
|
||||||
|
|
||||||
|
component.previewContainerView.addPortal(view: portalView)
|
||||||
|
self.portalView = portalView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var validIds = Set<AnyHashable>()
|
||||||
|
|
||||||
|
let itemSize = CGSize(width: 95.0, height: 112.0)
|
||||||
|
let spacing: CGFloat = 4.0
|
||||||
|
|
||||||
|
var itemFrame: CGRect = CGRect(origin: CGPoint(x: spacing, y: spacing), size: itemSize)
|
||||||
|
|
||||||
|
var index = 1
|
||||||
|
for item in component.items {
|
||||||
|
let id = item.asset.localIdentifier
|
||||||
|
validIds.insert(id)
|
||||||
|
|
||||||
|
var itemTransition = transition
|
||||||
|
let itemView: ItemView
|
||||||
|
if let current = self.itemViews[id] {
|
||||||
|
itemView = current
|
||||||
|
} else {
|
||||||
|
itemView = ItemView(frame: itemFrame)
|
||||||
|
self.scrollView.addSubview(itemView)
|
||||||
|
self.itemViews[id] = itemView
|
||||||
|
|
||||||
|
itemTransition = .immediate
|
||||||
|
}
|
||||||
|
itemView.toggleSelection = { [weak self] in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
component.itemSelectionToggled(id)
|
||||||
|
}
|
||||||
|
itemView.update(item: item, number: index, isSelected: item.asset.localIdentifier == component.selectedItemId, isEnabled: item.isEnabled, size: itemFrame.size, portalView: self.portalView, transition: itemTransition)
|
||||||
|
|
||||||
|
itemTransition.setBounds(view: itemView, bounds: CGRect(origin: .zero, size: itemFrame.size))
|
||||||
|
itemTransition.setPosition(view: itemView, position: itemFrame.center)
|
||||||
|
|
||||||
|
itemFrame.origin.x += itemSize.width + spacing
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
var removeIds: [AnyHashable] = []
|
||||||
|
for (id, itemView) in self.itemViews {
|
||||||
|
if !validIds.contains(id) {
|
||||||
|
removeIds.append(id)
|
||||||
|
transition.setAlpha(view: itemView, alpha: 0.0, completion: { [weak itemView] _ in
|
||||||
|
itemView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for id in removeIds {
|
||||||
|
self.itemViews.removeValue(forKey: id)
|
||||||
|
}
|
||||||
|
|
||||||
|
let contentSize = CGSize(width: itemFrame.minX, height: itemSize.height + spacing * 2.0)
|
||||||
|
if self.scrollView.contentSize != contentSize {
|
||||||
|
self.scrollView.contentSize = contentSize
|
||||||
|
}
|
||||||
|
|
||||||
|
let backgroundSize = CGSize(width: min(availableSize.width - 24.0, contentSize.width), height: contentSize.height)
|
||||||
|
self.backgroundView.frame = CGRect(origin: .zero, size: availableSize)
|
||||||
|
self.backgroundView.update(size: availableSize, transition: .immediate)
|
||||||
|
|
||||||
|
let contentFrame = CGRect(origin: CGPoint(x: availableSize.width - 12.0 - backgroundSize.width, y: component.frame.minY), size: backgroundSize)
|
||||||
|
self.backgroundMaskPanelView.frame = contentFrame
|
||||||
|
self.scrollView.frame = contentFrame
|
||||||
|
|
||||||
|
return availableSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeView() -> View {
|
||||||
|
return View()
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||||
|
return view.update(component: self, availableSize: availableSize, state: state, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class ReorderGestureRecognizer: UIGestureRecognizer {
|
||||||
|
private let shouldBegin: (CGPoint) -> (allowed: Bool, requiresLongPress: Bool, item: SelectionPanelComponent.View.ItemView?)
|
||||||
|
private let willBegin: (CGPoint) -> Void
|
||||||
|
private let began: (SelectionPanelComponent.View.ItemView) -> Void
|
||||||
|
private let ended: () -> Void
|
||||||
|
private let moved: (CGPoint) -> Void
|
||||||
|
private let isActiveUpdated: (Bool) -> Void
|
||||||
|
|
||||||
|
private var initialLocation: CGPoint?
|
||||||
|
private var longTapTimer: SwiftSignalKit.Timer?
|
||||||
|
private var longPressTimer: SwiftSignalKit.Timer?
|
||||||
|
|
||||||
|
private var itemView: SelectionPanelComponent.View.ItemView?
|
||||||
|
|
||||||
|
public init(shouldBegin: @escaping (CGPoint) -> (allowed: Bool, requiresLongPress: Bool, item: SelectionPanelComponent.View.ItemView?), willBegin: @escaping (CGPoint) -> Void, began: @escaping (SelectionPanelComponent.View.ItemView) -> Void, ended: @escaping () -> Void, moved: @escaping (CGPoint) -> Void, isActiveUpdated: @escaping (Bool) -> Void) {
|
||||||
|
self.shouldBegin = shouldBegin
|
||||||
|
self.willBegin = willBegin
|
||||||
|
self.began = began
|
||||||
|
self.ended = ended
|
||||||
|
self.moved = moved
|
||||||
|
self.isActiveUpdated = isActiveUpdated
|
||||||
|
|
||||||
|
super.init(target: nil, action: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.longTapTimer?.invalidate()
|
||||||
|
self.longPressTimer?.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func startLongTapTimer() {
|
||||||
|
self.longTapTimer?.invalidate()
|
||||||
|
let longTapTimer = SwiftSignalKit.Timer(timeout: 0.25, repeat: false, completion: { [weak self] in
|
||||||
|
self?.longTapTimerFired()
|
||||||
|
}, queue: Queue.mainQueue())
|
||||||
|
self.longTapTimer = longTapTimer
|
||||||
|
longTapTimer.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func stopLongTapTimer() {
|
||||||
|
self.itemView = nil
|
||||||
|
self.longTapTimer?.invalidate()
|
||||||
|
self.longTapTimer = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private func startLongPressTimer() {
|
||||||
|
self.longPressTimer?.invalidate()
|
||||||
|
let longPressTimer = SwiftSignalKit.Timer(timeout: 0.6, repeat: false, completion: { [weak self] in
|
||||||
|
self?.longPressTimerFired()
|
||||||
|
}, queue: Queue.mainQueue())
|
||||||
|
self.longPressTimer = longPressTimer
|
||||||
|
longPressTimer.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func stopLongPressTimer() {
|
||||||
|
self.itemView = nil
|
||||||
|
self.longPressTimer?.invalidate()
|
||||||
|
self.longPressTimer = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func reset() {
|
||||||
|
super.reset()
|
||||||
|
|
||||||
|
self.itemView = nil
|
||||||
|
self.stopLongTapTimer()
|
||||||
|
self.stopLongPressTimer()
|
||||||
|
self.initialLocation = nil
|
||||||
|
|
||||||
|
self.isActiveUpdated(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func longTapTimerFired() {
|
||||||
|
guard let location = self.initialLocation else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.longTapTimer?.invalidate()
|
||||||
|
self.longTapTimer = nil
|
||||||
|
|
||||||
|
self.willBegin(location)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func longPressTimerFired() {
|
||||||
|
guard let _ = self.initialLocation else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.isActiveUpdated(true)
|
||||||
|
self.state = .began
|
||||||
|
self.longPressTimer?.invalidate()
|
||||||
|
self.longPressTimer = nil
|
||||||
|
self.longTapTimer?.invalidate()
|
||||||
|
self.longTapTimer = nil
|
||||||
|
if let itemView = self.itemView {
|
||||||
|
self.began(itemView)
|
||||||
|
}
|
||||||
|
self.isActiveUpdated(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||||
|
super.touchesBegan(touches, with: event)
|
||||||
|
|
||||||
|
if self.numberOfTouches > 1 {
|
||||||
|
self.isActiveUpdated(false)
|
||||||
|
self.state = .failed
|
||||||
|
self.ended()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.state == .possible {
|
||||||
|
if let location = touches.first?.location(in: self.view) {
|
||||||
|
let (allowed, requiresLongPress, itemView) = self.shouldBegin(location)
|
||||||
|
if allowed {
|
||||||
|
self.isActiveUpdated(true)
|
||||||
|
|
||||||
|
self.itemView = itemView
|
||||||
|
self.initialLocation = location
|
||||||
|
if requiresLongPress {
|
||||||
|
self.startLongTapTimer()
|
||||||
|
self.startLongPressTimer()
|
||||||
|
} else {
|
||||||
|
self.state = .began
|
||||||
|
if let itemView = self.itemView {
|
||||||
|
self.began(itemView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.isActiveUpdated(false)
|
||||||
|
self.state = .failed
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.isActiveUpdated(false)
|
||||||
|
self.state = .failed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||||
|
super.touchesEnded(touches, with: event)
|
||||||
|
|
||||||
|
self.initialLocation = nil
|
||||||
|
|
||||||
|
self.stopLongTapTimer()
|
||||||
|
if self.longPressTimer != nil {
|
||||||
|
self.stopLongPressTimer()
|
||||||
|
self.isActiveUpdated(false)
|
||||||
|
self.state = .failed
|
||||||
|
}
|
||||||
|
if self.state == .began || self.state == .changed {
|
||||||
|
self.isActiveUpdated(false)
|
||||||
|
self.ended()
|
||||||
|
self.state = .failed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||||
|
super.touchesCancelled(touches, with: event)
|
||||||
|
|
||||||
|
self.initialLocation = nil
|
||||||
|
|
||||||
|
self.stopLongTapTimer()
|
||||||
|
if self.longPressTimer != nil {
|
||||||
|
self.isActiveUpdated(false)
|
||||||
|
self.stopLongPressTimer()
|
||||||
|
self.state = .failed
|
||||||
|
}
|
||||||
|
if self.state == .began || self.state == .changed {
|
||||||
|
self.isActiveUpdated(false)
|
||||||
|
self.ended()
|
||||||
|
self.state = .failed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||||
|
super.touchesMoved(touches, with: event)
|
||||||
|
|
||||||
|
if (self.state == .began || self.state == .changed), let initialLocation = self.initialLocation, let location = touches.first?.location(in: self.view) {
|
||||||
|
self.state = .changed
|
||||||
|
let offset = CGPoint(x: location.x - initialLocation.x, y: location.y - initialLocation.y)
|
||||||
|
self.moved(offset)
|
||||||
|
} else if let touch = touches.first, let initialTapLocation = self.initialLocation, self.longPressTimer != nil {
|
||||||
|
let touchLocation = touch.location(in: self.view)
|
||||||
|
let dX = touchLocation.x - initialTapLocation.x
|
||||||
|
let dY = touchLocation.y - initialTapLocation.y
|
||||||
|
|
||||||
|
if dX * dX + dY * dY > 3.0 * 3.0 {
|
||||||
|
self.stopLongTapTimer()
|
||||||
|
self.stopLongPressTimer()
|
||||||
|
self.initialLocation = nil
|
||||||
|
self.isActiveUpdated(false)
|
||||||
|
self.state = .failed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -10018,7 +10018,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
viewControllers = viewControllers.filter { !($0 is AttachmentController)}
|
viewControllers = viewControllers.filter { !($0 is AttachmentController)}
|
||||||
rootController.setViewControllers(viewControllers, animated: false)
|
rootController.setViewControllers(viewControllers, animated: false)
|
||||||
|
|
||||||
rootController.proceedWithStoryUpload(target: target, result: result, existingMedia: nil, forwardInfo: nil, externalState: externalState, commit: commit)
|
rootController.proceedWithStoryUpload(target: target, results: [result], existingMedia: nil, forwardInfo: nil, externalState: externalState, commit: commit)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cancelled: {}
|
cancelled: {}
|
||||||
@ -10053,7 +10053,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
|
|
||||||
self.openBotPreviewEditor(target: .botPreview(id: self.peerId, language: pane.currentBotPreviewLanguage?.id), source: result, transitionIn: (transitionView, transitionRect, transitionImage))
|
self.openBotPreviewEditor(target: .botPreview(id: self.peerId, language: pane.currentBotPreviewLanguage?.id), source: result, transitionIn: (transitionView, transitionRect, transitionImage))
|
||||||
},
|
},
|
||||||
multipleCompletion: { _ in },
|
multipleCompletion: { _, _ in },
|
||||||
dismissed: {},
|
dismissed: {},
|
||||||
groupsPresented: {}
|
groupsPresented: {}
|
||||||
)
|
)
|
||||||
|
@ -991,10 +991,22 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let sectionTitle: String
|
let sectionTitle: String
|
||||||
if section.id == 0, case .stories = component.stateContext.subject {
|
if section.id == 0, case let .stories(_, count) = component.stateContext.subject {
|
||||||
sectionTitle = component.coverItem == nil ? environment.strings.Story_Privacy_PostStoryAsHeader : ""
|
if component.coverItem == nil {
|
||||||
|
if count > 1 {
|
||||||
|
sectionTitle = environment.strings.Story_Privacy_PostStoriesAsHeader
|
||||||
|
} else {
|
||||||
|
sectionTitle = environment.strings.Story_Privacy_PostStoryAsHeader
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sectionTitle = ""
|
||||||
|
}
|
||||||
} else if section.id == 2 {
|
} else if section.id == 2 {
|
||||||
sectionTitle = environment.strings.Story_Privacy_WhoCanViewHeader
|
if case let .stories(_, count) = component.stateContext.subject, count > 1 {
|
||||||
|
sectionTitle = environment.strings.Story_Privacy_WhoCanViewStoriesHeader
|
||||||
|
} else {
|
||||||
|
sectionTitle = environment.strings.Story_Privacy_WhoCanViewHeader
|
||||||
|
}
|
||||||
} else if section.id == 1 {
|
} else if section.id == 1 {
|
||||||
if case let .members(isGroup, _, _) = component.stateContext.subject {
|
if case let .members(isGroup, _, _) = component.stateContext.subject {
|
||||||
sectionTitle = isGroup ? environment.strings.BoostGift_Members_SectionTitle : environment.strings.BoostGift_Subscribers_SectionTitle
|
sectionTitle = isGroup ? environment.strings.BoostGift_Members_SectionTitle : environment.strings.BoostGift_Subscribers_SectionTitle
|
||||||
@ -1637,12 +1649,21 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let footerValue = environment.strings.Story_Privacy_KeepOnMyPageHours(Int32(component.timeout / 3600))
|
let footerValue = environment.strings.Story_Privacy_KeepOnMyPageHours(Int32(component.timeout / 3600))
|
||||||
var footerText = environment.strings.Story_Privacy_KeepOnMyPageInfo(footerValue).string
|
var footerText: String
|
||||||
|
if case let .stories(_, count) = component.stateContext.subject, count > 1 {
|
||||||
if let sendAsPeerId = self.sendAsPeerId, sendAsPeerId.isGroupOrChannel == true {
|
if let sendAsPeerId = self.sendAsPeerId, sendAsPeerId.isGroupOrChannel == true {
|
||||||
footerText = isSendAsGroup ? environment.strings.Story_Privacy_KeepOnGroupPageInfo(footerValue).string : environment.strings.Story_Privacy_KeepOnChannelPageInfo(footerValue).string
|
footerText = isSendAsGroup ? environment.strings.Story_Privacy_KeepOnGroupPageManyInfo(footerValue).string : environment.strings.Story_Privacy_KeepOnChannelPageManyInfo(footerValue).string
|
||||||
|
} else {
|
||||||
|
footerText = environment.strings.Story_Privacy_KeepOnMyPageManyInfo(footerValue).string
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let sendAsPeerId = self.sendAsPeerId, sendAsPeerId.isGroupOrChannel == true {
|
||||||
|
footerText = isSendAsGroup ? environment.strings.Story_Privacy_KeepOnGroupPageInfo(footerValue).string : environment.strings.Story_Privacy_KeepOnChannelPageInfo(footerValue).string
|
||||||
|
} else {
|
||||||
|
footerText = environment.strings.Story_Privacy_KeepOnMyPageInfo(footerValue).string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let footerSize = sectionFooter.update(
|
let footerSize = sectionFooter.update(
|
||||||
transition: sectionFooterTransition,
|
transition: sectionFooterTransition,
|
||||||
component: AnyComponent(MultilineTextComponent(
|
component: AnyComponent(MultilineTextComponent(
|
||||||
@ -2371,7 +2392,7 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
)
|
)
|
||||||
|
|
||||||
var footersTotalHeight: CGFloat = 0.0
|
var footersTotalHeight: CGFloat = 0.0
|
||||||
if case let .stories(editing) = component.stateContext.subject {
|
if case let .stories(editing, _) = component.stateContext.subject {
|
||||||
let body = MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.freeTextColor)
|
let body = MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.freeTextColor)
|
||||||
let bold = MarkdownAttributeSet(font: Font.semibold(13.0), textColor: environment.theme.list.freeTextColor)
|
let bold = MarkdownAttributeSet(font: Font.semibold(13.0), textColor: environment.theme.list.freeTextColor)
|
||||||
let link = MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemAccentColor)
|
let link = MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemAccentColor)
|
||||||
@ -2451,7 +2472,7 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
itemHeight: peerItemSize.height,
|
itemHeight: peerItemSize.height,
|
||||||
itemCount: peers.count
|
itemCount: peers.count
|
||||||
))
|
))
|
||||||
} else if case let .stories(editing) = component.stateContext.subject {
|
} else if case let .stories(editing, _) = component.stateContext.subject {
|
||||||
if !editing && hasChannels {
|
if !editing && hasChannels {
|
||||||
sections.append(ItemLayout.Section(
|
sections.append(ItemLayout.Section(
|
||||||
id: 0,
|
id: 0,
|
||||||
@ -2533,12 +2554,17 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
switch component.stateContext.subject {
|
switch component.stateContext.subject {
|
||||||
case .peers:
|
case .peers:
|
||||||
title = environment.strings.Story_Privacy_PostStoryAs
|
title = environment.strings.Story_Privacy_PostStoryAs
|
||||||
case let .stories(editing):
|
case let .stories(editing, count):
|
||||||
if editing {
|
if editing {
|
||||||
title = environment.strings.Story_Privacy_EditStory
|
title = environment.strings.Story_Privacy_EditStory
|
||||||
} else {
|
} else {
|
||||||
title = environment.strings.Story_Privacy_ShareStory
|
if count > 1 {
|
||||||
actionButtonTitle = environment.strings.Story_Privacy_PostStory
|
title = environment.strings.Story_Privacy_ShareStories
|
||||||
|
actionButtonTitle = environment.strings.Story_Privacy_PostStories(count)
|
||||||
|
} else {
|
||||||
|
title = environment.strings.Story_Privacy_ShareStory
|
||||||
|
actionButtonTitle = environment.strings.Story_Privacy_PostStory
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case let .chats(grayList):
|
case let .chats(grayList):
|
||||||
if grayList {
|
if grayList {
|
||||||
@ -2627,7 +2653,7 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
inset = 1000.0
|
inset = 1000.0
|
||||||
} else if case .channels = component.stateContext.subject {
|
} else if case .channels = component.stateContext.subject {
|
||||||
inset = 1000.0
|
inset = 1000.0
|
||||||
} else if case let .stories(editing) = component.stateContext.subject {
|
} else if case let .stories(editing, _) = component.stateContext.subject {
|
||||||
if editing {
|
if editing {
|
||||||
inset = 351.0
|
inset = 351.0
|
||||||
inset += 10.0 + environment.safeInsets.bottom + 50.0 + footersTotalHeight
|
inset += 10.0 + environment.safeInsets.bottom + 50.0 + footersTotalHeight
|
||||||
@ -3026,7 +3052,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
|||||||
var categoryItems: [ShareWithPeersScreenComponent.CategoryItem] = []
|
var categoryItems: [ShareWithPeersScreenComponent.CategoryItem] = []
|
||||||
var optionItems: [ShareWithPeersScreenComponent.OptionItem] = []
|
var optionItems: [ShareWithPeersScreenComponent.OptionItem] = []
|
||||||
var coverItem: ShareWithPeersScreenComponent.CoverItem?
|
var coverItem: ShareWithPeersScreenComponent.CoverItem?
|
||||||
if case let .stories(editing) = stateContext.subject {
|
if case let .stories(editing, _) = stateContext.subject {
|
||||||
var everyoneSubtitle = presentationData.strings.Story_Privacy_ExcludePeople
|
var everyoneSubtitle = presentationData.strings.Story_Privacy_ExcludePeople
|
||||||
if (stateContext.stateValue?.savedSelectedPeers[.everyone]?.count ?? 0) > 0 {
|
if (stateContext.stateValue?.savedSelectedPeers[.everyone]?.count ?? 0) > 0 {
|
||||||
var peerNamesArray: [String] = []
|
var peerNamesArray: [String] = []
|
||||||
|
@ -44,7 +44,7 @@ public extension ShareWithPeersScreen {
|
|||||||
final class StateContext {
|
final class StateContext {
|
||||||
public enum Subject: Equatable {
|
public enum Subject: Equatable {
|
||||||
case peers(peers: [EnginePeer], peerId: EnginePeer.Id?)
|
case peers(peers: [EnginePeer], peerId: EnginePeer.Id?)
|
||||||
case stories(editing: Bool)
|
case stories(editing: Bool, count: Int32)
|
||||||
case chats(blocked: Bool)
|
case chats(blocked: Bool)
|
||||||
case contacts(base: EngineStoryPrivacy.Base)
|
case contacts(base: EngineStoryPrivacy.Base)
|
||||||
case contactsSearch(query: String, onlyContacts: Bool)
|
case contactsSearch(query: String, onlyContacts: Bool)
|
||||||
|
@ -5068,7 +5068,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
|
|
||||||
let stateContext = ShareWithPeersScreen.StateContext(
|
let stateContext = ShareWithPeersScreen.StateContext(
|
||||||
context: context,
|
context: context,
|
||||||
subject: .stories(editing: true),
|
subject: .stories(editing: true, count: 1),
|
||||||
editing: true,
|
editing: true,
|
||||||
initialSelectedPeers: selectedPeers,
|
initialSelectedPeers: selectedPeers,
|
||||||
closeFriends: component.closeFriends.get(),
|
closeFriends: component.closeFriends.get(),
|
||||||
|
@ -3572,7 +3572,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
return mediaPickerController(context: context, hasSearch: hasSearch, completion: completion)
|
return mediaPickerController(context: context, hasSearch: hasSearch, completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func makeStoryMediaPickerScreen(context: AccountContext, isDark: Bool, forCollage: Bool, selectionLimit: Int?, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, multipleCompletion: @escaping ([Any]) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void) -> ViewController {
|
public func makeStoryMediaPickerScreen(context: AccountContext, isDark: Bool, forCollage: Bool, selectionLimit: Int?, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, multipleCompletion: @escaping ([Any], Bool) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void) -> ViewController {
|
||||||
return storyMediaPickerController(context: context, isDark: isDark, forCollage: forCollage, selectionLimit: selectionLimit, getSourceRect: getSourceRect, completion: completion, multipleCompletion: multipleCompletion, dismissed: dismissed, groupsPresented: groupsPresented)
|
return storyMediaPickerController(context: context, isDark: isDark, forCollage: forCollage, selectionLimit: selectionLimit, getSourceRect: getSourceRect, completion: completion, multipleCompletion: multipleCompletion, dismissed: dismissed, groupsPresented: groupsPresented)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3737,7 +3737,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
externalState.storyTarget = target
|
externalState.storyTarget = target
|
||||||
|
|
||||||
if let rootController = context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {
|
if let rootController = context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {
|
||||||
rootController.proceedWithStoryUpload(target: target, result: result, existingMedia: nil, forwardInfo: nil, externalState: externalState, commit: commit)
|
rootController.proceedWithStoryUpload(target: target, results: [result], existingMedia: nil, forwardInfo: nil, externalState: externalState, commit: commit)
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: targetPeerId))
|
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: targetPeerId))
|
||||||
|
@ -391,6 +391,8 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
|||||||
return .asset(asset)
|
return .asset(asset)
|
||||||
case let .draft(draft):
|
case let .draft(draft):
|
||||||
return .draft(draft, nil)
|
return .draft(draft, nil)
|
||||||
|
case let .assets(assets):
|
||||||
|
return .assets(assets)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -451,7 +453,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
|||||||
|
|
||||||
if let customTarget, case .botPreview = customTarget {
|
if let customTarget, case .botPreview = customTarget {
|
||||||
externalState.storyTarget = customTarget
|
externalState.storyTarget = customTarget
|
||||||
self.proceedWithStoryUpload(target: customTarget, result: result, existingMedia: nil, forwardInfo: nil, externalState: externalState, commit: commit)
|
self.proceedWithStoryUpload(target: customTarget, results: [result], existingMedia: nil, forwardInfo: nil, externalState: externalState, commit: commit)
|
||||||
|
|
||||||
dismissCameraImpl?()
|
dismissCameraImpl?()
|
||||||
return
|
return
|
||||||
@ -484,7 +486,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
|||||||
externalState.isPeerArchived = channel.storiesHidden ?? false
|
externalState.isPeerArchived = channel.storiesHidden ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
self.proceedWithStoryUpload(target: target, result: result, existingMedia: nil, forwardInfo: nil, externalState: externalState, commit: commit)
|
self.proceedWithStoryUpload(target: target, results: [result], existingMedia: nil, forwardInfo: nil, externalState: externalState, commit: commit)
|
||||||
|
|
||||||
dismissCameraImpl?()
|
dismissCameraImpl?()
|
||||||
})
|
})
|
||||||
@ -548,8 +550,8 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public func proceedWithStoryUpload(target: Stories.PendingTarget, result: MediaEditorScreenResult, existingMedia: EngineMedia?, forwardInfo: Stories.PendingForwardInfo?, externalState: MediaEditorTransitionOutExternalState, commit: @escaping (@escaping () -> Void) -> Void) {
|
public func proceedWithStoryUpload(target: Stories.PendingTarget, results: [MediaEditorScreenResult], existingMedia: EngineMedia?, forwardInfo: Stories.PendingForwardInfo?, externalState: MediaEditorTransitionOutExternalState, commit: @escaping (@escaping () -> Void) -> Void) {
|
||||||
guard let result = result as? MediaEditorScreenImpl.Result else {
|
guard let results = results as? [MediaEditorScreenImpl.Result] else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let context = self.context
|
let context = self.context
|
||||||
@ -657,83 +659,85 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let _ = self.chatListController as? ChatListControllerImpl {
|
if let _ = self.chatListController as? ChatListControllerImpl {
|
||||||
var media: EngineStoryInputMedia?
|
for result in results {
|
||||||
|
var media: EngineStoryInputMedia?
|
||||||
if let mediaResult = result.media {
|
|
||||||
switch mediaResult {
|
if let mediaResult = result.media {
|
||||||
case let .image(image, dimensions):
|
switch mediaResult {
|
||||||
let tempFile = TempBox.shared.tempFile(fileName: "file")
|
case let .image(image, dimensions):
|
||||||
defer {
|
|
||||||
TempBox.shared.dispose(tempFile)
|
|
||||||
}
|
|
||||||
if let imageData = compressImageToJPEG(image, quality: 0.7, tempFilePath: tempFile.path) {
|
|
||||||
media = .image(dimensions: dimensions, data: imageData, stickers: result.stickers)
|
|
||||||
}
|
|
||||||
case let .video(content, firstFrameImage, values, duration, dimensions):
|
|
||||||
let adjustments: VideoMediaResourceAdjustments
|
|
||||||
if let valuesData = try? JSONEncoder().encode(values) {
|
|
||||||
let data = MemoryBuffer(data: valuesData)
|
|
||||||
let digest = MemoryBuffer(data: data.md5Digest())
|
|
||||||
adjustments = VideoMediaResourceAdjustments(data: data, digest: digest, isStory: true)
|
|
||||||
|
|
||||||
let resource: TelegramMediaResource
|
|
||||||
switch content {
|
|
||||||
case let .imageFile(path):
|
|
||||||
resource = LocalFileVideoMediaResource(randomId: Int64.random(in: .min ... .max), path: path, adjustments: adjustments)
|
|
||||||
case let .videoFile(path):
|
|
||||||
resource = LocalFileVideoMediaResource(randomId: Int64.random(in: .min ... .max), path: path, adjustments: adjustments)
|
|
||||||
case let .asset(localIdentifier):
|
|
||||||
resource = VideoLibraryMediaResource(localIdentifier: localIdentifier, conversion: .compress(adjustments))
|
|
||||||
}
|
|
||||||
let tempFile = TempBox.shared.tempFile(fileName: "file")
|
let tempFile = TempBox.shared.tempFile(fileName: "file")
|
||||||
defer {
|
defer {
|
||||||
TempBox.shared.dispose(tempFile)
|
TempBox.shared.dispose(tempFile)
|
||||||
}
|
}
|
||||||
let imageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: 0.6, tempFilePath: tempFile.path) }
|
if let imageData = compressImageToJPEG(image, quality: 0.7, tempFilePath: tempFile.path) {
|
||||||
let firstFrameFile = imageData.flatMap { data -> TempBoxFile? in
|
media = .image(dimensions: dimensions, data: imageData, stickers: result.stickers)
|
||||||
let file = TempBox.shared.tempFile(fileName: "image.jpg")
|
|
||||||
if let _ = try? data.write(to: URL(fileURLWithPath: file.path)) {
|
|
||||||
return file
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
case let .video(content, firstFrameImage, values, duration, dimensions):
|
||||||
var coverTime: Double?
|
let adjustments: VideoMediaResourceAdjustments
|
||||||
if let coverImageTimestamp = values.coverImageTimestamp {
|
if let valuesData = try? JSONEncoder().encode(values) {
|
||||||
if let trimRange = values.videoTrimRange {
|
let data = MemoryBuffer(data: valuesData)
|
||||||
coverTime = min(duration, coverImageTimestamp - trimRange.lowerBound)
|
let digest = MemoryBuffer(data: data.md5Digest())
|
||||||
} else {
|
adjustments = VideoMediaResourceAdjustments(data: data, digest: digest, isStory: true)
|
||||||
coverTime = min(duration, coverImageTimestamp)
|
|
||||||
|
let resource: TelegramMediaResource
|
||||||
|
switch content {
|
||||||
|
case let .imageFile(path):
|
||||||
|
resource = LocalFileVideoMediaResource(randomId: Int64.random(in: .min ... .max), path: path, adjustments: adjustments)
|
||||||
|
case let .videoFile(path):
|
||||||
|
resource = LocalFileVideoMediaResource(randomId: Int64.random(in: .min ... .max), path: path, adjustments: adjustments)
|
||||||
|
case let .asset(localIdentifier):
|
||||||
|
resource = VideoLibraryMediaResource(localIdentifier: localIdentifier, conversion: .compress(adjustments))
|
||||||
}
|
}
|
||||||
|
let tempFile = TempBox.shared.tempFile(fileName: "file")
|
||||||
|
defer {
|
||||||
|
TempBox.shared.dispose(tempFile)
|
||||||
|
}
|
||||||
|
let imageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: 0.6, tempFilePath: tempFile.path) }
|
||||||
|
let firstFrameFile = imageData.flatMap { data -> TempBoxFile? in
|
||||||
|
let file = TempBox.shared.tempFile(fileName: "image.jpg")
|
||||||
|
if let _ = try? data.write(to: URL(fileURLWithPath: file.path)) {
|
||||||
|
return file
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var coverTime: Double?
|
||||||
|
if let coverImageTimestamp = values.coverImageTimestamp {
|
||||||
|
if let trimRange = values.videoTrimRange {
|
||||||
|
coverTime = min(duration, coverImageTimestamp - trimRange.lowerBound)
|
||||||
|
} else {
|
||||||
|
coverTime = min(duration, coverImageTimestamp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
media = .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: result.stickers, coverTime: coverTime)
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
media = .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: result.stickers, coverTime: coverTime)
|
break
|
||||||
}
|
}
|
||||||
default:
|
} else if let existingMedia {
|
||||||
break
|
media = .existing(media: existingMedia._asMedia())
|
||||||
|
}
|
||||||
|
|
||||||
|
if let media {
|
||||||
|
let _ = (context.engine.messages.uploadStory(
|
||||||
|
target: target,
|
||||||
|
media: media,
|
||||||
|
mediaAreas: result.mediaAreas,
|
||||||
|
text: result.caption.string,
|
||||||
|
entities: generateChatInputTextEntities(result.caption),
|
||||||
|
pin: result.options.pin,
|
||||||
|
privacy: result.options.privacy,
|
||||||
|
isForwardingDisabled: result.options.isForwardingDisabled,
|
||||||
|
period: result.options.timeout,
|
||||||
|
randomId: result.randomId,
|
||||||
|
forwardInfo: forwardInfo
|
||||||
|
)
|
||||||
|
|> deliverOnMainQueue).startStandalone(next: { stableId in
|
||||||
|
moveStorySource(engine: context.engine, peerId: context.account.peerId, from: result.randomId, to: Int64(stableId))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
} else if let existingMedia {
|
|
||||||
media = .existing(media: existingMedia._asMedia())
|
|
||||||
}
|
|
||||||
|
|
||||||
if let media {
|
|
||||||
let _ = (context.engine.messages.uploadStory(
|
|
||||||
target: target,
|
|
||||||
media: media,
|
|
||||||
mediaAreas: result.mediaAreas,
|
|
||||||
text: result.caption.string,
|
|
||||||
entities: generateChatInputTextEntities(result.caption),
|
|
||||||
pin: result.options.pin,
|
|
||||||
privacy: result.options.privacy,
|
|
||||||
isForwardingDisabled: result.options.isForwardingDisabled,
|
|
||||||
period: result.options.timeout,
|
|
||||||
randomId: result.randomId,
|
|
||||||
forwardInfo: forwardInfo
|
|
||||||
)
|
|
||||||
|> deliverOnMainQueue).startStandalone(next: { stableId in
|
|
||||||
moveStorySource(engine: context.engine, peerId: context.account.peerId, from: result.randomId, to: Int64(stableId))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
completionImpl()
|
completionImpl()
|
||||||
}
|
}
|
||||||
|
@ -1483,7 +1483,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
externalState.storyTarget = target
|
externalState.storyTarget = target
|
||||||
|
|
||||||
if let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {
|
if let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {
|
||||||
rootController.proceedWithStoryUpload(target: target, result: result, existingMedia: nil, forwardInfo: nil, externalState: externalState, commit: commit)
|
rootController.proceedWithStoryUpload(target: target, results: [result], existingMedia: nil, forwardInfo: nil, externalState: externalState, commit: commit)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if let navigationController = self.controller?.getNavigationController() {
|
if let navigationController = self.controller?.getNavigationController() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user