Skip to content

Commit

Permalink
[IOS] Fix app stuck after cancel import ICS
Browse files Browse the repository at this point in the history
Co-authored-by: mup <[email protected]>
  • Loading branch information
2 people authored and domesticated-raptor committed Oct 14, 2024
1 parent fe5e62a commit b3f2e87
Show file tree
Hide file tree
Showing 17 changed files with 1,457 additions and 389 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,11 @@ class AndroidFileFacade(
return outputFile.toUri().toString()
}

override suspend fun openFileChooser(boundingRect: IpcClientRect, filter: List<String>?): List<String> {
override suspend fun openFileChooser(
boundingRect: IpcClientRect,
filter: List<String>?,
isFileOnly: Boolean?
): List<String> {
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
type = "*/*"
addCategory(Intent.CATEGORY_OPENABLE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,11 @@ class AndroidFileFacade(
return outputFile.toUri().toString()
}

override suspend fun openFileChooser(boundingRect: IpcClientRect, filter: List<String>?): List<String> {
override suspend fun openFileChooser(
boundingRect: IpcClientRect,
filter: List<String>?,
isFileOnly: Boolean?
): List<String> {
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
type = "*/*"
addCategory(Intent.CATEGORY_OPENABLE)
Expand Down Expand Up @@ -387,7 +391,7 @@ class AndroidFileFacade(
while (chunk * maxChunkSizeBytes <= fileSize) {
val tmpFilename = Integer.toHexString(file.hashCode()) + "." + chunk + ".blob"
val chunkedInputStream = BoundedInputStream.builder()
.setInputStream(inputStream)
.setInputStream(inputStream)
.setMaxCount(maxChunkSizeBytes.toLong())
.get()
val tmpFile = File(tempDir.decrypt, tmpFilename)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ interface FileFacade {
suspend fun openFileChooser(
boundingRect: IpcClientRect,
filter: List<String>?,
isFileOnly: Boolean?,
): List<String>
/**
* Opens OS file picker for selecting a folder. Only on desktop.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ class FileFacadeReceiveDispatcher(
"openFileChooser" -> {
val boundingRect: IpcClientRect = json.decodeFromString(arg[0])
val filter: List<String>? = json.decodeFromString(arg[1])
val isFileOnly: Boolean? = json.decodeFromString(arg[2])
val result: List<String> = this.facade.openFileChooser(
boundingRect,
filter,
isFileOnly,
)
return json.encodeToString(result)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ public protocol FileFacade {
*/
func openFileChooser(
_ boundingRect: IpcClientRect,
_ filter: [String]?
_ filter: [String]?,
_ isFileOnly: Bool?
) async throws -> [String]
/**
* Opens OS file picker for selecting a folder. Only on desktop.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ public class FileFacadeReceiveDispatcher {
case "openFileChooser":
let boundingRect = try! JSONDecoder().decode(IpcClientRect.self, from: arg[0].data(using: .utf8)!)
let filter = try! JSONDecoder().decode([String]?.self, from: arg[1].data(using: .utf8)!)
let isFileOnly = try! JSONDecoder().decode(Bool?.self, from: arg[2].data(using: .utf8)!)
let result = try await self.facade.openFileChooser(
boundingRect,
filter
filter,
isFileOnly
)
return toJson(result)
case "openFolderChooser":
Expand Down
82 changes: 43 additions & 39 deletions app-ios/calendar/Sources/Files/FileChooser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,69 +26,73 @@ class TUTFileChooser: NSObject, UIImagePickerControllerDelegate, UINavigationCon
imagePickerController.delegate = self
}

@MainActor public func open(withAnchorRect anchorRect: CGRect) async throws -> [String] {
@MainActor public func open(withAnchorRect anchorRect: CGRect, isFileOnly: Bool) async throws -> [String] {
if let previousHandler = resultHandler {
TUTSLog("Another file picker is already open?")
sourceController.dismiss(animated: true, completion: nil)
previousHandler(.success([]))
}

let attachmentTypeMenu = UIDocumentMenuViewController(documentTypes: supportedUTIs, in: UIDocumentPickerMode.import)

self.attachmentTypeMenu = attachmentTypeMenu

attachmentTypeMenu.delegate = self
var filePicker: UIDocumentPickerViewController?
if isFileOnly {
filePicker = UIDocumentPickerViewController(forOpeningContentTypes: [UTType.content, UTType.archive, UTType.data], asCopy: true)
filePicker!.delegate = self
} else {
self.attachmentTypeMenu = UIDocumentMenuViewController(documentTypes: supportedUTIs, in: UIDocumentPickerMode.import)
self.attachmentTypeMenu!.delegate = self
}

// add menu item for selecting images from photo library.
// according to developer documentation check if the source type is available first https://developer.apple.com/reference/uikit/uiimagepickercontroller
if UIImagePickerController.isSourceTypeAvailable(.savedPhotosAlbum) {
if !isFileOnly && UIImagePickerController.isSourceTypeAvailable(.savedPhotosAlbum) {
if UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.pad {
attachmentTypeMenu.modalPresentationStyle = .popover
popOverPresentationController = attachmentTypeMenu.popoverPresentationController
filePicker?.modalPresentationStyle = .popover
popOverPresentationController = filePicker?.popoverPresentationController
popOverPresentationController?.permittedArrowDirections = [.up, .down]
popOverPresentationController?.sourceView = sourceController.view
popOverPresentationController?.sourceRect = anchorRect
}
let photosLabel = translate("TutaoChoosePhotosAction", default: "Photos")
attachmentTypeMenu.addOption(
withTitle: photosLabel,
image: photoLibImage,
order: .first,
handler: { [weak self] in
// capture the weak reference to avoid reference self
guard let self else { return }

// No need to ask for permissions with new picker
if #available(iOS 14.0, *) {
self.showPhpicker(anchor: anchorRect)
} else {
// ask for permission because of changed behaviour in iOS 11
if PHPhotoLibrary.authorizationStatus() == .notDetermined {
PHPhotoLibrary.requestAuthorization({ status in
if status == .authorized { self.showLegacyImagePicker(anchor: anchorRect) } else { self.sendResult(filePath: nil) }
})
} else if PHPhotoLibrary.authorizationStatus() == .authorized {
self.showLegacyImagePicker(anchor: anchorRect)
self.attachmentTypeMenu!
.addOption(
withTitle: photosLabel,
image: photoLibImage,
order: .first,
handler: { [weak self] in
// capture the weak reference to avoid reference self
guard let self else { return }

// No need to ask for permissions with new picker
if #available(iOS 14.0, *) {
self.showPhpicker(anchor: anchorRect)
} else {
self.showPermissionDeniedDialog()
// ask for permission because of changed behaviour in iOS 11
if PHPhotoLibrary.authorizationStatus() == .notDetermined {
PHPhotoLibrary.requestAuthorization({ status in
if status == .authorized { self.showLegacyImagePicker(anchor: anchorRect) } else { self.sendResult(filePath: nil) }
})
} else if PHPhotoLibrary.authorizationStatus() == .authorized {
self.showLegacyImagePicker(anchor: anchorRect)
} else {
self.showPermissionDeniedDialog()
}
}
}
}
)
)
}

// add menu item for opening the camera and take a photo or video.
// according to developer documentation check if the source type is available first https://developer.apple.com/reference/uikit/uiimagepickercontroller
if UIImagePickerController.isSourceTypeAvailable(.camera) {
if !isFileOnly && UIImagePickerController.isSourceTypeAvailable(.camera) {
let cameraLabel = translate("TutaoShowCameraAction", default: "Camera")

// capture the weak reference to avoid reference cycle
attachmentTypeMenu.addOption(withTitle: cameraLabel, image: cameraImage, order: .first) { [weak self] in self?.openCamera() }
self.attachmentTypeMenu!.addOption(withTitle: cameraLabel, image: cameraImage, order: .first) { [weak self] in self?.openCamera() }
}

return try await withCheckedThrowingContinuation { continuation in
resultHandler = continuation.resume(with:)
sourceController.present(attachmentTypeMenu, animated: true, completion: nil)
sourceController.present((isFileOnly ? filePicker : self.attachmentTypeMenu)!, animated: true, completion: nil)
}
}

Expand Down Expand Up @@ -311,18 +315,18 @@ class TUTFileChooser: NSObject, UIImagePickerControllerDelegate, UINavigationCon
}

/**
* Replace ".heic" or ".heif" extensions with ".jpeg".
*/
* Replace ".heic" or ".heif" extensions with ".jpeg".
*/
private func changeExtensionToJpeg(filename: URL) -> URL { filename.deletingPathExtension().appendingPathExtension("jpg") }
}

/**
Extending TUTFileChooser on iOS14 to conform to ickerViewControllerDelegate
Extending TUTFileChooser on iOS14 to conform to ickerViewControllerDelegate
*/
@available(iOS 14.0, *) extension TUTFileChooser: PHPickerViewControllerDelegate {
/**
Invoked when user finished picking the files.
*/
Invoked when user finished picking the files.
*/
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true, completion: nil)

Expand Down
4 changes: 2 additions & 2 deletions app-ios/calendar/Sources/Files/IosFileFacade.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ class IosFileFacade: FileFacade {

func open(_ location: String, _ mimeType: String) async throws { await self.viewer.openFile(path: location) }

func openFileChooser(_ boundingRect: IpcClientRect, _ filter: [String]?) async throws -> [String] {
func openFileChooser(_ boundingRect: IpcClientRect, _ filter: [String]?, _ isFileOnly: Bool? = false) async throws -> [String] {
let anchor = CGRect(x: boundingRect.x, y: boundingRect.y, width: boundingRect.width, height: boundingRect.height)
let files = try await self.chooser.open(withAnchorRect: anchor)
let files = try await self.chooser.open(withAnchorRect: anchor, isFileOnly: isFileOnly!)
var returnfiles = [String]()
for file in files {
let fileUrl = URL(fileURLWithPath: file)
Expand Down
72 changes: 38 additions & 34 deletions app-ios/tutanota/Sources/Files/FileChooser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,69 +26,73 @@ class TUTFileChooser: NSObject, UIImagePickerControllerDelegate, UINavigationCon
imagePickerController.delegate = self
}

@MainActor public func open(withAnchorRect anchorRect: CGRect) async throws -> [String] {
@MainActor public func open(withAnchorRect anchorRect: CGRect, isFileOnly: Bool) async throws -> [String] {
if let previousHandler = resultHandler {
TUTSLog("Another file picker is already open?")
sourceController.dismiss(animated: true, completion: nil)
previousHandler(.success([]))
}

let attachmentTypeMenu = UIDocumentMenuViewController(documentTypes: supportedUTIs, in: UIDocumentPickerMode.import)

self.attachmentTypeMenu = attachmentTypeMenu

attachmentTypeMenu.delegate = self
var filePicker: UIDocumentPickerViewController?
if isFileOnly {
filePicker = UIDocumentPickerViewController(forOpeningContentTypes: [UTType.content, UTType.archive, UTType.data], asCopy: true)
filePicker!.delegate = self
} else {
self.attachmentTypeMenu = UIDocumentMenuViewController(documentTypes: supportedUTIs, in: UIDocumentPickerMode.import)
self.attachmentTypeMenu!.delegate = self
}

// add menu item for selecting images from photo library.
// according to developer documentation check if the source type is available first https://developer.apple.com/reference/uikit/uiimagepickercontroller
if UIImagePickerController.isSourceTypeAvailable(.savedPhotosAlbum) {
if !isFileOnly && UIImagePickerController.isSourceTypeAvailable(.savedPhotosAlbum) {
if UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.pad {
attachmentTypeMenu.modalPresentationStyle = .popover
popOverPresentationController = attachmentTypeMenu.popoverPresentationController
filePicker?.modalPresentationStyle = .popover
popOverPresentationController = filePicker?.popoverPresentationController
popOverPresentationController?.permittedArrowDirections = [.up, .down]
popOverPresentationController?.sourceView = sourceController.view
popOverPresentationController?.sourceRect = anchorRect
}
let photosLabel = translate("TutaoChoosePhotosAction", default: "Photos")
attachmentTypeMenu.addOption(
withTitle: photosLabel,
image: photoLibImage,
order: .first,
handler: { [weak self] in
// capture the weak reference to avoid reference self
guard let self else { return }

// No need to ask for permissions with new picker
if #available(iOS 14.0, *) {
self.showPhpicker(anchor: anchorRect)
} else {
// ask for permission because of changed behaviour in iOS 11
if PHPhotoLibrary.authorizationStatus() == .notDetermined {
PHPhotoLibrary.requestAuthorization({ status in
if status == .authorized { self.showLegacyImagePicker(anchor: anchorRect) } else { self.sendResult(filePath: nil) }
})
} else if PHPhotoLibrary.authorizationStatus() == .authorized {
self.showLegacyImagePicker(anchor: anchorRect)
self.attachmentTypeMenu!
.addOption(
withTitle: photosLabel,
image: photoLibImage,
order: .first,
handler: { [weak self] in
// capture the weak reference to avoid reference self
guard let self else { return }

// No need to ask for permissions with new picker
if #available(iOS 14.0, *) {
self.showPhpicker(anchor: anchorRect)
} else {
self.showPermissionDeniedDialog()
// ask for permission because of changed behaviour in iOS 11
if PHPhotoLibrary.authorizationStatus() == .notDetermined {
PHPhotoLibrary.requestAuthorization({ status in
if status == .authorized { self.showLegacyImagePicker(anchor: anchorRect) } else { self.sendResult(filePath: nil) }
})
} else if PHPhotoLibrary.authorizationStatus() == .authorized {
self.showLegacyImagePicker(anchor: anchorRect)
} else {
self.showPermissionDeniedDialog()
}
}
}
}
)
)
}

// add menu item for opening the camera and take a photo or video.
// according to developer documentation check if the source type is available first https://developer.apple.com/reference/uikit/uiimagepickercontroller
if UIImagePickerController.isSourceTypeAvailable(.camera) {
if !isFileOnly && UIImagePickerController.isSourceTypeAvailable(.camera) {
let cameraLabel = translate("TutaoShowCameraAction", default: "Camera")

// capture the weak reference to avoid reference cycle
attachmentTypeMenu.addOption(withTitle: cameraLabel, image: cameraImage, order: .first) { [weak self] in self?.openCamera() }
self.attachmentTypeMenu!.addOption(withTitle: cameraLabel, image: cameraImage, order: .first) { [weak self] in self?.openCamera() }
}

return try await withCheckedThrowingContinuation { continuation in
resultHandler = continuation.resume(with:)
sourceController.present(attachmentTypeMenu, animated: true, completion: nil)
sourceController.present((isFileOnly ? filePicker : self.attachmentTypeMenu)!, animated: true, completion: nil)
}
}

Expand Down
4 changes: 2 additions & 2 deletions app-ios/tutanota/Sources/Files/IosFileFacade.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ class IosFileFacade: FileFacade {

func open(_ location: String, _ mimeType: String) async throws { await self.viewer.openFile(path: location) }

func openFileChooser(_ boundingRect: IpcClientRect, _ filter: [String]?) async throws -> [String] {
func openFileChooser(_ boundingRect: IpcClientRect, _ filter: [String]?, _ isFileOnly: Bool? = false) async throws -> [String] {
let anchor = CGRect(x: boundingRect.x, y: boundingRect.y, width: boundingRect.width, height: boundingRect.height)
let files = try await self.chooser.open(withAnchorRect: anchor)
let files = try await self.chooser.open(withAnchorRect: anchor, isFileOnly: isFileOnly!)
var returnfiles = [String]()
for file in files {
let fileUrl = URL(fileURLWithPath: file)
Expand Down
3 changes: 3 additions & 0 deletions ipc-schema/facades/FileFacade.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
},
{
"filter": "List<string>?"
},
{
"isFileOnly": "boolean?"
}
],
"ret": "List<string>"
Expand Down
Loading

0 comments on commit b3f2e87

Please sign in to comment.