Skip to content

Commit

Permalink
fix(window): fix dialog impl on android
Browse files Browse the repository at this point in the history
  • Loading branch information
jwerle committed Jul 25, 2024
1 parent 50c495d commit 52ac4ac
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 131 deletions.
145 changes: 91 additions & 54 deletions src/window/dialog.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ using namespace SSC;
paths.push_back(url.path.UTF8String);
}
}
debug("before callback");
self.dialog->callback(paths);
debug("after callback");
}

- (void) documentPickerWasCancelled: (UIDocumentPickerViewController*) controller {
Expand Down Expand Up @@ -136,6 +134,8 @@ namespace SSC {

Vector<String> paths;

this->callback = callback;

#if SOCKET_RUNTIME_PLATFORM_APPLE
// state
NSMutableArray<UTType *>* contentTypes = [NSMutableArray new];
Expand Down Expand Up @@ -225,7 +225,6 @@ namespace SSC {

#if SOCKET_RUNTIME_PLATFORM_IOS
UIWindow* window = nullptr;
this->callback = callback;

if (this->window) {
window = this->window->window;
Expand Down Expand Up @@ -788,65 +787,103 @@ namespace SSC {
});
return true;
#elif SOCKET_RUNTIME_PLATFORM_ANDROID
if (this->window->androidWindowRef) {
const auto app = App::sharedApplication();
const auto attachment = Android::JNIEnvironmentAttachment(app->jvm);
const auto dialogRef = CallObjectClassMethodFromAndroidEnvironment(
attachment.env,
this->window->androidWindowRef,
"getDialog",
"()Lsocket/runtime/window/Dialog;"
);
const auto attachment = Android::JNIEnvironmentAttachment(app->jvm);
const auto dialog = CallObjectClassMethodFromAndroidEnvironment(
attachment.env,
app->core->platform.activity,
"getDialog",
"()Lsocket/runtime/window/Dialog;"
);

String mimeTypes;
// <mime>:<ext>,<ext>|<mime>:<ext>|...
for (const auto& contentTypeSpec : split(options.contentTypes, "|")) {
const auto parts = split(contentTypeSpec, ":");
const auto mime = trim(parts[0]);
const auto classes = split(mime, "/");
if (classes.size() == 2) {
if (mimeTypes.size() == 0) {
mimeTypes = mime;
} else {
mimeTypes += "|" + mime;
}
// construct the mime types into a packed string
String mimeTypes;
// <mime>:<ext>,<ext>|<mime>:<ext>|...
for (const auto& contentTypeSpec : split(options.contentTypes, "|")) {
const auto parts = split(contentTypeSpec, ":");
const auto mime = trim(parts[0]);
const auto classes = split(mime, "/");
if (classes.size() == 2) {
if (mimeTypes.size() == 0) {
mimeTypes = mime;
} else {
mimeTypes += "|" + mime;
}
}
}

const auto mimeTypesRef = attachment.env->NewStringUTF(mimeTypes.c_str());
const auto results = (jobjectArray) CallObjectClassMethodFromAndroidEnvironment(
attachment.env,
dialogRef,
"showFileSystemPicker",
"(Ljava/lang/String;ZZZ)[Landroid/net/Uri;",
mimeTypesRef,
allowDirectories,
allowMultiple,
allowFiles
// we'll set the pointer from this instance in this call so
// the `onResults` can reinterpret the `jlong` back into a `Dialog*`
CallVoidClassMethodFromAndroidEnvironment(
attachment.env,
dialog,
"showFileSystemPicker",
"(Ljava/lang/String;ZZZJ)V",
attachment.env->NewStringUTF(mimeTypes.c_str()),
allowDirectories,
allowMultiple,
allowFiles,
reinterpret_cast<jlong>(this)
);

return true;
#endif

return false;
}
}

#if SOCKET_RUNTIME_PLATFORM_ANDROID
extern "C" {
void ANDROID_EXTERNAL(window, Dialog, onResults) (
JNIEnv* env,
jobject self,
jlong pointer,
jobjectArray results
) {
const auto app = App::sharedApplication();

if (!app) {
return ANDROID_THROW(env, "Missing 'App' in environment");
}

const auto dialog = reinterpret_cast<Dialog*>(pointer);

if (!dialog) {
return ANDROID_THROW(
env,
"Missing 'Dialog' in results callback from 'showFileSystemPicker'"
);
}

const auto length = attachment.env->GetArrayLength(results);
for (int i = 0; i < length; ++i) {
const auto uri = (jstring) attachment.env->GetObjectArrayElement(results, i);
if (uri) {
const auto string = Android::StringWrap(attachment.env, CallObjectClassMethodFromAndroidEnvironment(
attachment.env,
uri,
"toString",
"()Ljava/lang/String;"
)).str();

paths.push_back(string);
}
}
if (dialog->callback == nullptr) {
return ANDROID_THROW(
env,
"Missing 'Dialog' callback in results callback from 'showFileSystemPicker'"
);
}

const auto attachment = Android::JNIEnvironmentAttachment(app->jvm);
const auto length = attachment.env->GetArrayLength(results);

Vector<String> paths;

app->dispatch([=]() {
callback(paths);
});
return true;
for (int i = 0; i < length; ++i) {
const auto uri = (jstring) attachment.env->GetObjectArrayElement(results, i);
if (uri) {
const auto string = Android::StringWrap(attachment.env, CallObjectClassMethodFromAndroidEnvironment(
attachment.env,
uri,
"toString",
"()Ljava/lang/String;"
)).str();

paths.push_back(string);
}
}
#endif

return false;
const auto callback = dialog->callback;
dialog->callback = nullptr;
callback(paths);
}
}
#endif
150 changes: 81 additions & 69 deletions src/window/dialog.kt
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
package socket.runtime.window

import java.lang.Runtime
import java.util.concurrent.Semaphore

import android.net.Uri
import android.webkit.WebChromeClient

import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity

import socket.runtime.core.console
import socket.runtime.window.WindowManagerActivity

/**
* XXX
*/
open class Dialog (val window: Window) {
open class Dialog (val activity: WindowManagerActivity) {
/**
* XXX
*/
open class FileSystemPickerOptions (
val params: WebChromeClient.FileChooserParams? = null,
Expand All @@ -28,95 +31,104 @@ open class Dialog (val window: Window) {
}
}

var results = arrayOf<Uri>()
val activity = window.activity as AppCompatActivity
val semaphore = Semaphore(1)
var callback: ((results: Array<Uri>) -> Unit)? = null

val launcherForSingleItem = activity.registerForActivityResult(
ActivityResultContracts.GetContent(),
fun (uri: Uri?) { this.resolve(uri) }
)

val launcherForMulitpleItems = activity.registerForActivityResult(
ActivityResultContracts.GetMultipleContents(),
{ uris -> this.resolve(uris) }
)

// XXX(@jwerle): unused at the moment
val launcherForSingleDocument = activity.registerForActivityResult(
ActivityResultContracts.OpenDocument(),
{ uri -> this.resolve(uri) }
)

// XXX(@jwerle): unused at the moment
val launcherForMulitpleDocuments = activity.registerForActivityResult(
ActivityResultContracts.OpenMultipleDocuments(),
{ uris -> this.resolve(uris) }
)

// XXX(@jwerle): unused at the moment
val launcherForSingleVisualMedia = activity.registerForActivityResult(
ActivityResultContracts.PickVisualMedia(),
{ uri -> this.resolve(uri) }
)

// XXX(@jwerle): unused at the moment
val launcherForMultipleVisualMedia = activity.registerForActivityResult(
ActivityResultContracts.PickMultipleVisualMedia(),
{ uris -> this.resolve(uris) }
)

fun resolve (uri: Uri?) {
if (uri != null) {
this.results = arrayOf(uri)
return this.resolve(arrayOf(uri))
}
this.semaphore.release()
}

fun resolve (uris: Array<Uri>) {
this.results = uris
this.semaphore.release()
return this.resolve(arrayOf<Uri>())
}

fun resolve (uris: List<Uri>) {
this.results = Array<Uri>(uris.size, { i -> uris[i] })
this.semaphore.release()
this.resolve(Array<Uri>(uris.size, { i -> uris[i] }))
}

fun showFileSystemPicker (options: FileSystemPickerOptions): Array<Uri> {
val mimeType =
if (options.mimeTypes.size > 0) { options.mimeTypes[0] }
else { "*/*" }

this.semaphore.acquireUninterruptibly()

val launcherForSingleItem = activity.registerForActivityResult(
ActivityResultContracts.GetContent(),
fun (uri: Uri?) { this.resolve(uri) }
)

val launcherForMulitpleItems = activity.registerForActivityResult(
ActivityResultContracts.GetMultipleContents(),
{ uris -> this.resolve(uris) }
)

// XXX(@jwerle): unused at the moment
val launcherForSingleDocument = activity.registerForActivityResult(
ActivityResultContracts.OpenDocument(),
{ uri -> this.resolve(uri) }
)

// XXX(@jwerle): unused at the moment
val launcherForMulitpleDocuments = activity.registerForActivityResult(
ActivityResultContracts.OpenMultipleDocuments(),
{ uris -> this.resolve(uris) }
)

// XXX(@jwerle): unused at the moment
val launcherForSingleVisualMedia = activity.registerForActivityResult(
ActivityResultContracts.PickVisualMedia(),
{ uri -> this.resolve(uri) }
)

// XXX(@jwerle): unused at the moment
val launcherForMultipleVisualMedia = activity.registerForActivityResult(
ActivityResultContracts.PickMultipleVisualMedia(),
{ uris -> this.resolve(uris) }
)

// TODO(@jwerle): support the other launcher types above
// through the `showFileSystemPicker()` method some how

if (options.multiple) {
launcherForMulitpleItems.launch(mimeType)
} else {
launcherForSingleItem.launch(mimeType)
fun resolve (uris: Array<Uri>) {
val callback = this.callback
if (callback != null) {
this.callback = null
callback(uris)
}
}

this.semaphore.acquireUninterruptibly()
val results = this.results
this.semaphore.release()
return results
fun showFileSystemPicker (
options: FileSystemPickerOptions,
callback: ((Array<Uri>) -> Unit)? = null
) {
val mimeType =
if (options.mimeTypes.size > 0 && options.mimeTypes[0].length > 0) {
options.mimeTypes[0]
} else { "*/*" }

this.callback = callback

this.activity.runOnUiThread {
// TODO(@jwerle): support the other launcher types above
// through the `showFileSystemPicker()` method some how
if (options.multiple) {
launcherForMulitpleItems.launch(mimeType)
} else {
launcherForSingleItem.launch(mimeType)
}
}
}

fun showFileSystemPicker (
mimeTypes: String,
directories: Boolean = false,
multiple: Boolean = false,
files: Boolean = true
): Array<Uri> {
files: Boolean = true,
pointer: Long = 0
) {
return this.showFileSystemPicker(FileSystemPickerOptions(
null,
mimeTypes.split("|").toMutableList(),
directories,
multiple,
files
))
), fun (uris: Array<Uri>) {
if (pointer != 0L) {
this.onResults(pointer, uris)
}
})
}

@Throws(Exception::class)
external fun onResults (pointer: Long, results: Array<Uri>): Unit
}
Loading

0 comments on commit 52ac4ac

Please sign in to comment.