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;
}
}