mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Camera and editor improvements
This commit is contained in:
parent
97e871fa22
commit
d38e81a324
@ -2454,9 +2454,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
initialFocusedId = AnyHashable(peer.id)
|
initialFocusedId = AnyHashable(peer.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !initialContent.contains(where: { slice in
|
if initialFocusedId == AnyHashable(self.context.account.peerId), let firstItem = initialContent.first, firstItem.id == initialFocusedId && firstItem.items.isEmpty {
|
||||||
return !slice.items.isEmpty
|
|
||||||
}) {
|
|
||||||
if let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {
|
if let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {
|
||||||
rootController.openStoryCamera(transitionIn: cameraTransitionIn, transitionOut: { [weak self] _ in
|
rootController.openStoryCamera(transitionIn: cameraTransitionIn, transitionOut: { [weak self] _ in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
|
@ -69,6 +69,7 @@ swift_library(
|
|||||||
"//submodules/Camera",
|
"//submodules/Camera",
|
||||||
"//submodules/Components/MultilineTextComponent",
|
"//submodules/Components/MultilineTextComponent",
|
||||||
"//submodules/Components/BlurredBackgroundComponent",
|
"//submodules/Components/BlurredBackgroundComponent",
|
||||||
|
"//submodules/Components/LottieAnimationComponent:LottieAnimationComponent",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -14,6 +14,7 @@ import Camera
|
|||||||
import MultilineTextComponent
|
import MultilineTextComponent
|
||||||
import BlurredBackgroundComponent
|
import BlurredBackgroundComponent
|
||||||
import Photos
|
import Photos
|
||||||
|
import LottieAnimationComponent
|
||||||
|
|
||||||
let videoRedColor = UIColor(rgb: 0xff3b30)
|
let videoRedColor = UIColor(rgb: 0xff3b30)
|
||||||
|
|
||||||
@ -284,17 +285,39 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
.cornerRadius(20.0)
|
.cornerRadius(20.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
let flashIconName: String
|
||||||
|
switch state.cameraState.flashMode {
|
||||||
|
case .off:
|
||||||
|
flashIconName = "flash_off"
|
||||||
|
case .on:
|
||||||
|
flashIconName = "flash_on"
|
||||||
|
case .auto:
|
||||||
|
flashIconName = "flash_auto"
|
||||||
|
@unknown default:
|
||||||
|
flashIconName = "flash_off"
|
||||||
|
}
|
||||||
|
|
||||||
let flashButton = flashButton.update(
|
let flashButton = flashButton.update(
|
||||||
component: CameraButton(
|
component: CameraButton(
|
||||||
content: AnyComponent(Image(
|
content: AnyComponent(
|
||||||
image: state.image(.flash)
|
LottieAnimationComponent(
|
||||||
)),
|
animation: LottieAnimationComponent.AnimationItem(
|
||||||
|
name: flashIconName,
|
||||||
|
mode: .animating(loop: false),
|
||||||
|
range: nil
|
||||||
|
),
|
||||||
|
colors: [:],
|
||||||
|
size: CGSize(width: 40.0, height: 40.0)
|
||||||
|
)
|
||||||
|
),
|
||||||
action: { [weak state] in
|
action: { [weak state] in
|
||||||
guard let state else {
|
guard let state else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if state.cameraState.flashMode == .off {
|
if state.cameraState.flashMode == .off {
|
||||||
state.camera.setFlashMode(.on)
|
state.camera.setFlashMode(.on)
|
||||||
|
} else if state.cameraState.flashMode == .on {
|
||||||
|
state.camera.setFlashMode(.auto)
|
||||||
} else {
|
} else {
|
||||||
state.camera.setFlashMode(.off)
|
state.camera.setFlashMode(.off)
|
||||||
}
|
}
|
||||||
@ -885,6 +908,13 @@ public class CameraScreen: ViewController {
|
|||||||
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let view = self.componentHost.findTaggedView(tag: flashButtonTag) {
|
||||||
|
view.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
|
||||||
|
view.layer.shadowRadius = 4.0
|
||||||
|
view.layer.shadowColor = UIColor.black.cgColor
|
||||||
|
view.layer.shadowOpacity = 0.2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateOut(completion: @escaping () -> Void) {
|
func animateOut(completion: @escaping () -> Void) {
|
||||||
@ -928,11 +958,11 @@ public class CameraScreen: ViewController {
|
|||||||
func animateOutToEditor() {
|
func animateOutToEditor() {
|
||||||
let transition = Transition(animation: .curve(duration: 0.2, curve: .easeInOut))
|
let transition = Transition(animation: .curve(duration: 0.2, curve: .easeInOut))
|
||||||
if let view = self.componentHost.findTaggedView(tag: cancelButtonTag) {
|
if let view = self.componentHost.findTaggedView(tag: cancelButtonTag) {
|
||||||
transition.setScale(view: view, scale: 0.1)
|
view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2)
|
||||||
transition.setAlpha(view: view, alpha: 0.0)
|
transition.setAlpha(view: view, alpha: 0.0)
|
||||||
}
|
}
|
||||||
if let view = self.componentHost.findTaggedView(tag: flashButtonTag) {
|
if let view = self.componentHost.findTaggedView(tag: flashButtonTag) {
|
||||||
transition.setScale(view: view, scale: 0.1)
|
view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2)
|
||||||
transition.setAlpha(view: view, alpha: 0.0)
|
transition.setAlpha(view: view, alpha: 0.0)
|
||||||
}
|
}
|
||||||
if let view = self.componentHost.findTaggedView(tag: zoomControlTag) {
|
if let view = self.componentHost.findTaggedView(tag: zoomControlTag) {
|
||||||
@ -973,15 +1003,15 @@ public class CameraScreen: ViewController {
|
|||||||
|
|
||||||
let transition = Transition(animation: .curve(duration: 0.2, curve: .easeInOut))
|
let transition = Transition(animation: .curve(duration: 0.2, curve: .easeInOut))
|
||||||
if let view = self.componentHost.findTaggedView(tag: cancelButtonTag) {
|
if let view = self.componentHost.findTaggedView(tag: cancelButtonTag) {
|
||||||
transition.setScale(view: view, scale: 1.0)
|
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
|
||||||
transition.setAlpha(view: view, alpha: 1.0)
|
transition.setAlpha(view: view, alpha: 1.0)
|
||||||
}
|
}
|
||||||
if let view = self.componentHost.findTaggedView(tag: flashButtonTag) {
|
if let view = self.componentHost.findTaggedView(tag: flashButtonTag) {
|
||||||
transition.setScale(view: view, scale: 1.0)
|
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
|
||||||
transition.setAlpha(view: view, alpha: 1.0)
|
transition.setAlpha(view: view, alpha: 1.0)
|
||||||
}
|
}
|
||||||
if let view = self.componentHost.findTaggedView(tag: zoomControlTag) {
|
if let view = self.componentHost.findTaggedView(tag: zoomControlTag) {
|
||||||
transition.setScale(view: view, scale: 1.0)
|
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
|
||||||
transition.setAlpha(view: view, alpha: 1.0)
|
transition.setAlpha(view: view, alpha: 1.0)
|
||||||
}
|
}
|
||||||
if let view = self.componentHost.findTaggedView(tag: captureControlsTag) as? CaptureControlsComponent.View {
|
if let view = self.componentHost.findTaggedView(tag: captureControlsTag) as? CaptureControlsComponent.View {
|
||||||
@ -1143,6 +1173,7 @@ public class CameraScreen: ViewController {
|
|||||||
let controller = self.context.sharedContext.makeMediaPickerScreen(context: self.context, completion: { [weak self] asset in
|
let controller = self.context.sharedContext.makeMediaPickerScreen(context: self.context, completion: { [weak self] asset in
|
||||||
dismissGalleryControllerImpl?()
|
dismissGalleryControllerImpl?()
|
||||||
if let self {
|
if let self {
|
||||||
|
self.node.animateOutToEditor()
|
||||||
self.completion(.single(.asset(asset)))
|
self.completion(.single(.asset(asset)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -16,7 +16,7 @@ final class ImageTextureSource: TextureSource {
|
|||||||
let textureLoader = MTKTextureLoader(device: device)
|
let textureLoader = MTKTextureLoader(device: device)
|
||||||
self.textureLoader = textureLoader
|
self.textureLoader = textureLoader
|
||||||
|
|
||||||
self.texture = try? textureLoader.newTexture(cgImage: cgImage, options: nil)
|
self.texture = try? textureLoader.newTexture(cgImage: cgImage, options: [.SRGB : false])
|
||||||
}
|
}
|
||||||
|
|
||||||
func start() {
|
func start() {
|
||||||
|
@ -113,7 +113,7 @@ final class MediaEditorComposer {
|
|||||||
}
|
}
|
||||||
if self.filteredImage == nil, let device = self.device, let cgImage = inputImage.cgImage {
|
if self.filteredImage == nil, let device = self.device, let cgImage = inputImage.cgImage {
|
||||||
let textureLoader = MTKTextureLoader(device: device)
|
let textureLoader = MTKTextureLoader(device: device)
|
||||||
if let texture = try? textureLoader.newTexture(cgImage: cgImage) {
|
if let texture = try? textureLoader.newTexture(cgImage: cgImage, options: [.SRGB : false]) {
|
||||||
self.renderer.consumeTexture(texture, rotation: .rotate0Degrees)
|
self.renderer.consumeTexture(texture, rotation: .rotate0Degrees)
|
||||||
self.renderer.renderFrame()
|
self.renderer.renderFrame()
|
||||||
|
|
||||||
|
@ -44,7 +44,6 @@ public final class MediaEditorVideoAVAssetWriter: MediaEditorVideoExportWriter {
|
|||||||
private var writer: AVAssetWriter?
|
private var writer: AVAssetWriter?
|
||||||
private var videoInput: AVAssetWriterInput?
|
private var videoInput: AVAssetWriterInput?
|
||||||
private var audioInput: AVAssetWriterInput?
|
private var audioInput: AVAssetWriterInput?
|
||||||
|
|
||||||
private var adaptor: AVAssetWriterInputPixelBufferAdaptor!
|
private var adaptor: AVAssetWriterInputPixelBufferAdaptor!
|
||||||
|
|
||||||
func setup(configuration: MediaEditorVideoExport.Configuration, outputPath: String) {
|
func setup(configuration: MediaEditorVideoExport.Configuration, outputPath: String) {
|
||||||
@ -83,8 +82,6 @@ public final class MediaEditorVideoAVAssetWriter: MediaEditorVideoExportWriter {
|
|||||||
|
|
||||||
if writer.canAdd(videoInput) {
|
if writer.canAdd(videoInput) {
|
||||||
writer.add(videoInput)
|
writer.add(videoInput)
|
||||||
} else {
|
|
||||||
//throw Error.cannotAddVideoInput
|
|
||||||
}
|
}
|
||||||
self.videoInput = videoInput
|
self.videoInput = videoInput
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,9 @@ swift_library(
|
|||||||
"//submodules/Components/LottieAnimationComponent:LottieAnimationComponent",
|
"//submodules/Components/LottieAnimationComponent:LottieAnimationComponent",
|
||||||
"//submodules/TelegramUI/Components/MessageInputPanelComponent",
|
"//submodules/TelegramUI/Components/MessageInputPanelComponent",
|
||||||
"//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode",
|
"//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode",
|
||||||
|
"//submodules/TooltipUI",
|
||||||
|
"//submodules/Components/BlurredBackgroundComponent",
|
||||||
|
"//submodules/AvatarNode",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -16,6 +16,9 @@ import Photos
|
|||||||
import LottieAnimationComponent
|
import LottieAnimationComponent
|
||||||
import MessageInputPanelComponent
|
import MessageInputPanelComponent
|
||||||
import EntityKeyboard
|
import EntityKeyboard
|
||||||
|
import TooltipUI
|
||||||
|
import BlurredBackgroundComponent
|
||||||
|
import AvatarNode
|
||||||
|
|
||||||
enum DrawingScreenType {
|
enum DrawingScreenType {
|
||||||
case drawing
|
case drawing
|
||||||
@ -23,6 +26,7 @@ enum DrawingScreenType {
|
|||||||
case sticker
|
case sticker
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private let privacyButtonTag = GenericComponentViewTag()
|
||||||
private let muteButtonTag = GenericComponentViewTag()
|
private let muteButtonTag = GenericComponentViewTag()
|
||||||
private let saveButtonTag = GenericComponentViewTag()
|
private let saveButtonTag = GenericComponentViewTag()
|
||||||
|
|
||||||
@ -31,17 +35,20 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
|
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let mediaEditor: MediaEditor?
|
let mediaEditor: MediaEditor?
|
||||||
|
let privacy: EngineStoryPrivacy
|
||||||
let openDrawing: (DrawingScreenType) -> Void
|
let openDrawing: (DrawingScreenType) -> Void
|
||||||
let openTools: () -> Void
|
let openTools: () -> Void
|
||||||
|
|
||||||
init(
|
init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
mediaEditor: MediaEditor?,
|
mediaEditor: MediaEditor?,
|
||||||
|
privacy: EngineStoryPrivacy,
|
||||||
openDrawing: @escaping (DrawingScreenType) -> Void,
|
openDrawing: @escaping (DrawingScreenType) -> Void,
|
||||||
openTools: @escaping () -> Void
|
openTools: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.mediaEditor = mediaEditor
|
self.mediaEditor = mediaEditor
|
||||||
|
self.privacy = privacy
|
||||||
self.openDrawing = openDrawing
|
self.openDrawing = openDrawing
|
||||||
self.openTools = openTools
|
self.openTools = openTools
|
||||||
}
|
}
|
||||||
@ -50,6 +57,9 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
if lhs.context !== rhs.context {
|
if lhs.context !== rhs.context {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.privacy != rhs.privacy {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,8 +149,9 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
|
|
||||||
private let scrubber = ComponentView<Empty>()
|
private let scrubber = ComponentView<Empty>()
|
||||||
|
|
||||||
private let saveButton = ComponentView<Empty>()
|
private let privacyButton = ComponentView<Empty>()
|
||||||
private let muteButton = ComponentView<Empty>()
|
private let muteButton = ComponentView<Empty>()
|
||||||
|
private let saveButton = ComponentView<Empty>()
|
||||||
|
|
||||||
private var component: MediaEditorScreenComponent?
|
private var component: MediaEditorScreenComponent?
|
||||||
private weak var state: State?
|
private weak var state: State?
|
||||||
@ -199,6 +210,11 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
|
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let view = self.privacyButton.view {
|
||||||
|
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateOutToCamera() {
|
func animateOutToCamera() {
|
||||||
@ -243,6 +259,11 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
transition.setAlpha(view: view, alpha: 0.0)
|
transition.setAlpha(view: view, alpha: 0.0)
|
||||||
transition.setScale(view: view, scale: 0.1)
|
transition.setScale(view: view, scale: 0.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let view = self.privacyButton.view {
|
||||||
|
transition.setAlpha(view: view, alpha: 0.0)
|
||||||
|
transition.setScale(view: view, scale: 0.1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateOutToTool() {
|
func animateOutToTool() {
|
||||||
@ -285,6 +306,11 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
transition.setAlpha(view: view, alpha: 0.0)
|
transition.setAlpha(view: view, alpha: 0.0)
|
||||||
transition.setScale(view: view, scale: 0.1)
|
transition.setScale(view: view, scale: 0.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let view = self.privacyButton.view {
|
||||||
|
transition.setAlpha(view: view, alpha: 0.0)
|
||||||
|
transition.setScale(view: view, scale: 0.1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateInFromTool() {
|
func animateInFromTool() {
|
||||||
@ -327,6 +353,11 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
transition.setAlpha(view: view, alpha: 1.0)
|
transition.setAlpha(view: view, alpha: 1.0)
|
||||||
transition.setScale(view: view, scale: 1.0)
|
transition.setScale(view: view, scale: 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let view = self.privacyButton.view {
|
||||||
|
transition.setAlpha(view: view, alpha: 1.0)
|
||||||
|
transition.setScale(view: view, scale: 1.0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(component: MediaEditorScreenComponent, availableSize: CGSize, state: State, environment: Environment<ViewControllerComponentContainer.Environment>, transition: Transition) -> CGSize {
|
func update(component: MediaEditorScreenComponent, availableSize: CGSize, state: State, environment: Environment<ViewControllerComponentContainer.Environment>, transition: Transition) -> CGSize {
|
||||||
@ -592,6 +623,49 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
transition.setFrame(view: inputPanelView, frame: inputPanelFrame)
|
transition.setFrame(view: inputPanelView, frame: inputPanelFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let privacyText: String
|
||||||
|
switch component.privacy.base {
|
||||||
|
case .everyone:
|
||||||
|
privacyText = "Everyone"
|
||||||
|
case .closeFriends:
|
||||||
|
privacyText = "Close Friends"
|
||||||
|
case .contacts:
|
||||||
|
privacyText = "Contacts"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let privacyButtonSize = self.privacyButton.update(
|
||||||
|
transition: transition,
|
||||||
|
component: AnyComponent(Button(
|
||||||
|
content: AnyComponent(
|
||||||
|
PrivacyButtonComponent(
|
||||||
|
icon: UIImage(bundleImageName: "Media Editor/Recipient")!,
|
||||||
|
text: privacyText
|
||||||
|
)
|
||||||
|
),
|
||||||
|
action: {
|
||||||
|
if let controller = environment.controller() as? MediaEditorScreen {
|
||||||
|
controller.presentPrivacySettings()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).tagged(privacyButtonTag)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: 44.0, height: 44.0)
|
||||||
|
)
|
||||||
|
let privacyButtonFrame = CGRect(
|
||||||
|
origin: CGPoint(x: 16.0, y: environment.safeInsets.top + 20.0 - inputPanelOffset),
|
||||||
|
size: privacyButtonSize
|
||||||
|
)
|
||||||
|
if let privacyButtonView = self.privacyButton.view {
|
||||||
|
if privacyButtonView.superview == nil {
|
||||||
|
self.addSubview(privacyButtonView)
|
||||||
|
}
|
||||||
|
transition.setPosition(view: privacyButtonView, position: privacyButtonFrame.center)
|
||||||
|
transition.setBounds(view: privacyButtonView, bounds: CGRect(origin: .zero, size: privacyButtonFrame.size))
|
||||||
|
transition.setScale(view: privacyButtonView, scale: self.inputPanelExternalState.isEditing ? 0.01 : 1.0)
|
||||||
|
transition.setAlpha(view: privacyButtonView, alpha: self.inputPanelExternalState.isEditing ? 0.0 : 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
let saveButtonSize = self.saveButton.update(
|
let saveButtonSize = self.saveButton.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(Button(
|
component: AnyComponent(Button(
|
||||||
@ -625,9 +699,9 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
if let saveButtonView = self.saveButton.view {
|
if let saveButtonView = self.saveButton.view {
|
||||||
if saveButtonView.superview == nil {
|
if saveButtonView.superview == nil {
|
||||||
saveButtonView.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
|
saveButtonView.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
|
||||||
saveButtonView.layer.shadowRadius = 2.0
|
saveButtonView.layer.shadowRadius = 4.0
|
||||||
saveButtonView.layer.shadowColor = UIColor.black.cgColor
|
saveButtonView.layer.shadowColor = UIColor.black.cgColor
|
||||||
saveButtonView.layer.shadowOpacity = 0.25
|
saveButtonView.layer.shadowOpacity = 0.2
|
||||||
self.addSubview(saveButtonView)
|
self.addSubview(saveButtonView)
|
||||||
}
|
}
|
||||||
transition.setPosition(view: saveButtonView, position: saveButtonFrame.center)
|
transition.setPosition(view: saveButtonView, position: saveButtonFrame.center)
|
||||||
@ -669,9 +743,9 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
if let muteButtonView = self.muteButton.view {
|
if let muteButtonView = self.muteButton.view {
|
||||||
if muteButtonView.superview == nil {
|
if muteButtonView.superview == nil {
|
||||||
muteButtonView.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
|
muteButtonView.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
|
||||||
muteButtonView.layer.shadowRadius = 2.0
|
muteButtonView.layer.shadowRadius = 4.0
|
||||||
muteButtonView.layer.shadowColor = UIColor.black.cgColor
|
muteButtonView.layer.shadowColor = UIColor.black.cgColor
|
||||||
muteButtonView.layer.shadowOpacity = 0.25
|
muteButtonView.layer.shadowOpacity = 0.2
|
||||||
//self.addSubview(muteButtonView)
|
//self.addSubview(muteButtonView)
|
||||||
}
|
}
|
||||||
transition.setPosition(view: muteButtonView, position: muteButtonFrame.center)
|
transition.setPosition(view: muteButtonView, position: muteButtonFrame.center)
|
||||||
@ -735,6 +809,7 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
|
|
||||||
fileprivate var subject: MediaEditorScreen.Subject?
|
fileprivate var subject: MediaEditorScreen.Subject?
|
||||||
private var subjectDisposable: Disposable?
|
private var subjectDisposable: Disposable?
|
||||||
|
fileprivate var storyPrivacy: EngineStoryPrivacy = EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: [])
|
||||||
|
|
||||||
private let backgroundDimView: UIView
|
private let backgroundDimView: UIView
|
||||||
fileprivate let componentHost: ComponentView<ViewControllerComponentContainer.Environment>
|
fileprivate let componentHost: ComponentView<ViewControllerComponentContainer.Environment>
|
||||||
@ -974,6 +1049,10 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Queue.mainQueue().after(0.5) {
|
||||||
|
self.presentPrivacyTooltip()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateOut(finished: Bool, completion: @escaping () -> Void) {
|
func animateOut(finished: Bool, completion: @escaping () -> Void) {
|
||||||
@ -1041,6 +1120,21 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func presentPrivacyTooltip() {
|
||||||
|
guard let sourceView = self.componentHost.findTaggedView(tag: privacyButtonTag) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let parentFrame = self.view.convert(self.bounds, to: nil)
|
||||||
|
let absoluteFrame = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0)
|
||||||
|
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.maxY + 3.0), size: CGSize())
|
||||||
|
|
||||||
|
let controller = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: "You can set who can view this story", location: .point(location, .top), displayDuration: .manual, inset: 16.0, shouldDismissOnTouch: { _ in
|
||||||
|
return .ignore
|
||||||
|
})
|
||||||
|
self.controller?.present(controller, in: .current)
|
||||||
|
}
|
||||||
|
|
||||||
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.componentHost.view {
|
||||||
@ -1051,6 +1145,12 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func requestUpdate() {
|
||||||
|
if let layout = self.validLayout {
|
||||||
|
self.containerLayoutUpdated(layout: layout, transition: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var drawingScreen: DrawingScreen?
|
private var drawingScreen: DrawingScreen?
|
||||||
func containerLayoutUpdated(layout: ContainerViewLayout, forceUpdate: Bool = false, animateOut: Bool = false, transition: Transition) {
|
func containerLayoutUpdated(layout: ContainerViewLayout, forceUpdate: Bool = false, animateOut: Bool = false, transition: Transition) {
|
||||||
guard let _ = self.controller else {
|
guard let _ = self.controller else {
|
||||||
@ -1090,6 +1190,7 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
MediaEditorScreenComponent(
|
MediaEditorScreenComponent(
|
||||||
context: self.context,
|
context: self.context,
|
||||||
mediaEditor: self.mediaEditor,
|
mediaEditor: self.mediaEditor,
|
||||||
|
privacy: self.storyPrivacy,
|
||||||
openDrawing: { [weak self] mode in
|
openDrawing: { [weak self] mode in
|
||||||
if let self {
|
if let self {
|
||||||
let controller = DrawingScreen(context: self.context, sourceHint: .storyEditor, size: self.previewContainerView.frame.size, originalSize: storyDimensions, isVideo: false, isAvatar: false, drawingView: self.drawingView, entitiesView: self.entitiesView, existingStickerPickerInputData: self.stickerPickerInputData)
|
let controller = DrawingScreen(context: self.context, sourceHint: .storyEditor, size: self.previewContainerView.frame.size, originalSize: storyDimensions, isVideo: false, isAvatar: false, drawingView: self.drawingView, entitiesView: self.entitiesView, existingStickerPickerInputData: self.stickerPickerInputData)
|
||||||
@ -1291,6 +1392,81 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
super.displayNodeDidLoad()
|
super.displayNodeDidLoad()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func presentPrivacySettings() {
|
||||||
|
enum AdditionalCategoryId: Int {
|
||||||
|
case everyone
|
||||||
|
case contacts
|
||||||
|
case closeFriends
|
||||||
|
}
|
||||||
|
|
||||||
|
let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 })
|
||||||
|
|
||||||
|
let additionalCategories: [ChatListNodeAdditionalCategory] = [
|
||||||
|
ChatListNodeAdditionalCategory(
|
||||||
|
id: AdditionalCategoryId.everyone.rawValue,
|
||||||
|
icon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Channel"), color: .white), cornerRadius: nil, color: .blue),
|
||||||
|
smallIcon: generateAvatarImage(size: CGSize(width: 22.0, height: 22.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Channel"), color: .white), iconScale: 0.6, cornerRadius: 6.0, circleCorners: true, color: .blue),
|
||||||
|
title: "Everyone",
|
||||||
|
appearance: .option(sectionTitle: "WHO CAN VIEW FOR 24 HOURS")
|
||||||
|
),
|
||||||
|
ChatListNodeAdditionalCategory(
|
||||||
|
id: AdditionalCategoryId.contacts.rawValue,
|
||||||
|
icon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Tabs/IconContacts"), color: .white), iconScale: 1.0 * 0.8, cornerRadius: nil, color: .yellow),
|
||||||
|
smallIcon: generateAvatarImage(size: CGSize(width: 22.0, height: 22.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Tabs/IconContacts"), color: .white), iconScale: 0.6 * 0.8, cornerRadius: 6.0, circleCorners: true, color: .yellow),
|
||||||
|
title: presentationData.strings.ChatListFolder_CategoryContacts,
|
||||||
|
appearance: .option(sectionTitle: "WHO CAN VIEW FOR 24 HOURS")
|
||||||
|
),
|
||||||
|
ChatListNodeAdditionalCategory(
|
||||||
|
id: AdditionalCategoryId.closeFriends.rawValue,
|
||||||
|
icon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Call/StarHighlighted"), color: .white), iconScale: 1.0 * 0.6, cornerRadius: nil, color: .green),
|
||||||
|
smallIcon: generateAvatarImage(size: CGSize(width: 22.0, height: 22.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Call/StarHighlighted"), color: .white), iconScale: 0.6 * 0.6, cornerRadius: 6.0, circleCorners: true, color: .green),
|
||||||
|
title: "Close Friends",
|
||||||
|
appearance: .option(sectionTitle: "WHO CAN VIEW FOR 24 HOURS")
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
let updatedPresentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)
|
||||||
|
|
||||||
|
let selectionController = self.context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: self.context, updatedPresentationData: (initial: updatedPresentationData, signal: .single(updatedPresentationData)), mode: .chatSelection(ContactMultiselectionControllerMode.ChatSelection(
|
||||||
|
title: "Share Story",
|
||||||
|
searchPlaceholder: "Search contacts",
|
||||||
|
selectedChats: Set(),
|
||||||
|
additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: Set([AdditionalCategoryId.everyone.rawValue])),
|
||||||
|
chatListFilters: nil,
|
||||||
|
displayPresence: true
|
||||||
|
)), options: [], filters: [.excludeSelf], alwaysEnabled: true, limit: 1000, reachedLimit: { _ in
|
||||||
|
}))
|
||||||
|
selectionController.navigationPresentation = .modal
|
||||||
|
self.push(selectionController)
|
||||||
|
|
||||||
|
let _ = (selectionController.result
|
||||||
|
|> take(1)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak selectionController, weak self] result in
|
||||||
|
selectionController?.dismiss()
|
||||||
|
guard case let .result(peerIds, additionalCategoryIds) = result else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var privacy = EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: [])
|
||||||
|
if additionalCategoryIds.contains(AdditionalCategoryId.everyone.rawValue) {
|
||||||
|
privacy.base = .everyone
|
||||||
|
} else if additionalCategoryIds.contains(AdditionalCategoryId.contacts.rawValue) {
|
||||||
|
privacy.base = .contacts
|
||||||
|
} else if additionalCategoryIds.contains(AdditionalCategoryId.closeFriends.rawValue) {
|
||||||
|
privacy.base = .closeFriends
|
||||||
|
}
|
||||||
|
privacy.additionallyIncludePeers = peerIds.compactMap { id -> EnginePeer.Id? in
|
||||||
|
switch id {
|
||||||
|
case let .peer(peerId):
|
||||||
|
return peerId
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self?.node.storyPrivacy = privacy
|
||||||
|
self?.node.requestUpdate()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func requestDismiss(animated: Bool) {
|
func requestDismiss(animated: Bool) {
|
||||||
self.cancelled()
|
self.cancelled()
|
||||||
|
|
||||||
@ -1298,7 +1474,7 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
self?.dismiss()
|
self?.dismiss()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func requestCompletion(caption: NSAttributedString, animated: Bool) {
|
func requestCompletion(caption: NSAttributedString, animated: Bool) {
|
||||||
guard let mediaEditor = self.node.mediaEditor, let subject = self.node.subject else {
|
guard let mediaEditor = self.node.mediaEditor, let subject = self.node.subject else {
|
||||||
return
|
return
|
||||||
@ -1383,32 +1559,56 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if mediaEditor.resultIsVideo {
|
if mediaEditor.resultIsVideo {
|
||||||
let exportSubject: MediaEditorVideoExport.Subject
|
let exportSubject: Signal<MediaEditorVideoExport.Subject, NoError>
|
||||||
switch subject {
|
switch subject {
|
||||||
case let .video(path, _):
|
case let .video(path, _):
|
||||||
let asset = AVURLAsset(url: NSURL(fileURLWithPath: path) as URL)
|
let asset = AVURLAsset(url: NSURL(fileURLWithPath: path) as URL)
|
||||||
exportSubject = .video(asset)
|
exportSubject = .single(.video(asset))
|
||||||
case let .image(image, _):
|
case let .image(image, _):
|
||||||
exportSubject = .image(image)
|
exportSubject = .single(.image(image))
|
||||||
default:
|
case let .asset(asset):
|
||||||
fatalError()
|
exportSubject = Signal { subscriber in
|
||||||
|
if asset.mediaType == .video {
|
||||||
|
PHImageManager.default().requestAVAsset(forVideo: asset, options: nil) { avAsset, _, _ in
|
||||||
|
if let avAsset {
|
||||||
|
subscriber.putNext(.video(avAsset))
|
||||||
|
subscriber.putCompletion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let options = PHImageRequestOptions()
|
||||||
|
options.deliveryMode = .highQualityFormat
|
||||||
|
PHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .default, options: options) { image, _ in
|
||||||
|
if let image {
|
||||||
|
subscriber.putNext(.image(image))
|
||||||
|
subscriber.putCompletion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return EmptyDisposable
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let configuration = recommendedVideoExportConfiguration(values: mediaEditor.values)
|
let _ = exportSubject.start(next: { [weak self] exportSubject in
|
||||||
let outputPath = NSTemporaryDirectory() + "\(Int64.random(in: 0 ..< .max)).mp4"
|
guard let self else {
|
||||||
let videoExport = MediaEditorVideoExport(account: self.context.account, subject: exportSubject, configuration: configuration, outputPath: outputPath)
|
return
|
||||||
self.videoExport = videoExport
|
|
||||||
|
|
||||||
videoExport.startExport()
|
|
||||||
|
|
||||||
self.exportDisposable = (videoExport.status
|
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
|
||||||
if let self {
|
|
||||||
if case .completed = status {
|
|
||||||
self.videoExport = nil
|
|
||||||
saveToPhotos(outputPath, true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
let configuration = recommendedVideoExportConfiguration(values: mediaEditor.values)
|
||||||
|
let outputPath = NSTemporaryDirectory() + "\(Int64.random(in: 0 ..< .max)).mp4"
|
||||||
|
let videoExport = MediaEditorVideoExport(account: self.context.account, subject: exportSubject, configuration: configuration, outputPath: outputPath)
|
||||||
|
self.videoExport = videoExport
|
||||||
|
|
||||||
|
videoExport.startExport()
|
||||||
|
|
||||||
|
self.exportDisposable = (videoExport.status
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||||
|
if let self {
|
||||||
|
if case .completed = status {
|
||||||
|
self.videoExport = nil
|
||||||
|
saveToPhotos(outputPath, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
if let image = mediaEditor.resultImage {
|
if let image = mediaEditor.resultImage {
|
||||||
@ -1429,3 +1629,70 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
(self.displayNode as! Node).containerLayoutUpdated(layout: layout, transition: Transition(transition))
|
(self.displayNode as! Node).containerLayoutUpdated(layout: layout, transition: Transition(transition))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class PrivacyButtonComponent: CombinedComponent {
|
||||||
|
let icon: UIImage
|
||||||
|
let text: String
|
||||||
|
|
||||||
|
init(
|
||||||
|
icon: UIImage,
|
||||||
|
text: String
|
||||||
|
) {
|
||||||
|
self.icon = icon
|
||||||
|
self.text = text
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: PrivacyButtonComponent, rhs: PrivacyButtonComponent) -> Bool {
|
||||||
|
if lhs.text != rhs.text {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
static var body: Body {
|
||||||
|
let background = Child(BlurredBackgroundComponent.self)
|
||||||
|
let icon = Child(Image.self)
|
||||||
|
let text = Child(Text.self)
|
||||||
|
|
||||||
|
return { context in
|
||||||
|
let icon = icon.update(
|
||||||
|
component: Image(image: context.component.icon, size: CGSize(width: 9.0, height: 11.0)),
|
||||||
|
availableSize: CGSize(width: 180.0, height: 100.0),
|
||||||
|
transition: .immediate
|
||||||
|
)
|
||||||
|
|
||||||
|
let text = text.update(
|
||||||
|
component: Text(
|
||||||
|
text: "\(context.component.text)",
|
||||||
|
font: Font.medium(14.0),
|
||||||
|
color: .white
|
||||||
|
),
|
||||||
|
availableSize: CGSize(width: 180.0, height: 100.0),
|
||||||
|
transition: .immediate
|
||||||
|
)
|
||||||
|
|
||||||
|
let backgroundSize = CGSize(width: text.size.width + 38.0, height: 30.0)
|
||||||
|
let background = background.update(
|
||||||
|
component: BlurredBackgroundComponent(color: UIColor(white: 0.0, alpha: 0.5)),
|
||||||
|
availableSize: backgroundSize,
|
||||||
|
transition: .immediate
|
||||||
|
)
|
||||||
|
|
||||||
|
context.add(background
|
||||||
|
.position(CGPoint(x: backgroundSize.width / 2.0, y: backgroundSize.height / 2.0))
|
||||||
|
.cornerRadius(min(backgroundSize.width, backgroundSize.height) / 2.0)
|
||||||
|
.clipsToBounds(true)
|
||||||
|
)
|
||||||
|
|
||||||
|
context.add(icon
|
||||||
|
.position(CGPoint(x: 16.0, y: backgroundSize.height / 2.0))
|
||||||
|
)
|
||||||
|
|
||||||
|
context.add(text
|
||||||
|
.position(CGPoint(x: backgroundSize.width / 2.0 + 7.0, y: backgroundSize.height / 2.0))
|
||||||
|
)
|
||||||
|
|
||||||
|
return backgroundSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
12
submodules/TelegramUI/Images.xcassets/Components/Close.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Components/Close.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "close.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
90
submodules/TelegramUI/Images.xcassets/Components/Close.imageset/close.pdf
vendored
Normal file
90
submodules/TelegramUI/Images.xcassets/Components/Close.imageset/close.pdf
vendored
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
%PDF-1.7
|
||||||
|
|
||||||
|
1 0 obj
|
||||||
|
<< /ExtGState << /E1 << /ca 0.500000 >> >> >>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
2 0 obj
|
||||||
|
<< /Length 3 0 R >>
|
||||||
|
stream
|
||||||
|
/DeviceRGB CS
|
||||||
|
/DeviceRGB cs
|
||||||
|
q
|
||||||
|
/E1 gs
|
||||||
|
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
|
||||||
|
1.000000 1.000000 1.000000 scn
|
||||||
|
8.000000 0.000000 m
|
||||||
|
12.418278 0.000000 16.000000 3.581722 16.000000 8.000000 c
|
||||||
|
16.000000 12.418278 12.418278 16.000000 8.000000 16.000000 c
|
||||||
|
3.581722 16.000000 0.000000 12.418278 0.000000 8.000000 c
|
||||||
|
0.000000 3.581722 3.581722 0.000000 8.000000 0.000000 c
|
||||||
|
h
|
||||||
|
4.469631 11.530369 m
|
||||||
|
4.762524 11.823262 5.237398 11.823262 5.530291 11.530369 c
|
||||||
|
7.999961 9.060699 l
|
||||||
|
10.469630 11.530369 l
|
||||||
|
10.762524 11.823262 11.237397 11.823262 11.530291 11.530369 c
|
||||||
|
11.823184 11.237476 11.823184 10.762602 11.530291 10.469709 c
|
||||||
|
9.060621 8.000039 l
|
||||||
|
11.530291 5.530370 l
|
||||||
|
11.823184 5.237476 11.823184 4.762603 11.530291 4.469709 c
|
||||||
|
11.237397 4.176816 10.762524 4.176816 10.469630 4.469709 c
|
||||||
|
7.999961 6.939379 l
|
||||||
|
5.530291 4.469709 l
|
||||||
|
5.237398 4.176816 4.762524 4.176816 4.469631 4.469709 c
|
||||||
|
4.176738 4.762603 4.176738 5.237476 4.469631 5.530370 c
|
||||||
|
6.939301 8.000039 l
|
||||||
|
4.469631 10.469709 l
|
||||||
|
4.176738 10.762602 4.176738 11.237476 4.469631 11.530369 c
|
||||||
|
h
|
||||||
|
f*
|
||||||
|
n
|
||||||
|
Q
|
||||||
|
|
||||||
|
endstream
|
||||||
|
endobj
|
||||||
|
|
||||||
|
3 0 obj
|
||||||
|
1048
|
||||||
|
endobj
|
||||||
|
|
||||||
|
4 0 obj
|
||||||
|
<< /Annots []
|
||||||
|
/Type /Page
|
||||||
|
/MediaBox [ 0.000000 0.000000 16.000000 16.000000 ]
|
||||||
|
/Resources 1 0 R
|
||||||
|
/Contents 2 0 R
|
||||||
|
/Parent 5 0 R
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
5 0 obj
|
||||||
|
<< /Kids [ 4 0 R ]
|
||||||
|
/Count 1
|
||||||
|
/Type /Pages
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
6 0 obj
|
||||||
|
<< /Pages 5 0 R
|
||||||
|
/Type /Catalog
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
xref
|
||||||
|
0 7
|
||||||
|
0000000000 65535 f
|
||||||
|
0000000010 00000 n
|
||||||
|
0000000074 00000 n
|
||||||
|
0000001178 00000 n
|
||||||
|
0000001201 00000 n
|
||||||
|
0000001374 00000 n
|
||||||
|
0000001448 00000 n
|
||||||
|
trailer
|
||||||
|
<< /ID [ (some) (id) ]
|
||||||
|
/Root 6 0 R
|
||||||
|
/Size 7
|
||||||
|
>>
|
||||||
|
startxref
|
||||||
|
1507
|
||||||
|
%%EOF
|
12
submodules/TelegramUI/Images.xcassets/Media Editor/Recipient.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Media Editor/Recipient.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "Recipient.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
114
submodules/TelegramUI/Images.xcassets/Media Editor/Recipient.imageset/Recipient.pdf
vendored
Normal file
114
submodules/TelegramUI/Images.xcassets/Media Editor/Recipient.imageset/Recipient.pdf
vendored
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
%PDF-1.7
|
||||||
|
|
||||||
|
1 0 obj
|
||||||
|
<< >>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
2 0 obj
|
||||||
|
<< /Length 3 0 R >>
|
||||||
|
stream
|
||||||
|
/DeviceRGB CS
|
||||||
|
/DeviceRGB cs
|
||||||
|
q
|
||||||
|
1.000000 0.000000 -0.000000 1.000000 4.330078 -0.830017 cm
|
||||||
|
1.000000 1.000000 1.000000 scn
|
||||||
|
-0.830000 1.660004 m
|
||||||
|
-0.830000 1.201607 -0.458396 0.830004 0.000000 0.830004 c
|
||||||
|
0.458396 0.830004 0.830000 1.201607 0.830000 1.660004 c
|
||||||
|
-0.830000 1.660004 l
|
||||||
|
h
|
||||||
|
0.830000 9.160004 m
|
||||||
|
0.830000 9.618400 0.458396 9.990004 0.000000 9.990004 c
|
||||||
|
-0.458396 9.990004 -0.830000 9.618400 -0.830000 9.160004 c
|
||||||
|
0.830000 9.160004 l
|
||||||
|
h
|
||||||
|
0.830000 1.660004 m
|
||||||
|
0.830000 9.160004 l
|
||||||
|
-0.830000 9.160004 l
|
||||||
|
-0.830000 1.660004 l
|
||||||
|
0.830000 1.660004 l
|
||||||
|
h
|
||||||
|
f
|
||||||
|
n
|
||||||
|
Q
|
||||||
|
q
|
||||||
|
1.000000 0.000000 -0.000000 1.000000 0.830078 4.507935 cm
|
||||||
|
1.000000 1.000000 1.000000 scn
|
||||||
|
-0.586899 2.408951 m
|
||||||
|
-0.911034 2.084816 -0.911034 1.559289 -0.586899 1.235153 c
|
||||||
|
-0.262763 0.911018 0.262763 0.911018 0.586899 1.235153 c
|
||||||
|
-0.586899 2.408951 l
|
||||||
|
h
|
||||||
|
3.500000 5.322052 m
|
||||||
|
4.086899 5.908951 l
|
||||||
|
3.762764 6.233086 3.237236 6.233086 2.913101 5.908951 c
|
||||||
|
3.500000 5.322052 l
|
||||||
|
h
|
||||||
|
6.413101 1.235153 m
|
||||||
|
6.737236 0.911018 7.262764 0.911018 7.586899 1.235153 c
|
||||||
|
7.911034 1.559289 7.911034 2.084816 7.586899 2.408951 c
|
||||||
|
6.413101 1.235153 l
|
||||||
|
h
|
||||||
|
0.586899 1.235153 m
|
||||||
|
4.086899 4.735153 l
|
||||||
|
2.913101 5.908951 l
|
||||||
|
-0.586899 2.408951 l
|
||||||
|
0.586899 1.235153 l
|
||||||
|
h
|
||||||
|
2.913101 4.735153 m
|
||||||
|
6.413101 1.235153 l
|
||||||
|
7.586899 2.408951 l
|
||||||
|
4.086899 5.908951 l
|
||||||
|
2.913101 4.735153 l
|
||||||
|
h
|
||||||
|
f
|
||||||
|
n
|
||||||
|
Q
|
||||||
|
|
||||||
|
endstream
|
||||||
|
endobj
|
||||||
|
|
||||||
|
3 0 obj
|
||||||
|
1279
|
||||||
|
endobj
|
||||||
|
|
||||||
|
4 0 obj
|
||||||
|
<< /Annots []
|
||||||
|
/Type /Page
|
||||||
|
/MediaBox [ 0.000000 0.000000 8.660156 10.660004 ]
|
||||||
|
/Resources 1 0 R
|
||||||
|
/Contents 2 0 R
|
||||||
|
/Parent 5 0 R
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
5 0 obj
|
||||||
|
<< /Kids [ 4 0 R ]
|
||||||
|
/Count 1
|
||||||
|
/Type /Pages
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
6 0 obj
|
||||||
|
<< /Pages 5 0 R
|
||||||
|
/Type /Catalog
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
xref
|
||||||
|
0 7
|
||||||
|
0000000000 65535 f
|
||||||
|
0000000010 00000 n
|
||||||
|
0000000034 00000 n
|
||||||
|
0000001369 00000 n
|
||||||
|
0000001392 00000 n
|
||||||
|
0000001564 00000 n
|
||||||
|
0000001638 00000 n
|
||||||
|
trailer
|
||||||
|
<< /ID [ (some) (id) ]
|
||||||
|
/Root 6 0 R
|
||||||
|
/Size 7
|
||||||
|
>>
|
||||||
|
startxref
|
||||||
|
1697
|
||||||
|
%%EOF
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
submodules/TelegramUI/Resources/Animations/flash_on.json
Normal file
1
submodules/TelegramUI/Resources/Animations/flash_on.json
Normal file
File diff suppressed because one or more lines are too long
@ -135,6 +135,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
|||||||
private let animatedStickerNode: AnimatedStickerNode
|
private let animatedStickerNode: AnimatedStickerNode
|
||||||
private var downArrowsNode: DownArrowsIconNode?
|
private var downArrowsNode: DownArrowsIconNode?
|
||||||
private let textNode: ImmediateTextNode
|
private let textNode: ImmediateTextNode
|
||||||
|
private let closeButtonNode: HighlightableButtonNode
|
||||||
|
|
||||||
private var isArrowInverted: Bool = false
|
private var isArrowInverted: Bool = false
|
||||||
|
|
||||||
@ -142,7 +143,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
|||||||
|
|
||||||
private var validLayout: ContainerViewLayout?
|
private var validLayout: ContainerViewLayout?
|
||||||
|
|
||||||
init(account: Account, sharedContext: SharedAccountContext, text: String, textEntities: [MessageTextEntity], style: TooltipScreen.Style, icon: TooltipScreen.Icon?, customContentNode: TooltipCustomContentNode? = nil, location: TooltipScreen.Location, displayDuration: TooltipScreen.DisplayDuration, inset: CGFloat = 13.0, shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, requestDismiss: @escaping () -> Void, openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)?) {
|
init(account: Account, sharedContext: SharedAccountContext, text: String, textEntities: [MessageTextEntity], style: TooltipScreen.Style, icon: TooltipScreen.Icon? = nil, customContentNode: TooltipCustomContentNode? = nil, location: TooltipScreen.Location, displayDuration: TooltipScreen.DisplayDuration, inset: CGFloat = 13.0, shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, requestDismiss: @escaping () -> Void, openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)?) {
|
||||||
self.tooltipStyle = style
|
self.tooltipStyle = style
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
self.customContentNode = customContentNode
|
self.customContentNode = customContentNode
|
||||||
@ -326,6 +327,9 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
|||||||
self.downArrowsNode = DownArrowsIconNode()
|
self.downArrowsNode = DownArrowsIconNode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.closeButtonNode = HighlightableButtonNode()
|
||||||
|
self.closeButtonNode.setImage(UIImage(bundleImageName: "Components/Close"), for: .normal)
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.containerNode.addSubnode(self.backgroundContainerNode)
|
self.containerNode.addSubnode(self.backgroundContainerNode)
|
||||||
@ -338,6 +342,11 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
|||||||
}
|
}
|
||||||
self.containerNode.addSubnode(self.textNode)
|
self.containerNode.addSubnode(self.textNode)
|
||||||
self.containerNode.addSubnode(self.animatedStickerNode)
|
self.containerNode.addSubnode(self.animatedStickerNode)
|
||||||
|
|
||||||
|
if case .manual = displayDuration {
|
||||||
|
self.containerNode.addSubnode(self.closeButtonNode)
|
||||||
|
}
|
||||||
|
|
||||||
if let downArrowsNode = self.downArrowsNode {
|
if let downArrowsNode = self.downArrowsNode {
|
||||||
self.containerNode.addSubnode(downArrowsNode)
|
self.containerNode.addSubnode(downArrowsNode)
|
||||||
}
|
}
|
||||||
@ -402,6 +411,12 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
|||||||
openActiveTextItem?(.hashtag(hashtag.hashtag), .longTap)
|
openActiveTextItem?(.hashtag(hashtag.hashtag), .longTap)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.closeButtonNode.addTarget(self, action: #selector(self.closePressed), forControlEvents: .touchUpInside)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func closePressed() {
|
||||||
|
self.requestDismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
@ -453,7 +468,10 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
|||||||
var invertArrow = false
|
var invertArrow = false
|
||||||
switch self.location {
|
switch self.location {
|
||||||
case let .point(rect, arrowPosition):
|
case let .point(rect, arrowPosition):
|
||||||
let backgroundWidth = textSize.width + contentInset * 2.0 + animationSize.width + animationSpacing
|
var backgroundWidth = textSize.width + contentInset * 2.0 + animationSize.width + animationSpacing
|
||||||
|
if self.closeButtonNode.supernode != nil {
|
||||||
|
backgroundWidth += 24.0
|
||||||
|
}
|
||||||
switch arrowPosition {
|
switch arrowPosition {
|
||||||
case .bottom, .top:
|
case .bottom, .top:
|
||||||
backgroundFrame = CGRect(origin: CGPoint(x: rect.midX - backgroundWidth / 2.0, y: rect.minY - bottomInset - backgroundHeight), size: CGSize(width: backgroundWidth, height: backgroundHeight))
|
backgroundFrame = CGRect(origin: CGPoint(x: rect.midX - backgroundWidth / 2.0, y: rect.minY - bottomInset - backgroundHeight), size: CGSize(width: backgroundWidth, height: backgroundHeight))
|
||||||
@ -533,7 +551,11 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
|||||||
self.arrowNode.isHidden = true
|
self.arrowNode.isHidden = true
|
||||||
}
|
}
|
||||||
|
|
||||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: contentInset + animationSize.width + animationSpacing, y: floor((backgroundHeight - textSize.height) / 2.0)), size: textSize))
|
let textFrame = CGRect(origin: CGPoint(x: contentInset + animationSize.width + animationSpacing, y: floor((backgroundHeight - textSize.height) / 2.0)), size: textSize)
|
||||||
|
transition.updateFrame(node: self.textNode, frame: textFrame)
|
||||||
|
|
||||||
|
let closeSize = CGSize(width: 44.0, height: 44.0)
|
||||||
|
transition.updateFrame(node: self.closeButtonNode, frame: CGRect(origin: CGPoint(x: textFrame.maxX - 6.0, y: floor((backgroundHeight - closeSize.height) / 2.0)), size: closeSize))
|
||||||
|
|
||||||
let animationFrame = CGRect(origin: CGPoint(x: contentInset - animationInset, y: contentVerticalInset - animationInset), size: CGSize(width: animationSize.width + animationInset * 2.0, height: animationSize.height + animationInset * 2.0))
|
let animationFrame = CGRect(origin: CGPoint(x: contentInset - animationInset, y: contentVerticalInset - animationInset), size: CGSize(width: animationSize.width + animationInset * 2.0, height: animationSize.height + animationInset * 2.0))
|
||||||
transition.updateFrame(node: self.animatedStickerNode, frame: animationFrame)
|
transition.updateFrame(node: self.animatedStickerNode, frame: animationFrame)
|
||||||
@ -557,6 +579,10 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
|||||||
eventIsPresses = event.type == .presses
|
eventIsPresses = event.type == .presses
|
||||||
}
|
}
|
||||||
if event.type == .touches || eventIsPresses {
|
if event.type == .touches || eventIsPresses {
|
||||||
|
if case .manual = self.displayDuration {
|
||||||
|
self.requestDismiss()
|
||||||
|
return self.view
|
||||||
|
}
|
||||||
switch self.shouldDismissOnTouch(point) {
|
switch self.shouldDismissOnTouch(point) {
|
||||||
case .ignore:
|
case .ignore:
|
||||||
break
|
break
|
||||||
@ -680,6 +706,7 @@ public final class TooltipScreen: ViewController {
|
|||||||
case `default`
|
case `default`
|
||||||
case custom(Double)
|
case custom(Double)
|
||||||
case infinite
|
case infinite
|
||||||
|
case manual
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Style {
|
public enum Style {
|
||||||
@ -722,7 +749,20 @@ public final class TooltipScreen: ViewController {
|
|||||||
|
|
||||||
public var alwaysVisible = false
|
public var alwaysVisible = false
|
||||||
|
|
||||||
public init(account: Account, sharedContext: SharedAccountContext, text: String, textEntities: [MessageTextEntity] = [], style: TooltipScreen.Style = .default, icon: TooltipScreen.Icon?, customContentNode: TooltipCustomContentNode? = nil, location: TooltipScreen.Location, displayDuration: DisplayDuration = .default, inset: CGFloat = 13.0, shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)? = nil) {
|
public init(
|
||||||
|
account: Account,
|
||||||
|
sharedContext: SharedAccountContext,
|
||||||
|
text: String,
|
||||||
|
textEntities: [MessageTextEntity] = [],
|
||||||
|
style: TooltipScreen.Style = .default,
|
||||||
|
icon: TooltipScreen.Icon? = nil,
|
||||||
|
customContentNode: TooltipCustomContentNode? = nil,
|
||||||
|
location: TooltipScreen.Location,
|
||||||
|
displayDuration: DisplayDuration = .default,
|
||||||
|
inset: CGFloat = 13.0,
|
||||||
|
shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch,
|
||||||
|
openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)? = nil
|
||||||
|
) {
|
||||||
self.account = account
|
self.account = account
|
||||||
self.sharedContext = sharedContext
|
self.sharedContext = sharedContext
|
||||||
self.text = text
|
self.text = text
|
||||||
@ -766,7 +806,7 @@ public final class TooltipScreen: ViewController {
|
|||||||
timeout = 5.0
|
timeout = 5.0
|
||||||
case let .custom(value):
|
case let .custom(value):
|
||||||
timeout = value
|
timeout = value
|
||||||
case .infinite:
|
case .infinite, .manual:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user