mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Use system photos picker when no photos access is available
This commit is contained in:
parent
d476b2ecf8
commit
e7a4815ec3
6 changed files with 206 additions and 132 deletions
|
|
@ -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.";
|
||||
|
|
|
|||
|
|
@ -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<StickerPickerInput>, completion: @escaping (FileMediaReference) -> Void) -> ViewController
|
||||
func makeProxySettingsController(sharedContext: SharedAccountContext, account: UnauthorizedAccount) -> ViewController
|
||||
|
|
|
|||
|
|
@ -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, NoError>) = (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, NoError>) = (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
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<PeerInfoAvatarUploadStatus>(.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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue