Import infpnt files to android version

This commit is contained in:
Yousef Khadadeh 2026-06-01 15:42:33 +03:00
parent cde106eba7
commit 0da317e87c
17 changed files with 135 additions and 9 deletions

View file

@ -24,6 +24,7 @@
void stopNetworkService();
void shareText(java.lang.String);
void shareInternalFiles(java.lang.String[], java.lang.String);
java.lang.String getFileNameFromUriString(java.lang.String);
}
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLActivity {

View file

@ -82,7 +82,7 @@
<meta-data android:name="SDL_ENV.SDL_ANDROID_TRAP_BACK_BUTTON" android:value="0"/>
-->
<service
android:name="InfiniPaintNetworkService"
android:name=".InfiniPaintNetworkService"
android:foregroundServiceType="specialUse"
android:exported="false">
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
@ -103,11 +103,12 @@
android:label="@string/app_name"
android:windowSoftInputMode="adjustNothing"
android:alwaysRetainTaskState="true"
android:launchMode="singleTop"
android:launchMode="singleTask"
android:configChanges="layoutDirection|locale|grammaticalGender|fontScale|fontWeightAdjustment|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
android:preferMinimalPostProcessing="true"
android:exported="true"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />

View file

@ -22,10 +22,12 @@ import static androidx.core.content.FileProvider.getUriForFile;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.provider.OpenableColumns;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@ -202,4 +204,35 @@ public class InfiniPaint extends SDLActivity {
Intent shareIntent = Intent.createChooser(sendIntent, null);
getContext().startActivity(shareIntent);
}
// https://stackoverflow.com/a/25005243
static public String getFileNameFromUriString(String strUri) {
if(strUri == null)
return null;
Uri uri = Uri.parse(strUri);
if(uri == null)
return null;
String result = null;
if (uri.getScheme() != null && uri.getScheme().equals("content")) {
Cursor cursor = getContext().getContentResolver().query(uri, null, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
result = cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME));
}
} finally {
if(cursor != null)
cursor.close();
}
}
if (result == null) {
result = uri.getPath();
if(result == null)
return null;
int cut = result.lastIndexOf('/');
if (cut != -1) {
result = result.substring(cut + 1);
}
}
return result;
}
}

View file

@ -189,3 +189,16 @@ std::vector<std::string> glob_path_as_string_list(const std::filesystem::path& f
toRet[i] = pathToStr(std::filesystem::path(filesInPath[i]));
return toRet;
}
std::filesystem::path sdl_safe_copy_file(const std::filesystem::path& toPath, const std::filesystem::path& fileToCopy, const std::string& fileName, const std::string& fileExtension) {
std::vector<std::string> toFolderListNames;
try {
toFolderListNames = glob_path_as_string_list(toPath, "*", 0, [&](const auto& p){ return p.stem().string();});
} catch(...) {
SDL_CreateDirectory(toPath.string().c_str());
}
std::string newFileName = ensure_string_unique(toFolderListNames, std::filesystem::path(fileName).stem().string());
std::filesystem::path newPath = toPath / (newFileName + "." + fileExtension);
SDL_CopyFile(fileToCopy.string().c_str(), newPath.string().c_str());
return newPath;
}

View file

@ -36,3 +36,4 @@ std::string remove_carriage_returns_from_str(std::string s);
std::string ensure_string_unique(const std::vector<std::string>& stringList, std::string str);
std::string sdl_time_to_nice_access_time(const SDL_DateTime& t, SDL_DateFormat dF, SDL_TimeFormat tF);
std::vector<std::string> glob_path_as_string_list(const std::filesystem::path& folder, const char* globStr, SDL_GlobFlags globFlags, const std::function<std::string(const std::filesystem::path)>& pathToStr);
std::filesystem::path sdl_safe_copy_file(const std::filesystem::path& toPath, const std::filesystem::path& fileToCopy, const std::string& fileName, const std::string& fileExtension);

View file

@ -64,6 +64,22 @@ namespace AndroidJNICalls {
bool textChanged = false;
bool cursorChanged = false;
std::string getFileNameFromURI(const std::string& uri) {
JNIEnv* env = static_cast<JNIEnv*>(SDL_GetAndroidJNIEnv());
std::string strToRet = "";
jni_local_frame(env, 16, [&] {
jobject activity = (jobject) SDL_GetAndroidActivity();
jclass clazz = env->GetObjectClass(activity);
jmethodID method_id = env->GetStaticMethodID(clazz, "getFileNameFromUriString",
"(Ljava/lang/String;)Ljava/lang/String;");
jobject obj = env->CallStaticObjectMethod(clazz, method_id, string2jstring(env, uri));
if(obj == nullptr)
return;
strToRet = jstring2string(env, static_cast<jstring>(obj));
});
return strToRet;
}
void shareInternalFiles(const std::vector<std::string>& filePaths, const std::string& mimeType) {
if(filePaths.empty())
return;

View file

@ -26,6 +26,7 @@ struct InputManager;
namespace AndroidJNICalls {
extern InputManager* globalInputManager;
std::string getFileNameFromURI(const std::string& uri);
void shareInternalFiles(const std::vector<std::string>& filePaths, const std::string& mimeType);
void shareText(const std::string& str);
void startTextInput(CustomEvents::InputTextBoxID newTextboxID, const std::shared_ptr<RichText::TextBox>& newTextbox, const std::shared_ptr<RichText::TextBox::Cursor>& newCursor, const std::shared_ptr<RichText::TextStyleModifier::ModifierMap>& modMap, int inputType);

View file

@ -41,6 +41,7 @@ void init() {
AddFileToCanvasEvent::EVENT_NUM = SDL_RegisterEvents(1);
RefreshTextBoxInputEvent::EVENT_NUM = SDL_RegisterEvents(1);
AndroidTextBoxInputEvent::EVENT_NUM = SDL_RegisterEvents(1);
MobileImportCanvasEvent::EVENT_NUM = SDL_RegisterEvents(1);
alreadyInitialized = true;
}
}
@ -50,5 +51,6 @@ uint32_t OpenInfiniPaintFileEvent::EVENT_NUM;
uint32_t AddFileToCanvasEvent::EVENT_NUM;
uint32_t RefreshTextBoxInputEvent::EVENT_NUM;
uint32_t AndroidTextBoxInputEvent::EVENT_NUM;
uint32_t MobileImportCanvasEvent::EVENT_NUM;
}

View file

@ -104,8 +104,8 @@ namespace CustomEvents {
Vector3i intData;
};
struct AndroidInsetsChangedEvent {
struct MobileImportCanvasEvent {
static uint32_t EVENT_NUM;
SCollision::AABB<float> safeWindowRect;
std::filesystem::path filePath;
};
}

View file

@ -70,7 +70,7 @@ MainProgram::MainProgram():
Logger::get().set_log_function(Logger::LogType::WORLDFATAL, [&](const std::string& text) {
*logFile << "[WORLDFATAL] " << text << std::endl;
std::cout << "[WORLDFATAL] " << text << std::endl;
logMessages.emplace_front(UserLogMessage{text, UserLogMessage::COLOR_ERROR, UserLogMessage::DISPLAY_DESKTOP_ONLY});
logMessages.emplace_front(UserLogMessage{text, UserLogMessage::COLOR_ERROR, UserLogMessage::DISPLAY_FOR_ALL});
if(logMessages.size() == LOG_SIZE)
logMessages.pop_back();
g.gui.set_to_layout();
@ -345,7 +345,11 @@ void MainProgram::create_new_tab(const CustomEvents::OpenInfiniPaintFileEvent& o
newWorld = std::make_shared<World>(*this, openFile);
}
catch(const std::runtime_error& e) {
#ifdef __ANDROID__
Logger::get().log(Logger::LogType::WORLDFATAL, std::string("Failed to open canvas: ") + e.what());
#else
Logger::get().log(Logger::LogType::WORLDFATAL, "Failed to open canvas: " + (openFile.filePathSource.has_value() ? openFile.filePathSource.value().string() : "NO PATH") + " with error: " + e.what());
#endif
return;
}
worlds.emplace_back(newWorld);
@ -390,6 +394,12 @@ void MainProgram::input_open_infinipaint_file_callback(const CustomEvents::OpenI
post_callback();
}
void MainProgram::input_mobile_import_canvas_callback(const CustomEvents::MobileImportCanvasEvent& mobileImport) {
screen->input_mobile_import_canvas_callback(mobileImport);
g.gui.set_to_layout();
post_callback();
}
void MainProgram::input_paste_callback(const CustomEvents::PasteEvent& paste) {
g.input_paste_callback(paste);
screen->input_paste_callback(paste);

View file

@ -161,6 +161,7 @@ class MainProgram {
void input_open_infinipaint_file_callback(const CustomEvents::OpenInfiniPaintFileEvent& openFile);
void input_paste_callback(const CustomEvents::PasteEvent& paste);
void input_android_text_box_input_callback(const CustomEvents::AndroidTextBoxInputEvent& textboxInput);
void input_mobile_import_canvas_callback(const CustomEvents::MobileImportCanvasEvent& mobileImport);
void input_app_about_to_go_to_background_callback();
void input_app_about_to_go_to_foreground_callback();

View file

@ -32,6 +32,7 @@
#include <cereal/archives/portable_binary.hpp>
#include <cereal/types/unordered_map.hpp>
#include "MainProgram.hpp"
#include "AndroidJNICalls.hpp"
ResourceManager::ResourceManager(World& initWorld):
world(initWorld)
@ -108,7 +109,13 @@ void ResourceManager::update() {
NetworkingObjects::NetObjTemporaryPtr<ResourceData> ResourceManager::add_resource_file(const std::filesystem::path& filePath) {
ResourceData resource;
resource.data = std::make_shared<std::string>();
#ifdef __ANDROID__
resource.name = AndroidJNICalls::getFileNameFromURI(filePath.string());
if(resource.name.empty())
resource.name = "New Resource";
#else
resource.name = std::filesystem::path(filePath).filename().string();
#endif
try {
*resource.data = read_file_to_string(filePath);

View file

@ -44,8 +44,10 @@
using namespace GUIStuff;
using namespace ElementHelpers;
#define MAIN_SAVES_FOLDER_STR "saves"
FileSelectScreen::FileSelectScreen(MainProgram& m): Screen(m) {
savePath = main.conf.configPath / "saves";
savePath = main.conf.configPath / MAIN_SAVES_FOLDER_STR;
trashPath = main.conf.configPath / "trash";
infoPath = main.conf.configPath / "fileSelectInfo.json";
@ -432,7 +434,7 @@ void FileSelectScreen::share_selected_files() {
std::vector<std::string> filesToSend;
for(const FileInfo& f : fileList) {
if(f.selected)
filesToSend.emplace_back("saves/" + f.fileName + ".infpnt");
filesToSend.emplace_back(std::string(MAIN_SAVES_FOLDER_STR) + std::string("/") + f.fileName + ".infpnt");
}
if(!filesToSend.empty())
AndroidJNICalls::shareInternalFiles(filesToSend, "application/octet-stream");
@ -702,7 +704,10 @@ void FileSelectScreen::title_bar() {
case MoreOptionsMenu::CLOSED:
break;
case MoreOptionsMenu::MAIN:
elemCount = 3;
if(selectedMenu == SelectedMenu::FILES)
elemCount = 4;
else
elemCount = 3;
break;
case MoreOptionsMenu::VIEW:
elemCount = 4;
@ -747,6 +752,17 @@ void FileSelectScreen::title_bar() {
});
break;
}
case 3: {
text_transparent_option_button("Import Canvas", "Import Canvas (.infpnt)", [&] {
open_file_selector("Open InfiniPaint Canvas", {{"InfiniPaint Canvas", "infpnt"}}, [&](const std::filesystem::path& p, const auto& e) {
CustomEvents::emit_event<CustomEvents::MobileImportCanvasEvent>({
.filePath = p,
});
});
moreOptionsMenu = MoreOptionsMenu::CLOSED;
});
break;
}
}
break;
}
@ -1213,7 +1229,10 @@ void FileSelectScreen::input_paste_callback(const CustomEvents::PasteEvent& past
void FileSelectScreen::input_open_infinipaint_file_callback(const CustomEvents::OpenInfiniPaintFileEvent& openFile) {
main.create_new_tab(openFile);
main.set_screen([&] (std::unique_ptr<Screen>) { return std::make_unique<PhoneDrawingProgramScreen>(main); });
if(main.world)
main.set_screen([&] (std::unique_ptr<Screen>) { return std::make_unique<PhoneDrawingProgramScreen>(main); });
else if(!openFile.isClient && openFile.filePathSource.has_value()) // Invalid file, remove it
SDL_RemovePath(openFile.filePathSource.value().string().c_str());
}
void FileSelectScreen::input_global_back_button_callback() {
@ -1232,6 +1251,22 @@ void FileSelectScreen::input_global_back_button_callback() {
main.g.gui.set_to_layout();
}
void FileSelectScreen::input_mobile_import_canvas_callback(const CustomEvents::MobileImportCanvasEvent& mobileImport) {
#ifdef __ANDROID__
std::string name = AndroidJNICalls::getFileNameFromURI(mobileImport.filePath.string());
if(name.empty())
name = "New File";
std::filesystem::path newPath = sdl_safe_copy_file(savePath, mobileImport.filePath, name, World::FILE_EXTENSION);
#else
std::filesystem::path newPath = sdl_safe_copy_file(savePath, mobileImport.filePath, mobileImport.filePath.stem().string(), World::FILE_EXTENSION);
#endif
CustomEvents::emit_event<CustomEvents::OpenInfiniPaintFileEvent>({
.isClient = false,
.saveThumbnail = true,
.filePathSource = newPath
});
}
void FileSelectScreen::draw(SkCanvas* canvas) {
canvas->clear(main.g.gui.io.theme->backColor0);
}

View file

@ -32,6 +32,7 @@ class FileSelectScreen : public Screen {
virtual void input_open_infinipaint_file_callback(const CustomEvents::OpenInfiniPaintFileEvent& openFile) override;
virtual void input_global_back_button_callback() override;
virtual void input_app_about_to_go_to_background_callback() override;
virtual void input_mobile_import_canvas_callback(const CustomEvents::MobileImportCanvasEvent& mobileImport) override;
struct TrashInfo {
struct TrashFile {

View file

@ -61,6 +61,7 @@ void Screen::gui_layout_run() {}
bool Screen::app_close_requested() { return true; }
void Screen::input_add_file_to_canvas_callback(const CustomEvents::AddFileToCanvasEvent& addFile) {}
void Screen::input_open_infinipaint_file_callback(const CustomEvents::OpenInfiniPaintFileEvent& openFile) {}
void Screen::input_mobile_import_canvas_callback(const CustomEvents::MobileImportCanvasEvent& mobileImport) {}
void Screen::input_paste_callback(const CustomEvents::PasteEvent& paste) {}
void Screen::input_android_text_box_input_callback(const CustomEvents::AndroidTextBoxInputEvent& textboxInput) {}
void Screen::input_global_back_button_callback() {}

View file

@ -45,6 +45,7 @@ class Screen {
virtual void input_open_infinipaint_file_callback(const CustomEvents::OpenInfiniPaintFileEvent& openFile);
virtual void input_paste_callback(const CustomEvents::PasteEvent& paste);
virtual void input_android_text_box_input_callback(const CustomEvents::AndroidTextBoxInputEvent& textboxInput);
virtual void input_mobile_import_canvas_callback(const CustomEvents::MobileImportCanvasEvent& mobileImport);
virtual void input_global_back_button_callback();
virtual void input_drop_file_callback(const InputManager::DropCallbackArgs& drop);
virtual void input_drop_text_callback(const InputManager::DropCallbackArgs& drop);

View file

@ -829,6 +829,8 @@ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) {
}
else if(event->type == CustomEvents::AndroidTextBoxInputEvent::EVENT_NUM)
mS.m->input_android_text_box_input_callback(*CustomEvents::get_event<CustomEvents::AndroidTextBoxInputEvent>());
else if(event->type == CustomEvents::MobileImportCanvasEvent::EVENT_NUM)
mS.m->input_mobile_import_canvas_callback(*CustomEvents::get_event<CustomEvents::MobileImportCanvasEvent>());
break;
}
}