Skip to content
Merged
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
32 changes: 28 additions & 4 deletions app/src/main/cpp/Core/fileselector/fileselector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ jclass class_FileSelector;
jclass class_String;
jmethodID method_nselectFile;
jmethodID method_nselectFileMulti;
jmethodID method_nselectFileMultiNamed;
jmethodID method_nselectFiles;


Expand All @@ -20,6 +21,7 @@ PRIVATE_API void fsel_setup(JNIEnv* env) {
class_String = (jclass)env->NewGlobalRef(env->FindClass("java/lang/String"));
method_nselectFile = env->GetStaticMethodID(class_FileSelector, "nselectFile", "(Ljava/lang/String;JZ)Z");
method_nselectFileMulti = env->GetStaticMethodID(class_FileSelector, "nselectFileMulti", "([Ljava/lang/String;JZ)Z");
method_nselectFileMultiNamed = env->GetStaticMethodID(class_FileSelector, "nselectFileMultiNamed", "([Ljava/lang/String;JZLjava/lang/String;)Z");
method_nselectFiles = env->GetStaticMethodID(class_FileSelector, "nselectFiles", "([Ljava/lang/String;J)Z");
}
extern "C"
Expand Down Expand Up @@ -91,8 +93,9 @@ static jobjectArray fsel_build_mime_array(JNIEnv* env,
return arr;
}

bool requestFileMulti(const char* const* mime_types, size_t mime_type_count,
callback_function callback, bool save) {
bool requestFileMultiNamed(const char* const* mime_types, size_t mime_type_count,
callback_function callback, bool save,
const char* suggested_name) {
if (!mime_types || mime_type_count == 0) return false;

JNIEnv* env;
Expand All @@ -104,17 +107,38 @@ bool requestFileMulti(const char* const* mime_types, size_t mime_type_count,
jobjectArray mimeArr = fsel_build_mime_array(env, mime_types, mime_type_count);
if (!mimeArr) return false;

// Null stays null all the way into the Java side, where it means "leave the
// name field as it was" - the pre-EXTRA_TITLE behaviour, and what
// requestFileMulti below still asks for.
jstring nameStr = nullptr;
if (suggested_name && suggested_name[0] != '\0') {
nameStr = env->NewStringUTF(suggested_name);
if (!nameStr || fsel_check_exception(env, "NewStringUTF(name)")) {
env->DeleteLocalRef(mimeArr);
return false;
}
}

jboolean result = env->CallStaticBooleanMethod(
class_FileSelector, method_nselectFileMulti,
mimeArr, (jlong)callback, save);
class_FileSelector, method_nselectFileMultiNamed,
mimeArr, (jlong)callback, save, nameStr);
const bool exceptionRaised = fsel_check_exception(env, "CallStaticBooleanMethod");

if (nameStr) env->DeleteLocalRef(nameStr);
env->DeleteLocalRef(mimeArr);

if (exceptionRaised) return false;
return result == JNI_TRUE;
}

// Kept as a delegate rather than a second copy of the body: this symbol is
// linked by every mod already built against this header, so it has to stay -
// but two JNI bodies to keep in step is how the two forms drift apart.
bool requestFileMulti(const char* const* mime_types, size_t mime_type_count,
callback_function callback, bool save) {
return requestFileMultiNamed(mime_types, mime_type_count, callback, save, nullptr);
}

bool requestFiles(const char* const* mime_types, size_t mime_type_count,
callback_function_files callback) {
if (!mime_types || mime_type_count == 0) return false;
Expand Down
23 changes: 23 additions & 0 deletions app/src/main/cpp/Core/fileselector/fileselector.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,29 @@ bool requestFileMulti(const char* const* mime_types,
callback_function callback,
bool save);

/**
* requestFileMulti, plus a proposed file name for save mode.
* suggested_name - initial name shown in the picker, or nullptr/"" for none.
* Ignored when save is false; the user can always change it.
*
* Pass this whenever save is true. ACTION_CREATE_DOCUMENT takes its initial
* name from EXTRA_TITLE, and with no name the picker's field opens empty - which
* the platform does not leave empty: an empty display name comes back as the
* literal "(invalid)" from FileUtils.buildValidFatFilename, so the file is
* created as "(invalid).<ext>".
*
* Include the extension. FileUtils.splitFileName keeps it when it maps back to
* mime_types[0] and swaps it for the type's own otherwise, so there is no
* double-suffix case to avoid. Illegal characters, over-long names and
* collisions are all handled by the provider - it appends "(1)" rather than
* overwriting.
*/
bool requestFileMultiNamed(const char* const* mime_types,
size_t mime_type_count,
callback_function callback,
bool save,
const char* suggested_name);

/**
* Callback for requestFiles.
* count - number of file descriptors; 0 on cancel or all-failed
Expand Down
43 changes: 40 additions & 3 deletions app/src/main/java/git/artdeell/skymodloader/FileSelector.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,26 @@ public boolean selectFile(String mimeType, long callbackFunction, boolean save)
public static boolean nselectFileMulti(String[] mimeTypes, long callbackFunction, boolean save) {
return selector.selectFileMulti(mimeTypes, callbackFunction, save);
}

// Save mode with a proposed file name.
//
// Kept as a SEPARATE entry point rather than a fourth argument on the one
// above: nselectFileMulti is resolved from native by its exact signature
// ([Ljava/lang/String;JZ)Z, so every mod built against the old Canvas would
// fail to find it the moment that changed.
public static boolean nselectFileMultiNamed(String[] mimeTypes, long callbackFunction,
boolean save, String suggestedName) {
return selector.selectFileMulti(mimeTypes, callbackFunction, save, suggestedName);
}

public boolean selectFileMulti(String[] mimeTypes, long callbackFunction, boolean save) {
return selectFileMulti(mimeTypes, callbackFunction, save, null);
}

// suggestedName - initial name for the save picker, or null to leave the
// field empty as before. May include the extension; see buildPickerIntent.
public boolean selectFileMulti(String[] mimeTypes, long callbackFunction, boolean save,
String suggestedName) {
if(this.callbackFunction != 0) return false;
if(mimeTypes == null || mimeTypes.length == 0) return false;

Expand All @@ -65,7 +84,7 @@ public boolean selectFileMulti(String[] mimeTypes, long callbackFunction, boolea
this.isSaveMode = save;
this.isMultiFilesMode = false;
gameActivity.startActivityForResult(
buildPickerIntent(types, save, /*allowMultiple=*/false),
buildPickerIntent(types, save, /*allowMultiple=*/false, suggestedName),
SELECTING_FILE);
});
return true;
Expand All @@ -85,13 +104,15 @@ public boolean selectFiles(String[] mimeTypes, long callbackFunction) {
this.isSaveMode = false;
this.isMultiFilesMode = true;
gameActivity.startActivityForResult(
buildPickerIntent(types, /*save=*/false, /*allowMultiple=*/true),
buildPickerIntent(types, /*save=*/false, /*allowMultiple=*/true,
/*suggestedName=*/null),
SELECTING_FILE);
});
return true;
}

private static Intent buildPickerIntent(String[] types, boolean save, boolean allowMultiple) {
private static Intent buildPickerIntent(String[] types, boolean save, boolean allowMultiple,
String suggestedName) {
Intent i = new Intent(save ? Intent.ACTION_CREATE_DOCUMENT : Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
if(allowMultiple) i.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
Expand All @@ -101,6 +122,22 @@ private static Intent buildPickerIntent(String[] types, boolean save, boolean al
i.setType("*/*");
i.putExtra(Intent.EXTRA_MIME_TYPES, types);
}
// ACTION_CREATE_DOCUMENT reads its initial file name from EXTRA_TITLE;
// the user can still change it before saving. Without it the name field
// opens empty, and an empty display name is NOT left empty by the
// platform - FileUtils.buildValidFatFilename substitutes the literal
// "(invalid)", so saved files arrive as "(invalid).json".
//
// The name may carry its extension. FileUtils.splitFileName keeps the
// caller's extension when it maps back to the requested MIME type and
// replaces it otherwise, so "sheet.json" + "application/json" stays
// "sheet.json" rather than gaining a second suffix. Illegal characters
// and over-long names are sanitized by the provider, so nothing here has
// to - only the empty case needs guarding, since that is the one
// buildValidFatFilename cannot rescue.
if(save && suggestedName != null && !suggestedName.isEmpty()) {
i.putExtra(Intent.EXTRA_TITLE, suggestedName);
}
return i;
}

Expand Down
Loading