From 0da317e87c0f51f116e3c71bfab11fcb2321c458 Mon Sep 17 00:00:00 2001 From: Yousef Khadadeh Date: Mon, 1 Jun 2026 15:42:33 +0300 Subject: [PATCH] Import infpnt files to android version --- android-project/app/proguard-rules.pro | 1 + .../app/src/main/AndroidManifest.xml | 5 ++- .../erroratline0/infinipaint/InfiniPaint.java | 33 ++++++++++++++ include/Helpers/StringHelpers.cpp | 13 ++++++ include/Helpers/StringHelpers.hpp | 1 + src/AndroidJNICalls.cpp | 16 +++++++ src/AndroidJNICalls.hpp | 1 + src/CustomEvents.cpp | 2 + src/CustomEvents.hpp | 4 +- src/MainProgram.cpp | 12 +++++- src/MainProgram.hpp | 1 + src/ResourceManager.cpp | 7 +++ src/Screens/FileSelectScreen.cpp | 43 +++++++++++++++++-- src/Screens/FileSelectScreen.hpp | 1 + src/Screens/Screen.cpp | 1 + src/Screens/Screen.hpp | 1 + src/main.cpp | 2 + 17 files changed, 135 insertions(+), 9 deletions(-) diff --git a/android-project/app/proguard-rules.pro b/android-project/app/proguard-rules.pro index ba24477..bc1478d 100644 --- a/android-project/app/proguard-rules.pro +++ b/android-project/app/proguard-rules.pro @@ -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 { diff --git a/android-project/app/src/main/AndroidManifest.xml b/android-project/app/src/main/AndroidManifest.xml index 1049b8e..a9f200d 100644 --- a/android-project/app/src/main/AndroidManifest.xml +++ b/android-project/app/src/main/AndroidManifest.xml @@ -82,7 +82,7 @@ --> + diff --git a/android-project/app/src/main/java/com/erroratline0/infinipaint/InfiniPaint.java b/android-project/app/src/main/java/com/erroratline0/infinipaint/InfiniPaint.java index ccda8d6..a7f5dfd 100644 --- a/android-project/app/src/main/java/com/erroratline0/infinipaint/InfiniPaint.java +++ b/android-project/app/src/main/java/com/erroratline0/infinipaint/InfiniPaint.java @@ -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; + } } diff --git a/include/Helpers/StringHelpers.cpp b/include/Helpers/StringHelpers.cpp index 2ebf165..4b41964 100644 --- a/include/Helpers/StringHelpers.cpp +++ b/include/Helpers/StringHelpers.cpp @@ -189,3 +189,16 @@ std::vector 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 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; +} diff --git a/include/Helpers/StringHelpers.hpp b/include/Helpers/StringHelpers.hpp index 81e57cf..a2f289f 100644 --- a/include/Helpers/StringHelpers.hpp +++ b/include/Helpers/StringHelpers.hpp @@ -36,3 +36,4 @@ std::string remove_carriage_returns_from_str(std::string s); std::string ensure_string_unique(const std::vector& stringList, std::string str); std::string sdl_time_to_nice_access_time(const SDL_DateTime& t, SDL_DateFormat dF, SDL_TimeFormat tF); std::vector glob_path_as_string_list(const std::filesystem::path& folder, const char* globStr, SDL_GlobFlags globFlags, const std::function& 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); diff --git a/src/AndroidJNICalls.cpp b/src/AndroidJNICalls.cpp index 5d1638c..d818e2a 100644 --- a/src/AndroidJNICalls.cpp +++ b/src/AndroidJNICalls.cpp @@ -64,6 +64,22 @@ namespace AndroidJNICalls { bool textChanged = false; bool cursorChanged = false; + std::string getFileNameFromURI(const std::string& uri) { + JNIEnv* env = static_cast(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(obj)); + }); + return strToRet; + } + void shareInternalFiles(const std::vector& filePaths, const std::string& mimeType) { if(filePaths.empty()) return; diff --git a/src/AndroidJNICalls.hpp b/src/AndroidJNICalls.hpp index 3518ef0..f0a0d23 100644 --- a/src/AndroidJNICalls.hpp +++ b/src/AndroidJNICalls.hpp @@ -26,6 +26,7 @@ struct InputManager; namespace AndroidJNICalls { extern InputManager* globalInputManager; + std::string getFileNameFromURI(const std::string& uri); void shareInternalFiles(const std::vector& filePaths, const std::string& mimeType); void shareText(const std::string& str); void startTextInput(CustomEvents::InputTextBoxID newTextboxID, const std::shared_ptr& newTextbox, const std::shared_ptr& newCursor, const std::shared_ptr& modMap, int inputType); diff --git a/src/CustomEvents.cpp b/src/CustomEvents.cpp index c868945..9a3a589 100644 --- a/src/CustomEvents.cpp +++ b/src/CustomEvents.cpp @@ -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; } diff --git a/src/CustomEvents.hpp b/src/CustomEvents.hpp index 48010fd..1456ceb 100644 --- a/src/CustomEvents.hpp +++ b/src/CustomEvents.hpp @@ -104,8 +104,8 @@ namespace CustomEvents { Vector3i intData; }; - struct AndroidInsetsChangedEvent { + struct MobileImportCanvasEvent { static uint32_t EVENT_NUM; - SCollision::AABB safeWindowRect; + std::filesystem::path filePath; }; } diff --git a/src/MainProgram.cpp b/src/MainProgram.cpp index a5c87ce..acf1a03 100755 --- a/src/MainProgram.cpp +++ b/src/MainProgram.cpp @@ -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(*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); diff --git a/src/MainProgram.hpp b/src/MainProgram.hpp index 81194df..8b01a57 100644 --- a/src/MainProgram.hpp +++ b/src/MainProgram.hpp @@ -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(); diff --git a/src/ResourceManager.cpp b/src/ResourceManager.cpp index 575d279..85729a9 100644 --- a/src/ResourceManager.cpp +++ b/src/ResourceManager.cpp @@ -32,6 +32,7 @@ #include #include #include "MainProgram.hpp" +#include "AndroidJNICalls.hpp" ResourceManager::ResourceManager(World& initWorld): world(initWorld) @@ -108,7 +109,13 @@ void ResourceManager::update() { NetworkingObjects::NetObjTemporaryPtr ResourceManager::add_resource_file(const std::filesystem::path& filePath) { ResourceData resource; resource.data = std::make_shared(); +#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); diff --git a/src/Screens/FileSelectScreen.cpp b/src/Screens/FileSelectScreen.cpp index 49420a0..94fb450 100644 --- a/src/Screens/FileSelectScreen.cpp +++ b/src/Screens/FileSelectScreen.cpp @@ -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 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({ + .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) { return std::make_unique(main); }); + if(main.world) + main.set_screen([&] (std::unique_ptr) { return std::make_unique(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({ + .isClient = false, + .saveThumbnail = true, + .filePathSource = newPath + }); +} + void FileSelectScreen::draw(SkCanvas* canvas) { canvas->clear(main.g.gui.io.theme->backColor0); } diff --git a/src/Screens/FileSelectScreen.hpp b/src/Screens/FileSelectScreen.hpp index 44dfdaf..da317d4 100644 --- a/src/Screens/FileSelectScreen.hpp +++ b/src/Screens/FileSelectScreen.hpp @@ -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 { diff --git a/src/Screens/Screen.cpp b/src/Screens/Screen.cpp index 98356d2..8a931f0 100644 --- a/src/Screens/Screen.cpp +++ b/src/Screens/Screen.cpp @@ -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() {} diff --git a/src/Screens/Screen.hpp b/src/Screens/Screen.hpp index f41d74a..26a674c 100644 --- a/src/Screens/Screen.hpp +++ b/src/Screens/Screen.hpp @@ -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); diff --git a/src/main.cpp b/src/main.cpp index 21b2e13..98c2d35 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -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()); + else if(event->type == CustomEvents::MobileImportCanvasEvent::EVENT_NUM) + mS.m->input_mobile_import_canvas_callback(*CustomEvents::get_event()); break; } }