Merge remote-tracking branch 'origin/V0.8.4' into V0.8.4

This commit is contained in:
Nut.andor
2023-01-21 13:32:42 +01:00
4 changed files with 562 additions and 297 deletions

View File

@@ -11,9 +11,7 @@ import java.util.List;
import java.util.Objects; import java.util.Objects;
import android.Manifest; import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity; import android.app.Activity;
import android.app.Dialog;
import android.content.ClipData; import android.content.ClipData;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
@@ -122,8 +120,7 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
boolean hasSavegames = !Savegames.getUsedSavegameSlots(this).isEmpty(); boolean hasSavegames = !Savegames.getUsedSavegameSlots(this).isEmpty();
exportSaves.setEnabled(hasSavegames); exportSaves.setEnabled(hasSavegames);
} } else {
else{
exportImportContainer.setVisibility(View.GONE); exportImportContainer.setVisibility(View.GONE);
} }
} }
@@ -133,12 +130,17 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
private static final int WRITE_EXTERNAL_STORAGE_REQUEST = 2; private static final int WRITE_EXTERNAL_STORAGE_REQUEST = 2;
private void checkAndRequestPermissions() { private void checkAndRequestPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
if (getApplicationContext().checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { && Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
this.requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, READ_EXTERNAL_STORAGE_REQUEST); if (getApplicationContext().checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
this.requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
READ_EXTERNAL_STORAGE_REQUEST);
} }
if (getApplicationContext().checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { if (getApplicationContext().checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
this.requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, WRITE_EXTERNAL_STORAGE_REQUEST); != PackageManager.PERMISSION_GRANTED) {
this.requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
WRITE_EXTERNAL_STORAGE_REQUEST);
} }
} }
} }
@@ -152,11 +154,15 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
} }
} }
private void addSavegameSlotButtons(ViewGroup parent, LayoutParams params, List<Integer> usedSavegameSlots) { private void addSavegameSlotButtons(ViewGroup parent,
LayoutParams params,
List<Integer> usedSavegameSlots) {
int unused = 1; int unused = 1;
for (int slot : usedSavegameSlots) { for (int slot : usedSavegameSlots) {
final FileHeader header = Savegames.quickload(this, slot); final FileHeader header = Savegames.quickload(this, slot);
if (header == null) continue; if (header == null) {
continue;
}
while (unused < slot) { while (unused < slot) {
Button b = new Button(this); Button b = new Button(this);
@@ -180,65 +186,76 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
} }
} }
private void cancelLoadSaveActivity(int slot){ private void cancelLoadSaveActivity(int slot) {
completeLoadSaveActivity(slot, false); completeLoadSaveActivity(slot, false);
} }
private void completeLoadSaveActivity(int slot) { private void completeLoadSaveActivity(int slot) {
completeLoadSaveActivity(slot, true); completeLoadSaveActivity(slot, true);
} }
private void completeLoadSaveActivity(int slot, boolean success) { private void completeLoadSaveActivity(int slot, boolean success) {
Intent i = new Intent(); Intent i = new Intent();
if (slot == SLOT_NUMBER_CREATE_NEW_SLOT) { if (slot == SLOT_NUMBER_CREATE_NEW_SLOT) {
slot = getFirstFreeSlot(); slot = getFirstFreeSlot();
} else if (slot == SLOT_NUMBER_EXPORT_SAVEGAMES } else if (slot == SLOT_NUMBER_EXPORT_SAVEGAMES
|| slot == SLOT_NUMBER_IMPORT_SAVEGAMES || slot == SLOT_NUMBER_IMPORT_SAVEGAMES
|| slot == SLOT_NUMBER_IMPORT_WORLDMAP) { || slot == SLOT_NUMBER_IMPORT_WORLDMAP) {
i.putExtra("import_export", true); i.putExtra("import_export", true);
if(slot == SLOT_NUMBER_IMPORT_WORLDMAP){ if (slot == SLOT_NUMBER_IMPORT_WORLDMAP) {
i.putExtra("import_worldmap", true); i.putExtra("import_worldmap", true);
} }
if(slot == SLOT_NUMBER_IMPORT_SAVEGAMES){ if (slot == SLOT_NUMBER_IMPORT_SAVEGAMES) {
i.putExtra("import_savegames", true); i.putExtra("import_savegames", true);
} }
if(slot == SLOT_NUMBER_EXPORT_SAVEGAMES){ if (slot == SLOT_NUMBER_EXPORT_SAVEGAMES) {
i.putExtra("export", true); i.putExtra("export", true);
} }
} else if (slot < SLOT_NUMBER_FIRST_SLOT) } else if (slot < SLOT_NUMBER_FIRST_SLOT) {
slot = SLOT_NUMBER_FIRST_SLOT; slot = SLOT_NUMBER_FIRST_SLOT;
}
i.putExtra("slot", slot); i.putExtra("slot", slot);
if(success) setResult(Activity.RESULT_OK, i); if (success) {
else setResult(Activity.RESULT_CANCELED, i); setResult(Activity.RESULT_OK, i);
} else {
setResult(Activity.RESULT_CANCELED, i);
}
LoadSaveActivity.this.finish(); LoadSaveActivity.this.finish();
} }
private int getFirstFreeSlot() { private int getFirstFreeSlot() {
int slot; int slot;
List<Integer> usedSlots = Savegames.getUsedSavegameSlots(this); List<Integer> usedSlots = Savegames.getUsedSavegameSlots(this);
if (usedSlots.isEmpty()) if (usedSlots.isEmpty()) {
slot = SLOT_NUMBER_FIRST_SLOT; slot = SLOT_NUMBER_FIRST_SLOT;
else slot = Collections.max(usedSlots) + 1; } else {
slot = Collections.max(usedSlots) + 1;
}
return slot; return slot;
} }
private String getConfirmOverwriteQuestion(int slot) { private String getConfirmOverwriteQuestion(int slot) {
if (isLoading) if (isLoading) {
return null; return null;
}
return getConfirmOverwriteQuestionIgnoringLoading(slot); return getConfirmOverwriteQuestionIgnoringLoading(slot);
} }
private String getConfirmOverwriteQuestionIgnoringLoading(int slot) { private String getConfirmOverwriteQuestionIgnoringLoading(int slot) {
if (slot == SLOT_NUMBER_CREATE_NEW_SLOT) if (slot == SLOT_NUMBER_CREATE_NEW_SLOT) {
return null;//creating a new savegame return null;//creating a new savegame
}
if (!Savegames.getSlotFile(slot, this).exists()) if (!Savegames.getSlotFile(slot, this).exists()) {
return null;//nothing in slot to overwrite return null;//nothing in slot to overwrite
}
if (preferences.displayOverwriteSavegame == AndorsTrailPreferences.CONFIRM_OVERWRITE_SAVEGAME_ALWAYS) { if (preferences.displayOverwriteSavegame
== AndorsTrailPreferences.CONFIRM_OVERWRITE_SAVEGAME_ALWAYS) {
return getString(R.string.loadsave_save_overwrite_confirmation_all); return getString(R.string.loadsave_save_overwrite_confirmation_all);
} }
if (preferences.displayOverwriteSavegame == AndorsTrailPreferences.CONFIRM_OVERWRITE_SAVEGAME_NEVER) { if (preferences.displayOverwriteSavegame == AndorsTrailPreferences.CONFIRM_OVERWRITE_SAVEGAME_NEVER) {
@@ -247,10 +264,14 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
final String currentPlayerName = model.player.getName(); final String currentPlayerName = model.player.getName();
final FileHeader header = Savegames.quickload(this, slot); final FileHeader header = Savegames.quickload(this, slot);
if (header == null) return null; if (header == null) {
return null;
}
final String savedPlayerName = header.playerName; final String savedPlayerName = header.playerName;
if (currentPlayerName.equals(savedPlayerName)) return null; //if the names match if (currentPlayerName.equals(savedPlayerName)) {
return null; //if the names match
}
return getString(R.string.loadsave_save_overwrite_confirmation, savedPlayerName, currentPlayerName); return getString(R.string.loadsave_save_overwrite_confirmation, savedPlayerName, currentPlayerName);
} }
@@ -259,7 +280,7 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
public void onClick(View view) { public void onClick(View view) {
final int slot = (Integer) view.getTag(); final int slot = (Integer) view.getTag();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
switch (slot) { switch (slot) {
case SLOT_NUMBER_IMPORT_WORLDMAP: case SLOT_NUMBER_IMPORT_WORLDMAP:
clickImportWorldmap(); clickImportWorldmap();
@@ -273,8 +294,9 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
} }
} }
if (!isLoading if (!isLoading
&& slot != SLOT_NUMBER_CREATE_NEW_SLOT && slot != SLOT_NUMBER_CREATE_NEW_SLOT
&& AndorsTrailApplication.CURRENT_VERSION == AndorsTrailApplication.DEVELOPMENT_INCOMPATIBLE_SAVEGAME_VERSION) { && AndorsTrailApplication.CURRENT_VERSION
== AndorsTrailApplication.DEVELOPMENT_INCOMPATIBLE_SAVEGAME_VERSION) {
if (!isOverwriteTargetInIncompatibleVersion(slot)) { if (!isOverwriteTargetInIncompatibleVersion(slot)) {
saveOrOverwriteSavegame(slot); saveOrOverwriteSavegame(slot);
} }
@@ -288,7 +310,7 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
private void saveOrOverwriteSavegame(int slot) { private void saveOrOverwriteSavegame(int slot) {
final String message = getConfirmOverwriteQuestion(slot); final String message = getConfirmOverwriteQuestion(slot);
if (message != null) { if (message != null) {
showConfirmoverwriteQuestion(slot, message); showConfirmOverwriteQuestion(slot, message);
} else { } else {
completeLoadSaveActivity(slot); completeLoadSaveActivity(slot);
} }
@@ -296,8 +318,11 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
private boolean isOverwriteTargetInIncompatibleVersion(int slot) { private boolean isOverwriteTargetInIncompatibleVersion(int slot) {
final FileHeader header = Savegames.quickload(this, slot); final FileHeader header = Savegames.quickload(this, slot);
if (header != null && header.fileversion != AndorsTrailApplication.DEVELOPMENT_INCOMPATIBLE_SAVEGAME_VERSION) { if (header != null
final CustomDialog d = CustomDialogFactory.createErrorDialog(this, "Overwriting not allowed", "You are currently using a development version of Andor's trail. Overwriting a regular savegame is not allowed in development mode."); && header.fileversion != AndorsTrailApplication.DEVELOPMENT_INCOMPATIBLE_SAVEGAME_VERSION) {
final CustomDialog d = CustomDialogFactory.createErrorDialog(this,
"Overwriting not allowed",
"You are currently using a development version of Andor's trail. Overwriting a regular savegame is not allowed in development mode.");
CustomDialogFactory.show(d); CustomDialogFactory.show(d);
return true; return true;
} }
@@ -306,28 +331,32 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
//region Imports/Exports //region Imports/Exports
//region Export
@RequiresApi(api = Build.VERSION_CODES.P) @RequiresApi(api = Build.VERSION_CODES.P)
private void exportSaveGames(Intent data) { private void exportSaveGames(Intent data) {
Uri uri = data.getData(); Uri uri = data.getData();
Context context = getApplicationContext(); Context context = getApplicationContext();
ContentResolver resolver = AndorsTrailApplication.getApplicationFromActivity(this).getContentResolver(); ContentResolver resolver = AndorsTrailApplication.getApplicationFromActivity(this)
.getContentResolver();
File storageDir = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY); File storageDir = AndroidStorage.getStorageDirectory(context,
DocumentFile source = DocumentFile.fromFile(storageDir); Constants.FILENAME_SAVEGAME_DIRECTORY);
DocumentFile target = DocumentFile.fromTreeUri(context, uri); DocumentFile target = DocumentFile.fromTreeUri(context, uri);
if (target == null) { if (target == null) {
return; return;
} }
DocumentFile[] files = source.listFiles(); File[] files = storageDir.listFiles();
if (files == null) {
showErrorExportingSaveGamesUnknown();
return;
}
boolean hasExistingFiles = false; boolean hasExistingFiles = false;
for (DocumentFile file : for (File file : files) {
files) {
String fileName = file.getName(); String fileName = file.getName();
if (fileName == null)
continue;
DocumentFile existingFile = target.findFile(fileName); DocumentFile existingFile = target.findFile(fileName);
if (existingFile != null) { if (existingFile != null) {
@@ -339,45 +368,70 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
if (hasExistingFiles) { if (hasExistingFiles) {
showConfirmOverwriteByExportQuestion(resolver, target, files); showConfirmOverwriteByExportQuestion(resolver, target, files);
} else { } else {
exportSaveGamesFolderContentToFolder(resolver, target, files); exportSaveGamesFolderContentToFolder(target, files);
} }
} }
@RequiresApi(api = Build.VERSION_CODES.P) @RequiresApi(api = Build.VERSION_CODES.P)
private void exportSaveGamesFolderContentToFolder(ContentResolver resolver, DocumentFile target, DocumentFile[] files) { private void exportSaveGamesFolderContentToFolder(DocumentFile target, File[] files) {
DocumentFile[] sourceFiles = new DocumentFile[files.length]; DocumentFile[] sourceFiles = new DocumentFile[files.length];
DocumentFile[] worldmapFiles = null; File[] worldmapFiles = null;
for (int i = 0; i < files.length; i++) { for (int i = 0; i < files.length; i++) {
DocumentFile file = files[i]; File file = files[i];
if (file.isFile()) { if (file.isFile()) {
sourceFiles[i] = file; sourceFiles[i] = DocumentFile.fromFile(file);
} else if (file.isDirectory() && Objects.equals(file.getName(), Constants.FILENAME_WORLDMAP_DIRECTORY)) { } else if (file.isDirectory() && Objects.equals(file.getName(),
Constants.FILENAME_WORLDMAP_DIRECTORY)) {
worldmapFiles = file.listFiles(); worldmapFiles = file.listFiles();
} }
} }
Context context =this; Context context = this;
DocumentFile[] finalWorldmapFiles = worldmapFiles; File[] finalWorldmapFiles = worldmapFiles;
AndroidStorage.copyDocumentFilesToDirAsync(sourceFiles, CopyFilesToExternalFolder(target, sourceFiles, context, finalWorldmapFiles);
context,
target,
(sucess) -> {
if (sucess) {
DocumentFile worldmapFolder = target.createDirectory(Constants.FILENAME_WORLDMAP_DIRECTORY);
AndroidStorage.copyDocumentFilesToDirAsync(finalWorldmapFiles,
context,
worldmapFolder,
(sucessWorldmap) -> completeLoadSaveActivity(SLOT_NUMBER_EXPORT_SAVEGAMES, sucessWorldmap));
} else {
completeLoadSaveActivity(SLOT_NUMBER_EXPORT_SAVEGAMES, false);
}
});
} }
@RequiresApi(api = Build.VERSION_CODES.P)
private void CopyFilesToExternalFolder(DocumentFile target,
DocumentFile[] sourceFiles,
Context context,
File[] finalWorldmapFiles) {
AndroidStorage.copyDocumentFilesToDirAsync(sourceFiles,
context,
target,
getString(R.string.loadsave_exporting_savegames),
(success) -> {
if (success) {
CopyWorldmapFilesAsZip(target,
context,
finalWorldmapFiles);
} else {
completeLoadSaveActivity(
SLOT_NUMBER_EXPORT_SAVEGAMES,
false);
}
});
}
@RequiresApi(api = Build.VERSION_CODES.N) @RequiresApi(api = Build.VERSION_CODES.P)
private void CopyWorldmapFilesAsZip(DocumentFile target,
Context context,
File[] finalWorldmapFiles) {
AndroidStorage.createZipDocumentFileFromFilesAsync(finalWorldmapFiles,
context,
target,
Constants.FILENAME_WORLDMAP_DIRECTORY,
getString(R.string.loadsave_exporting_worldmap),
(successWorldmap) -> completeLoadSaveActivity(
SLOT_NUMBER_EXPORT_SAVEGAMES,
successWorldmap));
}
//endregion
@RequiresApi(api = Build.VERSION_CODES.P)
private void importSaveGames(Intent data) { private void importSaveGames(Intent data) {
Uri uri = data.getData(); Uri uri = data.getData();
ClipData uris = data.getClipData(); ClipData uris = data.getClipData();
@@ -388,7 +442,8 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
} }
Context context = getApplicationContext(); Context context = getApplicationContext();
ContentResolver resolver = context.getContentResolver(); ContentResolver resolver = AndorsTrailApplication.getApplicationFromActivity(this)
.getContentResolver();
File storageDir = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY); File storageDir = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY);
DocumentFile appSavegameFolder = DocumentFile.fromFile(storageDir); DocumentFile appSavegameFolder = DocumentFile.fromFile(storageDir);
@@ -397,14 +452,18 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
if (uri != null) { if (uri != null) {
uriList.add(uri); uriList.add(uri);
} else { } else {
for (int i = 0; i < uris.getItemCount(); i++) for (int i = 0; i < uris.getItemCount(); i++) {
uriList.add(uris.getItemAt(i).getUri()); uriList.add(uris.getItemAt(i).getUri());
}
} }
importSaveGamesFromUris(context, resolver, appSavegameFolder, uriList); importSaveGamesFromUris(context, resolver, appSavegameFolder, uriList);
} }
@RequiresApi(api = Build.VERSION_CODES.N) @RequiresApi(api = Build.VERSION_CODES.P)
private void importSaveGamesFromUris(Context context, ContentResolver resolver, DocumentFile appSavegameFolder, List<Uri> uriList) { private void importSaveGamesFromUris(Context context,
ContentResolver resolver,
DocumentFile appSavegameFolder,
List<Uri> uriList) {
int count = uriList.size(); int count = uriList.size();
ArrayList<DocumentFile> alreadyExistingFiles = new ArrayList<>(); ArrayList<DocumentFile> alreadyExistingFiles = new ArrayList<>();
@@ -414,10 +473,11 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
Uri item = uriList.get(i); Uri item = uriList.get(i);
DocumentFile itemFile = DocumentFile.fromSingleUri(context, item); DocumentFile itemFile = DocumentFile.fromSingleUri(context, item);
boolean fileAlreadyExists = getExistsSavegameInOwnFiles(itemFile, appSavegameFolder); boolean fileAlreadyExists = getExistsSavegameInOwnFiles(itemFile, appSavegameFolder);
if (fileAlreadyExists) if (fileAlreadyExists) {
alreadyExistingFiles.add(itemFile); alreadyExistingFiles.add(itemFile);
else } else {
newFiles.add(itemFile); newFiles.add(itemFile);
}
} }
if (alreadyExistingFiles.size() > 0) { if (alreadyExistingFiles.size() > 0) {
@@ -427,7 +487,10 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
} }
} }
private void importSaveGames(ContentResolver resolver, DocumentFile appSavegameFolder, List<DocumentFile> saveFiles) { @RequiresApi(api = Build.VERSION_CODES.P)
private void importSaveGames(ContentResolver resolver,
DocumentFile appSavegameFolder,
List<DocumentFile> saveFiles) {
int size = saveFiles.size(); int size = saveFiles.size();
DocumentFile[] sources = new DocumentFile[size]; DocumentFile[] sources = new DocumentFile[size];
DocumentFile[] targets = new DocumentFile[size]; DocumentFile[] targets = new DocumentFile[size];
@@ -457,21 +520,18 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
} }
AndroidStorage.copyDocumentFilesFromToAsync(sources, AndroidStorage.copyDocumentFilesFromToAsync(sources,
this, this,
targets, targets,
(sucess) -> completeLoadSaveActivity(SLOT_NUMBER_IMPORT_SAVEGAMES, sucess)); getString(R.string.loadsave_importing_savegames),
} (sucess) -> completeLoadSaveActivity(
SLOT_NUMBER_IMPORT_SAVEGAMES,
private void completeSavegameImportAndCheckIfDone(List<Integer> importsNeedingConfirmation, int slot) { sucess));
importsNeedingConfirmation.remove((Object) slot);
if (importsNeedingConfirmation.isEmpty()) {
completeLoadSaveActivity(SLOT_NUMBER_IMPORT_SAVEGAMES);
}
} }
private boolean getExistsSavegameInOwnFiles(DocumentFile savegameFile, DocumentFile appSavegameFolder) { private boolean getExistsSavegameInOwnFiles(DocumentFile savegameFile, DocumentFile appSavegameFolder) {
if (savegameFile == null) if (savegameFile == null) {
return false; return false;
}
DocumentFile foundFile = appSavegameFolder.findFile(Objects.requireNonNull(savegameFile.getName())); DocumentFile foundFile = appSavegameFolder.findFile(Objects.requireNonNull(savegameFile.getName()));
return foundFile != null && foundFile.exists(); return foundFile != null && foundFile.exists();
@@ -479,7 +539,6 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
private int getSlotFromSavegameFileName(String fileName) { private int getSlotFromSavegameFileName(String fileName) {
if (fileName == null || !fileName.startsWith(Constants.FILENAME_SAVEGAME_FILENAME_PREFIX)) { if (fileName == null || !fileName.startsWith(Constants.FILENAME_SAVEGAME_FILENAME_PREFIX)) {
//TODO: Maybe output a message that the file didn't have the right name?
return -1; return -1;
} }
String slotStr = fileName.substring(Constants.FILENAME_SAVEGAME_FILENAME_PREFIX.length()); String slotStr = fileName.substring(Constants.FILENAME_SAVEGAME_FILENAME_PREFIX.length());
@@ -489,32 +548,16 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
slot = Integer.parseInt(slotStr); slot = Integer.parseInt(slotStr);
return slot; return slot;
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
//TODO: Maybe output a message that the file didn't have the right name?
return -1; return -1;
} }
} }
private void importSaveGameFile(ContentResolver resolver, DocumentFile appSavegameFolder, DocumentFile itemFile, int slot) {
String targetName = Savegames.getSlotFileName(slot);
DocumentFile targetFile = getOrCreateDocumentFile(appSavegameFolder, targetName);
if (targetFile == null || !targetName.equals(targetFile.getName())) {
showErrorImportingSaveGameUnknown();//TODO: maybe replace with a more specific error message
return;
}
try {
AndroidStorage.copyDocumentFile(itemFile, resolver, targetFile);
} catch (IOException e) {
showErrorImportingSaveGameUnknown();
e.printStackTrace();
}
}
private DocumentFile getOrCreateDocumentFile(DocumentFile folder, String targetName) { private DocumentFile getOrCreateDocumentFile(DocumentFile folder, String targetName) {
DocumentFile targetFile = folder.findFile(targetName);//try finding the file DocumentFile targetFile = folder.findFile(targetName);//try finding the file
if (targetFile == null)//no file found, creating new one if (targetFile == null)//no file found, creating new one
{
targetFile = folder.createFile(Constants.NO_FILE_EXTENSION_MIME_TYPE, targetName); targetFile = folder.createFile(Constants.NO_FILE_EXTENSION_MIME_TYPE, targetName);
}
return targetFile; return targetFile;
} }
@@ -522,72 +565,87 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
private void importWorldmap(Intent data) { private void importWorldmap(Intent data) {
Uri uri = data.getData(); Uri uri = data.getData();
Context context = getApplicationContext(); Context context = AndorsTrailApplication.getApplicationFromActivity(this).getApplicationContext();
ContentResolver resolver = AndorsTrailApplication.getApplicationFromActivity(this).getContentResolver();
File storageDir = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY); DocumentFile chosenZip = DocumentFile.fromSingleUri(context, uri);
DocumentFile storageFolder = DocumentFile.fromFile(storageDir); if (chosenZip == null || !chosenZip.isFile()) {
DocumentFile ownWorldmapFolder = storageFolder.findFile(Constants.FILENAME_WORLDMAP_DIRECTORY);
if (ownWorldmapFolder == null) {
ownWorldmapFolder = storageFolder.createDirectory(Constants.FILENAME_WORLDMAP_DIRECTORY);
}
DocumentFile chosenFolder = DocumentFile.fromTreeUri(context, uri);
if (chosenFolder == null || !chosenFolder.isDirectory()) {
showErrorImportingWorldmapWrongDirectory(); showErrorImportingWorldmapWrongDirectory();
return; return;
} }
if (!Constants.FILENAME_WORLDMAP_DIRECTORY.equals(chosenFolder.getName())) { String chosenZipName = chosenZip.getName();
//user did not select the worldmap folder directly if (!chosenZipName.startsWith(Constants.FILENAME_WORLDMAP_DIRECTORY)) {
DocumentFile file = chosenFolder.findFile(Constants.FILENAME_WORLDMAP_DIRECTORY); showErrorImportingWorldmapWrongDirectory();
if (file == null || !file.isDirectory() || !Constants.FILENAME_WORLDMAP_DIRECTORY.equals(file.getName())) { return;
//could not find a worldmap folder in the users selection
showErrorImportingWorldmapWrongDirectory();
return;
}
chosenFolder = file;
} }
AndroidStorage.copyDocumentFilesToDirAsync(chosenFolder.listFiles(), File ownWorldmapFolder = getOwnWorldmapFolder(context);
this,
ownWorldmapFolder,
(success) -> completeLoadSaveActivity(SLOT_NUMBER_IMPORT_WORLDMAP, success)); AndroidStorage.unzipDocumentFileToDirectoryAsync(chosenZip,
this,
ownWorldmapFolder,
false,
getString(R.string.loadsave_importing_worldmap),
(success) -> completeLoadSaveActivity(
SLOT_NUMBER_IMPORT_WORLDMAP,
success));
} }
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private File getOwnWorldmapFolder(Context context) {
File storageDir = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY);
File ownWorldmapFolder = null;
for (File f : storageDir.listFiles()) {
if (f.getName().equals(Constants.FILENAME_WORLDMAP_DIRECTORY)) {
ownWorldmapFolder = f;
break;
}
}
if (ownWorldmapFolder == null) {
ownWorldmapFolder = new File(storageDir, Constants.FILENAME_WORLDMAP_DIRECTORY);
ownWorldmapFolder.mkdir();
}
return ownWorldmapFolder;
}
@RequiresApi(api = Build.VERSION_CODES.P)
private void clickExportSaveGames() { private void clickExportSaveGames() {
startActivityForResult(AndroidStorage.getNewOpenDirectoryIntent(), -SLOT_NUMBER_EXPORT_SAVEGAMES); showStartExportInfo(view -> startActivityForResult(AndroidStorage.getNewOpenDirectoryIntent(),
-SLOT_NUMBER_EXPORT_SAVEGAMES));
} }
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @RequiresApi(api = Build.VERSION_CODES.P)
private void clickImportSaveGames() { private void clickImportSaveGames() {
startActivityForResult(AndroidStorage.getNewSelectMultipleSavegameFilesIntent(), -SLOT_NUMBER_IMPORT_SAVEGAMES); showStartImportSavesInfo(view -> startActivityForResult(AndroidStorage.getNewSelectMultipleSavegameFilesIntent(),
-SLOT_NUMBER_IMPORT_SAVEGAMES));
} }
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @RequiresApi(api = Build.VERSION_CODES.P)
private void clickImportWorldmap() { private void clickImportWorldmap() {
startActivityForResult(AndroidStorage.getNewOpenDirectoryIntent(), -SLOT_NUMBER_IMPORT_WORLDMAP); showStartImportWorldmapInfo(view -> startActivityForResult(AndroidStorage.getNewSelectZipIntent(),
-SLOT_NUMBER_IMPORT_WORLDMAP));
} }
@RequiresApi(api = Build.VERSION_CODES.P) @RequiresApi(api = Build.VERSION_CODES.P)
private void showConfirmOverwriteByExportQuestion(ContentResolver resolver, DocumentFile targetFolder, DocumentFile[] files) { private void showConfirmOverwriteByExportQuestion(ContentResolver resolver,
DocumentFile targetFolder,
File[] files) {
final CustomDialog d = CustomDialogFactory.createDialog(this, final CustomDialog d = CustomDialogFactory.createDialog(this,
getString(R.string.loadsave_export_overwrite_confirmation_title), getString(R.string.loadsave_export_overwrite_confirmation_title),
getResources().getDrawable(android.R.drawable.ic_dialog_alert), getResources().getDrawable(android.R.drawable.ic_dialog_alert),
getString(R.string.loadsave_export_overwrite_confirmation), getString(R.string.loadsave_export_overwrite_confirmation),
null, null,
true); true);
CustomDialogFactory.addButton(d, android.R.string.yes, v -> exportSaveGamesFolderContentToFolder(resolver, targetFolder, files)); CustomDialogFactory.addButton(d,
android.R.string.yes,
v -> exportSaveGamesFolderContentToFolder(targetFolder, files));
CustomDialogFactory.addDismissButton(d, android.R.string.no); CustomDialogFactory.addDismissButton(d, android.R.string.no);
CustomDialogFactory.show(d); CustomDialogFactory.show(d);
} }
@RequiresApi(api = Build.VERSION_CODES.N) @RequiresApi(api = Build.VERSION_CODES.P)
private void showConfirmOverwriteByImportQuestion(ContentResolver resolver, private void showConfirmOverwriteByImportQuestion(ContentResolver resolver,
DocumentFile appSavegameFolder, DocumentFile appSavegameFolder,
List<DocumentFile> alreadyExistingFiles, List<DocumentFile> alreadyExistingFiles,
@@ -601,9 +659,9 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
Context context = AndorsTrailApplication.getApplicationFromActivity(this).getApplicationContext(); Context context = AndorsTrailApplication.getApplicationFromActivity(this).getApplicationContext();
ArrayList<CustomDialog> dialogs = new ArrayList<CustomDialog>(amount) ; ArrayList<CustomDialog> dialogs = new ArrayList<>(amount);
for (int i = 0; i < amount ; i++) { for (int i = 0; i < amount; i++) {
DocumentFile alreadyExistingFile = alreadyExistingFiles.get(i); DocumentFile alreadyExistingFile = alreadyExistingFiles.get(i);
int slot = getSlotFromSavegameFileName(alreadyExistingFile.getName()); int slot = getSlotFromSavegameFileName(alreadyExistingFile.getName());
FileHeader existingFileHeader = Savegames.quickload(context, slot); FileHeader existingFileHeader = Savegames.quickload(context, slot);
@@ -620,20 +678,26 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
} }
StringBuilder messageSb = new StringBuilder(); StringBuilder messageSb = new StringBuilder();
String existingFileDescription = getString(R.string.loadsave_import_existing_description, slot, existingFileHeader.describe()); String existingFileDescription = getString(R.string.loadsave_import_existing_description,
String importedFileDescription = getString(R.string.loadsave_import_imported_description, slot, importedFileHeader.describe()); Integer.toString(slot),
messageSb.append(getString(R.string.loadsave_import_file_exists_question, existingFileDescription, importedFileDescription)); existingFileHeader.describe());
String importedFileDescription = getString(R.string.loadsave_import_imported_description,
Integer.toString(slot),
importedFileHeader.describe());
messageSb.append(getString(R.string.loadsave_import_file_exists_question,
existingFileDescription,
importedFileDescription));
String m = messageSb.toString(); String m = messageSb.toString();
CustomDialog dialog = CustomDialogFactory.createDialog(this, CustomDialog dialog = CustomDialogFactory.createDialog(this,
title, title,
getResources().getDrawable(android.R.drawable.ic_dialog_alert), getResources().getDrawable(android.R.drawable.ic_dialog_alert),
m, m,
null, null,
true, true,
false, false,
true); true);
CustomDialogFactory.addButton(dialog, R.string.loadsave_import_option_keep_existing, v -> { CustomDialogFactory.addButton(dialog, R.string.loadsave_import_option_keep_existing, v -> {
//do nothing //do nothing
@@ -646,7 +710,8 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
}); });
CustomDialogFactory.addButton(dialog, R.string.loadsave_import_option_add_as_new, v -> { CustomDialogFactory.addButton(dialog, R.string.loadsave_import_option_add_as_new, v -> {
newFiles.add(null);//add a null element as marker to know later if the next file should be imported as new or overwrite the existing one newFiles.add(null);//add a null element as marker to know later if the next file
// should be imported as new or overwrite the existing one
newFiles.add(alreadyExistingFile); newFiles.add(alreadyExistingFile);
GoToNextConflictOrFinish(resolver, appSavegameFolder, newFiles, dialogs); GoToNextConflictOrFinish(resolver, appSavegameFolder, newFiles, dialogs);
}); });
@@ -662,13 +727,15 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
GoToNextConflictOrFinish(resolver, appSavegameFolder, newFiles, dialogs); GoToNextConflictOrFinish(resolver, appSavegameFolder, newFiles, dialogs);
} }
@RequiresApi(api = Build.VERSION_CODES.N) @RequiresApi(api = Build.VERSION_CODES.P)
private void GoToNextConflictOrFinish(ContentResolver resolver, DocumentFile appSavegameFolder, List<DocumentFile> newFiles, ArrayList<CustomDialog> dialogs) { private void GoToNextConflictOrFinish(ContentResolver resolver,
if(dialogs.stream().count() > 0){ DocumentFile appSavegameFolder,
List<DocumentFile> newFiles,
ArrayList<CustomDialog> dialogs) {
if (dialogs.stream().count() > 0) {
CustomDialog d = dialogs.remove(0); CustomDialog d = dialogs.remove(0);
CustomDialogFactory.show(d); CustomDialogFactory.show(d);
} } else {
else{
importSaveGames(resolver, appSavegameFolder, newFiles); importSaveGames(resolver, appSavegameFolder, newFiles);
} }
} }
@@ -677,8 +744,9 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
protected void onActivityResult(int requestCode, int resultCode, Intent data) { protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data);
if (resultCode != Activity.RESULT_OK) if (resultCode != Activity.RESULT_OK) {
return; return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
switch (-requestCode) { switch (-requestCode) {
@@ -713,48 +781,90 @@ public final class LoadSaveActivity extends AndorsTrailBaseActivity implements O
//region show Dialogs //region show Dialogs
private void showErrorImportingWorldmapWrongDirectory() { //region Import/Export
final CustomDialog d = CustomDialogFactory.createErrorDialog(this,
getString(R.string.loadsave_import_worldmap_unsuccessfull), @RequiresApi(api = Build.VERSION_CODES.P)
getString(R.string.loadsave_import_worldmap_wrong_directory)); private void showStartExportInfo(OnClickListener onOk) {
final CustomDialog d = CustomDialogFactory.createDialog(this,
getString(R.string.loadsave_export),
getResources().getDrawable(android.R.drawable.ic_dialog_info),
getString(R.string.loadsave_export_info),
null,
true);
CustomDialogFactory.addButton(d, android.R.string.yes, onOk);
CustomDialogFactory.addDismissButton(d, android.R.string.no);
CustomDialogFactory.show(d); CustomDialogFactory.show(d);
} }
private void showErrorImportingSaveGameUnknown() { @RequiresApi(api = Build.VERSION_CODES.P)
final CustomDialog d = CustomDialogFactory.createErrorDialog(this, private void showStartImportSavesInfo(OnClickListener onOk) {
getString(R.string.loadsave_import_save_unsuccessfull), final CustomDialog d = CustomDialogFactory.createDialog(this,
getString(R.string.loadsave_import_save_error_unknown)); getString(R.string.loadsave_import_save),
getResources().getDrawable(android.R.drawable.ic_dialog_info),
getString(R.string.loadsave_import_save_info),
null,
true);
CustomDialogFactory.addButton(d, android.R.string.yes, onOk);
CustomDialogFactory.addDismissButton(d, android.R.string.no);
CustomDialogFactory.show(d); CustomDialogFactory.show(d);
} }
@RequiresApi(api = Build.VERSION_CODES.P)
private void showStartImportWorldmapInfo(OnClickListener onOk) {
final CustomDialog d = CustomDialogFactory.createDialog(this,
getString(R.string.loadsave_import_worldmap),
getResources().getDrawable(android.R.drawable.ic_dialog_info),
getString(R.string.loadsave_import_worldmap_info),
null,
true);
CustomDialogFactory.addButton(d, android.R.string.yes, onOk);
CustomDialogFactory.addDismissButton(d, android.R.string.no);
CustomDialogFactory.show(d);
}
private void showErrorImportingWorldmapWrongDirectory() {
final CustomDialog d = CustomDialogFactory.createErrorDialog(this,
getString(R.string.loadsave_import_worldmap_unsuccessfull),
getString(R.string.loadsave_import_worldmap_wrong_file));
CustomDialogFactory.show(d);
}
private void showErrorExportingSaveGamesUnknown() {
final CustomDialog d = CustomDialogFactory.createErrorDialog(this,
getString(R.string.loadsave_export_unsuccessfull),
getString(R.string.loadsave_export_error_unknown));
CustomDialogFactory.show(d);
}
//endregion
private void showErrorLoadingEmptySlot() { private void showErrorLoadingEmptySlot() {
final CustomDialog d = CustomDialogFactory.createErrorDialog(this, final CustomDialog d = CustomDialogFactory.createErrorDialog(this,
getString(R.string.startscreen_error_loading_game), getString(R.string.startscreen_error_loading_game),
getString(R.string.startscreen_error_loading_empty_slot)); getString(R.string.startscreen_error_loading_empty_slot));
CustomDialogFactory.show(d); CustomDialogFactory.show(d);
} }
private void showSlotGetsDeletedOnLoadWarning(final int slot) { private void showSlotGetsDeletedOnLoadWarning(final int slot) {
final CustomDialog d = CustomDialogFactory.createDialog(this, final CustomDialog d = CustomDialogFactory.createDialog(this,
getString(R.string.startscreen_attention_slot_gets_delete_on_load), getString(R.string.startscreen_attention_slot_gets_delete_on_load),
getResources().getDrawable(android.R.drawable.ic_dialog_alert), getResources().getDrawable(android.R.drawable.ic_dialog_alert),
getString(R.string.startscreen_attention_message_slot_gets_delete_on_load), getString(R.string.startscreen_attention_message_slot_gets_delete_on_load),
null, null,
true); true);
CustomDialogFactory.addButton(d, android.R.string.ok, v -> completeLoadSaveActivity(slot)); CustomDialogFactory.addButton(d, android.R.string.ok, v -> completeLoadSaveActivity(slot));
CustomDialogFactory.show(d); CustomDialogFactory.show(d);
} }
private void showConfirmoverwriteQuestion(final int slot, String message) { private void showConfirmOverwriteQuestion(final int slot, String message) {
final String title = final String title = getString(R.string.loadsave_save_overwrite_confirmation_title) + ' '
getString(R.string.loadsave_save_overwrite_confirmation_title) + ' ' + getString(R.string.loadsave_save_overwrite_confirmation_slot, slot);
+ getString(R.string.loadsave_save_overwrite_confirmation_slot, slot);
final CustomDialog d = CustomDialogFactory.createDialog(this, final CustomDialog d = CustomDialogFactory.createDialog(this,
title, title,
getResources().getDrawable(android.R.drawable.ic_dialog_alert), getResources().getDrawable(android.R.drawable.ic_dialog_alert),
message, message,
null, null,
true); true);
CustomDialogFactory.addButton(d, android.R.string.yes, v -> completeLoadSaveActivity(slot)); CustomDialogFactory.addButton(d, android.R.string.yes, v -> completeLoadSaveActivity(slot));
CustomDialogFactory.addDismissButton(d, android.R.string.no); CustomDialogFactory.addDismissButton(d, android.R.string.no);

View File

@@ -6,14 +6,12 @@ import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Environment; import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi; import android.support.annotation.RequiresApi;
import android.support.v4.content.FileProvider; import android.support.v4.content.FileProvider;
import android.support.v4.provider.DocumentFile; import android.support.v4.provider.DocumentFile;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.webkit.MimeTypeMap;
import com.gpl.rpg.AndorsTrail.R; import com.gpl.rpg.AndorsTrail.R;
import com.gpl.rpg.AndorsTrail.controller.Constants; import com.gpl.rpg.AndorsTrail.controller.Constants;
@@ -22,19 +20,22 @@ import com.gpl.rpg.AndorsTrail.view.CustomDialogFactory;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.concurrent.CancellationException; import java.util.concurrent.CancellationException;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
public final class AndroidStorage { public final class AndroidStorage {
public static File getStorageDirectory(Context context, String name) { public static File getStorageDirectory(Context context, String name) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return context.getExternalFilesDir(name); return context.getExternalFilesDir(name);
} } else {
else {
File root = Environment.getExternalStorageDirectory(); File root = Environment.getExternalStorageDirectory();
return new File(root, name); return new File(root, name);
} }
@@ -42,16 +43,16 @@ public final class AndroidStorage {
public static boolean shouldMigrateToInternalStorage(Context context) { public static boolean shouldMigrateToInternalStorage(Context context) {
boolean ret = false; boolean ret = false;
File externalSaveGameDirectory = new File(Environment.getExternalStorageDirectory(), Constants.FILENAME_SAVEGAME_DIRECTORY); File externalSaveGameDirectory = new File(Environment.getExternalStorageDirectory(),
Constants.FILENAME_SAVEGAME_DIRECTORY);
File internalSaveGameDirectory = getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY); File internalSaveGameDirectory = getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY);
if (externalSaveGameDirectory.exists() if (externalSaveGameDirectory.exists()
&& externalSaveGameDirectory.isDirectory() && externalSaveGameDirectory.isDirectory()
&& externalSaveGameDirectory.listFiles().length > 0 && externalSaveGameDirectory.listFiles().length > 0
&& ( && (!internalSaveGameDirectory.exists()
!internalSaveGameDirectory.exists() || internalSaveGameDirectory.isDirectory()
|| internalSaveGameDirectory.isDirectory() && internalSaveGameDirectory.listFiles().length < 2) && internalSaveGameDirectory.listFiles().length < 2)) {
) {
ret = true; ret = true;
} }
return ret; return ret;
@@ -60,11 +61,11 @@ public final class AndroidStorage {
public static boolean migrateToInternalStorage(Context context) { public static boolean migrateToInternalStorage(Context context) {
try { try {
copy(new File(Environment.getExternalStorageDirectory(), Constants.CHEAT_DETECTION_FOLDER), copy(new File(Environment.getExternalStorageDirectory(), Constants.CHEAT_DETECTION_FOLDER),
getStorageDirectory(context, Constants.CHEAT_DETECTION_FOLDER)); getStorageDirectory(context, Constants.CHEAT_DETECTION_FOLDER));
copy(new File(Environment.getExternalStorageDirectory(), Constants.FILENAME_SAVEGAME_DIRECTORY), copy(new File(Environment.getExternalStorageDirectory(), Constants.FILENAME_SAVEGAME_DIRECTORY),
getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY)); getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY));
} catch (IOException e) { } catch (IOException e) {
L.log("Error migrating data: " + e.toString()); L.log("Error migrating data: " + e);
return false; return false;
} }
return true; return true;
@@ -105,54 +106,174 @@ public final class AndroidStorage {
} }
} }
@RequiresApi(api = Build.VERSION_CODES.P)
public static void createZipDocumentFileFromFilesAsync(File[] files,
Context context,
DocumentFile targetDirectory,
String fileName,
String loadingMessage,
Consumer<Boolean> callback) {
public static void copyDocumentFileToNewOrExistingFile(DocumentFile sourceFile, ContentResolver resolver, DocumentFile targetFolder) throws IOException { BackgroundWorker<Boolean> worker = new BackgroundWorker<>();
copyDocumentFileToNewOrExistingFile(sourceFile, resolver, targetFolder, Constants.NO_FILE_EXTENSION_MIME_TYPE); CustomDialogFactory.CustomDialog progressDialog = getLoadingDialog(context, loadingMessage);
progressDialog.setOnCancelListener(dialog -> worker.cancel());
ContentResolver resolver = context.getContentResolver();
Handler handler = Handler.createAsync(Looper.getMainLooper());
worker.setTask(workerCallback -> {
try {
workerCallback.onInitialize();
//region create zip file
File zip = File.createTempFile("temp_worldmap", ".zip");
try (OutputStream out = new FileOutputStream(zip)) {
ZipOutputStream zipOut = new ZipOutputStream(out);
for (int i = 0; i < files.length; i++) {
File file = files[i];
try (FileInputStream fis = new FileInputStream(file)) {
workerCallback.onProgress((float) i / files.length);
zipOut.putNextEntry(new ZipEntry(file.getName()));
copyStream(fis, zipOut);
zipOut.closeEntry();
}
}
zipOut.close();
}
//endregion
DocumentFile worldmapZip = DocumentFile.fromFile(zip);
DocumentFile worldmapTarget = targetDirectory.createFile("application/zip", fileName);
if (worldmapTarget != null && worldmapTarget.exists()) {
AndroidStorage.copyDocumentFile(worldmapZip, resolver, worldmapTarget);
workerCallback.onComplete(true);
} else {
throw new FileNotFoundException("Could not create File");
}
} catch (NullPointerException e) {
if (worker.isCancelled()) {
workerCallback.onFailure(new CancellationException("Cancelled"));
} else {
workerCallback.onFailure(e);
}
} catch (Exception e) {
workerCallback.onFailure(e);
}
});
worker.setCallback(getDefaultBackgroundWorkerCallback(handler, progressDialog, callback));
worker.run();
}
public static void unzipToDirectory(File zipFile,
File targetDirectory,
boolean overwriteNotSkip) throws IOException {
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile))) {
unzipStreamToDirectory(targetDirectory, overwriteNotSkip, zis);
}
}
@RequiresApi(api = Build.VERSION_CODES.P)
public static void unzipDocumentFileToDirectoryAsync(DocumentFile zipFile,
Context context,
File targetDirectory,
boolean overwriteNotSkip,
String loadingMessage,
Consumer<Boolean> callback) {
BackgroundWorker<Boolean> worker = new BackgroundWorker<>();
CustomDialogFactory.CustomDialog progressDialog = getLoadingDialog(context, loadingMessage);
progressDialog.setOnCancelListener(dialog -> worker.cancel());
ContentResolver resolver = context.getContentResolver();
Handler handler = Handler.createAsync(Looper.getMainLooper());
worker.setTask(workerCallback -> {
try {
workerCallback.onInitialize();
workerCallback.onProgress(-1);//set dummy progress since we don't know the
// progress of the unzip
unzipDocumentFileToDirectory(zipFile, resolver, targetDirectory, overwriteNotSkip);
workerCallback.onComplete(true);
} catch (IOException e) {
workerCallback.onFailure(e);
}
});
worker.setCallback(getDefaultBackgroundWorkerCallback(handler, progressDialog, callback));
worker.run();
}
public static void unzipDocumentFileToDirectory(DocumentFile zipFile,
ContentResolver resolver,
File targetDirectory,
boolean overwriteNotSkip) throws IOException {
try (ZipInputStream zis = new ZipInputStream(resolver.openInputStream(zipFile.getUri()))) {
unzipStreamToDirectory(targetDirectory, overwriteNotSkip, zis);
}
}
private static void unzipStreamToDirectory(File targetDirectory,
boolean overwriteNotSkip,
ZipInputStream zis) throws IOException {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
File file = new File(targetDirectory, entry.getName());
if (entry.isDirectory()) {
file.mkdirs();
} else {
file.getParentFile().mkdirs();
if (file.exists() && !overwriteNotSkip) {
continue;
}
try (FileOutputStream fos = new FileOutputStream(file)) {
copyStream(zis, fos);
}
}
}
}
public static void copyDocumentFileToNewOrExistingFile(DocumentFile sourceFile,
ContentResolver resolver,
DocumentFile targetFolder) throws IOException {
copyDocumentFileToNewOrExistingFile(sourceFile,
resolver,
targetFolder,
Constants.NO_FILE_EXTENSION_MIME_TYPE);
} }
public static void copyDocumentFileToNewOrExistingFile(DocumentFile sourceFile, ContentResolver resolver, DocumentFile targetFolder, String mimeType) throws IOException { public static void copyDocumentFileToNewOrExistingFile(DocumentFile sourceFile,
ContentResolver resolver,
DocumentFile targetFolder,
String mimeType) throws IOException {
String fileName = sourceFile.getName(); String fileName = sourceFile.getName();
DocumentFile file = targetFolder.findFile(fileName); DocumentFile file = targetFolder.findFile(fileName);
if (file == null) if (file == null) {
file = targetFolder.createFile(mimeType, fileName); file = targetFolder.createFile(mimeType, fileName);
if (file == null) }
if (file == null) {
return; return;
}
AndroidStorage.copyDocumentFile(sourceFile, resolver, file); AndroidStorage.copyDocumentFile(sourceFile, resolver, file);
} }
public static void copyDocumentFile(DocumentFile sourceFile, ContentResolver resolver, DocumentFile targetFile) throws IOException { public static void copyDocumentFile(DocumentFile sourceFile,
ContentResolver resolver,
DocumentFile targetFile) throws IOException {
try (OutputStream outputStream = resolver.openOutputStream(targetFile.getUri()); try (OutputStream outputStream = resolver.openOutputStream(targetFile.getUri());
InputStream inputStream = resolver.openInputStream(sourceFile.getUri())) { InputStream inputStream = resolver.openInputStream(sourceFile.getUri())) {
copyStream(inputStream, outputStream); copyStream(inputStream, outputStream);
} }
} }
/**
* Gets the MIME-Type for a file.<p/>
* Fallback value is '* / *' (without spaces) <p/>
* Mostly copied together from: <a href="https://stackoverflow.com/q/8589645/17292289">StackOverflow</a>
*/
@NonNull
public static String getMimeType(ContentResolver resolver, Uri uri) {
String type = null;
if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
type = resolver.getType(uri);
return type;
}
final String extension = MimeTypeMap.getFileExtensionFromUrl(uri.getPath());
if (extension != null) {
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase());
}
if (type == null) {
type = "*/*"; // fallback type.
}
return type;
}
public static String getUrlForFile(Context context, File worldmap) { public static String getUrlForFile(Context context, File worldmap) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
String applicationId = context.getPackageName(); String applicationId = context.getPackageName();
@@ -163,11 +284,9 @@ public final class AndroidStorage {
} }
} }
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public static Intent getNewOpenDirectoryIntent() { public static Intent getNewOpenDirectoryIntent() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); return new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
return intent;
} }
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@@ -179,50 +298,59 @@ public final class AndroidStorage {
return intent; return intent;
} }
public static void copyDocumentFilesFromToAsync(DocumentFile[] sources, Context context, DocumentFile[] targets, Consumer<Boolean> callback) { @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
if(sources.length != targets.length) public static Intent getNewSelectZipIntent() {
{ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("application/zip");
return intent;
}
@RequiresApi(api = Build.VERSION_CODES.P)
public static void copyDocumentFilesFromToAsync(DocumentFile[] sources,
Context context,
DocumentFile[] targets,
String loadingMessage,
Consumer<Boolean> callback) {
if (sources.length != targets.length) {
throw new IllegalArgumentException("Both arrays, target & source have to have the same size"); throw new IllegalArgumentException("Both arrays, target & source have to have the same size");
} }
BackgroundWorker worker = new BackgroundWorker(); BackgroundWorker<Boolean> worker = new BackgroundWorker<>();
CustomDialogFactory.CustomDialog progressDialog = getLoadingDialog(context);
CustomDialogFactory.CustomDialog progressDialog = getLoadingDialog(context, loadingMessage);
progressDialog.setOnCancelListener(dialog -> worker.cancel()); progressDialog.setOnCancelListener(dialog -> worker.cancel());
ContentResolver resolver = context.getContentResolver(); ContentResolver resolver = context.getContentResolver();
Handler handler = Handler.createAsync(Looper.getMainLooper()); Handler handler = Handler.createAsync(Looper.getMainLooper());
worker.setTask(new BackgroundWorker.worker() { worker.setTask(workerCallback -> {
@Override try {
public void doWork(BackgroundWorkerCallback callback) { workerCallback.onInitialize();
try { for (int i = 0; i < sources.length; i++) {
callback.onInitialize();
for (int i = 0; i < sources.length ; i++) {
if (worker.isCancelled()) {
callback.onFailure(new CancellationException("Cancelled"));
return;
}
DocumentFile source = sources[i];
DocumentFile target = targets[i];
if(source == null || target == null)
{
continue;
}
copyDocumentFile(source, resolver,target);
float progress = i /(float) sources.length;
callback.onProgress(progress);
}
callback.onComplete(true);
} catch (NullPointerException e) {
if (worker.isCancelled()) { if (worker.isCancelled()) {
callback.onFailure(new CancellationException("Cancelled")); workerCallback.onFailure(new CancellationException("Cancelled"));
return; return;
} }
} catch (Exception e) { DocumentFile source = sources[i];
callback.onFailure(e); DocumentFile target = targets[i];
if (source == null || target == null) {
continue;
}
copyDocumentFile(source, resolver, target);
float progress = i / (float) sources.length;
workerCallback.onProgress(progress);
} }
workerCallback.onComplete(true);
} catch (NullPointerException e) {
if (worker.isCancelled()) {
workerCallback.onFailure(new CancellationException("Cancelled"));
return;
}
} catch (Exception e) {
workerCallback.onFailure(e);
} }
}); });
worker.setCallback(getDefaultBackgroundWorkerCallback(handler, progressDialog, callback)); worker.setCallback(getDefaultBackgroundWorkerCallback(handler, progressDialog, callback));
@@ -233,9 +361,10 @@ public final class AndroidStorage {
public static void copyDocumentFilesToDirAsync(DocumentFile[] files, public static void copyDocumentFilesToDirAsync(DocumentFile[] files,
Context context, Context context,
DocumentFile targetDirectory, DocumentFile targetDirectory,
String loadingMessage,
Consumer<Boolean> callback) { Consumer<Boolean> callback) {
BackgroundWorker<Boolean> worker = new BackgroundWorker<>(); BackgroundWorker<Boolean> worker = new BackgroundWorker<>();
CustomDialogFactory.CustomDialog progressDialog = getLoadingDialog(context); CustomDialogFactory.CustomDialog progressDialog = getLoadingDialog(context, loadingMessage);
progressDialog.setOnCancelListener(dialog -> worker.cancel()); progressDialog.setOnCancelListener(dialog -> worker.cancel());
ContentResolver resolver = context.getContentResolver(); ContentResolver resolver = context.getContentResolver();
Handler handler = Handler.createAsync(Looper.getMainLooper()); Handler handler = Handler.createAsync(Looper.getMainLooper());
@@ -249,11 +378,12 @@ public final class AndroidStorage {
return; return;
} }
DocumentFile file = files[i]; DocumentFile file = files[i];
if(file == null) if (file == null) {
continue; continue;
}
copyDocumentFileToNewOrExistingFile(file, resolver, targetDirectory); copyDocumentFileToNewOrExistingFile(file, resolver, targetDirectory);
float progress = i /(float) files.length; float progress = i / (float) files.length;
workerCallback.onProgress(progress); workerCallback.onProgress(progress);
} }
workerCallback.onComplete(true); workerCallback.onComplete(true);
@@ -270,10 +400,11 @@ public final class AndroidStorage {
} }
private static BackgroundWorkerCallback<Boolean> getDefaultBackgroundWorkerCallback(Handler handler, private static BackgroundWorkerCallback<Boolean> getDefaultBackgroundWorkerCallback(Handler handler,
CustomDialogFactory.CustomDialog progressDialog, CustomDialogFactory.CustomDialog progressDialog,
Consumer<Boolean> callback) { Consumer<Boolean> callback) {
return new BackgroundWorkerCallback<Boolean>() { return new BackgroundWorkerCallback<Boolean>() {
private int progress = -1; private int progress = -1;
@Override @Override
public void onInitialize() { public void onInitialize() {
handler.post(() -> { handler.post(() -> {
@@ -285,10 +416,17 @@ public final class AndroidStorage {
public void onProgress(float progress) { public void onProgress(float progress) {
handler.post(() -> { handler.post(() -> {
int intProgress = (int) (progress * 100); int intProgress = (int) (progress * 100);
if(this.progress == intProgress) if (this.progress == intProgress) {
return; return;
}
this.progress = intProgress; this.progress = intProgress;
if (progress == -1) {
CustomDialogFactory.setDesc(progressDialog, null);
return;
}
CustomDialogFactory.setDesc(progressDialog, intProgress + "%"); CustomDialogFactory.setDesc(progressDialog, intProgress + "%");
}); });
} }
@@ -296,10 +434,7 @@ public final class AndroidStorage {
@RequiresApi(api = Build.VERSION_CODES.N) @RequiresApi(api = Build.VERSION_CODES.N)
@Override @Override
public void onFailure(Exception e) { public void onFailure(Exception e) {
handler.post(() -> { this.onComplete(false);
progressDialog.dismiss();
callback.accept(false);
});
} }
@RequiresApi(api = Build.VERSION_CODES.N) @RequiresApi(api = Build.VERSION_CODES.N)
@@ -307,20 +442,31 @@ public final class AndroidStorage {
public void onComplete(Boolean result) { public void onComplete(Boolean result) {
handler.post(() -> { handler.post(() -> {
progressDialog.dismiss(); progressDialog.dismiss();
callback.accept(true); callback.accept(result);
}); });
} }
}; };
} }
private static CustomDialogFactory.CustomDialog getLoadingDialog(Context context) { private static CustomDialogFactory.CustomDialog getLoadingDialog(Context context) {
return CustomDialogFactory.createDialog(context, return getLoadingDialog(context, null);
context.getResources().getString(R.string.dialog_loading_message), }
context.getResources().getDrawable(R.drawable.loading_anim),
null, private static CustomDialogFactory.CustomDialog getLoadingDialog(Context context, String message) {
null, if (message == null) {
false, message = context.getResources().getString(R.string.dialog_loading_message);
false); }
CustomDialogFactory.CustomDialog dialog = CustomDialogFactory.createDialog(context,
message,
context.getResources()
.getDrawable(R.drawable.loading_anim),
null,
null,
true,
false);
CustomDialogFactory.addCancelButton(dialog, android.R.string.no);
return dialog;
} }
} }

View File

@@ -4,14 +4,14 @@ import java.util.concurrent.Executors;
public final class BackgroundWorker<T> { public final class BackgroundWorker<T> {
boolean cancelled = false; boolean cancelled = false;
worker task; worker<T> task;
BackgroundWorkerCallback callback; BackgroundWorkerCallback<T> callback;
public void setTask(worker task) { public void setTask(worker<T> task) {
this.task = task; this.task = task;
} }
public void setCallback(BackgroundWorkerCallback callback) { public void setCallback(BackgroundWorkerCallback<T> callback) {
this.callback = callback; this.callback = callback;
} }
@@ -20,7 +20,7 @@ public final class BackgroundWorker<T> {
} }
interface worker<T> { interface worker<T> {
void doWork(BackgroundWorkerCallback callback); void doWork(BackgroundWorkerCallback<T> callback);
} }
interface BackgroundWorkerCallback<T> { interface BackgroundWorkerCallback<T> {

View File

@@ -485,31 +485,40 @@
<string name="skill_number_of_increases_several">You may select %1$d skills to improve.</string> <string name="skill_number_of_increases_several">You may select %1$d skills to improve.</string>
<string name="levelup_adds_new_skillpoint">This level also gives you a new skill point to spend!</string> <string name="levelup_adds_new_skillpoint">This level also gives you a new skill point to spend!</string>
<string name="loadsave_save_to_new_slot">Create new savegame slot</string> <string name="loadsave_save_to_new_slot">Create new Savegame slot</string>
<string name="loadsave_save_overwrite_confirmation_title">Overwrite savegame?</string> <string name="loadsave_save_overwrite_confirmation_title">Overwrite Savegame?</string>
<string name="loadsave_save_overwrite_confirmation">This savegame contains a different player name (%1$s) than your current player name (%2$s). Are you sure you want to overwrite this savegame?</string> <string name="loadsave_save_overwrite_confirmation">This Savegame contains a different player name (%1$s) than your current player name (%2$s). Are you sure you want to overwrite this savegame?</string>
<string name="loadsave_export">Export savegames</string> <string name="loadsave_export">Export Savegames</string>
<string name="loadsave_export_info">Please select the directory to export all files to.</string>
<string name="loadsave_exporting_savegames">Exporting Savegames</string>
<string name="loadsave_exporting_worldmap">Exporting Worldmap</string>
<string name="loadsave_export_successfull">Export successful</string> <string name="loadsave_export_successfull">Export successful</string>
<string name="loadsave_export_unsuccessfull">Export unsuccessful</string> <string name="loadsave_export_unsuccessfull">Export unsuccessful</string>
<string name="loadsave_export_overwrite_confirmation_title">Overwrite Existing Files?</string> <string name="loadsave_export_overwrite_confirmation_title">Overwrite Existing Files?</string>
<string name="loadsave_export_overwrite_confirmation">The target Folder contains existing files with the same name of some Files that should be exported. Are you sure you want to overwrite those files?</string> <string name="loadsave_export_overwrite_confirmation">The target Folder contains existing files with the same name of some Files that should be exported. Are you sure you want to overwrite those files?</string>
<string name="loadsave_import_save">Import savegames</string> <string name="loadsave_export_error_unknown">An unknown error occurred while exporting.</string>
<string name="loadsave_import_save">Import Savegames</string>
<string name="loadsave_import_save_info">Please select all Savegames you want to import.</string>
<string name="loadsave_importing_savegames">Importing Savegames</string>
<string name="loadsave_import_save_successfull">Import successful</string> <string name="loadsave_import_save_successfull">Import successful</string>
<string name="loadsave_import_save_unsuccessfull">Import unsuccessful</string> <string name="loadsave_import_save_unsuccessfull">Import unsuccessful</string>
<string name="loadsave_import_save_error_unknown">An unknown error occurred while importing.</string> <string name="loadsave_import_error_unknown">An unknown error occurred while importing.</string>
<string name="loadsave_import_overwrite_confirmation_title">Overwrite Existing Slot?</string> <string name="loadsave_import_overwrite_confirmation_title">Overwrite Existing Slot?</string>
<string name="loadsave_import_file_exists_question">Theere is already a savegame in the target slot. Do you want to keep the existing save, overwrite it with the imported save or import the save to a slot?\n\n%1$s\n\n%2$s</string> <string name="loadsave_import_file_exists_question">There is already a savegame in the target slot. Do you want to keep the existing save, overwrite it with the imported save or import the save to a slot?\n\n%1$s\n\n%2$s</string>
<string name="loadsave_import_option_keep_existing">Keep existing save</string> <string name="loadsave_import_option_keep_existing">Keep existing save</string>
<string name="loadsave_import_option_keep_imported">Keep imported save</string> <string name="loadsave_import_option_keep_imported">Keep imported save</string>
<string name="loadsave_import_option_add_as_new">Add as new Save</string> <string name="loadsave_import_option_add_as_new">Add as new Save</string>
<string name="loadsave_import_existing_description">Existing save: Slot: %1$s:\n\t%2$s</string> <string name="loadsave_import_existing_description">Existing save: Slot: %1$s:\n\t%2$s</string>
<string name="loadsave_import_imported_description">Imported save: Slot: %1$s:\n\t%2$s</string> <string name="loadsave_import_imported_description">Imported save: Slot: %1$s:\n\t%2$s</string>
<string name="loadsave_import_worldmap">Import worldmap</string> <string name="loadsave_import_worldmap">Import worldmap</string>
<string name="loadsave_import_worldmap_info">Please select the Worldmap zip file.</string>
<string name="loadsave_importing_worldmap">Importing worldmap</string>
<string name="loadsave_import_worldmap_successfull">Import of Worldmap successful</string> <string name="loadsave_import_worldmap_successfull">Import of Worldmap successful</string>
<string name="loadsave_import_worldmap_unsuccessfull">Import of Worldmap unsuccessful</string> <string name="loadsave_import_worldmap_unsuccessfull">Import of Worldmap unsuccessful</string>
<string name="loadsave_import_worldmap_wrong_directory">Are you sure there is a worldmap in this folder? Please select the folder called \'worldmap\' inside your export location.</string> <string name="loadsave_import_worldmap_wrong_file">Are you sure you selected a worldmap? Please select the zip file called \'worldmap.zip\' that was exported into your export location.</string>
<string name="iteminfo_displaytypes_ordinary">Ordinary</string> <string name="iteminfo_displaytypes_ordinary">Ordinary</string>
<string name="iteminfo_displaytypes_quest">Quest item</string> <string name="iteminfo_displaytypes_quest">Quest item</string>