diff --git a/Telegram/Telegram-iOS/en.lproj/InfoPlist.strings b/Telegram/Telegram-iOS/en.lproj/InfoPlist.strings index ca33866340..e77338c5d5 100644 --- a/Telegram/Telegram-iOS/en.lproj/InfoPlist.strings +++ b/Telegram/Telegram-iOS/en.lproj/InfoPlist.strings @@ -5,8 +5,8 @@ "NSLocationAlwaysAndWhenInUseUsageDescription" = "When you choose to share your Live Location with friends in a chat, Telegram needs background access to your location to keep them updated for the duration of the live sharing."; "NSLocationAlwaysUsageDescription" = "When you choose to share your live location with friends in a chat, Telegram needs background access to your location to keep them updated for the duration of the live sharing. You also need this to send locations from an Apple Watch."; "NSCameraUsageDescription" = "We need this so that you can take and share photos and videos, as well as make video calls."; -"NSPhotoLibraryUsageDescription" = "We need this so that you can share photos and videos from your photo library."; -"NSPhotoLibraryAddUsageDescription" = "We need this so that you can save photos and videos to your photo library."; +"NSPhotoLibraryUsageDescription" = "We need this so that you can share photos and videos from your photo library and save the ones you capture."; +"NSPhotoLibraryAddUsageDescription" = "We need this so that you can save photos and videos to your photo library and save the ones you capture."; "NSMicrophoneUsageDescription" = "We need this so that you can record and share voice messages and videos with sound."; "NSSiriUsageDescription" = "You can use Siri to send messages."; "NSFaceIDUsageDescription" = "You can use Face ID to unlock the app."; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 59ae67fb10..0478f4a0e8 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1376,7 +1376,7 @@ public protocol SharedAccountContext: AnyObject { func makeBotPreviewEditorScreen(context: AccountContext, source: Any?, target: Stories.PendingTarget, transitionArguments: (UIView, CGRect, UIImage?)?, transitionOut: @escaping () -> BotPreviewEditorTransitionOut?, externalState: MediaEditorTransitionOutExternalState, completion: @escaping (MediaEditorScreenResult, @escaping (@escaping () -> Void) -> Void) -> Void, cancelled: @escaping () -> Void) -> ViewController func makeStickerEditorScreen(context: AccountContext, source: Any?, intro: Bool, transitionArguments: (UIView, CGRect, UIImage?)?, completion: @escaping (TelegramMediaFile, [String], @escaping () -> Void) -> Void, cancelled: @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 makeAvatarMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, canDelete: Bool, performDelete: @escaping () -> Void, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController + func makeAvatarMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, canDelete: Bool, performDelete: @escaping () -> Void, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> (ViewController?, Any?) func makeStoryMediaPickerScreen(context: AccountContext, isDark: Bool, forCollage: Bool, selectionLimit: Int?, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, multipleCompletion: @escaping ([Any], Bool) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void) -> ViewController func makeStickerPickerScreen(context: AccountContext, inputData: Promise, completion: @escaping (FileMediaReference) -> Void) -> ViewController func makeProxySettingsController(sharedContext: SharedAccountContext, account: UnauthorizedAccount) -> ViewController diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index b59c8fe046..99014a2e2a 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -3777,126 +3777,188 @@ public func avatarMediaPickerController( performDelete: @escaping () -> Void, 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 }) - let updatedPresentationData: (PresentationData, Signal) = (presentationData, .single(presentationData)) - let controller = AttachmentController( - context: context, - updatedPresentationData: updatedPresentationData, - style: .glass, - chatLocation: nil, - buttons: [.standalone], - initialButton: .standalone, - fromMenu: false, - hasTextInput: false, - makeEntityInputView: { - return nil - }) - controller.forceSourceRect = true - controller.getSourceRect = getSourceRect - controller.requestController = { [weak controller] _, present in - var mainButtonState: AttachmentMainButtonState? - - if canDelete { - mainButtonState = AttachmentMainButtonState(text: presentationData.strings.MediaPicker_RemovePhoto, font: .regular, background: .color(.clear), textColor: presentationData.theme.actionSheet.destructiveActionTextColor, isVisible: true, progress: .none, isEnabled: true, hasShimmer: false) +) -> (controller: ViewController?, holder: Any?) { + if #available(iOS 14.0, *), PHPhotoLibrary.authorizationStatus(for: .readWrite) != .authorized { + final class PickerDelegate: NSObject, PHPickerViewControllerDelegate { + var completion: ((Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void)? + + func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { + picker.dismiss(animated: true) + + for item in results { + if item.itemProvider.canLoadObject(ofClass: UIImage.self) { + item.itemProvider.loadObject(ofClass: UIImage.self) { image, error in + if let uiImage = image as? UIImage { + Queue.mainQueue().async { + self.completion?(uiImage, nil, CGRect(), nil, false, { _ in return nil }, {}) + } + } + } + } + } + } } - let mediaPickerController = MediaPickerScreenImpl( + let holder = PickerDelegate() + holder.completion = completion + + let openMediaPicker = { + var configuration = PHPickerConfiguration(photoLibrary: .shared()) + configuration.filter = .images + configuration.selectionLimit = 1 + + let picker = PHPickerViewController(configuration: configuration) + picker.delegate = holder + (context.sharedContext.mainWindow?.viewController as? NavigationController)?.topViewController?.present(picker, animated: true, completion: nil) + } + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let controller = ActionSheetController(presentationData: presentationData) + let dismissAction: () -> Void = { [weak controller] in + controller?.dismissAnimated() + } + + var items: [ActionSheetButtonItem] = [ + ActionSheetButtonItem(title: presentationData.strings.Settings_SetNewProfilePhotoOrVideo, color: .accent, action: { + dismissAction() + openMediaPicker() + }), + ActionSheetButtonItem(title: presentationData.strings.ProfilePhoto_SetEmoji, color: .accent, action: { + dismissAction() + completion(nil, nil, CGRect(), nil, false, { _ in return nil }, {}) + }) + ] + if canDelete { + items.append(ActionSheetButtonItem(title: presentationData.strings.MediaPicker_RemovePhoto, color: .destructive, action: { + dismissAction() + performDelete() + })) + } + controller.setItemGroups([ + ActionSheetItemGroup(items: items), + ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) + ]) + return (controller, holder) + } else { + let presentationData = context.sharedContext.currentPresentationData.with({ $0 }) + let updatedPresentationData: (PresentationData, Signal) = (presentationData, .single(presentationData)) + let controller = AttachmentController( context: context, updatedPresentationData: updatedPresentationData, style: .glass, - peer: nil, - threadTitle: nil, chatLocation: nil, - bannedSendPhotos: nil, - bannedSendVideos: nil, - subject: .assets(nil, .createAvatar), - mainButtonState: mainButtonState, - mainButtonAction: { [weak controller] in - controller?.dismiss(animated: true) - performDelete() - } - ) - mediaPickerController.customSelection = { controller, result in - if let result = result as? PHAsset { - controller.updateHiddenMediaId(result.localIdentifier) - if let transitionView = controller.transitionView(for: result.localIdentifier, snapshot: false) { - let transitionOut: (Bool?) -> (UIView, CGRect)? = { isNew in - if let isNew { - if isNew { - controller.updateHiddenMediaId(nil) - if let transitionView = controller.defaultTransitionView() { - return (transitionView, transitionView.bounds) - } - } else if let transitionView = controller.transitionView(for: result.localIdentifier, snapshot: false) { - return (transitionView, transitionView.bounds) - } - } - return nil - } - completion(result, transitionView, transitionView.bounds, controller.transitionImage(for: result.localIdentifier), false, transitionOut, { [weak controller] in - controller?.updateHiddenMediaId(nil) - }) - } - } - } - mediaPickerController.openAvatarEditor = { [weak controller] in - completion(nil, nil, .zero, nil, false, { _ in return nil }, { + buttons: [.standalone], + initialButton: .standalone, + fromMenu: false, + hasTextInput: false, + makeEntityInputView: { + return nil }) - controller?.dismiss(animated: true) - } - mediaPickerController.openCamera = { [weak controller] cameraHolder in - let _ = controller - guard let cameraHolder = cameraHolder as? CameraHolder else { - return + controller.forceSourceRect = true + controller.getSourceRect = getSourceRect + controller.requestController = { [weak controller] _, present in + var mainButtonState: AttachmentMainButtonState? + + if canDelete { + mainButtonState = AttachmentMainButtonState(text: presentationData.strings.MediaPicker_RemovePhoto, font: .regular, background: .color(.clear), textColor: presentationData.theme.actionSheet.destructiveActionTextColor, isVisible: true, progress: .none, isEnabled: true, hasShimmer: false) } - var returnToCameraImpl: (() -> Void)? - - let cameraScreen = context.sharedContext.makeCameraScreen( + let mediaPickerController = MediaPickerScreenImpl( context: context, - mode: .avatar, - cameraHolder: cameraHolder, - transitionIn: CameraScreenTransitionIn( - sourceView: cameraHolder.parentView, - sourceRect: cameraHolder.parentView.bounds, - sourceCornerRadius: 0.0, - useFillAnimation: false - ), - transitionOut: { _ in - return CameraScreenTransitionOut( - destinationView: cameraHolder.parentView, - destinationRect: cameraHolder.parentView.bounds, - destinationCornerRadius: 0.0 - ) - }, - completion: { result, commit in - completion(result, nil, .zero, nil, true, { _ in return nil }, { - returnToCameraImpl?() - }) - }, - transitionedOut: { [weak cameraHolder] in - if let cameraHolder { - cameraHolder.restore() - } + updatedPresentationData: updatedPresentationData, + style: .glass, + peer: nil, + threadTitle: nil, + chatLocation: nil, + bannedSendPhotos: nil, + bannedSendVideos: nil, + subject: .assets(nil, .createAvatar), + mainButtonState: mainButtonState, + mainButtonAction: { [weak controller] in + controller?.dismiss(animated: true) + performDelete() } ) - controller?.push(cameraScreen) - - returnToCameraImpl = { [weak cameraScreen] in - if let cameraScreen = cameraScreen as? CameraScreen { - cameraScreen.returnFromEditor() + mediaPickerController.customSelection = { controller, result in + if let result = result as? PHAsset { + controller.updateHiddenMediaId(result.localIdentifier) + if let transitionView = controller.transitionView(for: result.localIdentifier, snapshot: false) { + let transitionOut: (Bool?) -> (UIView, CGRect)? = { isNew in + if let isNew { + if isNew { + controller.updateHiddenMediaId(nil) + if let transitionView = controller.defaultTransitionView() { + return (transitionView, transitionView.bounds) + } + } else if let transitionView = controller.transitionView(for: result.localIdentifier, snapshot: false) { + return (transitionView, transitionView.bounds) + } + } + return nil + } + completion(result, transitionView, transitionView.bounds, controller.transitionImage(for: result.localIdentifier), false, transitionOut, { [weak controller] in + controller?.updateHiddenMediaId(nil) + }) + } } } + mediaPickerController.openAvatarEditor = { [weak controller] in + completion(nil, nil, .zero, nil, false, { _ in return nil }, { + }) + controller?.dismiss(animated: true) + } + mediaPickerController.openCamera = { [weak controller] cameraHolder in + let _ = controller + guard let cameraHolder = cameraHolder as? CameraHolder else { + return + } + + var returnToCameraImpl: (() -> Void)? + + let cameraScreen = context.sharedContext.makeCameraScreen( + context: context, + mode: .avatar, + cameraHolder: cameraHolder, + transitionIn: CameraScreenTransitionIn( + sourceView: cameraHolder.parentView, + sourceRect: cameraHolder.parentView.bounds, + sourceCornerRadius: 0.0, + useFillAnimation: false + ), + transitionOut: { _ in + return CameraScreenTransitionOut( + destinationView: cameraHolder.parentView, + destinationRect: cameraHolder.parentView.bounds, + destinationCornerRadius: 0.0 + ) + }, + completion: { result, commit in + completion(result, nil, .zero, nil, true, { _ in return nil }, { + returnToCameraImpl?() + }) + }, + transitionedOut: { [weak cameraHolder] in + if let cameraHolder { + cameraHolder.restore() + } + } + ) + controller?.push(cameraScreen) + + returnToCameraImpl = { [weak cameraScreen] in + if let cameraScreen = cameraScreen as? CameraScreen { + cameraScreen.returnFromEditor() + } + } + } + present(mediaPickerController, mediaPickerController.mediaPickerContext) } - present(mediaPickerController, mediaPickerController.mediaPickerContext) + controller.willDismiss = { + dismissed() + } + controller.navigationPresentation = .flatModal + controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) + return (controller, nil) } - controller.willDismiss = { - dismissed() - } - controller.navigationPresentation = .flatModal - controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) - return controller } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index ad318df6ff..95aa27abef 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -13490,6 +13490,8 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc private var tabBarItemDisposable: Disposable? + var avatarPickerHolder: Any? + var controllerNode: PeerInfoScreenNode { return self.displayNode as! PeerInfoScreenNode } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift index 88d95df359..4705dc95e5 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift @@ -103,9 +103,14 @@ extension PeerInfoScreenImpl { let parentController = (self.context.sharedContext.mainWindow?.viewController as? NavigationController)?.topViewController as? ViewController var dismissImpl: (() -> Void)? - let mainController = self.context.sharedContext.makeAvatarMediaPickerScreen(context: self.context, getSourceRect: { return nil }, canDelete: hasDeleteButton, performDelete: { [weak self] in + let (mainController, pickerHolder) = self.context.sharedContext.makeAvatarMediaPickerScreen(context: self.context, getSourceRect: { return nil }, canDelete: hasDeleteButton, performDelete: { [weak self] in self?.openAvatarRemoval(mode: mode, peer: peer, item: item) - }, completion: { result, transitionView, transitionRect, transitionImage, fromCamera, transitionOut, cancelled in + }, completion: { [weak self] result, transitionView, transitionRect, transitionImage, fromCamera, transitionOut, cancelled in + guard let self else { + return + } + self.avatarPickerHolder = nil + var resultImage: UIImage? let uploadStatusPromise = Promise(.progress(0.0)) @@ -155,7 +160,6 @@ extension PeerInfoScreenImpl { commit() } parentController?.push(controller) - //isFromEditor = true return } @@ -232,31 +236,37 @@ extension PeerInfoScreenImpl { self.parentController?.pushViewController(editorController) } }, dismissed: { - }) - dismissImpl = { [weak self, weak mainController] in - if let mainController, let navigationController = mainController.navigationController { - var viewControllers = navigationController.viewControllers - viewControllers = viewControllers.filter { c in - return !(c is CameraScreen) && c !== mainController + self.avatarPickerHolder = pickerHolder + if let mainController { + dismissImpl = { [weak self, weak mainController] in + 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) } - navigationController.setViewControllers(viewControllers, animated: false) - } - if let self, let navigationController = self.parentController, let mainController { - var viewControllers = navigationController.viewControllers - viewControllers = viewControllers.filter { c in - return !(c is CameraScreen) && c !== mainController + if let self, let navigationController = self.parentController, let mainController { + var viewControllers = navigationController.viewControllers + viewControllers = viewControllers.filter { c in + return !(c is CameraScreen) && c !== mainController + } + navigationController.setViewControllers(viewControllers, animated: false) + } + + } + if mainController is ActionSheetController { + self.present(mainController, in: .window(.root)) + } else { + mainController.navigationPresentation = .flatModal + mainController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) + if self.navigationController != nil { + self.push(mainController) + } else { + self.parentController?.pushViewController(mainController) } - navigationController.setViewControllers(viewControllers, animated: false) } - - } - mainController.navigationPresentation = .flatModal - mainController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) - if self.navigationController != nil { - self.push(mainController) - } else { - self.parentController?.pushViewController(mainController) } }) } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 90cafd6cc0..99ac7849f1 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -3713,7 +3713,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { return stickerMediaPickerController(context: context, getSourceRect: getSourceRect, completion: completion, dismissed: dismissed) } - public func makeAvatarMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, canDelete: Bool, performDelete: @escaping () -> Void, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController { + public func makeAvatarMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, canDelete: Bool, performDelete: @escaping () -> Void, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> (ViewController?, Any?) { return avatarMediaPickerController(context: context, getSourceRect: getSourceRect, canDelete: canDelete, performDelete: performDelete, completion: completion, dismissed: dismissed) }