Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(dialog): improve file type handling and media type checks #2061

Draft
wants to merge 1 commit into
base: v2
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 77 additions & 47 deletions plugins/dialog/ios/Sources/DialogPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ struct SaveFileDialogOptions: Decodable {
}

class DialogPlugin: Plugin {

var filePickerController: FilePickerController!
var onFilePickerResult: ((FilePickerEvent) -> Void)? = nil

Expand All @@ -52,50 +51,45 @@ class DialogPlugin: Plugin {
let args = try invoke.parseArgs(FilePickerOptions.self)

let parsedTypes = parseFiltersOption(args.filters ?? [])

var isMedia = !parsedTypes.isEmpty
var uniqueMimeType: Bool? = nil
var mimeKind: String? = nil
if !parsedTypes.isEmpty {
uniqueMimeType = true
for mime in parsedTypes {
let kind = mime.components(separatedBy: "/")[0]
if kind != "image" && kind != "video" {
isMedia = false
}
if mimeKind == nil {
mimeKind = kind
} else if mimeKind != kind {
uniqueMimeType = false
}
Logger.error("Parsed Types: %@", parsedTypes)

var hasImage = false
var hasVideo = false
var hasFile = false

for type in parsedTypes {
let kind = kindOfMedia(type)
if kind == "file" {
hasFile = true
break
} else if kind == "image" {
hasImage = true
} else if kind == "video" {
hasVideo = true
}
}

onFilePickerResult = { (event: FilePickerEvent) -> Void in
onFilePickerResult = { (event: FilePickerEvent) in
switch event {
case .selected(let urls):
case let .selected(urls):
invoke.resolve(["files": urls])
case .cancelled:
invoke.resolve(["files": nil])
case .error(let error):
case let .error(error):
invoke.reject(error)
}
}

if uniqueMimeType == true || isMedia {
if !hasFile {
DispatchQueue.main.async {
if #available(iOS 14, *) {
var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
configuration.selectionLimit = (args.multiple ?? false) ? 0 : 1

if uniqueMimeType == true {
if mimeKind == "image" {
configuration.filter = .images
} else if mimeKind == "video" {
configuration.filter = .videos
}
if hasImage && !hasVideo {
configuration.filter = .images
} else if hasVideo && !hasImage {
configuration.filter = .videos
}

let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self.filePickerController
picker.modalPresentationStyle = .fullScreen
Expand All @@ -104,8 +98,12 @@ class DialogPlugin: Plugin {
let picker = UIImagePickerController()
picker.delegate = self.filePickerController

if uniqueMimeType == true && mimeKind == "image" {
picker.sourceType = .photoLibrary
if hasImage && hasVideo {
picker.mediaTypes = [kUTTypeImage as String, kUTTypeMovie as String]
} else if hasImage {
picker.mediaTypes = [kUTTypeImage as String]
} else if hasVideo {
picker.mediaTypes = [kUTTypeMovie as String]
}

picker.sourceType = .photoLibrary
Expand Down Expand Up @@ -146,13 +144,13 @@ class DialogPlugin: Plugin {
try "".write(to: srcPath, atomically: true, encoding: .utf8)
}

onFilePickerResult = { (event: FilePickerEvent) -> Void in
onFilePickerResult = { (event: FilePickerEvent) in
switch event {
case .selected(let urls):
case let .selected(urls):
invoke.resolve(["file": urls.first!])
case .cancelled:
invoke.resolve(["file": nil])
case .error(let error):
case let .error(error):
invoke.reject(error)
}
}
Expand All @@ -169,27 +167,56 @@ class DialogPlugin: Plugin {
}

private func presentViewController(_ viewControllerToPresent: UIViewController) {
self.manager.viewController?.present(viewControllerToPresent, animated: true, completion: nil)
manager.viewController?.present(viewControllerToPresent, animated: true, completion: nil)
}

private func parseFiltersOption(_ filters: [Filter]) -> [String] {
var parsedTypes: [String] = []
for filter in filters {
for ext in filter.extensions ?? [] {
guard
let utType: String = UTTypeCreatePreferredIdentifierForTag(
kUTTagClassMIMEType, ext as CFString, nil)?.takeRetainedValue() as String?
else {
continue
if #available(iOS 14.0, *) {
if let utType = UTType(filenameExtension: ext) {
parsedTypes.append(utType.identifier)
}
} else {
if let unmanagedUTI = UTTypeCreatePreferredIdentifierForTag(
kUTTagClassFilenameExtension,
ext as CFString,
nil
) {
let uti = unmanagedUTI.takeRetainedValue() as String
parsedTypes.append(uti)
}
}
parsedTypes.append(utType)
}
}
return parsedTypes
}

private func kindOfMedia(_ uti: String) -> String {
if #available(iOS 14.0, *) {
let utType = UTType(uti)
if utType?.conforms(to: .image) == true {
return "image"
} else if utType?.conforms(to: .movie) == true {
return "video"
} else if utType?.conforms(to: .video) == true {
return "video"
}
} else {
if UTTypeConformsTo(uti as CFString, kUTTypeImage) {
return "image"
} else if UTTypeConformsTo(uti as CFString, kUTTypeMovie) {
return "video"
} else if UTTypeConformsTo(uti as CFString, kUTTypeVideo) {
return "video"
}
}
return "file"
}

public func onFilePickerEvent(_ event: FilePickerEvent) {
self.onFilePickerResult?(event)
onFilePickerResult?(event)
}

@objc public func showMessageDialog(_ invoke: Invoke) throws {
Expand All @@ -198,36 +225,39 @@ class DialogPlugin: Plugin {

DispatchQueue.main.async { [] in
let alert = UIAlertController(
title: args.title, message: args.message, preferredStyle: UIAlertController.Style.alert)
title: args.title, message: args.message, preferredStyle: UIAlertController.Style.alert
)

let cancelButtonLabel = args.cancelButtonLabel ?? ""
if !cancelButtonLabel.isEmpty {
alert.addAction(
UIAlertAction(
title: cancelButtonLabel, style: UIAlertAction.Style.default,
handler: { (_) -> Void in
handler: { _ in
Logger.error("cancel")

invoke.resolve([
"value": false,
"cancelled": false,
])
}))
}
))
}

let okButtonLabel = args.okButtonLabel ?? (cancelButtonLabel.isEmpty ? "OK" : "")
if !okButtonLabel.isEmpty {
alert.addAction(
UIAlertAction(
title: okButtonLabel, style: UIAlertAction.Style.default,
handler: { (_) -> Void in
handler: { _ in
Logger.error("ok")

invoke.resolve([
"value": true,
"cancelled": false,
])
}))
}
))
}

manager.viewController?.present(alert, animated: true, completion: nil)
Expand Down
4 changes: 2 additions & 2 deletions plugins/dialog/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ pub enum OpenResponse {
}

#[allow(dead_code)]
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
#[derive(Debug, Default, Clone, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct DialogFilter {
name: String,
extensions: Vec<String>,
Expand Down
Loading