Android file chooser fixes & support for save loading from file browser

This commit is contained in:
Anuken
2026-07-15 14:21:38 -04:00
parent cef447356b
commit 91c3e50771
6 changed files with 117 additions and 85 deletions
+20
View File
@@ -25,6 +25,7 @@
android:label="@string/app_name" android:label="@string/app_name"
android:screenOrientation="user" android:screenOrientation="user"
android:exported="true" android:exported="true"
android:launchMode="singleTask"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout"> android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout">
<intent-filter> <intent-filter>
@@ -39,6 +40,25 @@
<data android:scheme="mindustry"/> <data android:scheme="mindustry"/>
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="content" />
<data android:mimeType="application/octet-stream" />
<data android:mimeType="application/x-msav" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" />
<data android:host="*" />
<data android:mimeType="*/*" />
<data android:pathPattern=".*\\.msav" />
</intent-filter>
</activity> </activity>
</application> </application>
@@ -16,6 +16,7 @@ import arc.struct.*;
import arc.util.*; import arc.util.*;
import dalvik.system.*; import dalvik.system.*;
import mindustry.*; import mindustry.*;
import mindustry.game.EventType.*;
import mindustry.game.Saves.*; import mindustry.game.Saves.*;
import mindustry.io.*; import mindustry.io.*;
import mindustry.net.*; import mindustry.net.*;
@@ -222,7 +223,9 @@ public class AndroidLauncher extends AndroidApplication{
hideStatusBar = true; hideStatusBar = true;
useGL30 = true; useGL30 = true;
}}); }});
checkFiles(getIntent());
var intent = getIntent();
Events.on(ClientLoadEvent.class, u -> handleFiles(intent));
try{ try{
//new external folder //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{ try{
Uri uri = intent.getData(); Uri uri = intent.getData();
if(uri != null){ if(uri != null){
File myFile = null;
String scheme = uri.getScheme(); Fi file = Core.files.cache("temp-save." + saveExtension);
if(scheme.equals("file")){ file.write(getContentResolver().openInputStream(uri), false);
String fileName = uri.getEncodedPath(); //TODO: how to check if it's a server URL? e.g: mindustry://myserver.com or mindustry://127.0.0.1
myFile = new File(fileName);
}else if(!scheme.equals("content")){ //may run on a different thread
//error Core.app.post(() -> {
return; try{
} SaveMeta meta = SaveIO.getMeta(file);
boolean save = uri.getPath().endsWith(saveExtension); if(!meta.isMap()){ //open save
boolean map = uri.getPath().endsWith(mapExtension); SaveSlot slot = control.saves.importSave(file);
InputStream inStream; ui.load.runLoadSave(slot);
if(myFile != null) inStream = new FileInputStream(myFile); }else{ //open map
else inStream = getContentResolver().openInputStream(uri); if(!ui.maps.isShown()){
Core.app.post(() -> Core.app.post(() -> { ui.maps.show();
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);
} }
}else{ ui.maps.tryImportMap(file, result -> ui.maps.showMap(result));
ui.showErrorMessage("@save.import.invalid");
} }
}else if(map){ //open map }catch(Throwable e){
Fi file = Core.files.local("temp-map." + mapExtension); Log.err("Failed to load save", e);
file.write(inStream, false); ui.showException("@save.import.invalid", e);
Core.app.post(() -> {
System.out.println("Opening map.");
if(!ui.editor.isShown()){
ui.editor.show();
}
ui.editor.beginEditMap(file);
});
} }
})); });
//clear data (not sure if necessary?)
intent.setAction(Intent.ACTION_MAIN);
intent.setData(null);
} }
}catch(IOException e){ }catch(Throwable e){
e.printStackTrace(); Log.err(e);
} }
} }
+4
View File
@@ -28,4 +28,8 @@ public class SaveMeta{
this.tags = tags; this.tags = tags;
this.mods = JsonIO.read(String[].class, tags.get("mods", "[]")); this.mods = JsonIO.read(String[].class, tags.get("mods", "[]"));
} }
public boolean isMap(){
return tags.containsKey("name");
}
} }
+4 -2
View File
@@ -267,8 +267,8 @@ public class Maps{
} }
} }
/** Import a map, then save it. This updates all values and stored data necessary. */ /** Imports a map, then saves it. This updates all values and stored data necessary. */
public void importMap(Fi file) throws IOException{ public Map importMap(Fi file) throws IOException{
Fi dest = findFile(file.name()); Fi dest = findFile(file.name());
file.copyTo(dest); file.copyTo(dest);
@@ -288,6 +288,8 @@ public class Maps{
if(error[0] != null){ if(error[0] != null){
throw new IOException(error[0]); throw new IOException(error[0]);
} }
return map;
} }
/** Attempts to run the following code; /** Attempts to run the following code;
+1 -1
View File
@@ -14,7 +14,7 @@ public class FileChooser{
public static void export(String name, String extension, FileWriter writer){ public static void export(String name, String extension, FileWriter writer){
if(!ios){ if(!ios){
FileChooser.save(extension).name(name).submit(file -> { FileChooser.save(extension).name(name.endsWith("." + extension) ? name : name + "." + extension).submit(file -> {
ui.loadAnd(() -> { ui.loadAnd(() -> {
try{ try{
writer.write(file); writer.write(file);
@@ -2,6 +2,7 @@ package mindustry.ui.dialogs;
import arc.*; import arc.*;
import arc.files.*; import arc.files.*;
import arc.func.*;
import arc.graphics.*; import arc.graphics.*;
import arc.scene.ui.*; import arc.scene.ui.*;
import arc.scene.ui.layout.*; import arc.scene.ui.layout.*;
@@ -44,53 +45,60 @@ public class EditorMapsDialog extends MapListDialog{
FileChooser.open(mapExtension).submitMulti(files -> { FileChooser.open(mapExtension).submitMulti(files -> {
ui.loadAnd(() -> { ui.loadAnd(() -> {
for(Fi file : files){ for(Fi file : files){
maps.tryCatchMapError(() -> { tryImportMap(file, null);
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();
}
});
} }
}); });
}); });
}).size(210f, 64f); }).size(210f, 64f);
} }
public void tryImportMap(Fi file, @Nullable Cons<Map> 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 @Override
void showMap(Map map){ public void showMap(Map map){
BaseDialog dialog = activeDialog = new BaseDialog("@editor.mapinfo"); BaseDialog dialog = activeDialog = new BaseDialog("@editor.mapinfo");
dialog.addCloseButton(); dialog.addCloseButton();