[WIP] Stickers editor

This commit is contained in:
Ilya Laktyushin 2024-04-11 14:15:37 +04:00
parent 8a11a281b7
commit 2d357afd12
22 changed files with 709 additions and 288 deletions

View File

@ -11910,3 +11910,47 @@ Sorry for the inconvenience.";
"Conversation.ViewEmojis" = "VIEW EMOJIS";
"MediaEditor.StickersTooMuch" = "Sorry, you've reached the maximum number of stickers in this set. Try a different one.";
"Stickers.ChooseSticker.Title" = "Choose Sticker";
"Stickers.EditSticker" = "Edit Sticker";
"Stickers.Reorder" = "Reorder";
"Stickers.Delete" = "Delete";
"Stickers.Delete.ForEveryone" = "Delete for Everyone";
"StickerPack.EditStickers" = "Edit Stickers";
"StickerPack.EditName" = "Edit Name";
"StickerPack.Reorder" = "Reorder";
"StickerPack.Delete" = "Delete";
"StickerPack.Delete.DeleteForEveyone" = "Delete for Everyone";
"StickerPack.Delete.RemoveForMe" = "Remove for Me";
"StickerPack.EditInfo" = "Check [@stickers]() bot for more options.";
"StickerPack.CreateNew" = "Create a New Sticker";
"StickerPack.AddExisting" = "Add an Existing Sticker";
"StickerPack.EditName.Title" = "Edit Sticker Set Name";
"StickerPack.EditName.Text" = "Choose a new name for your set.";
"StickerPack.Delete.Title" = "Delete Sticker Set";
"StickerPack.Delete.Text" = "This will delete the sticker set for all users.";
"StickerPack.Delete.Delete" = "Delete";
"StickerPack.StickerAdded" = "Sticker added to **%@** sticker set.";
"StickerPack.StickerUpdated" = "Sticker updated.";
"MediaEditor.Undo" = "Undo";
"MediaEditor.Erase" = "Erase";
"MediaEditor.EraseInfo" = "Erase";
"MediaEditor.Restore" = "Restore";
"MediaEditor.RestoreInfo" = "Restore";
"MediaEditor.Cutout" = "Cut Out an Object";
"MediaEditor.CutoutInfo" = "Cut Out an Object";
"MediaEditor.Outline" = "Add Outline";
"MediaEditor.SetStickerEmoji" = "Set emoji that corresponds to your sticker";
"MediaEditor.CreateNewPack" = "New Sticker Set";
"MediaEditor.ReplaceSticker" = "Replace Sticker";
"MediaEditor.AddToStickerPack" = "Add to Sticker Set";
"MediaEditor.NewStickerPack.Title" = "New Sticker Set";
"MediaEditor.NewStickerPack.Text" = "Choose a name for your sticker set.";

View File

@ -1021,7 +1021,7 @@ public protocol SharedAccountContext: AnyObject {
func makeStickerEditorScreen(context: AccountContext, source: Any?, transitionArguments: (UIView, CGRect, UIImage?)?, completion: @escaping (TelegramMediaFile, [String], @escaping () -> Void) -> Void) -> ViewController
func makeStickerMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any?, UIView?, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController
func makeStickerMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController
func makeStoryMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void) -> ViewController
func makeStickerPickerScreen(context: AccountContext, inputData: Promise<StickerPickerInput>, completion: @escaping (FileMediaReference) -> Void) -> ViewController

View File

@ -3,5 +3,4 @@ import Foundation
public struct GlobalExperimentalSettings {
public static var isAppStoreBuild: Bool = false
public static var enableFeed: Bool = false
public static var enableWIPStickers: Bool = true
}

View File

@ -1066,15 +1066,28 @@ public final class Camera {
return false
}
}
public static var isIpad: Bool {
return DeviceModel.current.isIpad
}
}
public final class CameraHolder {
public let camera: Camera
public let previewView: CameraPreviewView
public let previewView: CameraSimplePreviewView
public let parentView: UIView
public let restore: () -> Void
public init(camera: Camera, previewView: CameraPreviewView) {
public init(
camera: Camera,
previewView: CameraSimplePreviewView,
parentView: UIView,
restore: @escaping () -> Void
) {
self.camera = camera
self.previewView = previewView
self.parentView = parentView
self.restore = restore
}
}

View File

@ -25,22 +25,30 @@
}
- (bool)setupWithOutputPath:(NSString *)outputPath width:(int)width height:(int)height bitrate:(int64_t)bitrate framerate:(int32_t)framerate {
avformat_alloc_output_context2(&_formatContext, NULL, "matroska", [outputPath UTF8String]);
avformat_alloc_output_context2(&_formatContext, nil, "matroska", [outputPath UTF8String]);
if (!_formatContext) {
return false;
}
if (avio_open(&_formatContext->pb, [outputPath UTF8String], AVIO_FLAG_WRITE) < 0) {
avformat_free_context(_formatContext);
_formatContext = nil;
return false;
}
const AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_VP9);
if (!codec) {
avio_closep(&_formatContext->pb);
avformat_free_context(_formatContext);
_formatContext = nil;
return false;
}
_codecContext = avcodec_alloc_context3(codec);
if (!_codecContext) {
avio_closep(&_formatContext->pb);
avformat_free_context(_formatContext);
_formatContext = nil;
return false;
}
@ -61,11 +69,22 @@
}
if (avcodec_open2(_codecContext, codec, NULL) < 0) {
avcodec_free_context(&_codecContext);
_codecContext = nil;
avio_closep(&_formatContext->pb);
avformat_free_context(_formatContext);
_formatContext = nil;
return false;
}
_stream = avformat_new_stream(_formatContext, codec);
if (!_stream) {
avcodec_close(_codecContext);
avcodec_free_context(&_codecContext);
_codecContext = nil;
avio_closep(&_formatContext->pb);
avformat_free_context(_formatContext);
_formatContext = nil;
return false;
}
@ -78,11 +97,23 @@
int ret = avcodec_parameters_from_context(_stream->codecpar, _codecContext);
if (ret < 0) {
avcodec_close(_codecContext);
avcodec_free_context(&_codecContext);
_codecContext = nil;
avio_closep(&_formatContext->pb);
avformat_free_context(_formatContext);
_formatContext = nil;
return false;
}
ret = avformat_write_header(_formatContext, NULL);
ret = avformat_write_header(_formatContext, nil);
if (ret < 0) {
avcodec_close(_codecContext);
avcodec_free_context(&_codecContext);
_codecContext = nil;
avio_closep(&_formatContext->pb);
avformat_free_context(_formatContext);
_formatContext = nil;
return false;
}
@ -110,7 +141,7 @@
AVPacket pkt;
av_init_packet(&pkt);
pkt.data = NULL;
pkt.data = nil;
pkt.size = 0;
while (sendRet >= 0) {
@ -118,6 +149,7 @@
if (recvRet == AVERROR(EAGAIN) || recvRet == AVERROR_EOF) {
break;
} else if (recvRet < 0) {
av_packet_unref(&pkt);
break;
}
@ -135,11 +167,15 @@
}
- (bool)finalizeVideo {
if (!_codecContext) {
return false;
}
int sendRet = avcodec_send_frame(_codecContext, NULL);
if (sendRet >= 0) {
AVPacket pkt;
av_init_packet(&pkt);
pkt.data = NULL;
pkt.data = nil;
pkt.size = 0;
while (avcodec_receive_packet(_codecContext, &pkt) == 0) {
@ -151,14 +187,25 @@
}
}
if (_formatContext) {
av_write_trailer(_formatContext);
}
if (_formatContext && _formatContext->pb) {
avio_closep(&_formatContext->pb);
}
if (_codecContext) {
avcodec_close(_codecContext);
avcodec_free_context(&_codecContext);
_codecContext = nil;
}
if (_formatContext) {
avformat_free_context(_formatContext);
_formatContext = nil;
}
return true;
}

View File

@ -499,7 +499,7 @@ open class ItemListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
panRecognizer.delaysTouchesBegan = false
panRecognizer.cancelsTouchesInView = true
self.panRecognizer = panRecognizer
self.view.addGestureRecognizer(panRecognizer)
// self.view.addGestureRecognizer(panRecognizer)
}
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {

View File

@ -45,6 +45,7 @@ swift_library(
"//submodules/TelegramUI/Components/CameraScreen",
"//submodules/TelegramUI/Components/MediaEditor",
"//submodules/RadialStatusNode",
"//submodules/Camera",
],
visibility = [
"//visibility:public",

View File

@ -21,6 +21,7 @@ import SparseItemGrid
import UndoUI
import PresentationDataUtils
import MoreButtonNode
import Camera
import CameraScreen
import MediaEditor
@ -184,7 +185,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
public weak var webSearchController: WebSearchController?
public var openCamera: ((TGAttachmentCameraView?) -> Void)?
public var openCamera: ((Any?) -> Void)?
public var presentSchedulePicker: (Bool, @escaping (Int32) -> Void) -> Void = { _, _ in }
public var presentTimerPicker: (@escaping (Int32) -> Void) -> Void = { _ in }
public var presentWebSearch: (MediaGroupsScreen, Bool) -> Void = { _, _ in }
@ -232,7 +233,14 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
private let containerNode: ASDisplayNode
private let backgroundNode: NavigationBackgroundNode
fileprivate let gridNode: GridNode
fileprivate let cameraWrapperView: UIView
fileprivate var cameraView: TGAttachmentCameraView?
fileprivate var modernCamera: Camera?
fileprivate var modernCameraView: CameraSimplePreviewView?
fileprivate var modernCameraTapGestureRecognizer: UITapGestureRecognizer?
private var cameraActivateAreaNode: AccessibilityAreaNode
private var placeholderNode: MediaPickerPlaceholderNode?
private var manageNode: MediaPickerManageNode?
@ -267,7 +275,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
}
fileprivate var isSuspended = false
private var hasGallery = false
fileprivate var hasGallery = false
private var isCameraPreviewVisible = true
private var validLayout: (ContainerViewLayout, CGFloat)?
@ -289,6 +297,8 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
self.gridNode = GridNode()
self.scrollingArea = SparseItemGridScrollingArea()
self.cameraWrapperView = UIView()
self.cameraActivateAreaNode = AccessibilityAreaNode()
self.cameraActivateAreaNode.accessibilityLabel = "Camera"
self.cameraActivateAreaNode.accessibilityTraits = [.button]
@ -306,6 +316,8 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
self.containerNode.addSubnode(self.gridNode)
self.containerNode.addSubnode(self.scrollingArea)
self.gridNode.scrollView.addSubview(self.cameraWrapperView)
let selectedCollection = controller.selectedCollection.get()
let preloadPromise = self.preloadPromise
let updatedState: Signal<State, NoError>
@ -497,11 +509,19 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
self.gridNode.visibleItemsUpdated = { [weak self] _ in
self?.updateScrollingArea()
if let self, let cameraView = self.cameraView {
if let self {
var cameraView: UIView?
if let view = self.cameraView {
cameraView = view
} else if let _ = self.modernCameraView {
cameraView = self.cameraWrapperView
}
if let cameraView {
self.isCameraPreviewVisible = self.gridNode.scrollView.bounds.intersects(cameraView.frame)
self.updateIsCameraActive()
}
}
}
self.updateScrollingArea()
let throttledContentOffsetSignal = self.fastScrollContentOffset.get()
@ -539,17 +559,89 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
self.gridNode.scrollView.addSubview(cameraView)
self.gridNode.addSubnode(self.cameraActivateAreaNode)
} else if let controller = self.controller, case .assets(nil, .createSticker) = controller.subject, !Camera.isIpad {
let cameraPreviewView = CameraSimplePreviewView(frame: .zero, main: true)
cameraPreviewView.resetPlaceholder(front: false)
self.modernCameraView = cameraPreviewView
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.cameraTapped))
cameraPreviewView.addGestureRecognizer(tapGestureRecognizer)
self.modernCameraTapGestureRecognizer = tapGestureRecognizer
if #available(iOS 13.0, *) {
let _ = (cameraPreviewView.isPreviewing
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).startStandalone(next: { [weak self] _ in
self?.modernCameraView?.removePlaceholder(delay: 0.35)
})
} else {
Queue.mainQueue().after(0.35) {
cameraPreviewView.removePlaceholder(delay: 0.15)
}
}
self.cameraWrapperView.addSubview(cameraPreviewView)
let camera = Camera(
configuration: Camera.Configuration(
preset: .hd1920x1080,
position: .back,
isDualEnabled: false,
audio: false,
photo: true,
metadata: false
),
previewView: cameraPreviewView,
secondaryPreviewView: nil
)
self.modernCamera = camera
camera.startCapture()
} else {
self.containerNode.clipsToBounds = true
}
}
@objc private func cameraTapped() {
guard let camera = self.modernCamera, let previewView = self.modernCameraView else {
return
}
self.modernCameraTapGestureRecognizer?.isEnabled = false
self.controller?.openCamera?(
CameraHolder(
camera: camera,
previewView: previewView,
parentView: self.cameraWrapperView,
restore: { [weak self, weak previewView] in
guard let self else{
return
}
self.modernCameraTapGestureRecognizer?.isEnabled = true
if let previewView {
self.cameraWrapperView.addSubview(previewView)
}
}
)
)
}
func updateIsCameraActive() {
let isCameraActive = !self.isSuspended && !self.hasGallery && self.isCameraPreviewVisible
if let cameraView = self.cameraView {
if isCameraActive {
self.cameraView?.resumePreview()
cameraView.resumePreview()
} else {
self.cameraView?.pausePreview()
cameraView.pausePreview()
}
} else if let camera = self.modernCamera, let cameraView = self.modernCameraView {
if isCameraActive {
cameraView.isEnabled = true
camera.startCapture()
} else {
cameraView.isEnabled = false
camera.stopCapture()
}
}
}
@ -1311,7 +1403,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
let itemWidth = floorToScreenPixels((width - itemSpacing * CGFloat(itemsPerRow - 1)) / CGFloat(itemsPerRow))
var cameraRect: CGRect? = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: 0.0), size: CGSize(width: itemWidth, height: itemWidth * 2.0 + 1.0))
if self.cameraView == nil {
if self.cameraView == nil && self.modernCameraView == nil {
cameraRect = nil
}
@ -1448,12 +1540,36 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
transition.updateFrame(node: selectionNode, frame: innerBounds)
}
if let cameraView = self.cameraView {
var cameraView: UIView?
if let view = self.cameraView {
cameraView = view
} else if let view = self.modernCameraView {
cameraView = view
}
if let cameraView {
if let cameraRect = cameraRect {
if cameraView.superview == self.cameraWrapperView {
transition.updateFrame(view: self.cameraWrapperView, frame: cameraRect)
let screenWidth = min(layout.deviceMetrics.screenSize.width, layout.deviceMetrics.screenSize.height)
let cameraFullSize = cameraRect.size.aspectFilled(CGSize(width: screenWidth, height: screenWidth))
let cameraScale = cameraRect.width / cameraFullSize.width
cameraView.bounds = CGRect(origin: .zero, size: cameraFullSize)
cameraView.center = CGPoint(x: cameraRect.size.width / 2.0, y: cameraRect.size.height / 2.0)
cameraView.transform = CGAffineTransform(scaleX: cameraScale, y: cameraScale)
transition.updateFrame(view: cameraView, frame: CGRect(origin: .zero, size: cameraRect.size))
} else {
transition.updateFrame(view: cameraView, frame: cameraRect)
}
self.cameraActivateAreaNode.frame = cameraRect
self.cameraWrapperView.isHidden = false
cameraView.isHidden = false
} else {
self.cameraWrapperView.isHidden = true
cameraView.isHidden = true
}
}
@ -2319,6 +2435,10 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
}
public func updateHiddenMediaId(_ id: String?) {
if self.customSelection != nil {
self.controllerNode.hasGallery = id != nil
self.controllerNode.updateIsCameraActive()
}
self.controllerNode.hiddenMediaId.set(.single(id))
}
@ -2644,8 +2764,8 @@ public func storyMediaPickerController(
public func stickerMediaPickerController(
context: AccountContext,
getSourceRect: @escaping () -> CGRect,
completion: @escaping (Any?, UIView?, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void,
getSourceRect: @escaping () -> CGRect?,
completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void,
dismissed: @escaping () -> Void
) -> ViewController {
let presentationData = context.sharedContext.currentPresentationData.with({ $0 })
@ -2674,16 +2794,16 @@ public func stickerMediaPickerController(
}
return nil
}
completion(result, transitionView, transitionView.bounds, controller.transitionImage(for: result.localIdentifier), transitionOut, { [weak controller] in
completion(result, transitionView, transitionView.bounds, controller.transitionImage(for: result.localIdentifier), false, transitionOut, { [weak controller] in
controller?.updateHiddenMediaId(nil)
})
}
}
}
mediaPickerController.createFromScratch = { [weak controller] in
completion(nil, nil, .zero, nil, { _ in return nil }, { [weak controller] in
controller?.dismiss(animated: true)
completion(nil, nil, .zero, nil, false, { _ in return nil }, {
})
controller?.dismiss(animated: true)
}
mediaPickerController.presentFilePicker = { [weak controller] in
controller?.present(legacyICloudFilePicker(theme: presentationData.theme, mode: .import, documentTypes: ["public.image"], forceDarkTheme: false, dismissed: {
@ -2710,7 +2830,7 @@ public func stickerMediaPickerController(
}
if let image = UIImage(contentsOfFile: copyPath) {
completion(image, nil, .zero, nil, { _ in return nil }, {})
completion(image, nil, .zero, nil, false, { _ in return nil }, {})
}
})
}
@ -2718,6 +2838,47 @@ public func stickerMediaPickerController(
controller?.dismiss(animated: true)
}
mediaPickerController.openCamera = { [weak controller] cameraHolder in
guard let cameraHolder = cameraHolder as? CameraHolder else {
return
}
var returnToCameraImpl: (() -> Void)?
let cameraScreen = CameraScreen(
context: context,
mode: .sticker,
holder: cameraHolder,
transitionIn: CameraScreen.TransitionIn(
sourceView: cameraHolder.parentView,
sourceRect: cameraHolder.parentView.bounds,
sourceCornerRadius: 0.0
),
transitionOut: { _ in
return CameraScreen.TransitionOut(
destinationView: cameraHolder.parentView,
destinationRect: cameraHolder.parentView.bounds,
destinationCornerRadius: 0.0
)
},
completion: { result, _, commit in
completion(result, nil, .zero, nil, true, { _ in return nil }, {
returnToCameraImpl?()
})
}
)
cameraScreen.transitionedOut = { [weak cameraHolder] in
if let cameraHolder {
cameraHolder.restore()
}
}
controller?.push(cameraScreen)
returnToCameraImpl = { [weak cameraScreen] in
if let cameraScreen {
cameraScreen.returnFromEditor()
}
}
}
present(mediaPickerController, mediaPickerController.mediaPickerContext)
}
controller.willDismiss = {

View File

@ -497,9 +497,6 @@ private final class StickerPackContainer: ASDisplayNode {
if let (info, _, _) = strongSelf.currentStickerPack, info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji) {
canEdit = true
}
if !GlobalExperimentalSettings.enableWIPStickers {
canEdit = false
}
let accountPeerId = strongSelf.context.account.peerId
return combineLatest(
@ -552,30 +549,30 @@ private final class StickerPackContainer: ASDisplayNode {
})))
if canEdit {
menuItems.append(.action(ContextMenuActionItem(text: "Edit Sticker", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Draw"), color: theme.contextMenu.primaryColor) }, action: { _, f in
menuItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Stickers_EditSticker, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Draw"), color: theme.contextMenu.primaryColor) }, action: { _, f in
f(.default)
if let self {
self.openEditSticker(item.file)
}
})))
if !strongSelf.isEditing {
menuItems.append(.action(ContextMenuActionItem(text: "Reorder", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) }, action: { _, f in
menuItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Stickers_Reorder, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) }, action: { _, f in
f(.default)
if let self {
self.updateIsEditing(true)
}
})))
}
menuItems.append(.action(ContextMenuActionItem(text: "Delete", textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] c, f in
if let _ = self {
menuItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Stickers_Delete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] c, f in
if let self {
let contextItems: [ContextMenuItem] = [
.action(ContextMenuActionItem(text: "Back", icon: { theme in
.action(ContextMenuActionItem(text: self.presentationData.strings.Common_Back, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor)
}, iconPosition: .left, action: { c ,f in
c.popItems()
})),
.separator,
.action(ContextMenuActionItem(text: "Delete for Everyone", textColor: .destructive, icon: { _ in return nil }, action: { [weak self] _ ,f in
.action(ContextMenuActionItem(text: self.presentationData.strings.Stickers_Delete_ForEveryone, textColor: .destructive, icon: { _ in return nil }, action: { [weak self] _ ,f in
f(.default)
if let self, let (info, items, installed) = self.currentStickerPack {
@ -999,7 +996,7 @@ private final class StickerPackContainer: ASDisplayNode {
case .none:
buttonColor = self.presentationData.theme.list.itemAccentColor
case let .result(info, _, installed):
if GlobalExperimentalSettings.enableWIPStickers && info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji) {
if info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji) {
buttonColor = installed ? self.presentationData.theme.list.itemAccentColor : self.presentationData.theme.list.itemCheckColors.foregroundColor
} else {
buttonColor = installed ? self.presentationData.theme.list.itemDestructiveColor : self.presentationData.theme.list.itemCheckColors.foregroundColor
@ -1038,9 +1035,6 @@ private final class StickerPackContainer: ASDisplayNode {
if let info = self.currentStickerPack?.0, info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji) {
isEditable = true
}
if !GlobalExperimentalSettings.enableWIPStickers {
isEditable = false
}
let transaction: StickerPackPreviewGridTransaction
if reload {
@ -1133,11 +1127,10 @@ private final class StickerPackContainer: ASDisplayNode {
}
})))
if GlobalExperimentalSettings.enableWIPStickers, let (info, packItems, _) = self.currentStickerPack, info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji) {
//TODO:localize
if let (info, packItems, _) = self.currentStickerPack, info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji) {
items.append(.separator)
if packItems.count > 0 {
items.append(.action(ContextMenuActionItem(text: "Reorder", icon: { theme in
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.StickerPack_Reorder, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in
f(.default)
@ -1145,7 +1138,7 @@ private final class StickerPackContainer: ASDisplayNode {
})))
}
items.append(.action(ContextMenuActionItem(text: "Edit Name", icon: { theme in
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.StickerPack_EditName, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in
f(.default)
@ -1153,18 +1146,18 @@ private final class StickerPackContainer: ASDisplayNode {
self?.presentEditPackTitle()
})))
items.append(.action(ContextMenuActionItem(text: "Delete", textColor: .destructive, icon: { theme in
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.StickerPack_Delete, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
}, action: { [weak self] c, f in
if let self, let (_, _, isInstalled) = self.currentStickerPack {
if isInstalled {
let contextItems: [ContextMenuItem] = [
.action(ContextMenuActionItem(text: "Delete for Everyone", textColor: .destructive, icon: { _ in return nil }, action: { [weak self] _ ,f in
.action(ContextMenuActionItem(text: self.presentationData.strings.StickerPack_Delete_DeleteForEveyone, textColor: .destructive, icon: { _ in return nil }, action: { [weak self] _ ,f in
f(.default)
self?.presentDeletePack()
})),
.action(ContextMenuActionItem(text: "Remove for Me", icon: { _ in return nil }, action: { [weak self] _ ,f in
.action(ContextMenuActionItem(text: self.presentationData.strings.StickerPack_Delete_RemoveForMe, icon: { _ in return nil }, action: { [weak self] _ ,f in
f(.default)
self?.togglePackInstalled()
@ -1180,7 +1173,7 @@ private final class StickerPackContainer: ASDisplayNode {
items.append(.separator)
items.append(.action(ContextMenuActionItem(text: "Check [@stickers]() bot for more options.", textLayout: .multiline, textFont: .small, parseMarkdown: true, icon: { _ in
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.StickerPack_EditInfo, textLayout: .multiline, textFont: .small, parseMarkdown: true, icon: { _ in
return nil
}, action: { [weak self] _, f in
f(.default)
@ -1199,10 +1192,9 @@ private final class StickerPackContainer: ASDisplayNode {
private let stickerPickerInputData = Promise<StickerPickerInput>()
private func presentAddStickerOptions() {
//TODO:localize
let actionSheet = ActionSheetController(presentationData: self.presentationData)
var items: [ActionSheetItem] = []
items.append(ActionSheetButtonItem(title: "Create a New Sticker", color: .accent, action: { [weak actionSheet, weak self] in
items.append(ActionSheetButtonItem(title: self.presentationData.strings.StickerPack_CreateNew, color: .accent, action: { [weak actionSheet, weak self] in
actionSheet?.dismissAnimated()
guard let self, let controller = self.controller else {
@ -1211,7 +1203,7 @@ private final class StickerPackContainer: ASDisplayNode {
self.presentCreateSticker()
controller.controllerNode.dismiss()
}))
items.append(ActionSheetButtonItem(title: "Add an Existing Sticker", color: .accent, action: { [weak actionSheet, weak self] in
items.append(ActionSheetButtonItem(title: self.presentationData.strings.StickerPack_AddExisting, color: .accent, action: { [weak actionSheet, weak self] in
actionSheet?.dismissAnimated()
guard let self, let controller = self.controller else {
@ -1262,7 +1254,7 @@ private final class StickerPackContainer: ASDisplayNode {
let mainController = context.sharedContext.makeStickerMediaPickerScreen(
context: context,
getSourceRect: { return .zero },
completion: { result, transitionView, transitionRect, transitionImage, completion, dismissed in
completion: { result, transitionView, transitionRect, transitionImage, fromCamera, completion, cancelled in
let editorController = context.sharedContext.makeStickerEditorScreen(
context: context,
source: result,
@ -1286,7 +1278,7 @@ private final class StickerPackContainer: ASDisplayNode {
(navigationController?.viewControllers.last as? ViewController)?.present(packController, in: .window(.root))
Queue.mainQueue().after(0.1) {
packController.present(UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, loop: true, title: nil, text: "Sticker added to **\(info.title)** sticker set.", undoText: nil, customAction: nil), elevatedLayout: false, action: { _ in return false }), in: .current)
packController.present(UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, loop: true, title: nil, text: presentationData.strings.StickerPack_StickerAdded(info.title).string, undoText: nil, customAction: nil), elevatedLayout: false, action: { _ in return false }), in: .current)
}
})
}
@ -1335,7 +1327,7 @@ private final class StickerPackContainer: ASDisplayNode {
(navigationController?.viewControllers.last as? ViewController)?.present(packController, in: .window(.root))
Queue.mainQueue().after(0.1) {
packController.present(UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file.media, loop: true, title: nil, text: "Sticker added to **\(info.title)** sticker set.", undoText: nil, customAction: nil), elevatedLayout: false, action: { _ in return false }), in: .current)
packController.present(UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file.media, loop: true, title: nil, text: presentationData.strings.StickerPack_StickerAdded(info.title).string, undoText: nil, customAction: nil), elevatedLayout: false, action: { _ in return false }), in: .current)
}
})
})
@ -1383,7 +1375,7 @@ private final class StickerPackContainer: ASDisplayNode {
(navigationController?.viewControllers.last as? ViewController)?.present(packController, in: .window(.root))
Queue.mainQueue().after(0.1) {
packController.present(UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, loop: true, title: nil, text: "Sticker updated.", undoText: nil, customAction: nil), elevatedLayout: false, action: { _ in return false }), in: .current)
packController.present(UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, loop: true, title: nil, text: presentationData.strings.StickerPack_StickerUpdated, undoText: nil, customAction: nil), elevatedLayout: false, action: { _ in return false }), in: .current)
}
})
}
@ -1396,9 +1388,8 @@ private final class StickerPackContainer: ASDisplayNode {
return
}
let context = self.context
//TODO:localize
var dismissImpl: (() -> Void)?
let controller = stickerPackEditTitleController(context: context, title: "Edit Sticker Set Name", text: "Choose a new name for your set.", placeholder: self.presentationData.strings.ImportStickerPack_NamePlaceholder, actionTitle: presentationData.strings.Common_Done, value: self.updatedTitle ?? info.title, maxLength: 64, apply: { [weak self] title in
let controller = stickerPackEditTitleController(context: context, title: self.presentationData.strings.StickerPack_EditName_Title, text: self.presentationData.strings.StickerPack_EditName_Text, placeholder: self.presentationData.strings.ImportStickerPack_NamePlaceholder, actionTitle: presentationData.strings.Common_Done, value: self.updatedTitle ?? info.title, maxLength: 64, apply: { [weak self] title in
guard let self, let title else {
return
}
@ -1421,7 +1412,7 @@ private final class StickerPackContainer: ASDisplayNode {
return
}
let context = self.context
controller.present(textAlertController(context: context, updatedPresentationData: controller.updatedPresentationData, title: "Delete Sticker Set", text: "This will delete the sticker set for all users.", actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .destructiveAction, title: "Delete", action: { [weak self] in
controller.present(textAlertController(context: context, updatedPresentationData: controller.updatedPresentationData, title: self.presentationData.strings.StickerPack_Delete_Title, text: self.presentationData.strings.StickerPack_Delete_Text, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .destructiveAction, title: self.presentationData.strings.StickerPack_Delete_Delete, action: { [weak self] in
let _ = (context.engine.stickers.deleteStickerSet(packReference: .id(id: info.id.id, accessHash: info.accessHash))
|> deliverOnMainQueue).startStandalone()
@ -1478,7 +1469,7 @@ private final class StickerPackContainer: ASDisplayNode {
}
self.requestDismiss()
} else if let (info, _, installed) = self.currentStickerPack {
if GlobalExperimentalSettings.enableWIPStickers, installed, info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji) {
if installed, info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji) {
self.updateIsEditing(!self.isEditing)
return
}
@ -1553,7 +1544,7 @@ private final class StickerPackContainer: ASDisplayNode {
if let currentContents = self.currentContents, currentContents.count == 1, let content = currentContents.first, case let .result(info, _, installed) = content {
if installed {
let text: String
if GlobalExperimentalSettings.enableWIPStickers, info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji) {
if info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji) {
if self.isEditing {
var updated = false
if let current = self.buttonNode.attributedTitle(for: .normal)?.string, !current.isEmpty && current != self.presentationData.strings.Common_Done {
@ -1572,8 +1563,9 @@ private final class StickerPackContainer: ASDisplayNode {
self.buttonNode.setTitle(self.presentationData.strings.Common_Done, with: Font.semibold(17.0), with: self.presentationData.theme.list.itemCheckColors.foregroundColor, for: .normal)
self.buttonNode.setBackgroundImage(generateStretchableFilledCircleImage(radius: 11, color: self.presentationData.theme.list.itemCheckColors.fillColor), for: [])
} else {
let buttonTitle = self.presentationData.strings.StickerPack_EditStickers
var updated = false
if let current = self.buttonNode.attributedTitle(for: .normal)?.string, !current.isEmpty && current != "Edit Stickers" {
if let current = self.buttonNode.attributedTitle(for: .normal)?.string, !current.isEmpty && current != buttonTitle {
updated = true
}
@ -1586,8 +1578,7 @@ private final class StickerPackContainer: ASDisplayNode {
self.buttonNode.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
//TODO:localize
text = "Edit Stickers"
text = buttonTitle
self.buttonNode.setTitle(text, with: Font.regular(17.0), with: self.presentationData.theme.list.itemAccentColor, for: .normal)
self.buttonNode.setBackgroundImage(nil, for: [])
}
@ -1732,7 +1723,7 @@ private final class StickerPackContainer: ASDisplayNode {
self.controller?.present(textAlertController(context: self.context, title: nil, text: self.presentationData.strings.StickerPack_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
self.controller?.dismiss(animated: true, completion: nil)
case let .result(info, items, installed):
isEditable = GlobalExperimentalSettings.enableWIPStickers && info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji)
isEditable = info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji)
self.onReady()
if !items.isEmpty && self.currentStickerPack == nil {
if let _ = self.validLayout, abs(self.expandScrollProgress - 1.0) < .ulpOfOne {
@ -1816,7 +1807,7 @@ private final class StickerPackContainer: ASDisplayNode {
self.updateButton(count: count)
}
if GlobalExperimentalSettings.enableWIPStickers && info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji) && entries.count < maxStickersCount {
if info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji) && entries.count < maxStickersCount {
entries.append(.add)
}
}
@ -1926,7 +1917,7 @@ private final class StickerPackContainer: ASDisplayNode {
self.currentEntries = entries
if let controller = self.controller {
let transaction = StickerPackPreviewGridTransaction(previousList: previousEntries, list: entries, context: self.context, interaction: self.interaction, theme: self.presentationData.theme, strings: self.presentationData.strings, animationCache: controller.animationCache, animationRenderer: controller.animationRenderer, scrollToItem: nil, isEditable: GlobalExperimentalSettings.enableWIPStickers && info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji), isEditing: self.isEditing, invert: invert)
let transaction = StickerPackPreviewGridTransaction(previousList: previousEntries, list: entries, context: self.context, interaction: self.interaction, theme: self.presentationData.theme, strings: self.presentationData.strings, animationCache: controller.animationCache, animationRenderer: controller.animationRenderer, scrollToItem: nil, isEditable: info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji), isEditing: self.isEditing, invert: invert)
self.enqueueTransaction(transaction)
}
}
@ -1986,7 +1977,7 @@ private final class StickerPackContainer: ASDisplayNode {
actionAreaBottomInset = 2.0
}
}
if let (info, _, isInstalled) = self.currentStickerPack, isInstalled, !GlobalExperimentalSettings.enableWIPStickers || (!info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji)) {
if let (info, _, isInstalled) = self.currentStickerPack, isInstalled, (!info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji)) {
buttonHeight = 42.0
actionAreaTopInset = 1.0
actionAreaBottomInset = 2.0

View File

@ -446,14 +446,14 @@ final class EmojiStickerAccessoryNode: SparseNode, PeekControllerAccessoryNode {
let items = topItems(selectedEmoji: selectedEmoji, recommendedEmoji: recommendedEmoji, count: 7)
let selectedItems = ValuePromise<[String]>(selectedEmoji)
//TODO:localize
let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkPresentationTheme)
let reactionContextNode = ReactionContextNode(
context: self.context,
animationCache: self.context.animationCache,
presentationData: self.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkPresentationTheme),
presentationData: presentationData,
items: items.map { .staticEmoji($0) },
selectedItems: Set(selectedEmoji),
title: "Set emoji that corresponds to your sticker",
title: presentationData.strings.MediaEditor_SetStickerEmoji,
reactionsLocked: false,
alwaysAllowPremiumReactions: true,
allPresetReactionsAreAvailable: true,

View File

@ -751,6 +751,13 @@ private final class CameraScreenComponent: CombinedComponent {
state.cameraState = component.cameraState
state.volumeButtonsListenerActive = component.hasAppeared && component.isVisible
let isSticker: Bool
if let controller = controller() as? CameraScreen, case .sticker = controller.mode {
isSticker = true
} else {
isSticker = false
}
let isTablet: Bool
if case .regular = environment.metrics.widthClass {
isTablet = true
@ -879,6 +886,7 @@ private final class CameraScreenComponent: CombinedComponent {
let captureControls = captureControls.update(
component: CaptureControlsComponent(
isTablet: isTablet,
isSticker: isSticker,
hasAppeared: component.hasAppeared && hasAllRequiredAccess,
hasAccess: hasAllRequiredAccess,
tintColor: controlsTintColor,
@ -1063,7 +1071,7 @@ private final class CameraScreenComponent: CombinedComponent {
.disappear(.default(scale: true))
)
if !isTablet && Camera.isDualCameraSupported(forRoundVideo: false) {
if !isSticker && !isTablet && Camera.isDualCameraSupported(forRoundVideo: false) {
let dualButton = dualButton.update(
component: CameraButton(
content: AnyComponentWithIdentity(
@ -1201,7 +1209,7 @@ private final class CameraScreenComponent: CombinedComponent {
}
}
if case .none = component.cameraState.recording, !state.isTransitioning && hasAllRequiredAccess {
if !isSticker, case .none = component.cameraState.recording, !state.isTransitioning && hasAllRequiredAccess {
let availableModeControlSize: CGSize
if isTablet {
availableModeControlSize = CGSize(width: panelWidth, height: 120.0)
@ -1314,6 +1322,11 @@ private class BlurView: UIVisualEffectView {
}
public class CameraScreen: ViewController {
public enum Mode {
case story
case sticker
}
public enum PIPPosition: Int32 {
case topLeft
case topRight
@ -1735,11 +1748,18 @@ public class CameraScreen: ViewController {
fileprivate var captureStartTimestamp: Double?
private func setupCamera() {
guard self.camera == nil else {
guard self.camera == nil, let controller = self.controller else {
return
}
let camera = Camera(
var isNew = false
let camera: Camera
if let cameraHolder = controller.holder {
camera = cameraHolder.camera
self.mainPreviewView = cameraHolder.previewView
self.mainPreviewContainerView.addSubview(self.mainPreviewView)
} else {
camera = Camera(
configuration: Camera.Configuration(
preset: .hd1920x1080,
position: self.cameraState.position,
@ -1751,6 +1771,8 @@ public class CameraScreen: ViewController {
previewView: self.mainPreviewView,
secondaryPreviewView: self.additionalPreviewView
)
isNew = true
}
self.cameraStateDisposable = combineLatest(
queue: Queue.mainQueue(),
@ -1841,12 +1863,14 @@ public class CameraScreen: ViewController {
})
camera.focus(at: CGPoint(x: 0.5, y: 0.5), autoFocus: true)
if isNew {
camera.startCapture()
}
self.captureStartTimestamp = CACurrentMediaTime()
self.camera = camera
if self.hasAppeared {
if isNew && self.hasAppeared {
self.maybePresentTooltips()
}
}
@ -2025,21 +2049,30 @@ public class CameraScreen: ViewController {
)
}
var animatedIn = false
func animateIn() {
guard let controller = self.controller else {
return
}
self.transitionDimView.alpha = 0.0
self.backgroundView.alpha = 0.0
UIView.animate(withDuration: 0.4, animations: {
self.backgroundView.alpha = 1.0
})
if let layout = self.validLayout, layout.metrics.isTablet {
self.controller?.statusBar.updateStatusBarStyle(.Hide, animated: true)
if let layout = self.validLayout {
if layout.metrics.isTablet {
controller.statusBar.updateStatusBarStyle(.Hide, animated: true)
} else {
controller.statusBar.updateStatusBarStyle(.White, animated: true)
}
}
if let transitionIn = self.controller?.transitionIn, let sourceView = transitionIn.sourceView {
let sourceLocalFrame = sourceView.convert(transitionIn.sourceRect, to: self.view)
if case .story = controller.mode {
let sourceScale = sourceLocalFrame.width / self.previewContainerView.frame.width
self.previewContainerView.layer.animatePosition(from: sourceLocalFrame.center, to: self.previewContainerView.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in
self.requestUpdateLayout(hasAppeared: true, transition: .immediate)
})
@ -2054,16 +2087,37 @@ public class CameraScreen: ViewController {
timingFunction: kCAMediaTimingFunctionSpring,
duration: 0.3
)
} else {
let sourceInnerFrame = sourceView.convert(transitionIn.sourceRect, to: self.previewContainerView)
let sourceCenter = sourceInnerFrame.center
self.mainPreviewView.layer.position = CGPoint(x: self.previewContainerView.frame.width / 2.0, y: self.previewContainerView.frame.height / 2.0)
self.mainPreviewView.layer.animatePosition(from: sourceCenter, to: self.mainPreviewView.layer.position, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in
self.requestUpdateLayout(hasAppeared: true, transition: .immediate)
})
self.mainPreviewView.layer.animateBounds(from: self.mainPreviewView.bounds, to: CGRect(origin: .zero, size: self.previewContainerView.frame.size), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
let sourceScale = self.mainPreviewView.layer.value(forKeyPath: "transform.scale.x") as? CGFloat ?? 1.0
self.mainPreviewView.transform = CGAffineTransform.identity
self.mainPreviewView.layer.animateScale(from: sourceScale, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in
Queue.mainQueue().justDispatch {
self.animatedIn = true
}
})
}
if let view = self.componentHost.view {
view.layer.animatePosition(from: sourceLocalFrame.center, to: view.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}
}
func animateOut(completion: @escaping () -> Void) {
self.camera?.stopCapture(invalidate: true)
guard let controller = self.controller else {
return
}
UIView.animate(withDuration: 0.25, animations: {
self.backgroundView.alpha = 0.0
@ -2071,8 +2125,9 @@ public class CameraScreen: ViewController {
if let transitionOut = self.controller?.transitionOut(false), let destinationView = transitionOut.destinationView {
let destinationLocalFrame = destinationView.convert(transitionOut.destinationRect, to: self.view)
let targetScale = destinationLocalFrame.width / self.previewContainerView.frame.width
if case .story = controller.mode {
self.previewContainerView.layer.animatePosition(from: self.previewContainerView.center, to: destinationLocalFrame.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
completion()
})
@ -2088,18 +2143,35 @@ public class CameraScreen: ViewController {
duration: 0.3,
removeOnCompletion: false
)
} else {
let destinationInnerFrame = destinationView.convert(transitionOut.destinationRect, to: self.previewContainerView)
let initialCenter = self.mainPreviewView.layer.position
self.mainPreviewView.layer.position = destinationInnerFrame.center
self.mainPreviewView.layer.animatePosition(from: initialCenter, to: self.mainPreviewView.layer.position, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in
completion()
})
self.mainPreviewView.layer.animateBounds(from: self.mainPreviewView.bounds, to: CGRect(origin: .zero, size: self.previewContainerView.frame.size), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
let targetScale = destinationInnerFrame.width / self.previewContainerView.frame.width
// self.mainPreviewView.transform = CGAffineTransform.identity
self.mainPreviewView.layer.animateScale(from: 1.0, to: targetScale, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
}
if let view = self.componentHost.view {
view.layer.animatePosition(from: view.center, to: destinationLocalFrame.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
view.layer.animateScale(from: 1.0, to: targetScale, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
view.layer.animateScale(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
}
} else {
completion()
}
self.componentHost.view?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
if case .story = controller.mode {
self.previewContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.35, removeOnCompletion: false)
}
}
func animateOutToEditor() {
self.cameraIsActive = false
@ -2344,7 +2416,7 @@ public class CameraScreen: ViewController {
fileprivate var hasAppeared = false
func containerLayoutUpdated(layout: ContainerViewLayout, forceUpdate: Bool = false, hasAppeared: Bool = false, transition: Transition) {
guard let _ = self.controller else {
guard let controller = self.controller else {
return
}
let isFirstTime = self.validLayout == nil
@ -2578,7 +2650,14 @@ public class CameraScreen: ViewController {
self.additionalPreviewContainerView.insertSubview(additionalPreviewView, at: 0)
}
if case .sticker = controller.mode {
if self.animatedIn {
mainPreviewView.frame = mainPreviewInnerFrame
}
} else {
mainPreviewView.frame = mainPreviewInnerFrame
}
additionalPreviewView.frame = additionalPreviewInnerFrame
self.previewFrameLeftDimView.isHidden = !isTablet
@ -2604,14 +2683,14 @@ public class CameraScreen: ViewController {
transition.setPosition(view: self.transitionCornersView, position: CGPoint(x: layout.size.width + screenCornerRadius / 2.0, y: layout.size.height / 2.0))
transition.setBounds(view: self.transitionCornersView, bounds: CGRect(origin: .zero, size: CGSize(width: screenCornerRadius, height: layout.size.height)))
if isTablet && isFirstTime {
if (controller.mode == .sticker || isTablet) && isFirstTime {
self.animateIn()
}
if self.cameraState.flashMode == .on && (self.cameraState.recording != .none || self.cameraState.mode == .video) {
self.controller?.statusBarStyle = .Black
controller.statusBarStyle = .Black
} else {
self.controller?.statusBarStyle = .White
controller.statusBarStyle = .White
}
}
}
@ -2621,6 +2700,7 @@ public class CameraScreen: ViewController {
}
private let context: AccountContext
fileprivate let mode: Mode
fileprivate let holder: CameraHolder?
fileprivate let transitionIn: TransitionIn?
fileprivate let transitionOut: (Bool) -> TransitionOut?
@ -2645,6 +2725,7 @@ public class CameraScreen: ViewController {
}
fileprivate let completion: (Signal<CameraScreen.Result, NoError>, ResultTransition?, @escaping () -> Void) -> Void
public var transitionedIn: () -> Void = {}
public var transitionedOut: () -> Void = {}
private var audioSessionDisposable: Disposable?
@ -2663,6 +2744,8 @@ public class CameraScreen: ViewController {
return self.node.cameraState
}
public var isEmbedded = false
fileprivate func updateCameraState(_ f: (CameraState) -> CameraState, transition: Transition) {
self.node.cameraState = f(self.node.cameraState)
self.node.requestUpdateLayout(hasAppeared: self.node.hasAppeared, transition: transition)
@ -2670,12 +2753,14 @@ public class CameraScreen: ViewController {
public init(
context: AccountContext,
mode: Mode,
holder: CameraHolder? = nil,
transitionIn: TransitionIn?,
transitionOut: @escaping (Bool) -> TransitionOut?,
completion: @escaping (Signal<CameraScreen.Result, NoError>, ResultTransition?, @escaping () -> Void) -> Void
) {
self.context = context
self.mode = mode
self.holder = holder
self.transitionIn = transitionIn
self.transitionOut = transitionOut
@ -2907,13 +2992,17 @@ public class CameraScreen: ViewController {
self.hapticFeedback.impact(.light)
}
if case .story = self.mode {
self.node.camera?.stopCapture(invalidate: true)
}
self.isDismissed = true
if animated {
self.ignoreStatusBar = true
if let layout = self.validLayout, layout.metrics.isTablet {
if let layout = self.validLayout, layout.metrics.isTablet || self.mode == .sticker {
self.node.animateOut(completion: {
self.dismiss(animated: false)
self.transitionedOut()
})
} else {
if !interactive {
@ -2923,6 +3012,7 @@ public class CameraScreen: ViewController {
}
self.updateTransitionProgress(0.0, transition: .animated(duration: 0.4, curve: .spring), completion: { [weak self] in
self?.dismiss(animated: false)
self?.transitionedOut()
})
}
} else {

View File

@ -459,6 +459,7 @@ final class CaptureControlsComponent: Component {
}
let isTablet: Bool
let isSticker: Bool
let hasAppeared: Bool
let hasAccess: Bool
let tintColor: UIColor
@ -478,6 +479,7 @@ final class CaptureControlsComponent: Component {
init(
isTablet: Bool,
isSticker: Bool,
hasAppeared: Bool,
hasAccess: Bool,
tintColor: UIColor,
@ -496,6 +498,7 @@ final class CaptureControlsComponent: Component {
flipAnimationAction: ActionSlot<Void>
) {
self.isTablet = isTablet
self.isSticker = isSticker
self.hasAppeared = hasAppeared
self.hasAccess = hasAccess
self.tintColor = tintColor
@ -518,6 +521,9 @@ final class CaptureControlsComponent: Component {
if lhs.isTablet != rhs.isTablet {
return false
}
if lhs.isSticker != rhs.isSticker {
return false
}
if lhs.hasAppeared != rhs.hasAppeared {
return false
}
@ -912,7 +918,9 @@ final class CaptureControlsComponent: Component {
isTransitioning = true
}
let galleryButtonFrame: CGRect
let gallerySize: CGSize
if !component.isSticker {
let galleryCornerRadius: CGFloat
if component.isTablet {
gallerySize = CGSize(width: 72.0, height: 72.0)
@ -950,7 +958,6 @@ final class CaptureControlsComponent: Component {
environment: {},
containerSize: gallerySize
)
let galleryButtonFrame: CGRect
if component.isTablet {
galleryButtonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - galleryButtonSize.width) / 2.0), y: size.height - galleryButtonSize.height - 56.0), size: galleryButtonSize)
} else {
@ -970,6 +977,10 @@ final class CaptureControlsComponent: Component {
transition.setScale(view: galleryButtonView, scale: isRecording || isTransitioning ? 0.1 : 1.0)
transition.setAlpha(view: galleryButtonView, alpha: isRecording || isTransitioning ? 0.0 : normalAlpha)
}
} else {
galleryButtonFrame = .zero
gallerySize = .zero
}
if !component.isTablet && component.hasAccess {
let flipButtonOriginX = availableSize.width - 48.0 - buttonSideInset
@ -1173,6 +1184,7 @@ final class CaptureControlsComponent: Component {
if let shutterButtonView = self.shutterButtonView.view {
if shutterButtonView.superview == nil {
if !component.isSticker {
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(_:)))
panGestureRecognizer.delegate = self
shutterButtonView.addGestureRecognizer(panGestureRecognizer)
@ -1181,7 +1193,7 @@ final class CaptureControlsComponent: Component {
pressGestureRecognizer.minimumPressDuration = 0.3
pressGestureRecognizer.delegate = self
shutterButtonView.addGestureRecognizer(pressGestureRecognizer)
}
self.addSubview(shutterButtonView)
}
let alpha: CGFloat = component.hasAccess ? 1.0 : 0.3

View File

@ -959,6 +959,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top), size: CGSize(width: actualSize.width - backgroundInsets.left - backgroundInsets.right, height: actualSize.height - backgroundInsets.top - backgroundInsets.bottom))
var patternTopRightPosition = CGPoint()
var patternAlpha: CGFloat = 1.0
if !contentAnimatedFilesValue.isEmpty, let (_, inlineMediaSize) = inlineMediaAndSize {
var inlineMediaFrame = CGRect(origin: CGPoint(x: actualSize.width - insets.right - inlineMediaSize.width, y: backgroundInsets.top + inlineMediaEdgeInset), size: inlineMediaSize)
@ -966,7 +967,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
inlineMediaFrame.origin.x = insets.left
}
patternTopRightPosition.x = insets.right + inlineMediaSize.width - 6.0
patternAlpha = 0.5
if !contentAnimatedFilesValue.isEmpty {
if contentAnimatedFilesValue.count < 4, let file = contentAnimatedFilesValue.first {
@ -1486,13 +1487,13 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
if let current = self.backgroundView {
backgroundView = current
animation.animator.updateFrame(layer: backgroundView.layer, frame: backgroundFrame, completion: nil)
backgroundView.update(size: backgroundFrame.size, isTransparent: false, primaryColor: mainColor, secondaryColor: secondaryColor, thirdColor: tertiaryColor, backgroundColor: nil, pattern: pattern, patternTopRightPosition: patternTopRightPosition, animation: animation)
backgroundView.update(size: backgroundFrame.size, isTransparent: false, primaryColor: mainColor, secondaryColor: secondaryColor, thirdColor: tertiaryColor, backgroundColor: nil, pattern: pattern, patternTopRightPosition: patternTopRightPosition, patternAlpha: patternAlpha, animation: animation)
} else {
backgroundView = MessageInlineBlockBackgroundView()
self.backgroundView = backgroundView
backgroundView.frame = backgroundFrame
self.transformContainer.view.insertSubview(backgroundView, at: 0)
backgroundView.update(size: backgroundFrame.size, isTransparent: false, primaryColor: mainColor, secondaryColor: secondaryColor, thirdColor: tertiaryColor, backgroundColor: nil, pattern: pattern, patternTopRightPosition: patternTopRightPosition, animation: .None)
backgroundView.update(size: backgroundFrame.size, isTransparent: false, primaryColor: mainColor, secondaryColor: secondaryColor, thirdColor: tertiaryColor, backgroundColor: nil, pattern: pattern, patternTopRightPosition: patternTopRightPosition, patternAlpha: patternAlpha, animation: .None)
}
} else {
if let backgroundView = self.backgroundView {

View File

@ -460,6 +460,7 @@ public final class MessageInlineBlockBackgroundView: UIView {
var backgroundColor: UIColor?
var pattern: Pattern?
var patternTopRightPosition: CGPoint?
var patternAlpha: CGFloat
var displayProgress: Bool
init(
@ -471,6 +472,7 @@ public final class MessageInlineBlockBackgroundView: UIView {
backgroundColor: UIColor?,
pattern: Pattern?,
patternTopRightPosition: CGPoint?,
patternAlpha: CGFloat,
displayProgress: Bool
) {
self.size = size
@ -481,6 +483,7 @@ public final class MessageInlineBlockBackgroundView: UIView {
self.backgroundColor = backgroundColor
self.pattern = pattern
self.patternTopRightPosition = patternTopRightPosition
self.patternAlpha = patternAlpha
self.displayProgress = displayProgress
}
}
@ -612,6 +615,7 @@ public final class MessageInlineBlockBackgroundView: UIView {
backgroundColor: UIColor?,
pattern: Pattern?,
patternTopRightPosition: CGPoint? = nil,
patternAlpha: CGFloat = 1.0,
animation: ListViewItemUpdateAnimation
) {
let params = Params(
@ -623,6 +627,7 @@ public final class MessageInlineBlockBackgroundView: UIView {
backgroundColor: backgroundColor,
pattern: pattern,
patternTopRightPosition: patternTopRightPosition,
patternAlpha: patternAlpha,
displayProgress: self.displayProgress
)
if self.params == params {
@ -766,7 +771,7 @@ public final class MessageInlineBlockBackgroundView: UIView {
patternContentLayer.frame = CGRect(origin: CGPoint(x: patternOrigin.x - placement.position.x / 3.0 - itemSize.width * 0.5, y: patternOrigin.y + placement.position.y / 3.0 - itemSize.height * 0.5), size: itemSize)
var alphaFraction = abs(placement.position.x / 3.0) / min(500.0, size.width)
alphaFraction = min(1.0, max(0.0, alphaFraction))
patternContentLayer.opacity = 0.3 * Float(1.0 - alphaFraction)
patternContentLayer.opacity = 0.3 * Float(1.0 - alphaFraction) * Float(patternAlpha)
maxIndex += 1
}

View File

@ -215,7 +215,7 @@ public final class EntityKeyboardAnimationData: Equatable {
self.isTemplate = isTemplate
}
public convenience init(file: TelegramMediaFile, isReaction: Bool = false) {
public convenience init(file: TelegramMediaFile, isReaction: Bool = false, partialReference: PartialMediaReference? = nil) {
let type: ItemType
if file.isVideoSticker || file.isVideoEmoji {
type = .video
@ -225,7 +225,14 @@ public final class EntityKeyboardAnimationData: Equatable {
type = .still
}
let isTemplate = file.isCustomTemplateEmoji
self.init(id: .file(file.fileId), type: type, resource: .standalone(resource: file.resource), dimensions: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), immediateThumbnailData: file.immediateThumbnailData, isReaction: isReaction, isTemplate: isTemplate)
let resourceReference: MediaResourceReference
if let partialReference {
resourceReference = partialReference.mediaReference(file).resourceReference(file.resource)
} else {
resourceReference = .standalone(resource: file.resource)
}
self.init(id: .file(file.fileId), type: type, resource: resourceReference, dimensions: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), immediateThumbnailData: file.immediateThumbnailData, isReaction: isReaction, isTemplate: isTemplate)
}
public static func ==(lhs: EntityKeyboardAnimationData, rhs: EntityKeyboardAnimationData) -> Bool {

View File

@ -1787,6 +1787,7 @@ public extension EmojiPagerContentComponent {
}
if let savedStickers = savedStickers {
let groupId = "saved"
for item in savedStickers.items {
guard let item = item.contents.get(SavedStickerItem.self) else {
continue
@ -1800,7 +1801,7 @@ public extension EmojiPagerContentComponent {
tintMode = .primary
}
let animationData = EntityKeyboardAnimationData(file: item.file)
let animationData = EntityKeyboardAnimationData(file: item.file, partialReference: .savedSticker)
let resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
@ -1810,7 +1811,6 @@ public extension EmojiPagerContentComponent {
tintMode: tintMode
)
let groupId = "saved"
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
} else {
@ -1836,7 +1836,7 @@ public extension EmojiPagerContentComponent {
tintMode = .primary
}
let animationData = EntityKeyboardAnimationData(file: item.media)
let animationData = EntityKeyboardAnimationData(file: item.media, partialReference: .recentSticker)
let resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),

View File

@ -587,12 +587,6 @@ final class MediaEditorScreenComponent: Component {
view.layer.animateAlpha(from: view.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2)
}
if let view = self.scrubber?.view {
view.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: 44.0), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
view.layer.animateAlpha(from: view.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2)
}
}
if let view = self.saveButton.view {
@ -615,6 +609,36 @@ final class MediaEditorScreenComponent: Component {
view.layer.animateAlpha(from: view.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2)
}
if let view = self.undoButton.view {
transition.setAlpha(view: view, alpha: 0.0)
transition.setScale(view: view, scale: 0.1)
}
if let view = self.eraseButton.view {
transition.setAlpha(view: view, alpha: 0.0)
transition.setScale(view: view, scale: 0.1)
}
if let view = self.restoreButton.view {
transition.setAlpha(view: view, alpha: 0.0)
transition.setScale(view: view, scale: 0.1)
}
if let view = self.outlineButton.view {
transition.setAlpha(view: view, alpha: 0.0)
transition.setScale(view: view, scale: 0.1)
}
if let view = self.cutoutButton.view {
transition.setAlpha(view: view, alpha: 0.0)
transition.setScale(view: view, scale: 0.1)
}
if let view = self.textSize.view {
transition.setAlpha(view: view, alpha: 0.0)
transition.setScale(view: view, scale: 0.1)
}
}
func animateOutToTool(inPlace: Bool, transition: Transition) {
@ -1978,7 +2002,9 @@ final class MediaEditorScreenComponent: Component {
var hasRestoreButton = false
var hasOutlineButton = false
if let canCutout = controller.node.canCutout {
if let subject = controller.node.subject, case .empty = subject {
} else if let canCutout = controller.node.canCutout {
if controller.node.isCutout || controller.node.stickerMaskDrawingView?.internalState.canUndo == true {
hasUndoButton = true
}
@ -2002,7 +2028,7 @@ final class MediaEditorScreenComponent: Component {
content: AnyComponent(CutoutButtonContentComponent(
backgroundColor: UIColor(rgb: 0xffffff, alpha: 0.18),
icon: state.image(.undo),
title: "Undo"
title: environment.strings.MediaEditor_Undo
)),
effectAlignment: .center,
action: {
@ -2048,7 +2074,7 @@ final class MediaEditorScreenComponent: Component {
content: AnyComponent(CutoutButtonContentComponent(
backgroundColor: UIColor(rgb: 0xffffff, alpha: 0.18),
icon: state.image(.cutout),
title: "Cut Out an Object"
title: environment.strings.MediaEditor_Cutout
)),
effectAlignment: .center,
action: {
@ -2100,7 +2126,7 @@ final class MediaEditorScreenComponent: Component {
content: AnyComponent(CutoutButtonContentComponent(
backgroundColor: UIColor(rgb: 0xffffff, alpha: 0.18),
icon: state.image(.erase),
title: "Erase",
title: environment.strings.MediaEditor_Erase,
minWidth: 160.0,
selected: component.isDisplayingTool == .cutoutErase
)),
@ -2123,7 +2149,7 @@ final class MediaEditorScreenComponent: Component {
content: AnyComponent(CutoutButtonContentComponent(
backgroundColor: UIColor(rgb: 0xffffff, alpha: 0.18),
icon: state.image(.restore),
title: "Restore",
title: environment.strings.MediaEditor_Restore,
minWidth: 160.0,
selected: component.isDisplayingTool == .cutoutRestore
)),
@ -2204,7 +2230,7 @@ final class MediaEditorScreenComponent: Component {
content: AnyComponent(CutoutButtonContentComponent(
backgroundColor: UIColor(rgb: 0xffffff, alpha: 0.18),
icon: state.image(.outline),
title: "Add Outline",
title: environment.strings.MediaEditor_Outline,
minWidth: 160.0,
selected: isOutlineActive
)),
@ -2898,7 +2924,6 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
if case .message = subject, self.context.sharedContext.currentPresentationData.with({$0}).autoNightModeTriggered {
mediaEditor.setNightTheme(true)
}
mediaEditor.attachPreviewView(self.previewView)
mediaEditor.valuesUpdated = { [weak self] values in
if let self, let controller = self.controller, values.gradientColors != nil, controller.previousSavedValues != values {
if !isSavingAvailable && controller.previousSavedValues == nil {
@ -2936,6 +2961,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
self.controller?.stickerRecommendedEmoji = emojiForClasses(classes.map { $0.0 })
}
mediaEditor.attachPreviewView(self.previewView)
if case .empty = effectiveSubject {
self.stickerMaskDrawingView?.emptyColor = .black
@ -4641,7 +4667,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
let controller = StickerPickerScreen(context: self.context, inputData: self.stickerPickerInputData.get(), forceDark: true, defaultToEmoji: self.defaultToEmoji, hasGifs: true, hasInteractiveStickers: hasInteractiveStickers)
controller.completion = { [weak self] content in
if let self {
guard let self else {
return false
}
if let content {
if case let .file(file, _) = content {
self.defaultToEmoji = file.media.isCustomEmoji
@ -4664,7 +4692,6 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
self.stickerScreen = nil
self.mediaEditor?.maybeUnpauseVideo()
}
return true
}
controller.customModalStyleOverlayTransitionFactorUpdated = { [weak self, weak controller] transition in
@ -6506,13 +6533,13 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.uploadSticker(file, action: .addToFavorites)
})
})))
menuItems.append(.action(ContextMenuActionItem(text: "Add to Sticker Set", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddSticker"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in
menuItems.append(.action(ContextMenuActionItem(text: presentationData.strings.MediaEditor_AddToStickerPack, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddSticker"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in
guard let self else {
return
}
var contextItems: [ContextMenuItem] = []
contextItems.append(.action(ContextMenuActionItem(text: "Back", icon: { theme in
contextItems.append(.action(ContextMenuActionItem(text: presentationData.strings.Common_Back, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor)
}, iconPosition: .left, action: { c, _ in
c.popItems()
@ -6520,7 +6547,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
contextItems.append(.separator)
contextItems.append(.action(ContextMenuActionItem(text: "New Sticker Set", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddCircle"), color: theme.contextMenu.primaryColor) }, iconPosition: .left, action: { [weak self] _, f in
contextItems.append(.action(ContextMenuActionItem(text: presentationData.strings.MediaEditor_CreateNewPack, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddCircle"), color: theme.contextMenu.primaryColor) }, iconPosition: .left, action: { [weak self] _, f in
if let self {
self.presentCreateStickerPack(file: file, completion: {
f(.default)
@ -6579,7 +6606,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
c.pushItems(items: .single(items))
})))
case .editing:
menuItems.append(.action(ContextMenuActionItem(text: "Replace Sticker", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Replace"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
menuItems.append(.action(ContextMenuActionItem(text: presentationData.strings.MediaEditor_ReplaceSticker, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Replace"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
guard let self else {
return
}
@ -6601,7 +6628,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
})
})))
case .addingToPack:
menuItems.append(.action(ContextMenuActionItem(text: "Add to Sticker Set", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddSticker"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in
menuItems.append(.action(ContextMenuActionItem(text: presentationData.strings.MediaEditor_AddToStickerPack, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddSticker"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in
guard let self else {
return
}
@ -6691,11 +6718,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
private func presentCreateStickerPack(file: TelegramMediaFile, completion: @escaping () -> Void) {
//TODO:localize
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme)
var dismissImpl: (() -> Void)?
let controller = stickerPackEditTitleController(context: self.context, forceDark: true, title: "New Sticker Set", text: "Choose a name for your sticker set.", placeholder: presentationData.strings.ImportStickerPack_NamePlaceholder, actionTitle: presentationData.strings.Common_Done, value: nil, maxLength: 64, apply: { [weak self] title in
let controller = stickerPackEditTitleController(context: self.context, forceDark: true, title: presentationData.strings.MediaEditor_NewStickerPack_Title, text: presentationData.strings.MediaEditor_NewStickerPack_Text, placeholder: presentationData.strings.ImportStickerPack_NamePlaceholder, actionTitle: presentationData.strings.Common_Done, value: nil, maxLength: 64, apply: { [weak self] title in
guard let self else {
return
}
@ -6942,7 +6968,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
(navigationController.viewControllers.last as? ViewController)?.present(controller, in: .window(.root))
Queue.mainQueue().after(0.1) {
controller.present(UndoOverlayController(presentationData: presentationData, content: .sticker(context: self.context, file: file, loop: true, title: nil, text: "Sticker added to **\(title)** sticker set.", undoText: nil, customAction: nil), elevatedLayout: false, action: { _ in return false }), in: .current)
controller.present(UndoOverlayController(presentationData: presentationData, content: .sticker(context: self.context, file: file, loop: true, title: nil, text: presentationData.strings.StickerPack_StickerAdded(title).string, undoText: nil, customAction: nil), elevatedLayout: false, action: { _ in return false }), in: .current)
}
}
}

View File

@ -259,9 +259,6 @@ private final class StickerSelectionComponent: Component {
interaction: nil,
chatPeerId: nil,
present: { c, a in
let _ = c
let _ = a
// controller?.presentInGlobalOverlay(c, with: a)
}
)
@ -309,7 +306,7 @@ private final class StickerSelectionComponent: Component {
switchToGifSubject: { _ in },
reorderItems: { _, _ in },
makeSearchContainerNode: { [weak self] content in
guard let self, let interaction = self.interaction, let inputNodeInteraction = self.inputNodeInteraction else {
guard let self, let interaction = self.interaction, let inputNodeInteraction = self.inputNodeInteraction, let component = self.component, let controller = component.getController() else {
return nil
}
@ -322,7 +319,7 @@ private final class StickerSelectionComponent: Component {
}
var presentationData = context.sharedContext.currentPresentationData.with { $0 }
if controller?.forceDark == true {
if controller.forceDark == true {
presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)
}
let searchContainerNode = PaneSearchContainerNode(
@ -440,7 +437,7 @@ public class StickerPickerScreen: ViewController {
let containerView: UIView
let hostView: ComponentHostView<Empty>
private var content: StickerPickerInputData?
fileprivate var content: StickerPickerInputData?
private let contentDisposable = MetaDisposable()
private var hasRecentGifsDisposable: Disposable?
fileprivate let trendingGifsPromise = Promise<ChatMediaInputGifPaneTrendingState?>(nil)
@ -534,6 +531,7 @@ public class StickerPickerScreen: ViewController {
self.wrappingView.addSubview(self.containerView)
self.containerView.addSubview(self.hostView)
if controller.hasInteractiveStickers {
self.storyStickersContentView = StoryStickersContentView(frame: .zero)
self.storyStickersContentView?.locationAction = { [weak self] in
self?.controller?.presentLocationPicker()
@ -547,6 +545,7 @@ public class StickerPickerScreen: ViewController {
self.storyStickersContentView?.cameraAction = { [weak self] in
self?.controller?.addCamera()
}
}
let gifItems: Signal<EntityKeyboardGifContent?, NoError>
if controller.hasGifs {
@ -654,11 +653,11 @@ public class StickerPickerScreen: ViewController {
self.contentDisposable.set(data.start(next: { [weak self] inputData, gifData, stickerSearchState, emojiSearchState in
if let strongSelf = self {
let presentationData = strongSelf.presentationData
guard var inputData = inputData as? StickerPickerInputData else {
return
}
let presentationData = strongSelf.presentationData
inputData.gifs = gifData?.component
if let emoji = inputData.emoji {
@ -1495,7 +1494,6 @@ public class StickerPickerScreen: ViewController {
return
}
if group.items.isEmpty && !result.isFinalResult {
//strongSelf.stickerSearchStateValue.isSearching = true
strongSelf.stickerSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: [
EmojiPagerContentComponent.ItemGroup(
supergroupId: "search",
@ -1535,7 +1533,7 @@ public class StickerPickerScreen: ViewController {
customLayout: nil,
externalBackground: nil,
externalExpansionView: nil,
customContentView: controller.hasInteractiveStickers ? self.storyStickersContentView : nil,
customContentView: self.storyStickersContentView,
useOpaqueTheme: false,
hideBackground: true,
stateContext: nil,
@ -2050,8 +2048,7 @@ public class StickerPickerScreen: ViewController {
self.statusBar.statusBarStyle = .Ignore
if expanded {
//TODO:localize
self.title = "Choose Sticker"
self.title = presentationData.strings.Stickers_ChooseSticker_Title
self.navigationPresentation = .modal
}
}

View File

@ -1891,8 +1891,10 @@ final class StoryItemSetContainerSendMessage {
guard let self, let view else {
return
}
if let cameraView = cameraView as? TGAttachmentCameraView {
self.openCamera(view: view, peer: peer, replyToMessageId: replyToMessageId, replyToStoryId: replyToStoryId, cameraView: cameraView)
}
}
controller.presentWebSearch = { [weak self, weak view, weak controller] mediaGroups, activateOnDisplay in
guard let self, let view, let controller else {
return

View File

@ -31,6 +31,7 @@ import PremiumGiftAttachmentScreen
import TelegramCallsUI
import AutomaticBusinessMessageSetupScreen
import MediaEditorScreen
import CameraScreen
extension ChatControllerImpl {
enum AttachMenuSubject {
@ -1160,8 +1161,10 @@ extension ChatControllerImpl {
}
let mediaPickerContext = controller.mediaPickerContext
controller.openCamera = { [weak self] cameraView in
if let cameraView = cameraView as? TGAttachmentCameraView {
self?.openCamera(cameraView: cameraView)
}
}
controller.presentWebSearch = { [weak self, weak controller] mediaGroups, activateOnDisplay in
self?.presentWebSearch(editingMessage: false, attachment: true, activateOnDisplay: activateOnDisplay, present: { [weak controller] c, a in
controller?.present(c, in: .current)
@ -1726,8 +1729,8 @@ extension ChatControllerImpl {
var dismissImpl: (() -> Void)?
let mainController = self.context.sharedContext.makeStickerMediaPickerScreen(
context: self.context,
getSourceRect: { return .zero },
completion: { [weak self] result, transitionView, transitionRect, transitionImage, transitionOut, dismissed in
getSourceRect: { return nil },
completion: { [weak self] result, transitionView, transitionRect, transitionImage, fromCamera, transitionOut, cancelled in
guard let self else {
return
}
@ -1736,6 +1739,18 @@ extension ChatControllerImpl {
subject = .single(.asset(asset))
} else if let image = result as? UIImage {
subject = .single(.image(image, PixelDimensions(image.size), nil, .bottomRight))
} else if let result = result as? Signal<CameraScreen.Result, NoError> {
subject = result
|> map { value -> MediaEditorScreen.Subject? in
switch value {
case .pendingImage:
return nil
case let .image(image):
return .image(image.image, PixelDimensions(image.image.size), nil, .topLeft)
default:
return nil
}
}
} else {
subject = .single(.empty(PixelDimensions(width: 1080, height: 1920)))
}
@ -1744,7 +1759,7 @@ extension ChatControllerImpl {
context: self.context,
mode: .stickerEditor(mode: .generic),
subject: subject,
transitionIn: transitionView.flatMap({ .gallery(
transitionIn: fromCamera ? .camera : transitionView.flatMap({ .gallery(
MediaEditorScreen.TransitionIn.GalleryTransitionIn(
sourceView: $0,
sourceRect: transitionRect,
@ -1772,6 +1787,9 @@ extension ChatControllerImpl {
}
} as (MediaEditorScreen.Result, @escaping (@escaping () -> Void) -> Void) -> Void
)
editorController.cancelled = { _ in
cancelled()
}
editorController.sendSticker = { [weak self] file, sourceView, sourceRect in
return self?.interfaceInteraction?.sendSticker(file, true, sourceView, sourceRect, nil, []) ?? false
}
@ -1780,7 +1798,13 @@ extension ChatControllerImpl {
dismissed: {}
)
dismissImpl = { [weak mainController] in
mainController?.dismiss()
if let mainController, let navigationController = mainController.navigationController {
var viewControllers = navigationController.viewControllers
viewControllers = viewControllers.filter { c in
return !(c is CameraScreen) && c !== mainController
}
navigationController.setViewControllers(viewControllers, animated: false)
}
}
mainController.navigationPresentation = .flatModal
mainController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)

View File

@ -2421,7 +2421,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return storyMediaPickerController(context: context, getSourceRect: getSourceRect, completion: completion, dismissed: dismissed, groupsPresented: groupsPresented)
}
public func makeStickerMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any?, UIView?, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController {
public func makeStickerMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController {
return stickerMediaPickerController(context: context, getSourceRect: getSourceRect, completion: completion, dismissed: dismissed)
}

View File

@ -300,6 +300,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
var showDraftTooltipImpl: (() -> Void)?
let cameraController = CameraScreen(
context: context,
mode: .story,
transitionIn: transitionIn.flatMap {
if let sourceView = $0.sourceView {
return CameraScreen.TransitionIn(