New Android file chooser implementation

This commit is contained in:
Anuken
2019-09-06 21:21:30 -04:00
parent b492c7ae27
commit b5f1e566b2
13 changed files with 87 additions and 142 deletions

View File

@@ -1,10 +1,12 @@
package io.anuke.mindustry; package io.anuke.mindustry;
import android.*; import android.*;
import android.app.*;
import android.content.*; import android.content.*;
import android.content.pm.*; import android.content.pm.*;
import android.net.*; import android.net.*;
import android.os.*; import android.os.*;
import android.os.Build.*;
import android.provider.Settings.*; import android.provider.Settings.*;
import android.telephony.*; import android.telephony.*;
import io.anuke.arc.*; import io.anuke.arc.*;
@@ -93,21 +95,39 @@ public class AndroidLauncher extends AndroidApplication{
} }
@Override @Override
public void showFileChooser(String text, String content, Consumer<FileHandle> cons, boolean open, Predicate<String> filetype){ public void showFileChooser(boolean open, String extension, Consumer<FileHandle> cons){
chooser = new FileChooser(text, file -> filetype.test(file.extension().toLowerCase()), open, cons); if(VERSION.SDK_INT >= 19){
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.M || (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED &&
checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)){ Intent intent = new Intent(open ? Intent.ACTION_OPEN_DOCUMENT : Intent.ACTION_CREATE_DOCUMENT);
chooser.show(); intent.addCategory(Intent.CATEGORY_OPENABLE);
chooser = null; intent.setType("*/*");
addResultListener(i -> startActivityForResult(intent, i), (code, in) -> {
if(code == Activity.RESULT_OK && in != null && in.getData() != null){
Uri uri = in.getData();
Core.app.post(() -> Core.app.post(() -> cons.accept(new FileHandle(uri.getPath()){
@Override
public InputStream read(){
try{
return getContentResolver().openInputStream(uri);
}catch(IOException e){
throw new ArcRuntimeException(e);
}
}
@Override
public OutputStream write(boolean append){
try{
return getContentResolver().openOutputStream(uri);
}catch(IOException e){
throw new ArcRuntimeException(e);
}
}
})));
}
});
}else{ }else{
ArrayList<String> perms = new ArrayList<>(); super.showFileChooser(open, extension, cons);
if(checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
perms.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
if(checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
perms.add(Manifest.permission.READ_EXTERNAL_STORAGE);
}
requestPermissions(perms.toArray(new String[0]), PERMISSION_REQUEST_CODE);
} }
} }

View File

@@ -235,6 +235,7 @@ editor.errorload = Error loading file:\n[accent]{0}
editor.errorsave = Error saving file:\n[accent]{0} editor.errorsave = Error saving file:\n[accent]{0}
editor.errorimage = That's an image, not a map. Don't go around changing extensions expecting it to work.\n\nIf you want to import a legacy map, use the 'import legacy map' button in the editor. editor.errorimage = That's an image, not a map. Don't go around changing extensions expecting it to work.\n\nIf you want to import a legacy map, use the 'import legacy map' button in the editor.
editor.errorlegacy = This map is too old, and uses a legacy map format that is no longer supported. editor.errorlegacy = This map is too old, and uses a legacy map format that is no longer supported.
editor.errornot = This is not a map file.
editor.errorheader = This map file is either not valid or corrupt. editor.errorheader = This map file is either not valid or corrupt.
editor.errorname = Map has no name defined. Are you trying to load a save file? editor.errorname = Map has no name defined. Are you trying to load a save file?
editor.update = Update editor.update = Update

View File

@@ -39,7 +39,7 @@ public class Items implements ContentList{
}}; }};
coal = new Item("coal", Color.valueOf("272727")){{ coal = new Item("coal", Color.valueOf("272727")){{
explosiveness = 0.4f; explosiveness = 0.2f;
flammability = 1f; flammability = 1f;
hardness = 2; hardness = 2;
}}; }};

View File

@@ -2,7 +2,6 @@ package io.anuke.mindustry.core;
import io.anuke.arc.*; import io.anuke.arc.*;
import io.anuke.arc.assets.*; import io.anuke.arc.assets.*;
import io.anuke.arc.files.*;
import io.anuke.arc.graphics.*; import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*; import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.input.*; import io.anuke.arc.input.*;
@@ -174,35 +173,6 @@ public class Control implements ApplicationListener, Loadable{
saves.load(); saves.load();
} }
//checks for existing 3.5 app data, android only
public void checkClassicData(){
try{
if(files.local("mindustry-maps").exists() || files.local("mindustry-saves").exists()){
settings.getBoolOnce("classic-backup-check", () -> {
app.post(() -> app.post(() -> ui.showConfirm("$classic.export", "$classic.export.text", () -> {
try{
platform.requestExternalPerms(() -> {
FileHandle external = files.external("MindustryClassic");
if(files.local("mindustry-maps").exists()){
files.local("mindustry-maps").copyTo(external);
}
if(files.local("mindustry-saves").exists()){
files.local("mindustry-saves").copyTo(external);
}
});
}catch(Exception e){
e.printStackTrace();
ui.showError(Strings.parseException(e, true));
}
})));
});
}
}catch(Throwable t){
t.printStackTrace();
}
}
void createPlayer(){ void createPlayer(){
player = new Player(); player = new Player();
player.name = Core.settings.getString("name"); player.name = Core.settings.getString("name");
@@ -379,7 +349,6 @@ public class Control implements ApplicationListener, Loadable{
if(android){ if(android){
Sounds.empty.loop(0f, 1f, 0f); Sounds.empty.loop(0f, 1f, 0f);
checkClassicData();
} }
} }

View File

@@ -1,13 +1,13 @@
package io.anuke.mindustry.core; package io.anuke.mindustry.core;
import io.anuke.arc.Core; import io.anuke.arc.*;
import io.anuke.arc.Input.TextInput; import io.anuke.arc.Input.*;
import io.anuke.arc.files.FileHandle; import io.anuke.arc.files.*;
import io.anuke.arc.function.Consumer; import io.anuke.arc.function.*;
import io.anuke.arc.function.Predicate; import io.anuke.arc.math.*;
import io.anuke.arc.math.RandomXS128; import io.anuke.arc.scene.ui.*;
import io.anuke.arc.scene.ui.TextField; import io.anuke.arc.util.serialization.*;
import io.anuke.arc.util.serialization.Base64Coder; import io.anuke.mindustry.ui.dialogs.*;
import static io.anuke.mindustry.Vars.mobile; import static io.anuke.mindustry.Vars.mobile;
@@ -70,13 +70,18 @@ public interface Platform{
/** /**
* Show a file chooser. * Show a file chooser.
* @param text File chooser title text
* @param content Description of the type of files to be loaded
* @param cons Selection listener * @param cons Selection listener
* @param open Whether to open or save files * @param open Whether to open or save files
* @param filetype File extension to filter * @param extension File extension to filter
*/ */
default void showFileChooser(String text, String content, Consumer<FileHandle> cons, boolean open, Predicate<String> filetype){ default void showFileChooser(boolean open, String extension, Consumer<FileHandle> cons){
new FileChooser(open ? "$open" : "$save", file -> file.extension().toLowerCase().equals(extension), open, file -> {
if(!open){
cons.accept(file.parent().child(file.nameWithoutExtension() + "." + extension));
}else{
cons.accept(file);
}
}).show();
} }
/** Hide the app. Android only. */ /** Hide the app. Android only. */

View File

@@ -91,20 +91,18 @@ public class MapEditorDialog extends Dialog implements Disposable{
createDialog("$editor.import", createDialog("$editor.import",
"$editor.importmap", "$editor.importmap.description", "icon-load-map", (Runnable)loadDialog::show, "$editor.importmap", "$editor.importmap.description", "icon-load-map", (Runnable)loadDialog::show,
"$editor.importfile", "$editor.importfile.description", "icon-file", (Runnable)() -> "$editor.importfile", "$editor.importfile.description", "icon-file", (Runnable)() ->
platform.showFileChooser("$editor.loadmap", "Map Files", file -> ui.loadAnd(() -> { platform.showFileChooser(true, mapExtension, file -> ui.loadAnd(() -> {
maps.tryCatchMapError(() -> { maps.tryCatchMapError(() -> {
if(MapIO.isImage(file)){ if(MapIO.isImage(file)){
ui.showInfo("$editor.errorimage"); ui.showInfo("$editor.errorimage");
}else if(file.extension().equalsIgnoreCase(oldMapExtension)){
editor.beginEdit(maps.makeLegacyMap(file));
}else{ }else{
editor.beginEdit(MapIO.createMap(file, true)); editor.beginEdit(MapIO.createMap(file, true));
} }
}); });
}), true, FileChooser.anyMapFiles), })),
"$editor.importimage", "$editor.importimage.description", "icon-file-image", (Runnable)() -> "$editor.importimage", "$editor.importimage.description", "icon-file-image", (Runnable)() ->
platform.showFileChooser("$loadimage", "Image Files", file -> platform.showFileChooser(true, "png", file ->
ui.loadAnd(() -> { ui.loadAnd(() -> {
try{ try{
Pixmap pixmap = new Pixmap(file); Pixmap pixmap = new Pixmap(file);
@@ -114,27 +112,25 @@ public class MapEditorDialog extends Dialog implements Disposable{
ui.showError(Core.bundle.format("editor.errorload", Strings.parseException(e, true))); ui.showError(Core.bundle.format("editor.errorload", Strings.parseException(e, true)));
Log.err(e); Log.err(e);
} }
}), true, FileChooser.pngFiles)) })))
); );
} }
Cell cell = t.addImageTextButton("$editor.export", "icon-save-map", isize, () -> { Cell cell = t.addImageTextButton("$editor.export", "icon-save-map", isize, () -> {
if(!ios){ if(!ios){
platform.showFileChooser("$editor.savemap", "Map Files", file -> { platform.showFileChooser(false, mapExtension, file -> {
file = file.parent().child(file.nameWithoutExtension() + "." + mapExtension);
FileHandle result = file;
ui.loadAnd(() -> { ui.loadAnd(() -> {
try{ try{
if(!editor.getTags().containsKey("name")){ if(!editor.getTags().containsKey("name")){
editor.getTags().put("name", result.nameWithoutExtension()); editor.getTags().put("name", file.nameWithoutExtension());
} }
MapIO.writeMap(result, editor.createMap(result)); MapIO.writeMap(file, editor.createMap(file));
}catch(Exception e){ }catch(Exception e){
ui.showError(Core.bundle.format("editor.errorsave", Strings.parseException(e, true))); ui.showError(Core.bundle.format("editor.errorsave", Strings.parseException(e, true)));
Log.err(e); Log.err(e);
} }
}); });
}, false, FileChooser.mapFiles); });
}else{ }else{
ui.loadAnd(() -> { ui.loadAnd(() -> {
try{ try{

View File

@@ -52,10 +52,12 @@ public class GlobalData{
} }
public void importData(FileHandle file){ public void importData(FileHandle file){
FileHandle zipped = new ZipFileHandle(file); FileHandle dest = Core.files.local("zipdata.zip");
file.copyTo(dest);
FileHandle zipped = new ZipFileHandle(dest);
FileHandle base = Core.settings.getDataDirectory(); FileHandle base = Core.settings.getDataDirectory();
if(!base.child("settings.bin").exists()){ if(!zipped.child("settings.bin").exists()){
throw new IllegalArgumentException("Not valid save data."); throw new IllegalArgumentException("Not valid save data.");
} }
@@ -63,12 +65,13 @@ public class GlobalData{
for(FileHandle f : base.list()){ for(FileHandle f : base.list()){
if(f.isDirectory()){ if(f.isDirectory()){
f.deleteDirectory(); f.deleteDirectory();
}else{ }else if(!f.name().equals("zipdata.zip")){
f.delete(); f.delete();
} }
} }
zipped.walk(f -> f.copyTo(base.child(f.path()))); zipped.walk(f -> f.copyTo(base.child(f.path())));
dest.delete();
} }
public void modified(){ public void modified(){

View File

@@ -297,9 +297,6 @@ public class Saves{
public void exportFile(FileHandle file) throws IOException{ public void exportFile(FileHandle file) throws IOException{
try{ try{
if(!file.extension().equals(saveExtension)){
file = file.parent().child(file.nameWithoutExtension() + "." + saveExtension);
}
SaveIO.fileFor(index).copyTo(file); SaveIO.fileFor(index).copyTo(file);
}catch(Exception e){ }catch(Exception e){
throw new IOException(e); throw new IOException(e);

View File

@@ -196,7 +196,7 @@ public class Maps{
Log.err(e); Log.err(e);
if("Outdated legacy map format".equals(e.getMessage())){ if("Outdated legacy map format".equals(e.getMessage())){
ui.showError("$editor.errorlegacy"); ui.showError("$editor.errornot");
}else if(e.getMessage() != null && e.getMessage().contains("Incorrect header!")){ }else if(e.getMessage() != null && e.getMessage().contains("Incorrect header!")){
ui.showError("$editor.errorheader"); ui.showError("$editor.errorheader");
}else{ }else{
@@ -290,34 +290,6 @@ public class Maps{
return str == null ? null : str.equals("[]") ? new Array<>() : Array.with(json.fromJson(SpawnGroup[].class, str)); return str == null ? null : str.equals("[]") ? new Array<>() : Array.with(json.fromJson(SpawnGroup[].class, str));
} }
public void loadLegacyMaps(){
boolean convertedAny = false;
for(FileHandle file : customMapDirectory.list()){
if(file.extension().equalsIgnoreCase(oldMapExtension)){
try{
convertedAny = true;
LegacyMapIO.convertMap(file, file.sibling(file.nameWithoutExtension() + "." + mapExtension));
//delete old, converted file; it is no longer useful
file.delete();
Log.info("Converted file {0}", file);
}catch(Exception e){
//rename the file to a 'mmap_conversion_failed' extension to keep it there just in case
//but don't delete it
file.copyTo(file.sibling(file.name() + "_conversion_failed"));
file.delete();
Log.err(e);
}
}
}
//free up any potential memory that was used up during conversion
if(convertedAny){
world.createTiles(1, 1);
//reload maps to load the converted ones
reload();
}
}
public void loadPreviews(){ public void loadPreviews(){
for(Map map : maps){ for(Map map : maps){

View File

@@ -9,7 +9,6 @@ import io.anuke.arc.scene.style.*;
import io.anuke.arc.scene.ui.*; import io.anuke.arc.scene.ui.*;
import io.anuke.arc.scene.ui.layout.*; import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.core.GameState.*; import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.game.Saves.*; import io.anuke.mindustry.game.Saves.*;
import io.anuke.mindustry.io.*; import io.anuke.mindustry.io.*;
@@ -91,17 +90,17 @@ public class LoadDialog extends FloatingDialog{
t.addImageButton("icon-save", "empty", iconsize, () -> { t.addImageButton("icon-save", "empty", iconsize, () -> {
if(!ios){ if(!ios){
platform.showFileChooser(Core.bundle.get("save.export"), "Mindustry Save", file -> { platform.showFileChooser(false, saveExtension, file -> {
try{ try{
slot.exportFile(file); slot.exportFile(file);
setup(); setup();
}catch(IOException e){ }catch(IOException e){
ui.showError(Core.bundle.format("save.export.fail", Strings.parseException(e, true))); ui.showError(Core.bundle.format("save.export.fail", Strings.parseException(e, true)));
} }
}, false, FileChooser.saveFiles); });
}else{ }else{
try{ try{
FileHandle file = Core.files.local("save-" + slot.getName() + "." + Vars.saveExtension); FileHandle file = Core.files.local("save-" + slot.getName() + "." + saveExtension);
slot.exportFile(file); slot.exportFile(file);
platform.shareFile(file); platform.shareFile(file);
}catch(Exception e){ }catch(Exception e){
@@ -172,7 +171,7 @@ public class LoadDialog extends FloatingDialog{
if(ios) return; if(ios) return;
slots.addImageTextButton("$save.import", "icon-add", iconsize, () -> { slots.addImageTextButton("$save.import", "icon-add", iconsize, () -> {
platform.showFileChooser(Core.bundle.get("save.import"), "Mindustry Save", file -> { platform.showFileChooser(true, saveExtension, file -> {
if(SaveIO.isSaveValid(file)){ if(SaveIO.isSaveValid(file)){
try{ try{
control.saves.importSave(file); control.saves.importSave(file);
@@ -184,7 +183,7 @@ public class LoadDialog extends FloatingDialog{
}else{ }else{
ui.showError("$save.import.invalid"); ui.showError("$save.import.invalid");
} }
}, true, FileChooser.saveFiles); });
}).fillX().margin(10f).minWidth(300f).height(70f).pad(4f).padRight(-4); }).fillX().margin(10f).minWidth(300f).height(70f).pad(4f).padRight(-4);
} }

View File

@@ -61,7 +61,7 @@ public class MapsDialog extends FloatingDialog{
if(!ios){ if(!ios){
buttons.addImageTextButton("$editor.importmap", "icon-load", iconsize, () -> { buttons.addImageTextButton("$editor.importmap", "icon-load", iconsize, () -> {
platform.showFileChooser("$editor.importmap", "Map File", file -> { platform.showFileChooser(true, mapExtension, file -> {
ui.loadAnd(() -> { ui.loadAnd(() -> {
maps.tryCatchMapError(() -> { maps.tryCatchMapError(() -> {
if(MapIO.isImage(file)){ if(MapIO.isImage(file)){
@@ -69,12 +69,8 @@ public class MapsDialog extends FloatingDialog{
return; return;
} }
Map map; Map map = MapIO.createMap(file, true);
if(file.extension().equalsIgnoreCase(mapExtension)){
map = MapIO.createMap(file, true);
}else{
map = maps.makeLegacyMap(file);
}
//when you attempt to import a save, it will have no name, so generate one //when you attempt to import a save, it will have no name, so generate one
String name = map.tags.getOr("name", () -> { String name = map.tags.getOr("name", () -> {
@@ -109,7 +105,7 @@ public class MapsDialog extends FloatingDialog{
}); });
}); });
}, true, FileChooser.anyMapFiles); });
}).size(210f, 64f); }).size(210f, 64f);
} }

View File

@@ -102,14 +102,6 @@ public class SettingsMenuDialog extends SettingsDialog{
t.row(); t.row();
if(android && (Core.files.local("mindustry-maps").exists() || Core.files.local("mindustry-saves").exists())){
t.addButton("$classic.export", style, () -> {
control.checkClassicData();
});
}
t.row();
t.addButton("$data.export", style, () -> { t.addButton("$data.export", style, () -> {
if(ios){ if(ios){
FileHandle file = Core.files.local("mindustry-data-export.zip"); FileHandle file = Core.files.local("mindustry-data-export.zip");
@@ -120,19 +112,15 @@ public class SettingsMenuDialog extends SettingsDialog{
} }
platform.shareFile(file); platform.shareFile(file);
}else{ }else{
platform.showFileChooser("$data.export", "Zip Files", file -> { platform.showFileChooser(false, "zip", file -> {
FileHandle ff = file;
if(!ff.extension().equals("zip")){
ff = ff.sibling(ff.nameWithoutExtension() + ".zip");
}
try{ try{
data.exportData(ff); data.exportData(file);
ui.showInfo("$data.exported"); ui.showInfo("$data.exported");
}catch(Exception e){ }catch(Exception e){
e.printStackTrace(); e.printStackTrace();
ui.showError(Strings.parseException(e, true)); ui.showError(Strings.parseException(e, true));
} }
}, false, f -> false); });
} }
}); });
@@ -140,7 +128,7 @@ public class SettingsMenuDialog extends SettingsDialog{
//iOS doesn't have a file chooser. //iOS doesn't have a file chooser.
if(!ios){ if(!ios){
t.addButton("$data.import", style, () -> ui.showConfirm("$confirm", "$data.import.confirm", () -> platform.showFileChooser("$data.import", "Zip Files", file -> { t.addButton("$data.import", style, () -> ui.showConfirm("$confirm", "$data.import.confirm", () -> platform.showFileChooser(true, "zip", file -> {
try{ try{
data.importData(file); data.importData(file);
Core.app.exit(); Core.app.exit();
@@ -148,9 +136,13 @@ public class SettingsMenuDialog extends SettingsDialog{
ui.showError("$data.invalid"); ui.showError("$data.invalid");
}catch(Exception e){ }catch(Exception e){
e.printStackTrace(); e.printStackTrace();
ui.showError(Strings.parseException(e, true)); if(e.getMessage() == null || !e.getMessage().contains("too short")){
ui.showError(Strings.parseException(e, true));
}else{
ui.showError("$data.invalid");
}
} }
}, true, f -> f.equalsIgnoreCase("zip")))); })));
} }
}); });

View File

@@ -65,11 +65,6 @@ public class DesktopPlatform extends ClientLauncher{
}); });
} }
@Override
public void showFileChooser(String text, String content, Consumer<FileHandle> cons, boolean open, Predicate<String> filetype){
new FileChooser(text, file -> filetype.test(file.extension().toLowerCase()), open, cons).show();
}
@Override @Override
public void updateRPC(){ public void updateRPC(){
if(!useDiscord) return; if(!useDiscord) return;