From 91c3e507717b7d844d9df2392372bc6859183e60 Mon Sep 17 00:00:00 2001 From: Anuken Date: Wed, 15 Jul 2026 14:21:38 -0400 Subject: [PATCH] Android file chooser fixes & support for save loading from file browser --- android/AndroidManifest.xml | 20 +++++ .../mindustry/android/AndroidLauncher.java | 82 +++++++++-------- core/src/mindustry/io/SaveMeta.java | 4 + core/src/mindustry/maps/Maps.java | 6 +- core/src/mindustry/ui/FileChooser.java | 2 +- .../ui/dialogs/EditorMapsDialog.java | 88 ++++++++++--------- 6 files changed, 117 insertions(+), 85 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 60e0dbc1d8..78c3080541 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -25,6 +25,7 @@ android:label="@string/app_name" android:screenOrientation="user" android:exported="true" + android:launchMode="singleTask" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout"> @@ -39,6 +40,25 @@ + + + + + + + + + + + + + + + + + + + diff --git a/android/src/mindustry/android/AndroidLauncher.java b/android/src/mindustry/android/AndroidLauncher.java index 652f494796..ea9fdc8a4d 100644 --- a/android/src/mindustry/android/AndroidLauncher.java +++ b/android/src/mindustry/android/AndroidLauncher.java @@ -16,6 +16,7 @@ import arc.struct.*; import arc.util.*; import dalvik.system.*; import mindustry.*; +import mindustry.game.EventType.*; import mindustry.game.Saves.*; import mindustry.io.*; import mindustry.net.*; @@ -222,7 +223,9 @@ public class AndroidLauncher extends AndroidApplication{ hideStatusBar = true; useGL30 = true; }}); - checkFiles(getIntent()); + + var intent = getIntent(); + Events.on(ClientLoadEvent.class, u -> handleFiles(intent)); try{ //new external folder @@ -280,54 +283,49 @@ public class AndroidLauncher extends AndroidApplication{ } } - private void checkFiles(Intent intent){ + @Override + protected void onNewIntent(Intent intent){ + super.onNewIntent(intent); + + handleFiles(intent); + } + + private void handleFiles(Intent intent){ + if(intent == null) return; + try{ Uri uri = intent.getData(); if(uri != null){ - File myFile = null; - String scheme = uri.getScheme(); - if(scheme.equals("file")){ - String fileName = uri.getEncodedPath(); - myFile = new File(fileName); - }else if(!scheme.equals("content")){ - //error - return; - } - boolean save = uri.getPath().endsWith(saveExtension); - boolean map = uri.getPath().endsWith(mapExtension); - InputStream inStream; - if(myFile != null) inStream = new FileInputStream(myFile); - else inStream = getContentResolver().openInputStream(uri); - Core.app.post(() -> Core.app.post(() -> { - if(save){ //open save - System.out.println("Opening save."); - Fi file = Core.files.local("temp-save." + saveExtension); - file.write(inStream, false); - if(SaveIO.isSaveValid(file)){ - try{ - SaveSlot slot = control.saves.importSave(file); - ui.load.runLoadSave(slot); - }catch(IOException e){ - ui.showException("@save.import.fail", e); + + Fi file = Core.files.cache("temp-save." + saveExtension); + file.write(getContentResolver().openInputStream(uri), false); + //TODO: how to check if it's a server URL? e.g: mindustry://myserver.com or mindustry://127.0.0.1 + + //may run on a different thread + Core.app.post(() -> { + try{ + SaveMeta meta = SaveIO.getMeta(file); + if(!meta.isMap()){ //open save + SaveSlot slot = control.saves.importSave(file); + ui.load.runLoadSave(slot); + }else{ //open map + if(!ui.maps.isShown()){ + ui.maps.show(); } - }else{ - ui.showErrorMessage("@save.import.invalid"); + ui.maps.tryImportMap(file, result -> ui.maps.showMap(result)); } - }else if(map){ //open map - Fi file = Core.files.local("temp-map." + mapExtension); - file.write(inStream, false); - Core.app.post(() -> { - System.out.println("Opening map."); - if(!ui.editor.isShown()){ - ui.editor.show(); - } - ui.editor.beginEditMap(file); - }); + }catch(Throwable e){ + Log.err("Failed to load save", e); + ui.showException("@save.import.invalid", e); } - })); + }); + + //clear data (not sure if necessary?) + intent.setAction(Intent.ACTION_MAIN); + intent.setData(null); } - }catch(IOException e){ - e.printStackTrace(); + }catch(Throwable e){ + Log.err(e); } } diff --git a/core/src/mindustry/io/SaveMeta.java b/core/src/mindustry/io/SaveMeta.java index 46b94ff978..30e536d228 100644 --- a/core/src/mindustry/io/SaveMeta.java +++ b/core/src/mindustry/io/SaveMeta.java @@ -28,4 +28,8 @@ public class SaveMeta{ this.tags = tags; this.mods = JsonIO.read(String[].class, tags.get("mods", "[]")); } + + public boolean isMap(){ + return tags.containsKey("name"); + } } diff --git a/core/src/mindustry/maps/Maps.java b/core/src/mindustry/maps/Maps.java index 12859cc2c4..93b0e1fe92 100644 --- a/core/src/mindustry/maps/Maps.java +++ b/core/src/mindustry/maps/Maps.java @@ -267,8 +267,8 @@ public class Maps{ } } - /** Import a map, then save it. This updates all values and stored data necessary. */ - public void importMap(Fi file) throws IOException{ + /** Imports a map, then saves it. This updates all values and stored data necessary. */ + public Map importMap(Fi file) throws IOException{ Fi dest = findFile(file.name()); file.copyTo(dest); @@ -288,6 +288,8 @@ public class Maps{ if(error[0] != null){ throw new IOException(error[0]); } + + return map; } /** Attempts to run the following code; diff --git a/core/src/mindustry/ui/FileChooser.java b/core/src/mindustry/ui/FileChooser.java index a76fb241d7..4dd1482210 100644 --- a/core/src/mindustry/ui/FileChooser.java +++ b/core/src/mindustry/ui/FileChooser.java @@ -14,7 +14,7 @@ public class FileChooser{ public static void export(String name, String extension, FileWriter writer){ if(!ios){ - FileChooser.save(extension).name(name).submit(file -> { + FileChooser.save(extension).name(name.endsWith("." + extension) ? name : name + "." + extension).submit(file -> { ui.loadAnd(() -> { try{ writer.write(file); diff --git a/core/src/mindustry/ui/dialogs/EditorMapsDialog.java b/core/src/mindustry/ui/dialogs/EditorMapsDialog.java index 017381f263..b1ece85c3d 100644 --- a/core/src/mindustry/ui/dialogs/EditorMapsDialog.java +++ b/core/src/mindustry/ui/dialogs/EditorMapsDialog.java @@ -2,6 +2,7 @@ package mindustry.ui.dialogs; import arc.*; import arc.files.*; +import arc.func.*; import arc.graphics.*; import arc.scene.ui.*; import arc.scene.ui.layout.*; @@ -44,53 +45,60 @@ public class EditorMapsDialog extends MapListDialog{ FileChooser.open(mapExtension).submitMulti(files -> { ui.loadAnd(() -> { for(Fi file : files){ - maps.tryCatchMapError(() -> { - if(MapIO.isImage(file)){ - ui.showErrorMessage("@editor.errorimage"); - return; - } - - Map map = MapIO.createMap(file, true); - - //when you attempt to import a save, it will have no name, so generate one - String name = map.tags.get("name", () -> { - String result = "unknown"; - int number = 0; - while(maps.byName(result + number++) != null) ; - return result + number; - }); - - //this will never actually get called, but it remains just in case - if(name == null){ - ui.showErrorMessage("@editor.errorname"); - return; - } - - Map conflict = maps.all().find(m -> m.name().equalsIgnoreCase(name)); - - if(conflict != null && !conflict.custom){ - ui.showInfo(Core.bundle.format("editor.import.exists", name)); - }else if(conflict != null){ - ui.showConfirm("@confirm", Core.bundle.format("editor.overwrite.confirm", map.name()), () -> { - maps.tryCatchMapError(() -> { - maps.removeMap(conflict); - maps.importMap(map.file); - setup(); - }); - }); - }else{ - maps.importMap(map.file); - setup(); - } - }); + tryImportMap(file, null); } }); }); }).size(210f, 64f); } + public void tryImportMap(Fi file, @Nullable Cons success){ + maps.tryCatchMapError(() -> { + if(MapIO.isImage(file)){ + ui.showErrorMessage("@editor.errorimage"); + return; + } + + Map map = MapIO.createMap(file, true); + + //when you attempt to import a save, it will have no name, so generate one + String name = map.tags.get("name", () -> { + String result = "unknown"; + int number = 0; + while(maps.byName(result + number++) != null) ; + return result + number; + }); + + //this will never actually get called, but it remains just in case + if(name == null){ + ui.showErrorMessage("@editor.errorname"); + return; + } + + Map conflict = maps.all().find(m -> m.name().equalsIgnoreCase(name)); + + if(conflict != null && !conflict.custom){ + ui.showInfo(Core.bundle.format("editor.import.exists", name)); + }else if(conflict != null){ + ui.showConfirm("@confirm", Core.bundle.format("editor.overwrite.confirm", map.name()), () -> { + maps.tryCatchMapError(() -> { + maps.removeMap(conflict); + var imported = maps.importMap(map.file); + setup(); + + if(success != null) success.get(imported); + }); + }); + }else{ + var imported = maps.importMap(map.file); + setup(); + if(success != null) success.get(imported); + } + }); + } + @Override - void showMap(Map map){ + public void showMap(Map map){ BaseDialog dialog = activeDialog = new BaseDialog("@editor.mapinfo"); dialog.addCloseButton();